返回 DeepSeek-Reasonix
StartupSplash.tsx
根目录 / desktop / frontend / src / components / StartupSplash.tsx
1 import { useEffect, useRef, useState } from "react";
2 import logoSymbol from "../assets/logo-symbol.svg";
3 import { useT } from "../lib/i18n";
4
5 const SPLASH_FLAG = "reasonix.splash.shown";
6 const MIN_VISIBLE_MS = 1400;
7 const FADE_OUT_MS = 420;
8 const MAX_HOLD_MS = 6000;
9
10 export function shouldShowStartupSplash(): boolean {
11 try {
12 return window.sessionStorage.getItem(SPLASH_FLAG) !== "1";
13 } catch {
14 return true;
15 }
16 }
17
18 function markSplashShown(): void {
19 try {
20 window.sessionStorage.setItem(SPLASH_FLAG, "1");
21 } catch {
22 /* sessionStorage unavailable */
23 }
24 }
25
26 export function StartupSplash({ hold, onDone }: { hold: boolean; onDone: () => void }) {
27 const t = useT();
28 const [minElapsed, setMinElapsed] = useState(false);
29 const [forceRelease, setForceRelease] = useState(false);
30 const [leaving, setLeaving] = useState(false);
31 const finishedRef = useRef(false);
32 const onDoneRef = useRef(onDone);
33 onDoneRef.current = onDone;
34
35 const finish = (skipHold = false) => {
36 if (finishedRef.current) return;
37 if (!skipHold && (!minElapsed || hold) && !forceRelease) return;
38 finishedRef.current = true;
39 setLeaving(true);
40 window.setTimeout(() => {
41 markSplashShown();
42 onDoneRef.current();
43 }, FADE_OUT_MS);
44 };
45
46 useEffect(() => {
47 const minTimer = window.setTimeout(() => setMinElapsed(true), MIN_VISIBLE_MS);
48 const maxTimer = window.setTimeout(() => setForceRelease(true), MAX_HOLD_MS);
49 return () => {
50 window.clearTimeout(minTimer);
51 window.clearTimeout(maxTimer);
52 };
53 }, []);
54
55 useEffect(() => {
56 finish();
57 }, [minElapsed, hold, forceRelease]);
58
59 useEffect(() => {
60 const skip = (event: KeyboardEvent) => {
61 if (event.key !== "Escape" && event.key !== "Enter" && event.key !== " ") return;
62 finish(true);
63 };
64 window.addEventListener("keydown", skip);
65 return () => window.removeEventListener("keydown", skip);
66 }, []);
67
68 return (
69 <div className="startup-splash" data-leaving={leaving} onClick={() => finish(true)}>
70 <div className="startup-splash__card">
71 <div className="startup-splash__mark" aria-hidden="true">
72 <img src={logoSymbol} alt="" draggable={false} />
73 </div>
74 <div className="startup-splash__name">Reasonix</div>
75 <div className="startup-splash__sub">{t("app.splashSubtitle")}</div>
76 <div className="startup-splash__dots" aria-hidden="true">
77 <span />
78 <span />
79 <span />
80 </div>
81 </div>
82 </div>
83 );
84 }
85
85 lines Plain Text