mirror of
https://github.com/t8y2/dbx.git
synced 2026-10-19 16:16:45 +00:00
fix(editor): keep edit-structure entry across table property tabs
* fix: 保留表属性编辑结构入口 * fix: 加深表属性页签选中态 --------- Co-authored-by: staff <staff@qimaos-MacBook-Pro.local>
This commit is contained in:
@@ -6895,7 +6895,7 @@ function copyDdl() {
|
||||
|
||||
function openTableStructureEditor() {
|
||||
if (!props.connectionId || !props.database || !props.tableMeta?.tableName || !canOpenTableStructureEditor.value) return;
|
||||
queryStore.openTableStructure(props.connectionId, props.database, props.tableMeta.schema, props.tableMeta.tableName);
|
||||
queryStore.openTableStructure(props.connectionId, props.database, props.tableMeta.schema, props.tableMeta.tableName, activeTableInfoTab.value);
|
||||
}
|
||||
|
||||
function toggleDdlWrap() {
|
||||
@@ -8656,18 +8656,18 @@ const gridContextMenuItems = computed<ContextMenuItem[]>(() => {
|
||||
<div class="flex items-center gap-2 px-3 py-1.5 border-b shrink-0 bg-muted/20 h-9">
|
||||
<TableProperties class="w-3.5 h-3.5 text-muted-foreground" />
|
||||
<span class="text-xs font-medium flex-1 min-w-0 truncate">{{ tableMeta?.tableName }}</span>
|
||||
<div v-if="activeTableInfoTab === 'ddl'" class="table-info-ddl-actions flex min-w-0 shrink-0 items-center gap-1">
|
||||
<Button v-if="canOpenTableStructureEditor" variant="ghost" size="sm" class="table-info-ddl-action-button h-6 px-2 text-xs" :title="t('contextMenu.editStructure')" :aria-label="t('contextMenu.editStructure')" @click="openTableStructureEditor">
|
||||
<PencilRuler class="w-3 h-3" />
|
||||
<span class="table-info-ddl-action-label">{{ t("contextMenu.editStructure") }}</span>
|
||||
</Button>
|
||||
<Button variant="ghost" size="sm" class="table-info-ddl-action-button h-6 px-2 text-xs" :title="t('grid.copyDdl')" :aria-label="t('grid.copyDdl')" @click="copyDdl">
|
||||
<div v-if="activeTableInfoTab === 'ddl'" class="table-info-actions flex min-w-0 shrink-0 items-center gap-1">
|
||||
<Button variant="ghost" size="sm" class="table-info-action-button h-6 px-2 text-xs" :title="t('grid.copyDdl')" :aria-label="t('grid.copyDdl')" @click="copyDdl">
|
||||
<Copy class="w-3 h-3" />
|
||||
<span class="table-info-ddl-action-label">{{ t("grid.copyDdl") }}</span>
|
||||
<span class="table-info-action-label">{{ t("grid.copyDdl") }}</span>
|
||||
</Button>
|
||||
<Button variant="ghost" size="icon" class="h-6 w-6" :class="{ 'bg-accent': ddlWrap }" @click="toggleDdlWrap">
|
||||
<WrapText class="w-3 h-3" />
|
||||
</Button>
|
||||
</div>
|
||||
<Button v-if="activeTableInfoTab === 'ddl'" variant="ghost" size="icon" class="h-6 w-6" :class="{ 'bg-accent': ddlWrap }" @click="toggleDdlWrap">
|
||||
<WrapText class="w-3 h-3" />
|
||||
<Button v-if="canOpenTableStructureEditor" variant="ghost" size="sm" class="table-info-action-button h-6 px-2 text-xs" :title="t('contextMenu.editStructure')" :aria-label="t('contextMenu.editStructure')" @click="openTableStructureEditor">
|
||||
<PencilRuler class="w-3 h-3" />
|
||||
<span class="table-info-action-label">{{ t("contextMenu.editStructure") }}</span>
|
||||
</Button>
|
||||
<Button variant="ghost" size="icon" class="h-5 w-5" @click="showTableInfo = false">
|
||||
<X class="w-3 h-3" />
|
||||
@@ -8677,8 +8677,8 @@ const gridContextMenuItems = computed<ContextMenuItem[]>(() => {
|
||||
<button
|
||||
v-for="tab in tableInfoTabs"
|
||||
:key="tab.id"
|
||||
class="h-9 min-w-0 px-1.5 text-[11px] text-muted-foreground border-b-2 border-transparent hover:bg-gray-200 dark:hover:bg-gray-800/50 hover:text-foreground"
|
||||
:class="{ 'border-primary text-foreground bg-muted/40': activeTableInfoTab === tab.id }"
|
||||
class="h-9 min-w-0 px-1.5 text-[11px] border-b-2 transition-colors"
|
||||
:class="activeTableInfoTab === tab.id ? 'border-primary bg-gray-300/80 text-foreground dark:bg-gray-700/80' : 'border-transparent text-muted-foreground hover:bg-gray-200 hover:text-foreground dark:hover:bg-gray-800/50'"
|
||||
:title="tab.label"
|
||||
@click="selectTableInfoTab(tab.id)"
|
||||
>
|
||||
@@ -9909,7 +9909,7 @@ const gridContextMenuItems = computed<ContextMenuItem[]>(() => {
|
||||
container-type: inline-size;
|
||||
}
|
||||
|
||||
.table-info-ddl-action-button {
|
||||
.table-info-action-button {
|
||||
gap: 0.25rem;
|
||||
max-width: 8rem;
|
||||
overflow: hidden;
|
||||
@@ -9918,7 +9918,7 @@ const gridContextMenuItems = computed<ContextMenuItem[]>(() => {
|
||||
padding-inline 180ms ease;
|
||||
}
|
||||
|
||||
.table-info-ddl-action-label {
|
||||
.table-info-action-label {
|
||||
min-width: 0;
|
||||
max-width: 6rem;
|
||||
overflow: hidden;
|
||||
@@ -9930,13 +9930,13 @@ const gridContextMenuItems = computed<ContextMenuItem[]>(() => {
|
||||
}
|
||||
|
||||
@container (max-width: 360px) {
|
||||
.table-info-ddl-action-button {
|
||||
.table-info-action-button {
|
||||
width: 1.5rem;
|
||||
max-width: 1.5rem;
|
||||
padding-inline: 0;
|
||||
}
|
||||
|
||||
.table-info-ddl-action-label {
|
||||
.table-info-action-label {
|
||||
max-width: 0;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
@@ -1342,6 +1342,8 @@ defineExpose({ focusSearch, refreshData, handleModRTarget, requestQueryEditorExe
|
||||
:database="activeTab.database"
|
||||
:schema="activeTab.schema"
|
||||
:table-name="activeTab.structureTableName || ''"
|
||||
:initial-tab="activeTab.structureInitialTab"
|
||||
:initial-tab-request-id="activeTab.structureInitialTabRequestId"
|
||||
:draft="activeTab.structureDraft"
|
||||
@update:draft="(draft) => (activeTab.structureDraft = draft)"
|
||||
@saved="(commentChanged) => emit('structureEditorSaved', commentChanged)"
|
||||
|
||||
@@ -27,7 +27,7 @@ import { PRESET_FIELDS_TEMPLATE_ID, createTableColumnTemplateDrafts } from "@/li
|
||||
import { getTableMetadataCapabilities } from "@/lib/tableMetadataCapabilities";
|
||||
import { canAddTableStructureColumn, getTableStructureCapabilities } from "@/lib/tableStructureCapabilities";
|
||||
import { connectionObjectTreeQuerySchema, tableStructureDatabaseTypeForConnection } from "@/lib/jdbcDialect";
|
||||
import type { TableStructureEditorDraft } from "@/types/database";
|
||||
import type { TableInfoTab, TableStructureEditorDraft } from "@/types/database";
|
||||
import {
|
||||
buildStructureTargetLabel,
|
||||
combineDataTypeForDatabase,
|
||||
@@ -81,6 +81,8 @@ const props = defineProps<{
|
||||
database: string;
|
||||
schema?: string;
|
||||
tableName: string;
|
||||
initialTab?: TableInfoTab;
|
||||
initialTabRequestId?: number;
|
||||
draft?: TableStructureEditorDraft;
|
||||
}>();
|
||||
|
||||
@@ -91,7 +93,7 @@ const emit = defineEmits<{
|
||||
openSettings: [initialTab?: string, initialSection?: string];
|
||||
}>();
|
||||
|
||||
const activeTab = ref("columns");
|
||||
const activeTab = ref<TableInfoTab>("columns");
|
||||
const loading = ref(false);
|
||||
const saving = ref(false);
|
||||
const postSaveRefreshing = ref(false);
|
||||
@@ -1581,10 +1583,12 @@ function unregisterStructureEditorShortcuts() {
|
||||
|
||||
onMounted(() => {
|
||||
resetState();
|
||||
applyInitialStructureTab();
|
||||
registerStructureEditorShortcuts();
|
||||
void loadDynamicDataTypeOptions();
|
||||
if (props.draft?.initialized) {
|
||||
restoreDraft(props.draft);
|
||||
applyInitialStructureTab();
|
||||
} else if (isCreateMode.value) {
|
||||
markDraftHydratedAndSync();
|
||||
} else {
|
||||
@@ -1616,14 +1620,27 @@ function firstStructureMetadataTab(capabilities = tableMetadataCapabilities.valu
|
||||
return "columns";
|
||||
}
|
||||
|
||||
function isStructureMetadataTabSupported(tab: TableInfoTab, capabilities = tableMetadataCapabilities.value) {
|
||||
return (tab === "columns" && capabilities.columns) || (tab === "indexes" && capabilities.indexes) || (tab === "foreignKeys" && capabilities.foreignKeys) || (tab === "triggers" && capabilities.triggers) || (tab === "ddl" && capabilities.ddl && !isCreateMode.value);
|
||||
}
|
||||
|
||||
function resolveStructureMetadataTab(tab: TableInfoTab | undefined, capabilities = tableMetadataCapabilities.value): TableInfoTab {
|
||||
if (tab && isStructureMetadataTabSupported(tab, capabilities)) return tab;
|
||||
return firstStructureMetadataTab(capabilities);
|
||||
}
|
||||
|
||||
function applyInitialStructureTab() {
|
||||
if (!props.initialTab) return;
|
||||
activeTab.value = resolveStructureMetadataTab(props.initialTab);
|
||||
}
|
||||
|
||||
watch(tableMetadataCapabilities, (capabilities) => {
|
||||
const supported =
|
||||
(activeTab.value === "columns" && capabilities.columns) ||
|
||||
(activeTab.value === "indexes" && capabilities.indexes) ||
|
||||
(activeTab.value === "foreignKeys" && capabilities.foreignKeys) ||
|
||||
(activeTab.value === "triggers" && capabilities.triggers) ||
|
||||
(activeTab.value === "ddl" && capabilities.ddl && !isCreateMode.value);
|
||||
if (!supported) activeTab.value = firstStructureMetadataTab(capabilities);
|
||||
if (!isStructureMetadataTabSupported(activeTab.value, capabilities)) activeTab.value = firstStructureMetadataTab(capabilities);
|
||||
});
|
||||
|
||||
watch([() => props.initialTab, () => props.initialTabRequestId], () => {
|
||||
if (!props.initialTab) return;
|
||||
applyInitialStructureTab();
|
||||
});
|
||||
|
||||
watch([() => props.connectionId, () => props.database, databaseType], () => {
|
||||
|
||||
@@ -2,7 +2,7 @@ import { defineStore } from "pinia";
|
||||
import { uuid } from "@/lib/utils";
|
||||
import { markRaw, ref, watch, computed } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import type { DatabaseType, QueryResult, QueryTab } from "@/types/database";
|
||||
import type { DatabaseType, QueryResult, QueryTab, TableInfoTab } from "@/types/database";
|
||||
import { orderPinnedFirst } from "@/lib/pinnedItems";
|
||||
import { canCancelQueryExecution } from "@/lib/queryExecutionState";
|
||||
import { closeAllTabsState, closeOtherTabsState } from "@/lib/tabCloseActions";
|
||||
@@ -745,11 +745,18 @@ export const useQueryStore = defineStore("query", () => {
|
||||
return id;
|
||||
}
|
||||
|
||||
function openTableStructure(connectionId: string, database: string, schema?: string, tableName?: string) {
|
||||
function applyTableStructureInitialTab(tab: QueryTab, initialTab?: TableInfoTab) {
|
||||
if (!initialTab) return;
|
||||
tab.structureInitialTab = initialTab;
|
||||
tab.structureInitialTabRequestId = (tab.structureInitialTabRequestId ?? 0) + 1;
|
||||
}
|
||||
|
||||
function openTableStructure(connectionId: string, database: string, schema?: string, tableName?: string, initialTab?: TableInfoTab) {
|
||||
const resolvedTableName = tableName || "";
|
||||
if (resolvedTableName) {
|
||||
const existing = tabs.value.find((tab) => tab.mode === "structure" && tab.connectionId === connectionId && tab.database === database && (tab.structureTableName || "") === resolvedTableName);
|
||||
if (existing) {
|
||||
applyTableStructureInitialTab(existing, initialTab);
|
||||
activeTabId.value = existing.id;
|
||||
return existing.id;
|
||||
}
|
||||
@@ -769,6 +776,8 @@ export const useQueryStore = defineStore("query", () => {
|
||||
isExplaining: false,
|
||||
mode: "structure",
|
||||
structureTableName: resolvedTableName,
|
||||
structureInitialTab: initialTab,
|
||||
structureInitialTabRequestId: initialTab ? 1 : undefined,
|
||||
};
|
||||
tabs.value.push(tab);
|
||||
activeTabId.value = id;
|
||||
|
||||
@@ -643,6 +643,8 @@ export interface QueryTab {
|
||||
nacosNamespace?: string;
|
||||
nacosNamespaceName?: string;
|
||||
structureTableName?: string;
|
||||
structureInitialTab?: TableInfoTab;
|
||||
structureInitialTabRequestId?: number;
|
||||
structureDraft?: TableStructureEditorDraft;
|
||||
objectBrowser?: {
|
||||
schema?: string;
|
||||
|
||||
@@ -2759,6 +2759,30 @@ test("new table structure tabs can open multiple drafts while existing tables st
|
||||
}
|
||||
});
|
||||
|
||||
test("reopening table structure tabs records the requested initial tab", () => {
|
||||
const restoreStorage = installMemoryStorage();
|
||||
try {
|
||||
setActivePinia(createPinia());
|
||||
const store = useQueryStore();
|
||||
|
||||
const structureId = store.openTableStructure("conn-1", "db", "public", "users", "indexes");
|
||||
const firstTab = store.tabs.find((item) => item.id === structureId);
|
||||
|
||||
assert.equal(firstTab?.structureInitialTab, "indexes");
|
||||
assert.equal(firstTab?.structureInitialTabRequestId, 1);
|
||||
|
||||
const reusedStructureId = store.openTableStructure("conn-1", "db", "public", "users", "foreignKeys");
|
||||
const reusedTab = store.tabs.find((item) => item.id === reusedStructureId);
|
||||
|
||||
assert.equal(reusedStructureId, structureId);
|
||||
assert.equal(reusedTab?.structureInitialTab, "foreignKeys");
|
||||
assert.equal(reusedTab?.structureInitialTabRequestId, 2);
|
||||
assert.equal(store.activeTabId, structureId);
|
||||
} finally {
|
||||
restoreStorage();
|
||||
}
|
||||
});
|
||||
|
||||
test("table structure refresh versions are scoped by table target", () => {
|
||||
setActivePinia(createPinia());
|
||||
const store = useQueryStore();
|
||||
|
||||
Reference in New Issue
Block a user