UI Cleanup
Some checks failed
CI / React UI Build (push) Successful in 11s
CI / Native Windows Build And Tests (push) Failing after 1m44s
CI / Windows Release Package (push) Has been skipped

This commit is contained in:
2026-05-22 17:44:12 +10:00
parent c35ca8d61c
commit 3c3b1d68ff
3 changed files with 33 additions and 142 deletions

View File

@@ -23,8 +23,6 @@ function AppFooter() {
function App() {
const [appState, setAppState] = useRuntimeState();
const [presetName, setPresetName] = useState("");
const [selectedPresetName, setSelectedPresetName] = useState("");
const [expandedLayerIds, setExpandedLayerIds] = useState([]);
const [dragLayerId, setDragLayerId] = useState(null);
const [dropTargetLayerId, setDropTargetLayerId] = useState(null);
@@ -34,16 +32,8 @@ function App() {
const performance = appState?.performance ?? {};
const runtime = appState?.runtime ?? {};
const video = appState?.video ?? {};
const videoOutput = appState?.videoOutput ?? {};
const app = appState?.app ?? {};
const stackPresets = appState?.stackPresets ?? [];
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));
@@ -70,7 +60,7 @@ function App() {
<header className="app-header">
<div>
<h2>Video Shader Toys</h2>
<p className="muted">Live shader stack, DeckLink status, and runtime controls.</p>
<p className="muted">Live shader stack, video IO status, and runtime controls.</p>
</div>
<div className={`status-pill${runtime.compileSucceeded ? " status-pill--ready" : " status-pill--error"}`}>
{runtime.compileSucceeded ? "Ready" : "Compile Error"}
@@ -88,8 +78,8 @@ function App() {
<dd>{layers.length}</dd>
</div>
<div className="summary-item">
<dt>Signal</dt>
<dd>{video.hasSignal ? "Present" : "Missing"}</dd>
<dt>Output</dt>
<dd>{videoOutput.enabled ? "Enabled" : "Disabled"}</dd>
</div>
<div className="summary-item">
<dt>Render</dt>
@@ -99,14 +89,8 @@ function App() {
</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}
/>
<StatusPanels app={app} performance={performance} runtime={runtime} video={video} videoOutput={videoOutput} />
<StackPresetToolbar />
</section>
<LayerStack

View File

@@ -1,48 +1,16 @@
import { Camera, FolderOpen, RefreshCw, Save } from "lucide-react";
import { useState } from "react";
import { RefreshCw } from "lucide-react";
import { postJson } from "../api/controlApi";
export function StackPresetToolbar({
presetName,
selectedPresetName,
stackPresets,
onPresetNameChange,
onSelectedPresetNameChange,
}) {
const [screenshotQueued, setScreenshotQueued] = useState(false);
const trimmedPresetName = presetName.trim();
const normalizedPresetName = trimmedPresetName
.toLowerCase()
.replace(/[^a-z0-9]+/g, "-")
.replace(/^-+|-+$/g, "");
const willOverwrite = normalizedPresetName ? stackPresets.includes(normalizedPresetName) : false;
async function requestScreenshot() {
setScreenshotQueued(true);
try {
await postJson("/api/screenshot", {});
} finally {
window.setTimeout(() => setScreenshotQueued(false), 1200);
}
}
export function StackPresetToolbar() {
return (
<div className="panel stack-panel">
<div className="panel__header stack-panel__header">
<div>
<h3>Stack presets</h3>
<p className="muted">Save or recall the current layer chain.</p>
<h3>Runtime controls</h3>
<p className="muted">Rescan manifests and rebuild changed active shader layers.</p>
</div>
<div className="stack-panel__actions">
<button
type="button"
className="button-with-icon stack-panel__screenshot"
onClick={requestScreenshot}
>
<Camera size={16} strokeWidth={1.9} aria-hidden="true" />
<span>{screenshotQueued ? "Queued" : "Screenshot"}</span>
</button>
<button
type="button"
className="button-with-icon stack-panel__reload"
@@ -53,84 +21,6 @@ export function StackPresetToolbar({
</button>
</div>
</div>
<div className="stack-panel__grid">
<div className="toolbar__group">
<label htmlFor="preset-name">Save stack</label>
<div className="toolbar__inline">
<input
id="preset-name"
type="text"
placeholder="Preset name"
value={presetName}
onChange={(event) => onPresetNameChange(event.target.value)}
/>
<button
type="button"
className={`button-with-icon${willOverwrite ? " stack-panel__save--overwrite" : ""}`}
disabled={!trimmedPresetName}
onClick={async () => {
if (!trimmedPresetName) {
return;
}
const response = await postJson("/api/stack-presets/save", { presetName: trimmedPresetName });
if (response?.ok) {
onSelectedPresetNameChange(normalizedPresetName);
onPresetNameChange("");
}
}}
>
<Save size={16} strokeWidth={1.9} aria-hidden="true" />
<span>{willOverwrite ? "Overwrite" : "Save"}</span>
</button>
</div>
{trimmedPresetName ? (
<p className="muted toolbar__status" role="status">
{willOverwrite
? `This will overwrite the existing preset "${normalizedPresetName}".`
: `This will save as "${normalizedPresetName}".`}
</p>
) : (
<p className="muted toolbar__status toolbar__status--placeholder" aria-hidden="true">
Preset status
</p>
)}
</div>
<div className="toolbar__group">
<label htmlFor="preset-select">Recall stack</label>
<div className="toolbar__inline">
<select
id="preset-select"
value={selectedPresetName}
onChange={(event) => onSelectedPresetNameChange(event.target.value)}
>
{stackPresets.length === 0 ? <option value="">No presets</option> : null}
{stackPresets.map((preset) => (
<option key={preset} value={preset}>
{preset}
</option>
))}
</select>
<button
type="button"
className="button-with-icon"
disabled={!selectedPresetName}
onClick={() => {
if (selectedPresetName) {
postJson("/api/stack-presets/load", { presetName: selectedPresetName });
}
}}
>
<FolderOpen size={16} strokeWidth={1.9} aria-hidden="true" />
<span>Recall</span>
</button>
</div>
<p className="muted toolbar__status toolbar__status--placeholder" aria-hidden="true">
Preset status
</p>
</div>
</div>
</div>
);
}

View File

@@ -4,8 +4,21 @@ function formatNumber(value, digits = 3) {
return Number(value ?? 0).toFixed(digits);
}
export function StatusPanels({ app, performance, runtime, video }) {
function formatEndpoint(endpoint) {
const backend = endpoint?.backend || "none";
const device = endpoint?.device ? ` ${endpoint.device}` : "";
return `${backend}${device}`;
}
function formatVideoMode(endpoint) {
const resolution = endpoint?.resolution || "Unknown";
const frameRate = endpoint?.frameRate ? ` ${endpoint.frameRate}` : "";
return `${resolution}${frameRate}`;
}
export function StatusPanels({ app, performance, runtime, video, videoOutput }) {
const budgetUsedPercent = Math.max(0, Math.min(100, Number(performance.budgetUsedPercent) || 0));
const outputEnabled = Boolean(videoOutput?.enabled);
return (
<>
@@ -16,8 +29,8 @@ export function StatusPanels({ app, performance, runtime, video }) {
<span className={`mini-status${runtime.compileSucceeded ? " mini-status--ready" : " mini-status--error"}`}>
{runtime.compileSucceeded ? "Ready" : "Error"}
</span>
<span className={`mini-status${video.hasSignal ? " mini-status--ready" : " mini-status--error"}`}>
{video.hasSignal ? "Signal" : "No signal"}
<span className={`mini-status${outputEnabled ? " mini-status--ready" : " mini-status--error"}`}>
{outputEnabled ? "Output" : "No output"}
</span>
</div>
</div>
@@ -51,9 +64,13 @@ export function StatusPanels({ app, performance, runtime, video }) {
<KvList
variant="rows"
values={[
["Input mode", video.modeName || "Unknown"],
["Input size", `${video.width || 0} x ${video.height || 0}`],
["Output", `${app.outputVideoFormat || "Unknown"}${app.outputFrameRate ? ` ${app.outputFrameRate}` : ""}`],
["Input", formatEndpoint(app.input)],
["Input mode", formatVideoMode(app.input)],
["Output", formatEndpoint(app.output)],
["Output mode", formatVideoMode(app.output)],
["Output size", `${video.width || 0} x ${video.height || 0}`],
["Output status", videoOutput?.statusMessage || "Unknown"],
["Schedule failures", `${videoOutput?.scheduleFailures ?? 0}`],
]}
/>
</section>