mirror of
https://github.com/t8y2/dbx.git
synced 2026-10-28 08:08:02 +00:00
add: 增加对CodeMirror的设置页
This commit is contained in:
+13
-1
@@ -1,7 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, watch, onMounted, onUnmounted, nextTick, type Ref } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { DatabaseZap, FilePlus2, Play, Loader2, Square, X, Globe, Moon, Sun, Upload, Download, Plus, History, Server, Table2, Database, Search, ShieldCheck, Bot, Pin, AlignLeft, CloudDownload, ArrowLeftRight, FileCode } from "lucide-vue-next";
|
||||
import { DatabaseZap, FilePlus2, Play, Loader2, Square, X, Globe, Moon, Sun, Upload, Download, Plus, History, Server, Table2, Database, Search, ShieldCheck, Bot, Pin, AlignLeft, CloudDownload, ArrowLeftRight, FileCode, Settings } from "lucide-vue-next";
|
||||
import { Splitpanes, Pane } from "splitpanes";
|
||||
import "splitpanes/dist/splitpanes.css";
|
||||
import { Button } from "@/components/ui/button";
|
||||
@@ -32,6 +32,7 @@ import AiAssistant from "@/components/editor/AiAssistant.vue";
|
||||
import MongoDocBrowser from "@/components/mongo/MongoDocBrowser.vue";
|
||||
import DatabaseIcon from "@/components/icons/DatabaseIcon.vue";
|
||||
import QueryHistory from "@/components/editor/QueryHistory.vue";
|
||||
import EditorSettingsDialog from "@/components/editor/EditorSettingsDialog.vue";
|
||||
import DangerConfirmDialog from "@/components/editor/DangerConfirmDialog.vue";
|
||||
import DataTransferDialog from "@/components/transfer/DataTransferDialog.vue";
|
||||
import SchemaDiffDialog from "@/components/diff/SchemaDiffDialog.vue";
|
||||
@@ -60,6 +61,7 @@ const settingsStore = useSettingsStore();
|
||||
const { message: toastMessage, visible: toastVisible, toast } = useToast();
|
||||
|
||||
const showConnectionDialog = ref(false);
|
||||
const showSettingsDialog = ref(false);
|
||||
const showHistory = ref(false);
|
||||
const showAiPanel = ref(localStorage.getItem("dbx-ai-panel-open") !== "false");
|
||||
const aiPanelWidth = ref(Number(localStorage.getItem("dbx-ai-panel-width")) || 360);
|
||||
@@ -785,6 +787,15 @@ async function setupFileDrop() {
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>GitHub</TooltipContent>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip>
|
||||
<TooltipTrigger as-child>
|
||||
<Button variant="ghost" size="icon" class="h-7 w-7" @click="showSettingsDialog = true">
|
||||
<Settings class="h-4 w-4" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>{{ t('settings.title') }}</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
||||
<!-- Main Content -->
|
||||
@@ -1194,6 +1205,7 @@ async function setupFileDrop() {
|
||||
@connect-succeeded="onConnectionConnectSucceeded"
|
||||
@connect-failed="onConnectionConnectFailed"
|
||||
/>
|
||||
<EditorSettingsDialog v-model:open="showSettingsDialog" />
|
||||
<DangerConfirmDialog v-model:open="showDangerDialog" :sql="dangerSql" @confirm="onDangerConfirm" />
|
||||
<DataTransferDialog
|
||||
v-model:open="showTransferDialog"
|
||||
|
||||
@@ -0,0 +1,173 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, watch } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { Settings } from "lucide-vue-next";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter,
|
||||
} from "@/components/ui/dialog";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import {
|
||||
Select, SelectContent, SelectItem, SelectTrigger, SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
import { useSettingsStore, EDITOR_THEMES, FONT_FAMILIES, type EditorTheme } from "@/stores/settingsStore";
|
||||
|
||||
const { t } = useI18n();
|
||||
const settingsStore = useSettingsStore();
|
||||
|
||||
const props = defineProps<{
|
||||
open: boolean;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
"update:open": [value: boolean];
|
||||
}>();
|
||||
|
||||
// Local edit state
|
||||
const editFontFamily = ref(settingsStore.editorSettings.fontFamily);
|
||||
const editFontSize = ref(settingsStore.editorSettings.fontSize);
|
||||
const editTheme = ref(settingsStore.editorSettings.theme);
|
||||
|
||||
// Sync from store when dialog opens
|
||||
watch(() => props.open, (open) => {
|
||||
if (open) {
|
||||
editFontFamily.value = settingsStore.editorSettings.fontFamily;
|
||||
editFontSize.value = settingsStore.editorSettings.fontSize;
|
||||
editTheme.value = settingsStore.editorSettings.theme;
|
||||
}
|
||||
});
|
||||
|
||||
function hasChanges(): boolean {
|
||||
return (
|
||||
editFontFamily.value !== settingsStore.editorSettings.fontFamily ||
|
||||
editFontSize.value !== settingsStore.editorSettings.fontSize ||
|
||||
editTheme.value !== settingsStore.editorSettings.theme
|
||||
);
|
||||
}
|
||||
|
||||
function applySettings() {
|
||||
settingsStore.updateEditorSettings({
|
||||
fontFamily: editFontFamily.value,
|
||||
fontSize: editFontSize.value,
|
||||
theme: editTheme.value,
|
||||
});
|
||||
emit("update:open", false);
|
||||
}
|
||||
|
||||
function resetDefaults() {
|
||||
editFontFamily.value = "'JetBrains Mono', 'Fira Code', monospace";
|
||||
editFontSize.value = 13;
|
||||
editTheme.value = "one-dark";
|
||||
}
|
||||
|
||||
function onFontFamilyChange(v: any) {
|
||||
if (typeof v === 'string') editFontFamily.value = v;
|
||||
}
|
||||
|
||||
function onThemeChange(v: any) {
|
||||
if (typeof v === 'string') editTheme.value = v as EditorTheme;
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Dialog :open="open" @update:open="(v: boolean) => emit('update:open', v)">
|
||||
<DialogContent class="sm:max-w-[480px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle class="flex items-center gap-2">
|
||||
<Settings class="h-4 w-4" />
|
||||
{{ t('settings.title') }}
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<div class="space-y-5 py-2">
|
||||
<!-- Font Family -->
|
||||
<div class="space-y-2">
|
||||
<Label>{{ t('settings.fontFamily') }}</Label>
|
||||
<Select :model-value="editFontFamily" @update:model-value="onFontFamilyChange">
|
||||
<SelectTrigger>
|
||||
<SelectValue :placeholder="t('settings.selectFont')" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem
|
||||
v-for="font in FONT_FAMILIES"
|
||||
:key="font.value"
|
||||
:value="font.value"
|
||||
:style="{ fontFamily: font.value }"
|
||||
>
|
||||
{{ font.label }}
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<p class="text-xs text-muted-foreground leading-relaxed font-mono" :style="{ fontFamily: editFontFamily }">
|
||||
SELECT * FROM users WHERE id = 1;
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<Separator />
|
||||
|
||||
<!-- Font Size -->
|
||||
<div class="space-y-2">
|
||||
<div class="flex items-center justify-between">
|
||||
<Label>{{ t('settings.fontSize') }}</Label>
|
||||
<span class="text-xs text-muted-foreground tabular-nums">{{ editFontSize }}px</span>
|
||||
</div>
|
||||
<input
|
||||
type="range"
|
||||
min="10"
|
||||
max="24"
|
||||
step="1"
|
||||
:value="editFontSize"
|
||||
@input="editFontSize = Number(($event.target as HTMLInputElement).value)"
|
||||
class="w-full accent-primary"
|
||||
/>
|
||||
<div class="flex items-center gap-2 text-xs text-muted-foreground">
|
||||
<span>10px</span>
|
||||
<span class="flex-1 border-b border-dashed border-muted-foreground/30" />
|
||||
<span>24px</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Separator />
|
||||
|
||||
<!-- Theme -->
|
||||
<div class="space-y-2">
|
||||
<Label>{{ t('settings.theme') }}</Label>
|
||||
<Select :model-value="editTheme" @update:model-value="onThemeChange">
|
||||
<SelectTrigger>
|
||||
<SelectValue :placeholder="t('settings.selectTheme')" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem
|
||||
v-for="theme in EDITOR_THEMES"
|
||||
:key="theme.value"
|
||||
:value="theme.value"
|
||||
>
|
||||
<div class="flex items-center gap-2">
|
||||
<span
|
||||
class="h-3 w-3 rounded-full border"
|
||||
:class="theme.dark ? 'bg-foreground border-foreground/20' : 'bg-muted-foreground/30 border-muted-foreground/40'"
|
||||
/>
|
||||
{{ theme.label }}
|
||||
</div>
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<DialogFooter class="gap-2 sm:gap-0">
|
||||
<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()" @click="applySettings">
|
||||
{{ t('settings.apply') }}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</template>
|
||||
@@ -2,9 +2,11 @@
|
||||
import { ref, onMounted, onBeforeUnmount, watch, shallowRef } from "vue";
|
||||
import type { CompletionContext } from "@codemirror/autocomplete";
|
||||
import type { EditorView as EditorViewType } from "@codemirror/view";
|
||||
import type { Extension } from "@codemirror/state";
|
||||
import { resolveExecutableSql } from "@/lib/sqlExecutionTarget";
|
||||
import { formatSqlText, type SqlFormatDialect } from "@/lib/sqlFormatter";
|
||||
import { useConnectionStore } from "@/stores/connectionStore";
|
||||
import { useSettingsStore, type EditorTheme } from "@/stores/settingsStore";
|
||||
import {
|
||||
buildSqlCompletionItemsFromContext,
|
||||
getSqlCompletionContext,
|
||||
@@ -29,48 +31,72 @@ const emit = defineEmits<{
|
||||
const editorRef = ref<HTMLDivElement>();
|
||||
const view = shallowRef<EditorViewType | null>(null);
|
||||
const connectionStore = useConnectionStore();
|
||||
const DEFAULT_FONT_SIZE = 13;
|
||||
const settingsStore = useSettingsStore();
|
||||
const MIN_FONT_SIZE = 10;
|
||||
const MAX_FONT_SIZE = 24;
|
||||
let editorViewModule: typeof import("@codemirror/view") | null = null;
|
||||
let fontSizeTheme: import("@codemirror/state").Compartment | null = null;
|
||||
let fontFamilyTheme: import("@codemirror/state").Compartment | null = null;
|
||||
let codeMirrorTheme: import("@codemirror/state").Compartment | null = null;
|
||||
|
||||
const savedFontSize = Number(localStorage.getItem("dbx-query-editor-font-size"));
|
||||
const fontSize = ref(
|
||||
Number.isFinite(savedFontSize)
|
||||
? Math.min(MAX_FONT_SIZE, Math.max(MIN_FONT_SIZE, savedFontSize))
|
||||
: DEFAULT_FONT_SIZE,
|
||||
);
|
||||
async function loadTheme(theme: EditorTheme): Promise<Extension> {
|
||||
switch (theme) {
|
||||
case "one-dark":
|
||||
return (await import("@codemirror/theme-one-dark")).oneDark;
|
||||
case "vscode-dark":
|
||||
return (await import("@uiw/codemirror-theme-vscode")).vscodeDark;
|
||||
case "vscode-light":
|
||||
return (await import("@uiw/codemirror-theme-vscode")).vscodeLight;
|
||||
case "nord":
|
||||
return (await import("@uiw/codemirror-theme-nord")).nord;
|
||||
case "okaidia":
|
||||
return (await import("@uiw/codemirror-theme-okaidia")).okaidia;
|
||||
case "material":
|
||||
return (await import("@uiw/codemirror-theme-material")).materialDark;
|
||||
case "duotone-light":
|
||||
return (await import("@uiw/codemirror-theme-duotone")).duotoneLight;
|
||||
case "duotone-dark":
|
||||
return (await import("@uiw/codemirror-theme-duotone")).duotoneDark;
|
||||
case "xcode":
|
||||
return (await import("@uiw/codemirror-theme-xcode")).xcodeLight;
|
||||
default:
|
||||
return (await import("@codemirror/theme-one-dark")).oneDark;
|
||||
}
|
||||
}
|
||||
|
||||
function fontTheme(EditorView: typeof import("@codemirror/view").EditorView, size: number) {
|
||||
function fontTheme(EditorView: typeof import("@codemirror/view").EditorView, size: number, family: string) {
|
||||
return EditorView.theme({
|
||||
"&": { height: "100%", fontSize: `${size}px` },
|
||||
".cm-scroller": { overflow: "auto" },
|
||||
".cm-content": { fontFamily: "'JetBrains Mono', 'Fira Code', monospace" },
|
||||
".cm-content": { fontFamily: family },
|
||||
});
|
||||
}
|
||||
|
||||
function setFontSize(size: number) {
|
||||
const next = Math.min(MAX_FONT_SIZE, Math.max(MIN_FONT_SIZE, size));
|
||||
fontSize.value = next;
|
||||
localStorage.setItem("dbx-query-editor-font-size", String(next));
|
||||
if (view.value && fontSizeTheme && editorViewModule) {
|
||||
const ss = settingsStore.editorSettings;
|
||||
ss.fontSize = next;
|
||||
settingsStore.updateEditorSettings({ fontSize: next });
|
||||
if (view.value && fontSizeTheme && fontFamilyTheme && editorViewModule) {
|
||||
view.value.dispatch({
|
||||
effects: fontSizeTheme.reconfigure(fontTheme(editorViewModule.EditorView, next)),
|
||||
effects: [
|
||||
fontSizeTheme.reconfigure(fontTheme(editorViewModule.EditorView, next, ss.fontFamily)),
|
||||
fontFamilyTheme.reconfigure(fontTheme(editorViewModule.EditorView, next, ss.fontFamily)),
|
||||
],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function zoomIn() {
|
||||
setFontSize(fontSize.value + 1);
|
||||
setFontSize(settingsStore.editorSettings.fontSize + 1);
|
||||
}
|
||||
|
||||
function zoomOut() {
|
||||
setFontSize(fontSize.value - 1);
|
||||
setFontSize(settingsStore.editorSettings.fontSize - 1);
|
||||
}
|
||||
|
||||
function resetZoom() {
|
||||
setFontSize(DEFAULT_FONT_SIZE);
|
||||
setFontSize(13);
|
||||
}
|
||||
|
||||
function selectedSqlFromView(currentView: EditorViewType): string {
|
||||
@@ -162,18 +188,20 @@ onMounted(async () => {
|
||||
{ EditorState, Compartment },
|
||||
{ sql, MySQL, PostgreSQL, SQLDialect },
|
||||
{ basicSetup },
|
||||
{ oneDark },
|
||||
{ autocompletion, startCompletion },
|
||||
] = await Promise.all([
|
||||
import("@codemirror/view"),
|
||||
import("@codemirror/state"),
|
||||
import("@codemirror/lang-sql"),
|
||||
import("codemirror"),
|
||||
import("@codemirror/theme-one-dark"),
|
||||
import("@codemirror/autocomplete"),
|
||||
]);
|
||||
editorViewModule = { EditorView, keymap } as typeof import("@codemirror/view");
|
||||
fontSizeTheme = new Compartment();
|
||||
fontFamilyTheme = new Compartment();
|
||||
codeMirrorTheme = new Compartment();
|
||||
|
||||
const ss = settingsStore.editorSettings;
|
||||
|
||||
const baseDialect = props.dialect === "postgres" ? PostgreSQL : MySQL;
|
||||
const extraKeywords = "PIVOT UNPIVOT EXCLUDE REPLACE QUALIFY ASOF POSITIONAL ANTI SEMI SAMPLE TABLESAMPLE STRUCT MAP LIST ARRAY LAMBDA UNNEST LATERAL FILTER RECURSIVE SUMMARIZE PRAGMA READ_CSV READ_PARQUET READ_JSON DESCRIBE SHOW COPY EXPORT IMPORT";
|
||||
@@ -220,6 +248,8 @@ onMounted(async () => {
|
||||
},
|
||||
]);
|
||||
|
||||
const theme = await loadTheme(ss.theme);
|
||||
|
||||
const state = EditorState.create({
|
||||
doc: props.modelValue,
|
||||
extensions: [
|
||||
@@ -231,7 +261,7 @@ onMounted(async () => {
|
||||
async (context: CompletionContext) => provideSqlCompletions(context.state, context.pos),
|
||||
],
|
||||
}),
|
||||
oneDark,
|
||||
codeMirrorTheme.of(theme),
|
||||
runKeymap,
|
||||
EditorView.updateListener.of((update) => {
|
||||
if (update.docChanged) {
|
||||
@@ -248,7 +278,8 @@ onMounted(async () => {
|
||||
emit("selectionChange", selectedSqlFromView(update.view));
|
||||
}
|
||||
}),
|
||||
fontSizeTheme.of(fontTheme(EditorView, fontSize.value)),
|
||||
fontSizeTheme.of(fontTheme(EditorView, ss.fontSize, ss.fontFamily)),
|
||||
fontFamilyTheme.of(fontTheme(EditorView, ss.fontSize, ss.fontFamily)),
|
||||
EditorView.domEventHandlers({
|
||||
wheel(event) {
|
||||
if (!event.metaKey && !event.ctrlKey) return false;
|
||||
@@ -282,6 +313,23 @@ watch(
|
||||
}
|
||||
);
|
||||
|
||||
// Reactively apply editor settings changes (theme, font family, font size)
|
||||
watch(
|
||||
() => settingsStore.editorSettings,
|
||||
async (ss) => {
|
||||
if (!view.value || !codeMirrorTheme || !fontFamilyTheme || !fontSizeTheme || !editorViewModule) return;
|
||||
const themeExt = await loadTheme(ss.theme);
|
||||
view.value.dispatch({
|
||||
effects: [
|
||||
codeMirrorTheme.reconfigure(themeExt),
|
||||
fontFamilyTheme.reconfigure(fontTheme(editorViewModule.EditorView, ss.fontSize, ss.fontFamily)),
|
||||
fontSizeTheme.reconfigure(fontTheme(editorViewModule.EditorView, ss.fontSize, ss.fontFamily)),
|
||||
],
|
||||
});
|
||||
},
|
||||
{ deep: true }
|
||||
);
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
view.value?.destroy();
|
||||
});
|
||||
|
||||
@@ -358,4 +358,14 @@ export default {
|
||||
executeSync: "Execute Sync",
|
||||
syncSuccess: "Sync executed successfully",
|
||||
},
|
||||
settings: {
|
||||
title: "Settings",
|
||||
fontFamily: "Font Family",
|
||||
selectFont: "Select font...",
|
||||
fontSize: "Font Size",
|
||||
theme: "Theme",
|
||||
selectTheme: "Select theme...",
|
||||
apply: "Apply",
|
||||
resetDefaults: "Reset Defaults",
|
||||
},
|
||||
};
|
||||
|
||||
@@ -358,4 +358,14 @@ export default {
|
||||
executeSync: "执行同步",
|
||||
syncSuccess: "同步执行成功",
|
||||
},
|
||||
settings: {
|
||||
title: "设置",
|
||||
fontFamily: "字体",
|
||||
selectFont: "选择字体...",
|
||||
fontSize: "字号",
|
||||
theme: "主题",
|
||||
selectTheme: "选择主题...",
|
||||
apply: "应用",
|
||||
resetDefaults: "恢复默认",
|
||||
},
|
||||
};
|
||||
|
||||
@@ -19,10 +19,69 @@ const defaultConfigs: Record<AiProvider, Omit<AiConfig, "apiKey">> = {
|
||||
custom: { provider: "custom", endpoint: "", model: "", apiStyle: "completions" },
|
||||
};
|
||||
|
||||
export type EditorTheme = "one-dark" | "vscode-dark" | "vscode-light" | "nord" | "okaidia" | "material" | "duotone-light" | "duotone-dark" | "xcode";
|
||||
|
||||
export interface EditorSettings {
|
||||
fontFamily: string;
|
||||
fontSize: number;
|
||||
theme: EditorTheme;
|
||||
}
|
||||
|
||||
export const EDITOR_THEMES: { value: EditorTheme; label: string; dark: boolean }[] = [
|
||||
{ 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 },
|
||||
];
|
||||
|
||||
export const FONT_FAMILIES: { value: string; label: string }[] = [
|
||||
{ value: "'JetBrains Mono', 'Fira Code', monospace", label: "JetBrains Mono" },
|
||||
{ value: "'Fira Code', monospace", label: "Fira Code" },
|
||||
{ value: "'Cascadia Code', monospace", label: "Cascadia Code" },
|
||||
{ value: "'Source Code Pro', monospace", label: "Source Code Pro" },
|
||||
{ value: "'SF Mono', 'Menlo', monospace", label: "SF Mono / Menlo" },
|
||||
{ value: "'Consolas', 'Courier New', monospace", label: "Consolas" },
|
||||
{ value: "monospace", label: "System Monospace" },
|
||||
];
|
||||
|
||||
const DEFAULT_EDITOR_SETTINGS: EditorSettings = {
|
||||
fontFamily: "'JetBrains Mono', 'Fira Code', monospace",
|
||||
fontSize: 13,
|
||||
theme: "one-dark",
|
||||
};
|
||||
|
||||
export const STORAGE_KEY = "dbx-editor-settings";
|
||||
|
||||
function loadEditorSettings(): EditorSettings {
|
||||
try {
|
||||
const raw = localStorage.getItem(STORAGE_KEY);
|
||||
if (raw) {
|
||||
const parsed = JSON.parse(raw) as Partial<EditorSettings>;
|
||||
return {
|
||||
fontFamily: parsed.fontFamily ?? DEFAULT_EDITOR_SETTINGS.fontFamily,
|
||||
fontSize: parsed.fontSize ?? DEFAULT_EDITOR_SETTINGS.fontSize,
|
||||
theme: parsed.theme ?? DEFAULT_EDITOR_SETTINGS.theme,
|
||||
};
|
||||
}
|
||||
} catch { /* ignore */ }
|
||||
return { ...DEFAULT_EDITOR_SETTINGS };
|
||||
}
|
||||
|
||||
function saveEditorSettings(settings: EditorSettings) {
|
||||
localStorage.setItem(STORAGE_KEY, JSON.stringify(settings));
|
||||
}
|
||||
|
||||
export const useSettingsStore = defineStore("settings", () => {
|
||||
const aiConfig = ref<AiConfig>({ ...defaultConfigs.claude, apiKey: "", apiStyle: "completions" });
|
||||
const isAiConfigLoaded = ref(false);
|
||||
|
||||
const editorSettings = ref<EditorSettings>(loadEditorSettings());
|
||||
|
||||
async function initAiConfig() {
|
||||
if (isAiConfigLoaded.value) return;
|
||||
const legacy = localStorage.getItem("dbx-ai-config");
|
||||
@@ -50,5 +109,13 @@ export const useSettingsStore = defineStore("settings", () => {
|
||||
return !!aiConfig.value.apiKey && !!aiConfig.value.endpoint;
|
||||
}
|
||||
|
||||
return { aiConfig, isAiConfigLoaded, initAiConfig, updateAiConfig, isConfigured };
|
||||
function updateEditorSettings(partial: Partial<EditorSettings>) {
|
||||
Object.assign(editorSettings.value, partial);
|
||||
saveEditorSettings(editorSettings.value);
|
||||
}
|
||||
|
||||
return {
|
||||
aiConfig, isAiConfigLoaded, initAiConfig, updateAiConfig, isConfigured,
|
||||
editorSettings, updateEditorSettings,
|
||||
};
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user