fix(objects): support modifier-click table selection
This commit is contained in:
1 parent
f9f21cbd88
commit
10b1ba4578
3 files changed
+99
No files matched your search
@@ -120,6 +120,7 @@ import {
|
||||
type ObjectBrowserSortKey,
|
||||
} from "@/lib/table/objectBrowserRows";
|
||||
import { isSourceOnlyObjectBrowserRow, resolveRowClickAction, shouldDeferSingleClick, type ObjectBrowserRowAction } from "@/lib/table/objectBrowserRowAction";
|
||||
import { objectBrowserTableSelectionAnchor, objectBrowserTableSelectionRange } from "@/lib/table/objectBrowserSelection";
|
||||
import { customTypeCapabilities, supportsTypeObjectSource } from "@/lib/database/databaseObjectCapabilities";
|
||||
import { filterObjectBrowserTableColumns } from "@/lib/table/objectBrowserTableInfo";
|
||||
import { createSidePanelRequestGuard } from "@/lib/table/sidePanelRequestGuard";
|
||||
@@ -247,6 +248,7 @@ const duplicateTableName = ref("");
|
||||
const showProcedureExecutionConfirm = ref(false);
|
||||
const procedureExecutionTarget = ref<ObjectBrowserRow | null>(null);
|
||||
const selectedTableIds = ref<Set<string>>(new Set());
|
||||
const tableSelectionAnchorId = ref<string | null>(null);
|
||||
const expandedPartitionParentIds = ref<Set<string>>(new Set());
|
||||
const showBatchDropConfirm = ref(false);
|
||||
const batchDropExecuting = ref(false);
|
||||
@@ -852,7 +854,27 @@ function executeRowAction(row: ObjectBrowserRow, action: ObjectBrowserRowAction)
|
||||
}
|
||||
}
|
||||
|
||||
function toggleTableSelectionWithAnchor(row: ObjectBrowserRow) {
|
||||
toggleTableSelection(row);
|
||||
tableSelectionAnchorId.value = row.id;
|
||||
}
|
||||
|
||||
function selectTableRangeFromAnchor(row: ObjectBrowserRow) {
|
||||
const anchorId = objectBrowserTableSelectionAnchor(filteredRows.value, tableSelectionAnchorId.value, row.id);
|
||||
tableSelectionAnchorId.value = anchorId;
|
||||
setSelectedTableIds(new Set(objectBrowserTableSelectionRange(filteredRows.value, anchorId, row.id)));
|
||||
}
|
||||
|
||||
function onRowClick(row: ObjectBrowserRow, event: MouseEvent) {
|
||||
if (row.type === "TABLE" && event.shiftKey) {
|
||||
selectTableRangeFromAnchor(row);
|
||||
return;
|
||||
}
|
||||
if (row.type === "TABLE" && (event.metaKey || event.ctrlKey)) {
|
||||
toggleTableSelectionWithAnchor(row);
|
||||
return;
|
||||
}
|
||||
if (row.type === "TABLE") tableSelectionAnchorId.value = row.id;
|
||||
const activation = settingsStore.editorSettings.sidebarActivation;
|
||||
const { action, isDouble } = resolveRowClickAction(row, event.detail, activation, effectiveDatabaseType.value);
|
||||
// Double click: cancel any pending single-click and fire immediately
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { objectBrowserTableSelectionAnchor, objectBrowserTableSelectionRange } from "@/lib/table/objectBrowserSelection";
|
||||
import type { ObjectBrowserRow } from "@/lib/table/objectBrowserRows";
|
||||
|
||||
function row(type: ObjectBrowserRow["type"], name: string): ObjectBrowserRow {
|
||||
return { id: `${type}-${name}`, name, displayName: name, type };
|
||||
}
|
||||
|
||||
describe("objectBrowserTableSelectionRange", () => {
|
||||
it("selects every table between anchor and current row, inclusive", () => {
|
||||
const rows = [row("TABLE", "a"), row("TABLE", "b"), row("TABLE", "c"), row("TABLE", "d")];
|
||||
expect(objectBrowserTableSelectionRange(rows, "TABLE-a", "TABLE-c")).toEqual(["TABLE-a", "TABLE-b", "TABLE-c"]);
|
||||
});
|
||||
|
||||
it("supports selecting upward when the current row precedes the anchor", () => {
|
||||
const rows = [row("TABLE", "a"), row("TABLE", "b"), row("TABLE", "c"), row("TABLE", "d")];
|
||||
expect(objectBrowserTableSelectionRange(rows, "TABLE-d", "TABLE-b")).toEqual(["TABLE-b", "TABLE-c", "TABLE-d"]);
|
||||
});
|
||||
|
||||
it("drops non-table rows inside the span, since only tables are selectable", () => {
|
||||
const rows = [row("TABLE", "a"), row("VIEW", "v1"), row("PROCEDURE", "p1"), row("TABLE", "b")];
|
||||
expect(objectBrowserTableSelectionRange(rows, "TABLE-a", "TABLE-b")).toEqual(["TABLE-a", "TABLE-b"]);
|
||||
});
|
||||
|
||||
it("returns just the current row when the anchor is not selectable and current is a table", () => {
|
||||
const rows = [row("TABLE", "a"), row("TABLE", "b")];
|
||||
expect(objectBrowserTableSelectionRange(rows, "missing-anchor", "TABLE-b")).toEqual(["TABLE-b"]);
|
||||
});
|
||||
|
||||
it("returns an empty range when the anchor is missing and current row is not a table", () => {
|
||||
const rows = [row("VIEW", "v1"), row("TABLE", "b")];
|
||||
expect(objectBrowserTableSelectionRange(rows, "missing-anchor", "VIEW-v1")).toEqual([]);
|
||||
});
|
||||
|
||||
it("collapses to a single row when anchor and current are the same table", () => {
|
||||
const rows = [row("TABLE", "a"), row("TABLE", "b")];
|
||||
expect(objectBrowserTableSelectionRange(rows, "TABLE-a", "TABLE-a")).toEqual(["TABLE-a"]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("objectBrowserTableSelectionAnchor", () => {
|
||||
it("keeps a visible table anchor", () => {
|
||||
const rows = [row("TABLE", "a"), row("TABLE", "b")];
|
||||
expect(objectBrowserTableSelectionAnchor(rows, "TABLE-a", "TABLE-b")).toBe("TABLE-a");
|
||||
});
|
||||
|
||||
it("uses the current table when the anchor is missing or no longer visible", () => {
|
||||
const rows = [row("TABLE", "a"), row("TABLE", "b")];
|
||||
expect(objectBrowserTableSelectionAnchor(rows, null, "TABLE-b")).toBe("TABLE-b");
|
||||
expect(objectBrowserTableSelectionAnchor(rows, "TABLE-filtered-out", "TABLE-b")).toBe("TABLE-b");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,25 @@
|
||||
import type { ObjectBrowserRow } from "@/lib/table/objectBrowserRows";
|
||||
|
||||
export function objectBrowserTableSelectionAnchor(rows: readonly ObjectBrowserRow[], anchorId: string | null, currentId: string): string {
|
||||
if (anchorId && rows.some((row) => row.id === anchorId && row.type === "TABLE")) return anchorId;
|
||||
return currentId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the contiguous table-id range for a shift-click in the object
|
||||
* browser, mirroring the sidebar tree's range-select behavior
|
||||
* (sidebarTreeSelection.ts): the range spans every row between the anchor
|
||||
* and the clicked row in visible order, but only TABLE rows are selectable
|
||||
* so non-table rows inside the span are dropped from the result.
|
||||
*/
|
||||
export function objectBrowserTableSelectionRange(rows: readonly ObjectBrowserRow[], anchorId: string, currentId: string): string[] {
|
||||
const anchorIndex = rows.findIndex((row) => row.id === anchorId);
|
||||
const currentIndex = rows.findIndex((row) => row.id === currentId);
|
||||
if (anchorIndex < 0 || currentIndex < 0) return rows.some((row) => row.id === currentId && row.type === "TABLE") ? [currentId] : [];
|
||||
const start = Math.min(anchorIndex, currentIndex);
|
||||
const end = Math.max(anchorIndex, currentIndex);
|
||||
return rows
|
||||
.slice(start, end + 1)
|
||||
.filter((row) => row.type === "TABLE")
|
||||
.map((row) => row.id);
|
||||
}
|
||||
Reference in new issue
Block a user