feat(settings): add sidebar activation preference

This commit is contained in:
t8y2
2026-05-15 18:09:24 +08:00
parent 6db2430994
commit d124efbb20
10 changed files with 171 additions and 11 deletions
+66 -7
View File
@@ -53,6 +53,7 @@ const editWordWrap = ref(settingsStore.editorSettings.wordWrap);
const editAppLayout = ref(settingsStore.editorSettings.appLayout);
const editRedisScanPageSize = ref(settingsStore.editorSettings.redisScanPageSize);
const editShortcuts = ref(normalizeShortcutSettings(settingsStore.editorSettings.shortcuts));
const editSidebarActivation = ref(settingsStore.editorSettings.sidebarActivation);
const redisScanPageSizeOptions = [200, 1000, 5000, 10000];
// Sync from store when dialog opens
@@ -68,6 +69,7 @@ watch(
editAppLayout.value = settingsStore.editorSettings.appLayout;
editRedisScanPageSize.value = settingsStore.editorSettings.redisScanPageSize;
editShortcuts.value = normalizeShortcutSettings(settingsStore.editorSettings.shortcuts);
editSidebarActivation.value = settingsStore.editorSettings.sidebarActivation;
}
},
);
@@ -89,7 +91,8 @@ function hasChanges(): boolean {
editWordWrap.value !== settingsStore.editorSettings.wordWrap ||
editAppLayout.value !== settingsStore.editorSettings.appLayout ||
editRedisScanPageSize.value !== settingsStore.editorSettings.redisScanPageSize ||
JSON.stringify(editShortcuts.value) !== JSON.stringify(settingsStore.editorSettings.shortcuts)
JSON.stringify(editShortcuts.value) !== JSON.stringify(settingsStore.editorSettings.shortcuts) ||
editSidebarActivation.value !== settingsStore.editorSettings.sidebarActivation
);
}
@@ -104,6 +107,7 @@ function applySettings() {
appLayout: editAppLayout.value,
redisScanPageSize: editRedisScanPageSize.value,
shortcuts: editShortcuts.value,
sidebarActivation: editSidebarActivation.value,
});
emit("update:open", false);
}
@@ -117,6 +121,7 @@ function resetDefaults() {
editAppLayout.value = DEFAULT_EDITOR_SETTINGS.appLayout;
editRedisScanPageSize.value = DEFAULT_EDITOR_SETTINGS.redisScanPageSize;
editShortcuts.value = normalizeShortcutSettings(DEFAULT_EDITOR_SETTINGS.shortcuts);
editSidebarActivation.value = DEFAULT_EDITOR_SETTINGS.sidebarActivation;
}
function onExecuteModeChange(v: any) {
@@ -161,13 +166,18 @@ function setAppLayout(value: "separated" | "classic") {
editAppLayout.value = value;
}
function setSidebarActivation(value: "single" | "double") {
editSidebarActivation.value = value;
}
const activeSettingsTab = ref("editor");
const isWeb = !isTauriRuntime();
const displayedAppVersion = computed(() => (props.appVersion ? `v${props.appVersion}` : ""));
type SettingsCategory = "editor" | "appearance" | "redis" | "shortcuts" | "ai" | "security" | "about";
type SettingsCategory = "editor" | "appearance" | "navigation" | "redis" | "shortcuts" | "ai" | "security" | "about";
const settingsCategoryNav = computed<{ value: SettingsCategory; label: string }[]>(() => [
{ value: "editor", label: t("settings.editorTab") },
{ value: "appearance", label: t("settings.appearanceTab") },
{ value: "navigation", label: t("settings.navigationTab") },
{ value: "redis", label: t("settings.redisTab") },
{ value: "shortcuts", label: t("settings.shortcutsTab") },
{ value: "ai", label: t("settings.aiTab") },
@@ -458,7 +468,7 @@ watch(
</button>
</nav>
<div class="min-w-0 flex-1 overflow-y-auto overflow-x-hidden pr-1">
<div class="min-w-0 flex-1 overflow-y-auto overflow-x-hidden px-1">
<section v-if="activeSettingsTab === 'editor'" class="flex min-h-full flex-col gap-5 py-2">
<div class="grid gap-4 md:grid-cols-[minmax(0,1fr)_220px]">
<!-- Font Family -->
@@ -592,8 +602,8 @@ watch(
<Button
type="button"
variant="outline"
class="h-auto justify-start p-3"
:class="editAppLayout === 'separated' ? 'border-blue-300 border-2 ring-2 ring-blue-300/50' : ''"
class="h-auto justify-start border p-3"
:class="editAppLayout === 'separated' ? 'border-blue-300 ring-2 ring-blue-300/50' : ''"
@click="setAppLayout('separated')"
>
<div class="text-left">
@@ -604,8 +614,8 @@ watch(
<Button
type="button"
variant="outline"
class="h-auto justify-start p-3"
:class="editAppLayout === 'classic' ? 'border-blue-300 border-2 ring-2 ring-blue-300/50' : ''"
class="h-auto justify-start border p-3"
:class="editAppLayout === 'classic' ? 'border-blue-300 ring-2 ring-blue-300/50' : ''"
@click="setAppLayout('classic')"
>
<div class="text-left">
@@ -630,6 +640,55 @@ watch(
</DialogFooter>
</section>
<section v-else-if="activeSettingsTab === 'navigation'" class="flex min-h-full flex-col gap-5 py-2">
<div class="space-y-2">
<Label>{{ t("settings.sidebarActivation") }}</Label>
<div class="grid grid-cols-2 gap-2">
<Button
type="button"
variant="outline"
class="h-auto justify-start border p-3"
:class="editSidebarActivation === 'single' ? 'border-blue-300 ring-2 ring-blue-300/50' : ''"
@click="setSidebarActivation('single')"
>
<div class="text-left">
<div class="text-sm font-medium">{{ t("settings.sidebarActivationSingle") }}</div>
<div class="text-xs text-muted-foreground">
{{ t("settings.sidebarActivationSingleDescription") }}
</div>
</div>
</Button>
<Button
type="button"
variant="outline"
class="h-auto justify-start border p-3"
:class="editSidebarActivation === 'double' ? 'border-blue-300 ring-2 ring-blue-300/50' : ''"
@click="setSidebarActivation('double')"
>
<div class="text-left">
<div class="text-sm font-medium">{{ t("settings.sidebarActivationDouble") }}</div>
<div class="text-xs text-muted-foreground">
{{ t("settings.sidebarActivationDoubleDescription") }}
</div>
</div>
</Button>
</div>
</div>
<DialogFooter class="mt-auto border-t-0 bg-transparent gap-3 sm:gap-3">
<Button variant="outline" @click="resetDefaults">
{{ t("settings.resetDefaults") }}
</Button>
<div class="flex-1" />
<Button variant="outline" @click="emit('update:open', false)">
{{ t("common.close") }}
</Button>
<Button :disabled="!hasChanges() || hasShortcutConflicts" @click="applySettings">
{{ t("settings.apply") }}
</Button>
</DialogFooter>
</section>
<section v-else-if="activeSettingsTab === 'redis'" class="flex min-h-full flex-col gap-5 py-2">
<div class="space-y-2">
<Label>{{ t("settings.redisScanPageSize") }}</Label>
+18 -2
View File
@@ -57,6 +57,7 @@ import {
import { useConnectionStore } from "@/stores/connectionStore";
import { useQueryStore } from "@/stores/queryStore";
import { useSavedSqlStore } from "@/stores/savedSqlStore";
import { useSettingsStore } from "@/stores/settingsStore";
import { useToast } from "@/composables/useToast";
import { useDatabaseOptions } from "@/composables/useDatabaseOptions";
import type { DatabaseType, TreeNode, TreeNodeType } from "@/types/database";
@@ -108,6 +109,7 @@ const isTruncated = computed(() => {
const connectionStore = useConnectionStore();
const queryStore = useQueryStore();
const savedSqlStore = useSavedSqlStore();
const settingsStore = useSettingsStore();
const { toast } = useToast();
const { getDatabaseOptions } = useDatabaseOptions();
const showVisibleDatabasesDialog = ref(false);
@@ -322,7 +324,7 @@ function runRowClickAction() {
void openObjectBrowser();
return;
}
const action = treeNodeRowAction(node.type, canExpand.value);
const action = treeNodeRowAction(node.type, canExpand.value, settingsStore.editorSettings.sidebarActivation);
if (action === "open-data") {
openData();
} else if (node.type === "procedure" || node.type === "function") {
@@ -336,14 +338,28 @@ function runRowClickAction() {
function onClick(event: MouseEvent) {
connectionStore.selectedTreeNodeId = props.node.id;
if (settingsStore.editorSettings.sidebarActivation === "double") return;
if (event.detail > 1) return;
runRowClickAction();
}
function onDoubleClick() {
const action = treeNodeRowDoubleClickAction(props.node.type, canOpenObjectBrowser.value);
const action = treeNodeRowDoubleClickAction(
props.node.type,
canOpenObjectBrowser.value,
settingsStore.editorSettings.sidebarActivation,
canExpand.value,
);
if (action === "open-object-browser") {
void openObjectBrowser();
} else if (action === "open-data") {
openData();
} else if (action === "open-source") {
void viewObjectSource();
} else if (action === "open-saved-sql") {
openSavedSqlFile();
} else if (action === "toggle") {
toggle();
}
}
+6
View File
@@ -1005,6 +1005,7 @@ export default {
title: "Settings",
editorTab: "Editor",
appearanceTab: "Appearance",
navigationTab: "Navigation",
redisTab: "Redis",
shortcutsTab: "Shortcuts",
aiTab: "AI",
@@ -1021,6 +1022,11 @@ export default {
appLayoutSeparatedDescription: "Clear panel separation with block-style tabs.",
appLayoutClassic: "Classic compact",
appLayoutClassicDescription: "Continuous tab bar with compact panel headers.",
sidebarActivation: "Sidebar activation",
sidebarActivationSingle: "Single click",
sidebarActivationSingleDescription: "Open actionable sidebar items with one click.",
sidebarActivationDouble: "Double click",
sidebarActivationDoubleDescription: "Single click selects rows; double click opens items.",
apply: "Apply",
reset: "Reset",
resetDefaults: "Reset Defaults",
+6
View File
@@ -983,6 +983,7 @@ export default {
title: "Configuración",
editorTab: "Editor",
appearanceTab: "Apariencia",
navigationTab: "Navegación",
redisTab: "Redis",
shortcutsTab: "Atajos",
aiTab: "IA",
@@ -999,6 +1000,11 @@ export default {
appLayoutSeparatedDescription: "Separación clara de paneles con pestañas en bloque.",
appLayoutClassic: "Clásico compacto",
appLayoutClassicDescription: "Barra de pestañas continua con encabezados de panel compactos.",
sidebarActivation: "Activación de la barra lateral",
sidebarActivationSingle: "Un clic",
sidebarActivationSingleDescription: "Abrir elementos accionables de la barra lateral con un clic.",
sidebarActivationDouble: "Doble clic",
sidebarActivationDoubleDescription: "Un clic selecciona la fila; doble clic abre elementos.",
apply: "Aplicar",
reset: "Restablecer",
resetDefaults: "Restablecer valores por defecto",
+6
View File
@@ -987,6 +987,7 @@ export default {
title: "设置",
editorTab: "编辑器",
appearanceTab: "外观",
navigationTab: "导航",
redisTab: "Redis",
shortcutsTab: "快捷键",
aiTab: "AI",
@@ -1003,6 +1004,11 @@ export default {
appLayoutSeparatedDescription: "面板分隔更清晰,标签页为块状样式。",
appLayoutClassic: "经典紧凑",
appLayoutClassicDescription: "使用连续标签栏和更紧凑的面板标题。",
sidebarActivation: "侧边栏打开方式",
sidebarActivationSingle: "单击打开",
sidebarActivationSingleDescription: "单击即可打开侧边栏中的可操作项目。",
sidebarActivationDouble: "双击打开",
sidebarActivationDoubleDescription: "单击只选中高亮,双击打开项目。",
apply: "应用",
reset: "重置",
resetDefaults: "恢复默认",
+24 -2
View File
@@ -1,13 +1,26 @@
import type { TreeNodeType } from "@/types/database";
export type TreeNodeRowAction = "open-data" | "toggle" | "none";
export type TreeNodeRowDoubleClickAction = "open-object-browser" | "none";
export type TreeNodeRowDoubleClickAction =
| "open-data"
| "open-object-browser"
| "open-source"
| "open-saved-sql"
| "toggle"
| "none";
export type SidebarActivation = "single" | "double";
const dataNodeTypes = new Set<TreeNodeType>(["table", "view"]);
const toggleLeafNodeTypes = new Set<TreeNodeType>(["redis-db", "mongo-collection"]);
const objectBrowserNodeTypes = new Set<TreeNodeType>(["database", "schema", "object-browser"]);
const sourceNodeTypes = new Set<TreeNodeType>(["procedure", "function"]);
export function treeNodeRowAction(type: TreeNodeType, canExpand: boolean): TreeNodeRowAction {
export function treeNodeRowAction(
type: TreeNodeType,
canExpand: boolean,
activation: SidebarActivation = "single",
): TreeNodeRowAction {
if (activation === "double") return "none";
if (dataNodeTypes.has(type)) return "open-data";
if (toggleLeafNodeTypes.has(type)) return "toggle";
if (canExpand) return "toggle";
@@ -17,7 +30,16 @@ export function treeNodeRowAction(type: TreeNodeType, canExpand: boolean): TreeN
export function treeNodeRowDoubleClickAction(
type: TreeNodeType,
canOpenObjectBrowser: boolean,
activation: SidebarActivation = "single",
canExpand = false,
): TreeNodeRowDoubleClickAction {
if (activation === "double") {
if (dataNodeTypes.has(type)) return "open-data";
if (sourceNodeTypes.has(type)) return "open-source";
if (type === "saved-sql-file") return "open-saved-sql";
if (toggleLeafNodeTypes.has(type)) return "toggle";
if (canExpand) return "toggle";
}
if (canOpenObjectBrowser && objectBrowserNodeTypes.has(type)) return "open-object-browser";
return "none";
}
+7
View File
@@ -2,6 +2,7 @@ import { defineStore } from "pinia";
import { ref } from "vue";
import * as api from "@/lib/api";
import { normalizeShortcutSettings, type ShortcutSettings } from "@/lib/shortcutRegistry";
import type { SidebarActivation } from "@/lib/treeNodeClick";
export type AiProvider = "claude" | "openai" | "custom";
export type AiApiStyle = "completions" | "responses";
@@ -54,6 +55,7 @@ export interface EditorSettings {
pageSize: number;
redisScanPageSize: number;
shortcuts: ShortcutSettings;
sidebarActivation: SidebarActivation;
}
export const EDITOR_THEMES: { value: EditorTheme; label: string; dark: boolean }[] = [
@@ -88,6 +90,7 @@ export const DEFAULT_EDITOR_SETTINGS: EditorSettings = {
pageSize: 100,
redisScanPageSize: 1000,
shortcuts: normalizeShortcutSettings(),
sidebarActivation: "single",
};
export const STORAGE_KEY = "dbx-editor-settings";
@@ -104,6 +107,10 @@ export function normalizeEditorSettings(settings: Partial<EditorSettings>): Edit
pageSize: settings.pageSize ?? DEFAULT_EDITOR_SETTINGS.pageSize,
redisScanPageSize: settings.redisScanPageSize ?? DEFAULT_EDITOR_SETTINGS.redisScanPageSize,
shortcuts: normalizeShortcutSettings(settings.shortcuts),
sidebarActivation:
settings.sidebarActivation === "single" || settings.sidebarActivation === "double"
? settings.sidebarActivation
: DEFAULT_EDITOR_SETTINGS.sidebarActivation,
};
}