dragonflight/services/web-ui/public/screens-editor.jsx

184 lines
7.7 KiB
JavaScript

// 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 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} />
{!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;