mirror of
https://github.com/SECTL/SecScore.git
synced 2026-07-19 19:04:23 +08:00
fix: 字体选择列表加载不了的问题
feat: 整理项目文件
This commit is contained in:
Binary file not shown.
@@ -1,6 +1,8 @@
|
|||||||
use parking_lot::RwLock;
|
use parking_lot::RwLock;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_json::Value as JsonValue;
|
use serde_json::Value as JsonValue;
|
||||||
|
use std::collections::BTreeSet;
|
||||||
|
use std::process::Command;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use tauri::{AppHandle, Emitter, State};
|
use tauri::{AppHandle, Emitter, State};
|
||||||
|
|
||||||
@@ -158,3 +160,135 @@ pub async fn settings_set(
|
|||||||
|
|
||||||
Ok(IpcResponse::success(()))
|
Ok(IpcResponse::success(()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn fallback_font_families() -> Vec<String> {
|
||||||
|
vec![
|
||||||
|
"系统默认".to_string(),
|
||||||
|
"Microsoft YaHei UI".to_string(),
|
||||||
|
"Microsoft YaHei".to_string(),
|
||||||
|
"PingFang SC".to_string(),
|
||||||
|
"Noto Sans CJK SC".to_string(),
|
||||||
|
"Source Han Sans SC".to_string(),
|
||||||
|
"Segoe UI".to_string(),
|
||||||
|
"Arial".to_string(),
|
||||||
|
"Helvetica Neue".to_string(),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn normalize_font_name(value: &str) -> String {
|
||||||
|
value
|
||||||
|
.trim()
|
||||||
|
.trim_start_matches('@')
|
||||||
|
.replace(" (TrueType)", "")
|
||||||
|
.replace(" (OpenType)", "")
|
||||||
|
.replace(" (All res)", "")
|
||||||
|
.trim()
|
||||||
|
.to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
fn query_system_fonts() -> Vec<String> {
|
||||||
|
let output = Command::new("reg")
|
||||||
|
.args([
|
||||||
|
"query",
|
||||||
|
r"HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Fonts",
|
||||||
|
])
|
||||||
|
.output();
|
||||||
|
|
||||||
|
let Ok(output) = output else {
|
||||||
|
return Vec::new();
|
||||||
|
};
|
||||||
|
if !output.status.success() {
|
||||||
|
return Vec::new();
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut families = BTreeSet::new();
|
||||||
|
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||||
|
for line in stdout.lines() {
|
||||||
|
if !line.contains("REG_") {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let Some((left, _)) = line.split_once("REG_") else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
let name = normalize_font_name(left);
|
||||||
|
if !name.is_empty() {
|
||||||
|
families.insert(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
families.into_iter().collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
fn query_system_fonts() -> Vec<String> {
|
||||||
|
let output = Command::new("fc-list").args([":", "family"]).output();
|
||||||
|
let Ok(output) = output else {
|
||||||
|
return Vec::new();
|
||||||
|
};
|
||||||
|
if !output.status.success() {
|
||||||
|
return Vec::new();
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut families = BTreeSet::new();
|
||||||
|
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||||
|
for line in stdout.lines() {
|
||||||
|
for part in line.split(',') {
|
||||||
|
let name = normalize_font_name(part);
|
||||||
|
if !name.is_empty() {
|
||||||
|
families.insert(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
families.into_iter().collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
fn query_system_fonts() -> Vec<String> {
|
||||||
|
let output = Command::new("system_profiler")
|
||||||
|
.args(["SPFontsDataType", "-detailLevel", "mini"])
|
||||||
|
.output();
|
||||||
|
let Ok(output) = output else {
|
||||||
|
return Vec::new();
|
||||||
|
};
|
||||||
|
if !output.status.success() {
|
||||||
|
return Vec::new();
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut families = BTreeSet::new();
|
||||||
|
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||||
|
for line in stdout.lines() {
|
||||||
|
let trimmed = line.trim();
|
||||||
|
if let Some(name) = trimmed.strip_prefix("Full Name:") {
|
||||||
|
let normalized = normalize_font_name(name);
|
||||||
|
if !normalized.is_empty() {
|
||||||
|
families.insert(normalized);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
families.into_iter().collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(any(target_os = "windows", target_os = "linux", target_os = "macos")))]
|
||||||
|
fn query_system_fonts() -> Vec<String> {
|
||||||
|
Vec::new()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn settings_get_system_fonts() -> Result<IpcResponse<Vec<String>>, String> {
|
||||||
|
let mut fonts = query_system_fonts();
|
||||||
|
if fonts.is_empty() {
|
||||||
|
fonts = fallback_font_families();
|
||||||
|
} else {
|
||||||
|
let mut merged = BTreeSet::new();
|
||||||
|
for item in fallback_font_families() {
|
||||||
|
merged.insert(item);
|
||||||
|
}
|
||||||
|
for item in fonts {
|
||||||
|
merged.insert(item);
|
||||||
|
}
|
||||||
|
fonts = merged.into_iter().collect();
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(IpcResponse::success(fonts))
|
||||||
|
}
|
||||||
|
|||||||
@@ -68,6 +68,7 @@ pub fn run() {
|
|||||||
settings_get_all,
|
settings_get_all,
|
||||||
settings_get,
|
settings_get,
|
||||||
settings_set,
|
settings_set,
|
||||||
|
settings_get_system_fonts,
|
||||||
auth_get_status,
|
auth_get_status,
|
||||||
auth_login,
|
auth_login,
|
||||||
auth_logout,
|
auth_logout,
|
||||||
@@ -155,7 +156,11 @@ fn setup_deep_link(app: &mut App) -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
use tauri_plugin_deep_link::DeepLinkExt;
|
use tauri_plugin_deep_link::DeepLinkExt;
|
||||||
|
|
||||||
app.deep_link().on_open_url(move |event| {
|
app.deep_link().on_open_url(move |event| {
|
||||||
let url = event.urls().first().map(|u| u.to_string()).unwrap_or_default();
|
let url = event
|
||||||
|
.urls()
|
||||||
|
.first()
|
||||||
|
.map(|u| u.to_string())
|
||||||
|
.unwrap_or_default();
|
||||||
if !url.is_empty() {
|
if !url.is_empty() {
|
||||||
let _ = handle.emit("deep-link://new-url", url);
|
let _ = handle.emit("deep-link://new-url", url);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ pub struct SettingsSpec {
|
|||||||
pub window_zoom: f64,
|
pub window_zoom: f64,
|
||||||
pub search_keyboard_layout: String,
|
pub search_keyboard_layout: String,
|
||||||
pub disable_search_keyboard: bool,
|
pub disable_search_keyboard: bool,
|
||||||
|
pub font_family: String,
|
||||||
pub themes_custom: JsonValue,
|
pub themes_custom: JsonValue,
|
||||||
pub auto_score_enabled: bool,
|
pub auto_score_enabled: bool,
|
||||||
pub auto_score_rules: JsonValue,
|
pub auto_score_rules: JsonValue,
|
||||||
@@ -28,6 +29,7 @@ impl Default for SettingsSpec {
|
|||||||
window_zoom: 1.0,
|
window_zoom: 1.0,
|
||||||
search_keyboard_layout: "qwerty26".to_string(),
|
search_keyboard_layout: "qwerty26".to_string(),
|
||||||
disable_search_keyboard: false,
|
disable_search_keyboard: false,
|
||||||
|
font_family: "system".to_string(),
|
||||||
themes_custom: JsonValue::Array(vec![]),
|
themes_custom: JsonValue::Array(vec![]),
|
||||||
auto_score_enabled: false,
|
auto_score_enabled: false,
|
||||||
auto_score_rules: JsonValue::Array(vec![]),
|
auto_score_rules: JsonValue::Array(vec![]),
|
||||||
@@ -58,6 +60,7 @@ pub enum SettingsKey {
|
|||||||
WindowZoom,
|
WindowZoom,
|
||||||
SearchKeyboardLayout,
|
SearchKeyboardLayout,
|
||||||
DisableSearchKeyboard,
|
DisableSearchKeyboard,
|
||||||
|
FontFamily,
|
||||||
ThemesCustom,
|
ThemesCustom,
|
||||||
AutoScoreEnabled,
|
AutoScoreEnabled,
|
||||||
AutoScoreRules,
|
AutoScoreRules,
|
||||||
@@ -76,6 +79,7 @@ impl SettingsKey {
|
|||||||
SettingsKey::WindowZoom => "window_zoom",
|
SettingsKey::WindowZoom => "window_zoom",
|
||||||
SettingsKey::SearchKeyboardLayout => "search_keyboard_layout",
|
SettingsKey::SearchKeyboardLayout => "search_keyboard_layout",
|
||||||
SettingsKey::DisableSearchKeyboard => "disable_search_keyboard",
|
SettingsKey::DisableSearchKeyboard => "disable_search_keyboard",
|
||||||
|
SettingsKey::FontFamily => "font_family",
|
||||||
SettingsKey::ThemesCustom => "themes_custom",
|
SettingsKey::ThemesCustom => "themes_custom",
|
||||||
SettingsKey::AutoScoreEnabled => "auto_score_enabled",
|
SettingsKey::AutoScoreEnabled => "auto_score_enabled",
|
||||||
SettingsKey::AutoScoreRules => "auto_score_rules",
|
SettingsKey::AutoScoreRules => "auto_score_rules",
|
||||||
@@ -94,6 +98,7 @@ impl SettingsKey {
|
|||||||
"window_zoom" => Some(SettingsKey::WindowZoom),
|
"window_zoom" => Some(SettingsKey::WindowZoom),
|
||||||
"search_keyboard_layout" => Some(SettingsKey::SearchKeyboardLayout),
|
"search_keyboard_layout" => Some(SettingsKey::SearchKeyboardLayout),
|
||||||
"disable_search_keyboard" => Some(SettingsKey::DisableSearchKeyboard),
|
"disable_search_keyboard" => Some(SettingsKey::DisableSearchKeyboard),
|
||||||
|
"font_family" => Some(SettingsKey::FontFamily),
|
||||||
"themes_custom" => Some(SettingsKey::ThemesCustom),
|
"themes_custom" => Some(SettingsKey::ThemesCustom),
|
||||||
"auto_score_enabled" => Some(SettingsKey::AutoScoreEnabled),
|
"auto_score_enabled" => Some(SettingsKey::AutoScoreEnabled),
|
||||||
"auto_score_rules" => Some(SettingsKey::AutoScoreRules),
|
"auto_score_rules" => Some(SettingsKey::AutoScoreRules),
|
||||||
@@ -327,6 +332,22 @@ impl SettingsService {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
defs.insert(
|
||||||
|
SettingsKey::FontFamily,
|
||||||
|
SettingDefinition {
|
||||||
|
kind: SettingValueKind::String,
|
||||||
|
default_value: SettingsValue::String("system".to_string()),
|
||||||
|
write_permission: PermissionRequirement::Admin,
|
||||||
|
validate: Some(|v| {
|
||||||
|
if let SettingsValue::String(s) = v {
|
||||||
|
!s.trim().is_empty()
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
defs.insert(
|
defs.insert(
|
||||||
SettingsKey::ThemesCustom,
|
SettingsKey::ThemesCustom,
|
||||||
SettingDefinition {
|
SettingDefinition {
|
||||||
@@ -549,6 +570,10 @@ impl SettingsService {
|
|||||||
SettingsValue::Boolean(b) => b,
|
SettingsValue::Boolean(b) => b,
|
||||||
_ => false,
|
_ => false,
|
||||||
},
|
},
|
||||||
|
font_family: match self.get_value(SettingsKey::FontFamily) {
|
||||||
|
SettingsValue::String(s) => s,
|
||||||
|
_ => "system".to_string(),
|
||||||
|
},
|
||||||
themes_custom: match self.get_value(SettingsKey::ThemesCustom) {
|
themes_custom: match self.get_value(SettingsKey::ThemesCustom) {
|
||||||
SettingsValue::Json(j) => j,
|
SettingsValue::Json(j) => j,
|
||||||
_ => JsonValue::Array(vec![]),
|
_ => JsonValue::Array(vec![]),
|
||||||
|
|||||||
+27
-4
@@ -29,8 +29,8 @@ import { useTranslation } from "react-i18next"
|
|||||||
import { Sidebar } from "./components/Sidebar"
|
import { Sidebar } from "./components/Sidebar"
|
||||||
import { ContentArea } from "./components/ContentArea"
|
import { ContentArea } from "./components/ContentArea"
|
||||||
import { OOBE } from "./components/OOBE/OOBE"
|
import { OOBE } from "./components/OOBE/OOBE"
|
||||||
import { OAuthLogin } from "./components/OAuthLogin"
|
import { OAuthLogin } from "./components/OAuth/OAuthLogin"
|
||||||
import { OAuthCallback } from "./components/OAuthCallback"
|
import { OAuthCallback } from "./components/OAuth/OAuthCallback"
|
||||||
import { ThemeProvider, useTheme } from "./contexts/ThemeContext"
|
import { ThemeProvider, useTheme } from "./contexts/ThemeContext"
|
||||||
import { MOBILE_NAV_ITEMS, MobileNavKey, sanitizeMobileNavKeys } from "./shared/mobileNavigation"
|
import { MOBILE_NAV_ITEMS, MobileNavKey, sanitizeMobileNavKeys } from "./shared/mobileNavigation"
|
||||||
|
|
||||||
@@ -39,6 +39,23 @@ const DEFAULT_MOBILE_BOTTOM_PRIMARY_KEYS: MobileNavKey[] = DEFAULT_MOBILE_BOTTOM
|
|||||||
0,
|
0,
|
||||||
4
|
4
|
||||||
)
|
)
|
||||||
|
const SYSTEM_FONT_STACK =
|
||||||
|
'"PingFang SC", "PingFangTC-Regular", "Hiragino Sans GB", "Hiragino Sans", "STHeiti", "Heiti SC", "Noto Sans CJK SC", "Noto Sans SC", "Source Han Sans SC", "Microsoft YaHei UI", "Microsoft YaHei", "微软雅黑", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Apple Color Emoji", "Segoe UI Emoji", sans-serif'
|
||||||
|
|
||||||
|
const resolveFontFamily = (fontValue?: string): string => {
|
||||||
|
if (!fontValue || fontValue === "system") return SYSTEM_FONT_STACK
|
||||||
|
if (fontValue.startsWith("system-")) {
|
||||||
|
const family = fontValue.slice("system-".length).trim()
|
||||||
|
if (family) return `"${family}", ${SYSTEM_FONT_STACK}`
|
||||||
|
}
|
||||||
|
return SYSTEM_FONT_STACK
|
||||||
|
}
|
||||||
|
|
||||||
|
const applyGlobalFontFamily = (fontValue?: string) => {
|
||||||
|
const fontFamily = resolveFontFamily(fontValue)
|
||||||
|
document.documentElement.style.setProperty("--ss-font-family", fontFamily)
|
||||||
|
document.body.style.fontFamily = fontFamily
|
||||||
|
}
|
||||||
|
|
||||||
function MainContent(): React.JSX.Element {
|
function MainContent(): React.JSX.Element {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
@@ -163,6 +180,7 @@ function MainContent(): React.JSX.Element {
|
|||||||
const settingsRes = await (window as any).api.getAllSettings()
|
const settingsRes = await (window as any).api.getAllSettings()
|
||||||
if (settingsRes?.success && settingsRes.data) {
|
if (settingsRes?.success && settingsRes.data) {
|
||||||
setMobileBottomNavItems(normalizeStoredBottomKeys(settingsRes.data.mobile_bottom_nav_items))
|
setMobileBottomNavItems(normalizeStoredBottomKeys(settingsRes.data.mobile_bottom_nav_items))
|
||||||
|
applyGlobalFontFamily(settingsRes.data.font_family)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -178,8 +196,13 @@ function MainContent(): React.JSX.Element {
|
|||||||
|
|
||||||
api
|
api
|
||||||
.onSettingChanged((change: { key?: string; value?: unknown }) => {
|
.onSettingChanged((change: { key?: string; value?: unknown }) => {
|
||||||
if (change?.key !== "mobile_bottom_nav_items") return
|
if (change?.key === "mobile_bottom_nav_items") {
|
||||||
setMobileBottomNavItems(normalizeStoredBottomKeys(change.value))
|
setMobileBottomNavItems(normalizeStoredBottomKeys(change.value))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (change?.key === "font_family") {
|
||||||
|
applyGlobalFontFamily(String(change.value || "system"))
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.then((fn: () => void) => {
|
.then((fn: () => void) => {
|
||||||
if (disposed) {
|
if (disposed) {
|
||||||
|
|||||||
@@ -18,8 +18,8 @@ import { type ImmutableTree } from "@react-awesome-query-builder/antd"
|
|||||||
import type { ColumnsType } from "antd/es/table"
|
import type { ColumnsType } from "antd/es/table"
|
||||||
import { useTranslation } from "react-i18next"
|
import { useTranslation } from "react-i18next"
|
||||||
import { fetchAllTags } from "./TagEditorDialog"
|
import { fetchAllTags } from "./TagEditorDialog"
|
||||||
import { ActionEditor } from "./AutoScoreManagerPage/ActionEditor"
|
import { ActionEditor } from "./AutoScore/ActionEditor"
|
||||||
import { TriggerRuleBuilder } from "./AutoScoreManagerPage/TriggerRuleBuilder"
|
import { TriggerRuleBuilder } from "./AutoScore/TriggerRuleBuilder"
|
||||||
import {
|
import {
|
||||||
actionDraftsToPayload,
|
actionDraftsToPayload,
|
||||||
actionsToDrafts,
|
actionsToDrafts,
|
||||||
@@ -33,7 +33,7 @@ import {
|
|||||||
type ActionDraft,
|
type ActionDraft,
|
||||||
type AutoScoreRule,
|
type AutoScoreRule,
|
||||||
type AutoScoreTagOption,
|
type AutoScoreTagOption,
|
||||||
} from "./AutoScoreManagerPage/AutoScoreUtils"
|
} from "./AutoScore/AutoScoreUtils"
|
||||||
|
|
||||||
interface StudentItem {
|
interface StudentItem {
|
||||||
id: number
|
id: number
|
||||||
|
|||||||
+117
-36
@@ -16,7 +16,7 @@ import {
|
|||||||
Typography,
|
Typography,
|
||||||
} from "antd"
|
} from "antd"
|
||||||
import { ThemeQuickSettings } from "./ThemeQuickSettings"
|
import { ThemeQuickSettings } from "./ThemeQuickSettings"
|
||||||
import { OAuthLogin } from "./OAuthLogin"
|
import { OAuthLogin } from "./OAuth/OAuthLogin"
|
||||||
import { useTranslation } from "react-i18next"
|
import { useTranslation } from "react-i18next"
|
||||||
import { changeLanguage, getCurrentLanguage, languageOptions, AppLanguage } from "../i18n"
|
import { changeLanguage, getCurrentLanguage, languageOptions, AppLanguage } from "../i18n"
|
||||||
import { useResponsive } from "../hooks/useResponsive"
|
import { useResponsive } from "../hooks/useResponsive"
|
||||||
@@ -39,6 +39,62 @@ interface FontOption {
|
|||||||
fontFamily: string
|
fontFamily: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const SYSTEM_FONT_STACK =
|
||||||
|
'"PingFang SC", "PingFangTC-Regular", "Hiragino Sans GB", "Hiragino Sans", "STHeiti", "Heiti SC", "Noto Sans CJK SC", "Noto Sans SC", "Source Han Sans SC", "Microsoft YaHei UI", "Microsoft YaHei", "微软雅黑", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Apple Color Emoji", "Segoe UI Emoji", sans-serif'
|
||||||
|
|
||||||
|
const defaultFontOptions: FontOption[] = [
|
||||||
|
{
|
||||||
|
value: "system",
|
||||||
|
label: "系统默认",
|
||||||
|
fontFamily: SYSTEM_FONT_STACK,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "system-Microsoft YaHei UI",
|
||||||
|
label: "Microsoft YaHei UI",
|
||||||
|
fontFamily: '"Microsoft YaHei UI", "Microsoft YaHei", "微软雅黑", sans-serif',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "system-Microsoft YaHei",
|
||||||
|
label: "Microsoft YaHei",
|
||||||
|
fontFamily: '"Microsoft YaHei", "微软雅黑", sans-serif',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "system-PingFang SC",
|
||||||
|
label: "PingFang SC",
|
||||||
|
fontFamily: '"PingFang SC", "Hiragino Sans GB", sans-serif',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "system-Noto Sans CJK SC",
|
||||||
|
label: "Noto Sans CJK SC",
|
||||||
|
fontFamily: '"Noto Sans CJK SC", "Noto Sans SC", "Source Han Sans SC", sans-serif',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "system-Segoe UI",
|
||||||
|
label: "Segoe UI",
|
||||||
|
fontFamily: '"Segoe UI", sans-serif',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "system-Arial",
|
||||||
|
label: "Arial",
|
||||||
|
fontFamily: "Arial, sans-serif",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
const mergeFontOptions = (options: FontOption[]): FontOption[] => {
|
||||||
|
const map = new Map<string, FontOption>()
|
||||||
|
for (const option of options) {
|
||||||
|
if (!map.has(option.value)) {
|
||||||
|
map.set(option.value, option)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Array.from(map.values())
|
||||||
|
}
|
||||||
|
|
||||||
|
const findFontOption = (options: FontOption[], value?: string): FontOption | undefined => {
|
||||||
|
if (!value) return options.find((item) => item.value === "system") || options[0]
|
||||||
|
return options.find((item) => item.value === value) || options.find((item) => item.value === "system")
|
||||||
|
}
|
||||||
|
|
||||||
const applyFontFamily = (fontFamily: string) => {
|
const applyFontFamily = (fontFamily: string) => {
|
||||||
document.documentElement.style.setProperty("--ss-font-family", fontFamily)
|
document.documentElement.style.setProperty("--ss-font-family", fontFamily)
|
||||||
document.body.style.fontFamily = fontFamily
|
document.body.style.fontFamily = fontFamily
|
||||||
@@ -76,8 +132,9 @@ export const Settings: React.FC<{
|
|||||||
search_keyboard_layout: "qwerty26",
|
search_keyboard_layout: "qwerty26",
|
||||||
disable_search_keyboard: false,
|
disable_search_keyboard: false,
|
||||||
})
|
})
|
||||||
const [fontOptions, setFontOptions] = useState<FontOption[]>([])
|
const [fontOptions, setFontOptions] = useState<FontOption[]>(defaultFontOptions)
|
||||||
const [isLoadingFonts, setIsLoadingFonts] = useState(true)
|
const [isLoadingFonts, setIsLoadingFonts] = useState(false)
|
||||||
|
const fontOptionsRef = useRef<FontOption[]>(defaultFontOptions)
|
||||||
|
|
||||||
const [securityStatus, setSecurityStatus] = useState<{
|
const [securityStatus, setSecurityStatus] = useState<{
|
||||||
permission: permissionLevel
|
permission: permissionLevel
|
||||||
@@ -168,6 +225,10 @@ export const Settings: React.FC<{
|
|||||||
)
|
)
|
||||||
}, [permission, t])
|
}, [permission, t])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fontOptionsRef.current = fontOptions
|
||||||
|
}, [fontOptions])
|
||||||
|
|
||||||
const emitDataUpdated = (category: "events" | "students" | "reasons" | "all") => {
|
const emitDataUpdated = (category: "events" | "students" | "reasons" | "all") => {
|
||||||
window.dispatchEvent(new CustomEvent("ss:data-updated", { detail: { category } }))
|
window.dispatchEvent(new CustomEvent("ss:data-updated", { detail: { category } }))
|
||||||
}
|
}
|
||||||
@@ -187,54 +248,73 @@ export const Settings: React.FC<{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const loadSystemFonts = async () => {
|
const loadSystemFonts = async (selectedFontValue?: string) => {
|
||||||
if (!(window as any).api?.getSystemFonts) return
|
|
||||||
|
|
||||||
setIsLoadingFonts(true)
|
setIsLoadingFonts(true)
|
||||||
|
|
||||||
|
const applySelectedFont = (options: FontOption[]) => {
|
||||||
|
const current = findFontOption(options, selectedFontValue || settings.font_family)
|
||||||
|
if (current) applyFontFamily(current.fontFamily)
|
||||||
|
}
|
||||||
|
|
||||||
|
const api = (window as any).api
|
||||||
|
if (!api?.getSystemFonts) {
|
||||||
|
setFontOptions(defaultFontOptions)
|
||||||
|
applySelectedFont(defaultFontOptions)
|
||||||
|
setIsLoadingFonts(false)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await (window as any).api.getSystemFonts()
|
const res = await api.getSystemFonts()
|
||||||
if (res.success && Array.isArray(res.data)) {
|
if (res.success && Array.isArray(res.data)) {
|
||||||
const fontList = res.data
|
const remoteOptions = res.data
|
||||||
.map((name: string) => ({
|
.map((name: string) => String(name || "").trim())
|
||||||
value: `system-${name}`,
|
.filter((name: string) => Boolean(name))
|
||||||
label: name,
|
.map(
|
||||||
fontFamily: `"${name}"`,
|
(name: string) =>
|
||||||
})) as FontOption[]
|
({
|
||||||
|
value: `system-${name}`,
|
||||||
|
label: name,
|
||||||
|
fontFamily: `"${name}", ${SYSTEM_FONT_STACK}`,
|
||||||
|
}) satisfies FontOption
|
||||||
|
)
|
||||||
|
|
||||||
setFontOptions(fontList)
|
const mergedOptions = mergeFontOptions([...defaultFontOptions, ...remoteOptions])
|
||||||
|
setFontOptions(mergedOptions)
|
||||||
if (settings.font_family) {
|
applySelectedFont(mergedOptions)
|
||||||
const currentFontOpt = fontList.find((f) => f.value === settings.font_family)
|
} else {
|
||||||
if (currentFontOpt) {
|
setFontOptions(defaultFontOptions)
|
||||||
applyFontFamily(currentFontOpt.fontFamily)
|
applySelectedFont(defaultFontOptions)
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to load system fonts:", error)
|
console.error("Failed to load system fonts:", error)
|
||||||
|
setFontOptions(defaultFontOptions)
|
||||||
|
applySelectedFont(defaultFontOptions)
|
||||||
} finally {
|
} finally {
|
||||||
setIsLoadingFonts(false)
|
setIsLoadingFonts(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const loadAll = async () => {
|
const loadAll = async () => {
|
||||||
if (!(window as any).api) return
|
const api = (window as any).api
|
||||||
const res = await (window as any).api.getAllSettings()
|
if (!api) {
|
||||||
|
setFontOptions(defaultFontOptions)
|
||||||
|
setIsLoadingFonts(false)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let savedFontFamily = settings.font_family || "system"
|
||||||
|
const res = await api.getAllSettings()
|
||||||
if (res.success && res.data) {
|
if (res.success && res.data) {
|
||||||
setSettings(res.data)
|
setSettings(res.data)
|
||||||
setPgConnectionString(res.data.pg_connection_string || "")
|
setPgConnectionString(res.data.pg_connection_string || "")
|
||||||
setPgConnectionStatus(res.data.pg_connection_status || { connected: true, type: "sqlite" })
|
setPgConnectionStatus(res.data.pg_connection_status || { connected: true, type: "sqlite" })
|
||||||
if (res.data.font_family) {
|
savedFontFamily = res.data.font_family || "system"
|
||||||
const fontOpt = fontOptions.find((f) => f.value === res.data.font_family)
|
|
||||||
if (fontOpt) {
|
|
||||||
applyFontFamily(fontOpt.fontFamily)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
const authRes = await (window as any).api.authGetStatus()
|
const authRes = await api.authGetStatus()
|
||||||
if (authRes.success && authRes.data) setSecurityStatus(authRes.data)
|
if (authRes.success && authRes.data) setSecurityStatus(authRes.data)
|
||||||
await loadMcpStatus()
|
await loadMcpStatus()
|
||||||
await loadSystemFonts()
|
await loadSystemFonts(savedFontFamily)
|
||||||
}
|
}
|
||||||
|
|
||||||
const loadAboutContent = async () => {
|
const loadAboutContent = async () => {
|
||||||
@@ -272,11 +352,12 @@ export const Settings: React.FC<{
|
|||||||
if (change?.key === "auto_score_enabled")
|
if (change?.key === "auto_score_enabled")
|
||||||
return { ...prev, auto_score_enabled: change.value }
|
return { ...prev, auto_score_enabled: change.value }
|
||||||
if (change?.key === "font_family") {
|
if (change?.key === "font_family") {
|
||||||
const fontOpt = fontOptions.find((f) => f.value === change.value)
|
const value = String(change.value || "system")
|
||||||
|
const fontOpt = findFontOption(fontOptionsRef.current, value)
|
||||||
if (fontOpt) {
|
if (fontOpt) {
|
||||||
applyFontFamily(fontOpt.fontFamily)
|
applyFontFamily(fontOpt.fontFamily)
|
||||||
}
|
}
|
||||||
return { ...prev, font_family: change.value }
|
return { ...prev, font_family: value }
|
||||||
}
|
}
|
||||||
return prev
|
return prev
|
||||||
})
|
})
|
||||||
@@ -726,12 +807,12 @@ export const Settings: React.FC<{
|
|||||||
<Select
|
<Select
|
||||||
value={settings.font_family || "system"}
|
value={settings.font_family || "system"}
|
||||||
onChange={async (v) => {
|
onChange={async (v) => {
|
||||||
const fontOpt = fontOptions.find((f) => f.value === v)
|
const fontOpt = findFontOption(fontOptions, String(v))
|
||||||
if (!fontOpt) return
|
if (!fontOpt) return
|
||||||
applyFontFamily(fontOpt.fontFamily)
|
applyFontFamily(fontOpt.fontFamily)
|
||||||
setSettings((prev) => ({ ...prev, font_family: v }))
|
setSettings((prev) => ({ ...prev, font_family: String(v) }))
|
||||||
if ((window as any).api) {
|
if ((window as any).api) {
|
||||||
const res = await (window as any).api.setSetting("font_family", v)
|
const res = await (window as any).api.setSetting("font_family", String(v))
|
||||||
if (res.success) {
|
if (res.success) {
|
||||||
messageApi.success(t("settings.general.saved"))
|
messageApi.success(t("settings.general.saved"))
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ export type settingsKey =
|
|||||||
| "window_zoom"
|
| "window_zoom"
|
||||||
| "search_keyboard_layout"
|
| "search_keyboard_layout"
|
||||||
| "disable_search_keyboard"
|
| "disable_search_keyboard"
|
||||||
|
| "font_family"
|
||||||
| "themes_custom"
|
| "themes_custom"
|
||||||
| "auto_score_enabled"
|
| "auto_score_enabled"
|
||||||
| "auto_score_rules"
|
| "auto_score_rules"
|
||||||
@@ -63,6 +64,7 @@ export interface settingsSpec {
|
|||||||
window_zoom: number
|
window_zoom: number
|
||||||
search_keyboard_layout: "t9" | "qwerty26"
|
search_keyboard_layout: "t9" | "qwerty26"
|
||||||
disable_search_keyboard: boolean
|
disable_search_keyboard: boolean
|
||||||
|
font_family: string
|
||||||
themes_custom: themeConfig[]
|
themes_custom: themeConfig[]
|
||||||
auto_score_enabled: boolean
|
auto_score_enabled: boolean
|
||||||
auto_score_rules: autoScoreRule[]
|
auto_score_rules: autoScoreRule[]
|
||||||
@@ -311,6 +313,8 @@ const api = {
|
|||||||
key: K,
|
key: K,
|
||||||
value: settingsSpec[K]
|
value: settingsSpec[K]
|
||||||
): Promise<{ success: boolean }> => invoke("settings_set", { key, value }),
|
): Promise<{ success: boolean }> => invoke("settings_set", { key, value }),
|
||||||
|
getSystemFonts: (): Promise<{ success: boolean; data: string[]; message?: string }> =>
|
||||||
|
invoke("settings_get_system_fonts"),
|
||||||
onSettingChanged: (callback: (change: settingChange) => void): Promise<UnlistenFn> => {
|
onSettingChanged: (callback: (change: settingChange) => void): Promise<UnlistenFn> => {
|
||||||
return listen<settingChange>("settings:changed", (event) => {
|
return listen<settingChange>("settings:changed", (event) => {
|
||||||
callback(event.payload)
|
callback(event.payload)
|
||||||
|
|||||||
Reference in New Issue
Block a user