Add Z-AMPP UI: screens-jobs + screens-editor + modal-new-recorder: screens-editor.jsx
This commit is contained in:
parent
f8bd80e38e
commit
b8e1796c33
1 changed files with 186 additions and 0 deletions
186
services/web-ui/public/screens-editor.jsx
Normal file
186
services/web-ui/public/screens-editor.jsx
Normal file
|
|
@ -0,0 +1,186 @@
|
|||
// screens-editor.jsx — Editor (timeline) stub
|
||||
const { ASSETS } = window.ZAMPP_DATA;
|
||||
|
||||
function Editor() {
|
||||
const [playing, setPlaying] = React.useState(false);
|
||||
const [currentMs, setCurrentMs] = React.useState(8200);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!playing) return;
|
||||
const i = setInterval(() => setCurrentMs(t => t + 100), 100);
|
||||
return () => clearInterval(i);
|
||||
}, [playing]);
|
||||
|
||||
return (
|
||||
<div className="editor-shell">
|
||||
<div className="editor-topbar">
|
||||
<span className="badge warning">DEV BUILD</span>
|
||||
<span style={{ fontWeight: 600, fontSize: 13 }}>Untitled sequence</span>
|
||||
<span className="muted" style={{ fontSize: 11.5 }}>Protour 2026 · auto-saved 1m ago</span>
|
||||
<div style={{ flex: 1 }} />
|
||||
<button className="btn ghost sm"><Icon name="download" />Render</button>
|
||||
<button className="btn subtle sm">Send to AMPP</button>
|
||||
<button className="btn primary sm">Publish</button>
|
||||
</div>
|
||||
<div className="editor-body">
|
||||
<aside className="editor-bins">
|
||||
<div style={{ padding: "12px 12px 8px", display: "flex", alignItems: "center", gap: 6 }}>
|
||||
<span style={{ fontWeight: 600, fontSize: 12 }}>Project bin</span>
|
||||
<span style={{ flex: 1 }} />
|
||||
<button className="icon-btn"><Icon name="search" size={12} /></button>
|
||||
</div>
|
||||
<div style={{ padding: "0 8px 12px", display: "flex", flexDirection: "column", gap: 4 }}>
|
||||
{ASSETS.slice(0, 8).map(a => (
|
||||
<div key={a.id} className="editor-bin-item">
|
||||
<div className="editor-bin-thumb"><FauxFrame seed={a.seed} /></div>
|
||||
<div style={{ flex: 1, minWidth: 0 }}>
|
||||
<div style={{ fontSize: 11.5, fontWeight: 500, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{a.name}</div>
|
||||
<div className="mono" style={{ fontSize: 10, color: "var(--text-3)" }}>{a.duration}</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</aside>
|
||||
<div className="editor-viewer">
|
||||
<div className="editor-canvas">
|
||||
<FauxFrame seed={2} />
|
||||
<div className="scanlines" />
|
||||
{!playing && (
|
||||
<button className="player-play-overlay" onClick={() => setPlaying(true)}>
|
||||
<Icon name="play" size={28} />
|
||||
</button>
|
||||
)}
|
||||
<div className="player-tc"><span className="mono">00:00:08:06</span></div>
|
||||
</div>
|
||||
<div className="editor-transport">
|
||||
<button className="icon-btn"><Icon name="arrowLeft" size={14} /></button>
|
||||
<button className="icon-btn" onClick={() => setPlaying(p => !p)}>
|
||||
<Icon name={playing ? "pause" : "play"} size={14} />
|
||||
</button>
|
||||
<button className="icon-btn"><Icon name="arrowRight" size={14} /></button>
|
||||
<span style={{ flex: 1 }} />
|
||||
<button className="btn ghost sm">Mark in</button>
|
||||
<button className="btn ghost sm">Mark out</button>
|
||||
<button className="btn subtle sm">Add to timeline</button>
|
||||
</div>
|
||||
</div>
|
||||
<aside className="editor-insp">
|
||||
<div style={{ padding: "12px", fontSize: 12, fontWeight: 600, borderBottom: "1px solid var(--border)" }}>Inspector</div>
|
||||
<div style={{ padding: 12, display: "flex", flexDirection: "column", gap: 12 }}>
|
||||
<InspGroup title="Transform">
|
||||
<InspRow label="Position" value="0, 0" />
|
||||
<InspRow label="Scale" value="100%" />
|
||||
<InspRow label="Rotation" value="0°" />
|
||||
</InspGroup>
|
||||
<InspGroup title="Color">
|
||||
<InspRow label="Exposure" value="0.0" />
|
||||
<InspRow label="Contrast" value="0.0" />
|
||||
<InspRow label="Saturation" value="0.0" />
|
||||
</InspGroup>
|
||||
<InspGroup title="Audio">
|
||||
<InspRow label="Level" value="0.0 dB" />
|
||||
<InspRow label="Pan" value="C" />
|
||||
</InspGroup>
|
||||
</div>
|
||||
</aside>
|
||||
</div>
|
||||
<EditorTimeline currentMs={currentMs} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function InspGroup({ title, children }) {
|
||||
return (
|
||||
<div>
|
||||
<div className="muted" style={{ fontSize: 10, textTransform: "uppercase", letterSpacing: "0.06em", fontWeight: 600, marginBottom: 6 }}>{title}</div>
|
||||
<div style={{ display: "flex", flexDirection: "column", gap: 4 }}>{children}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
function InspRow({ label, value }) {
|
||||
return (
|
||||
<div style={{ display: "grid", gridTemplateColumns: "70px 1fr", alignItems: "center", fontSize: 11.5 }}>
|
||||
<span style={{ color: "var(--text-3)" }}>{label}</span>
|
||||
<span className="mono" style={{ background: "var(--bg-2)", padding: "3px 6px", borderRadius: 4, border: "1px solid var(--border)" }}>{value}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function EditorTimeline({ currentMs }) {
|
||||
const v1Clips = [
|
||||
{ id: "v1a", start: 0, len: 12, color: "#5B7CFA", label: "Stage_Cam_A" },
|
||||
{ id: "v1b", start: 12, len: 8, color: "#5B7CFA", label: "Drone_Aerial" },
|
||||
{ id: "v1c", start: 20, len: 18, color: "#5B7CFA", label: "Wide_Cam_B" },
|
||||
{ id: "v1d", start: 38, len: 10, color: "#5B7CFA", label: "Trophy" },
|
||||
];
|
||||
const v2Clips = [
|
||||
{ id: "v2a", start: 6, len: 4, color: "#B57CFA", label: "Lower3rd" },
|
||||
{ id: "v2b", start: 32, len: 4, color: "#B57CFA", label: "Sponsor" },
|
||||
];
|
||||
const a1Clips = [
|
||||
{ id: "a1a", start: 0, len: 48, color: "#2DD4A8", label: "FOH Mix", audio: true },
|
||||
];
|
||||
const a2Clips = [
|
||||
{ id: "a2a", start: 12, len: 6, color: "#F5A623", label: "VO", audio: true },
|
||||
];
|
||||
const total = 60;
|
||||
const playheadPct = ((currentMs / 1000) / total) * 100;
|
||||
|
||||
return (
|
||||
<div className="editor-timeline">
|
||||
<div className="editor-timeline-head">
|
||||
<span className="mono" style={{ fontSize: 11, color: "var(--text-3)" }}>Timeline · 1m total</span>
|
||||
<span style={{ flex: 1 }} />
|
||||
<div className="tab-group">
|
||||
<button className="active"><Icon name="zap" size={11} /></button>
|
||||
<button><Icon name="layers" size={11} /></button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="timeline-ruler">
|
||||
{Array.from({ length: 13 }).map((_, i) => (
|
||||
<div key={i} className="ruler-tick">
|
||||
{i % 2 === 0 && <span className="mono">{`00:${String(i * 5).padStart(2, "0")}`}</span>}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<TimelineTrack label="V2" clips={v2Clips} total={total} />
|
||||
<TimelineTrack label="V1" clips={v1Clips} total={total} hasFilm />
|
||||
<TimelineTrack label="A1" clips={a1Clips} total={total} audio />
|
||||
<TimelineTrack label="A2" clips={a2Clips} total={total} audio />
|
||||
<div className="timeline-playhead" style={{ left: `calc(40px + (100% - 40px) * ${playheadPct / 100})` }} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function TimelineTrack({ label, clips, total, hasFilm, audio }) {
|
||||
return (
|
||||
<div className="timeline-track">
|
||||
<div className="timeline-track-label">{label}</div>
|
||||
<div className="timeline-track-lane">
|
||||
{clips.map(c => {
|
||||
const left = (c.start / total) * 100;
|
||||
const width = (c.len / total) * 100;
|
||||
return (
|
||||
<div key={c.id} className={`clip ${audio ? "audio" : ""}`} style={{ left: `${left}%`, width: `${width}%`, background: c.color }}>
|
||||
<div className="clip-label">{c.label}</div>
|
||||
{hasFilm && (
|
||||
<div className="clip-film">
|
||||
{Array.from({ length: 8 }).map((_, i) => (
|
||||
<div key={i} className="clip-film-cell"><FauxFrame seed={(i + c.start) % 6} /></div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
{audio && (
|
||||
<div className="clip-wave">
|
||||
<Waveform seed={c.start + label.length} color="rgba(255,255,255,0.55)" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
window.Editor = Editor;
|
||||
Loading…
Reference in a new issue