feat(settings): 启动时标签页恢复策略三选项 (all/pinned/none) (#2395)
* feat(settings): 添加启动标签页恢复策略 * i18n: 补齐启动标签页恢复策略 5 个 locale 译文 --------- Co-authored-by: staff <staff@qimaos-MacBook-Pro.local> Co-authored-by: t8y2 <1156263951@qq.com>
This commit is contained in:
14 files changed
+193
-8
No files matched your search
@@ -1,5 +1,6 @@
|
||||
import { createPinia, setActivePinia } from "pinia";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { ACTIVE_TAB_STORAGE_KEY, OPEN_TABS_STORAGE_KEY } from "@/lib/openTabsPersistence";
|
||||
|
||||
function installLocalStorage() {
|
||||
const data = new Map<string, string>();
|
||||
@@ -8,6 +9,7 @@ function installLocalStorage() {
|
||||
setItem: vi.fn((key: string, value: string) => data.set(key, value)),
|
||||
removeItem: vi.fn((key: string) => data.delete(key)),
|
||||
});
|
||||
return data;
|
||||
}
|
||||
|
||||
describe("queryStore database open state", () => {
|
||||
@@ -37,4 +39,66 @@ describe("queryStore database open state", () => {
|
||||
|
||||
expect(store.isDatabaseOpen("pg-1", "analytics")).toBe(false);
|
||||
});
|
||||
|
||||
it("does not restore open tabs when launch restore mode is none", async () => {
|
||||
const persistedTabs = JSON.stringify([
|
||||
{
|
||||
id: "tab-1",
|
||||
title: "Query 1",
|
||||
connectionId: "pg-1",
|
||||
database: "app",
|
||||
sql: "select 1",
|
||||
},
|
||||
]);
|
||||
|
||||
vi.resetModules();
|
||||
vi.unstubAllGlobals();
|
||||
const storage = installLocalStorage();
|
||||
storage.set("dbx-editor-settings", JSON.stringify({ openTabsRestoreMode: "none" }));
|
||||
storage.set(OPEN_TABS_STORAGE_KEY, persistedTabs);
|
||||
storage.set(ACTIVE_TAB_STORAGE_KEY, "tab-1");
|
||||
setActivePinia(createPinia());
|
||||
|
||||
const { useQueryStore } = await import("@/stores/queryStore");
|
||||
const store = useQueryStore();
|
||||
|
||||
expect(store.tabs).toEqual([]);
|
||||
expect(store.activeTabId).toBeNull();
|
||||
expect(storage.get(OPEN_TABS_STORAGE_KEY)).toBe(persistedTabs);
|
||||
expect(storage.get(ACTIVE_TAB_STORAGE_KEY)).toBe("tab-1");
|
||||
});
|
||||
|
||||
it("restores only pinned tabs when launch restore mode is pinned", async () => {
|
||||
const persistedTabs = JSON.stringify([
|
||||
{
|
||||
id: "tab-1",
|
||||
title: "Pinned",
|
||||
connectionId: "pg-1",
|
||||
database: "app",
|
||||
sql: "select 1",
|
||||
pinned: true,
|
||||
},
|
||||
{
|
||||
id: "tab-2",
|
||||
title: "Regular",
|
||||
connectionId: "pg-1",
|
||||
database: "app",
|
||||
sql: "select 2",
|
||||
},
|
||||
]);
|
||||
|
||||
vi.resetModules();
|
||||
vi.unstubAllGlobals();
|
||||
const storage = installLocalStorage();
|
||||
storage.set("dbx-editor-settings", JSON.stringify({ openTabsRestoreMode: "pinned" }));
|
||||
storage.set(OPEN_TABS_STORAGE_KEY, persistedTabs);
|
||||
storage.set(ACTIVE_TAB_STORAGE_KEY, "tab-2");
|
||||
setActivePinia(createPinia());
|
||||
|
||||
const { useQueryStore } = await import("@/stores/queryStore");
|
||||
const store = useQueryStore();
|
||||
|
||||
expect(store.tabs.map((tab) => tab.id)).toEqual(["tab-1"]);
|
||||
expect(store.activeTabId).toBe("tab-1");
|
||||
});
|
||||
});
|
||||
@@ -30,6 +30,21 @@ describe("normalizeEditorSettings", () => {
|
||||
expect(normalizeEditorSettings({}).updateDownloadSource).toBe("official");
|
||||
});
|
||||
|
||||
it("restores all open tabs on launch by default", () => {
|
||||
expect(normalizeEditorSettings({}).openTabsRestoreMode).toBe("all");
|
||||
});
|
||||
|
||||
it("preserves explicit open tab restore modes", () => {
|
||||
expect(normalizeEditorSettings({ openTabsRestoreMode: "pinned" }).openTabsRestoreMode).toBe("pinned");
|
||||
expect(normalizeEditorSettings({ openTabsRestoreMode: "none" }).openTabsRestoreMode).toBe("none");
|
||||
expect(normalizeEditorSettings({ openTabsRestoreMode: "invalid" as any }).openTabsRestoreMode).toBe("all");
|
||||
});
|
||||
|
||||
it("migrates legacy open tab restore booleans", () => {
|
||||
expect(normalizeEditorSettings({ restoreOpenTabsOnLaunch: false } as any).openTabsRestoreMode).toBe("none");
|
||||
expect(normalizeEditorSettings({ restoreOpenTabsOnLaunch: true } as any).openTabsRestoreMode).toBe("all");
|
||||
});
|
||||
|
||||
it("preserves CNB update download source and rejects invalid values", () => {
|
||||
expect(normalizeEditorSettings({ updateDownloadSource: "cnb" }).updateDownloadSource).toBe("cnb");
|
||||
expect(normalizeEditorSettings({ updateDownloadSource: "mirror" as any }).updateDownloadSource).toBe("official");
|
||||
|
||||
@@ -7,7 +7,7 @@ import { orderPinnedFirst } from "@/lib/pinnedItems";
|
||||
import { canCancelQueryExecution } from "@/lib/queryExecutionState";
|
||||
import { buildExplainSql, parseExplainResult, parseDamengExplainText } from "@/lib/explainPlan";
|
||||
import { allEditableColumnsWriteable, allPrimaryKeysPresent, analyzeEditableQuery, sourceColumnsForResult, type EditableQueryInfo } from "@/lib/sqlAnalysis";
|
||||
import { restoreOpenTabsState, serializeOpenTabs } from "@/lib/openTabsPersistence";
|
||||
import { ACTIVE_TAB_STORAGE_KEY, OPEN_TABS_STORAGE_KEY, restoreOpenTabsState, serializeOpenTabs } from "@/lib/openTabsPersistence";
|
||||
import {
|
||||
evaluateMongoAggregateSafety,
|
||||
evaluateMongoWriteSafety,
|
||||
@@ -45,8 +45,6 @@ import { useSettingsStore } from "@/stores/settingsStore";
|
||||
import { useSavedSqlStore } from "@/stores/savedSqlStore";
|
||||
import type { SavedSqlFile } from "@/types/database";
|
||||
|
||||
const STORAGE_KEY = "dbx-open-tabs";
|
||||
const ACTIVE_TAB_KEY = "dbx-active-tab";
|
||||
const ORACLE_LIKE_METADATA_TYPES = new Set<string>(["oracle", "dameng", "oceanbase-oracle"]);
|
||||
const BACKGROUND_CLIENT_SESSION_SUFFIXES = ["count", "explain", "export"] as const;
|
||||
const CANCEL_QUERY_TIMEOUT_MS = 10_000;
|
||||
@@ -158,14 +156,20 @@ function normalizeOracleLikeQueryAnalysis(dbType: string, analysis: EditableQuer
|
||||
|
||||
function saveTabs(tabs: QueryTab[], activeTabId: string | null) {
|
||||
try {
|
||||
localStorage.setItem(STORAGE_KEY, JSON.stringify(serializeOpenTabs(tabs)));
|
||||
localStorage.setItem(ACTIVE_TAB_KEY, activeTabId || "");
|
||||
localStorage.setItem(OPEN_TABS_STORAGE_KEY, JSON.stringify(serializeOpenTabs(tabs)));
|
||||
localStorage.setItem(ACTIVE_TAB_STORAGE_KEY, activeTabId || "");
|
||||
} catch {}
|
||||
}
|
||||
|
||||
function loadSavedTabs(): { tabs: QueryTab[]; activeTabId: string | null } {
|
||||
try {
|
||||
return restoreOpenTabsState(localStorage.getItem(STORAGE_KEY), localStorage.getItem(ACTIVE_TAB_KEY));
|
||||
const restoreMode = useSettingsStore().editorSettings.openTabsRestoreMode;
|
||||
if (restoreMode === "none") {
|
||||
return { tabs: [], activeTabId: null };
|
||||
}
|
||||
return restoreOpenTabsState(localStorage.getItem(OPEN_TABS_STORAGE_KEY), localStorage.getItem(ACTIVE_TAB_STORAGE_KEY), {
|
||||
filter: restoreMode === "pinned" ? "pinned" : "all",
|
||||
});
|
||||
} catch {
|
||||
return { tabs: [], activeTabId: null };
|
||||
}
|
||||
|
||||
@@ -60,6 +60,7 @@ export type InterfaceLayout = "separated" | "classic";
|
||||
|
||||
export type UpdateDownloadSource = "official" | "cnb";
|
||||
export type SqlSemanticDiagnosticsMode = "auto" | "enabled" | "disabled";
|
||||
export type OpenTabsRestoreMode = "all" | "pinned" | "none";
|
||||
|
||||
export const DEFAULT_SIDEBAR_TABLE_PAGE_SIZE = 1000;
|
||||
const SQL_SEMANTIC_DIAGNOSTICS_AUTO_ENABLED = false;
|
||||
@@ -357,6 +358,7 @@ export interface EditorSettings {
|
||||
sidebarActivation: SidebarActivation;
|
||||
sidebarObjectDisplay: "grouped" | "simple";
|
||||
autoSelectActiveSidebarNode: boolean;
|
||||
openTabsRestoreMode: OpenTabsRestoreMode;
|
||||
disconnectTabHandlingMode: DisconnectTabHandlingMode;
|
||||
reuseDataTab: boolean;
|
||||
updateNotificationsEnabled: boolean;
|
||||
@@ -466,6 +468,7 @@ export const DEFAULT_EDITOR_SETTINGS: EditorSettings = {
|
||||
sidebarActivation: "single",
|
||||
sidebarObjectDisplay: "grouped",
|
||||
autoSelectActiveSidebarNode: false,
|
||||
openTabsRestoreMode: "all",
|
||||
disconnectTabHandlingMode: "close-tabs",
|
||||
reuseDataTab: false,
|
||||
updateNotificationsEnabled: true,
|
||||
@@ -546,6 +549,12 @@ function normalizeDisconnectTabHandlingMode(value: unknown, legacyCloseTabsOnDis
|
||||
return DEFAULT_EDITOR_SETTINGS.disconnectTabHandlingMode;
|
||||
}
|
||||
|
||||
function normalizeOpenTabsRestoreMode(value: unknown, legacyRestoreOpenTabsOnLaunch?: unknown): OpenTabsRestoreMode {
|
||||
if (value === "all" || value === "pinned" || value === "none") return value;
|
||||
if (typeof legacyRestoreOpenTabsOnLaunch === "boolean") return legacyRestoreOpenTabsOnLaunch ? "all" : "none";
|
||||
return DEFAULT_EDITOR_SETTINGS.openTabsRestoreMode;
|
||||
}
|
||||
|
||||
function normalizeColumnFormatters(value: unknown): Record<string, ColumnFormatterConfig> {
|
||||
if (!value || typeof value !== "object" || Array.isArray(value)) return {};
|
||||
const formatters: Record<string, ColumnFormatterConfig> = {};
|
||||
@@ -664,6 +673,7 @@ export function normalizeEditorSettings(settings: Partial<EditorSettings>, exist
|
||||
sidebarActivation: settings.sidebarActivation === "single" || settings.sidebarActivation === "double" ? settings.sidebarActivation : DEFAULT_EDITOR_SETTINGS.sidebarActivation,
|
||||
sidebarObjectDisplay: settings.sidebarObjectDisplay === "simple" || settings.sidebarObjectDisplay === "grouped" ? settings.sidebarObjectDisplay : DEFAULT_EDITOR_SETTINGS.sidebarObjectDisplay,
|
||||
autoSelectActiveSidebarNode: settings.autoSelectActiveSidebarNode ?? DEFAULT_EDITOR_SETTINGS.autoSelectActiveSidebarNode,
|
||||
openTabsRestoreMode: normalizeOpenTabsRestoreMode((settings as Partial<EditorSettings>).openTabsRestoreMode, (settings as Partial<EditorSettings> & { restoreOpenTabsOnLaunch?: boolean }).restoreOpenTabsOnLaunch),
|
||||
disconnectTabHandlingMode: normalizeDisconnectTabHandlingMode((settings as Partial<EditorSettings>).disconnectTabHandlingMode, (settings as Partial<EditorSettings> & { closeQueryTabsOnDisconnect?: boolean }).closeQueryTabsOnDisconnect),
|
||||
reuseDataTab: settings.reuseDataTab ?? DEFAULT_EDITOR_SETTINGS.reuseDataTab,
|
||||
updateNotificationsEnabled: settings.updateNotificationsEnabled ?? DEFAULT_EDITOR_SETTINGS.updateNotificationsEnabled,
|
||||
@@ -844,6 +854,7 @@ export const useSettingsStore = defineStore("settings", () => {
|
||||
if (partial.sidebarActivation !== undefined) editorSettings.value.sidebarActivation = partial.sidebarActivation;
|
||||
if (partial.sidebarObjectDisplay !== undefined) editorSettings.value.sidebarObjectDisplay = partial.sidebarObjectDisplay;
|
||||
if (partial.autoSelectActiveSidebarNode !== undefined) editorSettings.value.autoSelectActiveSidebarNode = partial.autoSelectActiveSidebarNode;
|
||||
if (partial.openTabsRestoreMode !== undefined) editorSettings.value.openTabsRestoreMode = normalizeOpenTabsRestoreMode(partial.openTabsRestoreMode);
|
||||
if (partial.disconnectTabHandlingMode !== undefined) editorSettings.value.disconnectTabHandlingMode = normalizeDisconnectTabHandlingMode(partial.disconnectTabHandlingMode);
|
||||
if (partial.reuseDataTab !== undefined) editorSettings.value.reuseDataTab = partial.reuseDataTab;
|
||||
if (partial.updateNotificationsEnabled !== undefined) editorSettings.value.updateNotificationsEnabled = partial.updateNotificationsEnabled;
|
||||
|
||||
Reference in new issue
Block a user