diff --git a/src-tauri/data.sql b/src-tauri/data.sql index 34d90ab..c8b719d 100644 Binary files a/src-tauri/data.sql and b/src-tauri/data.sql differ diff --git a/src/components/AutoScoreManager.tsx b/src/components/AutoScoreManager.tsx index b5c8f25..2fd4f22 100644 --- a/src/components/AutoScoreManager.tsx +++ b/src/components/AutoScoreManager.tsx @@ -27,6 +27,7 @@ import { createEmptyTriggerTree, createTriggerQueryConfig, hasUnsupportedTriggerLogic, + normalizeActionDrafts, queryTreeToTriggers, triggersToQueryTree, type ActionDraft, @@ -183,7 +184,15 @@ function AutoScoreManager({ canEdit }: AutoScoreManagerProps): React.JSX.Element return } - const actionPayload = actionDraftsToPayload(actionDrafts) + const normalizedActionDrafts = normalizeActionDrafts(actionDrafts) + const actionPayload = actionDraftsToPayload(normalizedActionDrafts) + if (actionDrafts.length === 0) { + setActionDrafts(normalizedActionDrafts) + } + if (actionPayload.error === "action_required") { + messageApi.warning(t("autoScore.actionRequired")) + return + } if (actionPayload.error === "score_required") { messageApi.warning(t("autoScore.scorePlaceholder")) return @@ -423,7 +432,7 @@ function AutoScoreManager({ canEdit }: AutoScoreManagerProps): React.JSX.Element value={actionDrafts} tagOptions={tagOptions} canEdit={canEdit} - onChange={setActionDrafts} + onChange={(nextDrafts) => setActionDrafts(normalizeActionDrafts(nextDrafts))} />
diff --git a/src/components/AutoScoreManagerPage/ActionEditor.tsx b/src/components/AutoScoreManagerPage/ActionEditor.tsx index ab97de4..8508edb 100644 --- a/src/components/AutoScoreManagerPage/ActionEditor.tsx +++ b/src/components/AutoScoreManagerPage/ActionEditor.tsx @@ -1,4 +1,4 @@ -import React, { useMemo } from "react" +import React, { useEffect, useMemo } from "react" import { Button, Card, InputNumber, Select, Space } from "antd" import { DeleteOutlined, PlusOutlined } from "@ant-design/icons" import { useTranslation } from "react-i18next" @@ -19,6 +19,14 @@ export const ActionEditor: React.FC = ({ onChange, }) => { const { t } = useTranslation() + const fallbackDraft = useMemo(() => createDefaultActionDraft(), []) + const safeValue = value.length > 0 ? value : [fallbackDraft] + + useEffect(() => { + if (value.length === 0) { + onChange([fallbackDraft]) + } + }, [fallbackDraft, onChange, value.length]) const actionOptions = useMemo( () => [ @@ -31,7 +39,7 @@ export const ActionEditor: React.FC = ({ const mergedTagOptions = useMemo(() => { const optionMap = new Map(tagOptions.map((option) => [option.value, option])) - for (const action of value) { + for (const action of safeValue) { if (action.event !== "add_tag") continue const tagValues = Array.isArray(action.value) ? action.value @@ -47,19 +55,19 @@ export const ActionEditor: React.FC = ({ } return Array.from(optionMap.values()) - }, [tagOptions, value]) + }, [safeValue, tagOptions]) const updateAction = (id: string, patch: Partial>) => { - onChange(value.map((item) => (item.id === id ? { ...item, ...patch } : item))) + onChange(safeValue.map((item) => (item.id === id ? { ...item, ...patch } : item))) } const handleAddAction = () => { - onChange([...value, createDefaultActionDraft()]) + onChange([...safeValue, createDefaultActionDraft()]) } const handleRemoveAction = (id: string) => { - if (value.length <= 1) return - onChange(value.filter((item) => item.id !== id)) + if (safeValue.length <= 1) return + onChange(safeValue.filter((item) => item.id !== id)) } return ( @@ -68,7 +76,7 @@ export const ActionEditor: React.FC = ({ title={t("autoScore.triggeredActions")} > - {value.map((action) => ( + {safeValue.map((action) => (
{ + const fontOpt = fontOptions.find((f) => f.value === v) + if (!fontOpt) return + applyFontFamily(fontOpt.fontFamily) + setSettings((prev) => ({ ...prev, font_family: v })) + if ((window as any).api) { + const res = await (window as any).api.setSetting("font_family", v) + if (res.success) { + messageApi.success(t("settings.general.saved")) + } else { + messageApi.error(res.message || t("settings.general.saveFailed")) + } + } else { + messageApi.success(t("settings.general.saved")) + } + }} + style={{ width: "320px" }} + loading={isLoadingFonts} + options={fontOptions.map((opt) => ({ + value: opt.value, + label: opt.label, + }))} + showSearch + filterOption={(input, option) => + (option?.label ?? "").toLowerCase().includes(input.toLowerCase()) + } + /> +
+ {t("settings.fontFamilyHint")} +
+ + {!isMobile && ( <> diff --git a/src/i18n/locales/en-US.json b/src/i18n/locales/en-US.json index 6947474..75a8700 100644 --- a/src/i18n/locales/en-US.json +++ b/src/i18n/locales/en-US.json @@ -135,6 +135,8 @@ }, "language": "Language", "languageHint": "Select interface display language", + "fontFamily": "Global Font", + "fontFamilyHint": "Select the application interface font (page refresh required for full effect)", "theme": "Theme", "password": "Set Password", "themeMode": "Mode", diff --git a/src/i18n/locales/zh-CN.json b/src/i18n/locales/zh-CN.json index 5a7266b..d35a659 100644 --- a/src/i18n/locales/zh-CN.json +++ b/src/i18n/locales/zh-CN.json @@ -135,6 +135,8 @@ }, "language": "语言", "languageHint": "选择界面显示语言", + "fontFamily": "全局字体", + "fontFamilyHint": "选择应用界面的字体(需要刷新页面以完全生效)", "theme": "主题", "password": "设置密码", "themeMode": "模式",