import React, { useState, useEffect, useRef } from 'react'; import { Text } from '@/components/ui/text'; interface WorkingPlaceholderProps { statusText: string | null; isWaitingForPermission?: boolean; wasAborted?: boolean; completionId?: string | null; isComplete?: boolean; } const MIN_DISPLAY_TIME = 2000; const DONE_DISPLAY_TIME = 1500; type ResultState = 'success' | 'aborted' | null; export function WorkingPlaceholder({ statusText, isWaitingForPermission, wasAborted, completionId, isComplete, }: WorkingPlaceholderProps) { const [displayedStatus, setDisplayedStatus] = useState(null); const [displayedPermission, setDisplayedPermission] = useState(false); const [isVisible, setIsVisible] = useState(false); const [isFadingOut, setIsFadingOut] = useState(false); const [resultState, setResultState] = useState(null); const displayStartTimeRef = useRef(0); const statusQueueRef = useRef>([]); const removalPendingRef = useRef(false); const fadeTimeoutRef = useRef | null>(null); const resultTimeoutRef = useRef | null>(null); const rafIdRef = useRef(null); const lastCheckTimeRef = useRef(0); const lastActiveStatusRef = useRef(null); const hasShownActivityRef = useRef(false); const wasAbortedRef = useRef(false); const isCompleteRef = useRef(false); const windowFocusRef = useRef(true); const lastCompletionShownRef = useRef(null); const resultShownAtRef = useRef(null); const activateStatus = (status: string, permission: boolean) => { if (fadeTimeoutRef.current) { clearTimeout(fadeTimeoutRef.current); fadeTimeoutRef.current = null; } if (resultTimeoutRef.current) { clearTimeout(resultTimeoutRef.current); resultTimeoutRef.current = null; } if (status === 'aborted') { setDisplayedStatus(null); setDisplayedPermission(false); setIsFadingOut(false); setResultState('aborted'); lastActiveStatusRef.current = 'aborted'; hasShownActivityRef.current = true; wasAbortedRef.current = true; if (typeof requestAnimationFrame === 'function') { requestAnimationFrame(() => setIsVisible(true)); } else { setIsVisible(true); } return; } setResultState(null); setIsFadingOut(false); lastActiveStatusRef.current = status; hasShownActivityRef.current = true; setDisplayedStatus(status); setDisplayedPermission(permission); if (!isVisible) { if (typeof requestAnimationFrame === 'function') { requestAnimationFrame(() => { setIsVisible(true); }); } else { setIsVisible(true); } } }; useEffect(() => { const now = Date.now(); if (statusText) { removalPendingRef.current = false; if (!displayedStatus) { activateStatus(statusText, !!isWaitingForPermission); displayStartTimeRef.current = now; statusQueueRef.current = []; } else if ( statusText !== displayedStatus || !!isWaitingForPermission !== displayedPermission ) { statusQueueRef.current.push({ status: statusText, permission: !!isWaitingForPermission, }); } } else { removalPendingRef.current = true; } }, [statusText, isWaitingForPermission, displayedStatus, displayedPermission, wasAborted]); useEffect(() => { if (wasAborted) { wasAbortedRef.current = true; } }, [wasAborted]); useEffect(() => { isCompleteRef.current = !!isComplete; }, [isComplete]); useEffect(() => { if (isComplete) { removalPendingRef.current = true; } }, [isComplete]); useEffect(() => { const startFadeOut = (result: ResultState) => { if (isFadingOut) { return; } const hadActiveStatus = lastActiveStatusRef.current !== null || hasShownActivityRef.current; if (result && hadActiveStatus) { setIsFadingOut(false); setIsVisible(true); setDisplayedStatus(null); setDisplayedPermission(false); setResultState(result); lastActiveStatusRef.current = null; if (result === 'success' && completionId) { lastCompletionShownRef.current = completionId; } resultShownAtRef.current = Date.now(); if (resultTimeoutRef.current) { clearTimeout(resultTimeoutRef.current); } resultTimeoutRef.current = setTimeout(() => { setIsVisible(false); setResultState(null); hasShownActivityRef.current = false; resultTimeoutRef.current = null; }, DONE_DISPLAY_TIME); } else { setIsFadingOut(true); setIsVisible(false); setResultState(null); if (fadeTimeoutRef.current) { clearTimeout(fadeTimeoutRef.current); } fadeTimeoutRef.current = setTimeout(() => { setDisplayedStatus(null); setDisplayedPermission(false); setIsFadingOut(false); hasShownActivityRef.current = false; lastActiveStatusRef.current = null; fadeTimeoutRef.current = null; }, 180); } wasAbortedRef.current = false; }; const CHECK_THROTTLE_MS = 150; // Throttle checks to ~6-7 times per second const hasInitialWork = Boolean( statusText || displayedStatus || resultState !== null || removalPendingRef.current || statusQueueRef.current.length > 0 || wasAbortedRef.current || isCompleteRef.current ); if (!hasInitialWork) { if (rafIdRef.current !== null) { cancelAnimationFrame(rafIdRef.current); rafIdRef.current = null; } return; } const checkLoop = (timestamp: number) => { if (timestamp - lastCheckTimeRef.current < CHECK_THROTTLE_MS) { rafIdRef.current = requestAnimationFrame(checkLoop); return; } lastCheckTimeRef.current = timestamp; const now = Date.now(); const elapsed = now - displayStartTimeRef.current; const isDone = removalPendingRef.current && isCompleteRef.current; const shouldWaitForMinTime = !isDone && statusQueueRef.current.length > 0; if (shouldWaitForMinTime && elapsed < MIN_DISPLAY_TIME) { rafIdRef.current = requestAnimationFrame(checkLoop); return; } if (removalPendingRef.current && wasAbortedRef.current) { removalPendingRef.current = false; statusQueueRef.current = []; startFadeOut('aborted'); } else if (!isDone && statusQueueRef.current.length > 0) { const latest = statusQueueRef.current[statusQueueRef.current.length - 1]; activateStatus(latest.status, latest.permission); displayStartTimeRef.current = now; statusQueueRef.current = []; } else if (removalPendingRef.current) { removalPendingRef.current = false; if (statusQueueRef.current.length > 0) { hasShownActivityRef.current = true; } statusQueueRef.current = []; let result: ResultState = null; if (wasAbortedRef.current) { result = 'aborted'; } else if (isCompleteRef.current) { result = 'success'; hasShownActivityRef.current = true; } if (result === 'success' && completionId && lastCompletionShownRef.current === completionId) { setDisplayedStatus(null); setDisplayedPermission(false); setIsFadingOut(false); setIsVisible(false); setResultState(null); statusQueueRef.current = []; hasShownActivityRef.current = false; lastActiveStatusRef.current = null; removalPendingRef.current = false; wasAbortedRef.current = false; rafIdRef.current = null; return; } startFadeOut(result); } const hasPendingWork = Boolean( displayedStatus || resultState !== null || statusQueueRef.current.length > 0 || removalPendingRef.current || wasAbortedRef.current || isCompleteRef.current ); if (hasPendingWork) { rafIdRef.current = requestAnimationFrame(checkLoop); } else { rafIdRef.current = null; } }; rafIdRef.current = requestAnimationFrame(checkLoop); return () => { if (rafIdRef.current !== null) { cancelAnimationFrame(rafIdRef.current); rafIdRef.current = null; } }; }, [statusText, displayedStatus, resultState, isComplete, wasAborted, isFadingOut]); useEffect(() => { return () => { if (fadeTimeoutRef.current) { clearTimeout(fadeTimeoutRef.current); } if (resultTimeoutRef.current) { clearTimeout(resultTimeoutRef.current); } }; }, []); useEffect(() => { if (typeof window === 'undefined') { return; } windowFocusRef.current = typeof document !== 'undefined' && typeof document.hasFocus === 'function' ? document.hasFocus() : true; const handleFocus = () => { windowFocusRef.current = true; }; const handleBlur = () => { windowFocusRef.current = false; }; window.addEventListener('focus', handleFocus); window.addEventListener('blur', handleBlur); return () => { window.removeEventListener('focus', handleFocus); window.removeEventListener('blur', handleBlur); }; }, []); useEffect(() => { const handleVisibilityRestore = () => { if (typeof document === 'undefined' || typeof Date === 'undefined') { return; } if (document.visibilityState !== 'visible') { return; } const shownAt = resultShownAtRef.current; const isCompletionVisible = resultState !== null || displayedStatus !== null; if (isCompletionVisible && shownAt && Date.now() - shownAt > 500) { setDisplayedStatus(null); setDisplayedPermission(false); setIsFadingOut(false); setIsVisible(false); setResultState(null); statusQueueRef.current = []; hasShownActivityRef.current = false; lastActiveStatusRef.current = null; removalPendingRef.current = false; wasAbortedRef.current = false; } }; document.addEventListener('visibilitychange', handleVisibilityRestore); window.addEventListener('focus', handleVisibilityRestore); return () => { document.removeEventListener('visibilitychange', handleVisibilityRestore); window.removeEventListener('focus', handleVisibilityRestore); }; }, [displayedStatus, resultState]); if (!displayedStatus && resultState === null) { return null; } let label: string; if (resultState === 'success') { label = 'Done'; } else if (resultState === 'aborted') { label = 'Aborted'; } else if (displayedStatus) { label = displayedStatus.charAt(0).toUpperCase() + displayedStatus.slice(1); } else { label = 'Working'; } const ariaLive = displayedPermission ? 'assertive' : 'polite'; const displayText = resultState === null ? `${label}...` : label; return (
{resultState === null && ( {displayText} )} {resultState === 'success' && ( Done )} {resultState === 'aborted' && ( Aborted )}
); }