1617 lines
78 KiB
TypeScript
1617 lines
78 KiB
TypeScript
import { defineStore } from "pinia";
|
|
import { computed, ref } from "vue";
|
|
import { aiConfigToItem, generateId, getConfigKey } from "@/lib/ai/aiConfigList";
|
|
import { DEFAULT_DATA_GRID_FONT_FAMILY, DEFAULT_UI_FONT_FAMILY } from "@/lib/app/appFonts";
|
|
import * as api from "@/lib/backend/api";
|
|
import { setDebugLoggingEnabled } from "@/lib/backend/debugLog";
|
|
import { safeLocalStorageGet, safeLocalStorageRemove } from "@/lib/backend/safeStorage";
|
|
import { type ColumnFormatterConfig, type CustomColumnFormatterConfig, normalizeColumnFormatter, normalizeCustomColumnFormatter, normalizeGlobalDateTimePattern } from "@/lib/dataGrid/columnFormatter";
|
|
import { type DataGridCopyPreference, type DataGridExtractorOptions, DEFAULT_DATA_GRID_EXTRACTOR_OPTIONS, normalizeDataGridCopyPreference, normalizeDataGridExtractorOptions } from "@/lib/dataGrid/dataGridCopyExtractor";
|
|
import { normalizeResultPageSize } from "@/lib/dataGrid/paginationPageSize";
|
|
import { normalizeShortcutSettings, type ShortcutSettings } from "@/lib/editor/shortcutRegistry";
|
|
import type { SavedSqlOpenTargetMode } from "@/lib/savedSql/savedSqlExecutionTarget";
|
|
import type { ConnectionListSortMode } from "@/lib/sidebar/connectionListSort";
|
|
import { normalizeSidebarHiddenTablePrefixes } from "@/lib/sidebar/sidebarTableNameDisplay";
|
|
import type { SidebarActivation } from "@/lib/sidebar/treeNodeClick";
|
|
import { DEFAULT_SQL_SNIPPETS } from "@/lib/sql/sqlCompletion";
|
|
import { DEFAULT_SQL_FORMATTER_SETTINGS, normalizeSqlFormatterSettings, type SqlFormatterSettings } from "@/lib/sql/sqlFormatterConfig";
|
|
import { normalizeSqlVariableSyntaxOverrides, type SqlVariableSyntaxOverrides } from "@/lib/sql/sqlVariableSyntax";
|
|
import { DEFAULT_TABLE_COLUMN_TEMPLATE_FIELDS, normalizeTableColumnTemplateFields } from "@/lib/table/tableColumnTemplates";
|
|
import { type DataTabReuseMode, DEFAULT_DATA_TAB_REUSE_MODE, normalizeDataTabReuseMode } from "@/lib/tabs/dataTabReuseMode";
|
|
import type { AiApiStyle, AiAuthMethod, AiChatSelectionState, AiConfig, AiConfigItem, AiConfiguredModel, AiEffortLevel, AiEffortSelection, AiModelEffortPreference, AiProvider, AiReasoningLevel, AiTestConnectionResult } from "@/types/ai";
|
|
import type { SqlSnippet, TableInfoTab } from "@/types/database";
|
|
|
|
export type { AiApiStyle, AiAuthMethod, AiChatSelectionState, AiConfig, AiConfigItem, AiConfiguredModel, AiEffortLevel, AiEffortSelection, AiProvider, AiReasoningLevel, AiTestConnectionResult, DataTabReuseMode, SavedSqlOpenTargetMode };
|
|
|
|
export interface DesktopSettings {
|
|
show_tray_icon: boolean;
|
|
icon_theme: DesktopIconTheme;
|
|
quit_on_close: boolean;
|
|
close_action_prompted: boolean;
|
|
debug_logging_enabled: boolean;
|
|
duckdb_worker_process_isolation: boolean;
|
|
duckdb_worker_max_processes: number;
|
|
saved_sql_sync_dir?: string | null;
|
|
driver_store_dir?: string | null;
|
|
plugin_store_dir?: string | null;
|
|
agent_store_dir?: string | null;
|
|
sidebar_table_page_size?: number | null;
|
|
}
|
|
|
|
export interface McpGlobalPolicy {
|
|
readOnly: boolean;
|
|
allowDangerousSql: boolean;
|
|
allowedConnectionIds: string[] | null;
|
|
configured: boolean;
|
|
}
|
|
|
|
export type DesktopIconTheme = "default" | "black";
|
|
|
|
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;
|
|
export const DUCKDB_WORKER_MAX_PROCESSES_MIN = 1;
|
|
export const DUCKDB_WORKER_MAX_PROCESSES_MAX = 16;
|
|
export const DUCKDB_WORKER_MAX_PROCESSES_DEFAULT = 4;
|
|
const SQL_SEMANTIC_DIAGNOSTICS_AUTO_ENABLED = false;
|
|
|
|
export const DEFAULT_DESKTOP_SETTINGS: DesktopSettings = {
|
|
show_tray_icon: true,
|
|
icon_theme: "default",
|
|
quit_on_close: false,
|
|
close_action_prompted: false,
|
|
debug_logging_enabled: false,
|
|
duckdb_worker_process_isolation: false,
|
|
duckdb_worker_max_processes: DUCKDB_WORKER_MAX_PROCESSES_DEFAULT,
|
|
saved_sql_sync_dir: null,
|
|
driver_store_dir: null,
|
|
plugin_store_dir: null,
|
|
agent_store_dir: null,
|
|
sidebar_table_page_size: DEFAULT_SIDEBAR_TABLE_PAGE_SIZE,
|
|
};
|
|
|
|
export const DEFAULT_MCP_GLOBAL_POLICY: McpGlobalPolicy = {
|
|
readOnly: false,
|
|
allowDangerousSql: false,
|
|
allowedConnectionIds: null,
|
|
configured: false,
|
|
};
|
|
|
|
export function normalizeMcpGlobalPolicy(policy: Partial<McpGlobalPolicy> | null | undefined): McpGlobalPolicy {
|
|
const allowedConnectionIds = policy?.allowedConnectionIds === null || policy?.allowedConnectionIds === undefined ? null : [...new Set(policy.allowedConnectionIds.filter((id): id is string => typeof id === "string" && id.trim().length > 0).map((id) => id.trim()))];
|
|
return {
|
|
readOnly: policy?.readOnly === true,
|
|
allowDangerousSql: policy?.allowDangerousSql === true,
|
|
allowedConnectionIds,
|
|
configured: policy?.configured === true,
|
|
};
|
|
}
|
|
|
|
export function normalizeDesktopSettings(settings: Partial<DesktopSettings> | null | undefined): DesktopSettings {
|
|
const iconTheme = settings?.icon_theme === "black" ? "black" : DEFAULT_DESKTOP_SETTINGS.icon_theme;
|
|
const sidebarTablePageSize = typeof settings?.sidebar_table_page_size === "number" && settings.sidebar_table_page_size > 0 ? settings.sidebar_table_page_size : DEFAULT_DESKTOP_SETTINGS.sidebar_table_page_size;
|
|
return {
|
|
show_tray_icon: settings?.show_tray_icon ?? DEFAULT_DESKTOP_SETTINGS.show_tray_icon,
|
|
icon_theme: iconTheme,
|
|
quit_on_close: settings?.quit_on_close ?? DEFAULT_DESKTOP_SETTINGS.quit_on_close,
|
|
close_action_prompted: settings?.close_action_prompted ?? DEFAULT_DESKTOP_SETTINGS.close_action_prompted,
|
|
debug_logging_enabled: settings?.debug_logging_enabled ?? DEFAULT_DESKTOP_SETTINGS.debug_logging_enabled,
|
|
duckdb_worker_process_isolation: settings?.duckdb_worker_process_isolation ?? DEFAULT_DESKTOP_SETTINGS.duckdb_worker_process_isolation,
|
|
duckdb_worker_max_processes: normalizeDuckDbWorkerMaxProcesses(settings?.duckdb_worker_max_processes),
|
|
saved_sql_sync_dir: settings?.saved_sql_sync_dir?.trim() || DEFAULT_DESKTOP_SETTINGS.saved_sql_sync_dir,
|
|
driver_store_dir: settings?.driver_store_dir?.trim() || DEFAULT_DESKTOP_SETTINGS.driver_store_dir,
|
|
plugin_store_dir: settings?.plugin_store_dir?.trim() || DEFAULT_DESKTOP_SETTINGS.plugin_store_dir,
|
|
agent_store_dir: settings?.agent_store_dir?.trim() || DEFAULT_DESKTOP_SETTINGS.agent_store_dir,
|
|
sidebar_table_page_size: sidebarTablePageSize,
|
|
};
|
|
}
|
|
|
|
export function normalizeDuckDbWorkerMaxProcesses(value: unknown): number {
|
|
if (typeof value !== "number" || !Number.isFinite(value)) return DUCKDB_WORKER_MAX_PROCESSES_DEFAULT;
|
|
return Math.min(DUCKDB_WORKER_MAX_PROCESSES_MAX, Math.max(DUCKDB_WORKER_MAX_PROCESSES_MIN, Math.round(value)));
|
|
}
|
|
|
|
export interface AiProviderPreset extends Omit<AiConfig, "apiKey"> {
|
|
label: string;
|
|
iconSlug?: string;
|
|
requiresApiKey: boolean;
|
|
}
|
|
|
|
export const AI_PROVIDER_PRESETS: Record<AiProvider, AiProviderPreset> = {
|
|
claude: {
|
|
label: "Claude",
|
|
iconSlug: "anthropic",
|
|
provider: "claude",
|
|
endpoint: "https://api.anthropic.com/v1/messages",
|
|
model: "claude-sonnet-4-20250514",
|
|
apiStyle: "completions",
|
|
authMethod: "api-key",
|
|
requiresApiKey: true,
|
|
},
|
|
openai: {
|
|
label: "OpenAI",
|
|
iconSlug: "openai",
|
|
provider: "openai",
|
|
endpoint: "https://api.openai.com/v1/chat/completions",
|
|
model: "gpt-4o-mini",
|
|
apiStyle: "completions",
|
|
authMethod: "bearer",
|
|
requiresApiKey: true,
|
|
},
|
|
gemini: {
|
|
label: "Gemini",
|
|
iconSlug: "googlegemini",
|
|
provider: "gemini",
|
|
endpoint: "https://generativelanguage.googleapis.com",
|
|
model: "gemini-1.5-pro",
|
|
apiStyle: "completions",
|
|
authMethod: "api-key",
|
|
requiresApiKey: true,
|
|
},
|
|
deepseek: {
|
|
label: "DeepSeek",
|
|
iconSlug: "deepseek",
|
|
provider: "deepseek",
|
|
endpoint: "https://api.deepseek.com/v1",
|
|
model: "deepseek-v4-flash",
|
|
apiStyle: "completions",
|
|
authMethod: "bearer",
|
|
requiresApiKey: true,
|
|
},
|
|
qwen: {
|
|
label: "Qwen",
|
|
iconSlug: "alibabacloud",
|
|
provider: "qwen",
|
|
endpoint: "https://dashscope.aliyuncs.com/compatible-mode/v1",
|
|
model: "qwen-plus",
|
|
apiStyle: "completions",
|
|
authMethod: "bearer",
|
|
requiresApiKey: true,
|
|
},
|
|
minimax: {
|
|
label: "MiniMax",
|
|
iconSlug: "minimax",
|
|
provider: "minimax",
|
|
endpoint: "https://api.minimax.io/v1",
|
|
model: "MiniMax-M3",
|
|
apiStyle: "completions",
|
|
authMethod: "bearer",
|
|
requiresApiKey: true,
|
|
},
|
|
ollama: {
|
|
label: "Ollama",
|
|
iconSlug: "ollama",
|
|
provider: "ollama",
|
|
endpoint: "http://localhost:11434/v1",
|
|
model: "llama3.1",
|
|
apiStyle: "completions",
|
|
authMethod: "bearer",
|
|
requiresApiKey: false,
|
|
},
|
|
"anthropic-compatible": {
|
|
label: "Anthropic Compatible",
|
|
iconSlug: "anthropic",
|
|
provider: "anthropic-compatible",
|
|
endpoint: "",
|
|
model: "",
|
|
apiStyle: "anthropic-messages",
|
|
authMethod: "bearer",
|
|
requiresApiKey: false,
|
|
},
|
|
"openai-compatible": {
|
|
label: "OpenAI Compatible",
|
|
iconSlug: "openai",
|
|
provider: "openai-compatible",
|
|
endpoint: "",
|
|
model: "",
|
|
apiStyle: "completions",
|
|
authMethod: "bearer",
|
|
requiresApiKey: false,
|
|
},
|
|
"claude-code-cli": {
|
|
label: "Claude Code CLI",
|
|
iconSlug: "claudecode",
|
|
provider: "claude-code-cli",
|
|
endpoint: "",
|
|
model: "default",
|
|
apiStyle: "completions",
|
|
authMethod: "bearer",
|
|
requiresApiKey: false,
|
|
},
|
|
"codex-cli": {
|
|
label: "Codex CLI",
|
|
iconSlug: "codex",
|
|
provider: "codex-cli",
|
|
endpoint: "",
|
|
model: "default",
|
|
apiStyle: "completions",
|
|
authMethod: "bearer",
|
|
requiresApiKey: false,
|
|
},
|
|
"opencode-cli": {
|
|
label: "OpenCode CLI",
|
|
iconSlug: "opencode",
|
|
provider: "opencode-cli",
|
|
endpoint: "",
|
|
model: "default",
|
|
apiStyle: "completions",
|
|
authMethod: "bearer",
|
|
requiresApiKey: false,
|
|
},
|
|
"cursor-cli": {
|
|
label: "Cursor CLI",
|
|
iconSlug: "cursor",
|
|
provider: "cursor-cli",
|
|
endpoint: "",
|
|
model: "default",
|
|
apiStyle: "completions",
|
|
authMethod: "bearer",
|
|
requiresApiKey: false,
|
|
},
|
|
"pi-agent-cli": {
|
|
label: "Pi Coding Agent",
|
|
iconSlug: "pi",
|
|
provider: "pi-agent-cli",
|
|
endpoint: "",
|
|
model: "default",
|
|
apiStyle: "completions",
|
|
authMethod: "bearer",
|
|
requiresApiKey: false,
|
|
},
|
|
custom: {
|
|
label: "Custom",
|
|
provider: "custom",
|
|
endpoint: "",
|
|
model: "",
|
|
apiStyle: "completions",
|
|
authMethod: "bearer",
|
|
requiresApiKey: false,
|
|
},
|
|
};
|
|
|
|
const defaultConfigs: Record<AiProvider, Omit<AiConfig, "apiKey">> = Object.fromEntries(
|
|
Object.entries(AI_PROVIDER_PRESETS).map(([provider, preset]) => {
|
|
const { label: _label, iconSlug: _iconSlug, requiresApiKey: _requiresApiKey, ...config } = preset;
|
|
return [provider, config];
|
|
}),
|
|
) as Record<AiProvider, Omit<AiConfig, "apiKey">>;
|
|
|
|
const AI_REASONING_LEVELS: AiReasoningLevel[] = ["default", "minimal", "low", "medium", "high", "xhigh", "max"];
|
|
const AI_ENV_KEY_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
|
|
function normalizeAiReasoningLevel(value: unknown): AiReasoningLevel {
|
|
return typeof value === "string" && AI_REASONING_LEVELS.includes(value as AiReasoningLevel) ? (value as AiReasoningLevel) : "default";
|
|
}
|
|
|
|
export function normalizeAiEnv(value: unknown): Record<string, string> {
|
|
if (!value || typeof value !== "object" || Array.isArray(value)) return {};
|
|
const result: Record<string, string> = {};
|
|
for (const [rawKey, rawValue] of Object.entries(value as Record<string, unknown>)) {
|
|
const key = rawKey.trim();
|
|
if (!key || !AI_ENV_KEY_RE.test(key)) continue;
|
|
result[key] = rawValue == null ? "" : String(rawValue);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
export function normalizeAiConfig(config: Partial<AiConfig> | null | undefined): AiConfig {
|
|
const provider = config?.provider && config.provider in AI_PROVIDER_PRESETS ? config.provider : inferAiProviderFromConfig(config);
|
|
return {
|
|
...defaultConfigs[provider],
|
|
...config,
|
|
provider,
|
|
apiKey: (config?.apiKey ?? "").trim(),
|
|
apiStyle: config?.apiStyle ?? defaultConfigs[provider].apiStyle,
|
|
authMethod: config?.authMethod ?? defaultConfigs[provider].authMethod,
|
|
proxyEnabled: !!config?.proxyEnabled,
|
|
proxyUrl: config?.proxyUrl ?? "",
|
|
enableThinking: config?.enableThinking ?? true,
|
|
reasoningLevel: normalizeAiReasoningLevel(config?.reasoningLevel),
|
|
contextWindow: config?.contextWindow ?? undefined,
|
|
codexCliPath: config?.codexCliPath?.trim() || undefined,
|
|
codexCliEnv: normalizeAiEnv(config?.codexCliEnv),
|
|
claudeCodeCliPath: config?.claudeCodeCliPath?.trim() || undefined,
|
|
claudeCodeCliEnv: normalizeAiEnv(config?.claudeCodeCliEnv),
|
|
piAgentCliPath: config?.piAgentCliPath?.trim() || undefined,
|
|
piAgentCliEnv: normalizeAiEnv(config?.piAgentCliEnv),
|
|
opencodeCliPath: config?.opencodeCliPath?.trim() || undefined,
|
|
opencodeCliEnv: normalizeAiEnv(config?.opencodeCliEnv),
|
|
cursorCliPath: config?.cursorCliPath?.trim() || undefined,
|
|
cursorCliEnv: normalizeAiEnv(config?.cursorCliEnv),
|
|
};
|
|
}
|
|
|
|
function normalizeAiConfigItem(config: AiConfigItem): AiConfigItem {
|
|
return { ...config, ...normalizeAiConfig(config) };
|
|
}
|
|
|
|
function inferAiProviderFromConfig(config: Partial<AiConfig> | null | undefined): AiProvider {
|
|
const endpoint = config?.endpoint?.toLowerCase() ?? "";
|
|
const model = config?.model?.toLowerCase() ?? "";
|
|
if (endpoint.includes("deepseek") || model.includes("deepseek")) return "deepseek";
|
|
if (endpoint.includes("dashscope") || endpoint.includes("aliyuncs") || model.includes("qwen")) return "qwen";
|
|
if (endpoint.includes("generativelanguage.googleapis.com") || model.includes("gemini")) return "gemini";
|
|
if (endpoint.includes("minimax.io") || endpoint.includes("minimaxi.com") || model.includes("minimax")) return "minimax";
|
|
if (endpoint.includes("localhost:11434") || endpoint.includes("127.0.0.1:11434")) return "ollama";
|
|
if (endpoint.includes("openai.com") || model.startsWith("gpt-")) return "openai";
|
|
return "claude";
|
|
}
|
|
|
|
export type EditorTheme =
|
|
| "app"
|
|
| "one-dark"
|
|
| "vscode-dark"
|
|
| "vscode-light"
|
|
| "nord"
|
|
| "okaidia"
|
|
| "material"
|
|
| "duotone-light"
|
|
| "duotone-dark"
|
|
| "xcode"
|
|
| "xcode-dark"
|
|
| "idea-light"
|
|
| "idea-dark"
|
|
| "jetbrains-light"
|
|
| "jetbrains-dark"
|
|
| "cursor-light"
|
|
| "cursor-dark"
|
|
| "claude-light"
|
|
| "claude-dark"
|
|
| "custom";
|
|
|
|
const STRUCTURE_EDITOR_DENSITIES = ["compact", "standard", "comfortable"] as const;
|
|
export type StructureEditorDensity = (typeof STRUCTURE_EDITOR_DENSITIES)[number];
|
|
const COLUMN_WIDTH_DENSITIES = ["compact", "standard", "comfortable"] as const;
|
|
export type ColumnWidthDensity = (typeof COLUMN_WIDTH_DENSITIES)[number];
|
|
const CELL_DETAIL_PANEL_LAYOUTS = ["bottom", "right"] as const;
|
|
export type CellDetailPanelLayout = (typeof CELL_DETAIL_PANEL_LAYOUTS)[number];
|
|
const TAB_LAYOUT_MODES = ["scroll", "wrap"] as const;
|
|
export type TabLayoutMode = (typeof TAB_LAYOUT_MODES)[number];
|
|
const DATA_GRID_RENDER_MODES = ["dom", "canvas"] as const;
|
|
export type DataGridRenderMode = (typeof DATA_GRID_RENDER_MODES)[number];
|
|
const DATA_GRID_SEARCH_MODES = ["filter", "highlight"] as const;
|
|
export type DataGridSearchMode = (typeof DATA_GRID_SEARCH_MODES)[number];
|
|
const RESULT_RUN_DISPLAY_MODES = ["tabs", "list"] as const;
|
|
export type ResultRunDisplayMode = (typeof RESULT_RUN_DISPLAY_MODES)[number];
|
|
export const TABLE_FONT_SIZE_MIN = 8;
|
|
export const TABLE_FONT_SIZE_MAX = 16;
|
|
export const TABLE_FONT_SIZE_DEFAULT = 13;
|
|
const DISCONNECT_TAB_HANDLING_MODES = ["close-tabs", "keep-tabs-clear-results", "keep-tabs-keep-results"] as const;
|
|
export type DisconnectTabHandlingMode = (typeof DISCONNECT_TAB_HANDLING_MODES)[number];
|
|
|
|
const CLICK_TABLE_NAVIGATION_TARGETS = ["data", "ddl"] as const;
|
|
export type ClickTableNavigationTarget = (typeof CLICK_TABLE_NAVIGATION_TARGETS)[number];
|
|
|
|
export interface CustomThemeColors {
|
|
keyword: string;
|
|
field: string;
|
|
function: string;
|
|
string: string;
|
|
number: string;
|
|
comment: string;
|
|
table: string;
|
|
operator: string;
|
|
type: string;
|
|
builtin: string;
|
|
background?: string;
|
|
foreground?: string;
|
|
}
|
|
|
|
export const DEFAULT_CUSTOM_THEME_COLORS: CustomThemeColors = {
|
|
keyword: "#cba6f7",
|
|
field: "#f9e2af",
|
|
function: "#89dceb",
|
|
string: "#a6e3a1",
|
|
number: "#fab387",
|
|
comment: "#6c7086",
|
|
table: "#a6e3a1",
|
|
operator: "#89b4fa",
|
|
type: "#89b4fa",
|
|
builtin: "#f38ba8",
|
|
};
|
|
|
|
export interface CustomThemeDdlColors {
|
|
addedRowBg: string;
|
|
addedRowBgAlpha: number;
|
|
removedRowBg: string;
|
|
removedRowBgAlpha: number;
|
|
modifiedRowBg: string;
|
|
modifiedRowBgAlpha: number;
|
|
modifiedCharBg: string;
|
|
modifiedCharBgAlpha: number;
|
|
}
|
|
|
|
export const DEFAULT_CUSTOM_THEME_DDL_COLORS: CustomThemeDdlColors = {
|
|
addedRowBg: "#22c55e",
|
|
addedRowBgAlpha: 10,
|
|
removedRowBg: "#ef4444",
|
|
removedRowBgAlpha: 10,
|
|
modifiedRowBg: "#eab308",
|
|
modifiedRowBgAlpha: 10,
|
|
modifiedCharBg: "#f59e0b",
|
|
modifiedCharBgAlpha: 50,
|
|
};
|
|
|
|
export interface CustomTheme {
|
|
id: string;
|
|
name: string;
|
|
colors: CustomThemeColors;
|
|
ddlColors: CustomThemeDdlColors;
|
|
}
|
|
|
|
export const DEFAULT_CUSTOM_THEMES: CustomTheme[] = [
|
|
{
|
|
id: "default",
|
|
name: "Custom",
|
|
colors: { ...DEFAULT_CUSTOM_THEME_COLORS },
|
|
ddlColors: { ...DEFAULT_CUSTOM_THEME_DDL_COLORS },
|
|
},
|
|
];
|
|
|
|
export type SidebarObjectInfoMode = "comment-inline" | "comment-aligned" | "comment-right" | "size" | "hidden";
|
|
|
|
export interface EditorSettings {
|
|
fontFamily: string;
|
|
fontSize: number;
|
|
uiFontFamily: string;
|
|
uiScale: number;
|
|
theme: EditorTheme;
|
|
customThemeColors: CustomThemeColors;
|
|
customThemes: CustomTheme[];
|
|
activeCustomThemeId: string;
|
|
executeMode: "all" | "current";
|
|
executeModeDefaultVersion: number;
|
|
showExecutionTargetPicker: boolean;
|
|
showStatementRunButtons: boolean;
|
|
showCurrentStatementFrame: boolean;
|
|
showInsertValueHints: boolean;
|
|
autoAliasTables: boolean;
|
|
insertSpaceAfterCompletion: boolean;
|
|
wordWrap: boolean;
|
|
vimModeEnabled: boolean;
|
|
autoCloseBrackets: boolean;
|
|
sqlSemanticDiagnosticsMode: SqlSemanticDiagnosticsMode;
|
|
sqlSemanticDiagnosticsEnabled: boolean;
|
|
confirmDangerousSqlExecution: boolean;
|
|
confirmUnsavedSqlClose: boolean;
|
|
savedSqlOpenTargetMode: SavedSqlOpenTargetMode;
|
|
compactTabTitle: boolean;
|
|
tabLayout: TabLayoutMode;
|
|
appLayout: "separated" | "classic";
|
|
pageSize: number;
|
|
tableOpenPageSize: number;
|
|
infiniteScroll: boolean;
|
|
infiniteScrollMaxRows: number;
|
|
regexMaxMatchCount: number;
|
|
autoCalculateTotalRows: boolean;
|
|
mongoViewMode: "document" | "table";
|
|
showColumnCommentsInHeader: boolean;
|
|
showColumnTypesInHeader: boolean;
|
|
showIndexIndicatorsInHeader: boolean;
|
|
compactColumnHeaderActions: boolean;
|
|
columnWidthDensity: ColumnWidthDensity;
|
|
dataGridQuickEntry: boolean;
|
|
dataGridRenderMode: DataGridRenderMode;
|
|
dataGridSearchMode: DataGridSearchMode;
|
|
dataGridCopyExtractor: DataGridCopyPreference;
|
|
dataGridExtractorOptions: DataGridExtractorOptions;
|
|
resultRunDisplayMode: ResultRunDisplayMode;
|
|
dataGridAutoTransposeSingleRow: boolean;
|
|
dataGridMultiRowTranspose: boolean;
|
|
dataGridHideNullColumns: boolean;
|
|
dataGridBooleanDisplayMode: "dropdown" | "checkbox";
|
|
numericColumnRightAlign: boolean;
|
|
tableFontFamily: string;
|
|
tableFontSize: number;
|
|
structureEditorDensity: StructureEditorDensity;
|
|
tableInfoActiveTab: TableInfoTab;
|
|
tableInfoDrawerWidth: number;
|
|
cellDetailDrawerWidth: number;
|
|
cellDetailPanelLayout: CellDetailPanelLayout;
|
|
cellDetailJsonFormatted: boolean;
|
|
cellDetailMetadataCollapsed: boolean;
|
|
shortcuts: ShortcutSettings;
|
|
sqlFormatter: SqlFormatterSettings;
|
|
sidebarActivation: SidebarActivation;
|
|
sidebarConnectionSortMode: ConnectionListSortMode;
|
|
sidebarObjectDisplay: "grouped" | "simple";
|
|
routineSourceOpenMode: "query-tab" | "dialog";
|
|
sidebarTableSearchEnabled: boolean;
|
|
sidebarTableSearchLocal: boolean;
|
|
sidebarGlobalSearchLocal: boolean;
|
|
autoSelectActiveSidebarNode: boolean;
|
|
openTabsRestoreMode: OpenTabsRestoreMode;
|
|
disconnectTabHandlingMode: DisconnectTabHandlingMode;
|
|
dataTabReuseMode: DataTabReuseMode;
|
|
prefillNewQueryWithSelect: boolean;
|
|
updateNotificationsEnabled: boolean;
|
|
sidebarHiddenTablePrefixes: string[];
|
|
sidebarObjectInfoMode: SidebarObjectInfoMode;
|
|
sidebarAllowHorizontalScroll: boolean;
|
|
columnFormatters: Record<string, ColumnFormatterConfig>;
|
|
customColumnFormatters: Record<string, CustomColumnFormatterConfig>;
|
|
globalDateTimeDisplayFormat: string;
|
|
globalDateTimeExportFormat: string;
|
|
globalDateTimeImportFormat: string;
|
|
snippets: SqlSnippet[];
|
|
tableColumnTemplateFields: string[];
|
|
exportBatchSize: number;
|
|
exportRowLimitEnabled: boolean;
|
|
exportRowLimit: number;
|
|
queryExportKeysetOptimizationEnabled: boolean;
|
|
updateDownloadSource: UpdateDownloadSource;
|
|
toolbarItems: ToolbarItems;
|
|
objectBrowserShowCheckbox: boolean;
|
|
objectBrowserViewMode: "list" | "grid";
|
|
sqlVariableSyntaxOverrides: SqlVariableSyntaxOverrides;
|
|
continueOnErrorOnBatch: boolean;
|
|
clickTableNavigationTarget: ClickTableNavigationTarget;
|
|
}
|
|
|
|
export interface ToolbarItems {
|
|
dataTransfer: boolean;
|
|
driverManager: boolean;
|
|
sqlFile: boolean;
|
|
schemaDiff: boolean;
|
|
dataCompare: boolean;
|
|
checkUpdates: boolean;
|
|
sqlLibrary: boolean;
|
|
sqlFileTree: boolean;
|
|
history: boolean;
|
|
ai: boolean;
|
|
theme: boolean;
|
|
github: boolean;
|
|
exclusiveRightSidebarPanels: boolean;
|
|
}
|
|
|
|
export const DEFAULT_TOOLBAR_ITEMS: ToolbarItems = {
|
|
dataTransfer: true,
|
|
driverManager: true,
|
|
sqlFile: true,
|
|
schemaDiff: true,
|
|
dataCompare: true,
|
|
checkUpdates: true,
|
|
sqlLibrary: true,
|
|
sqlFileTree: true,
|
|
history: true,
|
|
ai: true,
|
|
theme: true,
|
|
github: true,
|
|
exclusiveRightSidebarPanels: true,
|
|
};
|
|
|
|
export const RIGHT_SIDEBAR_PANEL_IDS = ["ai", "history", "sqlLibrary", "sqlFile"] as const;
|
|
export type RightSidebarPanelId = (typeof RIGHT_SIDEBAR_PANEL_IDS)[number];
|
|
export type RightSidebarPanelState = Record<RightSidebarPanelId, boolean>;
|
|
|
|
export function transitionRightSidebarPanels(current: RightSidebarPanelState, panel: RightSidebarPanelId, open: boolean, exclusive: boolean): RightSidebarPanelState {
|
|
const next = { ...current };
|
|
if (open && exclusive) {
|
|
for (const panelId of RIGHT_SIDEBAR_PANEL_IDS) next[panelId] = false;
|
|
}
|
|
next[panel] = open;
|
|
return next;
|
|
}
|
|
|
|
export function enforceRightSidebarPanelExclusivity(current: RightSidebarPanelState, preferred?: RightSidebarPanelId): RightSidebarPanelState {
|
|
const panelToKeep = preferred && current[preferred] ? preferred : RIGHT_SIDEBAR_PANEL_IDS.find((panelId) => current[panelId]);
|
|
if (!panelToKeep) return { ...current };
|
|
return transitionRightSidebarPanels(current, panelToKeep, true, true);
|
|
}
|
|
|
|
export const EDITOR_THEMES: {
|
|
value: EditorTheme;
|
|
label: string;
|
|
dark: boolean;
|
|
}[] = [
|
|
{ value: "app", label: "Follow app theme", dark: false },
|
|
{ value: "one-dark", label: "One Dark", dark: true },
|
|
{ value: "vscode-dark", label: "VS Dark+", dark: true },
|
|
{ value: "vscode-light", label: "VS Light+", dark: false },
|
|
{ value: "nord", label: "Nord", dark: true },
|
|
{ value: "okaidia", label: "Okaidia", dark: true },
|
|
{ value: "material", label: "Material", dark: true },
|
|
{ value: "duotone-light", label: "Duotone Light", dark: false },
|
|
{ value: "duotone-dark", label: "Duotone Dark", dark: true },
|
|
{ value: "xcode", label: "Xcode", dark: false },
|
|
{ value: "xcode-dark", label: "Xcode Dark", dark: true },
|
|
{ value: "idea-light", label: "IDEA Light", dark: false },
|
|
{ value: "idea-dark", label: "IDEA Darcula", dark: true },
|
|
{ value: "jetbrains-light", label: "JetBrains Light", dark: false },
|
|
{ value: "jetbrains-dark", label: "JetBrains Dark", dark: true },
|
|
{ value: "cursor-light", label: "Cursor Light", dark: false },
|
|
{ value: "cursor-dark", label: "Cursor Dark", dark: true },
|
|
{ value: "claude-light", label: "Claude Code Light", dark: false },
|
|
{ value: "claude-dark", label: "Claude Code Dark", dark: true },
|
|
{ value: "custom", label: "Custom", dark: true },
|
|
];
|
|
|
|
const EDITOR_THEME_VALUES = new Set<EditorTheme>(EDITOR_THEMES.map((theme) => theme.value));
|
|
|
|
export const EXECUTE_MODE_CURRENT_DEFAULT_VERSION = 1;
|
|
|
|
export const DEFAULT_EDITOR_SETTINGS: EditorSettings = {
|
|
fontFamily: "'Fira Code', 'Cascadia Code', 'Cascadia Mono', 'JetBrains Mono', monospace",
|
|
fontSize: 13,
|
|
uiFontFamily: DEFAULT_UI_FONT_FAMILY,
|
|
uiScale: 1,
|
|
theme: "app",
|
|
customThemeColors: { ...DEFAULT_CUSTOM_THEME_COLORS },
|
|
customThemes: [...DEFAULT_CUSTOM_THEMES],
|
|
activeCustomThemeId: "default",
|
|
executeMode: "current",
|
|
executeModeDefaultVersion: EXECUTE_MODE_CURRENT_DEFAULT_VERSION,
|
|
showExecutionTargetPicker: false,
|
|
showStatementRunButtons: true,
|
|
showCurrentStatementFrame: true,
|
|
showInsertValueHints: true,
|
|
autoAliasTables: true,
|
|
insertSpaceAfterCompletion: true,
|
|
wordWrap: false,
|
|
vimModeEnabled: false,
|
|
autoCloseBrackets: true,
|
|
sqlSemanticDiagnosticsMode: "auto",
|
|
sqlSemanticDiagnosticsEnabled: SQL_SEMANTIC_DIAGNOSTICS_AUTO_ENABLED,
|
|
confirmDangerousSqlExecution: true,
|
|
confirmUnsavedSqlClose: true,
|
|
savedSqlOpenTargetMode: "saved",
|
|
compactTabTitle: false,
|
|
tabLayout: "scroll",
|
|
appLayout: "classic",
|
|
pageSize: 100,
|
|
tableOpenPageSize: 100,
|
|
infiniteScroll: false,
|
|
infiniteScrollMaxRows: 5000,
|
|
regexMaxMatchCount: 1000,
|
|
autoCalculateTotalRows: false,
|
|
mongoViewMode: "document",
|
|
showColumnCommentsInHeader: true,
|
|
showColumnTypesInHeader: true,
|
|
showIndexIndicatorsInHeader: true,
|
|
compactColumnHeaderActions: true,
|
|
columnWidthDensity: "standard",
|
|
dataGridQuickEntry: false,
|
|
dataGridRenderMode: "canvas",
|
|
dataGridSearchMode: "filter",
|
|
dataGridCopyExtractor: "smart",
|
|
dataGridExtractorOptions: normalizeDataGridExtractorOptions(DEFAULT_DATA_GRID_EXTRACTOR_OPTIONS),
|
|
resultRunDisplayMode: "tabs",
|
|
dataGridAutoTransposeSingleRow: false,
|
|
dataGridMultiRowTranspose: false,
|
|
dataGridHideNullColumns: false,
|
|
dataGridBooleanDisplayMode: "dropdown",
|
|
numericColumnRightAlign: true,
|
|
tableFontFamily: DEFAULT_DATA_GRID_FONT_FAMILY,
|
|
tableFontSize: TABLE_FONT_SIZE_DEFAULT,
|
|
structureEditorDensity: "compact",
|
|
tableInfoActiveTab: "ddl",
|
|
tableInfoDrawerWidth: 320,
|
|
cellDetailDrawerWidth: 380,
|
|
cellDetailPanelLayout: "bottom",
|
|
cellDetailJsonFormatted: false,
|
|
cellDetailMetadataCollapsed: false,
|
|
shortcuts: normalizeShortcutSettings(),
|
|
sqlFormatter: normalizeSqlFormatterSettings(DEFAULT_SQL_FORMATTER_SETTINGS),
|
|
sidebarActivation: "single",
|
|
sidebarConnectionSortMode: "manual",
|
|
sidebarObjectDisplay: "grouped",
|
|
routineSourceOpenMode: "query-tab",
|
|
sidebarTableSearchEnabled: false,
|
|
sidebarTableSearchLocal: true,
|
|
sidebarGlobalSearchLocal: false,
|
|
autoSelectActiveSidebarNode: false,
|
|
openTabsRestoreMode: "all",
|
|
disconnectTabHandlingMode: "close-tabs",
|
|
dataTabReuseMode: DEFAULT_DATA_TAB_REUSE_MODE,
|
|
prefillNewQueryWithSelect: true,
|
|
updateNotificationsEnabled: true,
|
|
sidebarHiddenTablePrefixes: [],
|
|
sidebarObjectInfoMode: "comment-inline",
|
|
sidebarAllowHorizontalScroll: false,
|
|
columnFormatters: {},
|
|
customColumnFormatters: {},
|
|
globalDateTimeDisplayFormat: "",
|
|
globalDateTimeExportFormat: "",
|
|
globalDateTimeImportFormat: "",
|
|
snippets: DEFAULT_SQL_SNIPPETS,
|
|
tableColumnTemplateFields: [...DEFAULT_TABLE_COLUMN_TEMPLATE_FIELDS],
|
|
exportBatchSize: 2000,
|
|
exportRowLimitEnabled: false,
|
|
exportRowLimit: 100000,
|
|
queryExportKeysetOptimizationEnabled: true,
|
|
updateDownloadSource: "official",
|
|
toolbarItems: { ...DEFAULT_TOOLBAR_ITEMS },
|
|
objectBrowserShowCheckbox: false,
|
|
objectBrowserViewMode: "list",
|
|
sqlVariableSyntaxOverrides: {},
|
|
continueOnErrorOnBatch: false,
|
|
clickTableNavigationTarget: "data",
|
|
};
|
|
|
|
export const STORAGE_KEY = "dbx-editor-settings";
|
|
const OLD_FONT_SIZE_KEY = "dbx-query-editor-font-size";
|
|
const EXPORT_BATCH_SIZE_DEFAULT_MIGRATION_KEY = "dbx-export-batch-size-default-migrated-v1";
|
|
const LEGACY_DEFAULT_EXPORT_BATCH_SIZE = 10000;
|
|
const MIN_UI_SCALE = 0.75;
|
|
const MAX_UI_SCALE = 2;
|
|
|
|
function normalizeUiScale(value: unknown): number {
|
|
if (typeof value !== "number" || !Number.isFinite(value)) return DEFAULT_EDITOR_SETTINGS.uiScale;
|
|
return Math.min(MAX_UI_SCALE, Math.max(MIN_UI_SCALE, Math.round(value * 100) / 100));
|
|
}
|
|
|
|
function normalizeFontFamily(value: unknown, fallback: string): string {
|
|
if (typeof value !== "string") return fallback;
|
|
const trimmed = value.trim();
|
|
return trimmed || fallback;
|
|
}
|
|
|
|
function normalizeDrawerWidth(value: unknown, min: number, fallback: number): number {
|
|
if (typeof value !== "number" || !Number.isFinite(value)) return fallback;
|
|
return Math.min(900, Math.max(min, Math.round(value)));
|
|
}
|
|
|
|
function normalizeStructureEditorDensity(value: unknown): StructureEditorDensity {
|
|
return STRUCTURE_EDITOR_DENSITIES.includes(value as StructureEditorDensity) ? (value as StructureEditorDensity) : DEFAULT_EDITOR_SETTINGS.structureEditorDensity;
|
|
}
|
|
function normalizeColumnWidthDensity(value: unknown): ColumnWidthDensity {
|
|
return COLUMN_WIDTH_DENSITIES.includes(value as ColumnWidthDensity) ? (value as ColumnWidthDensity) : DEFAULT_EDITOR_SETTINGS.columnWidthDensity;
|
|
}
|
|
|
|
function normalizeTabLayout(value: unknown): TabLayoutMode {
|
|
return TAB_LAYOUT_MODES.includes(value as TabLayoutMode) ? (value as TabLayoutMode) : DEFAULT_EDITOR_SETTINGS.tabLayout;
|
|
}
|
|
|
|
function normalizeCellDetailPanelLayout(value: unknown): CellDetailPanelLayout {
|
|
return CELL_DETAIL_PANEL_LAYOUTS.includes(value as CellDetailPanelLayout) ? (value as CellDetailPanelLayout) : DEFAULT_EDITOR_SETTINGS.cellDetailPanelLayout;
|
|
}
|
|
|
|
function normalizeDataGridRenderMode(value: unknown): DataGridRenderMode {
|
|
return DATA_GRID_RENDER_MODES.includes(value as DataGridRenderMode) ? (value as DataGridRenderMode) : DEFAULT_EDITOR_SETTINGS.dataGridRenderMode;
|
|
}
|
|
|
|
function normalizeDataGridSearchMode(value: unknown): DataGridSearchMode {
|
|
return DATA_GRID_SEARCH_MODES.includes(value as DataGridSearchMode) ? (value as DataGridSearchMode) : DEFAULT_EDITOR_SETTINGS.dataGridSearchMode;
|
|
}
|
|
|
|
function normalizeResultRunDisplayMode(value: unknown): ResultRunDisplayMode {
|
|
return RESULT_RUN_DISPLAY_MODES.includes(value as ResultRunDisplayMode) ? (value as ResultRunDisplayMode) : DEFAULT_EDITOR_SETTINGS.resultRunDisplayMode;
|
|
}
|
|
|
|
function normalizeTableFontSize(value: unknown): number {
|
|
if (typeof value !== "number" || !Number.isFinite(value)) return TABLE_FONT_SIZE_DEFAULT;
|
|
return Math.min(TABLE_FONT_SIZE_MAX, Math.max(TABLE_FONT_SIZE_MIN, Math.round(value)));
|
|
}
|
|
|
|
function normalizeUpdateDownloadSource(value: unknown): UpdateDownloadSource {
|
|
// Preserve the intent of users who previously selected the mainland-China mirror.
|
|
if (value === "atomgit") return "cnb";
|
|
return value === "cnb" ? "cnb" : DEFAULT_EDITOR_SETTINGS.updateDownloadSource;
|
|
}
|
|
|
|
function normalizeSqlSemanticDiagnosticsMode(value: unknown, legacyEnabled?: unknown): SqlSemanticDiagnosticsMode {
|
|
if (value === "auto" || value === "enabled" || value === "disabled") return value;
|
|
if (typeof legacyEnabled === "boolean") return legacyEnabled ? "enabled" : "disabled";
|
|
return DEFAULT_EDITOR_SETTINGS.sqlSemanticDiagnosticsMode;
|
|
}
|
|
|
|
function sqlSemanticDiagnosticsEnabledForMode(mode: SqlSemanticDiagnosticsMode): boolean {
|
|
if (mode === "enabled") return true;
|
|
if (mode === "disabled") return false;
|
|
return SQL_SEMANTIC_DIAGNOSTICS_AUTO_ENABLED;
|
|
}
|
|
|
|
function normalizeDisconnectTabHandlingMode(value: unknown, legacyCloseTabsOnDisconnect?: unknown): DisconnectTabHandlingMode {
|
|
if (DISCONNECT_TAB_HANDLING_MODES.includes(value as DisconnectTabHandlingMode)) {
|
|
return value as DisconnectTabHandlingMode;
|
|
}
|
|
if (value === "clear-state") return "keep-tabs-clear-results";
|
|
if (value === "keep-tabs") return "keep-tabs-keep-results";
|
|
if (typeof legacyCloseTabsOnDisconnect === "boolean") {
|
|
return legacyCloseTabsOnDisconnect ? "close-tabs" : "keep-tabs-clear-results";
|
|
}
|
|
return DEFAULT_EDITOR_SETTINGS.disconnectTabHandlingMode;
|
|
}
|
|
|
|
function normalizeClickTableNavigationTarget(value: unknown): ClickTableNavigationTarget {
|
|
return value === "data" || value === "ddl" ? value : DEFAULT_EDITOR_SETTINGS.clickTableNavigationTarget;
|
|
}
|
|
|
|
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 normalizeConnectionListSortMode(value: unknown): ConnectionListSortMode {
|
|
return value === "asc" || value === "desc" ? value : "manual";
|
|
}
|
|
|
|
function normalizeSidebarObjectInfoMode(value: unknown, legacyCommentLayout?: unknown, legacyHideTableComments?: unknown, legacyShowDatabaseSizes?: unknown): SidebarObjectInfoMode {
|
|
if (value === "comment-inline" || value === "comment-aligned" || value === "comment-right" || value === "size" || value === "hidden") return value;
|
|
if (legacyCommentLayout === "hidden" || legacyHideTableComments === true) return "hidden";
|
|
if (legacyShowDatabaseSizes === true) return "size";
|
|
if (legacyCommentLayout === "aligned") return "comment-aligned";
|
|
return DEFAULT_EDITOR_SETTINGS.sidebarObjectInfoMode;
|
|
}
|
|
|
|
function normalizeColumnFormatters(value: unknown): Record<string, ColumnFormatterConfig> {
|
|
if (!value || typeof value !== "object" || Array.isArray(value)) return {};
|
|
const formatters: Record<string, ColumnFormatterConfig> = {};
|
|
for (const [key, formatter] of Object.entries(value as Record<string, unknown>)) {
|
|
const normalized = normalizeColumnFormatter(formatter);
|
|
if (normalized) formatters[key] = normalized;
|
|
}
|
|
return formatters;
|
|
}
|
|
|
|
function normalizeCustomColumnFormatters(value: unknown): Record<string, CustomColumnFormatterConfig> {
|
|
if (!value || typeof value !== "object" || Array.isArray(value)) return {};
|
|
const formatters: Record<string, CustomColumnFormatterConfig> = {};
|
|
for (const formatter of Object.values(value as Record<string, unknown>)) {
|
|
const normalized = normalizeCustomColumnFormatter(formatter);
|
|
if (normalized) formatters[normalized.id] = normalized;
|
|
}
|
|
return formatters;
|
|
}
|
|
|
|
function normalizeSqlSnippets(value: unknown, existing?: SqlSnippet[]): SqlSnippet[] {
|
|
if (!Array.isArray(value)) return existing ?? DEFAULT_SQL_SNIPPETS;
|
|
if (value.length === 0) return [];
|
|
const valid: SqlSnippet[] = [];
|
|
const seenPrefixes = new Set<string>();
|
|
for (const item of value) {
|
|
if (!item || typeof item !== "object" || typeof item.id !== "string" || !item.id || typeof item.label !== "string" || !item.label || typeof item.prefix !== "string" || !item.prefix || typeof item.body !== "string") {
|
|
continue;
|
|
}
|
|
if (seenPrefixes.has(item.prefix)) continue;
|
|
seenPrefixes.add(item.prefix);
|
|
// Older settings do not have this field; only an explicit false disables a snippet.
|
|
valid.push({
|
|
id: item.id,
|
|
label: item.label,
|
|
prefix: item.prefix,
|
|
body: item.body,
|
|
enabled: item.enabled !== false,
|
|
});
|
|
}
|
|
if (valid.length === 0) return existing ?? DEFAULT_SQL_SNIPPETS;
|
|
return valid;
|
|
}
|
|
|
|
function normalizeToolbarItems(items: Partial<ToolbarItems> | undefined): ToolbarItems {
|
|
const defaults = DEFAULT_TOOLBAR_ITEMS;
|
|
if (!items || typeof items !== "object") return { ...defaults };
|
|
return {
|
|
dataTransfer: items.dataTransfer ?? defaults.dataTransfer,
|
|
driverManager: items.driverManager ?? defaults.driverManager,
|
|
sqlFile: items.sqlFile ?? defaults.sqlFile,
|
|
schemaDiff: items.schemaDiff ?? defaults.schemaDiff,
|
|
dataCompare: items.dataCompare ?? defaults.dataCompare,
|
|
checkUpdates: items.checkUpdates ?? defaults.checkUpdates,
|
|
sqlLibrary: items.sqlLibrary ?? defaults.sqlLibrary,
|
|
sqlFileTree: items.sqlFileTree ?? defaults.sqlFileTree,
|
|
history: items.history ?? defaults.history,
|
|
ai: items.ai ?? defaults.ai,
|
|
theme: items.theme ?? defaults.theme,
|
|
github: items.github ?? defaults.github,
|
|
// Saved settings from before right-sidebar exclusivity must adopt the new default.
|
|
exclusiveRightSidebarPanels: items.exclusiveRightSidebarPanels !== false,
|
|
};
|
|
}
|
|
|
|
const TABLE_INFO_TABS = new Set<TableInfoTab>(["ddl", "columns", "indexes", "foreignKeys", "triggers"]);
|
|
|
|
function normalizeTableInfoTab(value: unknown): TableInfoTab {
|
|
return typeof value === "string" && TABLE_INFO_TABS.has(value as TableInfoTab) ? (value as TableInfoTab) : DEFAULT_EDITOR_SETTINGS.tableInfoActiveTab;
|
|
}
|
|
|
|
export function normalizeEditorSettings(settings: Partial<EditorSettings>, existing?: EditorSettings): EditorSettings {
|
|
const sqlSemanticDiagnosticsMode = normalizeSqlSemanticDiagnosticsMode(settings.sqlSemanticDiagnosticsMode, settings.sqlSemanticDiagnosticsEnabled);
|
|
const savedExecuteModeDefaultVersion = settings.executeModeDefaultVersion;
|
|
const executeModeDefaultVersion = typeof savedExecuteModeDefaultVersion === "number" && savedExecuteModeDefaultVersion >= EXECUTE_MODE_CURRENT_DEFAULT_VERSION ? savedExecuteModeDefaultVersion : EXECUTE_MODE_CURRENT_DEFAULT_VERSION;
|
|
const hasCurrentExecuteModeDefault = executeModeDefaultVersion === savedExecuteModeDefaultVersion;
|
|
return {
|
|
fontFamily: normalizeFontFamily(settings.fontFamily, DEFAULT_EDITOR_SETTINGS.fontFamily),
|
|
fontSize: settings.fontSize ?? DEFAULT_EDITOR_SETTINGS.fontSize,
|
|
uiFontFamily: normalizeFontFamily(settings.uiFontFamily, DEFAULT_EDITOR_SETTINGS.uiFontFamily),
|
|
uiScale: normalizeUiScale(settings.uiScale),
|
|
theme: settings.theme && EDITOR_THEME_VALUES.has(settings.theme) ? settings.theme : DEFAULT_EDITOR_SETTINGS.theme,
|
|
customThemeColors: {
|
|
...DEFAULT_CUSTOM_THEME_COLORS,
|
|
...settings.customThemeColors,
|
|
},
|
|
customThemes: (() => {
|
|
if (Array.isArray(settings.customThemes) && settings.customThemes.length > 0) {
|
|
return settings.customThemes.map((theme) => {
|
|
const renamed = theme.name === "默认" ? { ...theme, name: "Custom" } : { ...theme };
|
|
return {
|
|
...renamed,
|
|
colors: { ...DEFAULT_CUSTOM_THEME_COLORS, ...renamed.colors },
|
|
ddlColors: {
|
|
...DEFAULT_CUSTOM_THEME_DDL_COLORS,
|
|
...(renamed as any).ddlColors,
|
|
},
|
|
};
|
|
});
|
|
}
|
|
return settings.customThemeColors
|
|
? [
|
|
{
|
|
id: "migrated",
|
|
name: "Migrated",
|
|
colors: {
|
|
...DEFAULT_CUSTOM_THEME_COLORS,
|
|
...settings.customThemeColors,
|
|
},
|
|
ddlColors: { ...DEFAULT_CUSTOM_THEME_DDL_COLORS },
|
|
},
|
|
]
|
|
: [];
|
|
})(),
|
|
activeCustomThemeId: settings.activeCustomThemeId ?? "default",
|
|
executeMode: hasCurrentExecuteModeDefault && (settings.executeMode === "all" || settings.executeMode === "current") ? settings.executeMode : DEFAULT_EDITOR_SETTINGS.executeMode,
|
|
executeModeDefaultVersion,
|
|
showExecutionTargetPicker: settings.showExecutionTargetPicker ?? DEFAULT_EDITOR_SETTINGS.showExecutionTargetPicker,
|
|
showStatementRunButtons: typeof settings.showStatementRunButtons === "boolean" ? settings.showStatementRunButtons : DEFAULT_EDITOR_SETTINGS.showStatementRunButtons,
|
|
showCurrentStatementFrame: typeof settings.showCurrentStatementFrame === "boolean" ? settings.showCurrentStatementFrame : DEFAULT_EDITOR_SETTINGS.showCurrentStatementFrame,
|
|
showInsertValueHints: typeof settings.showInsertValueHints === "boolean" ? settings.showInsertValueHints : DEFAULT_EDITOR_SETTINGS.showInsertValueHints,
|
|
autoAliasTables: settings.autoAliasTables ?? DEFAULT_EDITOR_SETTINGS.autoAliasTables,
|
|
insertSpaceAfterCompletion: typeof settings.insertSpaceAfterCompletion === "boolean" ? settings.insertSpaceAfterCompletion : DEFAULT_EDITOR_SETTINGS.insertSpaceAfterCompletion,
|
|
wordWrap: settings.wordWrap ?? DEFAULT_EDITOR_SETTINGS.wordWrap,
|
|
vimModeEnabled: typeof settings.vimModeEnabled === "boolean" ? settings.vimModeEnabled : DEFAULT_EDITOR_SETTINGS.vimModeEnabled,
|
|
autoCloseBrackets: typeof settings.autoCloseBrackets === "boolean" ? settings.autoCloseBrackets : DEFAULT_EDITOR_SETTINGS.autoCloseBrackets,
|
|
sqlSemanticDiagnosticsMode,
|
|
sqlSemanticDiagnosticsEnabled: sqlSemanticDiagnosticsEnabledForMode(sqlSemanticDiagnosticsMode),
|
|
confirmDangerousSqlExecution: settings.confirmDangerousSqlExecution ?? DEFAULT_EDITOR_SETTINGS.confirmDangerousSqlExecution,
|
|
confirmUnsavedSqlClose: settings.confirmUnsavedSqlClose ?? DEFAULT_EDITOR_SETTINGS.confirmUnsavedSqlClose,
|
|
savedSqlOpenTargetMode: settings.savedSqlOpenTargetMode === "current" ? "current" : DEFAULT_EDITOR_SETTINGS.savedSqlOpenTargetMode,
|
|
compactTabTitle: settings.compactTabTitle ?? DEFAULT_EDITOR_SETTINGS.compactTabTitle,
|
|
tabLayout: normalizeTabLayout(settings.tabLayout),
|
|
appLayout: settings.appLayout ?? DEFAULT_EDITOR_SETTINGS.appLayout,
|
|
pageSize: normalizeResultPageSize(settings.pageSize),
|
|
tableOpenPageSize: normalizeResultPageSize(settings.tableOpenPageSize, DEFAULT_EDITOR_SETTINGS.tableOpenPageSize),
|
|
infiniteScroll: settings.infiniteScroll ?? DEFAULT_EDITOR_SETTINGS.infiniteScroll,
|
|
infiniteScrollMaxRows: typeof settings.infiniteScrollMaxRows === "number" && settings.infiniteScrollMaxRows >= 1000 && settings.infiniteScrollMaxRows <= 50000 ? Math.round(settings.infiniteScrollMaxRows) : DEFAULT_EDITOR_SETTINGS.infiniteScrollMaxRows,
|
|
regexMaxMatchCount: typeof settings.regexMaxMatchCount === "number" && Number.isFinite(settings.regexMaxMatchCount) && settings.regexMaxMatchCount >= 100 && settings.regexMaxMatchCount <= 10000 ? Math.round(settings.regexMaxMatchCount) : DEFAULT_EDITOR_SETTINGS.regexMaxMatchCount,
|
|
autoCalculateTotalRows: settings.autoCalculateTotalRows ?? DEFAULT_EDITOR_SETTINGS.autoCalculateTotalRows,
|
|
mongoViewMode: settings.mongoViewMode === "table" ? "table" : DEFAULT_EDITOR_SETTINGS.mongoViewMode,
|
|
showColumnCommentsInHeader: settings.showColumnCommentsInHeader ?? DEFAULT_EDITOR_SETTINGS.showColumnCommentsInHeader,
|
|
showColumnTypesInHeader: settings.showColumnTypesInHeader ?? DEFAULT_EDITOR_SETTINGS.showColumnTypesInHeader,
|
|
showIndexIndicatorsInHeader: settings.showIndexIndicatorsInHeader ?? DEFAULT_EDITOR_SETTINGS.showIndexIndicatorsInHeader,
|
|
compactColumnHeaderActions: settings.compactColumnHeaderActions ?? DEFAULT_EDITOR_SETTINGS.compactColumnHeaderActions,
|
|
columnWidthDensity: normalizeColumnWidthDensity(settings.columnWidthDensity),
|
|
dataGridQuickEntry: settings.dataGridQuickEntry ?? DEFAULT_EDITOR_SETTINGS.dataGridQuickEntry,
|
|
dataGridRenderMode: normalizeDataGridRenderMode(settings.dataGridRenderMode),
|
|
dataGridSearchMode: normalizeDataGridSearchMode(settings.dataGridSearchMode),
|
|
dataGridCopyExtractor: normalizeDataGridCopyPreference(settings.dataGridCopyExtractor),
|
|
dataGridExtractorOptions: normalizeDataGridExtractorOptions(settings.dataGridExtractorOptions),
|
|
resultRunDisplayMode: normalizeResultRunDisplayMode(settings.resultRunDisplayMode),
|
|
dataGridAutoTransposeSingleRow: settings.dataGridAutoTransposeSingleRow === true,
|
|
dataGridMultiRowTranspose: settings.dataGridMultiRowTranspose === true,
|
|
dataGridHideNullColumns: settings.dataGridHideNullColumns === true,
|
|
dataGridBooleanDisplayMode: settings.dataGridBooleanDisplayMode === "checkbox" ? "checkbox" : "dropdown",
|
|
numericColumnRightAlign: typeof settings.numericColumnRightAlign === "boolean" ? settings.numericColumnRightAlign : DEFAULT_EDITOR_SETTINGS.numericColumnRightAlign,
|
|
tableFontFamily: normalizeFontFamily(settings.tableFontFamily, DEFAULT_EDITOR_SETTINGS.tableFontFamily),
|
|
tableFontSize: normalizeTableFontSize(settings.tableFontSize),
|
|
structureEditorDensity: normalizeStructureEditorDensity(settings.structureEditorDensity),
|
|
tableInfoActiveTab: normalizeTableInfoTab(settings.tableInfoActiveTab),
|
|
tableInfoDrawerWidth: normalizeDrawerWidth(settings.tableInfoDrawerWidth, 240, DEFAULT_EDITOR_SETTINGS.tableInfoDrawerWidth),
|
|
cellDetailDrawerWidth: normalizeDrawerWidth(settings.cellDetailDrawerWidth, 260, DEFAULT_EDITOR_SETTINGS.cellDetailDrawerWidth),
|
|
cellDetailPanelLayout: normalizeCellDetailPanelLayout(settings.cellDetailPanelLayout),
|
|
cellDetailJsonFormatted: typeof settings.cellDetailJsonFormatted === "boolean" ? settings.cellDetailJsonFormatted : DEFAULT_EDITOR_SETTINGS.cellDetailJsonFormatted,
|
|
cellDetailMetadataCollapsed: typeof settings.cellDetailMetadataCollapsed === "boolean" ? settings.cellDetailMetadataCollapsed : DEFAULT_EDITOR_SETTINGS.cellDetailMetadataCollapsed,
|
|
shortcuts: normalizeShortcutSettings(settings.shortcuts),
|
|
sqlFormatter: normalizeSqlFormatterSettings(settings.sqlFormatter),
|
|
sidebarActivation: settings.sidebarActivation === "single" || settings.sidebarActivation === "double" ? settings.sidebarActivation : DEFAULT_EDITOR_SETTINGS.sidebarActivation,
|
|
sidebarConnectionSortMode: normalizeConnectionListSortMode(settings.sidebarConnectionSortMode),
|
|
sidebarObjectDisplay: settings.sidebarObjectDisplay === "simple" || settings.sidebarObjectDisplay === "grouped" ? settings.sidebarObjectDisplay : DEFAULT_EDITOR_SETTINGS.sidebarObjectDisplay,
|
|
routineSourceOpenMode: settings.routineSourceOpenMode === "query-tab" || settings.routineSourceOpenMode === "dialog" ? settings.routineSourceOpenMode : DEFAULT_EDITOR_SETTINGS.routineSourceOpenMode,
|
|
sidebarTableSearchEnabled: typeof settings.sidebarTableSearchEnabled === "boolean" ? settings.sidebarTableSearchEnabled : DEFAULT_EDITOR_SETTINGS.sidebarTableSearchEnabled,
|
|
sidebarTableSearchLocal: typeof settings.sidebarTableSearchLocal === "boolean" ? settings.sidebarTableSearchLocal : DEFAULT_EDITOR_SETTINGS.sidebarTableSearchLocal,
|
|
sidebarGlobalSearchLocal: typeof settings.sidebarGlobalSearchLocal === "boolean" ? settings.sidebarGlobalSearchLocal : DEFAULT_EDITOR_SETTINGS.sidebarGlobalSearchLocal,
|
|
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,
|
|
),
|
|
dataTabReuseMode: normalizeDataTabReuseMode(
|
|
settings.dataTabReuseMode,
|
|
(
|
|
settings as Partial<EditorSettings> & {
|
|
reuseDataTab?: boolean;
|
|
}
|
|
).reuseDataTab,
|
|
),
|
|
prefillNewQueryWithSelect: typeof settings.prefillNewQueryWithSelect === "boolean" ? settings.prefillNewQueryWithSelect : DEFAULT_EDITOR_SETTINGS.prefillNewQueryWithSelect,
|
|
updateNotificationsEnabled: settings.updateNotificationsEnabled ?? DEFAULT_EDITOR_SETTINGS.updateNotificationsEnabled,
|
|
sidebarHiddenTablePrefixes: normalizeSidebarHiddenTablePrefixes(settings.sidebarHiddenTablePrefixes),
|
|
sidebarObjectInfoMode: normalizeSidebarObjectInfoMode(
|
|
settings.sidebarObjectInfoMode,
|
|
(
|
|
settings as Partial<EditorSettings> & {
|
|
sidebarTableCommentLayout?: string;
|
|
}
|
|
).sidebarTableCommentLayout,
|
|
(
|
|
settings as Partial<EditorSettings> & {
|
|
sidebarHideTableComments?: boolean;
|
|
}
|
|
).sidebarHideTableComments,
|
|
(
|
|
settings as Partial<EditorSettings> & {
|
|
sidebarShowDatabaseSizes?: boolean;
|
|
}
|
|
).sidebarShowDatabaseSizes,
|
|
),
|
|
sidebarAllowHorizontalScroll: settings.sidebarAllowHorizontalScroll ?? DEFAULT_EDITOR_SETTINGS.sidebarAllowHorizontalScroll,
|
|
columnFormatters: normalizeColumnFormatters(settings.columnFormatters),
|
|
customColumnFormatters: normalizeCustomColumnFormatters(settings.customColumnFormatters),
|
|
globalDateTimeDisplayFormat: normalizeGlobalDateTimePattern(settings.globalDateTimeDisplayFormat),
|
|
globalDateTimeExportFormat: normalizeGlobalDateTimePattern(settings.globalDateTimeExportFormat),
|
|
globalDateTimeImportFormat: normalizeGlobalDateTimePattern(settings.globalDateTimeImportFormat),
|
|
snippets: normalizeSqlSnippets(settings.snippets, existing?.snippets),
|
|
tableColumnTemplateFields: normalizeTableColumnTemplateFields(settings.tableColumnTemplateFields),
|
|
exportBatchSize: typeof settings.exportBatchSize === "number" && settings.exportBatchSize >= 100 && settings.exportBatchSize <= 100000 ? Math.round(settings.exportBatchSize) : DEFAULT_EDITOR_SETTINGS.exportBatchSize,
|
|
exportRowLimitEnabled: typeof settings.exportRowLimitEnabled === "boolean" ? settings.exportRowLimitEnabled : DEFAULT_EDITOR_SETTINGS.exportRowLimitEnabled,
|
|
exportRowLimit: typeof settings.exportRowLimit === "number" && settings.exportRowLimit >= 100 && settings.exportRowLimit <= 2147483647 ? Math.round(settings.exportRowLimit) : DEFAULT_EDITOR_SETTINGS.exportRowLimit,
|
|
queryExportKeysetOptimizationEnabled: typeof settings.queryExportKeysetOptimizationEnabled === "boolean" ? settings.queryExportKeysetOptimizationEnabled : DEFAULT_EDITOR_SETTINGS.queryExportKeysetOptimizationEnabled,
|
|
updateDownloadSource: normalizeUpdateDownloadSource(settings.updateDownloadSource),
|
|
toolbarItems: normalizeToolbarItems(settings.toolbarItems),
|
|
objectBrowserShowCheckbox: typeof settings.objectBrowserShowCheckbox === "boolean" ? settings.objectBrowserShowCheckbox : DEFAULT_EDITOR_SETTINGS.objectBrowserShowCheckbox,
|
|
objectBrowserViewMode: settings.objectBrowserViewMode === "grid" ? "grid" : DEFAULT_EDITOR_SETTINGS.objectBrowserViewMode,
|
|
sqlVariableSyntaxOverrides: normalizeSqlVariableSyntaxOverrides(settings.sqlVariableSyntaxOverrides),
|
|
continueOnErrorOnBatch: settings.continueOnErrorOnBatch === true,
|
|
clickTableNavigationTarget: normalizeClickTableNavigationTarget(settings.clickTableNavigationTarget),
|
|
};
|
|
}
|
|
|
|
function loadLegacyEditorSettings(): EditorSettings | null {
|
|
const raw = safeLocalStorageGet(STORAGE_KEY);
|
|
if (raw) {
|
|
try {
|
|
const parsed = JSON.parse(raw) as Partial<EditorSettings>;
|
|
if (parsed.exportBatchSize === LEGACY_DEFAULT_EXPORT_BATCH_SIZE && safeLocalStorageGet(EXPORT_BATCH_SIZE_DEFAULT_MIGRATION_KEY) !== "1") {
|
|
parsed.exportBatchSize = DEFAULT_EDITOR_SETTINGS.exportBatchSize;
|
|
}
|
|
return normalizeEditorSettings(parsed);
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
const oldSize = safeLocalStorageGet(OLD_FONT_SIZE_KEY);
|
|
if (!oldSize) return null;
|
|
const parsed = parseInt(oldSize, 10);
|
|
return Number.isNaN(parsed) ? null : normalizeEditorSettings({ fontSize: parsed });
|
|
}
|
|
|
|
function clearLegacyEditorSettings() {
|
|
safeLocalStorageRemove(STORAGE_KEY);
|
|
safeLocalStorageRemove(OLD_FONT_SIZE_KEY);
|
|
safeLocalStorageRemove(EXPORT_BATCH_SIZE_DEFAULT_MIGRATION_KEY);
|
|
}
|
|
|
|
function editorSettingsSnapshot(settings: EditorSettings): EditorSettings {
|
|
return JSON.parse(JSON.stringify(settings)) as EditorSettings;
|
|
}
|
|
|
|
function saveEditorSettings(settings: EditorSettings) {
|
|
void api.saveEditorSettings(editorSettingsSnapshot(settings)).catch(() => {});
|
|
}
|
|
|
|
export interface SettingsNavigationRequest {
|
|
id: number;
|
|
tab: string;
|
|
section?: string;
|
|
}
|
|
|
|
export const useSettingsStore = defineStore("settings", () => {
|
|
const settingsPageActive = ref(false);
|
|
const settingsNavigationRequest = ref<SettingsNavigationRequest | null>(null);
|
|
const activeModel = ref<{ configId: string; modelId: string } | null>(null);
|
|
const effortPreferences = ref<AiModelEffortPreference[]>([]);
|
|
const isAiConfigLoaded = ref(false);
|
|
const aiConfigs = ref<AiConfigItem[]>([]);
|
|
const desktopSettings = ref<DesktopSettings>({ ...DEFAULT_DESKTOP_SETTINGS });
|
|
const mcpGlobalPolicy = ref<McpGlobalPolicy>({
|
|
...DEFAULT_MCP_GLOBAL_POLICY,
|
|
});
|
|
const isDesktopSettingsLoaded = ref(false);
|
|
const isMcpGlobalPolicyLoaded = ref(false);
|
|
const isEditorSettingsLoaded = ref(false);
|
|
let pendingAiChatSelection: AiChatSelectionState | null = null;
|
|
let aiChatSelectionSaveRunning = false;
|
|
|
|
const editorSettings = ref<EditorSettings>(normalizeEditorSettings({}));
|
|
|
|
function requestSettingsNavigation(tab: string, section?: string) {
|
|
settingsNavigationRequest.value = {
|
|
id: Date.now(),
|
|
tab,
|
|
section,
|
|
};
|
|
}
|
|
|
|
function clearSettingsNavigationRequest(id: number) {
|
|
if (settingsNavigationRequest.value?.id === id) settingsNavigationRequest.value = null;
|
|
}
|
|
|
|
async function initEditorSettings() {
|
|
if (isEditorSettingsLoaded.value) return;
|
|
const saved = await api.loadEditorSettings().catch(() => null);
|
|
if (saved && typeof saved === "object" && !Array.isArray(saved)) {
|
|
const savedSettings = saved as Partial<EditorSettings>;
|
|
const normalized = normalizeEditorSettings(savedSettings);
|
|
editorSettings.value = normalized;
|
|
const needsExecuteModeDefaultMigration = typeof savedSettings.executeModeDefaultVersion !== "number" || savedSettings.executeModeDefaultVersion < EXECUTE_MODE_CURRENT_DEFAULT_VERSION;
|
|
const savedUpdateDownloadSource = (saved as { updateDownloadSource?: unknown }).updateDownloadSource;
|
|
if (savedUpdateDownloadSource === "atomgit" || needsExecuteModeDefaultMigration) {
|
|
// Persist one-time migrations so removed or unsafe defaults cannot reappear.
|
|
await api.saveEditorSettings(normalized).catch(() => {});
|
|
}
|
|
isEditorSettingsLoaded.value = true;
|
|
return;
|
|
}
|
|
|
|
const legacy = loadLegacyEditorSettings();
|
|
if (legacy) {
|
|
editorSettings.value = legacy;
|
|
try {
|
|
await api.saveEditorSettings(legacy);
|
|
// Existing desktop users keep settings in localStorage; remove them only
|
|
// after the async store has accepted the migrated value.
|
|
clearLegacyEditorSettings();
|
|
} catch {
|
|
/* keep legacy values for a later migration attempt */
|
|
}
|
|
}
|
|
isEditorSettingsLoaded.value = true;
|
|
}
|
|
|
|
async function initDesktopSettings() {
|
|
if (isDesktopSettingsLoaded.value) return;
|
|
desktopSettings.value = normalizeDesktopSettings(await api.loadDesktopSettings().catch(() => null));
|
|
setDebugLoggingEnabled(desktopSettings.value.debug_logging_enabled);
|
|
isDesktopSettingsLoaded.value = true;
|
|
}
|
|
|
|
async function updateDesktopSettings(partial: Partial<DesktopSettings>) {
|
|
const previous = desktopSettings.value;
|
|
const next = {
|
|
...desktopSettings.value,
|
|
...partial,
|
|
};
|
|
desktopSettings.value = normalizeDesktopSettings(next);
|
|
setDebugLoggingEnabled(desktopSettings.value.debug_logging_enabled);
|
|
try {
|
|
await api.saveDesktopSettings(desktopSettings.value);
|
|
} catch (error) {
|
|
desktopSettings.value = previous;
|
|
setDebugLoggingEnabled(previous.debug_logging_enabled);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
async function initMcpGlobalPolicy(force = false) {
|
|
if (isMcpGlobalPolicyLoaded.value && !force) return;
|
|
mcpGlobalPolicy.value = normalizeMcpGlobalPolicy(await api.loadMcpGlobalPolicy());
|
|
isMcpGlobalPolicyLoaded.value = true;
|
|
}
|
|
|
|
async function updateMcpGlobalPolicy(partial: Partial<Omit<McpGlobalPolicy, "configured">>) {
|
|
const previous = mcpGlobalPolicy.value;
|
|
const next = normalizeMcpGlobalPolicy({
|
|
...previous,
|
|
...partial,
|
|
configured: true,
|
|
});
|
|
mcpGlobalPolicy.value = next;
|
|
try {
|
|
await api.saveMcpGlobalPolicy({
|
|
readOnly: next.readOnly,
|
|
allowDangerousSql: next.allowDangerousSql,
|
|
allowedConnectionIds: next.allowedConnectionIds,
|
|
});
|
|
} catch (error) {
|
|
mcpGlobalPolicy.value = previous;
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
async function initAiConfigs(): Promise<void> {
|
|
if (isAiConfigLoaded.value) return;
|
|
|
|
// 尝试加载新格式
|
|
const newConfigs = await api.loadAiConfigs();
|
|
|
|
if (newConfigs.length > 0) {
|
|
aiConfigs.value = newConfigs.map(normalizeAiConfigItem);
|
|
} else {
|
|
// 迁移旧格式
|
|
aiConfigs.value = [];
|
|
await migrateToMultiConfig();
|
|
}
|
|
|
|
const savedSelection = await api.loadAiChatSelection().catch(() => null);
|
|
effortPreferences.value = (savedSelection?.effortPreferences ?? []).filter((preference) => aiConfigs.value.some((config) => config.id === preference.configId));
|
|
|
|
const savedActive = savedSelection?.active;
|
|
const savedConfig = savedActive ? aiConfigs.value.find((config) => config.id === savedActive.configId) : undefined;
|
|
if (savedConfig && savedActive?.modelId.trim()) {
|
|
activeModel.value = {
|
|
configId: savedConfig.id,
|
|
modelId: savedActive.modelId.trim(),
|
|
};
|
|
} else {
|
|
const fallback = aiConfigs.value.find((config) => config.isDefault) || aiConfigs.value[0];
|
|
activeModel.value = fallback?.model.trim() ? { configId: fallback.id, modelId: fallback.model.trim() } : null;
|
|
if (activeModel.value) persistAiChatSelection();
|
|
}
|
|
|
|
isAiConfigLoaded.value = true;
|
|
}
|
|
|
|
async function reloadAiConfigs(): Promise<void> {
|
|
isAiConfigLoaded.value = false;
|
|
await initAiConfigs();
|
|
}
|
|
|
|
async function migrateToMultiConfig(): Promise<void> {
|
|
const oldActiveConfig = await api.loadAiConfig().catch(() => null);
|
|
const oldProviderConfigs = await api.loadAiProviderConfigs().catch(() => null);
|
|
|
|
if (!oldActiveConfig && (!oldProviderConfigs || Object.keys(oldProviderConfigs).length === 0)) {
|
|
return;
|
|
}
|
|
|
|
const newConfigs: AiConfigItem[] = [];
|
|
const seenKeys = new Set<string>();
|
|
|
|
if (oldActiveConfig) {
|
|
const item = aiConfigToItem(normalizeAiConfig(oldActiveConfig), generateId(), oldActiveConfig.provider);
|
|
item.isDefault = true;
|
|
newConfigs.push(item);
|
|
seenKeys.add(getConfigKey(oldActiveConfig));
|
|
}
|
|
|
|
if (oldProviderConfigs) {
|
|
for (const [provider, config] of Object.entries(oldProviderConfigs)) {
|
|
const key = getConfigKey(config);
|
|
if (!seenKeys.has(key)) {
|
|
const item = aiConfigToItem(normalizeAiConfig(config), generateId(), provider);
|
|
item.isDefault = false;
|
|
newConfigs.push(item);
|
|
seenKeys.add(key);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (newConfigs.length > 0) {
|
|
await api.saveAiConfigs(newConfigs);
|
|
aiConfigs.value = newConfigs;
|
|
}
|
|
}
|
|
|
|
async function createAiConfig(config: AiConfigItem): Promise<void> {
|
|
const normalized = normalizeAiConfigItem(config);
|
|
await api.saveAiConfigItem(normalized);
|
|
aiConfigs.value.push(normalized);
|
|
if (aiConfigs.value.length === 1 && normalized.model.trim()) {
|
|
activeModel.value = {
|
|
configId: normalized.id,
|
|
modelId: normalized.model,
|
|
};
|
|
persistAiChatSelection();
|
|
}
|
|
}
|
|
|
|
async function updateAiConfigItem(id: string, config: Partial<AiConfigItem>): Promise<void> {
|
|
const index = aiConfigs.value.findIndex((c) => c.id === id);
|
|
if (index !== -1) {
|
|
const previous = aiConfigs.value[index];
|
|
const updated = normalizeAiConfigItem({ ...previous, ...config });
|
|
await api.saveAiConfigItem(updated);
|
|
aiConfigs.value[index] = updated;
|
|
if (previous.provider !== updated.provider) {
|
|
effortPreferences.value = effortPreferences.value.filter((preference) => preference.configId !== id);
|
|
if (activeModel.value?.configId === id) activeModel.value = null;
|
|
persistAiChatSelection();
|
|
}
|
|
}
|
|
}
|
|
|
|
async function deleteAiConfig(id: string): Promise<void> {
|
|
await api.deleteAiConfig(id);
|
|
aiConfigs.value = aiConfigs.value.filter((c) => c.id !== id);
|
|
effortPreferences.value = effortPreferences.value.filter((preference) => preference.configId !== id);
|
|
if (activeModel.value?.configId === id) {
|
|
const fallback = aiConfigs.value.find((config) => config.isDefault) || aiConfigs.value[0];
|
|
activeModel.value = fallback?.model.trim() ? { configId: fallback.id, modelId: fallback.model.trim() } : null;
|
|
}
|
|
persistAiChatSelection();
|
|
}
|
|
|
|
async function setDefaultAiConfig(id: string): Promise<void> {
|
|
await api.setDefaultAiConfig(id);
|
|
aiConfigs.value.forEach((c) => {
|
|
c.isDefault = c.id === id;
|
|
});
|
|
}
|
|
|
|
function updateActiveModel(model: { configId: string; modelId: string }) {
|
|
activeModel.value = {
|
|
configId: model.configId,
|
|
modelId: model.modelId.trim(),
|
|
};
|
|
persistAiChatSelection();
|
|
}
|
|
|
|
const activeEffort = computed<AiEffortSelection | null>(() => {
|
|
const active = activeModel.value;
|
|
if (!active) return null;
|
|
return effortPreferences.value.find((preference) => preference.configId === active.configId && preference.modelId === active.modelId)?.selection ?? null;
|
|
});
|
|
|
|
function updateActiveEffort(selection: AiEffortSelection | null) {
|
|
const active = activeModel.value;
|
|
if (!active) return;
|
|
effortPreferences.value = effortPreferences.value.filter((preference) => preference.configId !== active.configId || preference.modelId !== active.modelId);
|
|
if (selection) {
|
|
effortPreferences.value.push({
|
|
configId: active.configId,
|
|
modelId: active.modelId,
|
|
selection,
|
|
});
|
|
}
|
|
persistAiChatSelection();
|
|
}
|
|
|
|
function persistAiChatSelection() {
|
|
pendingAiChatSelection = {
|
|
version: 1,
|
|
active: activeModel.value ? { ...activeModel.value } : undefined,
|
|
effortPreferences: effortPreferences.value.map((preference) => ({
|
|
...preference,
|
|
selection: { ...preference.selection },
|
|
})),
|
|
};
|
|
if (!aiChatSelectionSaveRunning) void flushAiChatSelection();
|
|
}
|
|
|
|
async function flushAiChatSelection() {
|
|
aiChatSelectionSaveRunning = true;
|
|
try {
|
|
while (pendingAiChatSelection) {
|
|
const selection = pendingAiChatSelection;
|
|
pendingAiChatSelection = null;
|
|
await api.saveAiChatSelection(selection).catch(() => {});
|
|
}
|
|
} finally {
|
|
aiChatSelectionSaveRunning = false;
|
|
if (pendingAiChatSelection) void flushAiChatSelection();
|
|
}
|
|
}
|
|
|
|
const isConfigured = computed((): boolean => {
|
|
if (!activeModel.value) return false;
|
|
const config = aiConfigs.value.find((c) => c.id === activeModel.value!.configId);
|
|
if (!config) return false;
|
|
const preset = AI_PROVIDER_PRESETS[config.provider];
|
|
if (config.provider === "codex-cli" || config.provider === "claude-code-cli" || config.provider === "pi-agent-cli" || config.provider === "opencode-cli" || config.provider === "cursor-cli") return true;
|
|
return !!config.endpoint && !!activeModel.value!.modelId && (!preset.requiresApiKey || !!config.apiKey);
|
|
});
|
|
|
|
function updateEditorSettings(partial: Partial<EditorSettings>) {
|
|
if (partial.fontFamily !== undefined) editorSettings.value.fontFamily = normalizeFontFamily(partial.fontFamily, DEFAULT_EDITOR_SETTINGS.fontFamily);
|
|
if (partial.fontSize !== undefined) editorSettings.value.fontSize = partial.fontSize;
|
|
if (partial.uiFontFamily !== undefined) editorSettings.value.uiFontFamily = normalizeFontFamily(partial.uiFontFamily, DEFAULT_EDITOR_SETTINGS.uiFontFamily);
|
|
if (partial.uiScale !== undefined) editorSettings.value.uiScale = normalizeUiScale(partial.uiScale);
|
|
if (partial.theme !== undefined) editorSettings.value.theme = partial.theme;
|
|
if (partial.customThemeColors !== undefined) {
|
|
editorSettings.value.customThemeColors = {
|
|
...editorSettings.value.customThemeColors,
|
|
...partial.customThemeColors,
|
|
};
|
|
}
|
|
if (partial.customThemes !== undefined) {
|
|
editorSettings.value.customThemes = Array.isArray(partial.customThemes) ? partial.customThemes : editorSettings.value.customThemes;
|
|
}
|
|
if (partial.activeCustomThemeId !== undefined) {
|
|
editorSettings.value.activeCustomThemeId = partial.activeCustomThemeId;
|
|
}
|
|
if (partial.customThemes !== undefined || partial.activeCustomThemeId !== undefined) {
|
|
const themes = editorSettings.value.customThemes;
|
|
const activeId = editorSettings.value.activeCustomThemeId;
|
|
const activeTheme = themes.find((t) => t.id === activeId) || themes[0];
|
|
if (activeTheme) {
|
|
editorSettings.value.customThemeColors = { ...activeTheme.colors };
|
|
}
|
|
}
|
|
if (partial.executeMode !== undefined) editorSettings.value.executeMode = partial.executeMode;
|
|
if (partial.showExecutionTargetPicker !== undefined) editorSettings.value.showExecutionTargetPicker = partial.showExecutionTargetPicker;
|
|
if (partial.showStatementRunButtons !== undefined) editorSettings.value.showStatementRunButtons = partial.showStatementRunButtons === true;
|
|
if (partial.showCurrentStatementFrame !== undefined) editorSettings.value.showCurrentStatementFrame = partial.showCurrentStatementFrame === true;
|
|
if (partial.showInsertValueHints !== undefined) editorSettings.value.showInsertValueHints = partial.showInsertValueHints === true;
|
|
if (partial.autoAliasTables !== undefined) editorSettings.value.autoAliasTables = partial.autoAliasTables;
|
|
if (partial.insertSpaceAfterCompletion !== undefined) editorSettings.value.insertSpaceAfterCompletion = partial.insertSpaceAfterCompletion === true;
|
|
if (partial.wordWrap !== undefined) editorSettings.value.wordWrap = partial.wordWrap;
|
|
if (partial.vimModeEnabled !== undefined) editorSettings.value.vimModeEnabled = partial.vimModeEnabled === true;
|
|
if (partial.autoCloseBrackets !== undefined) editorSettings.value.autoCloseBrackets = partial.autoCloseBrackets === true;
|
|
if (partial.sqlSemanticDiagnosticsMode !== undefined || partial.sqlSemanticDiagnosticsEnabled !== undefined) {
|
|
const nextMode = normalizeSqlSemanticDiagnosticsMode(partial.sqlSemanticDiagnosticsMode, partial.sqlSemanticDiagnosticsEnabled);
|
|
editorSettings.value.sqlSemanticDiagnosticsMode = nextMode;
|
|
editorSettings.value.sqlSemanticDiagnosticsEnabled = sqlSemanticDiagnosticsEnabledForMode(nextMode);
|
|
}
|
|
if (partial.confirmDangerousSqlExecution !== undefined) editorSettings.value.confirmDangerousSqlExecution = partial.confirmDangerousSqlExecution;
|
|
if (partial.confirmUnsavedSqlClose !== undefined) editorSettings.value.confirmUnsavedSqlClose = partial.confirmUnsavedSqlClose;
|
|
if (partial.savedSqlOpenTargetMode !== undefined) editorSettings.value.savedSqlOpenTargetMode = partial.savedSqlOpenTargetMode === "current" ? "current" : "saved";
|
|
if (partial.compactTabTitle !== undefined) editorSettings.value.compactTabTitle = partial.compactTabTitle;
|
|
if (partial.tabLayout !== undefined) editorSettings.value.tabLayout = normalizeTabLayout(partial.tabLayout);
|
|
if (partial.appLayout !== undefined) editorSettings.value.appLayout = partial.appLayout;
|
|
if (partial.pageSize !== undefined) editorSettings.value.pageSize = normalizeResultPageSize(partial.pageSize);
|
|
if (partial.tableOpenPageSize !== undefined) editorSettings.value.tableOpenPageSize = normalizeResultPageSize(partial.tableOpenPageSize, DEFAULT_EDITOR_SETTINGS.tableOpenPageSize);
|
|
if (partial.infiniteScroll !== undefined) editorSettings.value.infiniteScroll = partial.infiniteScroll;
|
|
if (partial.infiniteScrollMaxRows !== undefined)
|
|
editorSettings.value.infiniteScrollMaxRows = typeof partial.infiniteScrollMaxRows === "number" && partial.infiniteScrollMaxRows >= 1000 && partial.infiniteScrollMaxRows <= 50000 ? Math.round(partial.infiniteScrollMaxRows) : DEFAULT_EDITOR_SETTINGS.infiniteScrollMaxRows;
|
|
if (partial.regexMaxMatchCount !== undefined)
|
|
editorSettings.value.regexMaxMatchCount =
|
|
typeof partial.regexMaxMatchCount === "number" && Number.isFinite(partial.regexMaxMatchCount) && partial.regexMaxMatchCount >= 100 && partial.regexMaxMatchCount <= 10000 ? Math.round(partial.regexMaxMatchCount) : DEFAULT_EDITOR_SETTINGS.regexMaxMatchCount;
|
|
if (partial.autoCalculateTotalRows !== undefined) editorSettings.value.autoCalculateTotalRows = partial.autoCalculateTotalRows === true;
|
|
if (partial.mongoViewMode !== undefined) editorSettings.value.mongoViewMode = partial.mongoViewMode;
|
|
if (partial.showColumnCommentsInHeader !== undefined) editorSettings.value.showColumnCommentsInHeader = partial.showColumnCommentsInHeader;
|
|
if (partial.showColumnTypesInHeader !== undefined) editorSettings.value.showColumnTypesInHeader = partial.showColumnTypesInHeader;
|
|
if (partial.showIndexIndicatorsInHeader !== undefined) editorSettings.value.showIndexIndicatorsInHeader = partial.showIndexIndicatorsInHeader;
|
|
if (partial.compactColumnHeaderActions !== undefined) editorSettings.value.compactColumnHeaderActions = partial.compactColumnHeaderActions;
|
|
if (partial.columnWidthDensity !== undefined) editorSettings.value.columnWidthDensity = normalizeColumnWidthDensity(partial.columnWidthDensity);
|
|
if (partial.dataGridQuickEntry !== undefined) editorSettings.value.dataGridQuickEntry = partial.dataGridQuickEntry;
|
|
if (partial.dataGridRenderMode !== undefined) editorSettings.value.dataGridRenderMode = normalizeDataGridRenderMode(partial.dataGridRenderMode);
|
|
if (partial.dataGridSearchMode !== undefined) editorSettings.value.dataGridSearchMode = normalizeDataGridSearchMode(partial.dataGridSearchMode);
|
|
if (partial.dataGridCopyExtractor !== undefined) editorSettings.value.dataGridCopyExtractor = normalizeDataGridCopyPreference(partial.dataGridCopyExtractor);
|
|
if (partial.dataGridExtractorOptions !== undefined) editorSettings.value.dataGridExtractorOptions = normalizeDataGridExtractorOptions(partial.dataGridExtractorOptions);
|
|
if (partial.resultRunDisplayMode !== undefined) editorSettings.value.resultRunDisplayMode = normalizeResultRunDisplayMode(partial.resultRunDisplayMode);
|
|
if (partial.dataGridAutoTransposeSingleRow !== undefined) editorSettings.value.dataGridAutoTransposeSingleRow = partial.dataGridAutoTransposeSingleRow === true;
|
|
if (partial.dataGridMultiRowTranspose !== undefined) editorSettings.value.dataGridMultiRowTranspose = partial.dataGridMultiRowTranspose === true;
|
|
if (partial.dataGridHideNullColumns !== undefined) editorSettings.value.dataGridHideNullColumns = partial.dataGridHideNullColumns === true;
|
|
if (partial.dataGridBooleanDisplayMode !== undefined) editorSettings.value.dataGridBooleanDisplayMode = partial.dataGridBooleanDisplayMode === "dropdown" ? "dropdown" : "checkbox";
|
|
if (partial.numericColumnRightAlign !== undefined) editorSettings.value.numericColumnRightAlign = partial.numericColumnRightAlign === true;
|
|
if (partial.tableFontFamily !== undefined) editorSettings.value.tableFontFamily = normalizeFontFamily(partial.tableFontFamily, DEFAULT_EDITOR_SETTINGS.tableFontFamily);
|
|
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.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);
|
|
if (partial.cellDetailJsonFormatted !== undefined) editorSettings.value.cellDetailJsonFormatted = partial.cellDetailJsonFormatted === true;
|
|
if (partial.cellDetailMetadataCollapsed !== undefined) editorSettings.value.cellDetailMetadataCollapsed = partial.cellDetailMetadataCollapsed === true;
|
|
if (partial.shortcuts !== undefined) editorSettings.value.shortcuts = normalizeShortcutSettings(partial.shortcuts);
|
|
if (partial.sqlFormatter !== undefined) editorSettings.value.sqlFormatter = normalizeSqlFormatterSettings(partial.sqlFormatter);
|
|
if (partial.sidebarActivation !== undefined) editorSettings.value.sidebarActivation = partial.sidebarActivation;
|
|
if (partial.sidebarConnectionSortMode !== undefined) editorSettings.value.sidebarConnectionSortMode = normalizeConnectionListSortMode(partial.sidebarConnectionSortMode);
|
|
if (partial.sidebarObjectDisplay !== undefined) editorSettings.value.sidebarObjectDisplay = partial.sidebarObjectDisplay;
|
|
if (partial.routineSourceOpenMode !== undefined) editorSettings.value.routineSourceOpenMode = partial.routineSourceOpenMode;
|
|
if (partial.sidebarTableSearchEnabled !== undefined) editorSettings.value.sidebarTableSearchEnabled = partial.sidebarTableSearchEnabled;
|
|
if (partial.sidebarTableSearchLocal !== undefined) editorSettings.value.sidebarTableSearchLocal = partial.sidebarTableSearchLocal;
|
|
if (partial.sidebarGlobalSearchLocal !== undefined) editorSettings.value.sidebarGlobalSearchLocal = partial.sidebarGlobalSearchLocal;
|
|
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.dataTabReuseMode !== undefined) editorSettings.value.dataTabReuseMode = normalizeDataTabReuseMode(partial.dataTabReuseMode);
|
|
if (partial.prefillNewQueryWithSelect !== undefined) editorSettings.value.prefillNewQueryWithSelect = partial.prefillNewQueryWithSelect;
|
|
if (partial.updateNotificationsEnabled !== undefined) editorSettings.value.updateNotificationsEnabled = partial.updateNotificationsEnabled;
|
|
if (partial.sidebarHiddenTablePrefixes !== undefined) editorSettings.value.sidebarHiddenTablePrefixes = normalizeSidebarHiddenTablePrefixes(partial.sidebarHiddenTablePrefixes);
|
|
if (partial.sidebarObjectInfoMode !== undefined) editorSettings.value.sidebarObjectInfoMode = normalizeSidebarObjectInfoMode(partial.sidebarObjectInfoMode);
|
|
if (partial.sidebarAllowHorizontalScroll !== undefined) editorSettings.value.sidebarAllowHorizontalScroll = partial.sidebarAllowHorizontalScroll;
|
|
if (partial.columnFormatters !== undefined) editorSettings.value.columnFormatters = partial.columnFormatters;
|
|
if (partial.customColumnFormatters !== undefined) editorSettings.value.customColumnFormatters = partial.customColumnFormatters;
|
|
if (partial.globalDateTimeDisplayFormat !== undefined) editorSettings.value.globalDateTimeDisplayFormat = normalizeGlobalDateTimePattern(partial.globalDateTimeDisplayFormat);
|
|
if (partial.globalDateTimeExportFormat !== undefined) editorSettings.value.globalDateTimeExportFormat = normalizeGlobalDateTimePattern(partial.globalDateTimeExportFormat);
|
|
if (partial.globalDateTimeImportFormat !== undefined) editorSettings.value.globalDateTimeImportFormat = normalizeGlobalDateTimePattern(partial.globalDateTimeImportFormat);
|
|
if (partial.snippets !== undefined) editorSettings.value.snippets = normalizeSqlSnippets(partial.snippets);
|
|
if (partial.tableColumnTemplateFields !== undefined) editorSettings.value.tableColumnTemplateFields = normalizeTableColumnTemplateFields(partial.tableColumnTemplateFields);
|
|
if (partial.exportBatchSize !== undefined) editorSettings.value.exportBatchSize = Math.min(100000, Math.max(100, Math.round(partial.exportBatchSize)));
|
|
if (partial.exportRowLimitEnabled !== undefined) editorSettings.value.exportRowLimitEnabled = partial.exportRowLimitEnabled;
|
|
if (partial.exportRowLimit !== undefined) editorSettings.value.exportRowLimit = Math.min(2147483647, Math.max(100, Math.round(partial.exportRowLimit)));
|
|
if (partial.queryExportKeysetOptimizationEnabled !== undefined) editorSettings.value.queryExportKeysetOptimizationEnabled = partial.queryExportKeysetOptimizationEnabled;
|
|
if (partial.updateDownloadSource !== undefined) editorSettings.value.updateDownloadSource = normalizeUpdateDownloadSource(partial.updateDownloadSource);
|
|
if (partial.toolbarItems !== undefined) editorSettings.value.toolbarItems = normalizeToolbarItems(partial.toolbarItems);
|
|
if (partial.objectBrowserShowCheckbox !== undefined) editorSettings.value.objectBrowserShowCheckbox = partial.objectBrowserShowCheckbox === true;
|
|
if (partial.objectBrowserViewMode !== undefined) editorSettings.value.objectBrowserViewMode = partial.objectBrowserViewMode === "grid" ? "grid" : "list";
|
|
if (partial.sqlVariableSyntaxOverrides !== undefined) editorSettings.value.sqlVariableSyntaxOverrides = normalizeSqlVariableSyntaxOverrides(partial.sqlVariableSyntaxOverrides);
|
|
if (partial.continueOnErrorOnBatch !== undefined) editorSettings.value.continueOnErrorOnBatch = partial.continueOnErrorOnBatch === true;
|
|
if (partial.clickTableNavigationTarget !== undefined) editorSettings.value.clickTableNavigationTarget = normalizeClickTableNavigationTarget(partial.clickTableNavigationTarget);
|
|
saveEditorSettings(editorSettings.value);
|
|
}
|
|
|
|
async function persistEditorSettings(): Promise<void> {
|
|
await api.saveEditorSettings(editorSettingsSnapshot(editorSettings.value));
|
|
}
|
|
|
|
function updateColumnFormatter(key: string, formatter: ColumnFormatterConfig | undefined) {
|
|
const columnFormatters = { ...editorSettings.value.columnFormatters };
|
|
const normalized = normalizeColumnFormatter(formatter);
|
|
if (normalized) {
|
|
columnFormatters[key] = normalized;
|
|
} else {
|
|
delete columnFormatters[key];
|
|
}
|
|
updateEditorSettings({ columnFormatters });
|
|
}
|
|
|
|
function upsertCustomColumnFormatter(formatter: CustomColumnFormatterConfig): CustomColumnFormatterConfig | undefined {
|
|
const normalized = normalizeCustomColumnFormatter(formatter);
|
|
if (!normalized) return undefined;
|
|
updateEditorSettings({
|
|
customColumnFormatters: {
|
|
...editorSettings.value.customColumnFormatters,
|
|
[normalized.id]: normalized,
|
|
},
|
|
});
|
|
return normalized;
|
|
}
|
|
|
|
function deleteCustomColumnFormatter(id: string) {
|
|
const customColumnFormatters = {
|
|
...editorSettings.value.customColumnFormatters,
|
|
};
|
|
delete customColumnFormatters[id];
|
|
const columnFormatters = Object.fromEntries(
|
|
Object.entries(editorSettings.value.columnFormatters).filter(([, formatter]) => {
|
|
return formatter.kind !== "custom-ref" || formatter.formatterId !== id;
|
|
}),
|
|
);
|
|
updateEditorSettings({ customColumnFormatters, columnFormatters });
|
|
}
|
|
|
|
return {
|
|
settingsPageActive,
|
|
settingsNavigationRequest,
|
|
requestSettingsNavigation,
|
|
clearSettingsNavigationRequest,
|
|
activeModel,
|
|
activeEffort,
|
|
isAiConfigLoaded,
|
|
aiConfigs,
|
|
initAiConfigs,
|
|
reloadAiConfigs,
|
|
migrateToMultiConfig,
|
|
createAiConfig,
|
|
updateAiConfigItem,
|
|
deleteAiConfig,
|
|
setDefaultAiConfig,
|
|
updateActiveModel,
|
|
updateActiveEffort,
|
|
isConfigured,
|
|
isEditorSettingsLoaded,
|
|
editorSettings,
|
|
desktopSettings,
|
|
mcpGlobalPolicy,
|
|
initEditorSettings,
|
|
updateEditorSettings,
|
|
persistEditorSettings,
|
|
initDesktopSettings,
|
|
updateDesktopSettings,
|
|
initMcpGlobalPolicy,
|
|
updateMcpGlobalPolicy,
|
|
updateColumnFormatter,
|
|
upsertCustomColumnFormatter,
|
|
deleteCustomColumnFormatter,
|
|
};
|
|
});
|