feat(ui): extract reusable one-shot slow-hint hook; refactor join(3s) and RTC negotiate(8s) hints; share rtcPhase mapper; update docs
This commit is contained in:
@@ -0,0 +1,19 @@
|
||||
export type Phase = "idle" | "negotiating" | "connected" | "disconnected";
|
||||
|
||||
// Normalize various RTC connection states into simplified phases used by UI
|
||||
export function mapPhase(state?: string): Phase {
|
||||
if (!state) return "idle";
|
||||
if (state === "new" || state === "connecting") return "negotiating";
|
||||
if (state === "connected") return "connected";
|
||||
if (state === "disconnected" || state === "failed" || state === "closed")
|
||||
return "disconnected";
|
||||
// Already normalized values from store
|
||||
if (
|
||||
state === "idle" ||
|
||||
(state as any) === "negotiating" ||
|
||||
(state as any) === "disconnected"
|
||||
)
|
||||
return state as Phase;
|
||||
return "idle";
|
||||
}
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
import { useCallback, useEffect, useRef } from "react";
|
||||
|
||||
type PutFn = (
|
||||
message: string,
|
||||
isShareEnd?: boolean,
|
||||
displayTimeMs?: number
|
||||
) => void;
|
||||
|
||||
type MessageFactory = () =>
|
||||
| {
|
||||
text?: string;
|
||||
isShareEnd?: boolean;
|
||||
}
|
||||
| null;
|
||||
|
||||
interface UseOneShotSlowHintOptions {
|
||||
thresholdMs: number;
|
||||
putMessageInMs: PutFn;
|
||||
getMessage: MessageFactory;
|
||||
visibilityGate?: boolean; // default true: only show when document is visible
|
||||
displayMs?: number; // default 6000
|
||||
}
|
||||
|
||||
// A small utility hook to manage a one-shot slow-hint timer per attempt.
|
||||
// - arm(): start a timer if not already started
|
||||
// - disarm(): clear running timer (does not reset shown flag)
|
||||
// - reset(): clear timer and reset once-shown & pending flags
|
||||
export function useOneShotSlowHint({
|
||||
thresholdMs,
|
||||
putMessageInMs,
|
||||
getMessage,
|
||||
visibilityGate = true,
|
||||
displayMs = 6000,
|
||||
}: UseOneShotSlowHintOptions) {
|
||||
const timerRef = useRef<number | null>(null);
|
||||
const shownRef = useRef<boolean>(false);
|
||||
const pendingRef = useRef<boolean>(false);
|
||||
|
||||
const disarm = useCallback(() => {
|
||||
if (timerRef.current) {
|
||||
clearTimeout(timerRef.current);
|
||||
timerRef.current = null;
|
||||
}
|
||||
}, []);
|
||||
|
||||
const reset = useCallback(() => {
|
||||
disarm();
|
||||
shownRef.current = false;
|
||||
pendingRef.current = false;
|
||||
}, [disarm]);
|
||||
|
||||
const fireIfEligible = useCallback(() => {
|
||||
if (shownRef.current) return;
|
||||
const payload = getMessage();
|
||||
if (!payload || !payload.text) return;
|
||||
if (visibilityGate && typeof document !== "undefined") {
|
||||
if (document.visibilityState !== "visible") {
|
||||
pendingRef.current = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
putMessageInMs(payload.text, payload.isShareEnd, displayMs);
|
||||
shownRef.current = true;
|
||||
pendingRef.current = false;
|
||||
}, [displayMs, getMessage, putMessageInMs, visibilityGate]);
|
||||
|
||||
const arm = useCallback(
|
||||
(_key?: string) => {
|
||||
if (timerRef.current) return; // already armed
|
||||
timerRef.current = window.setTimeout(() => {
|
||||
fireIfEligible();
|
||||
}, thresholdMs) as unknown as number;
|
||||
},
|
||||
[fireIfEligible, thresholdMs]
|
||||
);
|
||||
|
||||
// Visibility change handling: if pending, try to fire once when visible
|
||||
useEffect(() => {
|
||||
if (!visibilityGate) return;
|
||||
const handler = () => {
|
||||
if (document.visibilityState !== "visible") return;
|
||||
if (pendingRef.current && !shownRef.current) {
|
||||
fireIfEligible();
|
||||
}
|
||||
};
|
||||
document.addEventListener("visibilitychange", handler);
|
||||
return () => document.removeEventListener("visibilitychange", handler);
|
||||
}, [fireIfEligible, visibilityGate]);
|
||||
|
||||
// Cleanup on unmount
|
||||
useEffect(() => () => disarm(), [disarm]);
|
||||
|
||||
return { arm, disarm, reset } as const;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user