2021-08-18 00:01:11 +02:00
< template >
< div class = "w-full h-16 bg-primary relative" >
2022-12-17 23:36:41 +01:00
< div id = "appbar" class = "absolute top-0 bottom-0 left-0 w-full h-full px-2 md:px-6 py-1 z-60" >
2021-08-18 00:01:11 +02:00
< div class = "flex h-full items-center" >
2022-05-09 22:12:55 +02:00
< nuxt -link to = "/" >
2023-02-11 22:02:56 +01:00
< img src = "~static/icon.svg" :alt ="$strings.ButtonHome" class = "w-8 min-w-8 h-8 mr-2 sm:w-10 sm:min-w-10 sm:h-10 sm:mr-4" / >
2022-05-09 22:12:55 +02:00
< / n u x t - l i n k >
< nuxt -link to = "/" >
2023-02-11 22:02:56 +01:00
< h1 class = "text-xl mr-6 hidden lg:block hover:underline" > audiobookshelf < span v-if ="showExperimentalFeatures" class="material-icons text-lg text-warning pr-1" > logo_dev < / span > < / h1 >
2022-05-09 22:12:55 +02:00
< / n u x t - l i n k >
2021-10-05 05:11:42 +02:00
2022-07-27 01:54:32 +02:00
< ui -libraries -dropdown class = "mr-2" / >
2021-10-05 05:11:42 +02:00
2022-08-20 21:32:38 +02:00
< controls -global -search v -if = " currentLibrary " class = "mr-1 sm:mr-0" / >
2021-08-18 00:01:11 +02:00
< div class = "flex-grow" / >
2022-02-24 00:01:11 +01:00
< ui -tooltip v-if ="isChromecastInitialized && !isHttps" direction="bottom" text="Casting requires a secure connection" class="flex items-center" >
2022-11-21 14:18:10 +01:00
< span class = "material-icons-outlined text-2xl text-warning text-opacity-50" > cast < / span >
2022-02-24 00:01:11 +01:00
< / u i - t o o l t i p >
2022-06-19 22:43:45 +02:00
< div v-if ="isChromecastInitialized" class="w-6 min-w-6 h-6 ml-2 mr-1 sm:mx-2 cursor-pointer" >
2022-02-23 00:33:55 +01:00
< google -cast -launcher > < / g o o g l e - c a s t - l a u n c h e r >
< / div >
2023-05-27 21:51:03 +02:00
< widgets -notification -widget class = "hidden md:block" / >
2022-12-28 22:59:27 +01:00
< nuxt -link v-if ="currentLibrary" to="/config/stats" class="hover:text-gray-200 cursor-pointer w-8 h-8 hidden sm:flex items-center justify-center mx-1" >
2022-12-06 00:12:53 +01:00
< ui -tooltip :text ="$strings.HeaderYourStats" direction = "bottom" class = "flex items-center" >
2022-12-05 23:16:27 +01:00
< span class = "material-icons text-2xl" aria -label = " User Stats " role = "button" > equalizer < / span >
< / u i - t o o l t i p >
2021-11-08 02:52:13 +01:00
< / n u x t - l i n k >
2022-12-28 22:59:27 +01:00
< nuxt -link v-if ="userCanUpload && currentLibrary" to="/upload" class="hover:text-gray-200 cursor-pointer w-8 h-8 flex items-center justify-center mx-1" >
2022-12-06 00:12:58 +01:00
< ui -tooltip :text ="$strings.ButtonUpload" direction = "bottom" class = "flex items-center" >
2022-12-05 23:16:27 +01:00
< span class = "material-icons text-2xl" aria -label = " Upload Media " role = "button" > upload < / span >
< / u i - t o o l t i p >
2021-09-14 03:18:58 +02:00
< / n u x t - l i n k >
2022-12-28 22:59:27 +01:00
< nuxt -link v-if ="userIsAdminOrUp" to="/config" class="hover:text-gray-200 cursor-pointer w-8 h-8 flex items-center justify-center mx-1" >
2022-12-06 00:13:03 +01:00
< ui -tooltip :text ="$strings.HeaderSettings" direction = "bottom" class = "flex items-center" >
2022-12-05 23:16:27 +01:00
< span class = "material-icons text-2xl" aria -label = " System Settings " role = "button" > settings < / span >
< / u i - t o o l t i p >
2021-08-18 00:01:11 +02:00
< / n u x t - l i n k >
2022-12-28 22:59:27 +01:00
< nuxt -link to = "/account" class = "relative w-9 h-9 md:w-32 bg-fg border border-gray-500 rounded shadow-sm ml-1.5 sm:ml-3 md:ml-5 md:pl-3 md:pr-10 py-2 text-left sm:text-sm cursor-pointer hover:bg-bg hover:bg-opacity-40" aria -haspopup = " listbox " aria -expanded = " true " >
2021-11-04 23:35:59 +01:00
< span class = "items-center hidden md:flex" >
2021-09-05 20:21:02 +02:00
< span class = "block truncate" > { { username } } < / span >
< / span >
2021-11-04 23:35:59 +01:00
< span class = "h-full md:ml-3 md:absolute inset-y-0 md:right-0 flex items-center justify-center md:pr-2 pointer-events-none" >
2022-11-19 19:09:05 +01:00
< span class = "material-icons text-xl text-gray-100" > person < / span >
2021-09-05 20:21:02 +02:00
< / span >
< / n u x t - l i n k >
2021-08-18 00:01:11 +02:00
< / div >
2022-12-01 00:09:00 +01:00
< div v-show ="numMediaItemsSelected" class="absolute top-0 left-0 w-full h-full px-4 bg-primary flex items-center" >
< h1 class = "text-lg md:text-2xl px-4" > { { $getString ( 'MessageItemsSelected' , [ numMediaItemsSelected ] ) } } < / h1 >
2021-08-27 01:32:05 +02:00
< div class = "flex-grow" / >
2022-12-01 00:09:00 +01:00
< ui -btn v-if ="!isPodcastLibrary && selectedMediaItemsArePlayable" color="success" :padding-x="4" small class="flex items-center h-9 mr-2" @click="playSelectedItems" >
2022-11-21 14:18:10 +01:00
< span class = "material-icons text-2xl -ml-2 pr-1 text-white" > play _arrow < / span >
2022-11-19 17:20:10 +01:00
{ { $strings . ButtonPlay } }
< / u i - b t n >
2023-01-04 01:00:01 +01:00
< ui -tooltip v-if ="isBookLibrary" :text="selectedIsFinished ? $strings.MessageMarkAsNotFinished : $strings.MessageMarkAsFinished" direction="bottom" >
2022-03-17 19:33:22 +01:00
< ui -read -icon -btn :disabled ="processingBatch" :is-read ="selectedIsFinished" @click ="toggleBatchRead" class = "mx-1.5" / >
2021-09-16 15:37:09 +02:00
< / u i - t o o l t i p >
2023-01-04 01:00:01 +01:00
< ui -tooltip v-if ="userCanUpdate && isBookLibrary" :text="$strings.LabelAddToCollection" direction="bottom" >
2021-11-27 23:01:53 +01:00
< ui -icon -btn :disabled ="processingBatch" icon = "collections_bookmark" @click ="batchAddToCollectionClick" class = "mx-1.5" / >
< / u i - t o o l t i p >
2022-11-19 18:27:08 +01:00
< template v-if ="userCanUpdate" >
2022-12-16 00:57:42 +01:00
< ui -tooltip :text ="$strings.LabelEdit" direction = "bottom" >
2022-11-19 17:20:10 +01:00
< ui -icon -btn :disabled ="processingBatch" icon = "edit" bg -color = " warning " class = "mx-1.5" @click ="batchEditClick" / >
2022-04-29 00:44:07 +02:00
< / u i - t o o l t i p >
2021-09-07 00:42:15 +02:00
< / template >
2022-11-09 00:10:08 +01:00
< ui -tooltip v-if ="userCanDelete" :text="$strings.ButtonRemove" direction="bottom" >
2022-11-19 17:20:10 +01:00
< ui -icon -btn :disabled ="processingBatch" icon = "delete" bg -color = " error " class = "mx-1.5" @click ="batchDeleteClick" / >
2022-04-29 00:44:07 +02:00
< / u i - t o o l t i p >
2023-04-02 23:13:18 +02:00
< ui -context -menu -dropdown v -if = " contextMenuItems.length & & ! processingBatch " :items ="contextMenuItems" class = "ml-1" @action ="contextMenuAction" / >
< ui -tooltip :text ="$strings.LabelDeselectAll" direction = "bottom" class = "flex items-center" >
< span class = "material-icons text-3xl px-4 hover:text-gray-100 cursor-pointer" : class = "processingBatch ? 'text-gray-400' : ''" @click ="cancelSelectionMode" > close < / span >
2022-04-29 00:44:07 +02:00
< / u i - t o o l t i p >
2021-08-27 01:32:05 +02:00
< / div >
2021-08-18 00:01:11 +02:00
< / div >
< / div >
< / template >
< script >
export default {
data ( ) {
return {
2022-11-19 17:20:10 +01:00
totalEntities : 0
2021-08-18 00:01:11 +02:00
}
} ,
computed : {
2021-10-05 05:11:42 +02:00
currentLibrary ( ) {
return this . $store . getters [ 'libraries/getCurrentLibrary' ]
} ,
libraryName ( ) {
return this . currentLibrary ? this . currentLibrary . name : 'unknown'
} ,
2022-03-26 21:23:25 +01:00
libraryMediaType ( ) {
return this . currentLibrary ? this . currentLibrary . mediaType : null
} ,
isPodcastLibrary ( ) {
return this . libraryMediaType === 'podcast'
} ,
2023-01-04 01:00:01 +01:00
isBookLibrary ( ) {
return this . libraryMediaType === 'book'
} ,
2021-09-28 13:44:40 +02:00
isHome ( ) {
2021-10-05 05:11:42 +02:00
return this . $route . name === 'library-library'
2021-09-28 13:44:40 +02:00
} ,
2021-08-18 00:01:11 +02:00
user ( ) {
2021-08-24 01:31:04 +02:00
return this . $store . state . user . user
2021-08-18 00:01:11 +02:00
} ,
2022-05-04 02:16:16 +02:00
userIsAdminOrUp ( ) {
return this . $store . getters [ 'user/getIsAdminOrUp' ]
2021-08-27 14:01:47 +02:00
} ,
2021-08-18 00:01:11 +02:00
username ( ) {
return this . user ? this . user . username : 'err'
2021-08-27 01:32:05 +02:00
} ,
2022-12-01 00:09:00 +01:00
numMediaItemsSelected ( ) {
return this . selectedMediaItems . length
2021-08-27 01:32:05 +02:00
} ,
2022-12-01 00:09:00 +01:00
selectedMediaItems ( ) {
return this . $store . state . globals . selectedMediaItems
} ,
selectedMediaItemsArePlayable ( ) {
2022-12-13 00:36:53 +01:00
return ! this . selectedMediaItems . some ( ( i ) => ! i . hasTracks )
2021-08-27 01:32:05 +02:00
} ,
2022-03-26 17:59:34 +01:00
userMediaProgress ( ) {
return this . $store . state . user . user . mediaProgress || [ ]
2021-09-16 15:37:09 +02:00
} ,
2021-09-07 00:42:15 +02:00
userCanUpdate ( ) {
return this . $store . getters [ 'user/getUserCanUpdate' ]
} ,
userCanDelete ( ) {
return this . $store . getters [ 'user/getUserCanDelete' ]
2021-09-16 15:37:09 +02:00
} ,
2021-09-18 19:45:34 +02:00
userCanUpload ( ) {
return this . $store . getters [ 'user/getUserCanUpload' ]
} ,
2022-03-17 19:33:22 +01:00
selectedIsFinished ( ) {
// Find an item that is not finished, if none then all items finished
2022-12-01 00:09:00 +01:00
return ! this . selectedMediaItems . find ( ( item ) => {
const itemProgress = this . userMediaProgress . find ( ( lip ) => lip . libraryItemId === item . id )
2022-03-17 19:33:22 +01:00
return ! itemProgress || ! itemProgress . isFinished
2021-09-16 15:37:09 +02:00
} )
2021-09-17 21:15:15 +02:00
} ,
processingBatch ( ) {
return this . $store . state . processingBatch
2021-10-02 22:36:33 +02:00
} ,
showExperimentalFeatures ( ) {
return this . $store . state . showExperimentalFeatures
2022-02-23 00:33:55 +01:00
} ,
isChromecastEnabled ( ) {
return this . $store . getters [ 'getServerSetting' ] ( 'chromecastEnabled' )
} ,
isChromecastInitialized ( ) {
return this . $store . state . globals . isChromecastInitialized
2022-02-24 00:01:11 +01:00
} ,
isHttps ( ) {
return location . protocol === 'https:' || process . env . NODE _ENV === 'development'
2023-04-02 23:13:18 +02:00
} ,
contextMenuItems ( ) {
if ( ! this . userIsAdminOrUp ) return [ ]
const options = [
{
text : this . $strings . ButtonQuickMatch ,
action : 'quick-match'
}
]
if ( ! this . isPodcastLibrary && this . selectedMediaItemsArePlayable ) {
options . push ( {
text : 'Quick Embed Metadata' ,
action : 'quick-embed'
} )
}
2023-05-27 21:51:03 +02:00
options . push ( {
text : 'Re-Scan' ,
action : 'rescan'
} )
2023-04-02 23:13:18 +02:00
return options
2021-08-18 00:01:11 +02:00
}
} ,
methods : {
2023-04-02 23:13:18 +02:00
requestBatchQuickEmbed ( ) {
const payload = {
message : 'Warning! Quick embed will not backup your audio files. Make sure that you have a backup of your audio files. <br><br>Would you like to continue?' ,
callback : ( confirmed ) => {
if ( confirmed ) {
this . $axios
. $post ( ` /api/tools/batch/embed-metadata ` , {
libraryItemIds : this . selectedMediaItems . map ( ( i ) => i . id )
} )
. then ( ( ) => {
console . log ( 'Audio metadata embed started' )
this . cancelSelectionMode ( )
} )
. catch ( ( error ) => {
console . error ( 'Audio metadata embed failed' , error )
const errorMsg = error . response . data || 'Failed to embed metadata'
this . $toast . error ( errorMsg )
} )
}
} ,
type : 'yesNo'
}
this . $store . commit ( 'globals/setConfirmPrompt' , payload )
} ,
2023-05-30 00:38:38 +02:00
contextMenuAction ( { action } ) {
2023-04-02 23:13:18 +02:00
if ( action === 'quick-embed' ) {
this . requestBatchQuickEmbed ( )
} else if ( action === 'quick-match' ) {
this . batchAutoMatchClick ( )
2023-05-27 21:51:03 +02:00
} else if ( action === 'rescan' ) {
this . batchRescan ( )
}
} ,
async batchRescan ( ) {
const payload = {
message : ` Are you sure you want to re-scan ${ this . selectedMediaItems . length } items? ` ,
callback : ( confirmed ) => {
if ( confirmed ) {
this . $axios
. $post ( ` /api/items/batch/scan ` , {
libraryItemIds : this . selectedMediaItems . map ( ( i ) => i . id )
} )
. then ( ( ) => {
console . log ( 'Batch Re-Scan started' )
this . cancelSelectionMode ( )
} )
. catch ( ( error ) => {
console . error ( 'Batch Re-Scan failed' , error )
const errorMsg = error . response . data || 'Failed to batch re-scan'
this . $toast . error ( errorMsg )
} )
}
} ,
type : 'yesNo'
2023-04-02 23:13:18 +02:00
}
2023-05-27 21:51:03 +02:00
this . $store . commit ( 'globals/setConfirmPrompt' , payload )
2023-04-02 23:13:18 +02:00
} ,
2022-11-19 17:20:10 +01:00
async playSelectedItems ( ) {
this . $store . commit ( 'setProcessingBatch' , true )
2022-12-01 00:09:00 +01:00
const libraryItemIds = this . selectedMediaItems . map ( ( i ) => i . id )
2022-12-13 00:36:53 +01:00
const libraryItems = await this . $axios
. $post ( ` /api/items/batch/get ` , { libraryItemIds } )
. then ( ( res ) => res . libraryItems )
. catch ( ( error ) => {
const errorMsg = error . response . data || 'Failed to get items'
console . error ( errorMsg , error )
this . $toast . error ( errorMsg )
return [ ]
} )
2022-11-19 17:20:10 +01:00
if ( ! libraryItems . length ) {
this . $store . commit ( 'setProcessingBatch' , false )
return
}
const queueItems = [ ]
libraryItems . forEach ( ( item ) => {
2023-01-04 01:00:01 +01:00
let subtitle = ''
if ( item . mediaType === 'book' ) subtitle = item . media . metadata . authors . map ( ( au ) => au . name ) . join ( ', ' )
else if ( item . mediaType === 'music' ) subtitle = item . media . metadata . artists . join ( ', ' )
2022-11-19 17:20:10 +01:00
queueItems . push ( {
libraryItemId : item . id ,
libraryId : item . libraryId ,
episodeId : null ,
title : item . media . metadata . title ,
2023-01-04 01:00:01 +01:00
subtitle ,
2022-11-19 17:20:10 +01:00
caption : '' ,
duration : item . media . duration || null ,
coverPath : item . media . coverPath || null
} )
} )
this . $eventBus . $emit ( 'play-item' , {
libraryItemId : queueItems [ 0 ] . libraryItemId ,
queueItems
} )
this . $store . commit ( 'setProcessingBatch' , false )
2022-12-01 00:09:00 +01:00
this . $store . commit ( 'globals/resetSelectedMediaItems' , [ ] )
2022-11-19 17:20:10 +01:00
this . $eventBus . $emit ( 'bookshelf_clear_selection' )
} ,
2021-08-27 01:32:05 +02:00
cancelSelectionMode ( ) {
2022-11-19 17:20:10 +01:00
if ( this . processingBatch ) return
2022-12-01 00:09:00 +01:00
this . $store . commit ( 'globals/resetSelectedMediaItems' , [ ] )
2022-10-29 01:10:19 +02:00
this . $eventBus . $emit ( 'bookshelf_clear_selection' )
2021-08-27 01:32:05 +02:00
} ,
2021-09-16 15:37:09 +02:00
toggleBatchRead ( ) {
2021-09-17 21:15:15 +02:00
this . $store . commit ( 'setProcessingBatch' , true )
2022-12-01 00:09:00 +01:00
const newIsFinished = ! this . selectedIsFinished
const updateProgressPayloads = this . selectedMediaItems . map ( ( item ) => {
2021-09-16 15:37:09 +02:00
return {
2022-12-01 00:09:00 +01:00
libraryItemId : item . id ,
2022-03-17 19:33:22 +01:00
isFinished : newIsFinished
2021-09-16 15:37:09 +02:00
}
} )
2022-04-24 00:17:05 +02:00
console . log ( 'Progress payloads' , updateProgressPayloads )
2021-09-16 15:37:09 +02:00
this . $axios
2022-03-17 19:33:22 +01:00
. patch ( ` /api/me/progress/batch/update ` , updateProgressPayloads )
2021-09-16 15:37:09 +02:00
. then ( ( ) => {
this . $toast . success ( 'Batch update success!' )
this . $store . commit ( 'setProcessingBatch' , false )
2022-12-01 00:09:00 +01:00
this . $store . commit ( 'globals/resetSelectedMediaItems' , [ ] )
2022-10-29 01:10:19 +02:00
this . $eventBus . $emit ( 'bookshelf_clear_selection' )
2021-09-16 15:37:09 +02:00
} )
. catch ( ( error ) => {
this . $toast . error ( 'Batch update failed' )
console . error ( 'Failed to batch update read/not read' , error )
this . $store . commit ( 'setProcessingBatch' , false )
} )
} ,
2021-08-27 01:32:05 +02:00
batchDeleteClick ( ) {
2023-04-14 23:44:41 +02:00
const payload = {
message : ` This will delete ${ this . numMediaItemsSelected } library items from the database and your file system. Are you sure? ` ,
checkboxLabel : 'Delete from file system. Uncheck to only remove from database.' ,
yesButtonText : this . $strings . ButtonDelete ,
yesButtonColor : 'error' ,
checkboxDefaultValue : true ,
callback : ( confirmed , hardDelete ) => {
if ( confirmed ) {
this . $store . commit ( 'setProcessingBatch' , true )
this . $axios
. $post ( ` /api/items/batch/delete?hard= ${ hardDelete ? 1 : 0 } ` , {
libraryItemIds : this . selectedMediaItems . map ( ( i ) => i . id )
} )
. then ( ( ) => {
this . $toast . success ( 'Batch delete success' )
this . $store . commit ( 'globals/resetSelectedMediaItems' , [ ] )
this . $eventBus . $emit ( 'bookshelf_clear_selection' )
} )
. catch ( ( error ) => {
console . error ( 'Batch delete failed' , error )
this . $toast . error ( 'Batch delete failed' )
} )
. finally ( ( ) => {
this . $store . commit ( 'setProcessingBatch' , false )
} )
}
} ,
type : 'yesNo'
2021-08-27 01:32:05 +02:00
}
2023-04-14 23:44:41 +02:00
this . $store . commit ( 'globals/setConfirmPrompt' , payload )
2021-08-27 01:32:05 +02:00
} ,
batchEditClick ( ) {
this . $router . push ( '/batch' )
2021-11-27 23:01:53 +01:00
} ,
batchAddToCollectionClick ( ) {
2022-11-12 00:13:10 +01:00
this . $store . commit ( 'globals/setShowBatchCollectionsModal' , true )
2021-12-01 03:02:40 +01:00
} ,
2021-12-02 02:07:03 +01:00
setBookshelfTotalEntities ( totalEntities ) {
this . totalEntities = totalEntities
2022-09-19 17:29:24 +02:00
} ,
batchAutoMatchClick ( ) {
this . $store . commit ( 'globals/setShowBatchQuickMatchModal' , true )
2022-10-01 23:07:30 +02:00
}
2021-08-18 00:01:11 +02:00
} ,
2021-12-01 03:02:40 +01:00
mounted ( ) {
2021-12-02 02:07:03 +01:00
this . $eventBus . $on ( 'bookshelf-total-entities' , this . setBookshelfTotalEntities )
2021-12-01 03:02:40 +01:00
} ,
beforeDestroy ( ) {
2021-12-02 02:07:03 +01:00
this . $eventBus . $off ( 'bookshelf-total-entities' , this . setBookshelfTotalEntities )
2021-12-01 03:02:40 +01:00
}
2021-08-18 00:01:11 +02:00
}
< / script >
< style >
# appbar {
2021-08-18 13:50:24 +02:00
box - shadow : 0 px 5 px 5 px # 11111155 ;
2021-08-18 00:01:11 +02:00
}
2022-04-29 00:44:07 +02:00
< / style >