mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2024-12-24 19:11:44 +01:00
350 lines
12 KiB
Vue
350 lines
12 KiB
Vue
<template>
|
|
<div class="w-full h-full relative">
|
|
<form class="w-full h-full" @submit.prevent="submitForm">
|
|
<div id="formWrapper" class="px-4 py-6 details-form-wrapper w-full overflow-hidden overflow-y-auto">
|
|
<div class="flex -mx-1">
|
|
<div class="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">
|
|
<ui-text-input-with-label ref="subtitleInput" v-model="details.subtitle" label="Subtitle" />
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex mt-2 -mx-1">
|
|
<div class="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">
|
|
<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">
|
|
<ui-multi-select-query-input ref="seriesSelect" v-model="seriesItems" text-key="displayName" label="Series" readonly show-edit @edit="editSeriesItem" @add="addNewSeries" />
|
|
</div>
|
|
</div>
|
|
|
|
<ui-textarea-with-label ref="descriptionInput" v-model="details.description" :rows="3" label="Description" class="mt-2" />
|
|
|
|
<div class="flex mt-2 -mx-1">
|
|
<div class="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">
|
|
<ui-multi-select ref="tagsSelect" v-model="newTags" label="Tags" :items="tags" />
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex mt-2 -mx-1">
|
|
<div class="w-1/2 px-1">
|
|
<ui-multi-select ref="narratorsSelect" v-model="details.narrators" label="Narrators" :items="narrators" />
|
|
</div>
|
|
<div class="w-1/4 px-1">
|
|
<ui-text-input-with-label ref="isbnInput" v-model="details.isbn" label="ISBN" />
|
|
</div>
|
|
<div class="w-1/4 px-1">
|
|
<ui-text-input-with-label ref="asinInput" v-model="details.asin" label="ASIN" />
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex mt-2 -mx-1">
|
|
<div class="w-1/2 px-1">
|
|
<ui-text-input-with-label ref="publisherInput" v-model="details.publisher" label="Publisher" />
|
|
</div>
|
|
<div class="w-1/4 px-1">
|
|
<ui-text-input-with-label ref="languageInput" v-model="details.language" label="Language" />
|
|
</div>
|
|
<div class="flex-grow px-1 pt-6">
|
|
<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>
|
|
</div>
|
|
</form>
|
|
|
|
<div v-if="showSeriesForm" class="absolute top-0 left-0 z-20 w-full h-full bg-black bg-opacity-50 rounded-lg flex items-center justify-center" @click="cancelSeriesForm">
|
|
<div class="absolute top-0 right-0 p-4">
|
|
<span class="material-icons text-gray-200 hover:text-white text-4xl cursor-pointer">close</span>
|
|
</div>
|
|
<form @submit.prevent="submitSeriesForm">
|
|
<div class="bg-bg rounded-lg p-8" @click.stop>
|
|
<div class="flex">
|
|
<div class="flex-grow p-1 min-w-80">
|
|
<ui-input-dropdown ref="newSeriesSelect" v-model="selectedSeries.name" :items="existingSeriesNames" :disabled="!selectedSeries.id.startsWith('new')" label="Series Name" />
|
|
</div>
|
|
<div class="w-40 p-1">
|
|
<ui-text-input-with-label v-model="selectedSeries.sequence" label="Sequence" />
|
|
</div>
|
|
</div>
|
|
<div class="flex justify-end mt-2 p-1">
|
|
<ui-btn type="submit">Save</ui-btn>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script>
|
|
export default {
|
|
props: {
|
|
libraryItem: {
|
|
type: Object,
|
|
default: () => {}
|
|
}
|
|
},
|
|
data() {
|
|
return {
|
|
selectedSeries: {},
|
|
showSeriesForm: false,
|
|
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 || {}
|
|
},
|
|
existingSeriesNames() {
|
|
// Only show series names not already selected
|
|
var alreadySelectedSeriesIds = this.details.series.map((se) => se.id)
|
|
return this.series.filter((se) => !alreadySelectedSeriesIds.includes(se.id)).map((se) => se.name)
|
|
},
|
|
seriesItems: {
|
|
get() {
|
|
return this.details.series.map((se) => {
|
|
return {
|
|
displayName: se.sequence ? `${se.name} #${se.sequence}` : se.name,
|
|
...se
|
|
}
|
|
})
|
|
},
|
|
set(val) {
|
|
this.details.series = val
|
|
}
|
|
}
|
|
},
|
|
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()
|
|
}
|
|
},
|
|
cancelSeriesForm() {
|
|
this.showSeriesForm = false
|
|
},
|
|
editSeriesItem(series) {
|
|
var _series = this.details.series.find((se) => se.id === series.id)
|
|
if (!_series) return
|
|
this.selectedSeries = {
|
|
..._series
|
|
}
|
|
this.showSeriesForm = true
|
|
},
|
|
addNewSeries() {
|
|
this.selectedSeries = {
|
|
id: `new-${Date.now()}`,
|
|
name: '',
|
|
sequence: ''
|
|
}
|
|
this.showSeriesForm = true
|
|
},
|
|
submitSeriesForm() {
|
|
if (!this.selectedSeries.name) {
|
|
this.$toast.error('Must enter a series')
|
|
return
|
|
}
|
|
if (this.$refs.newSeriesSelect) {
|
|
this.$refs.newSeriesSelect.blur()
|
|
}
|
|
var existingSeriesIndex = this.details.series.findIndex((se) => se.id === this.selectedSeries.id)
|
|
|
|
var seriesSameName = this.series.find((se) => se.name.toLowerCase() === this.selectedSeries.name.toLowerCase())
|
|
if (existingSeriesIndex < 0 && seriesSameName) {
|
|
this.selectedSeries.id = seriesSameName.id
|
|
}
|
|
|
|
if (existingSeriesIndex >= 0) {
|
|
this.details.series.splice(existingSeriesIndex, 1, { ...this.selectedSeries })
|
|
} else {
|
|
this.details.series.push({
|
|
...this.selectedSeries
|
|
})
|
|
}
|
|
|
|
this.showSeriesForm = false
|
|
},
|
|
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> |