feat(desktop): add pinned auto-show table info panel
This commit is contained in:
14 files changed
+104
No files matched your search
@@ -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<TableInfoTab>(settingsStore.editorSettings.tableInfoActiveTab);
|
||||
const canPinTableInfo = computed(() => props.context === "table-data");
|
||||
const tableInfoDrawerPinned = computed(() => settingsStore.editorSettings.tableInfoDrawerPinned);
|
||||
const ddlContent = ref("");
|
||||
const tableInfoColumns = ref<ColumnInfo[]>(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() {
|
||||
<PencilRuler class="w-3 h-3" />
|
||||
<span class="table-info-action-label">{{ t("contextMenu.editStructure") }}</span>
|
||||
</Button>
|
||||
<Button
|
||||
v-if="canPinTableInfo"
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
class="h-5 w-5"
|
||||
:class="{ 'bg-accent text-primary': tableInfoDrawerPinned }"
|
||||
:title="tableInfoDrawerPinned ? t('grid.unpinTableInfo') : t('grid.pinTableInfo')"
|
||||
:aria-label="tableInfoDrawerPinned ? t('grid.unpinTableInfo') : t('grid.pinTableInfo')"
|
||||
:aria-pressed="tableInfoDrawerPinned"
|
||||
@click="toggleTableInfoDrawerPinned"
|
||||
>
|
||||
<Pin class="w-3 h-3" :class="{ 'fill-current': tableInfoDrawerPinned }" />
|
||||
</Button>
|
||||
<Button variant="ghost" size="icon" class="h-5 w-5" @click="showTableInfo = false">
|
||||
<X class="w-3 h-3" />
|
||||
</Button>
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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<string, unknown>]> = [
|
||||
["en", en as Record<string, unknown>],
|
||||
["es", es],
|
||||
["it", it_],
|
||||
["ja", ja],
|
||||
["ko", ko],
|
||||
["pt-BR", ptBR],
|
||||
["tr", tr],
|
||||
["zh-CN", zhCN],
|
||||
["zh-TW", zhTW],
|
||||
];
|
||||
|
||||
function gridEntry(locale: Record<string, unknown>, key: string): unknown {
|
||||
const grid = locale["grid"];
|
||||
if (!grid || typeof grid !== "object") return undefined;
|
||||
return (grid as Record<string, unknown>)[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<string, unknown>, 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<string, unknown>, key));
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -2019,6 +2019,8 @@ export default withEnglishFallback({
|
||||
rowsPerPageShort: " 行",
|
||||
columnDetails: "列詳細",
|
||||
tableInfo: "テーブル情報",
|
||||
pinTableInfo: "テーブル情報パネルを固定",
|
||||
unpinTableInfo: "テーブル情報パネルの固定を解除",
|
||||
tableInfoColumns: "列",
|
||||
tableInfoIndexes: "インデックス",
|
||||
columnPrimaryIndex: "主キーインデックス",
|
||||
|
||||
@@ -1983,6 +1983,8 @@ export default withEnglishFallback({
|
||||
rowsPerPageShort: "행",
|
||||
columnDetails: "컬럼 상세",
|
||||
tableInfo: "테이블 정보",
|
||||
pinTableInfo: "테이블 정보 패널 고정",
|
||||
unpinTableInfo: "테이블 정보 패널 고정 해제",
|
||||
tableInfoColumns: "컬럼",
|
||||
tableInfoIndexes: "인덱스",
|
||||
columnPrimaryIndex: "기본 키 인덱스",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -2084,6 +2084,8 @@ export default withEnglishFallback({
|
||||
rowsPerPageShort: " 行/页",
|
||||
columnDetails: "列详情",
|
||||
tableInfo: "表属性",
|
||||
pinTableInfo: "固定表属性面板",
|
||||
unpinTableInfo: "取消固定表属性面板",
|
||||
tableInfoColumns: "字段",
|
||||
tableInfoIndexes: "索引",
|
||||
columnPrimaryIndex: "主键索引",
|
||||
|
||||
@@ -2012,6 +2012,8 @@ export default withEnglishFallback({
|
||||
rowsPerPageShort: " 筆/頁",
|
||||
columnDetails: "欄位詳情",
|
||||
tableInfo: "資料表屬性",
|
||||
pinTableInfo: "固定資料表屬性面板",
|
||||
unpinTableInfo: "取消固定資料表屬性面板",
|
||||
tableInfoColumns: "欄位",
|
||||
tableInfoIndexes: "索引",
|
||||
columnPrimaryIndex: "主鍵索引",
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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<EditorSettings>, 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);
|
||||
|
||||
Reference in new issue
Block a user