From 1f1ec828f9a6e623c388dc1c28f5f14f0cfa7de2 Mon Sep 17 00:00:00 2001 From: NimBold Date: Thu, 23 Jul 2026 17:13:00 +0330 Subject: [PATCH] feat(settings): add locale-aware font preferences Bundle optional Inter and Outfit families, preserve the platform system default, and provide script-aware fallbacks for every supported locale. Refs #31 --- index.html | 3 -- package-lock.json | 20 ++++++++++++ package.json | 2 ++ src-tauri/src/ipc.rs | 19 ++++++++++++ src-tauri/src/settings.rs | 25 +++++++++++++-- src-tauri/tauri.conf.json | 2 +- src/App.tsx | 5 +++ src/bindings/FontFamily.ts | 3 ++ src/bindings/PersistedSettings.ts | 3 +- src/components/SettingsView.tsx | 23 ++++++++++++++ src/i18n/catalogs/en.ts | 7 +++++ src/i18n/catalogs/fa.ts | 7 +++++ src/i18n/catalogs/he.ts | 7 +++++ src/i18n/catalogs/ru.ts | 7 +++++ src/i18n/catalogs/uk.ts | 7 +++++ src/i18n/catalogs/zh-CN.ts | 7 +++++ src/i18n/resources.test.ts | 2 ++ src/index.css | 51 +++++++++++++++++++++++++++++-- src/main.tsx | 2 ++ src/store/useSettingsStore.ts | 14 +++++++++ 20 files changed, 206 insertions(+), 10 deletions(-) create mode 100644 src/bindings/FontFamily.ts diff --git a/index.html b/index.html index 4cde6fc..e07eaa9 100644 --- a/index.html +++ b/index.html @@ -5,9 +5,6 @@ Firelink Download Manager - - - diff --git a/package-lock.json b/package-lock.json index ceb92ef..5f65bf4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,8 @@ "version": "1.2.0", "license": "MIT", "dependencies": { + "@fontsource-variable/inter": "^5.3.0", + "@fontsource-variable/outfit": "^5.3.0", "@formkit/auto-animate": "^0.10.0", "@tailwindcss/vite": "^4.3.3", "@tauri-apps/api": "^2.11.1", @@ -81,6 +83,24 @@ "tslib": "^2.4.0" } }, + "node_modules/@fontsource-variable/inter": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@fontsource-variable/inter/-/inter-5.3.0.tgz", + "integrity": "sha512-OupL48va4JNofb97w6NYeF9S7W/kHNKM0Er8Dem5nqi4jeOLrVJDoE8tZEpnMJmtkvNbB1EIPPwHcdkF6b1oUA==", + "license": "OFL-1.1", + "funding": { + "url": "https://github.com/sponsors/ayuhito" + } + }, + "node_modules/@fontsource-variable/outfit": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@fontsource-variable/outfit/-/outfit-5.3.0.tgz", + "integrity": "sha512-fPb5XnYK0k7SbR++HEmm4+ZqDszmLKQ+sPRUr8oQ/KNC0vy6r6hazJFe8iYozv4cqlHbvJ/JECkJWrfDodUMVQ==", + "license": "OFL-1.1", + "funding": { + "url": "https://github.com/sponsors/ayuhito" + } + }, "node_modules/@formkit/auto-animate": { "version": "0.10.0", "resolved": "https://registry.npmjs.org/@formkit/auto-animate/-/auto-animate-0.10.0.tgz", diff --git a/package.json b/package.json index 48bf5ad..7b80729 100644 --- a/package.json +++ b/package.json @@ -39,6 +39,8 @@ "test": "vitest" }, "dependencies": { + "@fontsource-variable/inter": "^5.3.0", + "@fontsource-variable/outfit": "^5.3.0", "@formkit/auto-animate": "^0.10.0", "@tailwindcss/vite": "^4.3.3", "@tauri-apps/api": "^2.11.1", diff --git a/src-tauri/src/ipc.rs b/src-tauri/src/ipc.rs index bc9ebb5..dbdb1bd 100644 --- a/src-tauri/src/ipc.rs +++ b/src-tauri/src/ipc.rs @@ -214,6 +214,23 @@ pub enum Theme { Nord, } +#[derive(Clone, Debug, Serialize, Deserialize, TS)] +#[serde(rename_all = "lowercase")] +#[ts(export, export_to = "../../src/bindings/")] +pub enum FontFamily { + System, + Inter, + Outfit, + Serif, + Monospace, +} + +impl Default for FontFamily { + fn default() -> Self { + Self::System + } +} + #[derive(Clone, Debug, Serialize, Deserialize, TS)] #[serde(rename_all = "lowercase")] #[ts(export, export_to = "../../src/bindings/")] @@ -312,6 +329,8 @@ pub struct SchedulerSettings { pub struct PersistedSettings { pub theme: Theme, #[serde(default)] + pub font_family: FontFamily, + #[serde(default)] pub window_control_style: WindowControlStyle, #[serde(default)] pub calendar_preference: CalendarPreference, diff --git a/src-tauri/src/settings.rs b/src-tauri/src/settings.rs index a9630fd..995bff3 100644 --- a/src-tauri/src/settings.rs +++ b/src-tauri/src/settings.rs @@ -1,6 +1,7 @@ use crate::ipc::{ - AppFontSize, CalendarPreference, ListRowDensity, MediaCookieSource, PersistedSettings, - PostQueueAction, ProxyMode, SchedulerSettings, SettingsTab, Theme, WindowControlStyle, + AppFontSize, CalendarPreference, FontFamily, ListRowDensity, MediaCookieSource, + PersistedSettings, PostQueueAction, ProxyMode, SchedulerSettings, SettingsTab, Theme, + WindowControlStyle, }; use serde_json::{Map, Value}; use std::collections::HashMap; @@ -192,6 +193,11 @@ fn sanitize_persisted_setting_values(state: &mut Value) { "theme", &["system", "light", "dark", "dracula", "nord"], ); + sanitize_allowed_string( + state, + "fontFamily", + &["system", "inter", "outfit", "serif", "monospace"], + ); sanitize_allowed_string( state, "windowControlStyle", @@ -422,6 +428,7 @@ fn derived_location_path(base: &str, subfolder: &str) -> String { fn default_settings() -> PersistedSettings { PersistedSettings { theme: Theme::System, + font_family: FontFamily::System, window_control_style: WindowControlStyle::Auto, calendar_preference: CalendarPreference::Gregorian, language: "system".to_string(), @@ -478,7 +485,7 @@ fn default_settings() -> PersistedSettings { #[cfg(test)] mod tests { - use crate::ipc::WindowControlStyle; + use crate::ipc::{FontFamily, WindowControlStyle}; use super::{ decode_stored_settings, default_settings, preserve_portable_pairing_token, preserve_scheduler_runtime_keys, @@ -685,6 +692,18 @@ mod tests { assert_eq!(settings.sidebar_position, "auto"); } + #[test] + fn invalid_font_family_uses_system_font() { + let stored = json!({ + "state": {"fontFamily": "comic-sans"}, + "version": 5 + }); + + let settings = decode_stored_settings(&Value::String(stored.to_string())).unwrap(); + + assert!(matches!(settings.font_family, FontFamily::System)); + } + #[test] fn invalid_window_control_style_uses_automatic_layout() { let stored = json!({ diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 41539e4..920f4e5 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -23,7 +23,7 @@ } ], "security": { - "csp": "default-src 'self'; img-src 'self' data: https:; script-src 'self'; style-src 'self' 'unsafe-inline'; connect-src 'self' ws://localhost:* http://localhost:* http://127.0.0.1:* ws://127.0.0.1:*" + "csp": "default-src 'self'; img-src 'self' data: https:; script-src 'self'; style-src 'self' 'unsafe-inline'; font-src 'self'; connect-src 'self' ws://localhost:* http://localhost:* http://127.0.0.1:* ws://127.0.0.1:*" } }, "bundle": { diff --git a/src/App.tsx b/src/App.tsx index d11a419..25b1e0f 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -194,6 +194,7 @@ function App() { const sidebarPosition = useSettingsStore(state => state.sidebarPosition); const toggleSidebar = useSettingsStore(state => state.toggleSidebar); const activeView = useSettingsStore(state => state.activeView); + const fontFamily = useSettingsStore(state => state.fontFamily); const appFontSize = useSettingsStore(state => state.appFontSize); const listRowDensity = useSettingsStore(state => state.listRowDensity); @@ -649,6 +650,10 @@ function App() { }); }, [addToast, coreReady, showKeychainModal]); + useEffect(() => { + window.document.documentElement.setAttribute('data-font-family', fontFamily); + }, [fontFamily]); + useEffect(() => { window.document.documentElement.setAttribute('data-font-size', appFontSize); }, [appFontSize]); diff --git a/src/bindings/FontFamily.ts b/src/bindings/FontFamily.ts new file mode 100644 index 0000000..7023a86 --- /dev/null +++ b/src/bindings/FontFamily.ts @@ -0,0 +1,3 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type FontFamily = "system" | "inter" | "outfit" | "serif" | "monospace"; diff --git a/src/bindings/PersistedSettings.ts b/src/bindings/PersistedSettings.ts index 3472b9d..11fa5a9 100644 --- a/src/bindings/PersistedSettings.ts +++ b/src/bindings/PersistedSettings.ts @@ -1,6 +1,7 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. import type { AppFontSize } from "./AppFontSize"; import type { CalendarPreference } from "./CalendarPreference"; +import type { FontFamily } from "./FontFamily"; import type { ListRowDensity } from "./ListRowDensity"; import type { MediaCookieSource } from "./MediaCookieSource"; import type { ProxyMode } from "./ProxyMode"; @@ -10,4 +11,4 @@ import type { SiteLogin } from "./SiteLogin"; import type { Theme } from "./Theme"; import type { WindowControlStyle } from "./WindowControlStyle"; -export type PersistedSettings = { theme: Theme, windowControlStyle: WindowControlStyle, calendarPreference: CalendarPreference, language: string, baseDownloadFolder: string, categorySubfoldersEnabled: boolean, categorySubfolders: { [key in string]: string }, categoryDirectoryOverrides: { [key in string]: string }, approvedDownloadRoots: Array, maxConcurrentDownloads: number, globalSpeedLimit: string, speedLimitPresetValues: Array, logsEnabled: boolean, isSidebarVisible: boolean, sidebarPosition: string, activeSettingsTab: SettingsTab, scheduler: SchedulerSettings, schedulerRunning: boolean, schedulerActiveDownloadIds: Array, schedulerLastStartKey: string, schedulerLastStopKey: string, lastCustomSpeedLimitKiB: number, lastCustomSpeedLimitUnit: string, perServerConnections: number, maxAutomaticRetries: number, showNotifications: boolean, playCompletionSound: boolean, autoAddClipboardLinks: boolean, appFontSize: AppFontSize, listRowDensity: ListRowDensity, showDockBadge: boolean, showMenuBarIcon: boolean, proxyMode: ProxyMode, proxyHost: string, proxyPort: number, customUserAgent: string, askWhereToSaveEachFile: boolean, rememberLastUsedDownloadDirectory: boolean, preventsSleepWhileDownloading: boolean, mediaCookieSource: MediaCookieSource, siteLogins: Array, autoCheckUpdates: boolean, keychainAccessGranted: boolean, }; +export type PersistedSettings = { theme: Theme, fontFamily: FontFamily, windowControlStyle: WindowControlStyle, calendarPreference: CalendarPreference, language: string, baseDownloadFolder: string, categorySubfoldersEnabled: boolean, categorySubfolders: { [key in string]: string }, categoryDirectoryOverrides: { [key in string]: string }, approvedDownloadRoots: Array, maxConcurrentDownloads: number, globalSpeedLimit: string, speedLimitPresetValues: Array, logsEnabled: boolean, isSidebarVisible: boolean, sidebarPosition: string, activeSettingsTab: SettingsTab, scheduler: SchedulerSettings, schedulerRunning: boolean, schedulerActiveDownloadIds: Array, schedulerLastStartKey: string, schedulerLastStopKey: string, lastCustomSpeedLimitKiB: number, lastCustomSpeedLimitUnit: string, perServerConnections: number, maxAutomaticRetries: number, showNotifications: boolean, playCompletionSound: boolean, autoAddClipboardLinks: boolean, appFontSize: AppFontSize, listRowDensity: ListRowDensity, showDockBadge: boolean, showMenuBarIcon: boolean, proxyMode: ProxyMode, proxyHost: string, proxyPort: number, customUserAgent: string, askWhereToSaveEachFile: boolean, rememberLastUsedDownloadDirectory: boolean, preventsSleepWhileDownloading: boolean, mediaCookieSource: MediaCookieSource, siteLogins: Array, autoCheckUpdates: boolean, keychainAccessGranted: boolean, }; diff --git a/src/components/SettingsView.tsx b/src/components/SettingsView.tsx index 7267d76..a9a5576 100644 --- a/src/components/SettingsView.tsx +++ b/src/components/SettingsView.tsx @@ -1,6 +1,7 @@ import { useCallback, useRef, useState, useEffect } from 'react'; import { type AppFontSize, + type FontFamily, type ListRowDensity, type SidebarPosition, type WindowControlStyle, @@ -706,6 +707,13 @@ runEngineChecks(false); { value: 'gnome', label: t($ => $.settings.lookAndFeel.windowControlStyleGnome) }, { value: 'minimal', label: t($ => $.settings.lookAndFeel.windowControlStyleMinimal) }, ]; + const fontFamilyOptions: Array<{ value: FontFamily; label: string }> = [ + { value: 'system', label: t($ => $.settings.lookAndFeel.fontFamilySystem) }, + { value: 'inter', label: t($ => $.settings.lookAndFeel.fontFamilyInter) }, + { value: 'outfit', label: t($ => $.settings.lookAndFeel.fontFamilyOutfit) }, + { value: 'serif', label: t($ => $.settings.lookAndFeel.fontFamilySerif) }, + { value: 'monospace', label: t($ => $.settings.lookAndFeel.fontFamilyMonospace) }, + ]; const TabButton = ({ type, icon: Icon, label }: { type: SettingsTab; icon: typeof Download; label: string }) => { const active = activeTab === type; @@ -903,6 +911,21 @@ runEngineChecks(false);

{t($ => $.settings.lookAndFeel.display)}

+
+
+ {t($ => $.settings.lookAndFeel.fontFamily)} + {t($ => $.settings.lookAndFeel.fontFamilyDescription)} +
+ +
{t($ => $.settings.lookAndFeel.fontSize)} diff --git a/src/i18n/catalogs/en.ts b/src/i18n/catalogs/en.ts index 212ff1b..f92647a 100644 --- a/src/i18n/catalogs/en.ts +++ b/src/i18n/catalogs/en.ts @@ -618,6 +618,13 @@ const common = { nord: 'Nord', systemAppearance: 'System follows the current {{platform}} light or dark appearance.', display: 'Display', + fontFamily: 'Font', + fontFamilyDescription: 'Choose the interface font. Automatic uses platform and locale-aware system fallbacks.', + fontFamilySystem: 'System UI (recommended)', + fontFamilyInter: 'Inter', + fontFamilyOutfit: 'Outfit', + fontFamilySerif: 'Serif', + fontFamilyMonospace: 'Monospace', fontSize: 'Font size', fontSizeDescription: 'Scales the download list and compact controls.', small: 'Small', diff --git a/src/i18n/catalogs/fa.ts b/src/i18n/catalogs/fa.ts index ab52814..94b68c6 100644 --- a/src/i18n/catalogs/fa.ts +++ b/src/i18n/catalogs/fa.ts @@ -618,6 +618,13 @@ const fa = { nord: 'Nord', systemAppearance: 'برنامه از حالت روشن یا تاریک فعلی {{platform}} پیروی می‌کند.', display: 'نمایش', + fontFamily: 'فونت', + fontFamilyDescription: 'فونت رابط کاربری را انتخاب کنید. حالت خودکار از فونت‌های سیستمی متناسب با سیستم‌عامل و زبان استفاده می‌کند.', + fontFamilySystem: 'رابط کاربری سیستم (پیشنهادشده)', + fontFamilyInter: 'Inter', + fontFamilyOutfit: 'Outfit', + fontFamilySerif: 'سریف', + fontFamilyMonospace: 'تک‌فاصله', fontSize: 'اندازه قلم', fontSizeDescription: 'اندازه فهرست دانلود و کنترل‌های فشرده را تغییر می‌دهد.', small: 'کوچک', diff --git a/src/i18n/catalogs/he.ts b/src/i18n/catalogs/he.ts index 15ea875..cc2fc46 100644 --- a/src/i18n/catalogs/he.ts +++ b/src/i18n/catalogs/he.ts @@ -618,6 +618,13 @@ const he = { nord: 'Nord', systemAppearance: 'מערכת עוקבת אחר המראה הנוכחי (בהיר או כהה) של {{platform}}.', display: 'תצוגה', + fontFamily: 'גופן', + fontFamilyDescription: 'בחר את גופן הממשק. מצב אוטומטי משתמש בגופני מערכת המתאימים לפלטפורמה ולשפה.', + fontFamilySystem: 'ממשק המערכת (מומלץ)', + fontFamilyInter: 'Inter', + fontFamilyOutfit: 'Outfit', + fontFamilySerif: 'סריף', + fontFamilyMonospace: 'רוחב קבוע', fontSize: 'גודל גופן', fontSizeDescription: 'משנה את קנה המידה של רשימת ההורדות והפקדים הקומפקטיים.', small: 'קטן', diff --git a/src/i18n/catalogs/ru.ts b/src/i18n/catalogs/ru.ts index e358e6c..8407e33 100644 --- a/src/i18n/catalogs/ru.ts +++ b/src/i18n/catalogs/ru.ts @@ -618,6 +618,13 @@ const ru = { nord: 'Nord', systemAppearance: 'Системная тема соответствует текущему светлому или тёмному оформлению {{platform}}.', display: 'Отображение', + fontFamily: 'Шрифт', + fontFamilyDescription: 'Выберите шрифт интерфейса. Автоматический режим использует системные шрифты с учётом ОС и языка.', + fontFamilySystem: 'Системный интерфейс (рекомендуется)', + fontFamilyInter: 'Inter', + fontFamilyOutfit: 'Outfit', + fontFamilySerif: 'С засечками', + fontFamilyMonospace: 'Моноширинный', fontSize: 'Размер шрифта', fontSizeDescription: 'Масштабирует список загрузок и компактные элементы управления.', small: 'Мелкий', diff --git a/src/i18n/catalogs/uk.ts b/src/i18n/catalogs/uk.ts index 5465aa7..0ba23ad 100644 --- a/src/i18n/catalogs/uk.ts +++ b/src/i18n/catalogs/uk.ts @@ -618,6 +618,13 @@ const uk = { nord: 'Nord', systemAppearance: 'Система слідкує за поточним світлим або темним виглядом {{platform}}.', display: 'Відображення', + fontFamily: 'Шрифт', + fontFamilyDescription: 'Виберіть шрифт інтерфейсу. Автоматичний режим використовує системні шрифти з урахуванням ОС і мови.', + fontFamilySystem: 'Системний інтерфейс (рекомендовано)', + fontFamilyInter: 'Inter', + fontFamilyOutfit: 'Outfit', + fontFamilySerif: 'Із зарубками', + fontFamilyMonospace: 'Моноширинний', fontSize: 'Розмір шрифту', fontSizeDescription: 'Масштабує список завантажень та компактні елементи керування.', small: 'Дрібний', diff --git a/src/i18n/catalogs/zh-CN.ts b/src/i18n/catalogs/zh-CN.ts index ce38863..1d3dc2d 100644 --- a/src/i18n/catalogs/zh-CN.ts +++ b/src/i18n/catalogs/zh-CN.ts @@ -618,6 +618,13 @@ const zhCN = { nord: 'Nord', systemAppearance: '系统跟随当前的 {{platform}} 浅色或深色外观。', display: '显示', + fontFamily: '字体', + fontFamilyDescription: '选择界面字体。“自动”会使用适合操作系统和语言的系统字体及备用字体。', + fontFamilySystem: '系统界面字体(推荐)', + fontFamilyInter: 'Inter', + fontFamilyOutfit: 'Outfit', + fontFamilySerif: '衬线', + fontFamilyMonospace: '等宽', fontSize: '字体大小', fontSizeDescription: '缩放下载列表和紧凑型控件。', small: '小', diff --git a/src/i18n/resources.test.ts b/src/i18n/resources.test.ts index 920b81d..0cde90d 100644 --- a/src/i18n/resources.test.ts +++ b/src/i18n/resources.test.ts @@ -94,6 +94,8 @@ describe('translation catalogs', () => { 'settings.integrations.chromiumZip', 'settings.lookAndFeel.dracula', 'settings.lookAndFeel.nord', + 'settings.lookAndFeel.fontFamilyInter', + 'settings.lookAndFeel.fontFamilyOutfit', 'settings.lookAndFeel.windowControlStyleWindows', 'settings.lookAndFeel.windowControlStyleGnome', 'settings.network.chromeWindows', diff --git a/src/index.css b/src/index.css index 511919b..42f6cf5 100644 --- a/src/index.css +++ b/src/index.css @@ -223,8 +223,55 @@ --color-surface-overlay: hsl(var(--surface-overlay)); --color-bg-context-menu: hsl(var(--surface-overlay)); - /* Apple System Fonts */ - --font-sans: -apple-system, BlinkMacSystemFont, "SF Pro Text", "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; + /* + * Keep the default platform UI stack, and let each locale contribute a + * script-aware fallback. Optional custom families are bundled locally so + * the Tauri app remains usable offline. + */ + --font-locale-fallback: sans-serif; + --font-stack-system: -apple-system, BlinkMacSystemFont, "SF Pro Text", "Segoe UI", Roboto, Helvetica, Arial, var(--font-locale-fallback), "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; + --font-stack-inter: "Inter Variable", -apple-system, BlinkMacSystemFont, "SF Pro Text", "Segoe UI", Roboto, Helvetica, Arial, var(--font-locale-fallback), "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; + --font-stack-outfit: "Outfit Variable", -apple-system, BlinkMacSystemFont, "SF Pro Text", "Segoe UI", Roboto, Helvetica, Arial, var(--font-locale-fallback), "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; + --font-stack-serif: Georgia, "Times New Roman", var(--font-locale-fallback), serif; + --font-stack-monospace: ui-monospace, "SF Mono", Monaco, "Cascadia Code", "Fira Code", "JetBrains Mono", var(--font-locale-fallback), monospace; + --font-sans: var(--font-stack-system); +} + +html[lang="fa"] { + --font-locale-fallback: "Vazirmatn", "Noto Sans Arabic", "Segoe UI", Tahoma, Arial, sans-serif; +} + +html[lang="he"] { + --font-locale-fallback: "Arial Hebrew", "Noto Sans Hebrew", "Segoe UI", Arial, sans-serif; +} + +html[lang="zh-CN"] { + --font-locale-fallback: "PingFang SC", "Microsoft YaHei UI", "Noto Sans CJK SC", "Noto Sans SC", sans-serif; +} + +html[lang="ru"], +html[lang="uk"] { + --font-locale-fallback: "Segoe UI", "Noto Sans", Roboto, Arial, sans-serif; +} + +html[data-font-family="system"] { + --font-sans: var(--font-stack-system); +} + +html[data-font-family="inter"] { + --font-sans: var(--font-stack-inter); +} + +html[data-font-family="outfit"] { + --font-sans: var(--font-stack-outfit); +} + +html[data-font-family="serif"] { + --font-sans: var(--font-stack-serif); +} + +html[data-font-family="monospace"] { + --font-sans: var(--font-stack-monospace); } @layer base { diff --git a/src/main.tsx b/src/main.tsx index 33cf060..60bb629 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -1,5 +1,7 @@ import { StrictMode } from "react"; import { createRoot } from "react-dom/client"; +import "@fontsource-variable/inter/wght.css"; +import "@fontsource-variable/outfit/wght.css"; import "./index.css"; import App from "./App"; import { i18nReady } from "./i18n"; diff --git a/src/store/useSettingsStore.ts b/src/store/useSettingsStore.ts index b0f578b..0744f7a 100644 --- a/src/store/useSettingsStore.ts +++ b/src/store/useSettingsStore.ts @@ -4,6 +4,7 @@ import { invokeCommand as invoke } from '../ipc'; import { info } from '../utils/logger'; import type { ActiveView } from '../bindings/ActiveView'; import type { AppFontSize } from '../bindings/AppFontSize'; +import type { FontFamily } from '../bindings/FontFamily'; import type { ListRowDensity } from '../bindings/ListRowDensity'; import type { MediaCookieSource } from '../bindings/MediaCookieSource'; import type { PostQueueAction } from '../bindings/PostQueueAction'; @@ -74,6 +75,7 @@ const notifySettingsPersistenceError = () => { }; const THEME_VALUES = ['system', 'light', 'dark', 'dracula', 'nord'] as const; +const FONT_FAMILY_VALUES = ['system', 'inter', 'outfit', 'serif', 'monospace'] as const; const WINDOW_CONTROL_STYLE_VALUES = ['auto', 'macos', 'windows', 'gnome', 'minimal'] as const; const APP_FONT_SIZE_VALUES = ['small', 'standard', 'large'] as const; const LIST_ROW_DENSITY_VALUES = ['compact', 'standard', 'relaxed'] as const; @@ -162,6 +164,7 @@ const tauriStorage: StateStorage = { export type { ActiveView, AppFontSize, + FontFamily, CalendarPreference, ListRowDensity, MediaCookieSource, @@ -178,6 +181,7 @@ export type SidebarPosition = 'auto' | 'left' | 'right'; export interface SettingsState { theme: Theme; + fontFamily: FontFamily; windowControlStyle: WindowControlStyle; calendarPreference: CalendarPreference; language: AppLocalePreference; @@ -235,6 +239,7 @@ export interface SettingsState { showKeychainModal: boolean; setTheme: (theme: Theme) => void; + setFontFamily: (fontFamily: FontFamily) => void; setWindowControlStyle: (style: WindowControlStyle) => void; setCalendarPreference: (calendarPreference: CalendarPreference) => void; setLanguage: (language: AppLocalePreference) => void; @@ -293,6 +298,7 @@ export const useSettingsStore = create()( persist( (set, get) => ({ theme: 'system', + fontFamily: 'system', windowControlStyle: 'auto', calendarPreference: DEFAULT_CALENDAR_PREFERENCE, language: 'system', @@ -357,6 +363,10 @@ export const useSettingsStore = create()( showKeychainModal: false, setTheme: (theme) => { info('Settings updated: theme'); set({ theme }); }, + setFontFamily: (fontFamily) => { + info('Settings updated: fontFamily'); + set({ fontFamily }); + }, setWindowControlStyle: (windowControlStyle) => { info('Settings updated: windowControlStyle'); set({ windowControlStyle }); @@ -571,6 +581,7 @@ export const useSettingsStore = create()( }, partialize: (state): PersistedSettingsSnapshot => ({ theme: state.theme, + fontFamily: state.fontFamily, windowControlStyle: state.windowControlStyle, calendarPreference: state.calendarPreference, language: state.language, @@ -633,6 +644,9 @@ export const useSettingsStore = create()( theme: isAllowedSetting(THEME_VALUES, persisted.theme) ? persisted.theme : currentState.theme, + fontFamily: isAllowedSetting(FONT_FAMILY_VALUES, persisted.fontFamily) + ? persisted.fontFamily + : currentState.fontFamily, windowControlStyle: isAllowedSetting(WINDOW_CONTROL_STYLE_VALUES, persisted.windowControlStyle) ? persisted.windowControlStyle : currentState.windowControlStyle,