add: 将loadTheme抽象到外部模块,使用DEFAULT_EDITOR_SETTINGS默认值,增加老的配置项迁移

This commit is contained in:
rarnu 2026-05-02 17:10:42 +08:00
parent e5f3f3bad8
commit 4d2c34ecb1
4 changed files with 110 additions and 122 deletions

View File

@ -1,7 +1,6 @@
<script setup lang="ts">
import { ref, watch, shallowRef, computed } from "vue";
import type { EditorView as EditorViewType } from "@codemirror/view";
import type { Extension } from "@codemirror/state";
import { useI18n } from "vue-i18n";
import { Settings } from "lucide-vue-next";
import { Button } from "@/components/ui/button";
@ -13,7 +12,10 @@ 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";
import {
useSettingsStore, EDITOR_THEMES, FONT_FAMILIES, DEFAULT_EDITOR_SETTINGS,
} from "@/stores/settingsStore";
import { loadEditorTheme, editorFontTheme } from "@/lib/editorThemes";
const { t } = useI18n();
const settingsStore = useSettingsStore();
@ -58,9 +60,9 @@ function applySettings() {
}
function resetDefaults() {
editFontFamily.value = "'JetBrains Mono', 'Fira Code', monospace";
editFontSize.value = 13;
editTheme.value = "one-dark";
editFontFamily.value = DEFAULT_EDITOR_SETTINGS.fontFamily;
editFontSize.value = DEFAULT_EDITOR_SETTINGS.fontSize;
editTheme.value = DEFAULT_EDITOR_SETTINGS.theme;
}
function onFontFamilyChange(v: any) {
@ -68,78 +70,36 @@ function onFontFamilyChange(v: any) {
}
function onThemeChange(v: any) {
if (typeof v === 'string') editTheme.value = v as EditorTheme;
if (typeof v === 'string') editTheme.value = v as typeof DEFAULT_EDITOR_SETTINGS.theme;
}
// ---------- CodeMirror preview ----------
const previewRef = ref<HTMLDivElement>();
const previewView = shallowRef<EditorViewType | null>(null);
// Reactive settings computed for the preview
const previewSettings = computed(() => ({
fontFamily: editFontFamily.value,
fontSize: editFontSize.value,
theme: editTheme.value,
}));
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;
}
}
const previewSql = `SELECT u.id, u.name
FROM users u
ORDER BY u.id LIMIT 5;`;
let fontSizeComp: import("@codemirror/state").Compartment | null = null;
let fontFamilyComp: import("@codemirror/state").Compartment | null = null;
let fontThemeComp: import("@codemirror/state").Compartment | null = null;
let themeComp: import("@codemirror/state").Compartment | null = null;
let editorViewModule: typeof import("@codemirror/view") | null = null;
watch(previewSettings, async (ss) => {
if (
!previewView.value ||
!fontSizeComp ||
!fontFamilyComp ||
!themeComp ||
!editorViewModule
) return;
if (!previewView.value || !fontThemeComp || !themeComp || !editorViewModule) return;
const themeExt = await loadTheme(ss.theme);
const themeExt = await loadEditorTheme(ss.theme);
previewView.value.dispatch({
effects: [
themeComp.reconfigure(themeExt),
fontFamilyComp.reconfigure(
editorViewModule.EditorView.theme({
"&": { fontSize: `${ss.fontSize}px` },
".cm-content": { fontFamily: ss.fontFamily },
})
),
fontSizeComp.reconfigure(
editorViewModule.EditorView.theme({
"&": { fontSize: `${ss.fontSize}px` },
".cm-content": { fontFamily: ss.fontFamily },
})
fontThemeComp.reconfigure(
editorFontTheme(editorViewModule.EditorView, ss.fontSize, ss.fontFamily)
),
],
});
@ -165,12 +125,11 @@ watch(previewRef, async (el) => {
]);
editorViewModule = { EditorView } as typeof import("@codemirror/view");
fontSizeComp = new Compartment();
fontFamilyComp = new Compartment();
fontThemeComp = new Compartment();
themeComp = new Compartment();
const ss = previewSettings.value;
const themeExt = await loadTheme(ss.theme);
const themeExt = await loadEditorTheme(ss.theme);
const state = EditorState.create({
doc: previewSql,
@ -178,22 +137,10 @@ watch(previewRef, async (el) => {
basicSetup,
sql({ dialect: MySQL }),
themeComp.of(themeExt),
fontSizeComp.of(
EditorView.theme({
"&": { fontSize: `${ss.fontSize}px` },
".cm-scroller": { overflow: "auto" },
".cm-content": { fontFamily: ss.fontFamily },
})
),
fontFamilyComp.of(
EditorView.theme({
"&": { fontSize: `${ss.fontSize}px` },
".cm-scroller": { overflow: "auto" },
".cm-content": { fontFamily: ss.fontFamily },
})
fontThemeComp.of(
editorFontTheme(EditorView, ss.fontSize, ss.fontFamily)
),
],
// readOnly: true,
});
previewView.value = new EditorView({ state, parent: previewRef.value });
@ -204,8 +151,7 @@ watch(() => props.open, (open) => {
previewView.value.destroy();
previewView.value = null;
previewInitialized = false;
fontSizeComp = null;
fontFamilyComp = null;
fontThemeComp = null;
themeComp = null;
editorViewModule = null;
}

View File

@ -2,15 +2,15 @@
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 { useSettingsStore } from "@/stores/settingsStore";
import {
buildSqlCompletionItemsFromContext,
getSqlCompletionContext,
} from "@/lib/sqlCompletion";
import { loadEditorTheme, editorFontTheme } from "@/lib/editorThemes";
const props = defineProps<{
modelValue: string;
@ -35,53 +35,23 @@ 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 fontThemeComp: import("@codemirror/state").Compartment | null = null;
let codeMirrorTheme: import("@codemirror/state").Compartment | null = null;
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, family: string) {
return EditorView.theme({
"&": { height: "100%", fontSize: `${size}px` },
".cm-scroller": { overflow: "auto" },
".cm-content": { fontFamily: family },
});
}
function setFontSize(size: number) {
const next = Math.min(MAX_FONT_SIZE, Math.max(MIN_FONT_SIZE, size));
const ss = settingsStore.editorSettings;
ss.fontSize = next;
settingsStore.updateEditorSettings({ fontSize: next });
if (view.value && fontSizeTheme && fontFamilyTheme && editorViewModule) {
if (view.value && fontThemeComp && editorViewModule) {
view.value.dispatch({
effects: [
fontSizeTheme.reconfigure(fontTheme(editorViewModule.EditorView, next, ss.fontFamily)),
fontFamilyTheme.reconfigure(fontTheme(editorViewModule.EditorView, next, ss.fontFamily)),
fontThemeComp.reconfigure(
editorFontTheme(editorViewModule.EditorView, next, ss.fontFamily, {
fixedHeight: true,
scrollable: true,
})
),
],
});
}
@ -197,8 +167,7 @@ onMounted(async () => {
import("@codemirror/autocomplete"),
]);
editorViewModule = { EditorView, keymap } as typeof import("@codemirror/view");
fontSizeTheme = new Compartment();
fontFamilyTheme = new Compartment();
fontThemeComp = new Compartment();
codeMirrorTheme = new Compartment();
const ss = settingsStore.editorSettings;
@ -248,7 +217,7 @@ onMounted(async () => {
},
]);
const theme = await loadTheme(ss.theme);
const theme = await loadEditorTheme(ss.theme);
const state = EditorState.create({
doc: props.modelValue,
@ -278,8 +247,12 @@ onMounted(async () => {
emit("selectionChange", selectedSqlFromView(update.view));
}
}),
fontSizeTheme.of(fontTheme(EditorView, ss.fontSize, ss.fontFamily)),
fontFamilyTheme.of(fontTheme(EditorView, ss.fontSize, ss.fontFamily)),
fontThemeComp.of(
editorFontTheme(EditorView, ss.fontSize, ss.fontFamily, {
fixedHeight: true,
scrollable: true,
})
),
EditorView.domEventHandlers({
wheel(event) {
if (!event.metaKey && !event.ctrlKey) return false;
@ -317,13 +290,17 @@ watch(
watch(
() => settingsStore.editorSettings,
async (ss) => {
if (!view.value || !codeMirrorTheme || !fontFamilyTheme || !fontSizeTheme || !editorViewModule) return;
const themeExt = await loadTheme(ss.theme);
if (!view.value || !codeMirrorTheme || !fontThemeComp || !editorViewModule) return;
const themeExt = await loadEditorTheme(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)),
fontThemeComp.reconfigure(
editorFontTheme(editorViewModule.EditorView, ss.fontSize, ss.fontFamily, {
fixedHeight: true,
scrollable: true,
})
),
],
});
},

45
src/lib/editorThemes.ts Normal file
View File

@ -0,0 +1,45 @@
import type { Extension } from "@codemirror/state";
import type { EditorTheme } from "@/stores/settingsStore";
/** Load a CodeMirror theme extension by theme name. */
export async function loadEditorTheme(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;
}
}
/** Build a CodeMirror theme extension for font size + font family. */
export function editorFontTheme(
EditorView: typeof import("@codemirror/view").EditorView,
size: number,
family: string,
opts?: { fixedHeight?: boolean; scrollable?: boolean },
): Extension {
return EditorView.theme({
"&": {
...(opts?.fixedHeight ? { height: "100%" } : {}),
fontSize: `${size}px`,
},
...(opts?.scrollable ? { ".cm-scroller": { overflow: "auto" } } : {}),
".cm-content": { fontFamily: family },
});
}

View File

@ -49,15 +49,17 @@ export const FONT_FAMILIES: { value: string; label: string }[] = [
{ value: "monospace", label: "System Monospace" },
];
const DEFAULT_EDITOR_SETTINGS: EditorSettings = {
export const DEFAULT_EDITOR_SETTINGS: EditorSettings = {
fontFamily: "'JetBrains Mono', 'Fira Code', monospace",
fontSize: 13,
theme: "one-dark",
};
export const STORAGE_KEY = "dbx-editor-settings";
const OLD_FONT_SIZE_KEY = "dbx-query-editor-font-size";
function loadEditorSettings(): EditorSettings {
// Try new format first
try {
const raw = localStorage.getItem(STORAGE_KEY);
if (raw) {
@ -69,6 +71,24 @@ function loadEditorSettings(): EditorSettings {
};
}
} catch { /* ignore */ }
// Migrate old font-size key if new settings don't exist
try {
const oldSize = localStorage.getItem(OLD_FONT_SIZE_KEY);
if (oldSize) {
const parsed = parseInt(oldSize, 10);
if (!isNaN(parsed)) {
const migrated: EditorSettings = {
...DEFAULT_EDITOR_SETTINGS,
fontSize: parsed,
};
saveEditorSettings(migrated);
localStorage.removeItem(OLD_FONT_SIZE_KEY);
return migrated;
}
}
} catch { /* ignore */ }
return { ...DEFAULT_EDITOR_SETTINGS };
}