Add tasks queue dropdown

This commit is contained in:
mfcar 2023-03-05 11:15:36 +00:00
parent cfb3ce0c60
commit 61c759e0c4
No known key found for this signature in database
14 changed files with 213 additions and 21 deletions

View File

@ -0,0 +1,85 @@
<template>
<div class="flex items-center h-full px-1 overflow-hidden">
<div class="h-5 w-5 min-w-5 text-lg mr-1.5 flex items-center justify-center">
<span v-if="isFinished" :class="taskIconStatus" class="material-icons text-base">{{actionIcon}}</span>
<widgets-loading-spinner v-else />
</div>
<div class="flex-grow px-2 taskRunningCardContent">
<p class="truncate text-sm">{{ title }}</p>
<p class="truncate text-xs text-gray-300">{{ description }}</p>
<p v-if="isFailed && failedMessage" class="text-xs truncate text-red-500">{{ failedMessage }}</p>
</div>
</div>
</template>
<script>
export default {
props: {
task: {
type: Object,
default: () => {}
}
},
data() {
return {}
},
computed: {
title() {
return this.task.title || 'No Title'
},
description() {
return this.task.description || ''
},
details() {
return this.task.details || 'Unknown'
},
isFinished() {
return this.task.isFinished || false
},
isFailed() {
return this.task.isFailed || false
},
failedMessage() {
return this.task.error || ''
},
action() {
return this.task.action || ''
},
actionIcon() {
switch (this.action) {
case 'download-podcast-episode':
return 'cloud_download'
case 'encode-m4b':
return 'sync'
default:
return 'settings'
}
},
taskIconStatus() {
if (this.isFinished && this.isFailed) {
return 'text-red-500'
}
if (this.isFinished && !this.isFailed) {
return 'text-green-500'
}
return ''
}
},
methods: {
},
mounted() {}
}
</script>
<style>
.taskRunningCardContent {
width: calc(100% - 80px);
height: 75px;
display: flex;
flex-direction: column;
justify-content: center;
}
</style>

View File

@ -1,15 +1,51 @@
<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 v-if="tasksRunning" class="w-4 h-4 mx-3 relative" v-click-outside="clickOutsideObj">
<button type="button" :disabled="disabled" class="w-10 sm:w-full relative h-full cursor-pointer" aria-haspopup="listbox" :aria-expanded="showMenu" @click.stop.prevent="clickShowMenu">
<div class="flex h-full items-center justify-center">
<ui-tooltip text="Tasks running" direction="bottom" class="flex items-center">
<widgets-loading-spinner />
</ui-tooltip>
</div>
</button>
<transition name="menu">
<div class="sm:w-80 w-full relative">
<div v-show="showMenu" class="absolute z-40 -mt-px w-40 sm:w-full bg-bg border border-black-200 shadow-lg rounded-md py-1 px-2 text-base ring-1 ring-black ring-opacity-5 overflow-auto focus:outline-none sm:text-sm globalTaskRunningMenu">
<ul class="h-full w-full" role="listbox" aria-labelledby="listbox-label">
<template v-if="tasksRunningOrFailed.length">
<p class="uppercase text-xs text-gray-400 my-1 px-1 font-semibold">{{ $strings.LabelTasks }}</p>
<template v-for="task in tasksRunningOrFailed">
<nuxt-link v-if="actionLink(task)" :to="actionLink(task)">
<li :key="task.id" class="text-gray-50 select-none relative hover:bg-black-400 py-1 cursor-pointer">
<cards-item-task-running-card :task="task" />
</li>
</nuxt-link>
<li v-else :key="task.id" class="text-gray-50 select-none relative hover:bg-black-400 py-1">
<cards-item-task-running-card :task="task" />
</li>
</template>
</template>
<li v-else class="py-2 px-2">
<p>{{ $strings.MessageNoTasksRunning }}</p>
</li>
</ul>
</div>
</div>
</transition>
</div>
</template>
<script>
export default {
data() {
return {}
return {
clickOutsideObj: {
handler: this.clickedOutside,
events: ['mousedown'],
isActive: true
},
showMenu: false,
disabled: false
}
},
computed: {
tasks() {
@ -17,9 +53,37 @@ export default {
},
tasksRunning() {
return this.tasks.some((t) => !t.isFinished)
},
tasksRunningOrFailed() {
// return just the tasks that are running or failed in the last 1 minute
return this.tasks.filter((t) => !t.isFinished || (t.isFailed && t.finishedAt > new Date().getTime() - 1000 * 60)) || []
}
},
methods: {
clickShowMenu() {
if (this.disabled) return
this.showMenu = !this.showMenu
},
clickedOutside() {
this.showMenu = false
},
actionLink(task) {
switch (task.action) {
case 'download-podcast-episode':
return `/library/${task.data.libraryId}/podcast/download-queue`
case 'encode-m4b':
return `/audiobook/${task.data.libraryItemId}/manage?tool=m4b`
default:
return ''
}
}
},
methods: {},
mounted() {}
}
</script>
</script>
<style>
.globalTaskRunningMenu {
max-height: 80vh;
}
</style>

View File

@ -57,6 +57,15 @@ import DownloadQueueTable from "~/components/tables/podcast/DownloadQueueTable.v
export default {
components: {DownloadQueueTable},
async asyncData({ params, redirect }) {
if (!params.library) {
console.error('No library...', params.library)
return redirect('/')
}
return {
libraryId: params.library
}
},
data() {
return {
episodesDownloading: [],
@ -70,45 +79,44 @@ export default {
},
streamLibraryItem() {
return this.$store.state.streamLibraryItem
},
currentLibraryId() {
return this.$store.state.libraries.currentLibraryId
}
},
methods: {
episodeDownloadQueued(episodeDownload) {
if (episodeDownload.libraryId === this.currentLibraryId) {
if (episodeDownload.libraryId === this.libraryId) {
this.episodeDownloadsQueued.push(episodeDownload)
}
},
episodeDownloadStarted(episodeDownload) {
if (episodeDownload.libraryId === this.currentLibraryId) {
if (episodeDownload.libraryId === this.libraryId) {
this.episodeDownloadsQueued = this.episodeDownloadsQueued.filter((d) => d.id !== episodeDownload.id)
this.episodesDownloading.push(episodeDownload)
}
},
episodeDownloadFinished(episodeDownload) {
if (episodeDownload.libraryId === this.currentLibraryId) {
if (episodeDownload.libraryId === this.libraryId) {
this.episodeDownloadsQueued = this.episodeDownloadsQueued.filter((d) => d.id !== episodeDownload.id)
this.episodesDownloading = this.episodesDownloading.filter((d) => d.id !== episodeDownload.id)
}
},
downloadQueueUpdated(downloadQueue) {
this.episodeDownloadsQueued = downloadQueue.filter((q) => q.libraryId == this.currentLibraryId)
this.episodeDownloadsQueued = downloadQueue.filter((q) => q.libraryId == this.libraryId)
},
async loadInitialDownloadQueue() {
this.processing = true
const queuePayload = await this.$axios.$get(`/api/libraries/${this.currentLibraryId}/downloads`).catch((error) => {
const queuePayload = await this.$axios.$get(`/api/libraries/${this.libraryId}/downloads`).catch((error) => {
console.error('Failed to get download queue', error)
this.$toast.error('Failed to get download queue')
return null
})
this.processing = false
console.log('Episodes', queuePayload)
this.episodeDownloadsQueued = queuePayload || []
}
},
mounted() {
if (this.libraryId) {
this.$store.commit('libraries/setCurrentLibrary', this.libraryId)
}
this.loadInitialDownloadQueue()
this.$root.socket.on('episode_download_queued', this.episodeDownloadQueued)
this.$root.socket.on('episode_download_started', this.episodeDownloadStarted)

View File

@ -395,6 +395,7 @@
"LabelTag": "Schlagwort",
"LabelTags": "Schlagwörter",
"LabelTagsAccessibleToUser": "Für Benutzer zugängliche Schlagwörter",
"LabelTasks": "Tasks Running",
"LabelTimeListened": "Gehörte Zeit",
"LabelTimeListenedToday": "Heute gehörte Zeit",
"LabelTimeRemaining": "{0} verbleibend",
@ -514,6 +515,7 @@
"MessageNoSearchResultsFor": "Keine Suchergebnisse für \"{0}\"",
"MessageNoSeries": "Keine Serien",
"MessageNoTags": "Keine Tags",
"MessageNoTasksRunning": "No Tasks Running",
"MessageNotYetImplemented": "Noch nicht implementiert",
"MessageNoUpdateNecessary": "Keine Aktualisierung erforderlich",
"MessageNoUpdatesWereNecessary": "Keine Aktualisierungen waren notwendig",

View File

@ -395,6 +395,7 @@
"LabelTag": "Tag",
"LabelTags": "Tags",
"LabelTagsAccessibleToUser": "Tags Accessible to User",
"LabelTasks": "Tasks Running",
"LabelTimeListened": "Time Listened",
"LabelTimeListenedToday": "Time Listened Today",
"LabelTimeRemaining": "{0} remaining",
@ -514,6 +515,7 @@
"MessageNoSearchResultsFor": "No search results for \"{0}\"",
"MessageNoSeries": "No Series",
"MessageNoTags": "No Tags",
"MessageNoTasksRunning": "No Tasks Running",
"MessageNotYetImplemented": "Not yet implemented",
"MessageNoUpdateNecessary": "No update necessary",
"MessageNoUpdatesWereNecessary": "No updates were necessary",

View File

@ -395,6 +395,7 @@
"LabelTag": "Tag",
"LabelTags": "Tags",
"LabelTagsAccessibleToUser": "Tags Accessible to User",
"LabelTasks": "Tasks Running",
"LabelTimeListened": "Time Listened",
"LabelTimeListenedToday": "Time Listened Today",
"LabelTimeRemaining": "{0} remaining",
@ -514,6 +515,7 @@
"MessageNoSearchResultsFor": "No search results for \"{0}\"",
"MessageNoSeries": "No Series",
"MessageNoTags": "No Tags",
"MessageNoTasksRunning": "No Tasks Running",
"MessageNotYetImplemented": "Not yet implemented",
"MessageNoUpdateNecessary": "No update necessary",
"MessageNoUpdatesWereNecessary": "No updates were necessary",

View File

@ -395,6 +395,7 @@
"LabelTag": "Étiquette",
"LabelTags": "Étiquettes",
"LabelTagsAccessibleToUser": "Étiquettes accessibles à l'utilisateur",
"LabelTasks": "Tasks Running",
"LabelTimeListened": "Temps d'écoute",
"LabelTimeListenedToday": "Nombres d'écoutes Aujourd'hui",
"LabelTimeRemaining": "{0} restantes",
@ -514,6 +515,7 @@
"MessageNoSearchResultsFor": "Pas de résultats de recherche pour \"{0}\"",
"MessageNoSeries": "Pas de séries",
"MessageNoTags": "Pas d'étiquettes",
"MessageNoTasksRunning": "No Tasks Running",
"MessageNotYetImplemented": "Non implémenté",
"MessageNoUpdateNecessary": "Pas de mise à jour nécessaire",
"MessageNoUpdatesWereNecessary": "Aucune mise à jour n'était nécessaire",

View File

@ -395,6 +395,7 @@
"LabelTag": "Tag",
"LabelTags": "Tags",
"LabelTagsAccessibleToUser": "Tags dostupni korisniku",
"LabelTasks": "Tasks Running",
"LabelTimeListened": "Vremena odslušano",
"LabelTimeListenedToday": "Vremena odslušano danas",
"LabelTimeRemaining": "{0} preostalo",
@ -514,6 +515,7 @@
"MessageNoSearchResultsFor": "Nema rezultata pretragee za \"{0}\"",
"MessageNoSeries": "No Series",
"MessageNoTags": "No Tags",
"MessageNoTasksRunning": "No Tasks Running",
"MessageNotYetImplemented": "Not yet implemented",
"MessageNoUpdateNecessary": "Aktualiziranje nije potrebno",
"MessageNoUpdatesWereNecessary": "Aktualiziranje nije bilo potrebno",

View File

@ -395,6 +395,7 @@
"LabelTag": "Tag",
"LabelTags": "Tags",
"LabelTagsAccessibleToUser": "Tags permessi agli Utenti",
"LabelTasks": "Tasks Running",
"LabelTimeListened": "Tempo di Ascolto",
"LabelTimeListenedToday": "Tempo di Ascolto Oggi",
"LabelTimeRemaining": "{0} rimanente",
@ -514,6 +515,7 @@
"MessageNoSearchResultsFor": "Nessun risultato per \"{0}\"",
"MessageNoSeries": "Nessuna Serie",
"MessageNoTags": "No Tags",
"MessageNoTasksRunning": "No Tasks Running",
"MessageNotYetImplemented": "Non Ancora Implementato",
"MessageNoUpdateNecessary": "Nessun aggiornamento necessario",
"MessageNoUpdatesWereNecessary": "Nessun aggiornamento necessario",

View File

@ -395,6 +395,7 @@
"LabelTag": "Tag",
"LabelTags": "Tagi",
"LabelTagsAccessibleToUser": "Tagi dostępne dla użytkownika",
"LabelTasks": "Tasks Running",
"LabelTimeListened": "Czas odtwarzania",
"LabelTimeListenedToday": "Czas odtwarzania dzisiaj",
"LabelTimeRemaining": "Pozostało {0}",
@ -514,6 +515,7 @@
"MessageNoSearchResultsFor": "Brak wyników wyszukiwania dla \"{0}\"",
"MessageNoSeries": "No Series",
"MessageNoTags": "No Tags",
"MessageNoTasksRunning": "No Tasks Running",
"MessageNotYetImplemented": "Jeszcze nie zaimplementowane",
"MessageNoUpdateNecessary": "Brak konieczności aktualizacji",
"MessageNoUpdatesWereNecessary": "Brak aktualizacji",

View File

@ -395,6 +395,7 @@
"LabelTag": "Тег",
"LabelTags": "Теги",
"LabelTagsAccessibleToUser": "Теги Доступные для Пользователя",
"LabelTasks": "Tasks Running",
"LabelTimeListened": "Время Прослушивания",
"LabelTimeListenedToday": "Время Прослушивания Сегодня",
"LabelTimeRemaining": "{0} осталось",
@ -514,6 +515,7 @@
"MessageNoSearchResultsFor": "Нет результатов поиска для \"{0}\"",
"MessageNoSeries": "Нет Серий",
"MessageNoTags": "Нет Тегов",
"MessageNoTasksRunning": "No Tasks Running",
"MessageNotYetImplemented": "Пока не реализовано",
"MessageNoUpdateNecessary": "Обновление не требуется",
"MessageNoUpdatesWereNecessary": "Обновления не требовались",

View File

@ -395,6 +395,7 @@
"LabelTag": "标签",
"LabelTags": "标签",
"LabelTagsAccessibleToUser": "用户可访问的标签",
"LabelTasks": "Tasks Running",
"LabelTimeListened": "收听时间",
"LabelTimeListenedToday": "今日收听的时间",
"LabelTimeRemaining": "剩余 {0}",
@ -514,6 +515,7 @@
"MessageNoSearchResultsFor": "没有搜索到结果 \"{0}\"",
"MessageNoSeries": "无系列",
"MessageNoTags": "无标签",
"MessageNoTasksRunning": "No Tasks Running",
"MessageNotYetImplemented": "尚未实施",
"MessageNoUpdateNecessary": "无需更新",
"MessageNoUpdatesWereNecessary": "无需更新",

View File

@ -72,7 +72,7 @@ class Server {
this.abMergeManager = new AbMergeManager(this.db, this.taskManager)
this.playbackSessionManager = new PlaybackSessionManager(this.db)
this.coverManager = new CoverManager(this.db, this.cacheManager)
this.podcastManager = new PodcastManager(this.db, this.watcher, this.notificationManager)
this.podcastManager = new PodcastManager(this.db, this.watcher, this.notificationManager, this.taskManager)
this.audioMetadataManager = new AudioMetadataMangaer(this.db, this.taskManager)
this.rssFeedManager = new RssFeedManager(this.db)
this.eBookManager = new EBookManager(this.db)

View File

@ -14,12 +14,15 @@ const LibraryFile = require('../objects/files/LibraryFile')
const PodcastEpisodeDownload = require('../objects/PodcastEpisodeDownload')
const PodcastEpisode = require('../objects/entities/PodcastEpisode')
const AudioFile = require('../objects/files/AudioFile')
const Task = require("../objects/Task");
const Path = require("path");
class PodcastManager {
constructor(db, watcher, notificationManager) {
constructor(db, watcher, notificationManager, taskManager) {
this.db = db
this.watcher = watcher
this.notificationManager = notificationManager
this.taskManager = taskManager
this.downloadQueue = []
this.currentDownload = null
@ -57,11 +60,11 @@ class PodcastManager {
newPe.libraryItemId = libraryItem.id
var newPeDl = new PodcastEpisodeDownload()
newPeDl.setData(newPe, libraryItem, isAutoDownload, libraryItem.libraryId)
this.startPodcastEpisodeDownload(newPeDl)
this.startPodcastEpisodeDownload(newPeDl, libraryItem)
})
}
async startPodcastEpisodeDownload(podcastEpisodeDownload) {
async startPodcastEpisodeDownload(podcastEpisodeDownload, libraryItem) {
SocketAuthority.emitter('download_queue_updated', this.getDownloadQueueDetails())
if (this.currentDownload) {
this.downloadQueue.push(podcastEpisodeDownload)
@ -69,6 +72,15 @@ class PodcastManager {
return
}
const task = new Task()
const taskDescription = `Downloading episode "${podcastEpisodeDownload.podcastEpisode.title}".`
const taskData = {
libraryId: libraryItem.libraryId,
libraryItemId: libraryItem.id,
}
task.setData('download-podcast-episode', 'Downloading Episode', taskDescription, taskData)
this.taskManager.addTask(task)
SocketAuthority.emitter('episode_download_started', podcastEpisodeDownload.toJSONForClient())
this.currentDownload = podcastEpisodeDownload
@ -91,21 +103,26 @@ class PodcastManager {
if (!success) {
await fs.remove(this.currentDownload.targetPath)
this.currentDownload.setFinished(false)
task.setFailed('Failed to download episode')
} else {
Logger.info(`[PodcastManager] Successfully downloaded podcast episode "${this.currentDownload.podcastEpisode.title}"`)
this.currentDownload.setFinished(true)
task.setFinished()
}
} else {
task.setFailed('Failed to download episode')
this.currentDownload.setFinished(false)
}
this.taskManager.taskFinished(task)
SocketAuthority.emitter('episode_download_finished', this.currentDownload.toJSONForClient())
SocketAuthority.emitter('download_queue_updated', this.getDownloadQueueDetails())
this.watcher.removeIgnoreDir(this.currentDownload.libraryItem.path)
this.currentDownload = null
if (this.downloadQueue.length) {
this.startPodcastEpisodeDownload(this.downloadQueue.shift())
this.startPodcastEpisodeDownload(this.downloadQueue.shift(), libraryItem)
}
}