mirror of
https://github.com/t8y2/dbx.git
synced 2026-10-19 16:16:45 +00:00
fix: 修复双击模式下单击对象失效,右键查看DDL、源码等复用右侧面板
This commit is contained in:
@@ -77,7 +77,6 @@ import { useExportTracker, type ExportTask } from "@/composables/useExportTracke
|
||||
import { useSettingsStore } from "@/stores/settingsStore";
|
||||
import { useQueryStore } from "@/stores/queryStore";
|
||||
import QueryEditor from "@/components/editor/QueryEditor.vue";
|
||||
import DdlViewDialog from "./DdlViewDialog.vue";
|
||||
import { sqlFormatDialectForDbType, type SqlFormatDialect } from "@/lib/sql/sqlFormatter";
|
||||
import { isCancelSearchShortcut } from "@/lib/editor/keyboardShortcuts";
|
||||
import { executeWithProductionSqlGuard } from "@/lib/database/productionExecutionGuard";
|
||||
@@ -196,8 +195,6 @@ const duplicateTarget = ref<ObjectBrowserRow | null>(null);
|
||||
const duplicateTableName = ref("");
|
||||
const showProcedureExecutionConfirm = ref(false);
|
||||
const procedureExecutionTarget = ref<ObjectBrowserRow | null>(null);
|
||||
const ddlDialogTarget = ref<ObjectBrowserRow | null>(null);
|
||||
const showDdlDialog = ref(false);
|
||||
const selectedTableIds = ref<Set<string>>(new Set());
|
||||
const expandedPartitionParentIds = ref<Set<string>>(new Set());
|
||||
const showBatchDropConfirm = ref(false);
|
||||
@@ -741,7 +738,7 @@ function onRowClick(row: ObjectBrowserRow, event: MouseEvent) {
|
||||
}
|
||||
// Single click: defer when the row has a distinct double-click action so a
|
||||
// following second click can cancel it (e.g. TABLE single→table-info, double→open-table).
|
||||
if (shouldDeferSingleClick(row, action, activation)) {
|
||||
if (shouldDeferSingleClick(row, action)) {
|
||||
if (singleClickTimer) clearTimeout(singleClickTimer);
|
||||
singleClickTimer = setTimeout(() => {
|
||||
singleClickTimer = null;
|
||||
@@ -815,9 +812,9 @@ const filteredTableDdlContent = computed(() => {
|
||||
});
|
||||
});
|
||||
|
||||
async function openTableInfo(row: ObjectBrowserRow) {
|
||||
async function openTableInfo(row: ObjectBrowserRow, initialTab?: TableInfoTab) {
|
||||
// Toggle off if clicking the same table
|
||||
if (sidePanelRow.value?.id === row.id && sidePanelMode.value === "table-info") {
|
||||
if (sidePanelRow.value?.id === row.id && sidePanelMode.value === "table-info" && !initialTab) {
|
||||
closeSidePanel();
|
||||
return;
|
||||
}
|
||||
@@ -831,8 +828,8 @@ async function openTableInfo(row: ObjectBrowserRow) {
|
||||
tableForeignKeys.value = [];
|
||||
tableTriggers.value = [];
|
||||
tableInfoSearchQuery.value = "";
|
||||
// Determine first available tab
|
||||
const firstTab = tableInfoTabs.value[0]?.id ?? "ddl";
|
||||
// Determine initial tab: explicit request > first available > ddl
|
||||
const firstTab = initialTab ?? tableInfoTabs.value[0]?.id ?? "ddl";
|
||||
await selectTableInfoTab(firstTab);
|
||||
}
|
||||
|
||||
@@ -855,7 +852,7 @@ async function fetchTableDdl() {
|
||||
tableDdlLoading.value = true;
|
||||
try {
|
||||
const schema = row.schema || selectedSchema.value || props.database;
|
||||
const ddl = await api.getTableDdl(props.connection.id, props.database || "", schema, row.name);
|
||||
const ddl = await api.getTableDdl(props.connection.id, props.database || "", schema, row.name, tableDdlObjectType(row.type));
|
||||
if (sidePanelGuard.isStale(epoch)) return;
|
||||
tableDdlContent.value = ddl;
|
||||
} catch (e: any) {
|
||||
@@ -2233,10 +2230,7 @@ function getTableMenuItems(item: ObjectBrowserRow): ContextMenuItem[] {
|
||||
{ label: t("contextMenu.viewData"), action: () => openViewData(item), icon: Table2 },
|
||||
{
|
||||
label: t("contextMenu.viewDdl"),
|
||||
action: () => {
|
||||
ddlDialogTarget.value = item;
|
||||
showDdlDialog.value = true;
|
||||
},
|
||||
action: () => openTableInfo(item, "ddl"),
|
||||
icon: FileCode,
|
||||
},
|
||||
...(canOpenStructureEditor.value ? [{ label: t("contextMenu.editStructure"), action: () => openStructureEditor(item), icon: PencilRuler }] : []),
|
||||
@@ -2287,10 +2281,7 @@ function getViewMenuItems(item: ObjectBrowserRow): ContextMenuItem[] {
|
||||
{ label: t("contextMenu.viewSource"), action: () => openSource(item), icon: Code2 },
|
||||
{
|
||||
label: t("contextMenu.viewDdl"),
|
||||
action: () => {
|
||||
ddlDialogTarget.value = item;
|
||||
showDdlDialog.value = true;
|
||||
},
|
||||
action: () => openTableInfo(item, "ddl"),
|
||||
icon: ScrollText,
|
||||
},
|
||||
...(canRename(item) ? [{ label: t("contextMenu.renameObject"), action: () => requestRename(item), icon: Pencil }] : []),
|
||||
@@ -3043,18 +3034,6 @@ function getObjectBrowserMenuItems(item: ObjectBrowserRow): ContextMenuItem[] {
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
<DdlViewDialog
|
||||
v-if="ddlDialogTarget"
|
||||
:connection-id="props.connection.id"
|
||||
:database="props.database"
|
||||
:schema="ddlDialogTarget.schema || selectedSchema"
|
||||
:table-name="ddlDialogTarget.name"
|
||||
:object-type="tableDdlObjectType(ddlDialogTarget.type)"
|
||||
:dialect="sourceDialect"
|
||||
:format-dialect="sourceFormatDialect"
|
||||
v-model:open="showDdlDialog"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -94,9 +94,15 @@ describe("resolveRowClickAction", () => {
|
||||
});
|
||||
|
||||
describe("double-click activation mode", () => {
|
||||
it("single click (detail=1) returns none", () => {
|
||||
it("single click (detail=1) on TABLE returns table-info (side panel)", () => {
|
||||
const result = resolveRowClickAction(tableRow, 1, "double");
|
||||
expect(result.action).toBe("none");
|
||||
expect(result.action).toBe("table-info");
|
||||
expect(result.isDouble).toBe(false);
|
||||
});
|
||||
|
||||
it("single click (detail=1) on VIEW returns open-source (side panel)", () => {
|
||||
const result = resolveRowClickAction(viewRow, 1, "double");
|
||||
expect(result.action).toBe("open-source");
|
||||
expect(result.isDouble).toBe(false);
|
||||
});
|
||||
|
||||
@@ -118,28 +124,24 @@ describe("shouldDeferSingleClick", () => {
|
||||
const tableRow = row("TABLE", "users");
|
||||
const viewRow = row("VIEW", "v_users");
|
||||
|
||||
it("defers TABLE table-info in single mode (distinct single/double actions)", () => {
|
||||
expect(shouldDeferSingleClick(tableRow, "table-info", "single")).toBe(true);
|
||||
it("defers TABLE table-info (distinct single/double actions)", () => {
|
||||
expect(shouldDeferSingleClick(tableRow, "table-info")).toBe(true);
|
||||
});
|
||||
|
||||
it("does not defer VIEW open-source in single mode (same single/double action)", () => {
|
||||
expect(shouldDeferSingleClick(viewRow, "open-source", "single")).toBe(false);
|
||||
});
|
||||
|
||||
it("does not defer in double activation mode", () => {
|
||||
expect(shouldDeferSingleClick(tableRow, "table-info", "double")).toBe(false);
|
||||
it("does not defer VIEW open-source (same single/double action)", () => {
|
||||
expect(shouldDeferSingleClick(viewRow, "open-source")).toBe(false);
|
||||
});
|
||||
|
||||
it("does not defer none action", () => {
|
||||
expect(shouldDeferSingleClick(tableRow, "none", "single")).toBe(false);
|
||||
expect(shouldDeferSingleClick(tableRow, "none")).toBe(false);
|
||||
});
|
||||
|
||||
it("does not defer when action is not the single-click action", () => {
|
||||
expect(shouldDeferSingleClick(tableRow, "open-table", "single")).toBe(false);
|
||||
expect(shouldDeferSingleClick(tableRow, "open-table")).toBe(false);
|
||||
});
|
||||
|
||||
it("handles null/undefined row", () => {
|
||||
expect(shouldDeferSingleClick(null, "table-info", "single")).toBe(false);
|
||||
expect(shouldDeferSingleClick(undefined, "open-source", "single")).toBe(false);
|
||||
expect(shouldDeferSingleClick(null, "table-info")).toBe(false);
|
||||
expect(shouldDeferSingleClick(undefined, "open-source")).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -31,11 +31,17 @@ export function doubleClickRowAction(row: ObjectBrowserRow | null | undefined):
|
||||
/**
|
||||
* Resolve a row click event into a single or double action based on click detail
|
||||
* and the sidebar activation setting.
|
||||
*
|
||||
* In both single-click and double-click activation modes, a single click
|
||||
* triggers the side-panel action (table-info / open-source). When a distinct
|
||||
* double-click action exists (e.g. TABLE single→table-info, double→open-table),
|
||||
* the caller defers the single-click via shouldDeferSingleClick so the second
|
||||
* click can cancel it.
|
||||
*/
|
||||
export function resolveRowClickAction(row: ObjectBrowserRow | null | undefined, detail: number, activation: "single" | "double"): { action: ObjectBrowserRowAction; isDouble: boolean } {
|
||||
if (activation === "double") {
|
||||
if (detail === 2) return { action: doubleClickRowAction(row), isDouble: true };
|
||||
return { action: "none", isDouble: false };
|
||||
return { action: singleClickRowAction(row), isDouble: false };
|
||||
}
|
||||
// single-click activation
|
||||
if (detail > 1) return { action: doubleClickRowAction(row), isDouble: true };
|
||||
@@ -44,13 +50,12 @@ export function resolveRowClickAction(row: ObjectBrowserRow | null | undefined,
|
||||
|
||||
/**
|
||||
* Whether a single-click action should be deferred to distinguish it from a
|
||||
* possible upcoming double-click. Only applies in single-click activation mode
|
||||
* and when the row's single-click and double-click actions differ (e.g. TABLE:
|
||||
* single → table-info, double → open-table). For rows whose single and double
|
||||
* actions are identical (e.g. VIEW → open-source both), no deferral is needed.
|
||||
* possible upcoming double-click. Applies when the row's single-click and
|
||||
* double-click actions differ (e.g. TABLE: single → table-info, double →
|
||||
* open-table). For rows whose single and double actions are identical
|
||||
* (e.g. VIEW → open-source both), no deferral is needed.
|
||||
*/
|
||||
export function shouldDeferSingleClick(row: ObjectBrowserRow | null | undefined, action: ObjectBrowserRowAction, activation: "single" | "double"): boolean {
|
||||
if (activation !== "single") return false;
|
||||
export function shouldDeferSingleClick(row: ObjectBrowserRow | null | undefined, action: ObjectBrowserRowAction): boolean {
|
||||
if (action === "none") return false;
|
||||
const single = singleClickRowAction(row);
|
||||
const double = doubleClickRowAction(row);
|
||||
|
||||
Reference in New Issue
Block a user