mirror of
https://github.com/t8y2/dbx.git
synced 2026-10-29 16:18:55 +00:00
feat(settings): add sidebar activation preference
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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: "恢复默认",
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user