import { useEffect, useId, useState } from 'react'; import { normalizeUpdateSourceConfig } from '@snowluma/common/update-source'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Textarea } from '@/components/ui/textarea'; import { DropdownSelect } from '@/components/ui/dropdown-select'; import { SkeletonSwap } from '@/components/interior/skeleton-swap'; import { useActionFeedback } from '@/contexts/ActionFeedbackContext'; import { useApi } from '@/lib/api'; import type { UpdateSourceConfig, UpdateSourceProbe } from '@/types'; export function UpdateSources() { const api = useApi(); const { runAction } = useActionFeedback(); const id = useId(); const [config, setConfig] = useState(null); const [mirrors, setMirrors] = useState(''); const [results, setResults] = useState([]); const [error, setError] = useState(null); const [busy, setBusy] = useState(false); const [ready, setReady] = useState(false); const [reload, setReload] = useState(0); useEffect(() => { let disposed = false; setReady(false); void api.update .sources() .then(({ config: next }) => { if (!disposed) { setConfig(next); setMirrors(next.mirrors.join('\n')); setError(null); } }) .catch((caught: unknown) => { if (!disposed) { setError(caught instanceof Error ? caught.message : String(caught)); } }) .finally(() => { if (!disposed) { setReady(true); } }); return () => { disposed = true; }; }, [api, reload]); const act = async (testing: boolean) => { setBusy(true); setError(null); try { await runAction( { title: testing ? '正在检测更新来源' : '正在保存更新来源', successTitle: testing ? '来源检测完成' : '更新来源已保存', successDetail: testing ? '检测从 SnowLuma 所在服务器发起' : '下次检查和下载时生效', errorTitle: testing ? '来源检测失败' : '保存失败', }, async () => { const next = normalizeUpdateSourceConfig({ ...config, mirrors: mirrors .split(/\r?\n/) .map((line) => line.trim()) .filter(Boolean), }); if (testing) { setResults((await api.update.testSources(next)).results); } else { const saved = await api.update.saveSources(next); if (!saved.saved || !saved.applied) { throw new Error(saved.errors.join(';') || saved.message); } setConfig(saved.config); setMirrors(saved.config.mirrors.join('\n')); setResults([]); } }, ); } catch (caught) { setError(caught instanceof Error ? caught.message : String(caught)); } finally { setBusy(false); } }; return (

更新来源

{error && (

{error}

)} {config ? (
{ setConfig({ ...config, mode }); setResults([]); }} />

自动模式先尝试 GitHub,失败后按列表顺序切换。镜像用于文件下载,版本查询也会尝试 GitHub 发布页面。

{config.mode === 'mirror' && (
{ setConfig({ ...config, mirror: event.target.value }); setResults([]); }} />
)}
编辑候选镜像(每行一个地址)