feat(webui): select multiple items using shift+click

This commit is contained in:
Gauthier Roebroeck 2021-06-29 14:05:42 +08:00
parent 8545574d38
commit f69a31eaf1
2 changed files with 65 additions and 31 deletions

View file

@ -14,25 +14,25 @@
:class="flexClass" :class="flexClass"
> >
<v-item <v-item
v-for="item in localItems" v-for="item in localItems"
:key="item.id" :key="item.id"
class="my-2 mx-2" class="my-2 mx-2"
v-slot:default="{ toggle, active }" :value="item" v-slot:default="{ toggle, active }" :value="item"
> >
<slot name="item"> <slot name="item">
<div style="position: relative" <div style="position: relative"
:class="draggable ? 'draggable-item' : undefined" :class="draggable ? 'draggable-item' : undefined"
> >
<item-card <item-card
class="item-card" class="item-card"
:item="item" :item="item"
:width="itemWidth" :width="itemWidth"
:selected="active" :selected="active"
:no-link="draggable || deletable" :no-link="draggable || deletable"
:preselect="shouldPreselect" :preselect="shouldPreselect"
:onEdit="(draggable || deletable) ? undefined : editFunction" :onEdit="(draggable || deletable) ? undefined : editFunction"
:onSelected="(draggable || deletable) ? undefined : selectable ? toggle: undefined" :onSelected="(draggable || deletable) ? undefined : selectable ? (item, event) => handleSelectClick(toggle, item, event): undefined"
:action-menu="actionMenu" :action-menu="actionMenu"
></item-card> ></item-card>
<v-slide-y-reverse-transition> <v-slide-y-reverse-transition>
@ -73,13 +73,13 @@
<script lang="ts"> <script lang="ts">
import ItemCard from '@/components/ItemCard.vue' import ItemCard from '@/components/ItemCard.vue'
import { computeCardWidth } from '@/functions/grid-utilities' import {computeCardWidth} from '@/functions/grid-utilities'
import Vue from 'vue' import Vue from 'vue'
import draggable from 'vuedraggable' import draggable from 'vuedraggable'
export default Vue.extend({ export default Vue.extend({
name: 'ItemBrowser', name: 'ItemBrowser',
components: { ItemCard, draggable }, components: {ItemCard, draggable},
props: { props: {
items: { items: {
type: Array, type: Array,
@ -122,51 +122,53 @@ export default Vue.extend({
}, },
data: () => { data: () => {
return { return {
selectedItems: [], selectedItems: [] as any[],
localItems: [], localItems: [],
lastClickedNoShift: undefined as any,
lastClickedShift: undefined as any,
width: 150, width: 150,
} }
}, },
watch: { watch: {
selectedItems: { selectedItems: {
handler () { handler() {
this.$emit('update:selected', this.selectedItems) this.$emit('update:selected', this.selectedItems)
}, },
immediate: true, immediate: true,
}, },
selected: { selected: {
handler () { handler() {
this.selectedItems = this.selected as [] this.selectedItems = this.selected as []
}, },
immediate: true, immediate: true,
}, },
items: { items: {
handler () { handler() {
this.localItems = this.items as [] this.localItems = this.items as []
}, },
immediate: true, immediate: true,
}, },
localItems: { localItems: {
handler () { handler() {
this.$emit('update:items', this.localItems) this.$emit('update:items', this.localItems)
}, },
immediate: true, immediate: true,
}, },
}, },
computed: { computed: {
flexClass (): string { flexClass(): string {
return this.nowrap ? 'd-flex flex-nowrap' : 'd-flex flex-wrap' return this.nowrap ? 'd-flex flex-nowrap' : 'd-flex flex-wrap'
}, },
hasItems (): boolean { hasItems(): boolean {
return this.items.length > 0 return this.items.length > 0
}, },
itemWidth (): number { itemWidth(): number {
return this.fixedItemWidth ? this.fixedItemWidth : this.width return this.fixedItemWidth ? this.fixedItemWidth : this.width
}, },
shouldPreselect (): boolean { shouldPreselect(): boolean {
return this.selectedItems.length > 0 return this.selectedItems.length > 0
}, },
dragOptions (): any { dragOptions(): any {
return { return {
animation: 200, animation: 200,
group: 'item-cards', group: 'item-cards',
@ -176,11 +178,43 @@ export default Vue.extend({
}, },
}, },
methods: { methods: {
onResize () { // handle normal click and shift click
handleSelectClick(toggle: () => void, item: any, e: MouseEvent) {
if (!e.shiftKey) {
this.lastClickedShift = undefined
this.lastClickedNoShift = item
} else {
// if it's a shift click and we haven't clicked anywhere before, consider the first item as the beginning of the selection
if (!this.lastClickedNoShift) this.lastClickedNoShift = this.$_.head(this.localItems)
}
if (e.shiftKey && this.lastClickedNoShift) {
// recompute last shift selection so we can unselect it
if (this.lastClickedShift) {
const pf = (i: any) => i !== this.lastClickedShift && i !== this.lastClickedNoShift
let previousShiftSelection = this.$_.dropRightWhile(this.$_.dropWhile(this.localItems, pf), pf)
this.$_.pullAll(this.selectedItems, previousShiftSelection)
}
// compute shift selection and select it
const f = (i: any) => i !== item && i !== this.lastClickedNoShift
let shiftSelection = this.$_.dropRightWhile(this.$_.dropWhile(this.localItems, f), f)
shiftSelection.forEach(i => {
if (!this.selectedItems.includes(i)) this.selectedItems.push(i)
})
this.lastClickedShift = item
return
}
// perform a normal toggle
toggle()
},
onResize() {
const content = this.$refs.content as HTMLElement const content = this.$refs.content as HTMLElement
this.width = computeCardWidth(content.clientWidth, this.$vuetify.breakpoint.name.toString()) this.width = computeCardWidth(content.clientWidth, this.$vuetify.breakpoint.name.toString())
}, },
deleteItem (item: any) { deleteItem(item: any) {
const index = this.localItems.findIndex((e: any) => e.id === item.id) const index = this.localItems.findIndex((e: any) => e.id === item.id)
this.localItems.splice(index, 1) this.localItems.splice(index, 1)
}, },

View file

@ -266,9 +266,9 @@ export default Vue.extend({
this.thumbnailCacheBust = '?' + this.$_.random(1000) this.thumbnailCacheBust = '?' + this.$_.random(1000)
} }
}, },
onClick() { onClick(e: MouseEvent) {
if (this.preselect && this.onSelected !== undefined) { if (this.preselect && this.onSelected !== undefined) {
this.selectItem() this.selectItem(e)
} else if (!this.noLink) { } else if (!this.noLink) {
this.goto() this.goto()
} }
@ -276,9 +276,9 @@ export default Vue.extend({
goto() { goto() {
this.$router.push(this.computedItem.to()) this.$router.push(this.computedItem.to())
}, },
selectItem() { selectItem(e: MouseEvent) {
if (this.onSelected !== undefined) { if (this.onSelected !== undefined) {
this.onSelected() this.onSelected(this.item, e)
} }
}, },
editItem() { editItem() {