Add prettier dependency to format frontend code

This commit is contained in:
2026-03-04 15:37:59 +01:00
parent 2d97aa85c4
commit af44849eda
31 changed files with 1166 additions and 1070 deletions

1
front/.prettierignore Normal file
View File

@@ -0,0 +1 @@
public

View File

@@ -1,10 +1,10 @@
<!DOCTYPE html>
<!doctype html>
<html lang="">
<head>
<meta charset="UTF-8">
<link rel="icon" href="/favicon.ico">
<link rel="stylesheet" href="/css/bulma.min.css">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" />
<link rel="stylesheet" href="/css/bulma.min.css" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Personal Library Manager</title>
</head>
<body>

View File

@@ -4,33 +4,31 @@
import { useRouter } from 'vue-router'
import CoverUpload from './CoverUpload.vue'
const router = useRouter();
const router = useRouter()
const book = ref({
title: "",
author: "",
coverId: null
});
title: '',
author: '',
coverId: null,
})
const errors = ref(null)
const titleError = computed(() => {
return extractFormErrorFromField("Title", errors.value);
return extractFormErrorFromField('Title', errors.value)
})
const authorError = computed(() => {
return extractFormErrorFromField("Author", errors.value);
return extractFormErrorFromField('Author', errors.value)
})
function onSubmit(e) {
postBook(book)
.then((res) => {
postBook(book).then((res) => {
if (res.ok) {
router.push('/');
return;
router.push('/')
return
} else {
res.json().then((json) => (errors.value = json));
res.json().then((json) => (errors.value = json))
}
})
}
</script>
<template>
@@ -38,20 +36,31 @@
<div class="field">
<label class="label">{{ $t('addbook.title') }}</label>
<div class="control">
<input :class="'input ' + (titleError ? 'is-danger' : '')" type="text" maxlength="300"
required v-model="book.title" :placeholder="$t('addbook.title')">
<input
:class="'input ' + (titleError ? 'is-danger' : '')"
type="text"
maxlength="300"
required
v-model="book.title"
:placeholder="$t('addbook.title')"
/>
</div>
<p v-if="titleError" class="help is-danger">{{ titleError }}</p>
</div>
<div class="field">
<label class="label">{{ $t('addbook.author') }}</label>
<div class="control">
<input :class="'input ' + (authorError ? 'is-danger' : '')" type="text" maxlength="100"
v-model="book.author" :placeholder="$t('addbook.author')">
<input
:class="'input ' + (authorError ? 'is-danger' : '')"
type="text"
maxlength="100"
v-model="book.author"
:placeholder="$t('addbook.author')"
/>
</div>
<p v-if="authorError" class="help is-danger">{{ authorError }}</p>
</div>
<CoverUpload name="cover" @on-image-upload="(id) => book.coverId = id"/>
<CoverUpload name="cover" @on-image-upload="(id) => (book.coverId = id)" />
<div class="field">
<div class="control">
<button class="button is-link">{{ $t('addbook.submit') }}</button>

View File

@@ -7,58 +7,66 @@
import { getAppInfo, postLogin } from './api.js'
import { onMounted } from 'vue'
const authStore = useAuthStore();
const router = useRouter();
const authStore = useAuthStore()
const router = useRouter()
const isMenuActive = ref(false)
const isSearchBarShown = ref(false)
function logout() {
authStore.logout();
router.push('/');
authStore.logout()
router.push('/')
}
const appInfo = ref(null);
const appInfoErr = ref(null);
const appInfo = ref(null)
const appInfoErr = ref(null)
async function logInIfDemoMode(demoMode, demoUsername) {
if (!demoMode) {
return;
return
}
const demouser = ref({
username: demoUsername,
password: ""
});
password: '',
})
const res = await postLogin(demouser)
const json = await res.json();
await useAuthStore().login({username: demouser.value.username, token: json["token"]})
const json = await res.json()
await useAuthStore().login({ username: demouser.value.username, token: json['token'] })
}
onMounted(() => {
getAppInfo(appInfo, appInfoErr)
.then(() => logInIfDemoMode(appInfo.value.demoMode, appInfo.value.demoUsername));
getAppInfo(appInfo, appInfoErr).then(() =>
logInIfDemoMode(appInfo.value.demoMode, appInfo.value.demoUsername),
)
})
</script>
<template>
<nav class="navbar">
<div class="navbar-brand">
<RouterLink to="/" class="navbar-item" activeClass="is-active">
PLM
</RouterLink>
<RouterLink to="/" class="navbar-item" activeClass="is-active"> PLM </RouterLink>
<div class="navbar-item is-hidden-desktop">
<a @click="isSearchBarShown = !isSearchBarShown"
class="button is-medium" :class="isSearchBarShown ? 'is-active' : '' ">
<a
@click="isSearchBarShown = !isSearchBarShown"
class="button is-medium"
:class="isSearchBarShown ? 'is-active' : ''"
>
<span class="icon" :title="$t('navbar.search')">
<b-icon-search />
</span>
</a>
<BarcodeModal size-class="is-medium" />
</div>
<a v-if="authStore.user" role="button" class="navbar-burger" aria-label="menu"
:class="isMenuActive ? 'is-active' : '' " :aria-expanded="isMenuActive"
@click="isMenuActive = !isMenuActive">
<a
v-if="authStore.user"
role="button"
class="navbar-burger"
aria-label="menu"
:class="isMenuActive ? 'is-active' : ''"
:aria-expanded="isMenuActive"
@click="isMenuActive = !isMenuActive"
>
<span aria-hidden="true"></span>
<span aria-hidden="true"></span>
<span aria-hidden="true"></span>
@@ -77,7 +85,10 @@
<RouterLink v-if="authStore.user" to="/add" class="navbar-item" activeClass="is-active">
{{ $t('navbar.addbook') }}
</RouterLink>
<div v-if="authStore.user && appInfo && !appInfo.demoMode" class="navbar-item is-hidden-desktop">
<div
v-if="authStore.user && appInfo && !appInfo.demoMode"
class="navbar-item is-hidden-desktop"
>
<a @click="logout">
{{ $t('navbar.logout') }}
<span class="icon" :title="$t('navbar.logout')">
@@ -97,7 +108,11 @@
</div>
<div v-else class="navbar-item">
<div class="buttons">
<RouterLink v-if="appInfo && !appInfo.registrationDisabled" to="/signup" class="button is-primary">
<RouterLink
v-if="appInfo && !appInfo.registrationDisabled"
to="/signup"
class="button is-primary"
>
<strong>{{ $t('navbar.signup') }}</strong>
</RouterLink>
<RouterLink to="/login" class="button is-light">
@@ -107,7 +122,6 @@
</div>
</div>
</div>
</nav>
</template>

View File

@@ -4,18 +4,17 @@
import { onBeforeRouteUpdate } from 'vue-router'
import SearchBook from './SearchBook.vue'
const props = defineProps({
id: String
});
id: String,
})
let author = ref(null);
let authorfetcherror = ref(null);
let author = ref(null)
let authorfetcherror = ref(null)
getAuthor(author, authorfetcherror, props.id);
getAuthor(author, authorfetcherror, props.id)
onBeforeRouteUpdate(async (to, from) => {
getAuthor(author, authorfetcherror, to.params.id);
getAuthor(author, authorfetcherror, to.params.id)
})
</script>
@@ -30,5 +29,4 @@
</div>
</template>
<style scoped>
</style>
<style scoped></style>

View File

@@ -4,14 +4,14 @@ import { ref } from 'vue'
import { useRouter } from 'vue-router'
const props = defineProps({
sizeClass: String
});
sizeClass: String,
})
const open = ref(false);
const router = useRouter();
const open = ref(false)
const router = useRouter()
function onBarcodeDecode(isbn) {
open.value = false;
open.value = false
router.push('/search/' + isbn)
}
</script>

View File

@@ -5,36 +5,40 @@
icon: String,
legend: String,
isSet: Boolean,
isReadonly: Boolean
});
isReadonly: Boolean,
})
const hovered = ref(false)
const isOnMobile = ref(computeIsOnMobile())
const computedIcon = computed(() => props.icon +
(!props.isReadonly && ((hovered.value && !isOnMobile.value) || props.isSet) ? "Fill" : ""));
const computedIcon = computed(
() =>
props.icon +
(!props.isReadonly && ((hovered.value && !isOnMobile.value) || props.isSet) ? 'Fill' : ''),
)
function computeIsOnMobile() {
return window.innerWidth < 1024;
return window.innerWidth < 1024
}
function updateOnMobile() {
isOnMobile.value = computeIsOnMobile();
isOnMobile.value = computeIsOnMobile()
}
onMounted(() => {
window.addEventListener("resize", updateOnMobile);
});
window.addEventListener('resize', updateOnMobile)
})
onUnmounted(() => {
window.removeEventListener("resize", updateOnMobile);
});
window.removeEventListener('resize', updateOnMobile)
})
</script>
<template>
<div class="bigiconandlegend"
<div
class="bigiconandlegend"
:class="props.isReadonly ? '' : 'showcanclick'"
@mouseover="hovered = true"
@mouseout="hovered = false">
@mouseout="hovered = false"
>
<span class="bigicon" :title="props.legend">
<component :is="computedIcon"></component>
</span>
@@ -43,7 +47,6 @@
</template>
<style scoped>
.bigiconandlegend {
border-radius: 30px;
margin: 25px;

View File

@@ -2,7 +2,7 @@
import { computed } from 'vue'
import { useRouter } from 'vue-router'
import { getImagePathOrDefault } from './api.js'
import { VRating } from 'vuetify/components/VRating';
import { VRating } from 'vuetify/components/VRating'
const props = defineProps({
id: Number,
@@ -10,15 +10,14 @@
author: String,
coverPath: String,
rating: Number,
read: Boolean
});
const imagePathOrDefault = computed(() => getImagePathOrDefault(props.coverPath));
const router = useRouter();
read: Boolean,
})
const imagePathOrDefault = computed(() => getImagePathOrDefault(props.coverPath))
const router = useRouter()
function openBook() {
router.push(`/book/${props.id}`)
}
</script>
<template>
@@ -26,14 +25,15 @@ function openBook() {
<div class="media" @click="openBook">
<div class="media-left">
<figure class="image mb-3">
<img v-bind:src="imagePathOrDefault" v-bind:alt="title">
<img v-bind:src="imagePathOrDefault" v-bind:alt="title" />
</figure>
</div>
<div class="media-content">
<div class="content">
<div class="is-size-5">{{ title }}</div>
<div class="is-size-5 is-italic">{{ author }}</div>
<VRating v-if="rating > 0"
<VRating
v-if="rating > 0"
half-increments
readonly
:length="5"
@@ -64,5 +64,4 @@ img {
transform: scale(1.01);
transition: ease-in-out 0.02s;
}
</style>

View File

@@ -1,6 +1,6 @@
<script setup>
import { ref } from 'vue'
import BigIcon from './BigIcon.vue';
import BigIcon from './BigIcon.vue'
const props = defineProps({
icon: String,
@@ -10,58 +10,63 @@
isExpanded: Boolean,
isReadonly: Boolean,
useEndDate: Boolean,
lastWidget: Boolean
});
lastWidget: Boolean,
})
defineEmits(['onIconClick', 'onStartDateChange', 'onEndDateChange'])
const today = new Date().toISOString().slice(0, 10);
const today = new Date().toISOString().slice(0, 10)
function computeParentClasses() {
let classNames = "bookdatewidget";
let classNames = 'bookdatewidget'
if (props.isExpanded) {
classNames += " has-text-dark has-background-text";
classNames += ' has-text-dark has-background-text'
}
if (props.lastWidget) {
classNames += " border-radius-right-and-left";
classNames += ' border-radius-right-and-left'
} else {
classNames += " border-radius-right";
classNames += ' border-radius-right'
}
if (props.isReadonly) {
classNames += " widget-readonly"
classNames += ' widget-readonly'
}
return classNames;
return classNames
}
</script>
<template>
<div :class="computeParentClasses()">
<BigIcon :icon="props.icon"
<BigIcon
:icon="props.icon"
:is-readonly="props.isReadonly"
:legend="props.legend"
:isSet="props.isExpanded"
@click="props.isReadonly ? null : $emit('onIconClick') "/>
@click="props.isReadonly ? null : $emit('onIconClick')"
/>
<div v-if="props.isExpanded" class="inputdate">
<div class="ontopofinput">
<label class="datelabel" for="startread">
{{ $t('bookdatewidget.started') }}
</label>
<input class="datepicker has-background-dark has-text-light"
<input
class="datepicker has-background-dark has-text-light"
id="startread"
type="date"
@change="(e) => $emit('onStartDateChange', e.target.value)"
:value="props.startReadDate"
:max="today"/>
:max="today"
/>
<div v-if="props.useEndDate">
<label class="datelabel" for="endread">
{{ $t('bookdatewidget.finished') }}
</label>
<input class="datepicker has-background-dark has-text-light"
<input
class="datepicker has-background-dark has-text-light"
id="endread"
type="date"
@change="(e) => $emit('onEndDateChange', e.target.value)"
:value="props.endReadDate"
:max="today"/>
:max="today"
/>
</div>
</div>
</div>
@@ -69,7 +74,6 @@
</template>
<style scoped>
.inputdate {
display: flex;
justify-content: center;

View File

@@ -1,92 +1,99 @@
<script setup>
import { ref, computed } from 'vue'
import { getBook, getImagePathOrDefault, putReadBook, putWantReadBook, putRateBook,
putStartReadDate, putStartReadDateUnset, putEndReadDate, putEndReadDateUnset, putUnreadBook } from './api.js'
import {
getBook,
getImagePathOrDefault,
putReadBook,
putWantReadBook,
putRateBook,
putStartReadDate,
putStartReadDateUnset,
putEndReadDate,
putEndReadDateUnset,
putUnreadBook,
} from './api.js'
import { useRouter, onBeforeRouteUpdate } from 'vue-router'
import { VRating } from 'vuetify/components/VRating';
import BigIcon from './BigIcon.vue';
import BookDateWidget from './BookDateWidget.vue';
import { VRating } from 'vuetify/components/VRating'
import BigIcon from './BigIcon.vue'
import BookDateWidget from './BookDateWidget.vue'
const router = useRouter();
const router = useRouter()
const props = defineProps({
id: String
});
const today = new Date().toISOString().slice(0, 10);
id: String,
})
const today = new Date().toISOString().slice(0, 10)
let data = ref(null);
let error = ref(null);
getBook(data, error, props.id);
const imagePathOrDefault = computed(() => getImagePathOrDefault(data.value.coverPath));
let data = ref(null)
let error = ref(null)
getBook(data, error, props.id)
const imagePathOrDefault = computed(() => getImagePathOrDefault(data.value.coverPath))
onBeforeRouteUpdate(async (to, from) => {
getBook(data, error, to.params.id);
getBook(data, error, to.params.id)
})
function onRatingUpdate(rating) {
data.value.rating = rating * 2;
data.value.rating = rating * 2
if (data.value.rating > 0) {
data.value.read = true;
data.value.wantread = false;
data.value.read = true
data.value.wantread = false
}
putRateBook(props.id, {rating: data.value.rating});
putRateBook(props.id, { rating: data.value.rating })
}
function onReadIconClick() {
data.value.read = !data.value.read;
data.value.read = !data.value.read
if (data.value.read) {
data.value.wantread = false;
data.value.endReadDate = today;
putEndReadDate(props.id, today);
data.value.wantread = false
data.value.endReadDate = today
putEndReadDate(props.id, today)
} else {
putUnreadBook(props.id);
putUnreadBook(props.id)
}
}
function onWantReadIconClick() {
data.value.wantread = !data.value.wantread;
putWantReadBook(props.id, {wantread: data.value.wantread});
data.value.wantread = !data.value.wantread
putWantReadBook(props.id, { wantread: data.value.wantread })
}
function onStartReadIconClick() {
if (!data.value.startReadDate) {
data.value.startReadDate = today;
putStartReadDate(props.id, data.value.startReadDate);
data.value.startReadDate = today
putStartReadDate(props.id, data.value.startReadDate)
} else {
data.value.startReadDate = null;
putStartReadDateUnset(props.id);
data.value.startReadDate = null
putStartReadDateUnset(props.id)
}
}
function onStartReadDateChange(d) {
data.value.startReadDate = d;
if (d != "") {
putStartReadDate(props.id, data.value.startReadDate);
data.value.startReadDate = d
if (d != '') {
putStartReadDate(props.id, data.value.startReadDate)
} else {
putStartReadDateUnset(props.id);
putStartReadDateUnset(props.id)
}
}
function onEndReadDateChange(d) {
data.value.endReadDate = d;
if (d != "") {
putEndReadDate(props.id, data.value.endReadDate);
data.value.endReadDate = d
if (d != '') {
putEndReadDate(props.id, data.value.endReadDate)
} else {
putEndReadDateUnset(props.id);
putEndReadDateUnset(props.id)
}
}
function isStartReadExpanded() {
let isStartReadDateSet = data.value.startReadDate ? true : false;
let isReadUnset = !data.value.read ? true : false;
return isStartReadDateSet && isReadUnset;
let isStartReadDateSet = data.value.startReadDate ? true : false
let isReadUnset = !data.value.read ? true : false
return isStartReadDateSet && isReadUnset
}
function goToAuthor() {
router.push("/author/" + data.value.authorId)
router.push('/author/' + data.value.authorId)
}
</script>
<template>
@@ -94,7 +101,7 @@
<div v-if="data" class="columns">
<div class="column is-narrow left-panel">
<figure class="image">
<img v-bind:src="imagePathOrDefault" v-bind:alt="data.title">
<img v-bind:src="imagePathOrDefault" v-bind:alt="data.title" />
</figure>
<VRating
half-increments
@@ -119,10 +126,12 @@
<div class="column">
<div class="iconscontainer" :class="data.read ? 'remove-border-bottom' : ''">
<div class="bigiconcontainer">
<BigIcon icon="BIconEye"
<BigIcon
icon="BIconEye"
:legend="$t('bookform.wantread')"
:isSet="data.wantread"
@click="onWantReadIconClick"/>
@click="onWantReadIconClick"
/>
</div>
<BookDateWidget
icon="BIconBook"
@@ -131,7 +140,8 @@
:is-expanded="isStartReadExpanded()"
:is-readonly="data.read"
@onStartDateChange="onStartReadDateChange"
@onIconClick="onStartReadIconClick"/>
@onIconClick="onStartReadIconClick"
/>
<BookDateWidget
icon="BIconCheckCircle"
:legend="$t('bookform.read')"
@@ -142,7 +152,8 @@
:isExpanded="data.read"
@onStartDateChange="onStartReadDateChange"
@onEndDateChange="onEndReadDateChange"
@onIconClick="onReadIconClick"/>
@onIconClick="onReadIconClick"
/>
</div>
</div>
</div>
@@ -178,7 +189,6 @@ img {
}
}
@media (max-width: 1024px) {
img {
max-height: 250px;
@@ -200,5 +210,4 @@ img {
width: 100%;
}
}
</style>

View File

@@ -3,7 +3,7 @@
import { putReadBook, getImagePathOrDefault, postImportBook } from './api.js'
import { useRouter } from 'vue-router'
const router = useRouter();
const router = useRouter()
const props = defineProps({
id: Number,
@@ -16,39 +16,38 @@
read: Boolean,
wantread: Boolean,
coverPath: String,
});
const imagePathOrDefault = computed(() => getImagePathOrDefault(props.coverPath));
})
const imagePathOrDefault = computed(() => getImagePathOrDefault(props.coverPath))
const error = ref(null)
async function onUserBookRead() {
const res = await putReadBook(props.id);
const res = await putReadBook(props.id)
if (res.ok) {
router.push('/books')
} else {
res.json().then((json) => (error.value = json));
res.json().then((json) => (error.value = json))
}
}
async function openBook() {
if (props.id != 0) {
router.push(`/book/${props.id}`);
router.push(`/book/${props.id}`)
} else if (props.isinventaireedition) {
importInventaireEdition()
} else if (props.inventaireid != "") {
} else if (props.inventaireid != '') {
router.push(`/import/inventaire/${props.inventaireid}`)
}
}
async function importInventaireEdition(inventaireid) {
const res = await postImportBook(props.inventaireid, navigator.language.substring(0,2));
const json = await res.json();
const res = await postImportBook(props.inventaireid, navigator.language.substring(0, 2))
const json = await res.json()
if (res.ok) {
router.push(`/book/${json.id}`);
router.push(`/book/${json.id}`)
} else {
error.value = json;
error.value = json
}
}
</script>
<template>
@@ -59,7 +58,7 @@ async function importInventaireEdition(inventaireid) {
<div class="media column no-margin clickable" @click="openBook">
<div class="media-left">
<figure class="image mb-3">
<img v-bind:src="imagePathOrDefault" v-bind:alt="title">
<img v-bind:src="imagePathOrDefault" v-bind:alt="title" />
</figure>
</div>
<div class="media-content">
@@ -119,5 +118,4 @@ img {
.no-margin {
margin: 0px;
}
</style>

View File

@@ -4,67 +4,71 @@ import BookCard from './BookCard.vue'
import { getMyBooks } from './api.js'
import Pagination from './Pagination.vue'
const FilterStates = Object.freeze({
READ: "read",
WANTREAD: "wantread",
READING: "reading",
});
READ: 'read',
WANTREAD: 'wantread',
READING: 'reading',
})
const limit = 6;
const pageNumber = ref(1);
const limit = 6
const pageNumber = ref(1)
const offset = computed(() => (pageNumber.value - 1) * limit);
const offset = computed(() => (pageNumber.value - 1) * limit)
let currentFilterState = ref(FilterStates.READ);
let currentFilterState = ref(FilterStates.READ)
let data = ref(null);
let error = ref(null);
let data = ref(null)
let error = ref(null)
let totalBooksNumber = computed(() => (typeof(data) != 'undefined' &&
data.value != null) ? data.value["count"] : 0);
let totalBooksNumber = computed(() =>
typeof data != 'undefined' && data.value != null ? data.value['count'] : 0,
)
let pageTotal = computed(() => Math.ceil(totalBooksNumber.value / limit))
fetchData();
fetchData()
function fetchData() {
let res = getMyBooks(data, error, currentFilterState.value, limit, offset.value);
let res = getMyBooks(data, error, currentFilterState.value, limit, offset.value)
}
function onFilterButtonClick(newstate) {
currentFilterState.value = newstate;
pageNumber.value = 1;
fetchData();
currentFilterState.value = newstate
pageNumber.value = 1
fetchData()
}
function computeDynamicClass(state) {
return currentFilterState.value === state ? 'is-active is-primary' : '';
return currentFilterState.value === state ? 'is-active is-primary' : ''
}
function pageChange(newPageNumber) {
pageNumber.value = newPageNumber;
data.value = null;
fetchData();
pageNumber.value = newPageNumber
data.value = null
fetchData()
}
</script>
<template>
<div class="mb-5">
<button class="button is-medium"
<button
class="button is-medium"
@click="onFilterButtonClick(FilterStates.READ)"
:class="computeDynamicClass(FilterStates.READ)">
:class="computeDynamicClass(FilterStates.READ)"
>
{{ $t('bookbrowser.read') }}
</button>
<button class="button is-medium"
<button
class="button is-medium"
@click="onFilterButtonClick(FilterStates.READING)"
:class="computeDynamicClass(FilterStates.READING)">
:class="computeDynamicClass(FilterStates.READING)"
>
{{ $t('bookbrowser.reading') }}
</button>
<button class="button is-medium"
<button
class="button is-medium"
@click="onFilterButtonClick(FilterStates.WANTREAD)"
:class="computeDynamicClass(FilterStates.WANTREAD)">
:class="computeDynamicClass(FilterStates.WANTREAD)"
>
{{ $t('bookbrowser.wantread') }}
</button>
</div>
@@ -75,17 +79,22 @@ function pageChange(newPageNumber) {
<BookCard v-bind="book" />
</div>
</div>
<Pagination :pageNumber="pageNumber" :pageTotal="pageTotal" maxItemDisplayed="11" @pageChange="pageChange"/>
<Pagination
:pageNumber="pageNumber"
:pageTotal="pageTotal"
maxItemDisplayed="11"
@pageChange="pageChange"
/>
</div>
<div v-else>{{ $t('bookbrowser.loading') }}</div>
</template>
<style scoped>
.books {
position: relative;
float: left;
width:100%; height:auto;
width: 100%;
height: auto;
padding-bottom: 100px;
line-height: 2.5;

View File

@@ -4,30 +4,30 @@
const emit = defineEmits(['OnImageUpload'])
const props = defineProps({
name: String
});
name: String,
})
const imagePath = ref(null);
const error = ref(null);
const imagePath = ref(null)
const error = ref(null)
function onFileChanged(e) {
postImage(e.target.files[0])
.then((res) => res.json())
.then((json) => onJsonResult(json))
.catch((err) => (error.value = err["error"]));
.catch((err) => (error.value = err['error']))
}
function onJsonResult(json) {
imagePath.value = json["filepath"];
emit('OnImageUpload', json["fileId"])
imagePath.value = json['filepath']
emit('OnImageUpload', json['fileId'])
}
function unsetImage() {
imagePath.value = null;
imagePath.value = null
}
const imageSrc = computed(() => {
return "http://localhost:8080" + imagePath.value
return 'http://localhost:8080' + imagePath.value
})
</script>
@@ -35,7 +35,7 @@
<div v-if="imagePath">
<div class="relative">
<figure class="image mb-3">
<img v-bind:src="imageSrc" v-bind:alt="props.name">
<img v-bind:src="imageSrc" v-bind:alt="props.name" />
</figure>
<span class="icon is-large" @click="unsetImage">
<b-icon-x-circle-fill />
@@ -44,7 +44,13 @@
</div>
<div v-else class="file">
<label class="file-label">
<input class="file-input" @change="onFileChanged" type="file" :name="props.name" accept="image/*"/>
<input
class="file-input"
@change="onFileChanged"
type="file"
:name="props.name"
accept="image/*"
/>
<span class="file-cta">
<span class="file-icon">
<b-icon-upload />

View File

@@ -1,8 +1,7 @@
<script setup>
import { useAuthStore } from './auth.store.js'
const authStore = useAuthStore();
const authStore = useAuthStore()
</script>
<template>
@@ -22,6 +21,4 @@
</div>
</template>
<style scoped>
</style>
<style scoped></style>

View File

@@ -1,48 +1,46 @@
<script setup>
import { ref, computed } from 'vue'
import ImportListElement from './ImportListElement.vue';
import ImportListElement from './ImportListElement.vue'
import { getInventaireEditionBooks } from './api.js'
import { onBeforeRouteUpdate } from 'vue-router'
import Pagination from './Pagination.vue'
const limit = 5;
const pageNumber = ref(1);
const limit = 5
const pageNumber = ref(1)
const offset = computed(() => (pageNumber.value - 1) * limit);
const offset = computed(() => (pageNumber.value - 1) * limit)
const props = defineProps({
inventaireid: String,
});
})
let data = ref(null);
let error = ref(null);
let data = ref(null)
let error = ref(null)
const pageTotal = computed(() => {
let countValue = (data.value !== null) ? data.value['count'] : 0;
return Math.ceil(countValue / limit);
});
fetchData(props.inventaireid);
let countValue = data.value !== null ? data.value['count'] : 0
return Math.ceil(countValue / limit)
})
fetchData(props.inventaireid)
function fetchData(inventaireid, authorId) {
if (inventaireid != null) {
let lang = navigator.language.substring(0,2);
getInventaireEditionBooks(data, error, inventaireid, lang, limit, offset.value);
let lang = navigator.language.substring(0, 2)
getInventaireEditionBooks(data, error, inventaireid, lang, limit, offset.value)
}
}
onBeforeRouteUpdate(async (to, from) => {
pageNumber.value = 1;
fetchData(to.params.inventaireid);
pageNumber.value = 1
fetchData(to.params.inventaireid)
})
function pageChange(newPageNumber) {
pageNumber.value = newPageNumber;
data.value = null;
fetchData(props.inventaireid);
pageNumber.value = newPageNumber
data.value = null
fetchData(props.inventaireid)
}
</script>
<template>
@@ -57,7 +55,8 @@
:pageNumber="pageNumber"
:pageTotal="pageTotal"
maxItemDisplayed="11"
@pageChange="pageChange"/>
@pageChange="pageChange"
/>
</div>
<div v-else-if="data === null">{{ $t('searchbook.loading') }}</div>
<div v-else>{{ $t('searchbook.noresult') }}</div>
@@ -65,4 +64,3 @@
</template>
<style scoped></style>

View File

@@ -3,7 +3,7 @@
import { getInventaireImagePathOrDefault, postImportBook } from './api.js'
import { useRouter } from 'vue-router'
const router = useRouter();
const router = useRouter()
const props = defineProps({
uri: String,
title: String,
@@ -11,33 +11,33 @@
publisher: String,
date: String,
isbn: String,
lang: String
});
lang: String,
})
function displayDate(date) {
const regex = /^\d{4}-\d{2}-\d{2}$/;
const regex = /^\d{4}-\d{2}-\d{2}$/
if (regex.test(date)) {
const d = new Date(date);
return d.toLocaleDateString();
const d = new Date(date)
return d.toLocaleDateString()
} else {
return date;
return date
}
}
const error = ref(null);
const data = ref(null);
const importing = ref(false);
const error = ref(null)
const data = ref(null)
const importing = ref(false)
async function importInventaireEdition() {
importing.value = true;
const res = await postImportBook(props.uri, navigator.language.substring(0,2));
const json = await res.json();
importing.value = true
const res = await postImportBook(props.uri, navigator.language.substring(0, 2))
const json = await res.json()
if (res.ok) {
router.push(`/book/${json.id}`);
router.push(`/book/${json.id}`)
} else {
error.value = json;
error.value = json
}
}
const imagePathOrDefault = computed(() => getInventaireImagePathOrDefault(props.image));
const imagePathOrDefault = computed(() => getInventaireImagePathOrDefault(props.image))
</script>
<template>
@@ -46,20 +46,22 @@
<div v-else class="media column no-margin clickable" @click="importInventaireEdition">
<div class="media-left">
<figure class="image mb-3">
<img v-bind:src="imagePathOrDefault" v-bind:alt="title">
<img v-bind:src="imagePathOrDefault" v-bind:alt="title" />
</figure>
</div>
<div class="media-content">
<div v-if="error" class="has-text-danger">
<p>{{ error }}</p>
</div>
<div v-else>
<div class="is-size-4">{{ title }}</div>
<div v-if="props.date" class="is-size-5">{{$t('importlistelement.releasedate') + "" +
displayDate(date)}}</div>
<div v-if="props.publisher" class="is-size-5">{{$t('importlistelement.publisher') + "" + publisher}}</div>
<div v-if="props.isbn" class="is-size-5">{{"ISBN: " + isbn}}</div>
<div v-if="props.date" class="is-size-5">
{{ $t('importlistelement.releasedate') + '' + displayDate(date) }}
</div>
<div v-if="props.publisher" class="is-size-5">
{{ $t('importlistelement.publisher') + '' + publisher }}
</div>
<div v-if="props.isbn" class="is-size-5">{{ 'ISBN: ' + isbn }}</div>
</div>
</div>
</div>
@@ -95,5 +97,4 @@ img {
.no-margin {
margin: 0px;
}
</style>

View File

@@ -4,43 +4,42 @@
import { useRouter } from 'vue-router'
import { useAuthStore } from './auth.store.js'
const router = useRouter();
const router = useRouter()
const user = ref({
username: "",
password: ""
});
username: '',
password: '',
})
const errors = ref(null)
const formError = computed(() => {
return extractGlobalFormError(errors.value);
return extractGlobalFormError(errors.value)
})
const userError = computed(() => {
return extractFormErrorFromField("Username", errors.value);
return extractFormErrorFromField('Username', errors.value)
})
const passwordError = computed(() => {
return extractFormErrorFromField("Password", errors.value);
return extractFormErrorFromField('Password', errors.value)
})
async function onSubmit(e) {
const res = await postLogin(user)
if (res.ok) {
let json = await res.json();
await login(user.value.username, json);
router.push('/');
return;
let json = await res.json()
await login(user.value.username, json)
router.push('/')
return
} else {
res.json().then((json) => (errors.value = json));
res.json().then((json) => (errors.value = json))
}
}
async function login(username, json) {
useAuthStore().login({username: username, token: json["token"]})
useAuthStore().login({ username: username, token: json['token'] })
}
</script>
<template>
<div v-if="formError" class="notification is-danger">
<p>{{ formError }}</p>
@@ -50,16 +49,30 @@
<div class="field">
<label class="label">{{ $t('login.username') }}</label>
<div class="control">
<input :class="'input ' + (userError ? 'is-danger' : '')" type="text" minlength="2" maxlength="20"
required v-model="user.username" :placeholder="$t('login.username')">
<input
:class="'input ' + (userError ? 'is-danger' : '')"
type="text"
minlength="2"
maxlength="20"
required
v-model="user.username"
:placeholder="$t('login.username')"
/>
</div>
<p v-if="userError" class="help is-danger">{{ userError }}</p>
</div>
<div class="field">
<label class="label">{{ $t('login.password') }}</label>
<div class="control">
<input :class="'input ' + (passwordError ? 'is-danger' : '')" type="password" minlength="6"
maxlength="100" v-model="user.password" required :placeholder="$t('login.password')">
<input
:class="'input ' + (passwordError ? 'is-danger' : '')"
type="password"
minlength="6"
maxlength="100"
v-model="user.password"
required
:placeholder="$t('login.password')"
/>
</div>
<p v-if="passwordError" class="help is-danger">{{ passwordError }}</p>
</div>

View File

@@ -3,13 +3,13 @@
import { useRouter } from 'vue-router'
import BarcodeModal from './BarcodeModal.vue'
const searchterm = ref("");
const router = useRouter();
const searchterm = ref('')
const router = useRouter()
const props = defineProps({
sizeClass: String,
isMobile: Boolean
});
isMobile: Boolean,
})
const emit = defineEmits('searchDone')
@@ -18,15 +18,15 @@
if (binding.value) {
el.focus()
}
},
}
};
function onSearchClick() {
if (typeof searchterm.value === "undefined" || searchterm.value === "") {
if (typeof searchterm.value === 'undefined' || searchterm.value === '') {
return
}
emit('searchDone')
router.push('/search/' + searchterm.value);
router.push('/search/' + searchterm.value)
}
</script>
@@ -34,7 +34,14 @@
<div class="navbar-item">
<div class="field has-addons">
<div class="control" :class="isMobile ? 'fullwidth' : ''">
<input v-mobile-focus="isMobile ? true : null" v-model="searchterm" @keyup.enter="onSearchClick()" class="input" :class="sizeClass" type="text" />
<input
v-mobile-focus="isMobile ? true : null"
v-model="searchterm"
@keyup.enter="onSearchClick()"
class="input"
:class="sizeClass"
type="text"
/>
</div>
<div class="control">
<button @click="onSearchClick()" class="button" :class="sizeClass">

View File

@@ -1,74 +1,86 @@
<script setup>
import { computed } from 'vue'
const props = defineProps(['pageNumber', 'pageTotal', 'maxItemDisplayed']);
const emit = defineEmits(['pageChange']);
const props = defineProps(['pageNumber', 'pageTotal', 'maxItemDisplayed'])
const emit = defineEmits(['pageChange'])
const paginatedItems = computed(() => {
let items = [];
let items = []
if (props.pageTotal > props.maxItemDisplayed) {
//number of pages we can display before and after the current page
const maxNumberOfItemsAroundCurrentItem = Math.floor((props.maxItemDisplayed - 2) / 2);
const maxNumberOfItemsAroundCurrentItem = Math.floor((props.maxItemDisplayed - 2) / 2)
items.push(1);
items.push(1)
//compute first item number
let firstItemNumber;
let lastItemNumber;
let firstItemNumber
let lastItemNumber
if (props.pageNumber - maxNumberOfItemsAroundCurrentItem < 4) {
//starting at the left
firstItemNumber = 2;
lastItemNumber = props.maxItemDisplayed;
firstItemNumber = 2
lastItemNumber = props.maxItemDisplayed
} else if (props.pageNumber + maxNumberOfItemsAroundCurrentItem > props.pageTotal - 2) {
//starting at the right
firstItemNumber = props.pageTotal - props.maxItemDisplayed + 1;
lastItemNumber = props.pageTotal - 1;
firstItemNumber = props.pageTotal - props.maxItemDisplayed + 1
lastItemNumber = props.pageTotal - 1
} else {
firstItemNumber = props.pageNumber - maxNumberOfItemsAroundCurrentItem;
lastItemNumber = props.pageNumber + maxNumberOfItemsAroundCurrentItem;
firstItemNumber = props.pageNumber - maxNumberOfItemsAroundCurrentItem
lastItemNumber = props.pageNumber + maxNumberOfItemsAroundCurrentItem
}
if (firstItemNumber !== 2) {
items.push(-1);
items.push(-1)
}
for (let i = firstItemNumber; i <= lastItemNumber; i++) {
items.push(i);
items.push(i)
}
if (lastItemNumber !== props.pageTotal - 1) {
items.push(-1);
items.push(-1)
}
items.push(props.pageTotal);
items.push(props.pageTotal)
} else {
for (let i = 1; i <= props.pageTotal; i++) {
items.push(i);
items.push(i)
}
}
return items;})
return items
})
</script>
<template>
<nav v-if="props.pageTotal > 1" class="pagination" role="navigation" aria-label="pagination">
<a href="#" v-if="props.pageNumber > 1"
<a
href="#"
v-if="props.pageNumber > 1"
@click="$emit('pageChange', Math.max(1, props.pageNumber - 1))"
class="pagination-previous">
class="pagination-previous"
>
{{ $t('pagination.previous') }}
</a>
<a href="#" v-if="props.pageNumber < props.pageTotal"
<a
href="#"
v-if="props.pageNumber < props.pageTotal"
@click="$emit('pageChange', props.pageNumber + 1)"
class="pagination-next">
class="pagination-next"
>
{{ $t('pagination.next') }}
</a>
<ul class="pagination-list">
<li v-for="item in paginatedItems" :key="item">
<span v-if="item === -1" class="pagination-ellipsis">&hellip;</span>
<a v-else
<a
v-else
href="#"
@click="$emit('pageChange', item)"
class="pagination-link"
:class="item === props.pageNumber ? 'is-current' : ''"
:aria-current="item === props.pageNumber ? 'page' : null"
:aria-label="$t(item === props.pageNumber ? 'pagination.page' : 'pagination.goto', {pageNumber: item})">
:aria-label="
$t(item === props.pageNumber ? 'pagination.page' : 'pagination.goto', {
pageNumber: item,
})
"
>
{{ item }}
</a>
</li>
@@ -76,7 +88,4 @@
</nav>
</template>
<style scoped>
</style>
<style scoped></style>

View File

@@ -1,20 +1,20 @@
<script setup>
import { useTemplateRef, onMounted, ref } from 'vue'
import { BrowserMultiFormatReader } from "@zxing/library";
import { BrowserMultiFormatReader } from '@zxing/library'
const emit = defineEmits('readBarcode')
const scanResult = ref(null);
const codeReader = new BrowserMultiFormatReader();
const scannerElement = useTemplateRef("scanner");
const scanResult = ref(null)
const codeReader = new BrowserMultiFormatReader()
const scannerElement = useTemplateRef('scanner')
onMounted(() => {
codeReader.decodeFromVideoDevice(undefined, scannerElement.value, (result, err) => {
if (result) {
emit('readBarcode', result.text)
}
});
});
})
})
function onResult(result) {
scanResult.value = result

View File

@@ -1,66 +1,64 @@
<script setup>
import { ref, computed } from 'vue'
import BookListElement from './BookListElement.vue';
import BookListElement from './BookListElement.vue'
import { getSearchBooks, getAuthorBooks } from './api.js'
import { onBeforeRouteUpdate } from 'vue-router'
import Pagination from './Pagination.vue'
const limit = 5;
const pageNumber = ref(1);
const limit = 5
const pageNumber = ref(1)
const offset = computed(() => (pageNumber.value - 1) * limit);
const offset = computed(() => (pageNumber.value - 1) * limit)
const props = defineProps({
searchterm: String,
authorId: Number
});
authorId: Number,
})
const forceSearchInventaire = ref(false);
const forceSearchInventaire = ref(false)
const searchInventaire = computed(() => {
return forceSearchInventaire.value || (data.value !== null && data.value['inventaire'])
});
})
let data = ref(null);
let error = ref(null);
let data = ref(null)
let error = ref(null)
const pageTotal = computed(() => {
const countValue = (data.value !== null) ? data.value['count'] : 0;
return Math.ceil(countValue / limit);
});
fetchData(props.searchterm, props.authorId);
const countValue = data.value !== null ? data.value['count'] : 0
return Math.ceil(countValue / limit)
})
fetchData(props.searchterm, props.authorId)
function fetchData(searchTerm, authorId) {
data.value = null;
error.value = null;
data.value = null
error.value = null
if (searchTerm != null) {
const lang = navigator.language.substring(0,2);
getSearchBooks(data, error, searchTerm, lang, forceSearchInventaire.value, limit, offset.value);
const lang = navigator.language.substring(0, 2)
getSearchBooks(data, error, searchTerm, lang, forceSearchInventaire.value, limit, offset.value)
} else if (authorId != null) {
getAuthorBooks(data, error, authorId, limit, offset.value);
getAuthorBooks(data, error, authorId, limit, offset.value)
}
}
onBeforeRouteUpdate(async (to, from) => {
pageNumber.value = 1;
fetchData(to.params.searchterm, props.authorId);
pageNumber.value = 1
fetchData(to.params.searchterm, props.authorId)
})
function pageChange(newPageNumber) {
pageNumber.value = newPageNumber;
data.value = null;
fetchData(props.searchterm, props.authorId);
pageNumber.value = newPageNumber
data.value = null
fetchData(props.searchterm, props.authorId)
}
function toggleSearchInventaire() {
pageNumber.value = 1;
forceSearchInventaire.value = !forceSearchInventaire.value;
pageNumber.value = 1
forceSearchInventaire.value = !forceSearchInventaire.value
fetchData(props.searchterm, props.authorId)
window.scrollTo(0,0);
window.scrollTo(0, 0)
}
</script>
<template>
@@ -71,20 +69,25 @@
<div class="booksearchlist" v-for="book in data.books" :key="book.id">
<BookListElement v-bind="book" />
</div>
<div v-if="(!searchInventaire || forceSearchInventaire) && !authorId" class="box container clickable has-background-dark" @click="toggleSearchInventaire">
<div class="is-size-4">{{searchInventaire ? $t('searchbook.backtosearch') : $t('searchbook.searchinventaire')}}</div>
<div
v-if="(!searchInventaire || forceSearchInventaire) && !authorId"
class="box container clickable has-background-dark"
@click="toggleSearchInventaire"
>
<div class="is-size-4">
{{ searchInventaire ? $t('searchbook.backtosearch') : $t('searchbook.searchinventaire') }}
</div>
</div>
<Pagination
:pageNumber="pageNumber"
:pageTotal="pageTotal"
maxItemDisplayed="11"
@pageChange="pageChange"/>
@pageChange="pageChange"
/>
</div>
<div v-else-if="data === null">{{ $t('searchbook.loading') }}</div>
<div v-else>{{ $t('searchbook.noresult') }}</div>
</div>
</template>
<style scoped>
</style>
<style scoped></style>

View File

@@ -3,33 +3,32 @@
import { postSignUp, extractFormErrorFromField, extractGlobalFormError } from './api.js'
import { useRouter } from 'vue-router'
const router = useRouter();
const router = useRouter()
const user = ref({
username: "",
password: ""
});
username: '',
password: '',
})
const errors = ref(null)
const formError = computed(() => {
return extractGlobalFormError(errors.value);
return extractGlobalFormError(errors.value)
})
const userError = computed(() => {
return extractFormErrorFromField("Username", errors.value);
return extractFormErrorFromField('Username', errors.value)
})
const passwordError = computed(() => {
return extractFormErrorFromField("Password", errors.value);
return extractFormErrorFromField('Password', errors.value)
})
function onSubmit() {
postSignUp(user)
.then((res) => {
postSignUp(user).then((res) => {
if (res.ok) {
router.push('/');
return;
router.push('/')
return
} else {
res.json().then((json) => (errors.value = json));
res.json().then((json) => (errors.value = json))
}
})
}
@@ -44,16 +43,30 @@
<div class="field">
<label class="label">{{ $t('signup.username') }}</label>
<div class="control">
<input :class="'input ' + (userError ? 'is-danger' : '')" type="text" minlength="2" maxlength="20"
required v-model="user.username" :placeholder="$t('signup.username')">
<input
:class="'input ' + (userError ? 'is-danger' : '')"
type="text"
minlength="2"
maxlength="20"
required
v-model="user.username"
:placeholder="$t('signup.username')"
/>
</div>
<p v-if="userError" class="help is-danger">{{ userError }}</p>
</div>
<div class="field">
<label class="label">{{ $t('signup.password') }}</label>
<div class="control">
<input :class="'input ' + (passwordError ? 'is-danger' : '')" type="password" minlength="6"
maxlength="100" v-model="user.password" required :placeholder="$t('signup.password')">
<input
:class="'input ' + (passwordError ? 'is-danger' : '')"
type="password"
minlength="6"
maxlength="100"
v-model="user.password"
required
:placeholder="$t('signup.password')"
/>
</div>
<p v-if="passwordError" class="help is-danger">{{ passwordError }}</p>
</div>

View File

@@ -1,79 +1,93 @@
import { useAuthStore } from './auth.store.js'
export function getInventaireImagePathOrDefault(path) {
return getImagePathOrGivenDefault(path, "../../image/defaultinventairebook.png")
return getImagePathOrGivenDefault(path, '../../image/defaultinventairebook.png')
}
export function getImagePathOrDefault(path) {
return getImagePathOrGivenDefault(path, "../image/defaultbook.png")
return getImagePathOrGivenDefault(path, '../image/defaultbook.png')
}
export function getImagePathOrGivenDefault(path, defaultpath) {
if (path == "" || typeof path === 'undefined') {
return defaultpath;
} else if (path.startsWith("https://")) {
return path;
if (path == '' || typeof path === 'undefined') {
return defaultpath
} else if (path.startsWith('https://')) {
return path
} else {
return path;
return path
}
}
function useFetch(data, error, url) {
const { user } = useAuthStore();
const { user } = useAuthStore()
if (user != null) {
fetch(url, {
method: 'GET',
headers: {
'Authorization': 'Bearer ' + user.token
}
Authorization: 'Bearer ' + user.token,
},
})
.then((res) => {
if (res.status === 401) {
const authStore = useAuthStore();
authStore.logout();
const authStore = useAuthStore()
authStore.logout()
}
return res.json();
return res.json()
})
.then((json) => (data.value = json))
.catch((err) => (error.value = err));
.catch((err) => (error.value = err))
}
}
export async function getAppInfo(appInfo, appInfoErr) {
return fetch('/ws/appinfo', {
method: 'GET'
}).then((res) => res.json())
.then((json) => appInfo.value = json)
method: 'GET',
})
.then((res) => res.json())
.then((json) => (appInfo.value = json))
.catch((err) => (appInfoErr.value = err))
}
export function getMyBooks(data, error, arg, limit, offset) {
const queryParams = new URLSearchParams({limit: limit, offset: offset});
return useFetch(data, error, '/ws/mybooks/' + arg + "?" + queryParams.toString());
const queryParams = new URLSearchParams({ limit: limit, offset: offset })
return useFetch(data, error, '/ws/mybooks/' + arg + '?' + queryParams.toString())
}
export function getSearchBooks(data, error, searchterm, lang, searchInventaire, limit, offset) {
const queryParams = new URLSearchParams({lang: lang, inventaire: searchInventaire, limit: limit, offset: offset});
return useFetch(data, error, '/ws/search/' + encodeURIComponent(searchterm) + "?" + queryParams.toString());
const queryParams = new URLSearchParams({
lang: lang,
inventaire: searchInventaire,
limit: limit,
offset: offset,
})
return useFetch(
data,
error,
'/ws/search/' + encodeURIComponent(searchterm) + '?' + queryParams.toString(),
)
}
export function getInventaireEditionBooks(data, error, inventaireId, lang, limit, offset) {
const queryParams = new URLSearchParams({lang: lang, limit: limit, offset: offset});
return useFetch(data, error, '/ws/inventaire/books/' + encodeURIComponent(inventaireId) + "?" + queryParams.toString());
const queryParams = new URLSearchParams({ lang: lang, limit: limit, offset: offset })
return useFetch(
data,
error,
'/ws/inventaire/books/' + encodeURIComponent(inventaireId) + '?' + queryParams.toString(),
)
}
export function getAuthor(data, error, id) {
return useFetch(data, error, '/ws/author/' + id);
return useFetch(data, error, '/ws/author/' + id)
}
export function getAuthorBooks(data, error, id, limit, offset) {
const queryParams = new URLSearchParams({limit: limit, offset: offset});
return useFetch(data, error, '/ws/author/' + id + "/books" + "?" + queryParams.toString());
const queryParams = new URLSearchParams({ limit: limit, offset: offset })
return useFetch(data, error, '/ws/author/' + id + '/books' + '?' + queryParams.toString())
}
export function getBook(data, error, id) {
return useFetch(data, error, '/ws/book/' + id);
return useFetch(data, error, '/ws/book/' + id)
}
export function postBook(book) {
@@ -81,39 +95,39 @@ export function postBook(book) {
}
export async function postImportBook(id, language) {
return genericPayloadCall('/ws/importbook', {inventaireid: id, lang: language}, 'POST');
return genericPayloadCall('/ws/importbook', { inventaireid: id, lang: language }, 'POST')
}
export async function putReadBook(bookId) {
return genericPayloadCall('/ws/book/' + bookId + "/read", {read: true}, 'PUT')
return genericPayloadCall('/ws/book/' + bookId + '/read', { read: true }, 'PUT')
}
export async function putUnreadBook(bookId) {
return genericPayloadCall('/ws/book/' + bookId + "/read", {read: false}, 'PUT')
return genericPayloadCall('/ws/book/' + bookId + '/read', { read: false }, 'PUT')
}
export async function putEndReadDate(bookId, enddate) {
return genericPayloadCall('/ws/book/' + bookId + "/read", {read: true, endDate: enddate}, 'PUT')
return genericPayloadCall('/ws/book/' + bookId + '/read', { read: true, endDate: enddate }, 'PUT')
}
export async function putEndReadDateUnset(bookId) {
return genericPayloadCall('/ws/book/' + bookId + "/read", {read: true, endDate: "null"}, 'PUT')
return genericPayloadCall('/ws/book/' + bookId + '/read', { read: true, endDate: 'null' }, 'PUT')
}
export async function putStartReadDateUnset(bookId) {
return genericPayloadCall('/ws/book/' + bookId + "/startread", {startDate: "null"}, 'PUT')
return genericPayloadCall('/ws/book/' + bookId + '/startread', { startDate: 'null' }, 'PUT')
}
export async function putStartReadDate(bookId, startdate) {
return genericPayloadCall('/ws/book/' + bookId + "/startread", {startDate: startdate}, 'PUT')
return genericPayloadCall('/ws/book/' + bookId + '/startread', { startDate: startdate }, 'PUT')
}
export async function putWantReadBook(bookId, payload) {
return genericPayloadCall('/ws/book/' + bookId + "/wantread", payload, 'PUT')
return genericPayloadCall('/ws/book/' + bookId + '/wantread', payload, 'PUT')
}
export async function putRateBook(bookId, payload) {
return genericPayloadCall('/ws/book/' + bookId + "/rate", payload, 'PUT')
return genericPayloadCall('/ws/book/' + bookId + '/rate', payload, 'PUT')
}
export function postLogin(user) {
@@ -125,19 +139,19 @@ export function postSignUp(user) {
}
export function postImage(file) {
const { user } = useAuthStore();
const formData = new FormData();
formData.append('file', file);
const { user } = useAuthStore()
const formData = new FormData()
formData.append('file', file)
if (user != null) {
return fetch("/ws/upload/cover", {
return fetch('/ws/upload/cover', {
method: 'POST',
headers: {
'Authorization': 'Bearer ' + user.token
Authorization: 'Bearer ' + user.token,
},
body: formData
body: formData,
})
} else {
return Promise.resolve();
return Promise.resolve()
}
}
@@ -147,48 +161,47 @@ export function genericPostCallNoAuth(apiRoute, object) {
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(object)
body: JSON.stringify(object),
})
}
export function genericPayloadCall(apiRoute, object, method) {
const { user } = useAuthStore();
const { user } = useAuthStore()
if (user != null) {
return fetch(apiRoute, {
method: method,
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + user.token
Authorization: 'Bearer ' + user.token,
},
body: JSON.stringify(object)
body: JSON.stringify(object),
})
}
else {
return Promise.resolve();
} else {
return Promise.resolve()
}
}
export function extractFormErrorFromField(fieldName, errors) {
if (errors === null) {
return "";
return ''
}
if (errors.value == null) {
return "";
return ''
}
console.log(errors.value);
const titleErr = errors.find((e) => e["field"] === fieldName);
console.log(errors.value)
const titleErr = errors.find((e) => e['field'] === fieldName)
if (typeof titleErr !== 'undefined') {
return titleErr.error;
return titleErr.error
} else {
return "";
return ''
}
}
export function extractGlobalFormError(errors) {
if (errors !== null && "error" in errors) {
return errors["error"];
if (errors !== null && 'error' in errors) {
return errors['error']
} else {
return "";
return ''
}
}

View File

@@ -1,20 +1,19 @@
import { defineStore } from 'pinia';
import { defineStore } from 'pinia'
export const useAuthStore = defineStore('auth', {
state: () => ({
// initialize state from local storage to enable user to stay logged in
user: JSON.parse(localStorage.getItem('user')),
returnUrl: null
returnUrl: null,
}),
actions: {
async login(user) {
this.user = user;
localStorage.setItem('user', JSON.stringify(user));
this.user = user
localStorage.setItem('user', JSON.stringify(user))
},
logout() {
this.user = null;
localStorage.removeItem('user');
}
}
});
this.user = null
localStorage.removeItem('user')
},
},
})

View File

@@ -80,5 +80,4 @@
"publisher": "Publisher:",
"importing": "Importing..."
}
}

View File

@@ -1,26 +1,24 @@
import { createApp } from 'vue'
import { createI18n } from "vue-i18n";
import { createI18n } from 'vue-i18n'
import { createPinia } from 'pinia'
import { BootstrapIconsPlugin } from "bootstrap-icons-vue";
import { router } from './router.js';
import { BootstrapIconsPlugin } from 'bootstrap-icons-vue'
import { router } from './router.js'
import { createVuetify } from 'vuetify'
import { aliases, mdi } from 'vuetify/iconsets/mdi-svg'
import { VRating } from 'vuetify/components/VRating';
import { VRating } from 'vuetify/components/VRating'
import App from './App.vue'
import './styles/global.css';
import fr from './locales/fr.json';
import en from './locales/en.json';
import './styles/global.css'
import fr from './locales/fr.json'
import en from './locales/en.json'
// configure i18n
const i18n = createI18n({
locale: navigator.language,
fallbackLocale: "en",
fallbackLocale: 'en',
messages: { fr, en },
});
})
const vuetify = createVuetify({
VRating,
@@ -35,5 +33,4 @@ const vuetify = createVuetify({
const pinia = createPinia()
createApp(App).use(i18n).use(vuetify).use(pinia).use(BootstrapIconsPlugin).use(router).mount('#app')

View File

@@ -32,12 +32,12 @@ export const router = createRouter({
router.beforeEach(async (to) => {
// redirect to login page if not logged in and trying to access a restricted page
const publicPages = ['/', '/login', '/signup'];
const authRequired = !publicPages.includes(to.path);
const auth = useAuthStore();
const publicPages = ['/', '/login', '/signup']
const authRequired = !publicPages.includes(to.path)
const auth = useAuthStore()
if (authRequired && !auth.user) {
auth.returnUrl = to.fullPath;
return '/login';
auth.returnUrl = to.fullPath
return '/login'
}
});
})

View File

@@ -6,13 +6,10 @@ import vueDevTools from 'vite-plugin-vue-devtools'
// https://vite.dev/config/
export default defineConfig({
plugins: [
vue(),
vueDevTools(),
],
plugins: [vue(), vueDevTools()],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
'@': fileURLToPath(new URL('./src', import.meta.url)),
},
},
})