// screens-asset.jsx — asset detail (Frame.io-style player, filmstrip, comments) const { COMMENTS: SEED_COMMENTS } = window.ZAMPP_DATA; function AssetDetail({ asset, onClose }) { const [playing, setPlaying] = React.useState(false); const [currentMs, setCurrentMs] = React.useState(720000); const [tab, setTab] = React.useState("comments"); const [showResolved, setShowResolved] = React.useState(false); const [comments, setComments] = React.useState(SEED_COMMENTS); const [newComment, setNewComment] = React.useState(""); const totalMs = parseDuration(asset.duration); React.useEffect(() => { if (!playing) return; const i = setInterval(() => { setCurrentMs(t => { const next = t + 100; if (next >= totalMs) { setPlaying(false); return totalMs; } return next; }); }, 100); return () => clearInterval(i); }, [playing, totalMs]); const seek = (ms) => setCurrentMs(Math.max(0, Math.min(totalMs, ms))); const addComment = () => { if (!newComment.trim()) return; const t = msToTimecode(currentMs); setComments(c => [...c, { id: `n${Date.now()}`, who: "Zach Gaetano", avatar: "ZG", time: t, real: "just now", text: newComment, resolved: false, frame: Math.floor(currentMs / 1000 * 30), }]); setNewComment(""); }; const visibleComments = comments.filter(c => showResolved || !c.resolved); return (
{asset.name} v3
{asset.project}·{asset.bin}·updated {asset.updated}
{!playing && ( )}
{visibleComments .filter(c => Math.abs(parseDuration(c.time) - currentMs) < 200) .map(c => (
{c.avatar}
{c.text}
))}
{asset.status === "live" && (
LIVE · REC
)}
{msToTimecode(currentMs)} / {asset.duration}
{msToTimecode(currentMs)} {asset.duration}
{tab === "comments" && ( )}
{tab === "comments" && ( seek(parseDuration(c.time))} onResolve={(id) => { setComments(cs => cs.map(c => c.id === id ? { ...c, resolved: !c.resolved } : c)); }} /> )} {tab === "versions" && } {tab === "metadata" && } {tab === "audio" && } {tab === "activity" && }
); } function PlaybackBar({ current, total, onSeek, comments }) { const ref = React.useRef(null); const handle = (e) => { const r = ref.current.getBoundingClientRect(); const p = (e.clientX - r.left) / r.width; onSeek(Math.max(0, Math.min(1, p)) * total); }; const pct = (current / total) * 100; return (
{comments.map(c => { const x = (parseDuration(c.time) / total) * 100; return (
{ e.stopPropagation(); onSeek(parseDuration(c.time)); }} > {c.avatar}
); })}
); } function FilmStrip({ seed, current, total, onSeek, comments }) { const ref = React.useRef(null); const frames = 28; const handle = (e) => { const r = ref.current.getBoundingClientRect(); onSeek(((e.clientX - r.left) / r.width) * total); }; return (
{Array.from({ length: frames }).map((_, i) => (
))}
{comments.map(c => { const x = (parseDuration(c.time) / total) * 100; return (
{c.avatar}
); })}
{[0, 0.25, 0.5, 0.75, 1].map(p => ( {msToTimecode(p * total)} ))}
); } function CommentsList({ comments, onSeek, onResolve }) { if (comments.length === 0) { return
No comments yet.
; } return (
{comments.map(c => (
{c.avatar}
{c.who} {c.real}
{c.text}
{c.resolved &&
✓ Resolved
}
))}
); } function CommentComposer({ asset, currentMs, value, onChange, onSubmit }) { return (
Reviewers
{["KM", "ZG", "MO", "JT"].map((a, i) => (
{a}
))}
@ {msToTimecode(currentMs)} Drawing tools