feat(sidebar): add exclusive right panel setting
This commit is contained in:
parent
bbfbdf7ff5
commit
d4c28c5a9a
|
|
@ -14,7 +14,7 @@ import WelcomeScreen from "@/components/layout/WelcomeScreen.vue";
|
|||
import type { ConfigTab } from "@/components/connection/ConnectionDialog.vue";
|
||||
import { useConnectionStore } from "@/stores/connectionStore";
|
||||
import { useQueryStore } from "@/stores/queryStore";
|
||||
import { useSettingsStore } from "@/stores/settingsStore";
|
||||
import { enforceRightSidebarPanelExclusivity, RIGHT_SIDEBAR_PANEL_IDS, transitionRightSidebarPanels, useSettingsStore, type RightSidebarPanelId, type RightSidebarPanelState } from "@/stores/settingsStore";
|
||||
import { useSavedSqlStore } from "@/stores/savedSqlStore";
|
||||
import { useToast } from "@/composables/useToast";
|
||||
import { useTheme } from "@/composables/useTheme";
|
||||
|
|
@ -179,6 +179,18 @@ const showHistory = ref(false);
|
|||
const showAiPanel = ref(safeLocalStorageGet("dbx-ai-panel-open") === "true");
|
||||
const showSqlLibraryPanel = ref(safeLocalStorageGet("dbx-sql-library-open") === "true");
|
||||
const showSqlFilePanel = ref(safeLocalStorageGet("dbx-sql-file-panel-open") === "true");
|
||||
const rightSidebarPanelRefs: Record<RightSidebarPanelId, typeof showAiPanel> = {
|
||||
ai: showAiPanel,
|
||||
history: showHistory,
|
||||
sqlLibrary: showSqlLibraryPanel,
|
||||
sqlFile: showSqlFilePanel,
|
||||
};
|
||||
const rightSidebarPanelStorageKeys: Partial<Record<RightSidebarPanelId, string>> = {
|
||||
ai: "dbx-ai-panel-open",
|
||||
sqlLibrary: "dbx-sql-library-open",
|
||||
sqlFile: "dbx-sql-file-panel-open",
|
||||
};
|
||||
let lastOpenedRightSidebarPanel = RIGHT_SIDEBAR_PANEL_IDS.find((panelId) => rightSidebarPanelRefs[panelId].value);
|
||||
const sidebarOpen = ref(safeLocalStorageGet("dbx-sidebar-open") !== "false");
|
||||
const aiPanelReady = ref(false);
|
||||
const { sidebarWidth, aiPanelWidth, historyWidth, sqlLibraryWidth, sqlFilePanelWidth, startSidebarResize, startAiPanelResize, startHistoryResize, startSqlLibraryResize, startSqlFilePanelResize } = usePanelResize();
|
||||
|
|
@ -549,19 +561,50 @@ watch(
|
|||
{ immediate: true },
|
||||
);
|
||||
|
||||
function toggleAiPanel() {
|
||||
showAiPanel.value = !showAiPanel.value;
|
||||
safeLocalStorageSet("dbx-ai-panel-open", String(showAiPanel.value));
|
||||
watch(
|
||||
[() => settingsStore.isEditorSettingsLoaded, () => settingsStore.editorSettings.toolbarItems.exclusiveRightSidebarPanels],
|
||||
([loaded, exclusive]) => {
|
||||
if (!loaded || !exclusive) return;
|
||||
// Compatibility: old persisted panel flags may contain multiple open panels.
|
||||
applyRightSidebarPanelState(enforceRightSidebarPanelExclusivity(currentRightSidebarPanelState(), lastOpenedRightSidebarPanel));
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
|
||||
function currentRightSidebarPanelState(): RightSidebarPanelState {
|
||||
return Object.fromEntries(RIGHT_SIDEBAR_PANEL_IDS.map((panelId) => [panelId, rightSidebarPanelRefs[panelId].value])) as RightSidebarPanelState;
|
||||
}
|
||||
|
||||
function toggleSqlLibrary() {
|
||||
showSqlLibraryPanel.value = !showSqlLibraryPanel.value;
|
||||
safeLocalStorageSet("dbx-sql-library-open", String(showSqlLibraryPanel.value));
|
||||
function applyRightSidebarPanelState(next: RightSidebarPanelState) {
|
||||
for (const panelId of RIGHT_SIDEBAR_PANEL_IDS) {
|
||||
const panelRef = rightSidebarPanelRefs[panelId];
|
||||
if (panelRef.value === next[panelId]) continue;
|
||||
panelRef.value = next[panelId];
|
||||
const storageKey = rightSidebarPanelStorageKeys[panelId];
|
||||
if (storageKey) safeLocalStorageSet(storageKey, String(next[panelId]));
|
||||
}
|
||||
}
|
||||
|
||||
function toggleSqlFilePanel() {
|
||||
showSqlFilePanel.value = !showSqlFilePanel.value;
|
||||
safeLocalStorageSet("dbx-sql-file-panel-open", String(showSqlFilePanel.value));
|
||||
function setRightSidebarPanelOpen(panelId: RightSidebarPanelId, open: boolean) {
|
||||
const exclusive = settingsStore.isEditorSettingsLoaded && settingsStore.editorSettings.toolbarItems.exclusiveRightSidebarPanels;
|
||||
applyRightSidebarPanelState(transitionRightSidebarPanels(currentRightSidebarPanelState(), panelId, open, exclusive));
|
||||
if (open) {
|
||||
lastOpenedRightSidebarPanel = panelId;
|
||||
} else if (lastOpenedRightSidebarPanel === panelId) {
|
||||
lastOpenedRightSidebarPanel = RIGHT_SIDEBAR_PANEL_IDS.find((candidate) => rightSidebarPanelRefs[candidate].value);
|
||||
}
|
||||
}
|
||||
|
||||
function toggleRightSidebarPanel(panelId: RightSidebarPanelId) {
|
||||
setRightSidebarPanelOpen(panelId, !rightSidebarPanelRefs[panelId].value);
|
||||
}
|
||||
|
||||
function openRightSidebarPanel(panelId: RightSidebarPanelId) {
|
||||
setRightSidebarPanelOpen(panelId, true);
|
||||
}
|
||||
|
||||
function closeRightSidebarPanel(panelId: RightSidebarPanelId) {
|
||||
setRightSidebarPanelOpen(panelId, false);
|
||||
}
|
||||
|
||||
function invokeWhenAiReady(invoke: (handle: AiAssistantHandle) => void) {
|
||||
|
|
@ -580,26 +623,17 @@ function invokeWhenAiReady(invoke: (handle: AiAssistantHandle) => void) {
|
|||
}
|
||||
|
||||
function fixWithAi(errorMessage: string) {
|
||||
if (!showAiPanel.value) {
|
||||
showAiPanel.value = true;
|
||||
safeLocalStorageSet("dbx-ai-panel-open", "true");
|
||||
}
|
||||
openRightSidebarPanel("ai");
|
||||
invokeWhenAiReady((handle) => handle.triggerAction("fix", errorMessage));
|
||||
}
|
||||
|
||||
function sendSelectionToAi(sql: string) {
|
||||
if (!showAiPanel.value) {
|
||||
showAiPanel.value = true;
|
||||
safeLocalStorageSet("dbx-ai-panel-open", "true");
|
||||
}
|
||||
openRightSidebarPanel("ai");
|
||||
invokeWhenAiReady((handle) => handle.setPrompt(sql));
|
||||
}
|
||||
|
||||
function openAiPanel() {
|
||||
if (!showAiPanel.value) {
|
||||
showAiPanel.value = true;
|
||||
safeLocalStorageSet("dbx-ai-panel-open", "true");
|
||||
}
|
||||
openRightSidebarPanel("ai");
|
||||
}
|
||||
|
||||
function analyzeHistoryWithAi(entry: HistoryEntry) {
|
||||
|
|
@ -2035,10 +2069,10 @@ onUnmounted(() => {
|
|||
@new-connection="showConnectionDialog = true"
|
||||
@new-query="newQuery"
|
||||
@set-theme-mode="setThemeMode"
|
||||
@toggle-ai="toggleAiPanel"
|
||||
@toggle-history="showHistory = !showHistory"
|
||||
@toggle-sql-library="toggleSqlLibrary"
|
||||
@toggle-sql-file-panel="toggleSqlFilePanel"
|
||||
@toggle-ai="toggleRightSidebarPanel('ai')"
|
||||
@toggle-history="toggleRightSidebarPanel('history')"
|
||||
@toggle-sql-library="toggleRightSidebarPanel('sqlLibrary')"
|
||||
@toggle-sql-file-panel="toggleRightSidebarPanel('sqlFile')"
|
||||
@open-github="openGitHub"
|
||||
@open-settings="openSettings()"
|
||||
@open-driver-store="openDriverStorePage"
|
||||
|
|
@ -2210,7 +2244,7 @@ onUnmounted(() => {
|
|||
@open-saved-sql="openSavedSqlFromWelcome"
|
||||
@new-connection="showConnectionDialog = true"
|
||||
@new-query="newQuery"
|
||||
@show-history="showHistory = true"
|
||||
@show-history="openRightSidebarPanel('history')"
|
||||
@import-config="dialogs.onImportClick"
|
||||
@open-github="openGitHub"
|
||||
@open-mcp-guide="openMcpGuide"
|
||||
|
|
@ -2231,27 +2265,27 @@ onUnmounted(() => {
|
|||
@temp-run-sql="onAiTempRunSql"
|
||||
@request-auto-execute-sql="onAiRequestAutoExecuteSql"
|
||||
@open-explain-plan="onAiOpenExplainPlan"
|
||||
@close="toggleAiPanel"
|
||||
@close="closeRightSidebarPanel('ai')"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="showHistory" :class="isClassicLayout ? 'h-full shrink-0 relative z-30 isolate bg-background' : 'h-full shrink-0 relative z-30 isolate rounded-md border border-border/80 bg-background'" :style="{ width: historyWidth + 'px' }">
|
||||
<div class="panel-resize-handle panel-resize-handle--left" @mousedown="startHistoryResize" />
|
||||
<QueryHistory @restore="restoreHistorySql" @analyze-ai="analyzeHistoryWithAi" @close="showHistory = false" />
|
||||
<QueryHistory @restore="restoreHistorySql" @analyze-ai="analyzeHistoryWithAi" @close="closeRightSidebarPanel('history')" />
|
||||
</div>
|
||||
|
||||
<div v-if="showSqlLibraryPanel" :class="isClassicLayout ? 'h-full shrink-0 relative z-30 isolate bg-background' : 'h-full shrink-0 relative z-30 isolate rounded-md border border-border/80 bg-background'" :style="{ width: sqlLibraryWidth + 'px' }">
|
||||
<div class="panel-resize-handle panel-resize-handle--left" @mousedown="startSqlLibraryResize" />
|
||||
<div class="h-full min-h-0 overflow-hidden">
|
||||
<SqlLibraryPanel @close="toggleSqlLibrary" />
|
||||
<SqlLibraryPanel @close="closeRightSidebarPanel('sqlLibrary')" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="showSqlFilePanel" :class="isClassicLayout ? 'h-full shrink-0 relative z-30 isolate bg-background' : 'h-full shrink-0 relative z-30 isolate rounded-md border border-border/80 bg-background'" :style="{ width: sqlFilePanelWidth + 'px' }">
|
||||
<div class="panel-resize-handle panel-resize-handle--left" @mousedown="startSqlFilePanelResize" />
|
||||
<div class="h-full min-h-0 overflow-hidden">
|
||||
<SqlFilePanel @close="toggleSqlFilePanel" />
|
||||
<SqlFilePanel @close="closeRightSidebarPanel('sqlFile')" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -3678,6 +3678,13 @@ onUnmounted(cleanupPreviewEditor);
|
|||
<p>{{ t("settings.toolbarHiddenHint") }}</p>
|
||||
</HelpTooltip>
|
||||
</div>
|
||||
<div class="flex items-center justify-between gap-4 rounded-md border border-border/60 p-3">
|
||||
<div class="space-y-1">
|
||||
<Label for="exclusive-right-sidebar-panels" class="text-sm cursor-pointer">{{ t("settings.exclusiveRightSidebarPanels") }}</Label>
|
||||
<p class="text-xs text-muted-foreground">{{ t("settings.exclusiveRightSidebarPanelsDescription") }}</p>
|
||||
</div>
|
||||
<Switch id="exclusive-right-sidebar-panels" v-model="editToolbarItems.exclusiveRightSidebarPanels" />
|
||||
</div>
|
||||
<div class="grid grid-cols-3 gap-2 mt-2">
|
||||
<div
|
||||
v-for="item in [
|
||||
|
|
|
|||
|
|
@ -3529,6 +3529,8 @@ export default {
|
|||
fontSize: "Font Size",
|
||||
toolbarTitle: "Toolbar",
|
||||
toolbarHiddenHint: 'Some hidden or overflowed buttons appear in the "More" dropdown.',
|
||||
exclusiveRightSidebarPanels: "Show one right sidebar panel at a time",
|
||||
exclusiveRightSidebarPanelsDescription: "Opening a right sidebar panel closes the others. Disable this to allow multiple panels.",
|
||||
uiScale: "UI Scale",
|
||||
uiScaleDescription: "Scale the entire desktop UI for high-DPI displays. Changes apply immediately and are restored on next launch.",
|
||||
theme: "Theme",
|
||||
|
|
|
|||
|
|
@ -3307,6 +3307,8 @@ export default withEnglishFallback({
|
|||
fontSize: "Tamaño de fuente",
|
||||
toolbarTitle: "Barra de herramientas",
|
||||
toolbarHiddenHint: "Algunos botones ocultos o desbordados aparecerán en el menú «Más».",
|
||||
exclusiveRightSidebarPanels: "Mostrar un panel lateral derecho a la vez",
|
||||
exclusiveRightSidebarPanelsDescription: "Al abrir un panel lateral derecho se cierran los demás. Desactívalo para permitir varios paneles.",
|
||||
uiScale: "Escala de interfaz",
|
||||
uiScaleDescription: "Escala toda la interfaz de escritorio para pantallas de alta densidad. Los cambios se aplican al instante y se restauran al volver a abrir.",
|
||||
theme: "Tema",
|
||||
|
|
|
|||
|
|
@ -3305,6 +3305,8 @@ export default withEnglishFallback({
|
|||
fontSize: "Dimensione Carattere",
|
||||
toolbarTitle: "Barra degli strumenti",
|
||||
toolbarHiddenHint: "Alcuni pulsanti nascosti o in eccesso appariranno nel menu «Altro».",
|
||||
exclusiveRightSidebarPanels: "Mostra un pannello laterale destro alla volta",
|
||||
exclusiveRightSidebarPanelsDescription: "L'apertura di un pannello laterale destro chiude gli altri. Disattiva per consentire più pannelli.",
|
||||
uiScale: "Scala UI",
|
||||
uiScaleDescription: "Scala l'intera interfaccia utente desktop per display ad alta densità (High-DPI). Le modifiche si applicano immediatamente e vengono ripristinate al prossimo avvio.",
|
||||
theme: "Tema",
|
||||
|
|
|
|||
|
|
@ -3306,6 +3306,8 @@ export default withEnglishFallback({
|
|||
fontSize: "フォントサイズ",
|
||||
toolbarTitle: "ツールバー",
|
||||
toolbarHiddenHint: "非表示または幅が足りない一部のボタンは、「もっと見る」ドロップダウン内に表示されます。",
|
||||
exclusiveRightSidebarPanels: "右サイドバーのパネルを一度に1つだけ表示",
|
||||
exclusiveRightSidebarPanelsDescription: "右サイドバーのパネルを開くと他のパネルを閉じます。複数表示するには無効にします。",
|
||||
uiScale: "UIスケール",
|
||||
uiScaleDescription: "高DPIディスプレイ向けにデスクトップUI全体をスケーリングします。変更は即座に適用され、次回起動時に復元されます。",
|
||||
theme: "テーマ",
|
||||
|
|
|
|||
|
|
@ -3307,6 +3307,8 @@ export default withEnglishFallback({
|
|||
fontSize: "Tamanho da fonte",
|
||||
toolbarTitle: "Barra de ferramentas",
|
||||
toolbarHiddenHint: "Alguns botões ocultos ou excedentes aparecerão no menu «Mais».",
|
||||
exclusiveRightSidebarPanels: "Mostrar um painel lateral direito por vez",
|
||||
exclusiveRightSidebarPanelsDescription: "Abrir um painel lateral direito fecha os demais. Desative para permitir vários painéis.",
|
||||
uiScale: "Escala da UI",
|
||||
uiScaleDescription: "Dimensione toda a UI do desktop para telas de alta resolução (high-DPI). As alterações são aplicadas imediatamente e restauradas na próxima inicialização.",
|
||||
theme: "Tema",
|
||||
|
|
|
|||
|
|
@ -3519,6 +3519,8 @@ export default withEnglishFallback({
|
|||
fontSize: "字号",
|
||||
toolbarTitle: "工具栏",
|
||||
toolbarHiddenHint: '部分关闭或空间不足的按钮会自动收进"更多"下拉菜单。',
|
||||
exclusiveRightSidebarPanels: "右侧边栏一次仅显示一个面板",
|
||||
exclusiveRightSidebarPanelsDescription: "打开一个右侧面板时关闭其他面板。关闭此选项可同时显示多个面板。",
|
||||
uiScale: "界面缩放",
|
||||
uiScaleDescription: "按比例缩放整个桌面端界面,适合高清屏;修改后立即生效,并在下次启动时恢复。",
|
||||
theme: "主题",
|
||||
|
|
|
|||
|
|
@ -3123,6 +3123,8 @@ export default withEnglishFallback({
|
|||
fontSize: "字級",
|
||||
toolbarTitle: "工具列",
|
||||
toolbarHiddenHint: "部分關閉或空間不足的按鈕會自動收進「更多」下拉選單。",
|
||||
exclusiveRightSidebarPanels: "右側邊欄一次僅顯示一個面板",
|
||||
exclusiveRightSidebarPanelsDescription: "開啟一個右側面板時關閉其他面板。關閉此選項可同時顯示多個面板。",
|
||||
uiScale: "介面縮放",
|
||||
uiScaleDescription: "按比例縮放整個桌面端介面,適合高 DPI 螢幕;修改後立即生效,並在下次啟動時復原。",
|
||||
theme: "主題",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,52 @@
|
|||
import { readFileSync } from "node:fs";
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
const appSource = readFileSync(new URL("../../../App.vue", import.meta.url), "utf8");
|
||||
const toolbarSource = readFileSync(new URL("../../../components/layout/AppToolbar.vue", import.meta.url), "utf8");
|
||||
|
||||
function functionSource(name: string, nextName: string): string {
|
||||
const start = appSource.indexOf(`function ${name}`);
|
||||
const end = appSource.indexOf(`function ${nextName}`, start + 1);
|
||||
return appSource.slice(start, end);
|
||||
}
|
||||
|
||||
describe("right sidebar panel entry points", () => {
|
||||
it("routes toolbar and close actions through the centralized controller", () => {
|
||||
expect(appSource).toContain("@toggle-ai=\"toggleRightSidebarPanel('ai')\"");
|
||||
expect(appSource).toContain("@toggle-history=\"toggleRightSidebarPanel('history')\"");
|
||||
expect(appSource).toContain("@toggle-sql-library=\"toggleRightSidebarPanel('sqlLibrary')\"");
|
||||
expect(appSource).toContain("@toggle-sql-file-panel=\"toggleRightSidebarPanel('sqlFile')\"");
|
||||
expect(appSource).toContain("@close=\"closeRightSidebarPanel('history')\"");
|
||||
expect(appSource).toContain("@close=\"closeRightSidebarPanel('sqlLibrary')\"");
|
||||
expect(appSource).toContain("@close=\"closeRightSidebarPanel('sqlFile')\"");
|
||||
});
|
||||
|
||||
it("routes welcome, history analysis, selection, and error-fix opens through the same controller", () => {
|
||||
expect(appSource).toContain("@show-history=\"openRightSidebarPanel('history')\"");
|
||||
expect(functionSource("fixWithAi", "sendSelectionToAi")).toContain('openRightSidebarPanel("ai")');
|
||||
expect(functionSource("sendSelectionToAi", "openAiPanel")).toContain('openRightSidebarPanel("ai")');
|
||||
expect(functionSource("openAiPanel", "analyzeHistoryWithAi")).toContain('openRightSidebarPanel("ai")');
|
||||
});
|
||||
|
||||
it("keeps existing persisted panel keys and synchronizes exclusivity after settings load", () => {
|
||||
expect(appSource).toContain('ai: "dbx-ai-panel-open"');
|
||||
expect(appSource).toContain('sqlLibrary: "dbx-sql-library-open"');
|
||||
expect(appSource).toContain('sqlFile: "dbx-sql-file-panel-open"');
|
||||
expect(appSource).not.toContain('history: "dbx-');
|
||||
expect(appSource).toContain("settingsStore.isEditorSettingsLoaded");
|
||||
expect(appSource).toContain("enforceRightSidebarPanelExclusivity(currentRightSidebarPanelState(), lastOpenedRightSidebarPanel)");
|
||||
});
|
||||
|
||||
it("does not couple toolbar visibility to panel closing", () => {
|
||||
for (const [setting, event] of [
|
||||
["sqlLibrary", "toggle-sql-library"],
|
||||
["sqlFileTree", "toggle-sql-file-panel"],
|
||||
["history", "toggle-history"],
|
||||
["ai", "toggle-ai"],
|
||||
]) {
|
||||
expect(toolbarSource).toContain(`<Tooltip v-if="toolbarItems.${setting}">`);
|
||||
expect(toolbarSource).toContain(`@click="emit('${event}')"`);
|
||||
}
|
||||
expect(appSource).not.toMatch(/watch\([\s\S]{0,180}toolbarItems\.(ai|history|sqlLibrary|sqlFileTree)[\s\S]{0,180}closeRightSidebarPanel/);
|
||||
});
|
||||
});
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { EXECUTE_MODE_CURRENT_DEFAULT_VERSION, normalizeDesktopSettings, normalizeEditorSettings, normalizeMcpGlobalPolicy } from "@/stores/settingsStore";
|
||||
import { enforceRightSidebarPanelExclusivity, EXECUTE_MODE_CURRENT_DEFAULT_VERSION, normalizeDesktopSettings, normalizeEditorSettings, normalizeMcpGlobalPolicy, transitionRightSidebarPanels, type RightSidebarPanelState } from "@/stores/settingsStore";
|
||||
import { createPinia, setActivePinia } from "pinia";
|
||||
import type { AiConfigItem } from "@/types/ai";
|
||||
|
||||
|
|
@ -124,6 +124,41 @@ describe("normalizeEditorSettings", () => {
|
|||
expect(settings.toolbarItems.sqlFileTree).toBe(false);
|
||||
expect(settings.toolbarItems.history).toBe(false);
|
||||
expect(settings.toolbarItems.sqlLibrary).toBe(true);
|
||||
expect(settings.toolbarItems.exclusiveRightSidebarPanels).toBe(true);
|
||||
});
|
||||
|
||||
it("preserves disabled right sidebar panel exclusivity", () => {
|
||||
expect(
|
||||
normalizeEditorSettings({
|
||||
toolbarItems: {
|
||||
exclusiveRightSidebarPanels: false,
|
||||
} as any,
|
||||
}).toolbarItems.exclusiveRightSidebarPanels,
|
||||
).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("right sidebar panel transitions", () => {
|
||||
const state = (overrides: Partial<RightSidebarPanelState> = {}): RightSidebarPanelState => ({
|
||||
ai: false,
|
||||
history: false,
|
||||
sqlLibrary: false,
|
||||
sqlFile: false,
|
||||
...overrides,
|
||||
});
|
||||
|
||||
it("allows multiple panels when exclusivity is disabled", () => {
|
||||
expect(transitionRightSidebarPanels(state({ ai: true }), "history", true, false)).toEqual(state({ ai: true, history: true }));
|
||||
});
|
||||
|
||||
it("switches panels and allows the active panel to toggle closed", () => {
|
||||
const switched = transitionRightSidebarPanels(state({ ai: true }), "sqlLibrary", true, true);
|
||||
expect(switched).toEqual(state({ sqlLibrary: true }));
|
||||
expect(transitionRightSidebarPanels(switched, "sqlLibrary", false, true)).toEqual(state());
|
||||
});
|
||||
|
||||
it("collapses synchronized multi-panel state to the preferred open panel", () => {
|
||||
expect(enforceRightSidebarPanelExclusivity(state({ ai: true, history: true, sqlFile: true }), "history")).toEqual(state({ history: true }));
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -471,6 +471,7 @@ export interface ToolbarItems {
|
|||
ai: boolean;
|
||||
theme: boolean;
|
||||
github: boolean;
|
||||
exclusiveRightSidebarPanels: boolean;
|
||||
}
|
||||
|
||||
export const DEFAULT_TOOLBAR_ITEMS: ToolbarItems = {
|
||||
|
|
@ -486,8 +487,28 @@ export const DEFAULT_TOOLBAR_ITEMS: ToolbarItems = {
|
|||
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 },
|
||||
|
|
@ -752,6 +773,8 @@ function normalizeToolbarItems(items: Partial<ToolbarItems> | undefined): Toolba
|
|||
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,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue