import { useEffect, useRef } from 'react'; import { useAtom } from 'jotai'; import { aggregateUploadProgress, uploadProgressAtom } from '../state/uploadProgress'; import { invokeTauri, isTauri } from './useTauri'; /** Native updates at most ~4 per second. */ const MIN_INTERVAL_MS = 250; /** How long a failed upload keeps the bar red before it clears. */ const ERROR_HOLD_MS = 4_000; /** * [cinny-desktop #10] One taskbar progress bar for all uploads: the combined * percentage while files upload, indeterminate until a size is known, red for a * few seconds after a failure, cleared when done. No-op in the browser. */ export function useTauriTaskbarProgress(): void { const [entries, setEntries] = useAtom(uploadProgressAtom); const lastSent = useRef(''); const lastAt = useRef(0); const pending = useRef(undefined); // The (possibly delayed) send must use the latest uploads, not the ones from // when it was scheduled — otherwise the final "done" could be dropped. const latest = useRef(entries); latest.current = entries; useEffect(() => { if (!isTauri()) return undefined; const send = () => { pending.current = undefined; const state = aggregateUploadProgress(latest.current.values(), Date.now()); const key = JSON.stringify(state); if (key === lastSent.current) return; lastSent.current = key; lastAt.current = Date.now(); invokeTauri('set_taskbar_progress', { status: state.status, progress: 'progress' in state ? state.progress : null, }); }; const wait = MIN_INTERVAL_MS - (Date.now() - lastAt.current); if (wait <= 0) send(); else if (pending.current === undefined) pending.current = window.setTimeout(send, wait); return undefined; }, [entries]); // Drop failed entries after a moment so the red bar doesn't stick. useEffect(() => { const failed = Array.from(entries.values()).some((e) => e.failed); if (!failed) return undefined; const timer = window.setTimeout(() => { setEntries((prev) => new Map(Array.from(prev).filter(([, e]) => !e.failed))); }, ERROR_HOLD_MS); return () => window.clearTimeout(timer); }, [entries, setEntries]); useEffect( () => () => { if (pending.current !== undefined) window.clearTimeout(pending.current); }, [], ); }