From 7ed5b1744fb8e2bf3f839fc510707d2a23d33cb4 Mon Sep 17 00:00:00 2001 From: advplyr Date: Tue, 29 Nov 2022 18:03:50 -0600 Subject: [PATCH 01/20] Var cleanup --- server/scanner/MediaFileScanner.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/server/scanner/MediaFileScanner.js b/server/scanner/MediaFileScanner.js index 95854e23..f27555bb 100644 --- a/server/scanner/MediaFileScanner.js +++ b/server/scanner/MediaFileScanner.js @@ -14,7 +14,7 @@ class MediaFileScanner { getTrackAndDiscNumberFromFilename(mediaMetadataFromScan, audioLibraryFile) { const { title, author, series, publishedYear } = mediaMetadataFromScan const { filename, path } = audioLibraryFile.metadata - var partbasename = Path.basename(filename, Path.extname(filename)) + let partbasename = Path.basename(filename, Path.extname(filename)) // Remove title, author, series, and publishedYear from filename if there if (title) partbasename = partbasename.replace(title, '') @@ -23,8 +23,8 @@ class MediaFileScanner { if (publishedYear) partbasename = partbasename.replace(publishedYear) // Look for disc number - var discNumber = null - var discMatch = partbasename.match(/\b(disc|cd) ?(\d\d?)\b/i) + let discNumber = null + const discMatch = partbasename.match(/\b(disc|cd) ?(\d\d?)\b/i) if (discMatch && discMatch.length > 2 && discMatch[2]) { if (!isNaN(discMatch[2])) { discNumber = Number(discMatch[2]) @@ -35,14 +35,14 @@ class MediaFileScanner { } // Look for disc number in folder path e.g. /Book Title/CD01/audiofile.mp3 - var pathdir = Path.dirname(path).split('/').pop() + const pathdir = Path.dirname(path).split('/').pop() if (pathdir && /^cd\d{1,3}$/i.test(pathdir)) { - var discFromFolder = Number(pathdir.replace(/cd/i, '')) + const discFromFolder = Number(pathdir.replace(/cd/i, '')) if (!isNaN(discFromFolder) && discFromFolder !== null) discNumber = discFromFolder } - var numbersinpath = partbasename.match(/\d{1,4}/g) - var trackNumber = numbersinpath && numbersinpath.length ? parseInt(numbersinpath[0]) : null + const numbersinpath = partbasename.match(/\d{1,4}/g) + const trackNumber = numbersinpath && numbersinpath.length ? parseInt(numbersinpath[0]) : null return { trackNumber, discNumber @@ -51,7 +51,7 @@ class MediaFileScanner { getAverageScanDurationMs(results) { if (!results.length) return 0 - var total = 0 + let total = 0 for (let i = 0; i < results.length; i++) total += results[i].elapsed return Math.floor(total / results.length) } From d917f0e37dbd2dbc1ccc5730f3a8ab36351ec649 Mon Sep 17 00:00:00 2001 From: advplyr Date: Wed, 30 Nov 2022 16:15:25 -0600 Subject: [PATCH 02/20] Fix:Ebook reader for ebooks in root folder #1232 --- client/components/readers/Reader.vue | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/client/components/readers/Reader.vue b/client/components/readers/Reader.vue index 290ac12a..a06f4f5b 100644 --- a/client/components/readers/Reader.vue +++ b/client/components/readers/Reader.vue @@ -92,13 +92,18 @@ export default { }, ebookUrl() { if (!this.ebookFile) return null - var itemRelPath = this.selectedLibraryItem.relPath - if (itemRelPath.startsWith('/')) itemRelPath = itemRelPath.slice(1) - var relPath = this.ebookFile.metadata.relPath - if (relPath.startsWith('/')) relPath = relPath.slice(1) + let filepath = '' + if (this.selectedLibraryItem.isFile) { + filepath = this.$encodeUriPath(this.ebookFile.metadata.filename) + } else { + const itemRelPath = this.selectedLibraryItem.relPath + if (itemRelPath.startsWith('/')) itemRelPath = itemRelPath.slice(1) + const relPath = this.ebookFile.metadata.relPath + if (relPath.startsWith('/')) relPath = relPath.slice(1) - const relRelPath = this.$encodeUriPath(`${itemRelPath}/${relPath}`) - return `/ebook/${this.libraryId}/${this.folderId}/${relRelPath}` + filepath = this.$encodeUriPath(`${itemRelPath}/${relPath}`) + } + return `/ebook/${this.libraryId}/${this.folderId}/${filepath}` }, userToken() { return this.$store.getters['user/getToken'] From 521a673094eb9461767796481de63ed557161aea Mon Sep 17 00:00:00 2001 From: Tomazed Date: Wed, 30 Nov 2022 23:20:29 +0100 Subject: [PATCH 03/20] [Update] client/strings/fr.json --- client/strings/fr.json | 58 +++++++++++++++++++++--------------------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/client/strings/fr.json b/client/strings/fr.json index 875a0df1..95829866 100644 --- a/client/strings/fr.json +++ b/client/strings/fr.json @@ -41,7 +41,7 @@ "ButtonOpenManager": "Ouvrir le Gestionnaire", "ButtonPlay": "Ecouter", "ButtonPlaying": "En Lecture", - "ButtonPlaylists": "Playlists", + "ButtonPlaylists": "Listes de Lecture", "ButtonPurgeAllCache": "Purger Tout le Cache", "ButtonPurgeItemsCache": "Purger le Cache des Articles", "ButtonPurgeMediaProgress": "Purger la Progression des Médias", @@ -65,7 +65,7 @@ "ButtonSearch": "Rechercher", "ButtonSelectFolderPath": "Sélectionner le Chemin du Dossier", "ButtonSeries": "Séries", - "ButtonSetChaptersFromTracks": "Set chapters from tracks", + "ButtonSetChaptersFromTracks": "Positionner les Chapitre par rapports aux Pistes", "ButtonShiftTimes": "Décaler le Temps", "ButtonShow": "Montrer", "ButtonStartM4BEncode": "Démarrer l'Encodage M4B", @@ -113,8 +113,8 @@ "HeaderOtherFiles": "Autres Fichiers", "HeaderPermissions": "Permissions", "HeaderPlayerQueue": "Liste d'Ecoute", - "HeaderPlaylist": "Playlist", - "HeaderPlaylistItems": "Playlist Items", + "HeaderPlaylist": "Liste de Lecture", + "HeaderPlaylistItems": "Elements de la Liste de Lecture", "HeaderPodcastsToAdd": "Podcasts à Ajouter", "HeaderPreviewCover": "Prévisualiser la Couverture", "HeaderRemoveEpisode": "Supprimer l'Episode", @@ -151,8 +151,8 @@ "LabelAddedAt": "Date d'Ajout", "LabelAddToCollection": "Ajouter à la Collection", "LabelAddToCollectionBatch": "Ajout de {0} Livres à la Collection", - "LabelAddToPlaylist": "Add to Playlist", - "LabelAddToPlaylistBatch": "Add {0} Items to Playlist", + "LabelAddToPlaylist": "Ajouter à la Liste de Lecture", + "LabelAddToPlaylistBatch": "{0} Elements Ajoutés à la Liste de Lecture", "LabelAll": "Tout", "LabelAllUsers": "Tous les Utilisateurs", "LabelAuthor": "Auteur", @@ -288,7 +288,7 @@ "LabelPermissionsUpdate": "Peut Mettre à Jour", "LabelPermissionsUpload": "Peut Téléverser", "LabelPhotoPathURL": "Chemin/URL des photos", - "LabelPlaylists": "Playlists", + "LabelPlaylists": "Listes de Lecture", "LabelPlayMethod": "Méthode d'Ecoute", "LabelPodcast": "Podcast", "LabelPodcasts": "Podcasts", @@ -390,9 +390,9 @@ "LabelTotalTimeListened": "Temps d'Ecoute Total", "LabelTrackFromFilename": "Piste depuis le Fichier", "LabelTrackFromMetadata": "Piste depuis les Métadonnées", - "LabelTracks": "Tracks", - "LabelTracksMultiTrack": "Multi-track", - "LabelTracksSingleTrack": "Single-track", + "LabelTracks": "Pistes", + "LabelTracksMultiTrack": "Piste Multiple", + "LabelTracksSingleTrack": "Piste Simple", "LabelType": "Type", "LabelUnknown": "Inconnu", "LabelUpdateCover": "Mettre à jour la Couverture", @@ -415,9 +415,9 @@ "LabelWeekdaysToRun": "Jours de la semaine à exécuter", "LabelYourAudiobookDuration": "Durée de vos Livres Audios", "LabelYourBookmarks": "Vos Signets", - "LabelYourPlaylists": "Your Playlists", + "LabelYourPlaylists": "Vos Listes de Lecture", "LabelYourProgress": "Votre Progression", - "MessageAddToPlayerQueue": "Add to player queue", + "MessageAddToPlayerQueue": "Ajouter en Queue d'Ecoute", "MessageAppriseDescription": "Nécessite une instance d'API Apprise pour utiliser cette fonctionnalité ou une api qui prend en charge les mêmes requêtes.
L'URL de l'API Apprise doit comprendre le chemin complet pour envoyer la notification. Par exemple, si votre instance écoute sur http://192.168.1.1:8337 alors vous devez mettre http://192.168.1.1:8337/notify.", "MessageBackupsDescription": "Les Sauvegardes incluent les utilisateurs, la progression de lecture par utilisateur, les détails des articles des bibliothèques, les paramètres du serveur et les images sauvegardées. Les Sauvegardes n'incluent pas les fichiers de votre bibliothèque.", "MessageBatchQuickMatchDescription": "La Recherche par Correspondance Rapide tentera d'ajouter les couvertures et les métadonnées manquantes pour les articles sélectionnés. Activer l'option suivante pour autoriser la Recherche par Correspondance à écraser les données existantes.", @@ -426,9 +426,9 @@ "MessageBookshelfNoRSSFeeds": "Aucun flux RSS n'est ouvert", "MessageBookshelfNoSeries": "Vous n'avez aucune séries", "MessageChapterEndIsAfter": "Le Chapitre Fin est situé à la fin de votre Livre Audio", - "MessageChapterErrorFirstNotZero": "First chapter must start at 0", - "MessageChapterErrorStartGteDuration": "Invalid start time must be less than audiobook duration", - "MessageChapterErrorStartLtPrev": "Invalid start time must be greater than or equal to previous chapter start time", + "MessageChapterErrorFirstNotZero": "Le premier capitre doit débuter à 0", + "MessageChapterErrorStartGteDuration": "Horodatage invalide car il doit débuter avant la fin du livre", + "MessageChapterErrorStartLtPrev": "Horodatage invalide car il doit débuter au moins après le précédent chapitre", "MessageChapterStartIsAfter": "Le Chapitre Début est situé au début de votre Livre Audio", "MessageCheckingCron": "Vérification du cron...", "MessageConfirmDeleteBackup": "Etes vous certain de vouloir supprimer la Sauvegarde de {0}?", @@ -438,7 +438,7 @@ "MessageConfirmRemoveCollection": "Etes vous certain de vouloir supprimer la collection \"{0}\"?", "MessageConfirmRemoveEpisode": "Etes vous certain de vouloir supprimer l'épisode \"{0}\"?", "MessageConfirmRemoveEpisodes": "Etes vous certain de vouloir supprimer {0} épisodes?", - "MessageConfirmRemovePlaylist": "Are you sure you want to remove your playlist \"{0}\"?", + "MessageConfirmRemovePlaylist": "Etes vous certain de vouloir supprimer la liste de lecture \"{0}\"?", "MessageDownloadingEpisode": "Téléchargement de l'épisode", "MessageDragFilesIntoTrackOrder": "Faire glisser les fichiers dans l'ordre correct", "MessageEmbedFinished": "Intégration Terminée!", @@ -481,11 +481,11 @@ "MessageNoPodcastsFound": "Pas de podcasts trouvés", "MessageNoResults": "Pas de Résultats", "MessageNoSearchResultsFor": "Pas de résultats de recherche pour \"{0}\"", - "MessageNoSeries": "No Series", + "MessageNoSeries": "Pas de Séries", "MessageNotYetImplemented": "Non implémenté", "MessageNoUpdateNecessary": "Pas de mise à jour nécessaire", "MessageNoUpdatesWereNecessary": "Aucune mise à jour n'était nécessaire", - "MessageNoUserPlaylists": "You have no playlists", + "MessageNoUserPlaylists": "Vous n'avez aucune liste de lecture", "MessageOr": "ou", "MessagePauseChapter": "Suspendre la lecture du chapitre", "MessagePlayChapter": "Ecouter depuis le début du chapitre", @@ -494,15 +494,15 @@ "MessageRemoveAllItemsWarning": "ATTENTION! Cette action supprimera toute la base de données de la bibliothèque ainsi que les mises à jour ou correspondances qui auraient été effectuées. Cela n'a aucune incidence sur les fichiers de la bibliothèque. Voulez-vous continuer?", "MessageRemoveChapter": "Supprimer le chapitre", "MessageRemoveEpisodes": "Suppression de {0} épisode(s)", - "MessageRemoveFromPlayerQueue": "Remove from player queue", + "MessageRemoveFromPlayerQueue": "Supprimer de la liste d'écoute", "MessageRemoveUserWarning": "Etes-vous certain de vouloir supprimer définitivement l'utilisateur \"{0}\"?", "MessageReportBugsAndContribute": "Remonter des anomalies, demander des fonctionnalités et contribuer sur", - "MessageResetChaptersConfirm": "Are you sure you want to reset chapters and undo the changes you made?", + "MessageResetChaptersConfirm": "Etes-vous certain de vouloir réinitialiser les chapitres et annuler les changements effectués?", "MessageRestoreBackupConfirm": "Etes-vous certain de vouloir restaurer la sauvegarde créée le", "MessageRestoreBackupWarning": "Restaurer la sauvegarde écrasera la base de donnée située dans le dossier /config ainsi que les images sur /metadata/items & /metadata/authors.

Les sauvegardes ne touchent pas aux fichiers de la bibliothèque. Si vous avez activé le paramètre pour sauvegarder les métadonnées et les images de couverture dans le même dossier que les fichiers, ceux-ci ne ni sauvegardés, ni écrasés lors de la restauration.

Tous les clients utilisant votre serveur seront automatiquement mis à jour.", "MessageSearchResultsFor": "Résultats de recherche pour", "MessageServerCouldNotBeReached": "Serveur inaccessible", - "MessageSetChaptersFromTracksDescription": "Set chapters using each audio file as a chapter and chapter title as the audio file name", + "MessageSetChaptersFromTracksDescription": "Positionne un chapitre par fichier audio, avec le titre du fichier comme titre de chapitre", "MessageStartPlaybackAtTime": "Démarrer la lecture pour \"{0}\" à {1}?", "MessageThinking": "On Réfléchit...", "MessageUploaderItemFailed": "Echec du téléversement", @@ -524,7 +524,7 @@ "NoteUploaderUnsupportedFiles": "Les fichiers non-supportés seront ignorés. En sélectionnant ou déponsant un dossier, les autres fichiers qui ne sont pas un dossier contenant un article seront ignorés.", "PlaceholderNewCollection": "Nom de la nouvelle collection", "PlaceholderNewFolderPath": "Nouveau chemin de dossier", - "PlaceholderNewPlaylist": "New playlist name", + "PlaceholderNewPlaylist": "Nouveau nom de liste de lecture", "PlaceholderSearch": "Recherche...", "ToastAccountUpdateFailed": "Echec de la mise à jour du compte", "ToastAccountUpdateSuccess": "Compte mis à jour", @@ -549,8 +549,8 @@ "ToastBookmarkRemoveSuccess": "Signet supprimé", "ToastBookmarkUpdateFailed": "Echec de la mise à jour de signet", "ToastBookmarkUpdateSuccess": "Signet mis à jour", - "ToastChaptersHaveErrors": "Chapters have errors", - "ToastChaptersMustHaveTitles": "Chapters must have titles", + "ToastChaptersHaveErrors": "Les chapitres contiennent des erreurs", + "ToastChaptersMustHaveTitles": "Les chapitre doivent avoir un titre", "ToastCollectionItemsRemoveFailed": "Echec de la suppression de(s) article(s) de la collection", "ToastCollectionItemsRemoveSuccess": "Article(s) supprimé(s) de la collection", "ToastCollectionRemoveFailed": "Echec de la suppression de la collection", @@ -574,10 +574,10 @@ "ToastLibraryScanStarted": "Analyse de la bibliothèque démarrée", "ToastLibraryUpdateFailed": "Echec de la mise à jour de la bibliothèque", "ToastLibraryUpdateSuccess": "Bibliothèque \"{0}\" mise à jour", - "ToastPlaylistRemoveFailed": "Failed to remove playlist", - "ToastPlaylistRemoveSuccess": "Playlist removed", - "ToastPlaylistUpdateFailed": "Failed to update playlist", - "ToastPlaylistUpdateSuccess": "Playlist updated", + "ToastPlaylistRemoveFailed": "Echec de la suppression de la liste de lecture", + "ToastPlaylistRemoveSuccess": "Liste de lecture supprimée", + "ToastPlaylistUpdateFailed": "Echec de la mise à jour de la liste de lecture", + "ToastPlaylistUpdateSuccess": "Liste de lecture mise à jour", "ToastPodcastCreateFailed": "Echec de la création du Podcast", "ToastPodcastCreateSuccess": "Podcast créé", "ToastRemoveItemFromCollectionFailed": "Echec de la suppression de l'article de la collection", @@ -598,4 +598,4 @@ "WeekdayThursday": "Jeudi", "WeekdayTuesday": "Mardi", "WeekdayWednesday": "Mercredi" -} \ No newline at end of file +} From 725f8eecdb44e5bc913a8946e8c31ab21f136a9d Mon Sep 17 00:00:00 2001 From: advplyr Date: Wed, 30 Nov 2022 17:09:00 -0600 Subject: [PATCH 04/20] Fix:Batch selecting ebooks showing play button in appbar #1235 --- client/components/app/Appbar.vue | 46 ++++++++++--------- .../components/app/BookShelfCategorized.vue | 28 +++++++---- client/components/app/BookShelfRow.vue | 8 ++-- client/components/app/BookShelfToolbar.vue | 2 +- client/components/app/LazyBookshelf.vue | 25 +++++++--- .../modals/BatchQuickMatchModel.vue | 2 +- .../modals/collections/AddCreateModal.vue | 2 +- client/components/widgets/AuthorsSlider.vue | 2 +- client/components/widgets/EpisodeSlider.vue | 8 ++-- client/components/widgets/ItemSlider.vue | 8 ++-- client/components/widgets/SeriesSlider.vue | 2 +- client/layouts/default.vue | 9 ++-- client/mixins/bookshelfCardsHelpers.js | 4 +- client/pages/batch/index.vue | 8 ++-- client/store/globals.js | 27 +++++++++++ client/store/index.js | 25 ---------- 16 files changed, 117 insertions(+), 89 deletions(-) diff --git a/client/components/app/Appbar.vue b/client/components/app/Appbar.vue index 5bf9b794..47041520 100644 --- a/client/components/app/Appbar.vue +++ b/client/components/app/Appbar.vue @@ -45,10 +45,10 @@ -
-

{{ $getString('MessageItemsSelected', [numLibraryItemsSelected]) }}

+
+

{{ $getString('MessageItemsSelected', [numMediaItemsSelected]) }}

- + play_arrow {{ $strings.ButtonPlay }} @@ -109,11 +109,14 @@ export default { username() { return this.user ? this.user.username : 'err' }, - numLibraryItemsSelected() { - return this.selectedLibraryItems.length + numMediaItemsSelected() { + return this.selectedMediaItems.length }, - selectedLibraryItems() { - return this.$store.state.selectedLibraryItems + selectedMediaItems() { + return this.$store.state.globals.selectedMediaItems + }, + selectedMediaItemsArePlayable() { + return !this.selectedMediaItems.some(i => !i.hasTracks) }, userMediaProgress() { return this.$store.state.user.user.mediaProgress || [] @@ -129,8 +132,8 @@ export default { }, selectedIsFinished() { // Find an item that is not finished, if none then all items finished - return !this.selectedLibraryItems.find((libraryItemId) => { - var itemProgress = this.userMediaProgress.find((lip) => lip.libraryItemId === libraryItemId) + return !this.selectedMediaItems.find((item) => { + const itemProgress = this.userMediaProgress.find((lip) => lip.libraryItemId === item.id) return !itemProgress || !itemProgress.isFinished }) }, @@ -154,8 +157,9 @@ export default { async playSelectedItems() { this.$store.commit('setProcessingBatch', true) - var libraryItems = await this.$axios.$post(`/api/items/batch/get`, { libraryItemIds: this.selectedLibraryItems }).catch((error) => { - var errorMsg = error.response.data || 'Failed to get items' + const libraryItemIds = this.selectedMediaItems.map((i) => i.id) + const libraryItems = await this.$axios.$post(`/api/items/batch/get`, { libraryItemIds }).catch((error) => { + const errorMsg = error.response.data || 'Failed to get items' console.error(errorMsg, error) this.$toast.error(errorMsg) return [] @@ -185,20 +189,20 @@ export default { queueItems }) this.$store.commit('setProcessingBatch', false) - this.$store.commit('setSelectedLibraryItems', []) + this.$store.commit('globals/resetSelectedMediaItems', []) this.$eventBus.$emit('bookshelf_clear_selection') }, cancelSelectionMode() { if (this.processingBatch) return - this.$store.commit('setSelectedLibraryItems', []) + this.$store.commit('globals/resetSelectedMediaItems', []) this.$eventBus.$emit('bookshelf_clear_selection') }, toggleBatchRead() { this.$store.commit('setProcessingBatch', true) - var newIsFinished = !this.selectedIsFinished - var updateProgressPayloads = this.selectedLibraryItems.map((lid) => { + const newIsFinished = !this.selectedIsFinished + const updateProgressPayloads = this.selectedMediaItems.map((item) => { return { - libraryItemId: lid, + libraryItemId: item.id, isFinished: newIsFinished } }) @@ -208,7 +212,7 @@ export default { .then(() => { this.$toast.success('Batch update success!') this.$store.commit('setProcessingBatch', false) - this.$store.commit('setSelectedLibraryItems', []) + this.$store.commit('globals/resetSelectedMediaItems', []) this.$eventBus.$emit('bookshelf_clear_selection') }) .catch((error) => { @@ -218,18 +222,18 @@ export default { }) }, batchDeleteClick() { - var audiobookText = this.numLibraryItemsSelected > 1 ? `these ${this.numLibraryItemsSelected} items` : 'this item' - var confirmMsg = `Are you sure you want to remove ${audiobookText}?\n\n*Does not delete your files, only removes the items from Audiobookshelf` + const audiobookText = this.numMediaItemsSelected > 1 ? `these ${this.numMediaItemsSelected} items` : 'this item' + const confirmMsg = `Are you sure you want to remove ${audiobookText}?\n\n*Does not delete your files, only removes the items from Audiobookshelf` if (confirm(confirmMsg)) { this.$store.commit('setProcessingBatch', true) this.$axios .$post(`/api/items/batch/delete`, { - libraryItemIds: this.selectedLibraryItems + libraryItemIds: this.selectedMediaItems.map((i) => i.id) }) .then(() => { this.$toast.success('Batch delete success!') this.$store.commit('setProcessingBatch', false) - this.$store.commit('setSelectedLibraryItems', []) + this.$store.commit('globals/resetSelectedMediaItems', []) this.$eventBus.$emit('bookshelf_clear_selection') }) .catch((error) => { diff --git a/client/components/app/BookShelfCategorized.vue b/client/components/app/BookShelfCategorized.vue index f17ca365..202f4992 100644 --- a/client/components/app/BookShelfCategorized.vue +++ b/client/components/app/BookShelfCategorized.vue @@ -89,8 +89,8 @@ export default { var baseSize = this.isCoverSquareAspectRatio ? 192 : 120 return this.bookCoverWidth / baseSize }, - selectedLibraryItems() { - return this.$store.state.selectedLibraryItems || [] + selectedMediaItems() { + return this.$store.state.globals.selectedMediaItems || [] } }, methods: { @@ -100,15 +100,15 @@ export default { const indexOf = shelf.shelfStartIndex + entityShelfIndex const lastLastItemIndexSelected = this.lastItemIndexSelected - if (!this.selectedLibraryItems.includes(entity.id)) { + if (!this.selectedMediaItems.some((i) => i.id === entity.id)) { this.lastItemIndexSelected = indexOf } else { this.lastItemIndexSelected = -1 } if (shiftKey && lastLastItemIndexSelected >= 0) { - var loopStart = indexOf - var loopEnd = lastLastItemIndexSelected + let loopStart = indexOf + let loopEnd = lastLastItemIndexSelected if (indexOf > lastLastItemIndexSelected) { loopStart = lastLastItemIndexSelected loopEnd = indexOf @@ -117,12 +117,12 @@ export default { const flattenedEntitiesArray = [] this.shelves.map((s) => flattenedEntitiesArray.push(...s.entities)) - var isSelecting = false + let isSelecting = false // If any items in this range is not selected then select all otherwise unselect all for (let i = loopStart; i <= loopEnd; i++) { const thisEntity = flattenedEntitiesArray[i] if (thisEntity) { - if (!this.selectedLibraryItems.includes(thisEntity.id)) { + if (!this.selectedMediaItems.some((i) => i.id === thisEntity.id)) { isSelecting = true break } @@ -133,13 +133,23 @@ export default { for (let i = loopStart; i <= loopEnd; i++) { const thisEntity = flattenedEntitiesArray[i] if (thisEntity) { - this.$store.commit('setLibraryItemSelected', { libraryItemId: thisEntity.id, selected: isSelecting }) + const mediaItem = { + id: thisEntity.id, + mediaType: thisEntity.mediaType, + hasTracks: thisEntity.mediaType === 'podcast' || thisEntity.media.numTracks || (thisEntity.media.tracks && thisEntity.media.tracks.length) + } + this.$store.commit('globals/setMediaItemSelected', { item: mediaItem, selected: isSelecting }) } else { console.error('Invalid entity index', i) } } } else { - this.$store.commit('toggleLibraryItemSelected', entity.id) + const mediaItem = { + id: entity.id, + mediaType: entity.mediaType, + hasTracks: entity.mediaType === 'podcast' || entity.media.numTracks || (entity.media.tracks && entity.media.tracks.length) + } + this.$store.commit('globals/toggleMediaItemSelected', mediaItem) } this.$nextTick(() => { diff --git a/client/components/app/BookShelfRow.vue b/client/components/app/BookShelfRow.vue index f35d72a2..931e6b50 100644 --- a/client/components/app/BookShelfRow.vue +++ b/client/components/app/BookShelfRow.vue @@ -98,7 +98,7 @@ export default { return this.$store.state.libraries.currentLibraryId }, isSelectionMode() { - return this.$store.getters['getNumLibraryItemsSelected'] > 0 + return this.$store.getters['globals/getIsBatchSelectingMediaItems'] } }, methods: { @@ -119,14 +119,14 @@ export default { this.$store.commit('globals/setShowEditPodcastEpisodeModal', true) }, updateSelectionMode(val) { - var selectedLibraryItems = this.$store.state.selectedLibraryItems + const selectedMediaItems = this.$store.state.globals.selectedMediaItems if (this.shelf.type === 'book' || this.shelf.type === 'podcast') { this.shelf.entities.forEach((ent) => { var component = this.$refs[`shelf-book-${ent.id}`] if (!component || !component.length) return component = component[0] component.setSelectionMode(val) - component.selected = selectedLibraryItems.includes(ent.id) + component.selected = selectedMediaItems.some((i) => i.id === ent.id) }) } else if (this.shelf.type === 'episode') { this.shelf.entities.forEach((ent) => { @@ -134,7 +134,7 @@ export default { if (!component || !component.length) return component = component[0] component.setSelectionMode(val) - component.selected = selectedLibraryItems.includes(ent.id) + component.selected = selectedMediaItems.some((i) => i.id === ent.id) }) } }, diff --git a/client/components/app/BookShelfToolbar.vue b/client/components/app/BookShelfToolbar.vue index 9829fec0..c0de0308 100644 --- a/client/components/app/BookShelfToolbar.vue +++ b/client/components/app/BookShelfToolbar.vue @@ -205,7 +205,7 @@ export default { return this.seriesProgress.libraryItemIds || [] }, isBatchSelecting() { - return this.$store.state.selectedLibraryItems.length + return this.$store.getters['globals/getIsBatchSelectingMediaItems'] }, isSeriesFinished() { return this.seriesProgress && !!this.seriesProgress.isFinished diff --git a/client/components/app/LazyBookshelf.vue b/client/components/app/LazyBookshelf.vue index 3482e8d1..4c5dfc3d 100644 --- a/client/components/app/LazyBookshelf.vue +++ b/client/components/app/LazyBookshelf.vue @@ -201,8 +201,8 @@ export default { // Includes margin return this.entityWidth + 24 }, - selectedLibraryItems() { - return this.$store.state.selectedLibraryItems || [] + selectedMediaItems() { + return this.$store.state.globals.selectedMediaItems || [] }, sizeMultiplier() { var baseSize = this.isCoverSquareAspectRatio ? 192 : 120 @@ -232,7 +232,7 @@ export default { if (this.entityName === 'books' || this.entityName === 'series-books') { var indexOf = this.entities.findIndex((ent) => ent && ent.id === entity.id) const lastLastItemIndexSelected = this.lastItemIndexSelected - if (!this.selectedLibraryItems.includes(entity.id)) { + if (!this.selectedMediaItems.some((i) => i.id === entity.id)) { this.lastItemIndexSelected = indexOf } else { this.lastItemIndexSelected = -1 @@ -251,7 +251,7 @@ export default { for (let i = loopStart; i <= loopEnd; i++) { const thisEntity = this.entities[i] if (thisEntity && !thisEntity.collapsedSeries) { - if (!this.selectedLibraryItems.includes(thisEntity.id)) { + if (!this.selectedMediaItems.some((i) => i.id === thisEntity.id)) { isSelecting = true break } @@ -269,16 +269,27 @@ export default { const entityComponentRef = this.entityComponentRefs[i] if (thisEntity && entityComponentRef) { entityComponentRef.selected = isSelecting - this.$store.commit('setLibraryItemSelected', { libraryItemId: thisEntity.id, selected: isSelecting }) + + const mediaItem = { + id: thisEntity.id, + mediaType: thisEntity.mediaType, + hasTracks: thisEntity.mediaType === 'podcast' || thisEntity.media.numTracks || (thisEntity.media.tracks && thisEntity.media.tracks.length) + } + this.$store.commit('globals/setMediaItemSelected', { item: mediaItem, selected: isSelecting }) } else { console.error('Invalid entity index', i) } } } else { - this.$store.commit('toggleLibraryItemSelected', entity.id) + const mediaItem = { + id: entity.id, + mediaType: entity.mediaType, + hasTracks: entity.mediaType === 'podcast' || entity.media.numTracks || (entity.media.tracks && entity.media.tracks.length) + } + this.$store.commit('globals/toggleMediaItemSelected', mediaItem) } - var newIsSelectionMode = !!this.selectedLibraryItems.length + const newIsSelectionMode = !!this.selectedMediaItems.length if (this.isSelectionMode !== newIsSelectionMode) { this.isSelectionMode = newIsSelectionMode this.updateBookSelectionMode(newIsSelectionMode) diff --git a/client/components/modals/BatchQuickMatchModel.vue b/client/components/modals/BatchQuickMatchModel.vue index 2d70855d..0bbace4b 100644 --- a/client/components/modals/BatchQuickMatchModel.vue +++ b/client/components/modals/BatchQuickMatchModel.vue @@ -82,7 +82,7 @@ export default { return this.$store.state.globals.showBatchQuickMatchModal }, selectedBookIds() { - return this.$store.state.selectedLibraryItems || [] + return (this.$store.state.globals.selectedMediaItems || []).map((i) => i.id) }, currentLibraryId() { return this.$store.state.libraries.currentLibraryId diff --git a/client/components/modals/collections/AddCreateModal.vue b/client/components/modals/collections/AddCreateModal.vue index 09571b26..e124f9e3 100644 --- a/client/components/modals/collections/AddCreateModal.vue +++ b/client/components/modals/collections/AddCreateModal.vue @@ -104,7 +104,7 @@ export default { return this.$store.state.globals.showBatchCollectionModal }, selectedBookIds() { - return this.$store.state.selectedLibraryItems || [] + return (this.$store.state.globals.selectedMediaItems || []).map((i) => i.id) }, currentLibraryId() { return this.$store.state.libraries.currentLibraryId diff --git a/client/components/widgets/AuthorsSlider.vue b/client/components/widgets/AuthorsSlider.vue index 2a93f6a1..72e02f11 100644 --- a/client/components/widgets/AuthorsSlider.vue +++ b/client/components/widgets/AuthorsSlider.vue @@ -61,7 +61,7 @@ export default { return Math.floor(this.clientWidth / (this.cardWidth + 16)) }, isSelectionMode() { - return this.$store.getters['getNumLibraryItemsSelected'] > 0 + return this.$store.getters['globals/getIsBatchSelectingMediaItems'] } }, methods: { diff --git a/client/components/widgets/EpisodeSlider.vue b/client/components/widgets/EpisodeSlider.vue index 66fd45f0..1c6c55bb 100644 --- a/client/components/widgets/EpisodeSlider.vue +++ b/client/components/widgets/EpisodeSlider.vue @@ -77,7 +77,7 @@ export default { return Math.floor(this.clientWidth / (this.cardWidth + 16)) }, isSelectionMode() { - return this.$store.getters['getNumLibraryItemsSelected'] > 0 + return this.$store.getters['globals/getIsBatchSelectingMediaItems'] } }, methods: { @@ -101,14 +101,14 @@ export default { this.updateSelectionMode(this.isSelectionMode) }, updateSelectionMode(val) { - var selectedLibraryItems = this.$store.state.selectedLibraryItems + const selectedMediaItems = this.$store.state.globals.selectedMediaItems this.items.forEach((ent) => { - var component = this.$refs[`slider-episode-${ent.recentEpisode.id}`] + let component = this.$refs[`slider-episode-${ent.recentEpisode.id}`] if (!component || !component.length) return component = component[0] component.setSelectionMode(val) - component.selected = selectedLibraryItems.includes(ent.id) + component.selected = selectedMediaItems.some((i) => i.id === ent.id) }) }, scrolled() { diff --git a/client/components/widgets/ItemSlider.vue b/client/components/widgets/ItemSlider.vue index a181d600..7ee1f390 100644 --- a/client/components/widgets/ItemSlider.vue +++ b/client/components/widgets/ItemSlider.vue @@ -63,7 +63,7 @@ export default { return Math.floor(this.clientWidth / (this.cardWidth + 16)) }, isSelectionMode() { - return this.$store.getters['getNumLibraryItemsSelected'] > 0 + return this.$store.getters['globals/getIsBatchSelectingMediaItems'] } }, methods: { @@ -82,14 +82,14 @@ export default { this.updateSelectionMode(this.isSelectionMode) }, updateSelectionMode(val) { - var selectedLibraryItems = this.$store.state.selectedLibraryItems + const selectedMediaItems = this.$store.state.globals.selectedMediaItems this.items.forEach((item) => { - var component = this.$refs[`slider-item-${item.id}`] + let component = this.$refs[`slider-item-${item.id}`] if (!component || !component.length) return component = component[0] component.setSelectionMode(val) - component.selected = selectedLibraryItems.includes(item.id) + component.selected = selectedMediaItems.some((i) => i.id === item.id) }) }, scrolled() { diff --git a/client/components/widgets/SeriesSlider.vue b/client/components/widgets/SeriesSlider.vue index b1e16c55..2244e577 100644 --- a/client/components/widgets/SeriesSlider.vue +++ b/client/components/widgets/SeriesSlider.vue @@ -61,7 +61,7 @@ export default { return Math.floor(this.clientWidth / (this.cardWidth + 16)) }, isSelectionMode() { - return this.$store.getters['getNumLibraryItemsSelected'] > 0 + return this.$store.getters['globals/getIsBatchSelectingMediaItems'] } }, methods: { diff --git a/client/layouts/default.vue b/client/layouts/default.vue index 2dd600d7..fe2aab53 100644 --- a/client/layouts/default.vue +++ b/client/layouts/default.vue @@ -42,9 +42,8 @@ export default { if (this.$store.state.showEditModal) { this.$store.commit('setShowEditModal', false) } - if (this.$store.state.selectedLibraryItems) { - this.$store.commit('setSelectedLibraryItems', []) - } + + this.$store.commit('globals/resetSelectedMediaItems', []) this.updateBodyClass() } }, @@ -504,9 +503,9 @@ export default { } // Batch selecting - if (this.$store.getters['getNumLibraryItemsSelected'] && name === 'Escape') { + if (this.$store.getters['globals/getIsBatchSelectingMediaItems'] && name === 'Escape') { // ESCAPE key cancels batch selection - this.$store.commit('setSelectedLibraryItems', []) + this.$store.commit('globals/resetSelectedMediaItems', []) this.$eventBus.$emit('bookshelf_clear_selection') e.preventDefault() return diff --git a/client/mixins/bookshelfCardsHelpers.js b/client/mixins/bookshelfCardsHelpers.js index 69b53647..26f5a9df 100644 --- a/client/mixins/bookshelfCardsHelpers.js +++ b/client/mixins/bookshelfCardsHelpers.js @@ -32,7 +32,7 @@ export default { shelfEl.appendChild(bookComponent.$el) if (this.isSelectionMode) { bookComponent.setSelectionMode(true) - if (this.selectedLibraryItems.includes(bookComponent.libraryItemId) || this.isSelectAll) { + if (this.selectedMediaItems.some(i => i.id === bookComponent.libraryItemId) || this.isSelectAll) { bookComponent.selected = true } else { bookComponent.selected = false @@ -89,7 +89,7 @@ export default { } if (this.isSelectionMode) { instance.setSelectionMode(true) - if (instance.libraryItemId && this.selectedLibraryItems.includes(instance.libraryItemId) || this.isSelectAll) { + if (instance.libraryItemId && this.selectedMediaItems.some(i => i.id === instance.libraryItemId) || this.isSelectAll) { instance.selected = true } } diff --git a/client/pages/batch/index.vue b/client/pages/batch/index.vue index 1b074f1f..976005eb 100644 --- a/client/pages/batch/index.vue +++ b/client/pages/batch/index.vue @@ -91,11 +91,13 @@