added select all option for library items

This commit is contained in:
zipben 2025-06-05 01:08:08 +00:00
parent 140544ba77
commit 9baca4f16c
7 changed files with 169 additions and 10 deletions

View file

@ -499,6 +499,70 @@ export default {
} else {
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() {

View file

@ -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.addCollapseSeriesMenuItem(items)
@ -426,12 +433,19 @@ export default {
if (action === 'export-opml') {
this.exportOPML()
return
} else if (action === 'select-all') {
this.selectAllItems()
return
} else if (this.handleSubtitlesAction(action)) {
return
} else if (this.handleCollapseSeriesAction(action)) {
return
}
},
selectAllItems() {
// Emit an event to the parent component to handle selecting all items
this.$emit('select-all-items')
},
exportOPML() {
this.$downloadFile(`/api/libraries/${this.currentLibraryId}/opml?token=${this.$store.getters['user/getToken']}`, null, true)
},
@ -644,7 +658,6 @@ export default {
}
</script>
<style>
#toolbar {
box-shadow: 0px 8px 6px #111111aa;

View file

@ -880,6 +880,64 @@ export default {
const shelfOffsetY = this.shelfPaddingHeight * this.sizeMultiplier
const shelfOffsetX = (entityIndex - 1) * this.totalEntityCardWidth + this.bookshelfMarginLeft
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() {

View file

@ -1,7 +1,7 @@
<template>
<div class="page" :class="streamLibraryItem ? 'streaming' : ''">
<app-book-shelf-toolbar :page="id || ''" />
<app-lazy-bookshelf :page="id || ''" />
<app-book-shelf-toolbar :page="id || ''" @select-all-items="selectAllItems" />
<app-lazy-bookshelf ref="lazyBookshelf" :page="id || ''" />
</div>
</template>
@ -44,6 +44,13 @@ export default {
return this.$store.state.streamLibraryItem
}
},
methods: {}
methods: {
selectAllItems() {
if (this.$refs.lazyBookshelf) {
this.$refs.lazyBookshelf.selectAllItems()
} else {
}
}
}
}
</script>

View file

@ -1,7 +1,7 @@
<template>
<div class="page" :class="streamLibraryItem ? 'streaming' : ''">
<app-book-shelf-toolbar is-home />
<app-book-shelf-categorized />
<app-book-shelf-toolbar is-home @select-all-items="selectAllItems" />
<app-book-shelf-categorized ref="bookShelfCategorized" />
</div>
</template>
@ -25,7 +25,14 @@ export default {
return this.$store.state.streamLibraryItem
}
},
methods: {},
methods: {
selectAllItems() {
if (this.$refs.bookShelfCategorized) {
this.$refs.bookShelfCategorized.selectAllItems()
} else {
}
}
},
mounted() {},
beforeDestroy() {}
}

View file

@ -1,6 +1,6 @@
<template>
<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" />
<div v-else class="w-full py-16">
<p class="text-xl text-center">{{ $getString('MessageNoSearchResultsFor', [query]) }}</p>
@ -11,10 +11,13 @@
<script>
export default {
async asyncData({ store, params, redirect, query, app }) {
if (!query.q) {
return redirect(`/library/${params.library}`)
}
const libraryId = params.library
const library = await store.dispatch('libraries/fetch', libraryId)
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) => {
console.error('Failed to search library', error)
@ -74,6 +77,12 @@ export default {
this.$refs.bookshelf.setShelvesFromSearch()
}
})
},
selectAllItems() {
if (this.$refs.bookshelf) {
this.$refs.bookshelf.selectAllItems()
} else {
}
}
},
mounted() {},

View file

@ -1,4 +1,5 @@
{
"LabelSelectAll": "Select All",
"LabelComments": "Comments",
"PlaceholderAddComment": "Add a comment... (Ctrl+Enter to post)",
"ButtonPost": "Post",