2026-05-22 17:24:50 +08:00
|
|
|
|
import { useMemo } from "react";
|
2026-05-22 15:54:11 +08:00
|
|
|
|
import { useDevice } from "@/contexts/DeviceContext";
|
2026-09-20 09:59:33 +08:00
|
|
|
|
import { isLikelyNetworkFetchError } from "@/lib/networkError";
|
2026-05-22 15:54:11 +08:00
|
|
|
|
import { Loader2, Wifi } from "lucide-react";
|
|
|
|
|
|
import BottomNav from "@/components/BottomNav";
|
2026-05-22 17:24:50 +08:00
|
|
|
|
import localeZh from "@/locales/data-zh.json";
|
2026-10-22 13:50:31 +08:00
|
|
|
|
import { getUiLanguageOverride, isKnownLanguage, resolveLocalePack } from "@/locales/resolveLocale";
|
2026-05-22 17:24:50 +08:00
|
|
|
|
|
|
|
|
|
|
type ConnectLocale = (typeof localeZh)["connect"];
|
|
|
|
|
|
|
2026-10-22 13:50:31 +08:00
|
|
|
|
function pickConnectLocale(effectiveLanguage: number): ConnectLocale {
|
|
|
|
|
|
// 前端语言覆盖(de/es/fr)优先;否则沿用 URL ?lang=(设备语言);最后回退到生效语言。
|
|
|
|
|
|
const override = getUiLanguageOverride();
|
|
|
|
|
|
if (override != null) return resolveLocalePack(override).connect as ConnectLocale;
|
2026-05-22 17:24:50 +08:00
|
|
|
|
const params = new URLSearchParams(window.location.search);
|
2026-10-22 13:50:31 +08:00
|
|
|
|
const langParam = Number(params.get("lang"));
|
|
|
|
|
|
const lang = isKnownLanguage(langParam) ? langParam : effectiveLanguage;
|
|
|
|
|
|
return resolveLocalePack(lang).connect as ConnectLocale;
|
2026-05-22 17:24:50 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function fmt(template: string, vars: Record<string, string>): string {
|
|
|
|
|
|
let s = template;
|
|
|
|
|
|
for (const [k, v] of Object.entries(vars)) {
|
|
|
|
|
|
s = s.split(`{${k}}`).join(v);
|
|
|
|
|
|
}
|
|
|
|
|
|
return s;
|
|
|
|
|
|
}
|
2026-05-22 15:54:11 +08:00
|
|
|
|
|
|
|
|
|
|
/** 自动连接设备时的占位(替代原 ConnectScreen) */
|
|
|
|
|
|
export default function ConnectionPlaceholder() {
|
2026-10-22 13:50:31 +08:00
|
|
|
|
const { ip, isConnecting, error, ipMissing, effectiveLanguage } = useDevice();
|
|
|
|
|
|
const text = useMemo(() => pickConnectLocale(effectiveLanguage), [effectiveLanguage]);
|
2026-05-22 15:54:11 +08:00
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
|
<div className="min-h-screen bg-black flex flex-col items-center justify-center px-6 pb-24">
|
|
|
|
|
|
<div className="mb-6 flex size-[4.5rem] items-center justify-center rounded-[16px] border border-white/15">
|
|
|
|
|
|
<Wifi size={28} className="text-[#00FFF6]/80" />
|
|
|
|
|
|
</div>
|
2026-05-22 17:24:50 +08:00
|
|
|
|
{ipMissing ? (
|
|
|
|
|
|
<>
|
|
|
|
|
|
<p className="text-[15px] text-white/80 mb-2">{text.ipMissingTitle}</p>
|
|
|
|
|
|
<p className="text-[13px] text-white/45 text-center max-w-xs">{text.ipMissingDesc}</p>
|
2026-09-20 10:46:50 +08:00
|
|
|
|
<p className="text-[13px] text-[#00FFF6]/80 mt-3 font-mono">?ip=</p>
|
2026-05-22 17:24:50 +08:00
|
|
|
|
</>
|
|
|
|
|
|
) : isConnecting ? (
|
2026-05-22 15:54:11 +08:00
|
|
|
|
<>
|
|
|
|
|
|
<Loader2 size={28} className="animate-spin text-[#00FFF6] mb-3" />
|
2026-05-22 17:24:50 +08:00
|
|
|
|
<p className="text-[15px] text-white/70">{fmt(text.connecting, { ip })}</p>
|
2026-05-22 15:54:11 +08:00
|
|
|
|
</>
|
|
|
|
|
|
) : (
|
|
|
|
|
|
<>
|
2026-05-22 17:24:50 +08:00
|
|
|
|
<p className="text-[15px] text-white/80 mb-2">{text.failedTitle}</p>
|
2026-05-22 15:54:11 +08:00
|
|
|
|
<p className="text-[13px] text-white/45 text-center max-w-xs">
|
2026-09-20 09:59:33 +08:00
|
|
|
|
{error && !isLikelyNetworkFetchError(error) ? error : text.failedDesc}
|
2026-05-22 15:54:11 +08:00
|
|
|
|
</p>
|
2026-09-20 10:46:50 +08:00
|
|
|
|
<p className="text-[12px] text-white/30 mt-3 text-center max-w-xs">{fmt(text.failedHint, { ip })}</p>
|
2026-05-22 15:54:11 +08:00
|
|
|
|
</>
|
|
|
|
|
|
)}
|
|
|
|
|
|
<BottomNav />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|