130 lines
5.7 KiB
JavaScript
130 lines
5.7 KiB
JavaScript
// screens-editor.jsx — Editor (timeline)
|
|
|
|
function Editor() {
|
|
const { ASSETS } = window.ZAMPP_DATA;
|
|
const [playing, setPlaying] = React.useState(false);
|
|
const [currentMs, setCurrentMs] = React.useState(0);
|
|
|
|
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 }}>New sequence</span>
|
|
<div style={{ flex: 1 }} />
|
|
<button className="btn ghost sm"><Icon name="download" />Export</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.length === 0 ? (
|
|
<div style={{ padding: '16px 4px', color: 'var(--text-3)', fontSize: 12 }}>No assets in library.</div>
|
|
) : ASSETS.slice(0, 12).map(a => (
|
|
<div key={a.id} className="editor-bin-item">
|
|
<div className="editor-bin-thumb"><AssetThumb asset={a} /></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 />
|
|
{!playing && (
|
|
<button className="player-play-overlay" onClick={() => setPlaying(true)}>
|
|
<Icon name="play" size={28} />
|
|
</button>
|
|
)}
|
|
<div className="player-tc"><span className="mono">{msToTimecode ? msToTimecode(currentMs) : '00:00:00:00'}</span></div>
|
|
</div>
|
|
<div className="editor-transport">
|
|
<button className="icon-btn" onClick={() => setCurrentMs(0)}><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} total={60} clips={[]} />
|
|
</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, total = 60, clips = [] }) {
|
|
const playheadPct = total > 0 ? ((currentMs / 1000) / total) * 100 : 0;
|
|
return (
|
|
<div className="editor-timeline">
|
|
<div className="editor-timeline-head">
|
|
<span className="mono" style={{ fontSize: 11, color: 'var(--text-3)' }}>Timeline</span>
|
|
<span style={{ flex: 1 }} />
|
|
</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>
|
|
{clips.length === 0 && (
|
|
<div style={{ padding: '20px 40px', color: 'var(--text-3)', fontSize: 12 }}>Drop assets from the bin to build a sequence.</div>
|
|
)}
|
|
<div className="timeline-playhead" style={{ left: `calc(40px + (100% - 40px) * ${playheadPct / 100})` }} />
|
|
</div>
|
|
);
|
|
}
|
|
|
|
window.Editor = Editor;
|