/* Stage viewport — render preview, HUD, title-safe guides, transport. */
import { useEffect, useRef, useState } from "react";
import { Icon } from "./Icon";
import { cap, fmt } from "../state/util";
import type { EpisodeDocument, Shot, ViewMode } from "../state/types";
function RenderOverlay({ pct }: { pct: number }) {
const r = 32;
const c = 2 * Math.PI * r;
const indeterminate = pct < 0;
return (
{indeterminate ? "···" : Math.round(pct) + "%"}
Rendering preview
{indeterminate ? "aura · working…" : pct < 40 ? "aura · rigging cast…" : pct < 75 ? "aura · simulating lighting…" : "aura · compositing frames…"}
);
}
export interface StageProps {
data: EpisodeDocument;
shot: Shot | null;
time: number;
playing: boolean;
onPlay: () => void;
onScrub: (t: number) => void;
onStep: (dir: number) => void;
viewMode: ViewMode;
rendering: boolean;
renderPct: number;
/** Most recent REAL render output (served under /preview/*), or null before first render. */
renderVideo?: string | null;
renderPoster?: string | null;
/** No real working document exists yet — render the empty "describe a scene" state. */
empty?: boolean;
}
export function Stage({ data, shot, time, playing, onPlay, onScrub, onStep, viewMode, rendering, renderPct, renderVideo, renderPoster, empty }: StageProps) {
const [guides, setGuides] = useState(false);
const [cc, setCc] = useState(true);
const [isFullscreen, setIsFullscreen] = useState(false);
const stageRef = useRef(null);
const DUR = data.DUR;
useEffect(() => {
const onFsChange = () => setIsFullscreen(document.fullscreenElement === stageRef.current);
document.addEventListener("fullscreenchange", onFsChange);
return () => document.removeEventListener("fullscreenchange", onFsChange);
}, []);
const toggleFullscreen = () => {
if (document.fullscreenElement === stageRef.current) {
void document.exitFullscreen();
} else {
void stageRef.current?.requestFullscreen();
}
};
// Drive the actual