import { atom } from 'jotai'; import type { TUploadContent } from '../utils/matrix'; /** * [cinny-desktop #10] Every in-flight upload across all rooms, so the desktop * taskbar can show one combined progress bar. Written by `createUploadAtom` * (state/upload.ts) as its uploads start, progress, finish or fail. */ export type UploadProgressEntry = { loaded: number; total: number; failed: boolean; /** ms timestamp of the last change; stale entries are ignored. */ updatedAt: number; }; export const uploadProgressAtom = atom(new Map()); export type TaskbarProgress = | { status: 'none' } | { status: 'indeterminate' } | { status: 'normal'; progress: number } | { status: 'error'; progress: number }; /** An upload with no update for this long is treated as abandoned. */ export const UPLOAD_STALE_MS = 2 * 60_000; /** * Combine every upload into one taskbar state: the byte-weighted percentage * while anything is uploading (indeterminate while no size is known yet), red * when an upload failed and nothing else is running, nothing otherwise. */ export const aggregateUploadProgress = ( entries: Iterable, now: number, ): TaskbarProgress => { let loaded = 0; let total = 0; let active = 0; let failed = 0; let failedLoaded = 0; let failedTotal = 0; Array.from(entries).forEach((e) => { if (now - e.updatedAt > UPLOAD_STALE_MS) return; if (e.failed) { failed += 1; failedLoaded += e.loaded; failedTotal += e.total; return; } active += 1; loaded += Math.min(e.loaded, e.total); total += e.total; }); if (active > 0) { if (total <= 0) return { status: 'indeterminate' }; return { status: 'normal', progress: Math.min(100, Math.floor((loaded / total) * 100)) }; } if (failed > 0) { return { status: 'error', progress: failedTotal > 0 ? Math.min(100, Math.floor((failedLoaded / failedTotal) * 100)) : 100, }; } return { status: 'none' }; };