feat(settings): add interface font selection
This commit is contained in:
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">
|
||||
|
||||
@@ -22,6 +22,7 @@ const props = withDefaults(
|
||||
allowCustom?: boolean;
|
||||
triggerVariant?: ButtonVariants["variant"];
|
||||
triggerClass?: HTMLAttributes["class"];
|
||||
triggerIconClass?: HTMLAttributes["class"];
|
||||
contentClass?: HTMLAttributes["class"];
|
||||
itemClass?: HTMLAttributes["class"];
|
||||
displayName?: (option: string) => string;
|
||||
@@ -35,6 +36,7 @@ const props = withDefaults(
|
||||
clearable: false,
|
||||
loadingText: "Loading...",
|
||||
triggerVariant: "ghost",
|
||||
triggerIconClass: "h-3 w-3",
|
||||
displayName: (option: string) => option,
|
||||
normalizeCustom: (value: string) => value,
|
||||
},
|
||||
@@ -158,8 +160,8 @@ function handleKeydown(event: KeyboardEvent) {
|
||||
<slot name="trigger-label" :value="modelValue" :label="selectedLabel" :loading="loading">
|
||||
<span class="truncate">{{ loading ? loadingText : selectedLabel }}</span>
|
||||
</slot>
|
||||
<X v-if="clearable && !disabled && modelValue" class="h-3 w-3 shrink-0 opacity-60 hover:opacity-100" @pointerdown.stop.prevent="emit('update:modelValue', '')" />
|
||||
<ChevronDown v-else class="h-3 w-3 shrink-0 opacity-60" />
|
||||
<X v-if="clearable && !disabled && modelValue" :class="cn('shrink-0 opacity-60 hover:opacity-100', triggerIconClass)" @pointerdown.stop.prevent="emit('update:modelValue', '')" />
|
||||
<ChevronDown v-else :class="cn('shrink-0 opacity-60', triggerIconClass)" />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent align="end" :class="cn('w-52 gap-1 p-1.5', contentClass)">
|
||||
|
||||
Reference in new issue
Block a user