mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2026-01-02 00:59:37 +00:00
Add:Tasks widget in appbar for merging m4bs & remove old m4b merge routes
This commit is contained in:
parent
441b8c5bb7
commit
39979ff8a3
16 changed files with 285 additions and 457 deletions
|
|
@ -7,7 +7,7 @@
|
|||
</nuxt-link>
|
||||
|
||||
<nuxt-link to="/">
|
||||
<h1 class="text-2xl font-book mr-6 hidden lg:block hover:underline">audiobookshelf</h1>
|
||||
<h1 class="text-2xl font-book 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>
|
||||
</nuxt-link>
|
||||
|
||||
<ui-libraries-dropdown class="mr-2" />
|
||||
|
|
@ -15,7 +15,7 @@
|
|||
<controls-global-search v-if="currentLibrary" class="mr-1 sm:mr-0" />
|
||||
<div class="flex-grow" />
|
||||
|
||||
<span v-if="showExperimentalFeatures" class="material-icons text-2xl md:text-4xl text-warning pr-0 sm:pr-2 md:pr-4">logo_dev</span>
|
||||
<widgets-notification-widget class="hidden md:block" />
|
||||
|
||||
<ui-tooltip v-if="isChromecastInitialized && !isHttps" direction="bottom" text="Casting requires a secure connection" class="flex items-center">
|
||||
<span class="material-icons-outlined text-warning text-opacity-50"> cast </span>
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@
|
|||
</div>
|
||||
<div class="flex-grow" />
|
||||
<div>
|
||||
<ui-btn :to="`/audiobook/${libraryItemId}/manage`" class="flex items-center"
|
||||
<ui-btn :to="`/audiobook/${libraryItemId}/manage?tool=embed`" class="flex items-center"
|
||||
>Open Manager
|
||||
<span class="material-icons text-lg ml-2">launch</span>
|
||||
</ui-btn>
|
||||
|
|
|
|||
25
client/components/widgets/NotificationWidget.vue
Normal file
25
client/components/widgets/NotificationWidget.vue
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
<template>
|
||||
<div v-if="tasksRunning" class="w-4 h-4 mx-3 relative">
|
||||
<div class="flex h-full items-center justify-center">
|
||||
<widgets-loading-spinner />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {}
|
||||
},
|
||||
computed: {
|
||||
tasks() {
|
||||
return this.$store.state.tasks.tasks
|
||||
},
|
||||
tasksRunning() {
|
||||
return this.tasks.some((t) => !t.isFinished)
|
||||
}
|
||||
},
|
||||
methods: {},
|
||||
mounted() {}
|
||||
}
|
||||
</script>
|
||||
|
|
@ -270,6 +270,14 @@ export default {
|
|||
|
||||
this.$store.commit('scanners/addUpdate', data)
|
||||
},
|
||||
taskStarted(task) {
|
||||
console.log('Task started', task)
|
||||
this.$store.commit('tasks/addUpdateTask', task)
|
||||
},
|
||||
taskFinished(task) {
|
||||
console.log('Task finished', task)
|
||||
this.$store.commit('tasks/addUpdateTask', task)
|
||||
},
|
||||
userUpdated(user) {
|
||||
if (this.$store.state.user.user.id === user.id) {
|
||||
this.$store.commit('user/setUser', user)
|
||||
|
|
@ -302,53 +310,6 @@ export default {
|
|||
}
|
||||
this.$store.commit('user/removeCollection', collection)
|
||||
},
|
||||
abmergeStarted(download) {
|
||||
download.status = this.$constants.DownloadStatus.PENDING
|
||||
download.toastId = this.$toast(`Preparing download "${download.filename}"`, { timeout: false, draggable: false, closeOnClick: false })
|
||||
this.$store.commit('downloads/addUpdateDownload', download)
|
||||
},
|
||||
abmergeReady(download) {
|
||||
download.status = this.$constants.DownloadStatus.READY
|
||||
var existingDownload = this.$store.getters['downloads/getDownload'](download.id)
|
||||
|
||||
if (existingDownload && existingDownload.toastId !== undefined) {
|
||||
download.toastId = existingDownload.toastId
|
||||
this.$toast.update(existingDownload.toastId, { content: `Download "${download.filename}" is ready!`, options: { timeout: 5000, type: 'success' } }, true)
|
||||
} else {
|
||||
this.$toast.success(`Download "${download.filename}" is ready!`)
|
||||
}
|
||||
this.$store.commit('downloads/addUpdateDownload', download)
|
||||
},
|
||||
abmergeFailed(download) {
|
||||
download.status = this.$constants.DownloadStatus.FAILED
|
||||
var existingDownload = this.$store.getters['downloads/getDownload'](download.id)
|
||||
|
||||
var failedMsg = download.isTimedOut ? 'timed out' : 'failed'
|
||||
|
||||
if (existingDownload && existingDownload.toastId !== undefined) {
|
||||
download.toastId = existingDownload.toastId
|
||||
this.$toast.update(existingDownload.toastId, { content: `Download "${download.filename}" ${failedMsg}`, options: { timeout: 5000, type: 'error' } }, true)
|
||||
} else {
|
||||
console.warn('Download failed no existing download', existingDownload)
|
||||
this.$toast.error(`Download "${download.filename}" ${failedMsg}`)
|
||||
}
|
||||
this.$store.commit('downloads/addUpdateDownload', download)
|
||||
},
|
||||
abmergeKilled(download) {
|
||||
var existingDownload = this.$store.getters['downloads/getDownload'](download.id)
|
||||
if (existingDownload && existingDownload.toastId !== undefined) {
|
||||
download.toastId = existingDownload.toastId
|
||||
this.$toast.update(existingDownload.toastId, { content: `Download "${download.filename}" was terminated`, options: { timeout: 5000, type: 'error' } }, true)
|
||||
} else {
|
||||
console.warn('Download killed no existing download found', existingDownload)
|
||||
this.$toast.error(`Download "${download.filename}" was terminated`)
|
||||
}
|
||||
this.$store.commit('downloads/removeDownload', download)
|
||||
},
|
||||
abmergeExpired(download) {
|
||||
download.status = this.$constants.DownloadStatus.EXPIRED
|
||||
this.$store.commit('downloads/addUpdateDownload', download)
|
||||
},
|
||||
rssFeedOpen(data) {
|
||||
this.$store.commit('feeds/addFeed', data)
|
||||
},
|
||||
|
|
@ -362,7 +323,7 @@ export default {
|
|||
batchQuickMatchComplete(result) {
|
||||
var success = result.success || false
|
||||
var toast = 'Batch quick match complete!\n' + result.updates + ' Updated'
|
||||
if (result.unmatched && (result.unmatched > 0)) {
|
||||
if (result.unmatched && result.unmatched > 0) {
|
||||
toast += '\n' + result.unmatched + ' with no matches'
|
||||
}
|
||||
if (success) {
|
||||
|
|
@ -430,19 +391,16 @@ export default {
|
|||
this.socket.on('scan_complete', this.scanComplete)
|
||||
this.socket.on('scan_progress', this.scanProgress)
|
||||
|
||||
// Download Listeners
|
||||
this.socket.on('abmerge_started', this.abmergeStarted)
|
||||
this.socket.on('abmerge_ready', this.abmergeReady)
|
||||
this.socket.on('abmerge_failed', this.abmergeFailed)
|
||||
this.socket.on('abmerge_killed', this.abmergeKilled)
|
||||
this.socket.on('abmerge_expired', this.abmergeExpired)
|
||||
// Task Listeners
|
||||
this.socket.on('task_started', this.taskStarted)
|
||||
this.socket.on('task_finished', this.taskFinished)
|
||||
|
||||
// Feed Listeners
|
||||
this.socket.on('rss_feed_open', this.rssFeedOpen)
|
||||
this.socket.on('rss_feed_closed', this.rssFeedClosed)
|
||||
|
||||
this.socket.on('backup_applied', this.backupApplied)
|
||||
|
||||
|
||||
this.socket.on('batch_quickmatch_complete', this.batchQuickMatchComplete)
|
||||
},
|
||||
showUpdateToast(versionData) {
|
||||
|
|
@ -548,6 +506,19 @@ export default {
|
|||
// Queue auto play
|
||||
var playerQueueAutoPlay = localStorage.getItem('playerQueueAutoPlay')
|
||||
this.$store.commit('setPlayerQueueAutoPlay', playerQueueAutoPlay !== '0')
|
||||
},
|
||||
loadTasks() {
|
||||
this.$axios
|
||||
.$get('/api/tasks')
|
||||
.then((payload) => {
|
||||
console.log('Fetched tasks', payload)
|
||||
if (payload.tasks) {
|
||||
this.$store.commit('tasks/setTasks', payload.tasks)
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Failed to load tasks', error)
|
||||
})
|
||||
}
|
||||
},
|
||||
beforeMount() {
|
||||
|
|
@ -565,6 +536,8 @@ export default {
|
|||
|
||||
this.checkVersionUpdate()
|
||||
|
||||
this.loadTasks()
|
||||
|
||||
if (this.$route.query.error) {
|
||||
this.$toast.error(this.$route.query.error)
|
||||
this.$router.replace(this.$route.path)
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@
|
|||
<div class="w-full max-h-72 overflow-auto">
|
||||
<p v-if="!metadataChapters.length" class="py-5 text-center text-gray-200">No chapters</p>
|
||||
<template v-for="(chapter, index) in metadataChapters">
|
||||
<div :key="index" class="flex py-1 px-4 text-sm" :class="index % 2 === 0 ? 'bg-primary bg-opacity-25' : ''">
|
||||
<div :key="index" class="flex py-1 px-4 text-sm" :class="index % 2 === 1 ? 'bg-primary bg-opacity-25' : ''">
|
||||
<div class="flex-grow font-semibold">{{ chapter.title }}</div>
|
||||
<div class="w-24">
|
||||
{{ $secondsToTimestamp(chapter.start) }}
|
||||
|
|
@ -67,7 +67,8 @@
|
|||
<p v-else class="text-success text-lg font-semibold">Embed Finished!</p>
|
||||
</div>
|
||||
<div v-else class="w-full flex justify-end items-center mb-4">
|
||||
<ui-btn v-if="!isFinished" color="primary" :loading="processing" @click.stop="encodeM4bClick">Start M4B Encode</ui-btn>
|
||||
<ui-btn v-if="!isTaskFinished" color="primary" :loading="processing" @click.stop="encodeM4bClick">Start M4B Encode</ui-btn>
|
||||
<p v-else-if="taskFailed" class="text-error text-lg font-semibold">M4B Failed! {{ taskError }}</p>
|
||||
<p v-else class="text-success text-lg font-semibold">M4B Finished!</p>
|
||||
</div>
|
||||
|
||||
|
|
@ -160,14 +161,23 @@ export default {
|
|||
},
|
||||
data() {
|
||||
return {
|
||||
processing: false,
|
||||
audiofilesEncoding: {},
|
||||
audiofilesFinished: {},
|
||||
processing: false,
|
||||
isFinished: false,
|
||||
toneObject: null,
|
||||
selectedTool: 'embed'
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
task: {
|
||||
handler(newVal) {
|
||||
if (newVal) {
|
||||
this.taskUpdated(newVal)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
libraryItemId() {
|
||||
return this.libraryItem.id
|
||||
|
|
@ -202,6 +212,21 @@ export default {
|
|||
{ value: 'm4b', text: 'M4B Encoder' }
|
||||
]
|
||||
}
|
||||
},
|
||||
taskFailed() {
|
||||
return this.isTaskFinished && this.task.isFailed
|
||||
},
|
||||
taskError() {
|
||||
return this.taskFailed ? this.task.error || 'Unknown Error' : null
|
||||
},
|
||||
isTaskFinished() {
|
||||
return this.task && this.task.isFinished
|
||||
},
|
||||
task() {
|
||||
return this.$store.getters['tasks/getTaskByLibraryItemId'](this.libraryItemId)
|
||||
},
|
||||
taskRunning() {
|
||||
return this.task && !this.task.isFinished
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
|
@ -210,12 +235,12 @@ export default {
|
|||
this.$axios
|
||||
.$get(`/api/audiobook-merge/${this.libraryItemId}`)
|
||||
.then(() => {
|
||||
this.processing = false
|
||||
console.log('Ab m4b merge started')
|
||||
})
|
||||
.catch((error) => {
|
||||
var errorMsg = error.response ? error.response.data || 'Unknown Error' : 'Unknown Error'
|
||||
this.$toast.error(errorMsg)
|
||||
this.processing = false
|
||||
this.processing = true
|
||||
})
|
||||
},
|
||||
embedClick() {
|
||||
|
|
@ -246,7 +271,6 @@ export default {
|
|||
console.log('audio metadata started', data)
|
||||
if (data.libraryItemId !== this.libraryItemId) return
|
||||
this.audiofilesFinished = {}
|
||||
this.processing = true
|
||||
},
|
||||
audioMetadataFinished(data) {
|
||||
console.log('audio metadata finished', data)
|
||||
|
|
@ -278,6 +302,8 @@ export default {
|
|||
this.selectedToolUpdated()
|
||||
}
|
||||
}
|
||||
|
||||
if (this.task) this.taskUpdated(this.task)
|
||||
},
|
||||
fetchToneObject() {
|
||||
this.$axios
|
||||
|
|
@ -289,6 +315,9 @@ export default {
|
|||
.catch((error) => {
|
||||
console.error('Failed to fetch tone object', error)
|
||||
})
|
||||
},
|
||||
taskUpdated(task) {
|
||||
this.processing = !task.isFinished
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
|
|
|
|||
|
|
@ -1,34 +0,0 @@
|
|||
|
||||
export const state = () => ({
|
||||
downloads: []
|
||||
})
|
||||
|
||||
export const getters = {
|
||||
getDownloads: (state) => (libraryItemId) => {
|
||||
return state.downloads.filter(d => d.libraryItemId === libraryItemId)
|
||||
},
|
||||
getDownload: (state) => (id) => {
|
||||
return state.downloads.find(d => d.id === id)
|
||||
}
|
||||
}
|
||||
|
||||
export const actions = {
|
||||
|
||||
}
|
||||
|
||||
export const mutations = {
|
||||
setDownloads(state, downloads) {
|
||||
state.downloads = downloads
|
||||
},
|
||||
addUpdateDownload(state, download) {
|
||||
var index = state.downloads.findIndex(d => d.id === download.id)
|
||||
if (index >= 0) {
|
||||
state.downloads.splice(index, 1, download)
|
||||
} else {
|
||||
state.downloads.push(download)
|
||||
}
|
||||
},
|
||||
removeDownload(state, download) {
|
||||
state.downloads = state.downloads.filter(d => d.id !== download.id)
|
||||
}
|
||||
}
|
||||
31
client/store/tasks.js
Normal file
31
client/store/tasks.js
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
|
||||
export const state = () => ({
|
||||
tasks: []
|
||||
})
|
||||
|
||||
export const getters = {
|
||||
getTaskByLibraryItemId: (state) => (libraryItemId) => {
|
||||
return state.tasks.find(t => t.data && t.data.libraryItemId === libraryItemId)
|
||||
}
|
||||
}
|
||||
|
||||
export const actions = {
|
||||
|
||||
}
|
||||
|
||||
export const mutations = {
|
||||
setTasks(state, tasks) {
|
||||
state.tasks = tasks
|
||||
},
|
||||
addUpdateTask(state, task) {
|
||||
var index = state.tasks.findIndex(d => d.id === task.id)
|
||||
if (index >= 0) {
|
||||
state.tasks.splice(index, 1, task)
|
||||
} else {
|
||||
state.tasks.push(task)
|
||||
}
|
||||
},
|
||||
removeTask(state, task) {
|
||||
state.tasks = state.tasks.filter(d => d.id !== task.id)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue