Add:Support for shift selecting multiple library items #1020

This commit is contained in:
advplyr 2022-10-15 17:17:40 -05:00
parent 4cf43bc105
commit 77139c7256
7 changed files with 129 additions and 26 deletions

View File

@ -16,10 +16,10 @@
<!-- Alternate plain view --> <!-- Alternate plain view -->
<div v-else-if="isAlternativeBookshelfView" class="w-full mb-24"> <div v-else-if="isAlternativeBookshelfView" class="w-full mb-24">
<template v-for="(shelf, index) in shelves"> <template v-for="(shelf, index) in shelves">
<widgets-item-slider v-if="shelf.type === 'book' || shelf.type === 'podcast'" :key="index + '.'" :items="shelf.entities" :continue-listening-shelf="shelf.id === 'continue-listening'" :height="232 * sizeMultiplier" class="bookshelf-row pl-8 my-6"> <widgets-item-slider v-if="shelf.type === 'book' || shelf.type === 'podcast'" :key="index + '.'" :items="shelf.entities" :continue-listening-shelf="shelf.id === 'continue-listening'" :height="232 * sizeMultiplier" class="bookshelf-row pl-8 my-6" @selectEntity="(payload) => selectEntity(payload, index)">
<p class="font-semibold text-gray-100" :style="{ fontSize: sizeMultiplier + 'rem' }">{{ shelf.label }}</p> <p class="font-semibold text-gray-100" :style="{ fontSize: sizeMultiplier + 'rem' }">{{ shelf.label }}</p>
</widgets-item-slider> </widgets-item-slider>
<widgets-episode-slider v-else-if="shelf.type === 'episode'" :key="index + '.'" :items="shelf.entities" :continue-listening-shelf="shelf.id === 'continue-listening'" :height="232 * sizeMultiplier" class="bookshelf-row pl-8 my-6"> <widgets-episode-slider v-else-if="shelf.type === 'episode'" :key="index + '.'" :items="shelf.entities" :continue-listening-shelf="shelf.id === 'continue-listening'" :height="232 * sizeMultiplier" class="bookshelf-row pl-8 my-6" @selectEntity="(payload) => selectEntity(payload, index)">
<p class="font-semibold text-gray-100" :style="{ fontSize: sizeMultiplier + 'rem' }">{{ shelf.label }}</p> <p class="font-semibold text-gray-100" :style="{ fontSize: sizeMultiplier + 'rem' }">{{ shelf.label }}</p>
</widgets-episode-slider> </widgets-episode-slider>
<widgets-series-slider v-else-if="shelf.type === 'series'" :key="index + '.'" :items="shelf.entities" :height="232 * sizeMultiplier" class="bookshelf-row pl-8 my-6"> <widgets-series-slider v-else-if="shelf.type === 'series'" :key="index + '.'" :items="shelf.entities" :height="232 * sizeMultiplier" class="bookshelf-row pl-8 my-6">
@ -33,7 +33,7 @@
<!-- Regular bookshelf view --> <!-- Regular bookshelf view -->
<div v-else class="w-full"> <div v-else class="w-full">
<template v-for="(shelf, index) in shelves"> <template v-for="(shelf, index) in shelves">
<app-book-shelf-row :key="index" :index="index" :shelf="shelf" :size-multiplier="sizeMultiplier" :book-cover-width="bookCoverWidth" :book-cover-aspect-ratio="coverAspectRatio" :continue-listening-shelf="shelf.id === 'continue-listening'" /> <app-book-shelf-row :key="index" :index="index" :shelf="shelf" :size-multiplier="sizeMultiplier" :book-cover-width="bookCoverWidth" :book-cover-aspect-ratio="coverAspectRatio" :continue-listening-shelf="shelf.id === 'continue-listening'" @selectEntity="(payload) => selectEntity(payload, index)" />
</template> </template>
</div> </div>
</div> </div>
@ -54,7 +54,8 @@ export default {
keywordFilterTimeout: null, keywordFilterTimeout: null,
scannerParseSubtitle: false, scannerParseSubtitle: false,
wrapperClientWidth: 0, wrapperClientWidth: 0,
shelves: [] shelves: [],
lastItemIndexSelected: -1
} }
}, },
computed: { computed: {
@ -87,9 +88,64 @@ export default {
sizeMultiplier() { sizeMultiplier() {
var baseSize = this.isCoverSquareAspectRatio ? 192 : 120 var baseSize = this.isCoverSquareAspectRatio ? 192 : 120
return this.bookCoverWidth / baseSize return this.bookCoverWidth / baseSize
},
selectedLibraryItems() {
return this.$store.state.selectedLibraryItems || []
} }
}, },
methods: { methods: {
selectEntity({ entity, shiftKey }, shelfIndex) {
const shelf = this.shelves[shelfIndex]
const entityShelfIndex = shelf.entities.findIndex((ent) => ent.id === entity.id)
const indexOf = shelf.shelfStartIndex + entityShelfIndex
const lastLastItemIndexSelected = this.lastItemIndexSelected
if (!this.selectedLibraryItems.includes(entity.id)) {
this.lastItemIndexSelected = indexOf
} else {
this.lastItemIndexSelected = -1
}
if (shiftKey && lastLastItemIndexSelected >= 0) {
var loopStart = indexOf
var loopEnd = lastLastItemIndexSelected
if (indexOf > lastLastItemIndexSelected) {
loopStart = lastLastItemIndexSelected
loopEnd = indexOf
}
const flattenedEntitiesArray = []
this.shelves.map((s) => flattenedEntitiesArray.push(...s.entities))
var 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)) {
isSelecting = true
break
}
}
}
if (isSelecting) this.lastItemIndexSelected = indexOf
for (let i = loopStart; i <= loopEnd; i++) {
const thisEntity = flattenedEntitiesArray[i]
if (thisEntity) {
this.$store.commit('setLibraryItemSelected', { libraryItemId: thisEntity.id, selected: isSelecting })
} else {
console.error('Invalid entity index', i)
}
}
} else {
this.$store.commit('toggleLibraryItemSelected', entity.id)
}
this.$nextTick(() => {
this.$eventBus.$emit('item-selected', entity)
})
},
async init() { async init() {
this.wrapperClientWidth = this.$refs.wrapper ? this.$refs.wrapper.clientWidth : 0 this.wrapperClientWidth = this.$refs.wrapper ? this.$refs.wrapper.clientWidth : 0
@ -110,6 +166,12 @@ export default {
console.error('Failed to fetch categories', error) console.error('Failed to fetch categories', error)
return [] return []
}) })
let totalEntityCount = 0
for (const shelf of categories) {
shelf.shelfStartIndex = totalEntityCount
totalEntityCount += shelf.entities.length
}
this.shelves = categories this.shelves = categories
}, },
async setShelvesFromSearch() { async setShelvesFromSearch() {

View File

@ -138,11 +138,8 @@ export default {
}) })
} }
}, },
selectItem(libraryItem) { selectItem(payload) {
this.$store.commit('toggleLibraryItemSelected', libraryItem.id) this.$emit('selectEntity', payload)
this.$nextTick(() => {
this.$eventBus.$emit('item-selected', libraryItem)
})
}, },
itemSelectedEvt() { itemSelectedEvt() {
this.updateSelectionMode(this.isSelectionMode) this.updateSelectionMode(this.isSelectionMode)

View File

@ -61,7 +61,8 @@ export default {
keywordFilter: null, keywordFilter: null,
currScrollTop: 0, currScrollTop: 0,
resizeTimeout: null, resizeTimeout: null,
mountWindowWidth: 0 mountWindowWidth: 0,
lastItemIndexSelected: -1
} }
}, },
watch: { watch: {
@ -212,9 +213,55 @@ export default {
this.updateBookSelectionMode(false) this.updateBookSelectionMode(false)
this.isSelectionMode = false this.isSelectionMode = false
}, },
selectEntity(entity) { selectEntity(entity, shiftKey) {
if (this.entityName === 'books' || this.entityName === 'series-books') { if (this.entityName === 'books' || this.entityName === 'series-books') {
this.$store.commit('toggleLibraryItemSelected', entity.id) var indexOf = this.entities.findIndex((ent) => ent && ent.id === entity.id)
const lastLastItemIndexSelected = this.lastItemIndexSelected
if (!this.selectedLibraryItems.includes(entity.id)) {
this.lastItemIndexSelected = indexOf
} else {
this.lastItemIndexSelected = -1
}
if (shiftKey && lastLastItemIndexSelected >= 0) {
var loopStart = indexOf
var loopEnd = lastLastItemIndexSelected
if (indexOf > lastLastItemIndexSelected) {
loopStart = lastLastItemIndexSelected
loopEnd = indexOf
}
var 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 = this.entities[i]
if (thisEntity && !thisEntity.collapsedSeries) {
if (!this.selectedLibraryItems.includes(thisEntity.id)) {
isSelecting = true
break
}
}
}
if (isSelecting) this.lastItemIndexSelected = indexOf
for (let i = loopStart; i <= loopEnd; i++) {
const thisEntity = this.entities[i]
if (thisEntity.collapsedSeries) {
console.warn('Ignoring collapsed series')
continue
}
const entityComponentRef = this.entityComponentRefs[i]
if (thisEntity && entityComponentRef) {
entityComponentRef.selected = isSelecting
this.$store.commit('setLibraryItemSelected', { libraryItemId: thisEntity.id, selected: isSelecting })
} else {
console.error('Invalid entity index', i)
}
}
} else {
this.$store.commit('toggleLibraryItemSelected', entity.id)
}
var newIsSelectionMode = !!this.selectedLibraryItems.length var newIsSelectionMode = !!this.selectedLibraryItems.length
if (this.isSelectionMode !== newIsSelectionMode) { if (this.isSelectionMode !== newIsSelectionMode) {
@ -229,6 +276,9 @@ export default {
this.entityComponentRefs[key].setSelectionMode(isSelectionMode) this.entityComponentRefs[key].setSelectionMode(isSelectionMode)
} }
} }
if (!isSelectionMode) {
this.lastItemIndexSelected = -1
}
}, },
async fetchEntites(page = 0) { async fetchEntites(page = 0) {
var startIndex = page * this.booksPerFetch var startIndex = page * this.booksPerFetch

View File

@ -719,10 +719,10 @@ export default {
console.log('Got library itemn', libraryItem) console.log('Got library itemn', libraryItem)
this.store.commit('showEReader', libraryItem) this.store.commit('showEReader', libraryItem)
}, },
selectBtnClick() { selectBtnClick(evt) {
if (this.processingBatch) return if (this.processingBatch) return
this.selected = !this.selected this.selected = !this.selected
this.$emit('select', this.libraryItem) this.$emit('select', { entity: this.libraryItem, shiftKey: evt.shiftKey })
}, },
async play() { async play() {
var eventBus = this.$eventBus || this.$nuxt.$eventBus var eventBus = this.$eventBus || this.$nuxt.$eventBus

View File

@ -94,11 +94,8 @@ export default {
this.$store.commit('setBookshelfBookIds', itemIds) this.$store.commit('setBookshelfBookIds', itemIds)
this.$store.commit('showEditModal', libraryItem) this.$store.commit('showEditModal', libraryItem)
}, },
selectItem(libraryItem) { selectItem(payload) {
this.$store.commit('toggleLibraryItemSelected', libraryItem.id) this.$emit('selectEntity', payload)
this.$nextTick(() => {
this.$eventBus.$emit('item-selected', libraryItem)
})
}, },
itemSelectedEvt() { itemSelectedEvt() {
this.updateSelectionMode(this.isSelectionMode) this.updateSelectionMode(this.isSelectionMode)

View File

@ -74,11 +74,8 @@ export default {
this.$store.commit('setBookshelfBookIds', itemIds) this.$store.commit('setBookshelfBookIds', itemIds)
this.$store.commit('showEditModal', libraryItem) this.$store.commit('showEditModal', libraryItem)
}, },
selectItem(libraryItem) { selectItem(payload) {
this.$store.commit('toggleLibraryItemSelected', libraryItem.id) this.$emit('selectEntity', payload)
this.$nextTick(() => {
this.$eventBus.$emit('item-selected', libraryItem)
})
}, },
itemSelectedEvt() { itemSelectedEvt() {
this.updateSelectionMode(this.isSelectionMode) this.updateSelectionMode(this.isSelectionMode)

View File

@ -68,8 +68,8 @@ export default {
this.$on('edit', (entity) => { this.$on('edit', (entity) => {
if (_this.editEntity) _this.editEntity(entity) if (_this.editEntity) _this.editEntity(entity)
}) })
this.$on('select', (entity) => { this.$on('select', ({ entity, shiftKey }) => {
if (_this.selectEntity) _this.selectEntity(entity) if (_this.selectEntity) _this.selectEntity(entity, shiftKey)
}) })
} }
}) })