From 0ef8369e2b4846ea292650ce953bde05281c51a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BA=8C=E4=B8=AB=E8=AE=B2=E6=A2=B5?= Date: Sun, 6 Sep 2026 15:01:27 +0800 Subject: [PATCH] feat(desktop): add pinned auto-show table info panel --- apps/desktop/src/components/grid/DataGrid.vue | 32 ++++++++++++++ .../src/components/layout/ContentArea.vue | 1 + .../__tests__/tableInfoPinMessages.spec.ts | 42 +++++++++++++++++++ apps/desktop/src/i18n/locales/en.ts | 2 + apps/desktop/src/i18n/locales/es.ts | 2 + apps/desktop/src/i18n/locales/it.ts | 2 + apps/desktop/src/i18n/locales/ja.ts | 2 + apps/desktop/src/i18n/locales/ko.ts | 2 + apps/desktop/src/i18n/locales/pt-BR.ts | 2 + apps/desktop/src/i18n/locales/tr.ts | 2 + apps/desktop/src/i18n/locales/zh-CN.ts | 2 + apps/desktop/src/i18n/locales/zh-TW.ts | 2 + .../stores/__tests__/settingsStore.spec.ts | 7 ++++ apps/desktop/src/stores/settingsStore.ts | 4 ++ 14 files changed, 104 insertions(+) create mode 100644 apps/desktop/src/i18n/__tests__/tableInfoPinMessages.spec.ts diff --git a/apps/desktop/src/components/grid/DataGrid.vue b/apps/desktop/src/components/grid/DataGrid.vue index be8c8b6bef..84bcdda58e 100644 --- a/apps/desktop/src/components/grid/DataGrid.vue +++ b/apps/desktop/src/components/grid/DataGrid.vue @@ -47,6 +47,7 @@ import { Eraser, Columns3, PencilRuler, + Pin, Settings2, WandSparkles, Camera, @@ -493,6 +494,7 @@ interface DataGridProps { primaryKeys: string[]; }; tableInfoTab?: TableInfoTab; + autoShowTableInfo?: boolean; pageOffset?: number; pageLimit?: number; countSql?: string; @@ -11005,6 +11007,8 @@ function clampCellDetailPanelSize(value: number, layout = cellDetailPanelLayout. // module-global leaks the drawer into other kept-alive tabs. const showTableInfo = ref(false); const activeTableInfoTab = ref(settingsStore.editorSettings.tableInfoActiveTab); +const canPinTableInfo = computed(() => props.context === "table-data"); +const tableInfoDrawerPinned = computed(() => settingsStore.editorSettings.tableInfoDrawerPinned); const ddlContent = ref(""); const tableInfoColumns = ref(props.tableMeta?.columns ?? []); const tableInfoColumnsLoading = ref(false); @@ -11337,6 +11341,10 @@ async function toggleTableInfo(tab?: TableInfoTab) { await selectTableInfoTab(nextTab); } +function toggleTableInfoDrawerPinned() { + settingsStore.updateEditorSettings({ tableInfoDrawerPinned: !tableInfoDrawerPinned.value }); +} + async function selectTableInfoTab(tab: TableInfoTab) { const tabSupported = tableInfoTabs.value.some((item) => item.id === tab); const nextTab = tabSupported ? tab : tableInfoTabs.value[0]?.id; @@ -11358,6 +11366,16 @@ watch( { immediate: true }, ); +watch( + () => props.autoShowTableInfo, + (shouldShow) => { + if (!shouldShow || !props.tableMeta) return; + showTableInfo.value = true; + void selectTableInfoTab(activeTableInfoTab.value); + }, + { immediate: true }, +); + async function fetchDdl(force = settingsStore.editorSettings.refreshDdlOnOpen) { if (!props.connectionId || !props.tableMeta) return; showTableInfo.value = true; @@ -11648,6 +11666,7 @@ watch( if (showIndexIndicatorsInHeader.value && canShowTableIndexes.value && currentIndexTableIdentity.value) { void fetchIndexes(); } + if (props.autoShowTableInfo && props.tableMeta) showTableInfo.value = true; if (showTableInfo.value) selectTableInfoTab(activeTableInfoTab.value); if (showTableInfo.value) void fetchTableOwner(); }, @@ -14320,6 +14339,19 @@ function openGridSnapshot() { {{ t("contextMenu.editStructure") }} + diff --git a/apps/desktop/src/components/layout/ContentArea.vue b/apps/desktop/src/components/layout/ContentArea.vue index d81e2b2968..5888f66242 100644 --- a/apps/desktop/src/components/layout/ContentArea.vue +++ b/apps/desktop/src/components/layout/ContentArea.vue @@ -2294,6 +2294,7 @@ defineExpose({ :execution-database="activeDataTabExecutionDatabase" :table-meta="activeDataTabTableMeta" :table-info-tab="activeTab.tableInfoTab" + :auto-show-table-info="settingsStore.editorSettings.tableInfoDrawerPinned" :page-offset="activeTab.resultPageOffset" :page-limit="activeTab.resultPageLimit" :total-row-count="activeTab.resultTotalRowCount" diff --git a/apps/desktop/src/i18n/__tests__/tableInfoPinMessages.spec.ts b/apps/desktop/src/i18n/__tests__/tableInfoPinMessages.spec.ts new file mode 100644 index 0000000000..bf48ac064c --- /dev/null +++ b/apps/desktop/src/i18n/__tests__/tableInfoPinMessages.spec.ts @@ -0,0 +1,42 @@ +import { describe, expect, it } from "vitest"; +import en from "../locales/en"; +import es from "../locales/es"; +import it_ from "../locales/it"; +import ja from "../locales/ja"; +import ko from "../locales/ko"; +import ptBR from "../locales/pt-BR"; +import tr from "../locales/tr"; +import zhCN from "../locales/zh-CN"; +import zhTW from "../locales/zh-TW"; + +const locales: Array<[string, Record]> = [ + ["en", en as Record], + ["es", es], + ["it", it_], + ["ja", ja], + ["ko", ko], + ["pt-BR", ptBR], + ["tr", tr], + ["zh-CN", zhCN], + ["zh-TW", zhTW], +]; + +function gridEntry(locale: Record, key: string): unknown { + const grid = locale["grid"]; + if (!grid || typeof grid !== "object") return undefined; + return (grid as Record)[key]; +} + +describe("table-info pin i18n messages", () => { + it.each(["pinTableInfo", "unpinTableInfo"] as const)("en resolves grid.%s to text", (key) => { + expect(gridEntry(en as Record, key), `grid.${key} missing from locales/en.ts`).toBeTypeOf("string"); + }); + + it.each(locales.slice(1))("%s translates table-info pin labels instead of using the English fallback", (_name, locale) => { + for (const key of ["pinTableInfo", "unpinTableInfo"] as const) { + const value = gridEntry(locale, key); + expect(value, `${_name} grid.${key}`).toBeTypeOf("string"); + expect(value).not.toBe(gridEntry(en as Record, key)); + } + }); +}); diff --git a/apps/desktop/src/i18n/locales/en.ts b/apps/desktop/src/i18n/locales/en.ts index 1b0fbd9d25..22cf9d80df 100644 --- a/apps/desktop/src/i18n/locales/en.ts +++ b/apps/desktop/src/i18n/locales/en.ts @@ -2160,6 +2160,8 @@ export default { rowsPerPageShort: " rows", columnDetails: "Column Details", tableInfo: "Table Info", + pinTableInfo: "Pin table info panel", + unpinTableInfo: "Unpin table info panel", tableInfoColumns: "Columns", tableInfoIndexes: "Indexes", columnPrimaryIndex: "Primary key index", diff --git a/apps/desktop/src/i18n/locales/es.ts b/apps/desktop/src/i18n/locales/es.ts index 764841d8fb..28741eefac 100644 --- a/apps/desktop/src/i18n/locales/es.ts +++ b/apps/desktop/src/i18n/locales/es.ts @@ -2020,6 +2020,8 @@ export default withEnglishFallback({ searchColumn: "Buscar columna/comentario...", noColumnsFound: "No se encontraron columnas", tableInfo: "Información de tabla", + pinTableInfo: "Fijar el panel de información de la tabla", + unpinTableInfo: "Desfijar el panel de información de la tabla", tableInfoColumns: "Columnas", tableInfoIndexes: "Índices", columnPrimaryIndex: "Índice de clave primaria", diff --git a/apps/desktop/src/i18n/locales/it.ts b/apps/desktop/src/i18n/locales/it.ts index 60121c448a..95e5404e6a 100644 --- a/apps/desktop/src/i18n/locales/it.ts +++ b/apps/desktop/src/i18n/locales/it.ts @@ -2007,6 +2007,8 @@ export default withEnglishFallback({ rowsPerPageShort: " righe", columnDetails: "Dettagli Colonna", tableInfo: "Informazioni Tabella", + pinTableInfo: "Blocca il pannello delle informazioni sulla tabella", + unpinTableInfo: "Sblocca il pannello delle informazioni sulla tabella", tableInfoColumns: "Colonne", tableInfoIndexes: "Indici", columnPrimaryIndex: "Indice chiave primaria", diff --git a/apps/desktop/src/i18n/locales/ja.ts b/apps/desktop/src/i18n/locales/ja.ts index 385c6565fa..b5b7848623 100644 --- a/apps/desktop/src/i18n/locales/ja.ts +++ b/apps/desktop/src/i18n/locales/ja.ts @@ -2019,6 +2019,8 @@ export default withEnglishFallback({ rowsPerPageShort: " 行", columnDetails: "列詳細", tableInfo: "テーブル情報", + pinTableInfo: "テーブル情報パネルを固定", + unpinTableInfo: "テーブル情報パネルの固定を解除", tableInfoColumns: "列", tableInfoIndexes: "インデックス", columnPrimaryIndex: "主キーインデックス", diff --git a/apps/desktop/src/i18n/locales/ko.ts b/apps/desktop/src/i18n/locales/ko.ts index 6c1a137a4d..3563c19c1a 100644 --- a/apps/desktop/src/i18n/locales/ko.ts +++ b/apps/desktop/src/i18n/locales/ko.ts @@ -1983,6 +1983,8 @@ export default withEnglishFallback({ rowsPerPageShort: "행", columnDetails: "컬럼 상세", tableInfo: "테이블 정보", + pinTableInfo: "테이블 정보 패널 고정", + unpinTableInfo: "테이블 정보 패널 고정 해제", tableInfoColumns: "컬럼", tableInfoIndexes: "인덱스", columnPrimaryIndex: "기본 키 인덱스", diff --git a/apps/desktop/src/i18n/locales/pt-BR.ts b/apps/desktop/src/i18n/locales/pt-BR.ts index d317dec021..c3ba345b6f 100644 --- a/apps/desktop/src/i18n/locales/pt-BR.ts +++ b/apps/desktop/src/i18n/locales/pt-BR.ts @@ -2009,6 +2009,8 @@ export default withEnglishFallback({ rowsPerPageShort: " linhas", columnDetails: "Detalhes da Coluna", tableInfo: "Informações da Tabela", + pinTableInfo: "Fixar painel de informações da tabela", + unpinTableInfo: "Desafixar painel de informações da tabela", tableInfoColumns: "Colunas", tableInfoIndexes: "Índices", columnPrimaryIndex: "Índice de chave primária", diff --git a/apps/desktop/src/i18n/locales/tr.ts b/apps/desktop/src/i18n/locales/tr.ts index e5267ecb6c..14fc5dae8e 100644 --- a/apps/desktop/src/i18n/locales/tr.ts +++ b/apps/desktop/src/i18n/locales/tr.ts @@ -2154,6 +2154,8 @@ export default withEnglishFallback({ rowsPerPageShort: " satır", columnDetails: "Sütun Ayrıntıları", tableInfo: "Tablo Bilgisi", + pinTableInfo: "Tablo bilgileri panelini sabitle", + unpinTableInfo: "Tablo bilgileri panelinin sabitlemesini kaldır", tableInfoColumns: "Sütunlar", tableInfoIndexes: "Dizinler", columnPrimaryIndex: "Birincil anahtar dizini", diff --git a/apps/desktop/src/i18n/locales/zh-CN.ts b/apps/desktop/src/i18n/locales/zh-CN.ts index 916ce5bd0a..2c6f4adde3 100644 --- a/apps/desktop/src/i18n/locales/zh-CN.ts +++ b/apps/desktop/src/i18n/locales/zh-CN.ts @@ -2084,6 +2084,8 @@ export default withEnglishFallback({ rowsPerPageShort: " 行/页", columnDetails: "列详情", tableInfo: "表属性", + pinTableInfo: "固定表属性面板", + unpinTableInfo: "取消固定表属性面板", tableInfoColumns: "字段", tableInfoIndexes: "索引", columnPrimaryIndex: "主键索引", diff --git a/apps/desktop/src/i18n/locales/zh-TW.ts b/apps/desktop/src/i18n/locales/zh-TW.ts index d97f18a1d0..13a772b164 100644 --- a/apps/desktop/src/i18n/locales/zh-TW.ts +++ b/apps/desktop/src/i18n/locales/zh-TW.ts @@ -2012,6 +2012,8 @@ export default withEnglishFallback({ rowsPerPageShort: " 筆/頁", columnDetails: "欄位詳情", tableInfo: "資料表屬性", + pinTableInfo: "固定資料表屬性面板", + unpinTableInfo: "取消固定資料表屬性面板", tableInfoColumns: "欄位", tableInfoIndexes: "索引", columnPrimaryIndex: "主鍵索引", diff --git a/apps/desktop/src/stores/__tests__/settingsStore.spec.ts b/apps/desktop/src/stores/__tests__/settingsStore.spec.ts index 11e6ced8f1..87b64b6b06 100644 --- a/apps/desktop/src/stores/__tests__/settingsStore.spec.ts +++ b/apps/desktop/src/stores/__tests__/settingsStore.spec.ts @@ -23,6 +23,13 @@ describe("normalizeEditorSettings", () => { expect(normalizeEditorSettings({ refreshDdlOnOpen: "true" } as any).refreshDdlOnOpen).toBe(false); }); + it("keeps table-info drawer pinning disabled unless explicitly enabled", () => { + expect(normalizeEditorSettings({}).tableInfoDrawerPinned).toBe(false); + expect(normalizeEditorSettings({ tableInfoDrawerPinned: true }).tableInfoDrawerPinned).toBe(true); + expect(normalizeEditorSettings({ tableInfoDrawerPinned: false }).tableInfoDrawerPinned).toBe(false); + expect(normalizeEditorSettings({ tableInfoDrawerPinned: "true" } as any).tableInfoDrawerPinned).toBe(false); + }); + it("enables SQL variable substitution by default and only preserves booleans", () => { expect(normalizeEditorSettings({}).sqlVariableSubstitutionEnabled).toBe(true); expect(normalizeEditorSettings({ sqlVariableSubstitutionEnabled: true }).sqlVariableSubstitutionEnabled).toBe(true); diff --git a/apps/desktop/src/stores/settingsStore.ts b/apps/desktop/src/stores/settingsStore.ts index 9c71141f2a..c94ccc6547 100644 --- a/apps/desktop/src/stores/settingsStore.ts +++ b/apps/desktop/src/stores/settingsStore.ts @@ -776,6 +776,7 @@ export interface EditorSettings { tableFontSize: number; structureEditorDensity: StructureEditorDensity; tableInfoActiveTab: TableInfoTab; + tableInfoDrawerPinned: boolean; tableInfoDrawerWidth: number; cellDetailDrawerWidth: number; cellDetailPanelLayout: CellDetailPanelLayout; @@ -1004,6 +1005,7 @@ export const DEFAULT_EDITOR_SETTINGS: EditorSettings = { tableFontSize: TABLE_FONT_SIZE_DEFAULT, structureEditorDensity: "compact", tableInfoActiveTab: "ddl", + tableInfoDrawerPinned: false, tableInfoDrawerWidth: 320, cellDetailDrawerWidth: 380, cellDetailPanelLayout: "bottom", @@ -1448,6 +1450,7 @@ export function normalizeEditorSettings(settings: Partial, exist tableFontSize: normalizeTableFontSize(settings.tableFontSize), structureEditorDensity: normalizeStructureEditorDensity(settings.structureEditorDensity), tableInfoActiveTab: normalizeTableInfoTab(settings.tableInfoActiveTab), + tableInfoDrawerPinned: settings.tableInfoDrawerPinned === true, tableInfoDrawerWidth: normalizeDrawerWidth(settings.tableInfoDrawerWidth, 240, DEFAULT_EDITOR_SETTINGS.tableInfoDrawerWidth), cellDetailDrawerWidth: normalizeDrawerWidth(settings.cellDetailDrawerWidth, 260, DEFAULT_EDITOR_SETTINGS.cellDetailDrawerWidth), cellDetailPanelLayout: normalizeCellDetailPanelLayout(settings.cellDetailPanelLayout), @@ -2161,6 +2164,7 @@ export const useSettingsStore = defineStore("settings", () => { if (partial.tableFontSize !== undefined) editorSettings.value.tableFontSize = normalizeTableFontSize(partial.tableFontSize); if (partial.structureEditorDensity !== undefined) editorSettings.value.structureEditorDensity = normalizeStructureEditorDensity(partial.structureEditorDensity); if (partial.tableInfoActiveTab !== undefined) editorSettings.value.tableInfoActiveTab = normalizeTableInfoTab(partial.tableInfoActiveTab); + if (partial.tableInfoDrawerPinned !== undefined) editorSettings.value.tableInfoDrawerPinned = partial.tableInfoDrawerPinned === true; if (partial.tableInfoDrawerWidth !== undefined) editorSettings.value.tableInfoDrawerWidth = normalizeDrawerWidth(partial.tableInfoDrawerWidth, 240, DEFAULT_EDITOR_SETTINGS.tableInfoDrawerWidth); if (partial.cellDetailDrawerWidth !== undefined) editorSettings.value.cellDetailDrawerWidth = normalizeDrawerWidth(partial.cellDetailDrawerWidth, 260, DEFAULT_EDITOR_SETTINGS.cellDetailDrawerWidth); if (partial.cellDetailPanelLayout !== undefined) editorSettings.value.cellDetailPanelLayout = normalizeCellDetailPanelLayout(partial.cellDetailPanelLayout);