import { IconAlertCircle, IconCheck, IconExternalLink, IconX, } from "@tabler/icons-react"; import { invoke } from "@tauri-apps/api/core"; import { listen } from "@tauri-apps/api/event"; import { open as openExternal } from "@tauri-apps/plugin-shell"; import { useEffect, useState } from "react"; type NativeUploadProgress = { stage?: string; message?: string; detail?: string | null; progress?: number | null; }; type ProcessingProgress = { stage?: string; progress?: number | null; viewUrl?: string; savedLocally?: boolean; }; type NativeUploadFinished = { recordingId?: string; ok?: boolean; viewUrl?: string; error?: string | null; localFilePath?: string | null; }; const FINALIZING_RESULT_STORAGE_KEY = "clips-finalizing-result"; function takePersistedFinalizingResult(): NativeUploadFinished | null { try { const raw = window.localStorage.getItem(FINALIZING_RESULT_STORAGE_KEY); if (!raw) return null; window.localStorage.removeItem(FINALIZING_RESULT_STORAGE_KEY); const parsed: unknown = JSON.parse(raw); if (!parsed || typeof parsed !== "object") return null; const payload = parsed as Record; if ( typeof payload.recordingId !== "string" || typeof payload.viewUrl !== "string" || typeof payload.ok !== "boolean" ) { return null; } return { recordingId: payload.recordingId, viewUrl: payload.viewUrl, ok: payload.ok, error: typeof payload.error === "string" ? payload.error : null, localFilePath: typeof payload.localFilePath === "string" ? payload.localFilePath : null, }; } catch { return null; } } /** * Compact bottom-left feedback window. Rendered the moment the user clicks * Stop and kept visible while the desktop finishes its durable backup and * first upload/finalize attempt. The browser can open `/r/:id` earlier so the * page shows live progress. */ export function Finalizing() { const [progress, setProgress] = useState({ stage: "finalizing", progress: null, }); useEffect(() => { let disposed = false; let unlisten: (() => void) | null = null; let unlistenFinished: (() => void) | null = null; let completionTimer: ReturnType | null = null; let openingWatchdog: ReturnType | null = null; const hardWatchdog = window.setTimeout(() => { void invoke("show_popover").catch(() => {}); void invoke("hide_finalizing").catch(() => {}); }, 120_000); let finishedHandled = false; const clearCompletionTimer = () => { if (completionTimer) { window.clearTimeout(completionTimer); completionTimer = null; } }; const clearOpeningWatchdog = () => { if (openingWatchdog) { window.clearTimeout(openingWatchdog); openingWatchdog = null; } }; listen("clips:native-upload-progress", (event) => { if (finishedHandled) return; const payload = event.payload ?? {}; if (payload.stage === "opening" && payload.progress === 1) { clearOpeningWatchdog(); openingWatchdog = window.setTimeout(() => { void invoke("show_popover").catch(() => {}); void invoke("hide_finalizing").catch(() => {}); }, 15000); } else if (payload.stage !== "opening") { clearOpeningWatchdog(); } setProgress({ stage: payload.stage, progress: typeof payload.progress === "number" && Number.isFinite(payload.progress) ? Math.min(1, Math.max(0, payload.progress)) : null, }); }) .then((u) => { if (disposed) { u(); return; } unlisten = u; }) .catch(() => {}); const claimNativeOpen = async ( recordingId: string | undefined, ): Promise => { if (!recordingId) return true; return invoke("native_fullscreen_claim_upload_open", { recordingId, }).catch(() => true); }; const handleFinished = (payload: NativeUploadFinished) => { if (disposed || finishedHandled) return; finishedHandled = true; try { window.localStorage.removeItem(FINALIZING_RESULT_STORAGE_KEY); } catch { // Storage is a best-effort event-race fallback only. } clearCompletionTimer(); clearOpeningWatchdog(); if (payload.ok && payload.viewUrl) { setProgress({ stage: "uploaded", progress: 1, viewUrl: payload.viewUrl, }); void claimNativeOpen(payload.recordingId).then((claimed) => { if (!claimed || disposed) return; void openExternal(payload.viewUrl as string).catch((err) => { console.error("[clips-finalizing] open clip failed:", err); }); }); completionTimer = window.setTimeout(() => { void invoke("hide_finalizing").catch(() => {}); }, 2500); return; } setProgress({ stage: "failed", progress: 1, viewUrl: payload.viewUrl, savedLocally: Boolean(payload.localFilePath), }); completionTimer = window.setTimeout(() => { void invoke("show_popover").catch(() => {}); void invoke("hide_finalizing").catch(() => {}); }, 2500); }; listen("clips:native-upload-finished", (event) => { handleFinished(event.payload ?? {}); }) .then((u) => { if (disposed) { u(); return; } unlistenFinished = u; const persisted = takePersistedFinalizingResult(); if (persisted) { handleFinished(persisted); return; } void invoke( "native_fullscreen_take_upload_finished", ) .then((payload) => { if (payload) handleFinished(payload); }) .catch(() => {}); }) .catch(() => {}); return () => { disposed = true; clearCompletionTimer(); clearOpeningWatchdog(); window.clearTimeout(hardWatchdog); unlisten?.(); unlistenFinished?.(); }; }, []); const percent = typeof progress.progress === "number" ? Math.round(progress.progress * 100) : null; const caption = progress.stage === "uploaded" ? "Uploaded" : progress.stage === "failed" ? progress.savedLocally ? "Upload paused — clip saved locally" : "Upload paused — open Clips to recover" : progress.stage === "uploading" || progress.stage === "processing" || progress.stage === "opening" ? "Uploading clip..." : "Optimizing clip..."; const finished = progress.stage === "uploaded" || progress.stage === "failed"; const dismiss = () => { void invoke("hide_finalizing").catch(() => {}); }; const openClip = () => { if (!progress.viewUrl) return; void openExternal(progress.viewUrl).catch((err) => { console.error("[clips-finalizing] open clip failed:", err); }); }; return (
{progress.stage === "uploaded" ? (