2021-08-18 00:01:11 +02:00
< template >
2021-09-06 21:13:01 +02:00
< div class = "w-full h-full relative" >
2021-09-06 23:11:37 +02:00
< form class = "w-full h-full" @submit.prevent ="submitForm" >
< div ref = "formWrapper" class = "px-4 py-6 details-form-wrapper w-full overflow-hidden overflow-y-auto" >
2022-01-10 01:37:16 +01:00
< div class = "flex -mx-1" >
< div class = "w-1/2 px-1" >
< ui -text -input -with -label v -model = " details.title " label = "Title" / >
< / div >
< div class = "flex-grow px-1" >
< ui -text -input -with -label v -model = " details.subtitle " label = "Subtitle" / >
< / div >
< / div >
2021-09-05 02:58:39 +02:00
2021-09-06 21:13:01 +02:00
< div class = "flex mt-2 -mx-1" >
< div class = "w-3/4 px-1" >
2022-03-12 02:46:32 +01:00
< ui -multi -select -query -input ref = "authorsSelect" v -model = " details.authors " label = "Authors" endpoint = "authors/search" / >
2021-09-06 21:13:01 +02:00
< / div >
< div class = "flex-grow px-1" >
< ui -text -input -with -label v -model = " details.publishYear " type = "number" label = "Publish Year" / >
< / div >
2021-08-20 02:14:24 +02:00
< / div >
2021-08-18 00:01:11 +02:00
2021-09-06 21:13:01 +02:00
< div class = "flex mt-2 -mx-1" >
< div class = "flex-grow px-1" >
2022-03-12 02:46:32 +01:00
< ui -multi -select -query -input ref = "seriesSelect" v -model = " seriesItems " text -key = " displayName " label = "Series" readonly show -edit @edit ="editSeriesItem" @add ="addNewSeries" / >
2021-09-06 21:13:01 +02:00
< / div >
2021-08-25 03:24:40 +02:00
< / div >
2021-08-19 18:31:03 +02:00
2021-09-06 21:13:01 +02:00
< ui -textarea -with -label v -model = " details.description " :rows ="3" label = "Description" class = "mt-2" / >
2021-08-19 18:31:03 +02:00
2021-09-06 21:13:01 +02:00
< div class = "flex mt-2 -mx-1" >
< div class = "w-1/2 px-1" >
2021-12-02 02:07:03 +01:00
< ui -multi -select ref = "genresSelect" v -model = " details.genres " label = "Genres" :items ="genres" / >
2021-09-06 21:13:01 +02:00
< / div >
< div class = "flex-grow px-1" >
2021-12-02 02:07:03 +01:00
< ui -multi -select ref = "tagsSelect" v -model = " newTags " label = "Tags" :items ="tags" / >
2021-09-06 21:13:01 +02:00
< / div >
2021-08-22 15:52:37 +02:00
< / div >
2021-08-18 00:01:11 +02:00
2021-09-06 21:13:01 +02:00
< div class = "flex mt-2 -mx-1" >
2021-11-09 03:05:12 +01:00
< div class = "w-1/3 px-1" >
2021-10-07 04:08:52 +02:00
< ui -text -input -with -label v -model = " details.narrator " label = "Narrator" / >
2021-09-06 21:13:01 +02:00
< / div >
2021-11-09 03:05:12 +01:00
< div class = "w-1/3 px-1" >
< ui -text -input -with -label v -model = " details.publisher " label = "Publisher" / >
< / div >
< div class = "flex-grow px-1" >
2022-01-10 01:37:16 +01:00
< ui -text -input -with -label v -model = " details.language " label = "Language" / >
< / div >
< / div >
< div class = "flex mt-2 -mx-1" >
< div class = "w-1/3 px-1" >
2021-11-09 03:05:12 +01:00
< ui -text -input -with -label v -model = " details.isbn " label = "ISBN" / >
< / div >
2022-01-10 01:37:16 +01:00
< div class = "w-1/3 px-1" >
< ui -text -input -with -label v -model = " details.asin " label = "ASIN" / >
< / div >
2021-09-05 02:58:39 +02:00
< / div >
2021-09-06 23:11:37 +02:00
< / div >
2021-09-05 02:58:39 +02:00
2021-09-06 23:11:37 +02:00
< div class = "absolute bottom-0 left-0 w-full py-4 bg-bg" : class = "isScrollable ? 'box-shadow-md-up' : 'box-shadow-sm-up border-t border-primary border-opacity-50'" >
2021-10-27 01:35:37 +02:00
< div class = "flex items-center px-4" >
2022-03-11 01:45:02 +01:00
< ui -btn v-if ="userCanDelete" color="error" type="button" class="h-8" :padding-x="3" small @click.stop.prevent="removeItem" > Remove < / ui -btn >
2021-09-29 17:16:38 +02:00
2021-10-27 01:35:37 +02:00
< div class = "flex-grow" / >
2021-11-04 23:35:59 +01:00
< ui -tooltip v-if ="!isMissing" text="(Root User Only) Save a NFO metadata file in your audiobooks directory" direction="bottom" class="mr-4 hidden sm:block" >
2021-09-29 17:16:38 +02:00
< ui -btn v-if ="isRootUser" :loading="savingMetadata" color="bg" type="button" class="h-full" small @click.stop.prevent="saveMetadata" > Save Metadata < / ui -btn >
< / u i - t o o l t i p >
2022-02-15 23:15:09 +01:00
< ui -tooltip :disabled ="!!quickMatching" : text = "`(Root User Only) Populate empty book details & cover with first book result from '${libraryProvider}'. Does not overwrite details.`" direction = "bottom" class = "mr-4" >
< ui -btn v-if ="isRootUser" :loading="quickMatching" color="bg" type="button" class="h-full" small @click.stop.prevent="quickMatch" > Quick Match < / ui -btn >
< / u i - t o o l t i p >
2021-10-27 01:35:37 +02:00
< ui -tooltip :disabled ="!!libraryScan" text = "(Root User Only) Rescan audiobook including metadata" direction = "bottom" class = "mr-4" >
2021-10-23 23:49:34 +02:00
< ui -btn v-if ="isRootUser" :loading="rescanning" :disabled="!!libraryScan" color="bg" type="button" class="h-full" small @click.stop.prevent="rescan" > Re -Scan < / ui -btn >
2021-10-01 01:52:32 +02:00
< / u i - t o o l t i p >
2021-09-06 23:11:37 +02:00
< ui -btn type = "submit" > Submit < / u i - b t n >
< / div >
2021-08-18 00:01:11 +02:00
< / div >
2021-09-06 23:11:37 +02:00
< / form >
2022-03-12 02:46:32 +01:00
< 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 < / u i - b t n >
< / div >
< / div >
< / form >
< / div >
2021-08-18 00:01:11 +02:00
< / div >
< / template >
< script >
export default {
props : {
processing : Boolean ,
2022-03-11 01:45:02 +01:00
libraryItem : {
2021-08-18 00:01:11 +02:00
type : Object ,
default : ( ) => { }
}
} ,
data ( ) {
return {
2022-03-12 02:46:32 +01:00
selectedSeries : { } ,
showSeriesForm : false ,
2021-08-18 00:01:11 +02:00
details : {
title : null ,
2021-09-05 02:58:39 +02:00
subtitle : null ,
2021-08-18 00:01:11 +02:00
description : null ,
2021-08-19 18:31:03 +02:00
author : null ,
2021-10-07 04:08:52 +02:00
narrator : null ,
2021-08-19 18:31:03 +02:00
series : null ,
2021-08-20 02:14:24 +02:00
publishYear : null ,
2021-11-09 03:05:12 +01:00
publisher : null ,
2022-01-10 01:37:16 +01:00
language : null ,
2021-11-09 03:05:12 +01:00
isbn : null ,
2022-01-10 01:37:16 +01:00
asin : null ,
2021-08-20 00:29:36 +02:00
genres : [ ]
2021-08-18 00:01:11 +02:00
} ,
2021-08-22 15:52:37 +02:00
newTags : [ ] ,
2021-09-06 21:13:01 +02:00
resettingProgress : false ,
2021-09-29 17:16:38 +02:00
isScrollable : false ,
2021-10-01 01:52:32 +02:00
savingMetadata : false ,
2022-02-15 23:15:09 +01:00
rescanning : false ,
quickMatching : false
2021-08-18 00:01:11 +02:00
}
} ,
watch : {
2022-03-11 01:45:02 +01:00
libraryItem : {
2021-08-18 00:01:11 +02:00
immediate : true ,
handler ( newVal ) {
if ( newVal ) this . init ( )
}
}
} ,
computed : {
isProcessing : {
get ( ) {
return this . processing
} ,
set ( val ) {
this . $emit ( 'update:processing' , val )
}
} ,
2021-09-29 17:16:38 +02:00
isRootUser ( ) {
return this . $store . getters [ 'user/getIsRoot' ]
} ,
2021-10-27 00:55:48 +02:00
isMissing ( ) {
2022-03-11 01:45:02 +01:00
return ! ! this . libraryItem && ! ! this . libraryItem . isMissing
2021-10-27 00:55:48 +02:00
} ,
2022-03-11 01:45:02 +01:00
libraryItemId ( ) {
return this . libraryItem ? this . libraryItem . id : null
2021-08-18 00:01:11 +02:00
} ,
2022-03-11 01:45:02 +01:00
media ( ) {
return this . libraryItem ? this . libraryItem . media || { } : { }
} ,
mediaMetadata ( ) {
return this . media . metadata || { }
2021-08-18 00:01:11 +02:00
} ,
2021-09-07 00:42:15 +02:00
userCanDelete ( ) {
return this . $store . getters [ 'user/getUserCanDelete' ]
} ,
2021-08-22 15:52:37 +02:00
genres ( ) {
2021-12-02 02:07:03 +01:00
return this . filterData . genres || [ ]
2021-08-22 15:52:37 +02:00
} ,
tags ( ) {
2021-12-02 02:07:03 +01:00
return this . filterData . tags || [ ]
2021-08-22 15:52:37 +02:00
} ,
series ( ) {
2021-12-02 02:07:03 +01:00
return this . filterData . series || [ ]
} ,
filterData ( ) {
return this . $store . state . libraries . filterData || { }
2021-10-06 04:10:49 +02:00
} ,
libraryId ( ) {
2022-03-11 01:45:02 +01:00
return this . libraryItem ? this . libraryItem . libraryId : null
2021-10-06 04:10:49 +02:00
} ,
2022-02-15 23:15:09 +01:00
libraryProvider ( ) {
return this . $store . getters [ 'libraries/getLibraryProvider' ] ( this . libraryId ) || 'google'
} ,
2021-10-06 04:10:49 +02:00
libraryScan ( ) {
if ( ! this . libraryId ) return null
return this . $store . getters [ 'scanners/getLibraryScan' ] ( this . libraryId )
2022-03-12 02:46:32 +01:00
} ,
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
}
2021-08-18 00:01:11 +02:00
}
} ,
methods : {
2022-03-12 02:46:32 +01:00
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
} ,
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
} ,
addNewSeries ( ) {
this . selectedSeries = {
id : ` new- ${ Date . now ( ) } ` ,
name : '' ,
sequence : ''
}
this . showSeriesForm = true
} ,
2022-02-15 23:15:09 +01:00
quickMatch ( ) {
this . quickMatching = true
var matchOptions = {
provider : this . libraryProvider ,
2022-02-15 23:36:22 +01:00
title : this . details . title ,
author : this . details . author !== this . book . author ? this . details . author : null
2022-02-15 23:15:09 +01:00
}
this . $axios
2022-03-11 01:45:02 +01:00
. $post ( ` /api/books/ ${ this . libraryItemId } /match ` , matchOptions )
2022-02-15 23:15:09 +01:00
. then ( ( res ) => {
this . quickMatching = false
if ( res . warning ) {
this . $toast . warning ( res . warning )
} else if ( res . updated ) {
2022-03-11 01:45:02 +01:00
this . $toast . success ( 'Item details updated' )
2022-02-15 23:15:09 +01:00
} else {
this . $toast . info ( 'No updates were made' )
}
} )
. catch ( ( error ) => {
var errMsg = error . response ? error . response . data || '' : ''
console . error ( 'Failed to match' , error )
this . $toast . error ( errMsg || 'Failed to match' )
this . quickMatching = false
} )
} ,
2022-03-13 00:45:32 +01:00
libraryScanComplete ( result ) {
2021-10-01 01:52:32 +02:00
this . rescanning = false
if ( ! result ) {
this . $toast . error ( ` Re-Scan Failed for " ${ this . title } " ` )
} else if ( result === 'UPDATED' ) {
2022-03-13 00:45:32 +01:00
this . $toast . success ( ` Re-Scan complete item was updated ` )
2021-10-01 01:52:32 +02:00
} else if ( result === 'UPTODATE' ) {
2022-03-13 00:45:32 +01:00
this . $toast . success ( ` Re-Scan complete item was up to date ` )
2021-10-01 01:52:32 +02:00
} else if ( result === 'REMOVED' ) {
2022-03-13 00:45:32 +01:00
this . $toast . error ( ` Re-Scan complete item was removed ` )
2021-10-01 01:52:32 +02:00
}
} ,
rescan ( ) {
this . rescanning = true
2022-03-13 00:45:32 +01:00
this . $root . socket . once ( 'item_scan_complete' , this . libraryScanComplete )
this . $root . socket . emit ( 'scan_item' , this . libraryItemId )
2021-10-01 01:52:32 +02:00
} ,
2021-09-29 17:16:38 +02:00
saveMetadataComplete ( result ) {
this . savingMetadata = false
if ( result . error ) {
this . $toast . error ( result . error )
} else if ( result . audiobookId ) {
var { savedPath } = result
if ( ! savedPath ) {
this . $toast . error ( ` Failed to save metadata file ( ${ result . audiobookId } ) ` )
} else {
this . $toast . success ( ` Metadata file saved " ${ result . audiobookTitle } " ` )
}
}
} ,
saveMetadata ( ) {
this . savingMetadata = true
this . $root . socket . once ( 'save_metadata_complete' , this . saveMetadataComplete )
2022-03-11 01:45:02 +01:00
this . $root . socket . emit ( 'save_metadata' , this . libraryItemId )
2021-09-29 17:16:38 +02:00
} ,
2021-12-02 02:07:03 +01:00
submitForm ( ) {
2021-08-25 13:38:32 +02:00
if ( this . isProcessing ) {
return
}
2021-08-18 00:01:11 +02:00
this . isProcessing = true
2022-03-12 02:46:32 +01:00
if ( this . $refs . authorsSelect && this . $refs . authorsSelect . isFocused ) {
this . $refs . authorsSelect . forceBlur ( )
2021-12-02 02:07:03 +01:00
}
if ( this . $refs . genresSelect && this . $refs . genresSelect . isFocused ) {
this . $refs . genresSelect . forceBlur ( )
}
if ( this . $refs . tagsSelect && this . $refs . tagsSelect . isFocused ) {
this . $refs . tagsSelect . forceBlur ( )
}
this . $nextTick ( this . handleForm )
} ,
async handleForm ( ) {
2021-08-18 00:01:11 +02:00
const updatePayload = {
2022-03-12 02:46:32 +01:00
metadata : this . details ,
2021-08-22 15:52:37 +02:00
tags : this . newTags
2021-08-18 00:01:11 +02:00
}
2022-03-12 02:46:32 +01:00
console . log ( 'Sending update' , updatePayload )
var updatedAudiobook = await this . $axios . $patch ( ` /api/items/ ${ this . libraryItemId } /media ` , updatePayload ) . catch ( ( error ) => {
2021-08-18 00:01:11 +02:00
console . error ( 'Failed to update' , error )
return false
} )
this . isProcessing = false
if ( updatedAudiobook ) {
this . $toast . success ( 'Update Successful' )
this . $emit ( 'close' )
}
} ,
init ( ) {
2022-03-11 01:45:02 +01:00
this . details . title = this . mediaMetadata . title
this . details . subtitle = this . mediaMetadata . subtitle
this . details . description = this . mediaMetadata . description
2022-03-12 02:46:32 +01:00
this . $set (
this . details ,
'authors' ,
( this . mediaMetadata . authors || [ ] ) . map ( ( se ) => ( { ... se } ) )
)
2022-03-11 01:45:02 +01:00
this . details . narrator = this . mediaMetadata . narrator
2022-03-12 02:46:32 +01:00
this . details . genres = [ ... ( this . mediaMetadata . genres || [ ] ) ]
this . $set (
this . details ,
'series' ,
( this . mediaMetadata . series || [ ] ) . map ( ( se ) => ( { ... se } ) )
)
2022-03-11 01:45:02 +01:00
this . details . publishYear = this . mediaMetadata . publishYear
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
2022-03-12 02:46:32 +01:00
this . newTags = [ ... ( this . media . tags || [ ] ) ]
2021-08-18 00:01:11 +02:00
} ,
2022-03-11 01:45:02 +01:00
removeItem ( ) {
if ( confirm ( ` Are you sure you want to remove this item? \ n \ n*Does not delete your files, only removes the item from audiobookshelf ` ) ) {
2021-08-18 00:01:11 +02:00
this . isProcessing = true
this . $axios
2022-03-13 00:45:32 +01:00
. $delete ( ` /api/items/ ${ this . libraryItemId } ` )
2021-08-18 00:01:11 +02:00
. then ( ( ) => {
2022-03-11 01:45:02 +01:00
console . log ( 'Item removed' )
this . $toast . success ( 'Item Removed' )
2021-08-18 00:01:11 +02:00
this . $emit ( 'close' )
this . isProcessing = false
} )
. catch ( ( error ) => {
2022-03-11 01:45:02 +01:00
console . error ( 'Remove item failed' , error )
2021-08-18 00:01:11 +02:00
this . isProcessing = false
} )
}
2021-09-06 21:13:01 +02:00
} ,
checkIsScrollable ( ) {
this . $nextTick ( ( ) => {
if ( this . $refs . formWrapper ) {
if ( this . $refs . formWrapper . scrollHeight > this . $refs . formWrapper . clientHeight ) {
this . isScrollable = true
} else {
this . isScrollable = false
}
}
} )
} ,
setResizeObserver ( ) {
try {
this . $nextTick ( ( ) => {
const resizeObserver = new ResizeObserver ( ( ) => {
this . checkIsScrollable ( )
} )
resizeObserver . observe ( this . $refs . formWrapper )
} )
} catch ( error ) {
console . error ( 'Failed to set resize observer' )
}
2021-08-18 00:01:11 +02:00
}
2021-09-06 21:13:01 +02:00
} ,
mounted ( ) {
this . setResizeObserver ( )
2021-08-18 00:01:11 +02:00
}
}
2021-09-06 21:13:01 +02:00
< / script >
< style scoped >
. details - form - wrapper {
height : calc ( 100 % - 70 px ) ;
2021-09-06 23:11:37 +02:00
max - height : calc ( 100 % - 70 px ) ;
2021-09-06 21:13:01 +02:00
}
< / style >