91 lines
4.1 KiB
TypeScript
91 lines
4.1 KiB
TypeScript
/**
|
|
* Connection multi-selection state for the sidebar tree.
|
|
*
|
|
* The transitions live here rather than in the components because the checkbox
|
|
* that grows the selection and the bulk actions that release it sit in two
|
|
* different files, and only their combination decides what the next bulk action
|
|
* operates on.
|
|
*/
|
|
export type ConnectionMultiSelection = {
|
|
connectionIds: string[];
|
|
activeConnectionId: string | null;
|
|
anchorConnectionId: string | null;
|
|
active: boolean;
|
|
};
|
|
|
|
/** The tree selection fields a multi-selection is written back to. */
|
|
export type ConnectionMultiSelectionTarget = {
|
|
selectedTreeNodeIds: string[];
|
|
selectedTreeNodeId: string | null;
|
|
treeSelectionAnchorId: string | null;
|
|
connectionMultiSelectActive: boolean;
|
|
};
|
|
|
|
export type TreeNodeSelection = {
|
|
nodeIds: string[];
|
|
activeNodeId: string | null;
|
|
anchorNodeId: string | null;
|
|
};
|
|
|
|
export function isExitConnectionMultiSelectionShortcut(event: Pick<KeyboardEvent, "key" | "metaKey" | "ctrlKey" | "altKey" | "shiftKey">): boolean {
|
|
return event.key === "Escape" && !event.metaKey && !event.ctrlKey && !event.altKey && !event.shiftKey;
|
|
}
|
|
|
|
export function emptyConnectionMultiSelection(): ConnectionMultiSelection {
|
|
return { connectionIds: [], activeConnectionId: null, anchorConnectionId: null, active: false };
|
|
}
|
|
|
|
/**
|
|
* Ticking a checkbox adds to the current selection while multi-select is on, and
|
|
* starts a new one when it is off. A bulk action that forgets to release the
|
|
* selection therefore hands its connections to the next one (issue #5758).
|
|
*/
|
|
export function connectionMultiSelectionAfterToggle(selection: Pick<ConnectionMultiSelection, "connectionIds" | "active">, connectionId: string): ConnectionMultiSelection {
|
|
const next = new Set(selection.active ? selection.connectionIds : []);
|
|
if (next.has(connectionId)) next.delete(connectionId);
|
|
else next.add(connectionId);
|
|
|
|
const connectionIds = [...next];
|
|
return {
|
|
connectionIds,
|
|
activeConnectionId: next.has(connectionId) ? connectionId : (connectionIds[0] ?? null),
|
|
anchorConnectionId: connectionId,
|
|
active: connectionIds.length > 0,
|
|
};
|
|
}
|
|
|
|
export function applyConnectionMultiSelection(target: ConnectionMultiSelectionTarget, selection: ConnectionMultiSelection): void {
|
|
target.selectedTreeNodeIds = selection.connectionIds;
|
|
target.selectedTreeNodeId = selection.activeConnectionId;
|
|
target.treeSelectionAnchorId = selection.anchorConnectionId;
|
|
target.connectionMultiSelectActive = selection.active;
|
|
}
|
|
|
|
/**
|
|
* Keep modifier-key connection and connection-group selection aligned with the
|
|
* checkbox mode. Mixed tree selections remain regular tree selections because
|
|
* type-specific bulk actions must never consume nodes of another kind.
|
|
*/
|
|
export function applyTreeNodeSelection(target: ConnectionMultiSelectionTarget, selection: TreeNodeSelection, connectionIds: ReadonlySet<string>, connectionGroupIds: ReadonlySet<string> = new Set()): void {
|
|
target.selectedTreeNodeIds = selection.nodeIds;
|
|
target.selectedTreeNodeId = selection.activeNodeId;
|
|
target.treeSelectionAnchorId = selection.anchorNodeId;
|
|
const containsOnlyConnections = selection.nodeIds.every((id) => connectionIds.has(id));
|
|
const containsOnlyConnectionGroups = selection.nodeIds.every((id) => connectionGroupIds.has(id));
|
|
target.connectionMultiSelectActive = selection.nodeIds.length > 0 && (containsOnlyConnections || containsOnlyConnectionGroups);
|
|
}
|
|
|
|
export function releaseConnectionFromMultiSelection(target: ConnectionMultiSelectionTarget, connectionId: string): void {
|
|
if (!target.connectionMultiSelectActive || !target.selectedTreeNodeIds.includes(connectionId)) return;
|
|
|
|
const connectionIds = target.selectedTreeNodeIds.filter((id) => id !== connectionId);
|
|
const activeConnectionId = target.selectedTreeNodeId && connectionIds.includes(target.selectedTreeNodeId) ? target.selectedTreeNodeId : (connectionIds[0] ?? null);
|
|
const anchorConnectionId = target.treeSelectionAnchorId && connectionIds.includes(target.treeSelectionAnchorId) ? target.treeSelectionAnchorId : activeConnectionId;
|
|
applyConnectionMultiSelection(target, {
|
|
connectionIds,
|
|
activeConnectionId,
|
|
anchorConnectionId,
|
|
active: connectionIds.length > 0,
|
|
});
|
|
}
|