mirror of
https://github.com/galaxyproject/galaxy.git
synced 2026-10-28 02:58:18 +08:00
add a first and last item tracker to SelectedItems, remove Shift+Ctrl
Modified `SelectedItems` so that for range select, we track the first and last item in a range, along with the anchor. This way, we can persist with a selected range even if the user deselects a few items within it, since we keep track of the beginning and end of the range. Also, added a CSS indicator to `ContentItem`s which actually indicates which item if the Shift+click range select anchor. This also removes the `Shift+Ctrl+Click` multiple range select.
This commit is contained in:
@@ -35,6 +35,7 @@ interface Props {
|
||||
addHighlightBtn?: boolean;
|
||||
highlight?: string;
|
||||
isDataset?: boolean;
|
||||
isRangeSelectAnchor?: boolean;
|
||||
isHistoryItem?: boolean;
|
||||
selected?: boolean;
|
||||
selectable?: boolean;
|
||||
@@ -48,6 +49,7 @@ const props = withDefaults(defineProps<Props>(), {
|
||||
addHighlightBtn: false,
|
||||
highlight: undefined,
|
||||
isDataset: true,
|
||||
isRangeSelectAnchor: false,
|
||||
isHistoryItem: false,
|
||||
selected: false,
|
||||
selectable: false,
|
||||
@@ -58,12 +60,12 @@ const props = withDefaults(defineProps<Props>(), {
|
||||
const emit = defineEmits<{
|
||||
(e: "update:selected", selected: boolean): void;
|
||||
(e: "update:expand-dataset", expand: boolean): void;
|
||||
(e: "shift-select", direction: string): void;
|
||||
(e: "shift-arrow-select", direction: string): void;
|
||||
(e: "init-key-selection"): void;
|
||||
(e: "arrow-navigate", direction: string): void;
|
||||
(e: "hide-selection"): void;
|
||||
(e: "select-all"): void;
|
||||
(e: "selected-to", reset: boolean): void;
|
||||
(e: "selected-to"): void;
|
||||
(e: "delete", item: any, recursive: boolean): void;
|
||||
(e: "undelete"): void;
|
||||
(e: "unhide"): void;
|
||||
@@ -173,6 +175,10 @@ const isBeingUsed = computed(() => {
|
||||
return Object.values(itemUrls.value).includes(route.path) ? "being-used" : "";
|
||||
});
|
||||
|
||||
const rangeSelectClass = computed(() => {
|
||||
return props.isRangeSelectAnchor ? "range-select-anchor" : "";
|
||||
});
|
||||
|
||||
/** Based on the user's keyboard platform, checks if it is the
|
||||
* typical key for selection (ctrl for windows/linux, cmd for mac)
|
||||
*/
|
||||
@@ -199,7 +205,7 @@ function onKeyDown(event: KeyboardEvent) {
|
||||
} else {
|
||||
event.preventDefault();
|
||||
if ((event.key === "ArrowUp" || event.key === "ArrowDown") && event.shiftKey) {
|
||||
emit("shift-select", event.key);
|
||||
emit("shift-arrow-select", event.key);
|
||||
} else if (event.key === "ArrowUp" || event.key === "ArrowDown") {
|
||||
emit("init-key-selection");
|
||||
} else if (event.key === "Delete" && !props.selected && !props.item.deleted) {
|
||||
@@ -217,15 +223,12 @@ function onKeyDown(event: KeyboardEvent) {
|
||||
function onClick(e?: Event) {
|
||||
const event = e as KeyboardEvent;
|
||||
if (event && props.writable) {
|
||||
if (event.shiftKey && isSelectKey(event)) {
|
||||
emit("selected-to", false);
|
||||
return;
|
||||
} else if (isSelectKey(event)) {
|
||||
if (isSelectKey(event)) {
|
||||
emit("init-key-selection");
|
||||
emit("update:selected", !props.selected);
|
||||
return;
|
||||
} else if (event.shiftKey) {
|
||||
emit("selected-to", true);
|
||||
emit("selected-to");
|
||||
return;
|
||||
} else {
|
||||
emit("init-key-selection");
|
||||
@@ -326,7 +329,7 @@ function unexpandedClick(event: Event) {
|
||||
<div
|
||||
:id="contentId"
|
||||
ref="contentItem"
|
||||
:class="['content-item m-1 p-0 rounded btn-transparent-background', contentCls, isBeingUsed]"
|
||||
:class="['content-item m-1 p-0 rounded btn-transparent-background', contentCls, isBeingUsed, rangeSelectClass]"
|
||||
:data-hid="id"
|
||||
:data-state="dataState"
|
||||
tabindex="0"
|
||||
@@ -450,6 +453,10 @@ function unexpandedClick(event: Event) {
|
||||
box-shadow: 0 0 0 0.2rem transparentize($brand-primary, 0.75);
|
||||
}
|
||||
|
||||
&.range-select-anchor {
|
||||
box-shadow: 0 0 0 0.2rem transparentize($brand-primary, 0.75);
|
||||
}
|
||||
|
||||
&.being-used {
|
||||
border-left: 0.25rem solid $brand-primary;
|
||||
margin-left: 0rem !important;
|
||||
|
||||
@@ -11,6 +11,7 @@ export default {
|
||||
getItemKey: { type: Function, required: true },
|
||||
filterText: { type: String, required: true },
|
||||
totalItemsInQuery: { type: Number, required: false },
|
||||
allItems: { type: Array, required: true },
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@@ -19,6 +20,8 @@ export default {
|
||||
allSelected: false,
|
||||
initSelectedItem: null,
|
||||
initDirection: null,
|
||||
firstInRange: null,
|
||||
lastInRange: null,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@@ -34,18 +37,27 @@ export default {
|
||||
initSelectedKey() {
|
||||
return this.initSelectedItem ? this.getItemKey(this.initSelectedItem) : null;
|
||||
},
|
||||
lastInRangeIndex() {
|
||||
return this.lastInRange ? this.allItems.indexOf(this.lastInRange) : null;
|
||||
},
|
||||
firstInRangeIndex() {
|
||||
return this.firstInRange ? this.allItems.indexOf(this.firstInRange) : null;
|
||||
},
|
||||
rangeSelectActive() {
|
||||
return this.lastInRange && this.initDirection;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
setShowSelection(val) {
|
||||
this.showSelection = val;
|
||||
},
|
||||
selectAllInCurrentQuery(loadedItems = [], force = true) {
|
||||
selectAllInCurrentQuery(force = true) {
|
||||
// if we are not forcing selectAll, and all items are already selected; deselect them
|
||||
if (!force && this.allSelected) {
|
||||
this.setShowSelection(false);
|
||||
return;
|
||||
}
|
||||
this.selectItems(loadedItems);
|
||||
this.selectItems(this.allItems);
|
||||
this.allSelected = true;
|
||||
},
|
||||
isSelected(item) {
|
||||
@@ -55,19 +67,49 @@ export default {
|
||||
const key = this.getItemKey(item);
|
||||
return this.items.has(key);
|
||||
},
|
||||
setSelected(item, selected) {
|
||||
/** Adds/Removes an item from the selected items
|
||||
*
|
||||
* @param {Object} item - the item to be selected/deselected
|
||||
* @param {Boolean} selected - whether to select or deselect the item
|
||||
* @param {Boolean} checkInRange - whether to check if the item lies outside the range
|
||||
*/
|
||||
setSelected(item, selected, checkInRange = true) {
|
||||
const key = this.getItemKey(item);
|
||||
const newSelected = new Map(this.items);
|
||||
selected ? newSelected.set(key, item) : newSelected.delete(key);
|
||||
this.items = newSelected;
|
||||
this.breakQuerySelection();
|
||||
|
||||
// item selected from panel, range select is active, check if item lies outside the range
|
||||
if (checkInRange && this.rangeSelectActive) {
|
||||
const currentItemIndex = this.allItems.indexOf(item);
|
||||
|
||||
/** If either of the following are `true`, change the range anchor */
|
||||
// if range select was downwards and the current item is below the lastInRange or above the firstInRange
|
||||
if (
|
||||
this.initDirection === "ArrowDown" &&
|
||||
(currentItemIndex > this.lastInRangeIndex || currentItemIndex < this.firstInRangeIndex)
|
||||
) {
|
||||
this.firstInRange = item;
|
||||
this.lastInRange = item;
|
||||
}
|
||||
// if range select was upwards and the current item is above the lastInRange or below the firstInRange
|
||||
else if (
|
||||
this.initDirection === "ArrowUp" &&
|
||||
(currentItemIndex < this.lastInRangeIndex || currentItemIndex > this.firstInRangeIndex)
|
||||
) {
|
||||
this.firstInRange = item;
|
||||
this.lastInRange = item;
|
||||
}
|
||||
}
|
||||
},
|
||||
shiftSelect(item, nextItem, eventKey) {
|
||||
/** Selecting items using `Shift+ArrowUp/ArrowDown` keys */
|
||||
shiftArrowKeySelect(item, nextItem, eventKey) {
|
||||
const currentItemKey = this.getItemKey(item);
|
||||
if (!this.initSelectedKey) {
|
||||
this.initSelectedItem = item;
|
||||
this.initDirection = eventKey;
|
||||
this.setSelected(item, true);
|
||||
this.setSelected(item, true, false);
|
||||
}
|
||||
// got back to the initial selected item
|
||||
else if (this.initSelectedKey === currentItemKey) {
|
||||
@@ -75,50 +117,101 @@ export default {
|
||||
}
|
||||
// same direction
|
||||
else if (this.initDirection === eventKey) {
|
||||
this.setSelected(item, true);
|
||||
this.setSelected(item, true, false);
|
||||
}
|
||||
// different direction
|
||||
else {
|
||||
this.setSelected(item, false);
|
||||
this.setSelected(item, false, false);
|
||||
}
|
||||
if (nextItem) {
|
||||
this.setSelected(nextItem, true);
|
||||
this.setSelected(nextItem, true, false);
|
||||
}
|
||||
},
|
||||
selectTo(item, prevItem, allItems, reset = true) {
|
||||
/** Range selecting items using `Shift+Click` */
|
||||
rangeSelect(item, prevItem) {
|
||||
if (prevItem && item) {
|
||||
// we are staring a new shift+click selectTo from `prevItem`
|
||||
if (!this.initSelectedKey) {
|
||||
this.initSelectedItem = prevItem;
|
||||
// either a range select is not active or we have modified a range select (changing the anchor)
|
||||
const noRangeSelectOrModified = !this.initSelectedKey;
|
||||
if (noRangeSelectOrModified) {
|
||||
// there is a range select active
|
||||
if (this.rangeSelectActive) {
|
||||
const currentItemIndex = this.allItems.indexOf(item);
|
||||
|
||||
// the current item is outside the range in the same direction;
|
||||
// the new range will follow the last item in prev range
|
||||
if (
|
||||
(this.initDirection === "ArrowDown" && currentItemIndex > this.lastInRangeIndex) ||
|
||||
(this.initDirection === "ArrowUp" && currentItemIndex < this.lastInRangeIndex)
|
||||
) {
|
||||
this.initSelectedItem = this.lastInRange;
|
||||
}
|
||||
// the current item is outside the range in the opposite direction;
|
||||
// the new range will follow the first item in prev range
|
||||
else if (
|
||||
(this.initDirection === "ArrowDown" && currentItemIndex < this.firstInRangeIndex) ||
|
||||
(this.initDirection === "ArrowUp" && currentItemIndex > this.firstInRangeIndex)
|
||||
) {
|
||||
this.initSelectedItem = this.firstInRange;
|
||||
} else {
|
||||
this.initSelectedItem = prevItem;
|
||||
this.firstInRange = prevItem;
|
||||
}
|
||||
}
|
||||
// there is no range select active
|
||||
else {
|
||||
// we are staring a new shift+click rangeSelect from `prevItem`
|
||||
this.initSelectedItem = prevItem;
|
||||
this.firstInRange = 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);
|
||||
const initItemIndex = this.allItems.indexOf(this.initSelectedItem);
|
||||
const currentItemIndex = this.allItems.indexOf(item);
|
||||
|
||||
const lastDirection = this.initDirection;
|
||||
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);
|
||||
selections = this.allItems.slice(initItemIndex + 1, currentItemIndex + 1);
|
||||
} else if (initItemIndex > currentItemIndex) {
|
||||
this.initDirection = "ArrowUp";
|
||||
selections = allItems.slice(currentItemIndex, initItemIndex);
|
||||
selections = this.allItems.slice(currentItemIndex, initItemIndex);
|
||||
}
|
||||
|
||||
let deselections;
|
||||
// there is an existing range select; deselect items in certain conditions
|
||||
if (this.rangeSelectActive) {
|
||||
// if there is an active, uninterrupted range-select and the direction changed;
|
||||
// deselect the items between the lastInRange and initSelectedItem
|
||||
if (!noRangeSelectOrModified && lastDirection && lastDirection !== this.initDirection) {
|
||||
if (this.lastInRangeIndex >= initItemIndex) {
|
||||
deselections = this.allItems.slice(initItemIndex + 1, this.lastInRangeIndex + 1);
|
||||
} else {
|
||||
deselections = this.allItems.slice(this.lastInRangeIndex, initItemIndex);
|
||||
}
|
||||
}
|
||||
|
||||
// if the range has become smaller, deselect items between the lastInRange and the current item
|
||||
else if (this.lastInRangeIndex < currentItemIndex && this.initDirection === "ArrowUp") {
|
||||
deselections = this.allItems.slice(this.lastInRangeIndex, currentItemIndex);
|
||||
} else if (this.lastInRangeIndex > currentItemIndex && this.initDirection === "ArrowDown") {
|
||||
deselections = this.allItems.slice(currentItemIndex + 1, this.lastInRangeIndex + 1);
|
||||
}
|
||||
}
|
||||
|
||||
this.selectItems(selections);
|
||||
if (deselections) {
|
||||
deselections.forEach((deselected) => this.setSelected(deselected, false, false));
|
||||
}
|
||||
} else {
|
||||
this.setSelected(item, true);
|
||||
}
|
||||
this.lastInRange = item;
|
||||
},
|
||||
/** Resets the initial item in a range select (or shiftArrowKeySelect) */
|
||||
initKeySelection() {
|
||||
this.initSelectedItem = null;
|
||||
this.initDirection = null;
|
||||
},
|
||||
selectItems(items = []) {
|
||||
const newItems = [...this.items.values(), ...items];
|
||||
@@ -139,6 +232,9 @@ export default {
|
||||
this.items = new Map();
|
||||
this.allSelected = false;
|
||||
this.initKeySelection();
|
||||
this.lastInRange = null;
|
||||
this.firstInRange = null;
|
||||
this.initDirection = null;
|
||||
},
|
||||
cancelSelection() {
|
||||
this.showSelection = false;
|
||||
@@ -176,12 +272,13 @@ export default {
|
||||
setShowSelection: this.setShowSelection,
|
||||
selectAllInCurrentQuery: this.selectAllInCurrentQuery,
|
||||
selectItems: this.selectItems,
|
||||
selectTo: this.selectTo,
|
||||
rangeSelect: this.rangeSelect,
|
||||
isSelected: this.isSelected,
|
||||
setSelected: this.setSelected,
|
||||
resetSelection: this.reset,
|
||||
shiftSelect: this.shiftSelect,
|
||||
shiftArrowKeySelect: this.shiftArrowKeySelect,
|
||||
initKeySelection: this.initKeySelection,
|
||||
initSelectedItem: this.initSelectedItem,
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
@@ -470,15 +470,17 @@ function setItemDragstart(
|
||||
setShowSelection,
|
||||
selectAllInCurrentQuery,
|
||||
isSelected,
|
||||
selectTo,
|
||||
rangeSelect,
|
||||
setSelected,
|
||||
shiftSelect,
|
||||
shiftArrowKeySelect,
|
||||
initKeySelection,
|
||||
resetSelection,
|
||||
initSelectedItem,
|
||||
}"
|
||||
:scope-key="queryKey"
|
||||
:get-item-key="getItemKey"
|
||||
:filter-text="filterText"
|
||||
:all-items="historyItems"
|
||||
:total-items-in-query="totalMatchesCount"
|
||||
@query-selection-break="querySelectionBreak = true">
|
||||
<!-- eslint-disable-next-line vuejs-accessibility/no-static-element-interactions -->
|
||||
@@ -545,7 +547,7 @@ function setItemDragstart(
|
||||
<HistorySelectionStatus
|
||||
v-if="showSelection"
|
||||
:selection-size="selectionSize"
|
||||
@select-all="selectAllInCurrentQuery(historyItems)"
|
||||
@select-all="selectAllInCurrentQuery()"
|
||||
@reset-selection="resetSelection" />
|
||||
</template>
|
||||
</HistoryOperations>
|
||||
@@ -597,6 +599,9 @@ function setItemDragstart(
|
||||
:writable="canEditHistory"
|
||||
:expand-dataset="isExpanded(item)"
|
||||
:is-dataset="isDataset(item)"
|
||||
:is-range-select-anchor="
|
||||
initSelectedItem && itemUniqueKey(item) === itemUniqueKey(initSelectedItem)
|
||||
"
|
||||
:highlight="getHighlight(item)"
|
||||
:selected="isSelected(item)"
|
||||
:selectable="showSelection"
|
||||
@@ -613,11 +618,11 @@ function setItemDragstart(
|
||||
"
|
||||
@hide-selection="setShowSelection(false)"
|
||||
@init-key-selection="initKeySelection"
|
||||
@shift-select="
|
||||
(eventKey) => shiftSelect(item, arrowNavigate(item, eventKey), eventKey)
|
||||
@shift-arrow-select="
|
||||
(eventKey) => shiftArrowKeySelect(item, arrowNavigate(item, eventKey), eventKey)
|
||||
"
|
||||
@select-all="selectAllInCurrentQuery(historyItems, false)"
|
||||
@selected-to="(reset) => selectTo(item, lastItemFocused, historyItems, reset)"
|
||||
@select-all="selectAllInCurrentQuery(false)"
|
||||
@selected-to="rangeSelect(item, lastItemFocused)"
|
||||
@tag-click="updateFilterValue('tag', $event)"
|
||||
@tag-change="onTagChange"
|
||||
@toggleHighlights="updateFilterValue('related', item.hid)"
|
||||
|
||||
Reference in New Issue
Block a user