From a4686c09b56ada22fea77fc12877dd260369adca Mon Sep 17 00:00:00 2001 From: CPTProgrammer <46586216+CPTProgrammer@users.noreply.github.com> Date: Sat, 11 Jul 2026 12:58:35 +0800 Subject: [PATCH] Refactor ConfigModal to load metadata independently per select --- src/components/ConfigModal.tsx | 261 ++++++++++++++++++++++----------- 1 file changed, 172 insertions(+), 89 deletions(-) diff --git a/src/components/ConfigModal.tsx b/src/components/ConfigModal.tsx index 339225b..4dec2ab 100644 --- a/src/components/ConfigModal.tsx +++ b/src/components/ConfigModal.tsx @@ -11,7 +11,6 @@ import { Button, Space, App, - Spin, } from "antd"; import { PlusOutlined, DeleteOutlined } from "@ant-design/icons"; import { useDebouncedCallback } from "use-debounce"; @@ -42,10 +41,103 @@ const RELATION_TYPES = [ { label: "Tool", value: "tool" }, ]; +// --------------------------------------------------------------------------- +// Data-fetching hooks — each Select loads independently, no blocking +// --------------------------------------------------------------------------- + +function useModrinthLoaders(): { + loading: boolean; + options: { label: string; value: string }[]; +} { + const [loading, setLoading] = useState(true); + const [options, setOptions] = useState<{ label: string; value: string }[]>( + [], + ); + + useEffect(() => { + let cancelled = false; + setLoading(true); + getModrinthLoaders() + .then((list) => { + if (!cancelled) { + setOptions(list.map((l) => ({ label: l, value: l }))); + } + }) + .catch(() => {}) + .finally(() => { + if (!cancelled) setLoading(false); + }); + return () => { + cancelled = true; + }; + }, []); + + return { loading, options }; +} + +function useModrinthEnvironments(): { + loading: boolean; + options: { value: string; label: string }[]; +} { + const [loading, setLoading] = useState(true); + const [options, setOptions] = useState< + { value: string; label: string }[] + >([]); + + useEffect(() => { + let cancelled = false; + setLoading(true); + getModrinthEnvironments() + .then((list) => { + if (!cancelled) setOptions(list); + }) + .catch(() => {}) + .finally(() => { + if (!cancelled) setLoading(false); + }); + return () => { + cancelled = true; + }; + }, []); + + return { loading, options }; +} + +function useCurseforgeMeta(): { + loading: boolean; + environments: string[]; + loaders: string[]; +} { + const [loading, setLoading] = useState(true); + const [environments, setEnvironments] = useState([]); + const [loaders, setLoaders] = useState([]); + + useEffect(() => { + let cancelled = false; + setLoading(true); + getCurseForgeMeta() + .then((meta) => { + if (!cancelled) { + setEnvironments(meta.environments); + setLoaders(meta.loaders); + } + }) + .catch(() => {}) + .finally(() => { + if (!cancelled) setLoading(false); + }); + return () => { + cancelled = true; + }; + }, []); + + return { loading, environments, loaders }; +} + type ConfigValues = Config; // --------------------------------------------------------------------------- -// Outer component — Modal shell, meta loading, state +// Component // --------------------------------------------------------------------------- interface ConfigModalProps { @@ -68,40 +160,6 @@ export function ConfigModal({ const { message } = App.useApp(); const [saving, setSaving] = useState(false); - // --------------- Metadata --------------- - const [metaLoading, setMetaLoading] = useState(false); - const [meta, setMeta] = useState<{ - modrinthLoaders: string[]; - modrinthEnvironments: { value: string; label: string }[]; - curseforgeEnvs: string[]; - curseforgeLoaders: string[]; - } | null>(null); - - const loadMeta = useCallback(async () => { - setMetaLoading(true); - try { - const [loaders, envs, cfMeta] = await Promise.all([ - getModrinthLoaders(), - getModrinthEnvironments(), - getCurseForgeMeta(), - ]); - setMeta({ - modrinthLoaders: loaders, - modrinthEnvironments: envs, - curseforgeEnvs: cfMeta.environments, - curseforgeLoaders: cfMeta.loaders, - }); - } catch { - message.warning("部分元数据加载失败,手动输入仍然可用"); - } finally { - setMetaLoading(false); - } - }, []); - - useEffect(() => { - if (open) loadMeta(); - }, [open, loadMeta]); - // --------------- Save --------------- const handleSave = useCallback( @@ -138,7 +196,7 @@ export function ConfigModal({ setSaving(false); } }, - [mode, initialName, onSaved], + [mode, initialName, onSaved, message], ); // --------------- Debounced project name lookup --------------- @@ -180,7 +238,6 @@ export function ConfigModal({ type="primary" loading={saving} onClick={() => { - // Trigger form submit via a hidden button inside the form document .getElementById("config-modal-submit") ?.click(); @@ -191,41 +248,26 @@ export function ConfigModal({ } > - {metaLoading ? ( -
- -
- ) : meta ? ( - - ) : null} + ); } // --------------------------------------------------------------------------- -// Inner component — Form (only rendered when meta is loaded) +// Inner component — Form with independently-loading Selects // --------------------------------------------------------------------------- interface ConfigModalFormProps { mode: "add" | "edit"; - initialName: string | null; initialConfig: Config | null; - meta: { - modrinthLoaders: string[]; - modrinthEnvironments: { value: string; label: string }[]; - curseforgeEnvs: string[]; - curseforgeLoaders: string[]; - }; modrinthProjectName: string | null; curseforgeProjectName: string | null; lookupModrinthProject: (projectId: string) => void; @@ -236,7 +278,6 @@ interface ConfigModalFormProps { function ConfigModalForm({ mode, initialConfig, - meta, modrinthProjectName, curseforgeProjectName, lookupModrinthProject, @@ -245,6 +286,11 @@ function ConfigModalForm({ }: ConfigModalFormProps) { const [form] = Form.useForm(); + // Each Select loads its own data independently + const mrLoaders = useModrinthLoaders(); + const mrEnvs = useModrinthEnvironments(); + const cfMeta = useCurseforgeMeta(); + // Reset form when modal opens or mode/config changes useEffect(() => { if (mode === "edit" && initialConfig) { @@ -259,7 +305,8 @@ function ConfigModalForm({ source_filename_format: "", modrinth: { project_id: "", - version_name: "ModName v${version} for Minecraft ${mc_version_range}", + version_name: + "ModName v${version} for Minecraft ${mc_version_range}", version: "${version}-mc${mc_version}", loaders: [], environment: "client_and_server", @@ -277,15 +324,13 @@ function ConfigModalForm({ }, [mode, initialConfig, form]); const handleFinish = useCallback( - async (values: ConfigValues) => { + (values: ConfigValues) => { onSave(values); }, [onSave], ); - const handleFinishFailed = useCallback(() => { - // Ant Design shows inline validation errors - }, []); + const handleFinishFailed = useCallback(() => {}, []); return (
- {/* Hidden submit button triggered by modal footer */} -