2026-02-20 15:48:15 -08:00
|
|
|
import { useCallback, useRef } from "react";
|
2026-02-28 17:58:09 -08:00
|
|
|
import { MdOndemandVideo } from "react-icons/md";
|
|
|
|
|
import { useDemoActions, useDemoRecording } from "./DemoProvider";
|
|
|
|
|
import { createDemoStreamingRecording } from "../demo/streaming";
|
2026-03-01 09:40:17 -08:00
|
|
|
import styles from "./LoadDemoButton.module.css";
|
2026-02-20 15:48:15 -08:00
|
|
|
|
|
|
|
|
export function LoadDemoButton() {
|
2026-02-28 17:58:09 -08:00
|
|
|
const recording = useDemoRecording();
|
|
|
|
|
const { setRecording } = useDemoActions();
|
2026-02-20 15:48:15 -08:00
|
|
|
const inputRef = useRef<HTMLInputElement>(null);
|
2026-02-28 17:58:09 -08:00
|
|
|
const parseTokenRef = useRef(0);
|
2026-02-20 15:48:15 -08:00
|
|
|
|
|
|
|
|
const handleClick = useCallback(() => {
|
|
|
|
|
if (recording) {
|
|
|
|
|
// Unload the current recording.
|
2026-02-28 17:58:09 -08:00
|
|
|
parseTokenRef.current += 1;
|
2026-02-20 15:48:15 -08:00
|
|
|
setRecording(null);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
inputRef.current?.click();
|
|
|
|
|
}, [recording, setRecording]);
|
|
|
|
|
|
|
|
|
|
const handleFileChange = useCallback(
|
|
|
|
|
async (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
|
|
|
const file = e.target.files?.[0];
|
|
|
|
|
if (!file) return;
|
|
|
|
|
// Reset the input so the same file can be re-selected.
|
|
|
|
|
e.target.value = "";
|
|
|
|
|
try {
|
|
|
|
|
const buffer = await file.arrayBuffer();
|
2026-02-28 17:58:09 -08:00
|
|
|
const parseToken = parseTokenRef.current + 1;
|
|
|
|
|
parseTokenRef.current = parseToken;
|
|
|
|
|
const recording = await createDemoStreamingRecording(buffer);
|
|
|
|
|
if (parseTokenRef.current !== parseToken) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
// Metadata-first: mission/game-mode sync happens immediately.
|
|
|
|
|
setRecording(recording);
|
2026-02-20 15:48:15 -08:00
|
|
|
} catch (err) {
|
|
|
|
|
console.error("Failed to load demo:", err);
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
[setRecording],
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<>
|
|
|
|
|
<input
|
|
|
|
|
ref={inputRef}
|
|
|
|
|
type="file"
|
|
|
|
|
accept=".rec"
|
|
|
|
|
style={{ display: "none" }}
|
|
|
|
|
onChange={handleFileChange}
|
|
|
|
|
/>
|
|
|
|
|
<button
|
|
|
|
|
type="button"
|
2026-03-01 09:40:17 -08:00
|
|
|
className={styles.Root}
|
2026-02-20 15:48:15 -08:00
|
|
|
aria-label={recording ? "Unload demo" : "Load demo (.rec)"}
|
|
|
|
|
title={recording ? "Unload demo" : "Load demo (.rec)"}
|
|
|
|
|
onClick={handleClick}
|
|
|
|
|
data-active={recording ? "true" : undefined}
|
|
|
|
|
>
|
2026-03-01 09:40:17 -08:00
|
|
|
<MdOndemandVideo className={styles.DemoIcon} />
|
|
|
|
|
<span className={styles.ButtonLabel}>
|
2026-02-20 15:48:15 -08:00
|
|
|
{recording ? "Unload demo" : "Demo"}
|
|
|
|
|
</span>
|
|
|
|
|
</button>
|
|
|
|
|
</>
|
|
|
|
|
);
|
|
|
|
|
}
|