Implement Ctrl+A Select All and Batch Reset functionality

This commit is contained in:
Tiberiu Ichim 2026-02-15 16:33:16 +02:00
parent 23034e6672
commit f7506e84d3
9 changed files with 266 additions and 3 deletions

View file

@ -186,6 +186,11 @@ export default {
action: 'rescan'
})
options.push({
text: 'Reset Metadata',
action: 'reset-metadata'
})
// The limit of 50 is introduced because of the URL length. Each id has 36 chars, so 36 * 40 = 1440
// + 40 , separators = 1480 chars + base path 280 chars = 1760 chars. This keeps the URL under 2000 chars even with longer domains
if (this.selectedMediaItems.length <= 40) {
@ -261,6 +266,8 @@ export default {
this.batchMerge()
} else if (action === 'consolidate') {
this.batchConsolidate()
} else if (action === 'reset-metadata') {
this.batchResetMetadata()
}
},
batchConsolidate() {
@ -294,6 +301,34 @@ export default {
}
this.$store.commit('globals/setConfirmPrompt', payload)
},
batchResetMetadata() {
const payload = {
message: `Are you sure you want to reset metadata for ${this.numMediaItemsSelected} items? This will remove metadata files and re-scan the items from files.`,
callback: (confirmed) => {
if (confirmed) {
this.$store.commit('setProcessingBatch', true)
this.$axios
.$post('/api/items/batch/reset-metadata', {
libraryItemIds: this.selectedMediaItems.map((i) => i.id)
})
.then(() => {
this.$toast.success('Batch reset metadata successful')
this.cancelSelectionMode()
})
.catch((error) => {
console.error('Batch reset metadata failed', error)
const errorMsg = error.response?.data || 'Batch reset metadata failed'
this.$toast.error(errorMsg)
})
.finally(() => {
this.$store.commit('setProcessingBatch', false)
})
}
},
type: 'yesNo'
}
this.$store.commit('globals/setConfirmPrompt', payload)
},
batchMerge() {
const payload = {
message: this.$strings.MessageConfirmBatchMerge,

View file

@ -83,7 +83,8 @@ export default {
lastTimestamp: 0,
postScrollTimeout: null,
currFirstEntityIndex: -1,
currLastEntityIndex: -1
currLastEntityIndex: -1,
isSelectAll: false
}
},
watch: {
@ -246,17 +247,48 @@ export default {
}
},
clearSelectedEntities() {
this.isSelectAll = false
this.updateBookSelectionMode(false)
this.isSelectionMode = false
},
selectAll() {
if (this.entityName !== 'items' && this.entityName !== 'series-books' && this.entityName !== 'collections' && this.entityName !== 'playlists') {
return
}
this.isSelectAll = true
this.isSelectionMode = true
const itemsToSelect = []
this.entities.forEach((entity) => {
if (entity && !entity.collapsedSeries) {
const mediaItem = {
id: entity.id,
libraryId: entity.libraryId,
mediaType: entity.mediaType,
hasTracks: entity.mediaType === 'podcast' || entity.media.audioFile || entity.media.numTracks || (entity.media.tracks && entity.media.tracks.length)
}
itemsToSelect.push(mediaItem)
}
})
if (itemsToSelect.length) {
this.$store.commit('globals/addBatchMediaItemsSelected', itemsToSelect)
}
this.updateBookSelectionMode(true)
},
selectEntity(entity, shiftKey) {
if (this.entityName === 'items' || this.entityName === 'series-books') {
const indexOf = this.entities.findIndex((ent) => ent && ent.id === entity.id)
const lastLastItemIndexSelected = this.lastItemIndexSelected
if (!this.selectedMediaItems.some((i) => i.id === entity.id)) {
const alreadySelected = this.selectedMediaItems.some((i) => i.id === entity.id)
if (!alreadySelected) {
this.lastItemIndexSelected = indexOf
} else {
this.lastItemIndexSelected = -1
this.isSelectAll = false // Deselecting an item turns off "Select All" mode
}
if (shiftKey && lastLastItemIndexSelected >= 0) {
@ -322,7 +354,11 @@ export default {
updateBookSelectionMode(isSelectionMode) {
for (const key in this.entityComponentRefs) {
if (this.entityIndexesMounted.includes(Number(key))) {
this.entityComponentRefs[key].setSelectionMode(isSelectionMode)
const component = this.entityComponentRefs[key]
component.setSelectionMode(isSelectionMode)
if (isSelectionMode && this.isSelectAll) {
component.selected = true
}
}
}
if (!isSelectionMode) {
@ -780,8 +816,19 @@ export default {
windowResize() {
this.executeRebuild()
},
handleKeyDown(e) {
if ((e.ctrlKey || e.metaKey) && e.key === 'a') {
// Only trigger if no input/textarea is focused
if (['INPUT', 'TEXTAREA'].includes(document.activeElement.tagName)) {
return
}
e.preventDefault()
this.selectAll()
}
},
initListeners() {
window.addEventListener('resize', this.windowResize)
window.addEventListener('keydown', this.handleKeyDown)
this.$nextTick(() => {
var bookshelf = document.getElementById('bookshelf')
@ -817,6 +864,7 @@ export default {
},
removeListeners() {
window.removeEventListener('resize', this.windowResize)
window.removeEventListener('keydown', this.handleKeyDown)
var bookshelf = document.getElementById('bookshelf')
if (bookshelf) {
bookshelf.removeEventListener('scroll', this.scroll)