From 2331e4d8c3b868f4a6356101fd3236b865065672 Mon Sep 17 00:00:00 2001 From: Ahmed Awan Date: Mon, 4 Mar 2024 14:24:47 -0600 Subject: [PATCH] add `Shift+Click` (and `Shift+Ctrl+Click`) selection Users can select a batch of items starting from one focused item, until the next `Shift+Click` item. If the user uses a combination of `Shift+Ctrl+Click`, the remainder of the selection persists and is not reset. --- .../History/Content/ContentItem.vue | 18 ++++++-- .../History/Content/SelectedItems.js | 45 ++++++++++++++++--- .../History/CurrentHistory/HistoryPanel.vue | 37 +++++++++------ 3 files changed, 78 insertions(+), 22 deletions(-) diff --git a/client/src/components/History/Content/ContentItem.vue b/client/src/components/History/Content/ContentItem.vue index 6d5a548b3f30..a17182251a19 100644 --- a/client/src/components/History/Content/ContentItem.vue +++ b/client/src/components/History/Content/ContentItem.vue @@ -6,7 +6,8 @@ :data-state="dataState" tabindex="0" role="button" - @keydown="onKeyDown"> + @keydown="onKeyDown" + @focus="$emit('update:item-focused')">
@@ -245,7 +246,10 @@ export default { this.$emit("shift-select", event.key); } else if (event.key === "ArrowUp" || event.key === "ArrowDown") { event.preventDefault(); + this.$emit("init-key-selection"); this.$emit("arrow-navigate", event.key); + } else if (event.key === "Tab") { + this.$emit("init-key-selection"); } else if (event.key === "Delete" && !this.selected && !this.item.deleted) { event.preventDefault(); this.onDelete(event.shiftKey); @@ -258,11 +262,17 @@ export default { } }, onClick(event) { - if (event && this.isCtrlKey(event)) { + if (event && event.shiftKey && this.isCtrlKey(event)) { + this.$emit("selected-to", false); + } else if (event && this.isCtrlKey(event)) { + this.$emit("init-key-selection"); this.$emit("update:selected", !this.selected); + } else if (event && event.shiftKey) { + this.$emit("selected-to", true); } else if (this.isPlaceholder) { - return; + this.$emit("init-key-selection"); } else if (this.isDataset) { + this.$emit("init-key-selection"); this.$emit("update:expand-dataset", !this.expandDataset); } else { this.$emit("view-collection", this.item, this.name); @@ -289,10 +299,12 @@ export default { onDelete(recursive = false) { this.$emit("delete", this.item, recursive); this.$emit("update:selected", false); + this.$emit("init-key-selection"); }, onUndelete() { this.$emit("undelete"); this.$emit("update:selected", false); + this.$emit("init-key-selection"); }, onDragStart(evt) { this.$emit("drag-start", evt); diff --git a/client/src/components/History/Content/SelectedItems.js b/client/src/components/History/Content/SelectedItems.js index 8185b3cd71c3..7a0d3a24196f 100644 --- a/client/src/components/History/Content/SelectedItems.js +++ b/client/src/components/History/Content/SelectedItems.js @@ -17,7 +17,7 @@ export default { items: new Map(), showSelection: false, allSelected: false, - initSelectedKey: null, + initSelectedItem: null, initDirection: null, }; }, @@ -31,6 +31,9 @@ export default { currentFilters() { return HistoryFilters.getFiltersForText(this.filterText); }, + initSelectedKey() { + return this.initSelectedItem ? this.getItemKey(this.initSelectedItem) : null; + }, }, methods: { setShowSelection(val) { @@ -59,10 +62,10 @@ export default { this.items = newSelected; this.breakQuerySelection(); }, - shiftSelect({ item, nextItem, eventKey }) { + shiftSelect(item, nextItem, eventKey) { const currentItemKey = this.getItemKey(item); - if (!this.initDirection) { - this.initSelectedKey = currentItemKey; + if (!this.initSelectedKey) { + this.initSelectedItem = item; this.initDirection = eventKey; this.setSelected(item, true); } @@ -82,8 +85,39 @@ export default { this.setSelected(nextItem, true); } }, + selectTo(item, prevItem, allItems, reset = true) { + if (prevItem && item) { + // we are staring a new shift+click selectTo from `prevItem` + if (!this.initSelectedKey) { + this.initSelectedItem = prevItem; + } + + // `reset = false` in the case user is holding shift+ctrl key + if (reset) { + // clear this.items of any other selections + this.items = new Map(); + } + this.setSelected(this.initSelectedItem, true); + + const initItemIndex = allItems.indexOf(this.initSelectedItem); + const currentItemIndex = allItems.indexOf(item); + + let selections = []; + // from allItems, get the items between the init item and the current item + if (initItemIndex < currentItemIndex) { + this.initDirection = "ArrowDown"; + selections = allItems.slice(initItemIndex + 1, currentItemIndex + 1); + } else if (initItemIndex > currentItemIndex) { + this.initDirection = "ArrowUp"; + selections = allItems.slice(currentItemIndex, initItemIndex); + } + this.selectItems(selections); + } else { + this.setSelected(item, true); + } + }, initKeySelection() { - this.initSelectedKey = null; + this.initSelectedItem = null; this.initDirection = null; }, selectItems(items = []) { @@ -142,6 +176,7 @@ export default { setShowSelection: this.setShowSelection, selectAllInCurrentQuery: this.selectAllInCurrentQuery, selectItems: this.selectItems, + selectTo: this.selectTo, isSelected: this.isSelected, setSelected: this.setSelected, resetSelection: this.reset, diff --git a/client/src/components/History/CurrentHistory/HistoryPanel.vue b/client/src/components/History/CurrentHistory/HistoryPanel.vue index bb4f90ac80d2..6f863bdb4deb 100644 --- a/client/src/components/History/CurrentHistory/HistoryPanel.vue +++ b/client/src/components/History/CurrentHistory/HistoryPanel.vue @@ -82,6 +82,8 @@ const contentItemRefs = computed(() => { return acc; }, {}); }); +const currItemFocused = ref(null); +const lastItemFocused = ref(null); const { currentFilterText, currentHistoryId } = storeToRefs(useHistoryStore()); const { lastCheckedTime, totalMatchesCount, isWatching } = storeToRefs(useHistoryItemsStore()); @@ -149,6 +151,8 @@ watch( invisibleHistoryItems.value = {}; offsetQueryParam.value = 0; loadHistoryItems(); + currItemFocused.value = null; + lastItemFocused.value = null; } ); @@ -176,6 +180,8 @@ watch( (newValue, currentValue) => { if (newValue !== currentValue) { operationRunning.value = null; + currItemFocused.value = null; + lastItemFocused.value = null; } } ); @@ -193,6 +199,15 @@ watch(historyItems, (newHistoryItems) => { } }); +watch( + () => currItemFocused.value, + (newItem, oldItem) => { + if (newItem) { + lastItemFocused.value = oldItem; + } + } +); + function getHighlight(item: HistoryItem) { if (unref(isLoading)) { return undefined; @@ -389,15 +404,6 @@ onMounted(async () => { await loadHistoryItems(); }); -function nextSelections(item: HistoryItem, eventKey: string) { - const nextItem = arrowNavigate(item, eventKey); - return { - item, - nextItem, - eventKey, - }; -} - function arrowNavigate(item: HistoryItem, eventKey: string) { let nextItem = null; if (eventKey === "ArrowDown") { @@ -444,6 +450,7 @@ function setItemDragstart( setShowSelection, selectAllInCurrentQuery, isSelected, + selectTo, setSelected, shiftSelect, initKeySelection, @@ -574,10 +581,7 @@ function setItemDragstart( :selected="isSelected(item)" :selectable="showSelection" :filterable="filterable" - @arrow-navigate=" - arrowNavigate(item, $event); - initKeySelection(); - " + @arrow-navigate="arrowNavigate(item, $event)" @drag-start=" setItemDragstart( item, @@ -588,12 +592,17 @@ function setItemDragstart( ) " @hide-selection="setShowSelection(false)" - @shift-select="(eventKey) => shiftSelect(nextSelections(item, eventKey))" + @init-key-selection="initKeySelection" + @shift-select=" + (eventKey) => shiftSelect(item, arrowNavigate(item, eventKey), eventKey) + " @select-all="selectAllInCurrentQuery(historyItems, false)" + @selected-to="(reset) => selectTo(item, lastItemFocused, historyItems, reset)" @tag-click="updateFilterValue('tag', $event)" @tag-change="onTagChange" @toggleHighlights="updateFilterValue('related', item.hid)" @update:expand-dataset="setExpanded(item, $event)" + @update:item-focused="currItemFocused = item" @update:selected="setSelected(item, $event)" @view-collection="$emit('view-collection', item, currentOffset)" @delete="onDelete"