diff --git a/frontend/src/helpers/selectionRange.ts b/frontend/src/helpers/selectionRange.ts index 373e3a388..4d4e885c4 100644 --- a/frontend/src/helpers/selectionRange.ts +++ b/frontend/src/helpers/selectionRange.ts @@ -24,6 +24,14 @@ export function mergeSelectedIds(selected: string[], idsToAdd: string[]) { return [...new Set([...selected, ...idsToAdd])] } +// Devices are selected in click order, so re-order on each change to follow the list the user +// is looking at — which respects whatever sort they've chosen. Ids no longer in the list (a +// selection outliving a filter change) keep their relative order at the end. +export function sortSelectedIds(selected: string[], devices: IDevice[]) { + const order = new Map(devices.map((device, index) => [device.id, index])) + return [...selected].sort((a, b) => (order.get(a) ?? Infinity) - (order.get(b) ?? Infinity)) +} + export function removeSelectedIds(selected: string[], idsToRemove: string[]) { const remove = new Set(idsToRemove) return selected.filter(id => !remove.has(id)) diff --git a/frontend/src/hooks/useSelect.ts b/frontend/src/hooks/useSelect.ts index 0a396585e..91288c6f2 100644 --- a/frontend/src/hooks/useSelect.ts +++ b/frontend/src/hooks/useSelect.ts @@ -1,7 +1,13 @@ import { useDispatch, useSelector, useStore } from 'react-redux' import { Dispatch, State } from '../store' import { selectVisibleDevices } from '../selectors/devices' -import { getInclusiveIdRange, getSelectableDeviceIds, mergeSelectedIds, removeSelectedIds } from '../helpers/selectionRange' +import { + getInclusiveIdRange, + getSelectableDeviceIds, + mergeSelectedIds, + removeSelectedIds, + sortSelectedIds, +} from '../helpers/selectionRange' type UseSelectParams = { deviceId: string @@ -17,28 +23,13 @@ export const useSelect = ({ deviceId, selectMode }: UseSelectParams) => { const handleSelect = (shiftKey?: boolean) => { const state = store.getState() const selected = state.ui.selected - const selectionAnchor = state.ui.selectionAnchor const visibleDevices = selectVisibleDevices(state) - const nextSelected = [...selected] const selectableIds = getSelectableDeviceIds(visibleDevices) - const range = shiftKey ? getInclusiveIdRange(selectableIds, selectionAnchor, deviceId) : [] + const range = shiftKey ? getInclusiveIdRange(selectableIds, state.ui.selectionAnchor, deviceId) : [] + const ids = range.length ? range : [deviceId] + const nextSelected = isSelected ? removeSelectedIds(selected, ids) : mergeSelectedIds(selected, ids) - if (range.length) { - const rangeSelected = isSelected ? removeSelectedIds(nextSelected, range) : mergeSelectedIds(nextSelected, range) - dispatch.ui.set({ selected: rangeSelected }) - dispatch.ui.set({ selectionAnchor: deviceId }) - return - } - - if (isSelected) { - const index = nextSelected.indexOf(deviceId) - nextSelected.splice(index, 1) - } else { - nextSelected.push(deviceId) - } - - dispatch.ui.set({ selected: nextSelected }) - dispatch.ui.set({ selectionAnchor: deviceId }) + dispatch.ui.set({ selected: sortSelectedIds(nextSelected, visibleDevices), selectionAnchor: deviceId }) } return { isSelected, isAnchorRow, handleSelect }