Add downloading indicator to hack action bar

This commit is contained in:
Jared Schoeny
2025-10-23 10:49:20 -10:00
parent 5c1b380203
commit 0ddd0f719e
2 changed files with 59 additions and 10 deletions

View File

@@ -26,14 +26,55 @@ const HackActions: React.FC<HackActionsProps> = ({
}) => {
const { isLinked, hasPermission, hasCached, importUploadedBlob, ensurePermission, linkRom, getFileBlob, supported } = useBaseRoms();
const [file, setFile] = React.useState<File | null>(null);
const [status, setStatus] = React.useState<"idle" | "ready" | "patching" | "done">("idle");
const [status, setStatus] = React.useState<"idle" | "ready" | "patching" | "done" | "downloading">("idle");
const [error, setError] = React.useState<string | null>(null);
const [patchBlob, setPatchBlob] = React.useState<Blob | null>(null);
React.useEffect(() => {
if (isLinked(baseRomId) && (hasPermission(baseRomId) || hasCached(baseRomId))) {
setStatus("ready");
if (status !== "downloading" && status !== "patching" && status !== "done") {
setStatus("ready");
}
}
}, [baseRomId, isLinked, hasPermission, hasCached]);
}, [baseRomId, isLinked, hasPermission, hasCached, status]);
// Pre-download patch on mount (or when patchUrl changes) and cache as Blob
React.useEffect(() => {
let aborted = false;
async function prefetchPatch() {
try {
setPatchBlob(null);
if (!patchUrl) return;
// indicate downloading while we fetch the patch blob
setStatus((prev) => (prev === "patching" || prev === "done" ? prev : "downloading"));
const res = await fetch(patchUrl);
if (!res.ok) throw new Error("Failed to fetch patch");
const blob = await res.blob();
if (aborted) return;
setPatchBlob(blob);
// restore status after download: ready if base rom uploaded/linked, else idle
setStatus((prev) => {
if (prev === "patching" || prev === "done") return prev;
const romReady = !!file || (isLinked(baseRomId) && (hasPermission(baseRomId) || hasCached(baseRomId)));
return romReady ? "ready" : "idle";
});
} catch {
if (!aborted) {
setPatchBlob(null);
// on error, fall back to current readiness state
setStatus((prev) => {
if (prev === "patching" || prev === "done") return prev;
const romReady = !!file || (isLinked(baseRomId) && (hasPermission(baseRomId) || hasCached(baseRomId)));
return romReady ? "ready" : "idle";
});
}
}
}
prefetchPatch();
return () => {
aborted = true;
};
}, [patchUrl]);
function onSelectFile(e: React.ChangeEvent<HTMLInputElement>) {
const f = e.target.files?.[0] ?? null;
@@ -64,10 +105,16 @@ const HackActions: React.FC<HackActionsProps> = ({
// Read inputs
const [romBuf, patchBuf] = await Promise.all([
baseFile.arrayBuffer(),
fetch(patchUrl).then((r) => {
if (!r.ok) throw new Error("Failed to fetch patch");
return r.arrayBuffer();
}),
(async () => {
let blob = patchBlob;
if (!blob) {
const resp = await fetch(patchUrl);
if (!resp.ok) throw new Error("Failed to fetch patch");
blob = await resp.blob();
setPatchBlob(blob);
}
return await blob.arrayBuffer();
})(),
]);
// Build BinFiles

View File

@@ -7,7 +7,7 @@ export default function StickyActionBar({ title, version, author, onPatch, statu
version?: string;
author: string;
onPatch: () => void;
status: "idle" | "ready" | "patching" | "done";
status: "idle" | "ready" | "patching" | "done" | "downloading";
isLinked: boolean;
ready: boolean;
onClickLink: () => void;
@@ -31,13 +31,15 @@ export default function StickyActionBar({ title, version, author, onPatch, statu
</div>
<div className="flex flex-wrap items-center gap-2">
<span className={`rounded-full px-2 py-0.5 text-xs ring-1 ${
ready
status === "downloading"
? "bg-[var(--surface-2)] text-foreground/85 ring-[var(--border)]"
: ready
? "bg-emerald-600/60 text-white ring-emerald-700/80 dark:bg-emerald-500/25 dark:text-emerald-100 dark:ring-emerald-400/90"
: isLinked
? "bg-amber-600/60 text-white ring-amber-700/80 dark:bg-amber-500/50 dark:text-amber-100 dark:ring-amber-400/90"
: "bg-red-600/60 text-white ring-red-700/80 dark:bg-red-500/50 dark:text-red-100 dark:ring-red-400/90"
}`}>
{ready ? "Ready" : isLinked ? "Permission needed" : "Base ROM needed"}
{status === "downloading" ? "Downloading..." : ready ? "Ready" : isLinked ? "Permission needed" : "Base ROM needed"}
</span>
{!ready && !isLinked && (
<label className="inline-flex items-center gap-2 text-xs text-foreground/80">