feat(webui): handle new metadata fields

closes #276
This commit is contained in:
Gauthier Roebroeck 2020-08-24 14:46:51 +08:00
parent efdcc98604
commit 5567adc946
10 changed files with 537 additions and 181 deletions

View file

@ -11389,6 +11389,19 @@
"integrity": "sha512-xf88rTeHiXk+XE2Vhi6yj8Wm3gMZrygGdKjJqN8HkV+PwF/t50/LdAKHoHpPcxFAlmQszTZ1CugrK25S7qDRLA==",
"dev": true
},
"language-subtag-registry": {
"version": "0.3.20",
"resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.20.tgz",
"integrity": "sha512-KPMwROklF4tEx283Xw0pNKtfTj1gZ4UByp4EsIFWLgBavJltF4TiYPc39k06zSTsLzxTVXXDSpbwaQXaFB4Qeg=="
},
"language-tags": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/language-tags/-/language-tags-1.0.5.tgz",
"integrity": "sha1-0yHbxNowuovzAk4ED6XBRmH5GTo=",
"requires": {
"language-subtag-registry": "~0.3.2"
}
},
"launch-editor": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.2.1.tgz",

View file

@ -12,6 +12,7 @@
"axios": "^0.19.2",
"core-js": "^3.6.5",
"jquery": "^3.5.1",
"language-tags": "^1.0.5",
"lodash": "^4.17.19",
"moment": "^2.27.0",
"qs": "^6.9.4",

View file

@ -24,7 +24,7 @@
</v-card-title>
<v-tabs :vertical="$vuetify.breakpoint.smAndUp" v-model="tab">
<v-tab class="justify-start">
<v-tab class="justify-start" v-if="single">
<v-icon left class="hidden-xs-only">mdi-format-align-center</v-icon>
General
</v-tab>
@ -32,9 +32,13 @@
<v-icon left class="hidden-xs-only">mdi-account-multiple</v-icon>
Authors
</v-tab>
<v-tab class="justify-start">
<v-icon left class="hidden-xs-only">mdi-tag-multiple</v-icon>
Tags
</v-tab>
<!-- Tab: General -->
<v-tab-item>
<v-tab-item v-if="single">
<v-card flat>
<v-container fluid>
@ -130,52 +134,6 @@
</v-col>
</v-row>
<!-- Publisher -->
<v-row>
<v-col cols="6">
<v-text-field v-model="form.publisher"
label="Publisher"
filled
dense
:placeholder="mixed.publisher ? 'MIXED' : ''"
@input="$v.form.publisher.$touch()"
@change="form.publisherLock = true"
>
<template v-slot:prepend>
<v-icon :color="form.publisherLock ? 'secondary' : ''"
@click="form.publisherLock = !form.publisherLock"
>
{{ form.publisherLock ? 'mdi-lock' : 'mdi-lock-open' }}
</v-icon>
</template>
</v-text-field>
</v-col>
<!-- Age Rating -->
<v-col cols="6">
<v-text-field v-model="form.ageRating"
label="Age Rating"
clearable
filled
dense
type="number"
:placeholder="mixed.ageRating ? 'MIXED' : ''"
:error-messages="ageRatingErrors"
@input="$v.form.ageRating.$touch()"
@blur="$v.form.ageRating.$touch()"
@change="form.ageRatingLock = true"
>
<template v-slot:prepend>
<v-icon :color="form.ageRatingLock ? 'secondary' : ''"
@click="form.ageRatingLock = !form.ageRatingLock"
>
{{ form.ageRatingLock ? 'mdi-lock' : 'mdi-lock-open' }}
</v-icon>
</template>
</v-text-field>
</v-col>
</v-row>
<!-- Release Date -->
<v-row v-if="single">
<v-col cols="12">
@ -200,29 +158,6 @@
</v-col>
</v-row>
<!-- Reading Direction -->
<v-row>
<v-col cols="">
<v-select v-model="form.readingDirection"
:items="readingDirections"
label="Reading Direction"
clearable
filled
:placeholder="mixed.readingDirection ? 'MIXED' : ''"
@input="$v.form.readingDirection.$touch()"
@change="form.readingDirectionLock = true"
>
<template v-slot:prepend>
<v-icon :color="form.readingDirectionLock ? 'secondary' : ''"
@click="form.readingDirectionLock = !form.readingDirectionLock"
>
{{ form.readingDirectionLock ? 'mdi-lock' : 'mdi-lock-open' }}
</v-icon>
</template>
</v-select>
</v-col>
</v-row>
</v-container>
</v-card>
</v-tab-item>
@ -270,6 +205,47 @@
</v-card>
</v-tab-item>
<!-- Tab: Tags -->
<v-tab-item>
<v-card flat>
<v-container fluid>
<v-alert v-if="!single"
type="warning"
outlined
dense
>
You are editing tags for multiple books. This will override existing tags of each book.
</v-alert>
<!-- Tags -->
<v-row>
<v-col cols="12">
<span class="text-body-2">Tags</span>
<v-combobox v-model="form.tags"
:items="tagsAvailable"
@input="$v.form.tags.$touch()"
@change="form.tagsLock = true"
hide-selected
chips
deletable-chips
multiple
filled
dense
>
<template v-slot:prepend>
<v-icon :color="form.tagsLock ? 'secondary' : ''"
@click="form.tagsLock = !form.tagsLock"
>
{{ form.tagsLock ? 'mdi-lock' : 'mdi-lock-open' }}
</v-icon>
</template>
</v-combobox>
</v-col>
</v-row>
</v-container>
</v-card>
</v-tab-item>
</v-tabs>
<v-card-actions class="hidden-xs-only">
@ -300,10 +276,9 @@
<script lang="ts">
import { groupAuthorsByRole } from '@/functions/authors'
import { authorRoles } from '@/types/author-roles'
import { ReadingDirection } from '@/types/enum-books'
import moment from 'moment'
import Vue from 'vue'
import { helpers, minValue, requiredIf } from 'vuelidate/lib/validators'
import { helpers, requiredIf } from 'vuelidate/lib/validators'
const validDate = (value: any) => !helpers.req(value) || moment(value, 'YYYY-MM-DD', true).isValid()
@ -324,25 +299,17 @@ export default Vue.extend({
numberLock: false,
numberSort: 0,
numberSortLock: false,
readingDirection: '',
readingDirectionLock: false,
publisher: '',
publisherLock: false,
ageRating: undefined as unknown as number,
ageRatingLock: false,
releaseDate: '',
releaseDateLock: false,
authors: {},
authorsLock: false,
},
mixed: {
readingDirection: false,
publisher: false,
ageRating: false,
tags: [] as string[],
tagsLock: false,
},
authorRoles,
authorSearch: [],
authorSearchResults: [] as string[],
tagsAvailable: [] as string[],
}
},
props: {
@ -390,26 +357,19 @@ export default Vue.extend({
return this.single
}),
},
ageRating: { minValue: minValue(0) },
tags: {},
releaseDate: { validDate },
summary: {},
readingDirection: {},
publisher: {},
authors: {},
},
},
async created () {
this.tagsAvailable = await this.$komgaReferential.getTags()
},
computed: {
single (): boolean {
return !Array.isArray(this.books)
},
readingDirections (): any[] {
return Object.keys(ReadingDirection).map(x => (
{
text: this.$_.capitalize(x.replace(/_/g, ' ')),
value: x,
}),
)
},
authorSearchResultsFull (): string[] {
// merge local values with server search, so that already input value is available
const local = (this.$_.values(this.form.authors).flat()) as unknown as string[]
@ -420,12 +380,6 @@ export default Vue.extend({
? `Edit ${this.$_.get(this.books, 'metadata.title')}`
: `Edit ${(this.books as BookDto[]).length} books`
},
ageRatingErrors (): string[] {
const errors = [] as string[]
if (!this.$v.form?.ageRating?.$dirty) return errors
!this.$v?.form?.ageRating?.minValue && errors.push('Age rating must be 0 or more')
return errors
},
releaseDateErrors (): string[] {
const errors = [] as string[]
if (!this.$v.form?.releaseDate?.$dirty) return errors
@ -438,7 +392,7 @@ export default Vue.extend({
const errors = [] as string[]
const formField = this.$v.form!![fieldName] as any
if (!formField.$dirty) return errors
!formField.required && errors.push('Required')
!formField.required && errors.push('Requiredb')
return errors
},
dialogReset (books: BookDto | BookDto[]) {
@ -447,32 +401,17 @@ export default Vue.extend({
if (Array.isArray(books) && books.length === 0) return
else if (this.$_.isEmpty(books)) return
if (Array.isArray(books) && books.length > 0) {
const readingDirection = this.$_.uniq(books.map(x => x.metadata.readingDirection))
this.form.readingDirection = readingDirection.length > 1 ? '' : readingDirection[0]
this.mixed.readingDirection = readingDirection.length > 1
const readingDirectionLock = this.$_.uniq(books.map(x => x.metadata.readingDirectionLock))
this.form.readingDirectionLock = readingDirectionLock.length > 1 ? false : readingDirectionLock[0]
const ageRating = this.$_.uniq(books.map(x => x.metadata.ageRating))
this.form.ageRating = ageRating.length > 1 ? undefined as unknown as number : ageRating[0]
this.mixed.ageRating = ageRating.length > 1
const ageRatingLock = this.$_.uniq(books.map(x => x.metadata.ageRatingLock))
this.form.ageRatingLock = ageRatingLock.length > 1 ? false : ageRatingLock[0]
const publisher = this.$_.uniq(books.map(x => x.metadata.publisher))
this.form.publisher = publisher.length > 1 ? '' : publisher[0]
this.mixed.publisher = publisher.length > 1
const publisherLock = this.$_.uniq(books.map(x => x.metadata.publisherLock))
this.form.publisherLock = publisherLock.length > 1 ? false : publisherLock[0]
this.form.authors = {}
const authorsLock = this.$_.uniq(books.map(x => x.metadata.authorsLock))
this.form.authorsLock = authorsLock.length > 1 ? false : authorsLock[0]
this.form.tags = []
const tagsLock = this.$_.uniq(books.map(x => x.metadata.tagsLock))
this.form.tagsLock = tagsLock.length > 1 ? false : tagsLock[0]
} else {
this.form.tags = []
const book = books as BookDto
this.$_.merge(this.form, book.metadata)
this.form.authors = groupAuthorsByRole(book.metadata.authors)
@ -494,22 +433,8 @@ export default Vue.extend({
validateForm (): any {
if (!this.$v.$invalid) {
const metadata = {
readingDirectionLock: this.form.readingDirectionLock,
ageRatingLock: this.form.ageRatingLock,
publisherLock: this.form.publisherLock,
authorsLock: this.form.authorsLock,
}
if (this.$v.form?.readingDirection?.$dirty) {
this.$_.merge(metadata, { readingDirection: this.form.readingDirection ? this.form.readingDirection : null })
}
if (this.$v.form?.ageRating?.$dirty) {
this.$_.merge(metadata, { ageRating: this.form.ageRating })
}
if (this.$v.form?.publisher?.$dirty) {
this.$_.merge(metadata, { publisher: this.form.publisher })
tagsLock: this.form.tagsLock,
}
if (this.$v.form?.authors?.$dirty) {
@ -520,6 +445,10 @@ export default Vue.extend({
})
}
if (this.$v.form?.tags?.$dirty) {
this.$_.merge(metadata, { tags: this.form.tags })
}
if (this.single) {
this.$_.merge(metadata, {
titleLock: this.form.titleLock,

View file

@ -23,13 +23,17 @@
{{ dialogTitle }}
</v-card-title>
<v-tabs :vertical="$vuetify.breakpoint.smAndUp">
<v-tabs :vertical="$vuetify.breakpoint.smAndUp" v-model="tab">
<v-tab class="justify-start">
<v-icon left class="hidden-xs-only">mdi-format-align-center</v-icon>
General
</v-tab>
<v-tab class="justify-start">
<v-icon left class="hidden-xs-only">mdi-tag-multiple</v-icon>
Tags
</v-tab>
<!-- General -->
<!-- Tab: General -->
<v-tab-item>
<v-card flat>
<v-container fluid>
@ -80,9 +84,30 @@
</v-col>
</v-row>
<!-- Status -->
<!-- Summary -->
<v-row v-if="single">
<v-col cols="12">
<v-textarea v-model="form.summary"
label="Summary"
filled
dense
@input="$v.form.summary.$touch()"
@change="form.summaryLock = true"
>
<template v-slot:prepend>
<v-icon :color="form.summaryLock ? 'secondary' : ''"
@click="form.summaryLock = !form.summaryLock"
>
{{ form.summaryLock ? 'mdi-lock' : 'mdi-lock-open' }}
</v-icon>
</template>
</v-textarea>
</v-col>
</v-row>
<v-row>
<v-col cols="auto">
<!-- Status -->
<v-col cols="6">
<v-select :items="seriesStatus"
v-model="form.status"
label="Status"
@ -100,11 +125,168 @@
</template>
</v-select>
</v-col>
<!-- Language -->
<v-col cols="6">
<v-text-field v-model="form.language"
label="Language"
filled
dense
:placeholder="mixed.language ? 'MIXED' : ''"
:error-messages="languageErrors"
@input="$v.form.language.$touch()"
@change="form.languageLock = true"
>
<template v-slot:prepend>
<v-icon :color="form.languageLock ? 'secondary' : ''"
@click="form.languageLock = !form.languageLock"
>
{{ form.languageLock ? 'mdi-lock' : 'mdi-lock-open' }}
</v-icon>
</template>
</v-text-field>
</v-col>
</v-row>
<!-- Reading Direction -->
<v-row>
<v-col cols="12">
<v-select v-model="form.readingDirection"
:items="readingDirections"
label="Reading Direction"
clearable
filled
:placeholder="mixed.readingDirection ? 'MIXED' : ''"
@input="$v.form.readingDirection.$touch()"
@change="form.readingDirectionLock = true"
>
<template v-slot:prepend>
<v-icon :color="form.readingDirectionLock ? 'secondary' : ''"
@click="form.readingDirectionLock = !form.readingDirectionLock"
>
{{ form.readingDirectionLock ? 'mdi-lock' : 'mdi-lock-open' }}
</v-icon>
</template>
</v-select>
</v-col>
</v-row>
<v-row>
<!-- Publisher -->
<v-col cols="6">
<v-text-field v-model="form.publisher"
label="Publisher"
filled
dense
:placeholder="mixed.publisher ? 'MIXED' : ''"
@input="$v.form.publisher.$touch()"
@change="form.publisherLock = true"
>
<template v-slot:prepend>
<v-icon :color="form.publisherLock ? 'secondary' : ''"
@click="form.publisherLock = !form.publisherLock"
>
{{ form.publisherLock ? 'mdi-lock' : 'mdi-lock-open' }}
</v-icon>
</template>
</v-text-field>
</v-col>
<!-- Age Rating -->
<v-col cols="6">
<v-text-field v-model="form.ageRating"
label="Age Rating"
clearable
filled
dense
type="number"
:placeholder="mixed.ageRating ? 'MIXED' : ''"
:error-messages="ageRatingErrors"
@input="$v.form.ageRating.$touch()"
@blur="$v.form.ageRating.$touch()"
@change="form.ageRatingLock = true"
>
<template v-slot:prepend>
<v-icon :color="form.ageRatingLock ? 'secondary' : ''"
@click="form.ageRatingLock = !form.ageRatingLock"
>
{{ form.ageRatingLock ? 'mdi-lock' : 'mdi-lock-open' }}
</v-icon>
</template>
</v-text-field>
</v-col>
</v-row>
</v-container>
</v-card>
</v-tab-item>
<!-- Tab: Tags -->
<v-tab-item>
<v-card flat>
<v-container fluid>
<v-alert v-if="!single"
type="warning"
outlined
dense
>
You are editing tags for multiple series. This will override existing tags of each series.
</v-alert>
<!-- Genres -->
<v-row>
<v-col cols="12">
<span class="text-body-2">Genres</span>
<v-combobox v-model="form.genres"
:items="genresAvailable"
@input="$v.form.genres.$touch()"
@change="form.genresLock = true"
hide-selected
chips
deletable-chips
multiple
filled
dense
>
<template v-slot:prepend>
<v-icon :color="form.genresLock ? 'secondary' : ''"
@click="form.genresLock = !form.genresLock"
>
{{ form.genresLock ? 'mdi-lock' : 'mdi-lock-open' }}
</v-icon>
</template>
</v-combobox>
</v-col>
</v-row>
<!-- Tags -->
<v-row>
<v-col cols="12">
<span class="text-body-2">Tags</span>
<v-combobox v-model="form.tags"
:items="tagsAvailable"
@input="$v.form.tags.$touch()"
@change="form.tagsLock = true"
hide-selected
chips
deletable-chips
multiple
filled
dense
>
<template v-slot:prepend>
<v-icon :color="form.tagsLock ? 'secondary' : ''"
@click="form.tagsLock = !form.tagsLock"
>
{{ form.tagsLock ? 'mdi-lock' : 'mdi-lock-open' }}
</v-icon>
</template>
</v-combobox>
</v-col>
</v-row>
</v-container>
</v-card>
</v-tab-item>
</v-tabs>
<v-card-actions class="hidden-xs-only">
@ -135,7 +317,12 @@
<script lang="ts">
import Vue from 'vue'
import { SeriesStatus } from '@/types/enum-series'
import { requiredIf } from 'vuelidate/lib/validators'
import { helpers, minValue, requiredIf } from 'vuelidate/lib/validators'
import { ReadingDirection } from '@/types/enum-books'
const tags = require('language-tags')
const validLanguage = (value: string) => !helpers.req(value) || tags.check(value)
export default Vue.extend({
name: 'EditSeriesDialog',
@ -144,6 +331,7 @@ export default Vue.extend({
modal: false,
snackbar: false,
snackText: '',
tab: 0,
form: {
status: '',
statusLock: false,
@ -151,10 +339,30 @@ export default Vue.extend({
titleLock: false,
titleSort: '',
titleSortLock: false,
summary: '',
summaryLock: false,
readingDirection: '',
readingDirectionLock: false,
publisher: '',
publisherLock: false,
ageRating: undefined as unknown as number,
ageRatingLock: false,
language: '',
languageLock: false,
genres: [],
genresLock: false,
tags: [],
tagsLock: false,
},
mixed: {
status: false,
readingDirection: false,
publisher: false,
ageRating: false,
language: false,
},
genresAvailable: [] as string[],
tagsAvailable: [] as string[],
}
},
props: {
@ -192,18 +400,50 @@ export default Vue.extend({
return this.single
}),
},
summary: {},
language: {
validLanguage: validLanguage,
},
genres: {},
tags: {},
ageRating: { minValue: minValue(0) },
readingDirection: {},
publisher: {},
},
},
async created () {
this.genresAvailable = await this.$komgaReferential.getGenres()
this.tagsAvailable = await this.$komgaReferential.getTags()
},
computed: {
single (): boolean {
return !Array.isArray(this.series)
},
readingDirections (): any[] {
return Object.keys(ReadingDirection).map(x => (
{
text: this.$_.capitalize(x.replace(/_/g, ' ')),
value: x,
}),
)
},
seriesStatus (): any[] {
return Object.keys(SeriesStatus).map(x => ({
text: this.$_.capitalize(x),
value: x,
}))
},
ageRatingErrors (): string[] {
const errors = [] as string[]
if (!this.$v.form?.ageRating?.$dirty) return errors
!this.$v?.form?.ageRating?.minValue && errors.push('Age rating must be 0 or more')
return errors
},
languageErrors (): string[] {
if (!this.$v.form?.language?.$dirty) return []
if (!this.$v?.form?.language?.validLanguage) return tags(this.form.language).errors().map((x: any) => x.message)
return []
},
dialogTitle (): string {
return this.single
? `Edit ${this.$_.get(this.series, 'metadata.title')}`
@ -219,6 +459,7 @@ export default Vue.extend({
return errors
},
dialogReset (series: SeriesDto | SeriesDto[]) {
this.tab = 0
this.$v.$reset()
if (Array.isArray(series) && series.length === 0) return
if (Array.isArray(series) && series.length > 0) {
@ -228,7 +469,47 @@ export default Vue.extend({
const statusLock = this.$_.uniq(series.map(x => x.metadata.statusLock))
this.form.statusLock = statusLock.length > 1 ? false : statusLock[0]
const readingDirection = this.$_.uniq(series.map(x => x.metadata.readingDirection))
this.form.readingDirection = readingDirection.length > 1 ? '' : readingDirection[0]
this.mixed.readingDirection = readingDirection.length > 1
const readingDirectionLock = this.$_.uniq(series.map(x => x.metadata.readingDirectionLock))
this.form.readingDirectionLock = readingDirectionLock.length > 1 ? false : readingDirectionLock[0]
const ageRating = this.$_.uniq(series.map(x => x.metadata.ageRating))
this.form.ageRating = ageRating.length > 1 ? undefined as unknown as number : ageRating[0]
this.mixed.ageRating = ageRating.length > 1
const ageRatingLock = this.$_.uniq(series.map(x => x.metadata.ageRatingLock))
this.form.ageRatingLock = ageRatingLock.length > 1 ? false : ageRatingLock[0]
const publisher = this.$_.uniq(series.map(x => x.metadata.publisher))
this.form.publisher = publisher.length > 1 ? '' : publisher[0]
this.mixed.publisher = publisher.length > 1
const publisherLock = this.$_.uniq(series.map(x => x.metadata.publisherLock))
this.form.publisherLock = publisherLock.length > 1 ? false : publisherLock[0]
const language = this.$_.uniq(series.map(x => x.metadata.language))
this.form.language = language.length > 1 ? '' : language[0]
this.mixed.language = language.length > 1
const languageLock = this.$_.uniq(series.map(x => x.metadata.languageLock))
this.form.languageLock = languageLock.length > 1 ? false : languageLock[0]
this.form.genres = []
const genresLock = this.$_.uniq(series.map(x => x.metadata.genresLock))
this.form.genresLock = genresLock.length > 1 ? false : genresLock[0]
this.form.tags = []
const tagsLock = this.$_.uniq(series.map(x => x.metadata.tagsLock))
this.form.tagsLock = tagsLock.length > 1 ? false : tagsLock[0]
} else {
this.form.genres = []
this.form.tags = []
this.$_.merge(this.form, (series as SeriesDto).metadata)
}
},
@ -249,16 +530,47 @@ export default Vue.extend({
if (!this.$v.$invalid) {
const metadata = {
statusLock: this.form.statusLock,
readingDirectionLock: this.form.readingDirectionLock,
ageRatingLock: this.form.ageRatingLock,
publisherLock: this.form.publisherLock,
languageLock: this.form.languageLock,
genresLock: this.form.genresLock,
tagsLock: this.form.tagsLock,
}
if (this.$v.form?.status?.$dirty) {
this.$_.merge(metadata, { status: this.form.status })
}
if (this.$v.form?.readingDirection?.$dirty) {
this.$_.merge(metadata, { readingDirection: this.form.readingDirection ? this.form.readingDirection : null })
}
if (this.$v.form?.ageRating?.$dirty) {
this.$_.merge(metadata, { ageRating: this.form.ageRating })
}
if (this.$v.form?.publisher?.$dirty) {
this.$_.merge(metadata, { publisher: this.form.publisher })
}
if (this.$v.form?.genres?.$dirty) {
this.$_.merge(metadata, { genres: this.form.genres })
}
if (this.$v.form?.tags?.$dirty) {
this.$_.merge(metadata, { tags: this.form.tags })
}
if (this.$v.form?.language?.$dirty) {
this.$_.merge(metadata, { language: this.form.language })
}
if (this.single) {
this.$_.merge(metadata, {
titleLock: this.form.titleLock,
titleSortLock: this.form.titleSortLock,
summaryLock: this.form.summaryLock,
})
if (this.$v.form?.title?.$dirty) {
@ -268,6 +580,10 @@ export default Vue.extend({
if (this.$v.form?.titleSort?.$dirty) {
this.$_.merge(metadata, { titleSort: this.form.titleSort })
}
if (this.$v.form?.summary?.$dirty) {
this.$_.merge(metadata, { summary: this.form.summary })
}
}
return metadata

View file

@ -27,4 +27,28 @@ export default class KomgaReferentialService {
throw new Error(msg)
}
}
async getGenres (): Promise<string[]> {
try {
return (await this.http.get('/api/v1/genres')).data
} catch (e) {
let msg = 'An error occurred while trying to retrieve genres'
if (e.response.data.message) {
msg += `: ${e.response.data.message}`
}
throw new Error(msg)
}
}
async getTags (): Promise<string[]> {
try {
return (await this.http.get('/api/v1/tags')).data
} catch (e) {
let msg = 'An error occurred while trying to retrieve tags'
if (e.response.data.message) {
msg += `: ${e.response.data.message}`
}
throw new Error(msg)
}
}
}

View file

@ -48,16 +48,12 @@ interface BookMetadataDto {
numberLock: boolean,
numberSort: number,
numberSortLock: boolean,
readingDirection: string,
readingDirectionLock: boolean,
publisher: string,
publisherLock: boolean,
ageRating: number,
ageRatingLock: boolean,
releaseDate: string,
releaseDateLock: boolean,
authors: AuthorDto[],
authorsLock: boolean,
tags: String[],
tagsLock: boolean
}
interface ReadProgressDto {
@ -76,16 +72,12 @@ interface BookMetadataUpdateDto {
numberLock?: boolean,
numberSort?: number,
numberSortLock?: boolean,
readingDirection?: string,
readingDirectionLock?: boolean,
publisher?: string,
publisherLock?: boolean,
ageRating?: number,
ageRatingLock?: boolean,
releaseDate?: string,
releaseDateLock?: boolean,
authors?: AuthorDto[],
authorsLock?: boolean,
tags?: String[],
tagsLock?: boolean
}
interface AuthorDto {

View file

@ -19,7 +19,21 @@ interface SeriesMetadata {
title: string,
titleLock: boolean,
titleSort: string,
titleSortLock: boolean
titleSortLock: boolean,
summary: string,
summaryLock: boolean,
readingDirection: string,
readingDirectionLock: boolean,
publisher: string,
publisherLock: boolean,
ageRating: number,
ageRatingLock: boolean,
language: string,
languageLock: boolean,
genres: string[],
genresLock: boolean,
tags: String[],
tagsLock: boolean
}
interface SeriesMetadataUpdateDto {
@ -28,5 +42,19 @@ interface SeriesMetadataUpdateDto {
title?: string,
titleLock?: boolean,
titleSort?: string,
titleSortLock?: boolean
titleSortLock?: boolean,
summary?: string,
summaryLock?: boolean,
readingDirection?: string,
readingDirectionLock?: boolean,
publisher?: string,
publisherLock?: boolean,
ageRating?: number,
ageRatingLock?: boolean,
language?: string,
languageLock?: boolean,
genres?: string[],
genresLock?: boolean,
tags?: String[],
tagsLock?: boolean
}

View file

@ -238,7 +238,7 @@
timeout="3000"
>
<p class="text-body-1 text-center ma-0">
{{ readingDirectionText }}{{ notificationReadingDirection.fromMetadata ? ' (from book metadata)' : '' }}
{{ readingDirectionText }}{{ notificationReadingDirection.fromMetadata ? ' (from series metadata)' : '' }}
</p>
</v-snackbar>
@ -576,9 +576,9 @@ export default Vue.extend({
}
// set non-persistent reading direction if exists in metadata
if (this.book.metadata.readingDirection in ReadingDirection && this.readingDirection !== this.book.metadata.readingDirection) {
if (this.series.metadata.readingDirection in ReadingDirection && this.readingDirection !== this.series.metadata.readingDirection) {
// bypass setter so cookies aren't set
this.settings.readingDirection = this.book.metadata.readingDirection as ReadingDirection
this.settings.readingDirection = this.series.metadata.readingDirection as ReadingDirection
this.sendNotificationReadingDirection(true)
} else {
this.sendNotificationReadingDirection(false)

View file

@ -86,7 +86,6 @@
<v-row class="text-body-2">
<v-col>
<span class="mr-3">#{{ book.metadata.number }}</span>
<badge v-if="book.metadata.ageRating">{{ book.metadata.ageRating }}+</badge>
</v-col>
<v-col cols="auto" v-if="book.metadata.releaseDate">
{{ book.metadata.releaseDate | moment('MMMM DD, YYYY') }}
@ -95,11 +94,6 @@
<v-divider/>
<v-row class="text-body-2" v-if="book.metadata.publisher">
<v-col cols="6" sm="4" md="2">PUBLISHER</v-col>
<v-col>{{ book.metadata.publisher }}</v-col>
</v-row>
<v-row class="text-body-2"
v-for="(names, key) in authorsByRole"
:key="key"
@ -121,6 +115,20 @@
</v-col>
</v-row>
<v-row v-if="book.metadata.tags.length > 0">
<v-col cols="6" sm="4" md="2" class="text-body-2 py-1">TAGS</v-col>
<v-col class="text-body-2 text-capitalize py-1">
<v-chip v-for="(t, i) in book.metadata.tags"
:key="i"
class="mr-2"
label
small
outlined
>{{ t }}
</v-chip>
</v-col>
</v-row>
<v-row v-if="$vuetify.breakpoint.name !== 'xs'">
<v-col>
<read-lists-expansion-panels :read-lists="readLists"/>
@ -181,12 +189,6 @@
</v-col>
</v-row>
<v-row v-if="book.metadata.readingDirection">
<v-col cols="2" md="1" lg="1" xl="1" class="text-body-2">READING DIRECTION</v-col>
<v-col cols="10" class="text-body-2">{{ readingDirection }}
</v-col>
</v-row>
<v-row align="center">
<v-col cols="2" md="1" lg="1" xl="1" class="text-body-2">FILE</v-col>
<v-col cols="10" class="text-body-2">{{ book.url }}</v-col>
@ -198,7 +200,6 @@
</template>
<script lang="ts">
import Badge from '@/components/Badge.vue'
import BookActionsMenu from '@/components/menus/BookActionsMenu.vue'
import ItemCard from '@/components/ItemCard.vue'
import ToolbarSticky from '@/components/bars/ToolbarSticky.vue'
@ -214,7 +215,7 @@ import ReadListsExpansionPanels from '@/components/ReadListsExpansionPanels.vue'
export default Vue.extend({
name: 'BrowseBook',
components: { ToolbarSticky, Badge, ItemCard, BookActionsMenu, ReadListsExpansionPanels },
components: { ToolbarSticky, ItemCard, BookActionsMenu, ReadListsExpansionPanels },
data: () => {
return {
book: {} as BookDto,
@ -281,9 +282,6 @@ export default Vue.extend({
readProgressPercentage (): number {
return getReadProgressPercentage(this.book)
},
readingDirection (): string {
return this.$_.capitalize(this.book.metadata.readingDirection.replace(/_/g, ' '))
},
previousId (): string {
return this.siblingPrevious?.id?.toString() || '0'
},

View file

@ -63,17 +63,66 @@
></item-card>
</v-col>
<v-col cols="8">
<v-col cols="8" v-if="series.metadata">
<v-row>
<v-col>
<div class="text-h5" v-if="$_.get(series, 'metadata.title')">{{ series.metadata.title }}</div>
</v-col>
</v-row>
<v-row class="text-body-2">
<v-col>
<v-chip label small>{{ series.metadata.status }}</v-chip>
<v-chip label small v-if="series.metadata.ageRating" class="ml-2">{{
series.metadata.ageRating
}}+
</v-chip>
<v-chip label small v-if="series.metadata.language" class="ml-2">{{ languageDisplay }}</v-chip>
<v-chip label small v-if="series.metadata.readingDirection" class="ml-2">{{ readingDirection }}</v-chip>
</v-col>
</v-row>
<v-row class="mt-3">
<v-col>
<div class="text-body-1"
style="white-space: pre-wrap"
>{{ series.metadata.summary }}
</div>
</v-col>
</v-row>
<v-row>
<v-col cols="auto" class="text-body-2">STATUS</v-col>
<v-col cols="auto" class="text-body-2 text-capitalize" v-if="series.metadata">{{
series.metadata.status.toLowerCase() }}
<v-col cols="6" sm="4" md="2" class="text-body-2 py-1">PUBLISHER</v-col>
<v-col class="text-body-2 text-capitalize py-1" v-if="series.metadata.publisher">
{{ series.metadata.publisher }}
</v-col>
</v-row>
<v-row v-if="series.metadata.genres.length > 0">
<v-col cols="6" sm="4" md="2" class="text-body-2 py-1">GENRE</v-col>
<v-col class="text-body-2 text-capitalize py-1">
<v-chip v-for="(t, i) in series.metadata.genres"
:key="i"
class="mr-2"
label
small
outlined
>{{ t }}
</v-chip>
</v-col>
</v-row>
<v-row v-if="series.metadata.tags.length > 0">
<v-col cols="6" sm="4" md="2" class="text-body-2 py-1">TAGS</v-col>
<v-col class="text-body-2 text-capitalize py-1">
<v-chip v-for="(t, i) in series.metadata.tags"
:key="i"
class="mr-2"
label
small
outlined
>{{ t }}
</v-chip>
</v-col>
</v-row>
@ -205,6 +254,12 @@ export default Vue.extend({
return 15
}
},
readingDirection (): string {
return this.$_.capitalize(this.series.metadata.readingDirection.replace(/_/g, ' '))
},
languageDisplay (): string {
return tags(this.series.metadata.language).language().descriptions()[0]
},
},
props: {
seriesId: {