UI Cleanup
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user