add: 增加设置的预览框

This commit is contained in:
rarnu
2026-05-02 15:34:36 +08:00
parent 4af43e35e1
commit 1209a5dad6
3 changed files with 159 additions and 5 deletions
+157 -5
View File
@@ -1,5 +1,7 @@
<script setup lang="ts">
import { ref, watch } from "vue";
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";
@@ -68,11 +70,151 @@ function onFontFamilyChange(v: any) {
function onThemeChange(v: any) {
if (typeof v === 'string') editTheme.value = v as EditorTheme;
}
// ---------- 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 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;
const themeExt = await loadTheme(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 },
})
),
],
});
}, { deep: true });
let previewInitialized = false;
watch(previewRef, async (el) => {
if (!el || previewInitialized) return;
previewInitialized = true;
if (previewView.value) return;
const [
{ EditorView },
{ EditorState, Compartment },
{ sql, MySQL },
{ basicSetup },
] = await Promise.all([
import("@codemirror/view"),
import("@codemirror/state"),
import("@codemirror/lang-sql"),
import("codemirror"),
]);
editorViewModule = { EditorView } as typeof import("@codemirror/view");
fontSizeComp = new Compartment();
fontFamilyComp = new Compartment();
themeComp = new Compartment();
const ss = previewSettings.value;
const themeExt = await loadTheme(ss.theme);
const state = EditorState.create({
doc: previewSql,
extensions: [
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 },
})
),
],
readOnly: true,
});
previewView.value = new EditorView({ state, parent: previewRef.value });
});
watch(() => props.open, (open) => {
if (!open && previewView.value) {
previewView.value.destroy();
previewView.value = null;
previewInitialized = false;
fontSizeComp = null;
fontFamilyComp = null;
themeComp = null;
editorViewModule = null;
}
});
</script>
<template>
<Dialog :open="open" @update:open="(v: boolean) => emit('update:open', v)">
<DialogContent class="sm:max-w-[480px]">
<DialogContent class="sm:max-w-[720px] max-h-[calc(100vh-80px)] overflow-y-auto">
<DialogHeader>
<DialogTitle class="flex items-center gap-2">
<Settings class="h-4 w-4" />
@@ -99,9 +241,6 @@ function onThemeChange(v: any) {
</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 />
@@ -154,6 +293,19 @@ function onThemeChange(v: any) {
</SelectContent>
</Select>
</div>
<Separator />
<!-- Live Preview -->
<div class="space-y-2">
<Label>{{ t('settings.preview') }}</Label>
<div
class="rounded-md border overflow-auto max-w-full"
:class="editTheme === 'vscode-light' || editTheme === 'duotone-light' || editTheme === 'xcode' ? 'border-border' : 'border-border/50'"
>
<div ref="previewRef" style="height: 160px; min-width: 100%" />
</div>
</div>
</div>
<DialogFooter class="gap-2 sm:gap-0">
+1
View File
@@ -367,5 +367,6 @@ export default {
selectTheme: "Select theme...",
apply: "Apply",
resetDefaults: "Reset Defaults",
preview: "Live Preview",
},
};
+1
View File
@@ -367,5 +367,6 @@ export default {
selectTheme: "选择主题...",
apply: "应用",
resetDefaults: "恢复默认",
preview: "实时预览",
},
};