audiobookshelf/client/components/widgets/BookDetailsEdit.vue

263 lines
9.0 KiB
Vue
Raw Normal View History

<template>
<div class="w-full h-full relative">
<form class="w-full h-full px-2 md:px-4 py-6" @submit.prevent="submitForm">
<div class="flex flex-wrap -mx-1">
<div class="w-full md:w-1/2 px-1">
<ui-text-input-with-label ref="titleInput" v-model="details.title" label="Title" />
</div>
<div class="flex-grow px-1 mt-2 md:mt-0">
<ui-text-input-with-label ref="subtitleInput" v-model="details.subtitle" label="Subtitle" />
</div>
</div>
<div class="flex flex-wrap mt-2 -mx-1">
<div class="w-full md:w-3/4 px-1">
<!-- Authors filter only contains authors in this library, use query input to query all authors -->
<ui-multi-select-query-input ref="authorsSelect" v-model="details.authors" label="Authors" endpoint="authors/search" />
</div>
<div class="flex-grow px-1 mt-2 md:mt-0">
<ui-text-input-with-label ref="publishYearInput" v-model="details.publishedYear" type="number" label="Publish Year" />
</div>
</div>
<div class="flex mt-2 -mx-1">
<div class="flex-grow px-1">
<widgets-series-input-widget v-model="details.series" />
</div>
</div>
<ui-textarea-with-label ref="descriptionInput" v-model="details.description" :rows="3" label="Description" class="mt-2" />
<div class="flex flex-wrap mt-2 -mx-1">
<div class="w-full md:w-1/2 px-1">
<ui-multi-select ref="genresSelect" v-model="details.genres" label="Genres" :items="genres" />
</div>
<div class="flex-grow px-1 mt-2 md:mt-0">
<ui-multi-select ref="tagsSelect" v-model="newTags" label="Tags" :items="tags" />
</div>
</div>
<div class="flex flex-wrap mt-2 -mx-1">
<div class="w-full md:w-1/2 px-1">
<ui-multi-select ref="narratorsSelect" v-model="details.narrators" label="Narrators" :items="narrators" />
</div>
<div class="w-1/2 md:w-1/4 px-1 mt-2 md:mt-0">
<ui-text-input-with-label ref="isbnInput" v-model="details.isbn" label="ISBN" />
</div>
<div class="w-1/2 md:w-1/4 px-1 mt-2 md:mt-0">
<ui-text-input-with-label ref="asinInput" v-model="details.asin" label="ASIN" />
</div>
</div>
<div class="flex flex-wrap mt-2 -mx-1">
<div class="w-full md:w-1/2 px-1">
<ui-text-input-with-label ref="publisherInput" v-model="details.publisher" label="Publisher" />
</div>
<div class="w-1/2 md:w-1/4 px-1 mt-2 md:mt-0">
<ui-text-input-with-label ref="languageInput" v-model="details.language" label="Language" />
</div>
<div class="flex-grow px-1 pt-6 mt-2 md:mt-0">
<div class="flex justify-center">
<ui-checkbox v-model="details.explicit" label="Explicit" checkbox-bg="primary" border-color="gray-600" label-class="pl-2 text-base font-semibold" />
</div>
</div>
</div>
</form>
</div>
</template>
<script>
export default {
props: {
libraryItem: {
type: Object,
default: () => {}
}
},
data() {
return {
details: {
title: null,
subtitle: null,
description: null,
authors: [],
narrators: [],
series: [],
publishedYear: null,
publisher: null,
language: null,
isbn: null,
asin: null,
genres: [],
explicit: false
},
newTags: []
}
},
watch: {
libraryItem: {
immediate: true,
handler(newVal) {
if (newVal) this.init()
}
}
},
computed: {
media() {
return this.libraryItem ? this.libraryItem.media || {} : {}
},
mediaMetadata() {
return this.media.metadata || {}
},
genres() {
return this.filterData.genres || []
},
tags() {
return this.filterData.tags || []
},
series() {
return this.filterData.series || []
},
narrators() {
return this.filterData.narrators || []
},
filterData() {
return this.$store.state.libraries.filterData || {}
}
},
methods: {
getDetails() {
this.forceBlur()
return this.checkForChanges()
},
getTitleAndAuthorName() {
this.forceBlur()
return {
title: this.details.title,
author: (this.details.authors || []).map((au) => au.name).join(', ')
}
},
mapBatchDetails(batchDetails) {
for (const key in batchDetails) {
if (key === 'tags') {
this.newTags = [...batchDetails.tags]
} else if (key === 'genres' || key === 'narrators') {
this.details[key] = [...batchDetails[key]]
} else if (key === 'authors' || key === 'series') {
this.details[key] = batchDetails[key].map((i) => ({ ...i }))
} else {
this.details[key] = batchDetails[key]
}
}
},
forceBlur() {
if (this.$refs.titleInput) this.$refs.titleInput.blur()
if (this.$refs.subtitleInput) this.$refs.subtitleInput.blur()
if (this.$refs.publishYearInput) this.$refs.publishYearInput.blur()
if (this.$refs.descriptionInput) this.$refs.descriptionInput.blur()
if (this.$refs.isbnInput) this.$refs.isbnInput.blur()
if (this.$refs.asinInput) this.$refs.asinInput.blur()
if (this.$refs.publisherInput) this.$refs.publisherInput.blur()
if (this.$refs.languageInput) this.$refs.languageInput.blur()
if (this.$refs.authorsSelect && this.$refs.authorsSelect.isFocused) {
this.$refs.authorsSelect.forceBlur()
}
if (this.$refs.narratorsSelect && this.$refs.narratorsSelect.isFocused) {
this.$refs.narratorsSelect.forceBlur()
}
if (this.$refs.genresSelect && this.$refs.genresSelect.isFocused) {
this.$refs.genresSelect.forceBlur()
}
if (this.$refs.tagsSelect && this.$refs.tagsSelect.isFocused) {
this.$refs.tagsSelect.forceBlur()
}
},
stringArrayEqual(array1, array2) {
// return false if different
if (array1.length !== array2.length) return false
for (var item of array1) {
if (!array2.includes(item)) return false
}
return true
},
objectArrayEqual(array1, array2) {
const isIterable = (value) => {
return Symbol.iterator in Object(value)
}
if (!isIterable(array1) || !isIterable(array2)) {
console.error(array1, array2)
throw new Error('Invalid arrays passed in')
}
// array of objects with id key
if (array1.length !== array2.length) return false
for (var item of array1) {
var matchingItem = array2.find((a) => a.id === item.id)
if (!matchingItem) return false
for (var key in item) {
if (item[key] !== matchingItem[key]) {
// console.log('Object array item keys changed', key, item[key], matchingItem[key])
return false
}
}
}
return true
},
checkForChanges() {
var metadata = {}
for (const key in this.details) {
var newValue = this.details[key]
var oldValue = this.mediaMetadata[key]
// Key cleared out or key first populated
if ((!newValue && oldValue) || (newValue && !oldValue)) {
metadata[key] = newValue
} else if (key === 'narrators' || key === 'genres') {
// Check array of strings
if (!this.stringArrayEqual(newValue, oldValue)) {
metadata[key] = [...newValue]
}
} else if (key === 'authors' || key === 'series') {
if (!this.objectArrayEqual(newValue, oldValue)) {
metadata[key] = newValue.map((v) => ({ ...v }))
}
} else if (newValue && newValue != oldValue) {
// Intentional !=
metadata[key] = newValue
}
}
var updatePayload = {}
if (!!Object.keys(metadata).length) updatePayload.metadata = metadata
if (!this.stringArrayEqual(this.newTags, this.media.tags || [])) {
updatePayload.tags = [...this.newTags]
}
return {
updatePayload,
hasChanges: !!Object.keys(updatePayload).length
}
},
init() {
this.details.title = this.mediaMetadata.title
this.details.subtitle = this.mediaMetadata.subtitle
this.details.description = this.mediaMetadata.description
this.details.authors = (this.mediaMetadata.authors || []).map((se) => ({ ...se }))
this.details.narrators = [...(this.mediaMetadata.narrators || [])]
this.details.genres = [...(this.mediaMetadata.genres || [])]
this.details.series = (this.mediaMetadata.series || []).map((se) => ({ ...se }))
this.details.publishedYear = this.mediaMetadata.publishedYear
this.details.publisher = this.mediaMetadata.publisher || null
this.details.language = this.mediaMetadata.language || null
this.details.isbn = this.mediaMetadata.isbn || null
this.details.asin = this.mediaMetadata.asin || null
this.details.explicit = !!this.mediaMetadata.explicit
this.newTags = [...(this.media.tags || [])]
},
submitForm() {
this.$emit('submit')
}
},
mounted() {}
}
</script>