Files
video-shader-toys/ui/src/App.jsx
Aiden be315111ea
Some checks failed
CI / Native Windows Build And Tests (push) Has been cancelled
CI / React UI Build (push) Has been cancelled
CI / Windows Release Package (push) Has been cancelled
UI updates and preroll buffer to 8 frames
2026-05-05 20:56:53 +10:00

122 lines
4.2 KiB
JavaScript

import { useEffect, useState } from "react";
import { LayerStack } from "./components/LayerStack";
import { StackPresetToolbar } from "./components/StackPresetToolbar";
import { StatusPanels } from "./components/StatusPanels";
import { useRuntimeState } from "./hooks/useRuntimeState";
function App() {
const [appState, setAppState] = useRuntimeState();
const [pendingShaderId, setPendingShaderId] = useState("");
const [presetName, setPresetName] = useState("");
const [selectedPresetName, setSelectedPresetName] = useState("");
const [expandedLayerIds, setExpandedLayerIds] = useState([]);
const [dragLayerId, setDragLayerId] = useState(null);
const [dropTargetLayerId, setDropTargetLayerId] = useState(null);
const layers = appState?.layers ?? [];
const shaders = appState?.shaders ?? [];
const performance = appState?.performance ?? {};
const runtime = appState?.runtime ?? {};
const video = appState?.video ?? {};
const app = appState?.app ?? {};
const stackPresets = appState?.stackPresets ?? [];
useEffect(() => {
if (!pendingShaderId && shaders.length > 0) {
setPendingShaderId(shaders[0].id);
} else if (pendingShaderId && !shaders.some((shader) => shader.id === pendingShaderId)) {
setPendingShaderId(shaders[0]?.id ?? "");
}
}, [pendingShaderId, shaders]);
useEffect(() => {
if (!selectedPresetName && stackPresets.length > 0) {
setSelectedPresetName(stackPresets[0]);
} else if (selectedPresetName && !stackPresets.includes(selectedPresetName)) {
setSelectedPresetName(stackPresets[0] ?? "");
}
}, [selectedPresetName, stackPresets]);
useEffect(() => {
const layerIds = new Set(layers.map((layer) => layer.id));
setExpandedLayerIds((current) => current.filter((layerId) => layerIds.has(layerId)));
}, [layers]);
if (!appState) {
return (
<main className="layout">
<section className="panel">
<h3>Loading</h3>
<p className="muted">Waiting for control state from the native host.</p>
<div className="progress-track" aria-hidden="true">
<div className="progress-bar is-indeterminate" />
</div>
</section>
</main>
);
}
return (
<main className="layout">
<header className="app-header">
<div>
<h2>Video Shader Toys</h2>
<p className="muted">Live shader stack, DeckLink status, and runtime controls.</p>
</div>
<div className={`status-pill${runtime.compileSucceeded ? " status-pill--ready" : " status-pill--error"}`}>
{runtime.compileSucceeded ? "Ready" : "Compile Error"}
</div>
</header>
<section className="panel app-summary" aria-label="Runtime summary">
<dl className="summary-grid">
<div className="summary-item">
<dt>Shaders</dt>
<dd>{shaders.length}</dd>
</div>
<div className="summary-item">
<dt>Layers</dt>
<dd>{layers.length}</dd>
</div>
<div className="summary-item">
<dt>Signal</dt>
<dd>{video.hasSignal ? "Present" : "Missing"}</dd>
</div>
<div className="summary-item">
<dt>Render</dt>
<dd>{Number(performance.renderMs ?? 0).toFixed(2)} ms</dd>
</div>
</dl>
</section>
<section className="dashboard-grid">
<StatusPanels app={app} performance={performance} runtime={runtime} video={video} />
<StackPresetToolbar
presetName={presetName}
selectedPresetName={selectedPresetName}
stackPresets={stackPresets}
onPresetNameChange={setPresetName}
onSelectedPresetNameChange={setSelectedPresetName}
/>
</section>
<LayerStack
dragLayerId={dragLayerId}
dropTargetLayerId={dropTargetLayerId}
expandedLayerIds={expandedLayerIds}
layers={layers}
pendingShaderId={pendingShaderId}
setAppState={setAppState}
setDragLayerId={setDragLayerId}
setDropTargetLayerId={setDropTargetLayerId}
setExpandedLayerIds={setExpandedLayerIds}
setPendingShaderId={setPendingShaderId}
shaders={shaders}
/>
</main>
);
}
export default App;