feat(desktop): add DDL shortcut and grid row delete action
This commit is contained in:
19 files changed
+157
-4
No files matched your search
@@ -1,6 +1,7 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import {
|
||||
DATA_GRID_CONDITION_TOOLBAR_MIN_WIDTH,
|
||||
dataGridDeleteRowToolbarState,
|
||||
dataGridToolbarCompactBreakpoint,
|
||||
dataGridToolbarIntervalOptions,
|
||||
isDataGridToolbarCompact,
|
||||
@@ -32,6 +33,17 @@ function autoRefreshCapability(overrides: Partial<DataGridToolbarAutoRefreshCapa
|
||||
}
|
||||
|
||||
describe("data grid toolbar capabilities", () => {
|
||||
it("shows delete only for editable grids with a supported deletion path", () => {
|
||||
expect(dataGridDeleteRowToolbarState({ editable: false, canDeleteRows: true, canDeleteExistingRows: true, deletableTargetCount: 1, isSaving: false })).toEqual({ visible: false, disabled: false });
|
||||
expect(dataGridDeleteRowToolbarState({ editable: true, canDeleteRows: true, canDeleteExistingRows: true, deletableTargetCount: 0, isSaving: false })).toEqual({ visible: true, disabled: true });
|
||||
expect(dataGridDeleteRowToolbarState({ editable: true, canDeleteRows: true, canDeleteExistingRows: false, deletableTargetCount: 0, isSaving: false })).toEqual({ visible: false, disabled: true });
|
||||
});
|
||||
|
||||
it("keeps unsaved deletable rows available when existing-row deletion is unsupported", () => {
|
||||
expect(dataGridDeleteRowToolbarState({ editable: true, canDeleteRows: true, canDeleteExistingRows: false, deletableTargetCount: 1, isSaving: false })).toEqual({ visible: true, disabled: false });
|
||||
expect(dataGridDeleteRowToolbarState({ editable: true, canDeleteRows: true, canDeleteExistingRows: true, deletableTargetCount: 2, isSaving: true })).toEqual({ visible: true, disabled: true });
|
||||
});
|
||||
|
||||
it("uses a viewport-relative compact breakpoint within stable bounds", () => {
|
||||
expect(dataGridToolbarCompactBreakpoint(1920)).toBe(1050);
|
||||
expect(dataGridToolbarCompactBreakpoint(1280)).toBe(960);
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { readFileSync } from "node:fs";
|
||||
|
||||
const dataGridToolbarSource = readFileSync(new URL("../../../components/grid/DataGridToolbar.vue", import.meta.url), "utf8");
|
||||
const dataGridSource = readFileSync(new URL("../../../components/grid/DataGrid.vue", import.meta.url), "utf8");
|
||||
|
||||
describe("data grid toolbar delete row", () => {
|
||||
it("declares and renders the delete-row action button", () => {
|
||||
expect(dataGridToolbarSource).toMatch(/deleteRow\?: DataGridToolbarActionCapability/);
|
||||
expect(dataGridToolbarSource).toMatch(/isDataGridToolbarCapabilityVisible\(deleteRow\)/);
|
||||
expect(dataGridToolbarSource).toMatch(/triggerDataGridToolbarAction\(deleteRow\)/);
|
||||
});
|
||||
|
||||
it("wires the delete-row capability into the toolbar", () => {
|
||||
expect(dataGridSource).toMatch(/:delete-row="deleteRowToolbarCapability"/);
|
||||
});
|
||||
|
||||
it("keeps the delete-row capability tied to row deletion state", () => {
|
||||
expect(dataGridSource).toMatch(/deleteRowToolbarTargetCount = computed\(\(\) => deletableRowIds\(selectedOrCurrentRowIds\(\)\)\.length\)/);
|
||||
expect(dataGridSource).toMatch(/visible: deleteRowToolbarState\.value\.visible/);
|
||||
expect(dataGridSource).toMatch(/disabled: deleteRowToolbarState\.value\.disabled/);
|
||||
expect(dataGridSource).toMatch(/onTrigger: \(\) => \{\s*deleteCurrentRow\(\);/);
|
||||
});
|
||||
});
|
||||
@@ -21,7 +21,7 @@ describe("shortcutRegistry editor actions", () => {
|
||||
"exPasteSqlInCondition",
|
||||
"toggleFold",
|
||||
];
|
||||
const sidebarShortcutActionIds: ShortcutActionId[] = ["copySidebarSelection", "pasteSidebarSelection", "editSidebarConnection"];
|
||||
const sidebarShortcutActionIds: ShortcutActionId[] = ["copySidebarSelection", "pasteSidebarSelection", "editSidebarConnection", "viewTableDdl"];
|
||||
|
||||
it("registers the new-data-tab mouse modifier as a configurable sidebar shortcut", () => {
|
||||
const definition = SHORTCUT_DEFINITIONS.find((item) => item.id === "openDataInNewTab");
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import type { TreeNode } from "@/types/database";
|
||||
import { sidebarNodeSupportsDdlView } from "@/lib/sidebar/sidebarTreeDdlShortcut";
|
||||
|
||||
function node(type: TreeNode["type"]): TreeNode {
|
||||
return { id: "n", label: "n", type, connectionId: "c", database: "db" };
|
||||
}
|
||||
|
||||
describe("sidebar tree DDL shortcut", () => {
|
||||
it("supports table, view and materialized view nodes", () => {
|
||||
expect(sidebarNodeSupportsDdlView(node("table"))).toBe(true);
|
||||
expect(sidebarNodeSupportsDdlView(node("view"))).toBe(true);
|
||||
expect(sidebarNodeSupportsDdlView(node("materialized_view"))).toBe(true);
|
||||
});
|
||||
|
||||
it("rejects nodes without a DDL view", () => {
|
||||
expect(sidebarNodeSupportsDdlView(node("connection"))).toBe(false);
|
||||
expect(sidebarNodeSupportsDdlView(node("database"))).toBe(false);
|
||||
expect(sidebarNodeSupportsDdlView(node("schema"))).toBe(false);
|
||||
expect(sidebarNodeSupportsDdlView(node("column"))).toBe(false);
|
||||
expect(sidebarNodeSupportsDdlView(node("index"))).toBe(false);
|
||||
expect(sidebarNodeSupportsDdlView(node("procedure"))).toBe(false);
|
||||
expect(sidebarNodeSupportsDdlView(node("group"))).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -25,6 +25,14 @@ export interface DataGridToolbarActionCapability {
|
||||
onTrigger: () => void | Promise<void>;
|
||||
}
|
||||
|
||||
export function dataGridDeleteRowToolbarState(options: { editable: boolean; canDeleteRows: boolean; canDeleteExistingRows: boolean; deletableTargetCount: number; isSaving: boolean }): { visible: boolean; disabled: boolean } {
|
||||
const deletionAvailable = options.editable && options.canDeleteRows;
|
||||
return {
|
||||
visible: deletionAvailable && (options.canDeleteExistingRows || options.deletableTargetCount > 0),
|
||||
disabled: options.isSaving || options.deletableTargetCount === 0,
|
||||
};
|
||||
}
|
||||
|
||||
export interface DataGridToolbarSaveCapability extends DataGridToolbarActionCapability {
|
||||
pendingCount: number;
|
||||
shortcutLabel?: string;
|
||||
|
||||
@@ -223,6 +223,10 @@ export function isEditSidebarConnectionShortcut(event: ShortcutLikeEvent, shortc
|
||||
return matchesShortcut(event, actionShortcut("editSidebarConnection", shortcuts));
|
||||
}
|
||||
|
||||
export function isViewTableDdlShortcut(event: ShortcutLikeEvent, shortcuts?: Partial<ShortcutSettings>): boolean {
|
||||
return matchesShortcut(event, actionShortcut("viewTableDdl", shortcuts));
|
||||
}
|
||||
|
||||
export function isQuickOpenShortcut(event: ShortcutLikeEvent, shortcuts?: Partial<ShortcutSettings>): boolean {
|
||||
return matchesShortcut(event, actionShortcut("quickOpen", shortcuts));
|
||||
}
|
||||
|
||||
@@ -55,6 +55,7 @@ export type ShortcutActionId =
|
||||
| "pasteSidebarSelection"
|
||||
| "editSidebarConnection"
|
||||
| "openDataInNewTab"
|
||||
| "viewTableDdl"
|
||||
| "sendSelectionToAi";
|
||||
|
||||
export type ShortcutScope = "global" | "editor" | "grid" | "search" | "sidebar";
|
||||
@@ -409,6 +410,12 @@ export const SHORTCUT_DEFINITIONS: ShortcutDefinition[] = [
|
||||
defaultShortcut: "Alt",
|
||||
inputKind: "modifier-only",
|
||||
},
|
||||
{
|
||||
id: "viewTableDdl",
|
||||
labelKey: "settings.shortcutViewTableDdl",
|
||||
scope: "sidebar",
|
||||
defaultShortcut: "Shift+Mod+D",
|
||||
},
|
||||
{
|
||||
id: "sendSelectionToAi",
|
||||
labelKey: "settings.shortcutSendSelectionToAi",
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
import type { TreeNode } from "@/types/database";
|
||||
|
||||
/**
|
||||
* 侧边栏中支持“查看 DDL”的节点类型,与右键菜单的
|
||||
* “查看 DDL”菜单项可见条件保持一致。
|
||||
*/
|
||||
export function sidebarNodeSupportsDdlView(node: TreeNode): boolean {
|
||||
return node.type === "table" || node.type === "view" || node.type === "materialized_view";
|
||||
}
|
||||
Reference in new issue
Block a user