mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2026-07-09 10:51:37 +00:00
added select all option for library items
This commit is contained in:
parent
140544ba77
commit
9baca4f16c
7 changed files with 169 additions and 10 deletions
|
|
@ -499,6 +499,70 @@ export default {
|
||||||
} else {
|
} else {
|
||||||
console.error('Error socket not initialized')
|
console.error('Error socket not initialized')
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
selectAllItems() {
|
||||||
|
console.log('BookShelfCategorized: selectAllItems called!')
|
||||||
|
console.log('BookShelfCategorized: shelves:', this.shelves)
|
||||||
|
|
||||||
|
// Get all visible items from shelves
|
||||||
|
const allVisibleEntities = []
|
||||||
|
this.shelves.forEach((shelf) => {
|
||||||
|
console.log('BookShelfCategorized: processing shelf:', shelf.label, 'type:', shelf.type, 'entities:', shelf.entities.length)
|
||||||
|
if (shelf.type === 'book' || shelf.type === 'podcast') {
|
||||||
|
allVisibleEntities.push(...shelf.entities)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Check if all visible items are already selected
|
||||||
|
const selectedMediaItems = this.$store.state.globals.selectedMediaItems
|
||||||
|
const visibleEntityIds = allVisibleEntities.map((e) => e.id)
|
||||||
|
const selectedVisibleCount = selectedMediaItems.filter((item) => visibleEntityIds.includes(item.id)).length
|
||||||
|
const shouldDeselect = selectedVisibleCount === visibleEntityIds.length && selectedVisibleCount > 0
|
||||||
|
|
||||||
|
console.log('BookShelfCategorized: visible entities:', visibleEntityIds.length, 'selected visible:', selectedVisibleCount, 'shouldDeselect:', shouldDeselect)
|
||||||
|
|
||||||
|
if (shouldDeselect) {
|
||||||
|
// Deselect all visible items
|
||||||
|
this.shelves.forEach((shelf) => {
|
||||||
|
console.log('BookShelfCategorized: processing shelf for deselection:', shelf.label, 'type:', shelf.type, 'entities:', shelf.entities.length)
|
||||||
|
if (shelf.type === 'book' || shelf.type === 'podcast') {
|
||||||
|
shelf.entities.forEach((entity) => {
|
||||||
|
console.log('BookShelfCategorized: deselecting entity:', entity.id, entity.mediaType)
|
||||||
|
// Create the media item object the same way the individual deselect does
|
||||||
|
const mediaItem = {
|
||||||
|
id: entity.id,
|
||||||
|
mediaType: entity.mediaType,
|
||||||
|
hasTracks: entity.mediaType === 'podcast' || entity.media.audioFile || entity.media.numTracks || (entity.media.tracks && entity.media.tracks.length)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove the item from selection
|
||||||
|
this.$store.commit('globals/setMediaItemSelected', { item: mediaItem, selected: false })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
// Select all visible items
|
||||||
|
this.shelves.forEach((shelf) => {
|
||||||
|
if (shelf.type === 'book' || shelf.type === 'podcast') {
|
||||||
|
shelf.entities.forEach((entity) => {
|
||||||
|
// Create the media item object the same way the individual select does
|
||||||
|
const mediaItem = {
|
||||||
|
id: entity.id,
|
||||||
|
mediaType: entity.mediaType,
|
||||||
|
hasTracks: entity.mediaType === 'podcast' || entity.media.audioFile || entity.media.numTracks || (entity.media.tracks && entity.media.tracks.length)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use the existing store mutation to mark the item as selected
|
||||||
|
this.$store.commit('globals/setMediaItemSelected', { item: mediaItem, selected: true })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trigger a single UI update after all items are selected/deselected
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.$eventBus.$emit('item-selected')
|
||||||
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
|
|
|
||||||
|
|
@ -328,6 +328,13 @@ export default {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!this.isBatchSelecting && (!this.page || this.page === 'search')) {
|
||||||
|
items.push({
|
||||||
|
text: 'Select All',
|
||||||
|
action: 'select-all'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
this.addSubtitlesMenuItem(items)
|
this.addSubtitlesMenuItem(items)
|
||||||
this.addCollapseSeriesMenuItem(items)
|
this.addCollapseSeriesMenuItem(items)
|
||||||
|
|
||||||
|
|
@ -426,12 +433,19 @@ export default {
|
||||||
if (action === 'export-opml') {
|
if (action === 'export-opml') {
|
||||||
this.exportOPML()
|
this.exportOPML()
|
||||||
return
|
return
|
||||||
|
} else if (action === 'select-all') {
|
||||||
|
this.selectAllItems()
|
||||||
|
return
|
||||||
} else if (this.handleSubtitlesAction(action)) {
|
} else if (this.handleSubtitlesAction(action)) {
|
||||||
return
|
return
|
||||||
} else if (this.handleCollapseSeriesAction(action)) {
|
} else if (this.handleCollapseSeriesAction(action)) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
selectAllItems() {
|
||||||
|
// Emit an event to the parent component to handle selecting all items
|
||||||
|
this.$emit('select-all-items')
|
||||||
|
},
|
||||||
exportOPML() {
|
exportOPML() {
|
||||||
this.$downloadFile(`/api/libraries/${this.currentLibraryId}/opml?token=${this.$store.getters['user/getToken']}`, null, true)
|
this.$downloadFile(`/api/libraries/${this.currentLibraryId}/opml?token=${this.$store.getters['user/getToken']}`, null, true)
|
||||||
},
|
},
|
||||||
|
|
@ -644,9 +658,8 @@ export default {
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
#toolbar {
|
#toolbar {
|
||||||
box-shadow: 0px 8px 6px #111111aa;
|
box-shadow: 0px 8px 6px #111111aa;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
@ -880,6 +880,64 @@ export default {
|
||||||
const shelfOffsetY = this.shelfPaddingHeight * this.sizeMultiplier
|
const shelfOffsetY = this.shelfPaddingHeight * this.sizeMultiplier
|
||||||
const shelfOffsetX = (entityIndex - 1) * this.totalEntityCardWidth + this.bookshelfMarginLeft
|
const shelfOffsetX = (entityIndex - 1) * this.totalEntityCardWidth + this.bookshelfMarginLeft
|
||||||
return `translate3d(${shelfOffsetX}px, ${shelfOffsetY}px, 0px)`
|
return `translate3d(${shelfOffsetX}px, ${shelfOffsetY}px, 0px)`
|
||||||
|
},
|
||||||
|
selectAllItems() {
|
||||||
|
// Check if all visible items are already selected
|
||||||
|
const selectedMediaItems = this.$store.state.globals.selectedMediaItems
|
||||||
|
const visibleEntityIds = this.entities.filter((e) => e).map((e) => e.id)
|
||||||
|
const selectedVisibleCount = selectedMediaItems.filter((item) => visibleEntityIds.includes(item.id)).length
|
||||||
|
const shouldDeselect = selectedVisibleCount === visibleEntityIds.length && selectedVisibleCount > 0
|
||||||
|
|
||||||
|
if (shouldDeselect) {
|
||||||
|
// Deselect all visible items
|
||||||
|
this.entities.forEach((entity) => {
|
||||||
|
if (entity) {
|
||||||
|
// Create the media item object the same way the individual deselect does
|
||||||
|
const mediaItem = {
|
||||||
|
id: entity.id,
|
||||||
|
mediaType: entity.mediaType,
|
||||||
|
hasTracks: entity.mediaType === 'podcast' || entity.media.audioFile || entity.media.numTracks || (entity.media.tracks && entity.media.tracks.length)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove the item from selection
|
||||||
|
this.$store.commit('globals/setMediaItemSelected', { item: mediaItem, selected: false })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
// Select all visible items
|
||||||
|
this.entities.forEach((entity) => {
|
||||||
|
if (entity) {
|
||||||
|
// Create the media item object the same way the individual select does
|
||||||
|
const mediaItem = {
|
||||||
|
id: entity.id,
|
||||||
|
mediaType: entity.mediaType,
|
||||||
|
hasTracks: entity.mediaType === 'podcast' || entity.media.audioFile || entity.media.numTracks || (entity.media.tracks && entity.media.tracks.length)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use the existing store mutation to mark the item as selected
|
||||||
|
this.$store.commit('globals/setMediaItemSelected', { item: mediaItem, selected: true })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Force update all card components to reflect the selection state
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.updateBookSelectionMode(true)
|
||||||
|
|
||||||
|
// Also update the selected property of each card component
|
||||||
|
const updatedSelectedItems = this.$store.state.globals.selectedMediaItems
|
||||||
|
|
||||||
|
for (const key in this.entityComponentRefs) {
|
||||||
|
if (this.entityIndexesMounted.includes(Number(key))) {
|
||||||
|
const component = this.entityComponentRefs[key]
|
||||||
|
const entity = this.entities[key]
|
||||||
|
if (component && entity) {
|
||||||
|
const isSelected = updatedSelectedItems.some((item) => item.id === entity.id)
|
||||||
|
component.selected = isSelected
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async mounted() {
|
async mounted() {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="page" :class="streamLibraryItem ? 'streaming' : ''">
|
<div class="page" :class="streamLibraryItem ? 'streaming' : ''">
|
||||||
<app-book-shelf-toolbar :page="id || ''" />
|
<app-book-shelf-toolbar :page="id || ''" @select-all-items="selectAllItems" />
|
||||||
<app-lazy-bookshelf :page="id || ''" />
|
<app-lazy-bookshelf ref="lazyBookshelf" :page="id || ''" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
@ -44,6 +44,13 @@ export default {
|
||||||
return this.$store.state.streamLibraryItem
|
return this.$store.state.streamLibraryItem
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {}
|
methods: {
|
||||||
|
selectAllItems() {
|
||||||
|
if (this.$refs.lazyBookshelf) {
|
||||||
|
this.$refs.lazyBookshelf.selectAllItems()
|
||||||
|
} else {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="page" :class="streamLibraryItem ? 'streaming' : ''">
|
<div class="page" :class="streamLibraryItem ? 'streaming' : ''">
|
||||||
<app-book-shelf-toolbar is-home />
|
<app-book-shelf-toolbar is-home @select-all-items="selectAllItems" />
|
||||||
<app-book-shelf-categorized />
|
<app-book-shelf-categorized ref="bookShelfCategorized" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
@ -25,7 +25,14 @@ export default {
|
||||||
return this.$store.state.streamLibraryItem
|
return this.$store.state.streamLibraryItem
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {},
|
methods: {
|
||||||
|
selectAllItems() {
|
||||||
|
if (this.$refs.bookShelfCategorized) {
|
||||||
|
this.$refs.bookShelfCategorized.selectAllItems()
|
||||||
|
} else {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
mounted() {},
|
mounted() {},
|
||||||
beforeDestroy() {}
|
beforeDestroy() {}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="page" :class="streamLibraryItem ? 'streaming' : ''">
|
<div class="page" :class="streamLibraryItem ? 'streaming' : ''">
|
||||||
<app-book-shelf-toolbar is-home page="search" :search-query="query" />
|
<app-book-shelf-toolbar is-home page="search" :search-query="query" @select-all-items="selectAllItems" />
|
||||||
<app-book-shelf-categorized v-if="hasResults" ref="bookshelf" search :results="results" />
|
<app-book-shelf-categorized v-if="hasResults" ref="bookshelf" search :results="results" />
|
||||||
<div v-else class="w-full py-16">
|
<div v-else class="w-full py-16">
|
||||||
<p class="text-xl text-center">{{ $getString('MessageNoSearchResultsFor', [query]) }}</p>
|
<p class="text-xl text-center">{{ $getString('MessageNoSearchResultsFor', [query]) }}</p>
|
||||||
|
|
@ -11,10 +11,13 @@
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
async asyncData({ store, params, redirect, query, app }) {
|
async asyncData({ store, params, redirect, query, app }) {
|
||||||
|
if (!query.q) {
|
||||||
|
return redirect(`/library/${params.library}`)
|
||||||
|
}
|
||||||
const libraryId = params.library
|
const libraryId = params.library
|
||||||
const library = await store.dispatch('libraries/fetch', libraryId)
|
const library = await store.dispatch('libraries/fetch', libraryId)
|
||||||
if (!library) {
|
if (!library) {
|
||||||
return redirect('/oops?message=Library not found')
|
return redirect(`/oops?message=Library "${libraryId}" not found`)
|
||||||
}
|
}
|
||||||
let results = await app.$axios.$get(`/api/libraries/${libraryId}/search?q=${encodeURIComponent(query.q)}`).catch((error) => {
|
let results = await app.$axios.$get(`/api/libraries/${libraryId}/search?q=${encodeURIComponent(query.q)}`).catch((error) => {
|
||||||
console.error('Failed to search library', error)
|
console.error('Failed to search library', error)
|
||||||
|
|
@ -74,6 +77,12 @@ export default {
|
||||||
this.$refs.bookshelf.setShelvesFromSearch()
|
this.$refs.bookshelf.setShelvesFromSearch()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
},
|
||||||
|
selectAllItems() {
|
||||||
|
if (this.$refs.bookshelf) {
|
||||||
|
this.$refs.bookshelf.selectAllItems()
|
||||||
|
} else {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {},
|
mounted() {},
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
{
|
{
|
||||||
|
"LabelSelectAll": "Select All",
|
||||||
"LabelComments": "Comments",
|
"LabelComments": "Comments",
|
||||||
"PlaceholderAddComment": "Add a comment... (Ctrl+Enter to post)",
|
"PlaceholderAddComment": "Add a comment... (Ctrl+Enter to post)",
|
||||||
"ButtonPost": "Post",
|
"ButtonPost": "Post",
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue