feat(settings): add interface font selection

This commit is contained in:
t8y2 committed 2026-07-02 19:55:43 +08:00
1 parent 074715b002
commit d249bd5ac8
13 files changed
+175 -25

No files matched your search

@@ -84,6 +84,7 @@ import { currentLocale, setLocale, type Locale } from "@/i18n";
import { LOCALE_OPTIONS } from "@/lib/localeOptions";
import { DEFAULT_WEB_DAV_AUTO_UPLOAD_INTERVAL_MINUTES, DEFAULT_WEB_DAV_REMOTE_PATH, normalizedWebDavAutoUploadInterval, writeWebDavAutoUploadFields } from "@/lib/webdavAutoUploadConfig";
import { apiUrl } from "@/lib/webPath";
import { DEFAULT_UI_FONT_FAMILY, SYSTEM_UI_FONT_FAMILY } from "@/lib/appFonts";
const { t } = useI18n();
const settingsStore = useSettingsStore();
@@ -202,6 +203,7 @@ function createEmptyTableColumnTemplateRow(): TableColumnTemplateGridRow {
// Local edit state
const editFontFamily = ref(settingsStore.editorSettings.fontFamily);
const editFontSize = ref(settingsStore.editorSettings.fontSize);
const editUiFontFamily = ref(settingsStore.editorSettings.uiFontFamily);
const editUiScale = ref(settingsStore.editorSettings.uiScale);
const editTheme = ref(settingsStore.editorSettings.theme);
const editCustomThemes = ref<CustomTheme[]>([...settingsStore.editorSettings.customThemes]);
@@ -260,6 +262,9 @@ const systemFonts = ref<string[]>([]);
const systemFontsLoading = ref(false);
const systemFontsLoaded = ref(false);
const uiScaleOptions = [0.75, 0.9, 1, 1.1, 1.25, 1.5, 1.75, 2];
const fontSearchTriggerClass =
"h-8 w-full max-w-none justify-between gap-1.5 rounded-[6px] border border-input bg-transparent py-2 pl-2.5 pr-2 text-sm font-normal shadow-none hover:bg-transparent focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 aria-expanded:bg-transparent dark:bg-input/30 dark:hover:bg-input/50";
const fontSearchTriggerIconClass = "size-4 text-muted-foreground";
const disconnectTabHandlingModeDescriptionKey = computed(() => {
switch (editDisconnectTabHandlingMode.value) {
case "close-tabs":
@@ -425,6 +430,7 @@ function confirmDeleteSnippet(snippet: SqlSnippet) {
const presetFontLabels = new Map(FONT_FAMILIES.map((font) => [font.value, font.label]));
const presetFontValues = new Set(FONT_FAMILIES.map((font) => font.value));
const uiFontPreviewValues = new Set([DEFAULT_UI_FONT_FAMILY, SYSTEM_UI_FONT_FAMILY]);
function cssFontFamilyForName(name: string): string {
return `'${name.replace(/\\/g, "\\\\").replace(/'/g, "\\'")}', monospace`;
@@ -449,12 +455,24 @@ const systemFontOptions = computed(() => {
return [...options];
});
const uiFontOptions = computed(() => {
const options = new Set([SYSTEM_UI_FONT_FAMILY, DEFAULT_UI_FONT_FAMILY, ...systemFontOptions.value]);
if (editUiFontFamily.value) options.add(editUiFontFamily.value);
return [...options];
});
function displayFontFamily(value: string): string {
return presetFontLabels.get(value) ?? readableFontFamily(value);
}
function fontOptionStyle(value: string) {
return presetFontValues.has(value) || value === editFontFamily.value ? { fontFamily: value } : undefined;
function displayUiFontFamily(value: string): string {
if (value === SYSTEM_UI_FONT_FAMILY) return t("settings.uiFontSystemDefault");
if (value === DEFAULT_UI_FONT_FAMILY) return t("settings.uiFontAppDefault");
return displayFontFamily(value);
}
function fontOptionStyle(value: string, selectedValue = editFontFamily.value) {
return presetFontValues.has(value) || uiFontPreviewValues.has(value) || value === selectedValue ? { fontFamily: value } : undefined;
}
async function loadSystemFontOptions() {
@@ -485,6 +503,7 @@ watch(
if (open) {
editFontFamily.value = settingsStore.editorSettings.fontFamily;
editFontSize.value = settingsStore.editorSettings.fontSize;
editUiFontFamily.value = settingsStore.editorSettings.uiFontFamily;
editUiScale.value = settingsStore.editorSettings.uiScale;
editTheme.value = settingsStore.editorSettings.theme;
editCustomThemes.value = [...settingsStore.editorSettings.customThemes];
@@ -553,6 +572,7 @@ function hasChanges(): boolean {
return (
editFontFamily.value !== settingsStore.editorSettings.fontFamily ||
editFontSize.value !== settingsStore.editorSettings.fontSize ||
editUiFontFamily.value !== settingsStore.editorSettings.uiFontFamily ||
editUiScale.value !== settingsStore.editorSettings.uiScale ||
editTheme.value !== settingsStore.editorSettings.theme ||
JSON.stringify(editCustomThemes.value) !== JSON.stringify(settingsStore.editorSettings.customThemes) ||
@@ -607,6 +627,7 @@ async function persistSettings() {
settingsStore.updateEditorSettings({
fontFamily: editFontFamily.value,
fontSize: editFontSize.value,
uiFontFamily: editUiFontFamily.value,
uiScale: editUiScale.value,
theme: editTheme.value,
customThemes: editCustomThemes.value,
@@ -687,6 +708,7 @@ function resetDefaultsForTab(tab: SettingsCategory) {
editSqlFormatter.value = normalizeSqlFormatterSettings(DEFAULT_EDITOR_SETTINGS.sqlFormatter);
sqlFormatterConfigValid.value = true;
} else if (tab === "appearance") {
editUiFontFamily.value = DEFAULT_EDITOR_SETTINGS.uiFontFamily;
editUiScale.value = DEFAULT_EDITOR_SETTINGS.uiScale;
editTheme.value = DEFAULT_EDITOR_SETTINGS.theme;
editCustomThemes.value = [...DEFAULT_EDITOR_SETTINGS.customThemes];
@@ -734,6 +756,7 @@ function resetDefaultsForTab(tab: SettingsCategory) {
function resetAllDefaults() {
editFontFamily.value = DEFAULT_EDITOR_SETTINGS.fontFamily;
editFontSize.value = DEFAULT_EDITOR_SETTINGS.fontSize;
editUiFontFamily.value = DEFAULT_EDITOR_SETTINGS.uiFontFamily;
editUiScale.value = DEFAULT_EDITOR_SETTINGS.uiScale;
editTheme.value = DEFAULT_EDITOR_SETTINGS.theme;
editCustomThemes.value = [...DEFAULT_EDITOR_SETTINGS.customThemes];
@@ -919,6 +942,10 @@ function onFontFamilyChange(v: any) {
if (typeof v === "string") editFontFamily.value = v;
}
function onUiFontFamilyChange(v: any) {
if (typeof v === "string") editUiFontFamily.value = v;
}
const themeSelectValue = computed(() => {
if (editTheme.value === "custom") {
return `custom:${editActiveCustomThemeId.value}`;
@@ -2044,7 +2071,8 @@ onUnmounted(cleanupPreviewEditor);
allow-custom
:display-name="displayFontFamily"
:normalize-custom="normalizeCustomFontFamilyInput"
trigger-class="h-9 w-full max-w-none justify-between border bg-background px-3 text-sm shadow-xs hover:bg-accent"
:trigger-class="fontSearchTriggerClass"
:trigger-icon-class="fontSearchTriggerIconClass"
content-class="w-[var(--reka-popover-trigger-width)] min-w-[260px]"
@update:model-value="onFontFamilyChange"
@update:open="(open: boolean) => open && loadSystemFontOptions()"
@@ -2260,23 +2288,60 @@ onUnmounted(cleanupPreviewEditor);
</section>
<section v-else-if="activeSettingsTab === 'appearance'" class="flex flex-col gap-5 py-2">
<div class="space-y-2">
<Label>{{ t("settings.languageTitle") }}</Label>
<Select :model-value="currentLocale()" @update:model-value="onLocaleChange">
<SelectTrigger class="min-w-36">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem v-for="locale in LOCALE_OPTIONS" :key="locale.value" :value="locale.value">
<div class="flex items-center gap-2">
<span class="inline-flex h-5 w-6 shrink-0 items-center justify-center text-sm font-medium leading-none">
{{ locale.flag }}
</span>
<span>{{ locale.label }}</span>
</div>
</SelectItem>
</SelectContent>
</Select>
<div class="grid gap-4 md:grid-cols-[minmax(220px,280px)_minmax(260px,1fr)]">
<div class="space-y-2 min-w-0">
<Label>{{ t("settings.languageTitle") }}</Label>
<Select :model-value="currentLocale()" @update:model-value="onLocaleChange">
<SelectTrigger class="h-8 w-full">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem v-for="locale in LOCALE_OPTIONS" :key="locale.value" :value="locale.value">
<div class="flex items-center gap-2">
<span class="inline-flex h-5 w-6 shrink-0 items-center justify-center text-sm font-medium leading-none">
{{ locale.flag }}
</span>
<span>{{ locale.label }}</span>
</div>
</SelectItem>
</SelectContent>
</Select>
</div>
<div class="space-y-2 min-w-0">
<Label>{{ t("settings.uiFontFamily") }}</Label>
<SearchableSelect
:model-value="editUiFontFamily"
:options="uiFontOptions"
:placeholder="t('settings.selectFont')"
:search-placeholder="t('settings.searchFont')"
:empty-text="t('settings.noFontsFound')"
:loading-text="t('settings.loadingFonts')"
allow-custom
:display-name="displayUiFontFamily"
:normalize-custom="normalizeCustomFontFamilyInput"
:trigger-class="fontSearchTriggerClass"
:trigger-icon-class="fontSearchTriggerIconClass"
content-class="w-[var(--reka-popover-trigger-width)] min-w-[260px]"
@update:model-value="onUiFontFamilyChange"
@update:open="(open: boolean) => open && loadSystemFontOptions()"
>
<template #trigger-label="{ label, loading }">
<span class="truncate" :style="{ fontFamily: editUiFontFamily }">
{{ loading ? t("settings.loadingFonts") : label }}
</span>
</template>
<template #option-label="{ option, label }">
<span class="truncate" :style="fontOptionStyle(option, editUiFontFamily)">{{ label }}</span>
</template>
<template #custom-option-label="{ value }">
<span class="truncate" :style="{ fontFamily: value }">
{{ t("settings.useCustomFont", { font: readableFontFamily(value) }) }}
</span>
</template>
</SearchableSelect>
<p class="text-xs text-muted-foreground">{{ t("settings.uiFontFamilyDescription") }}</p>
</div>
</div>
<div class="space-y-2">