Add Z-AMPP UI: screens-home + screens-library: screens-library.jsx
This commit is contained in:
parent
100fc054cc
commit
4a77c1bed8
1 changed files with 156 additions and 0 deletions
156
services/web-ui/public/screens-library.jsx
Normal file
156
services/web-ui/public/screens-library.jsx
Normal file
|
|
@ -0,0 +1,156 @@
|
|||
// screens-library.jsx — library / asset grid + asset detail
|
||||
const { ASSETS: ALL_ASSETS, BINS, COMMENTS, PROJECTS } = window.ZAMPP_DATA;
|
||||
|
||||
function Library({ navigate, onOpenAsset, project = "Protour 2026" }) {
|
||||
const [bin, setBin] = React.useState("b1");
|
||||
const [view, setView] = React.useState("grid");
|
||||
const [filter, setFilter] = React.useState("all");
|
||||
const [search, setSearch] = React.useState("");
|
||||
|
||||
let assets = ALL_ASSETS;
|
||||
if (filter !== "all") assets = assets.filter(a => a.status === filter);
|
||||
if (search) assets = assets.filter(a => a.name.toLowerCase().includes(search.toLowerCase()));
|
||||
|
||||
return (
|
||||
<div className="library-layout">
|
||||
<aside className="library-rail">
|
||||
<div>
|
||||
<h4>Projects</h4>
|
||||
<div className="rail-list">
|
||||
{PROJECTS.slice(0, 6).map(p => (
|
||||
<div key={p.id} className={`rail-item ${p.name === project ? "active" : ""}`}>
|
||||
<span className="rail-color-dot" style={{ background: p.color }} />
|
||||
<span style={{ overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{p.name}</span>
|
||||
<span className="rail-count">{p.assets}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h4>Bins</h4>
|
||||
<div className="rail-list">
|
||||
{BINS.map(b => (
|
||||
<div key={b.id} className={`rail-item ${bin === b.id ? "active" : ""}`} onClick={() => setBin(b.id)}>
|
||||
<Icon name={binIcon(b.icon)} size={13} className="rail-icon" />
|
||||
<span>{b.name}</span>
|
||||
<span className="rail-count">{b.count}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h4>Smart filters</h4>
|
||||
<div className="rail-list">
|
||||
<div className="rail-item"><Icon name="alert" size={13} className="rail-icon" /><span>Errors</span><span className="rail-count">2</span></div>
|
||||
<div className="rail-item"><Icon name="clock" size={13} className="rail-icon" /><span>Last 24h</span><span className="rail-count">38</span></div>
|
||||
<div className="rail-item"><Icon name="comment" size={13} className="rail-icon" /><span>With comments</span><span className="rail-count">9</span></div>
|
||||
<div className="rail-item"><Icon name="check" size={13} className="rail-icon" /><span>Approved</span><span className="rail-count">14</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<div className="library-main">
|
||||
<div className="library-toolbar">
|
||||
<div className="toolbar-title">{project}</div>
|
||||
<span className="count">· {assets.length} assets</span>
|
||||
<div style={{ flex: 1 }} />
|
||||
<div className="search" style={{ width: 220 }}>
|
||||
<Icon name="search" className="search-icon" />
|
||||
<input value={search} onChange={e => setSearch(e.target.value)} placeholder="Filter assets…" />
|
||||
</div>
|
||||
<div className="tab-group">
|
||||
{["all", "ready", "processing", "live", "error"].map(f => (
|
||||
<button key={f} className={filter === f ? "active" : ""} onClick={() => setFilter(f)}>
|
||||
{f === "all" ? "All" : f[0].toUpperCase() + f.slice(1)}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<div className="tab-group">
|
||||
<button className={view === "grid" ? "active" : ""} onClick={() => setView("grid")} data-tip="Grid"><Icon name="grid" size={12} /></button>
|
||||
<button className={view === "list" ? "active" : ""} onClick={() => setView("list")} data-tip="List"><Icon name="list" size={12} /></button>
|
||||
</div>
|
||||
<button className="btn primary"><Icon name="upload" />Upload</button>
|
||||
</div>
|
||||
|
||||
{view === "grid" ? (
|
||||
<div className="library-grid">
|
||||
{assets.map(a => <AssetCard key={a.id} asset={a} onOpen={() => onOpenAsset(a)} />)}
|
||||
</div>
|
||||
) : (
|
||||
<div className="library-list">
|
||||
<div className="list-row head">
|
||||
<div></div>
|
||||
<div>Name</div>
|
||||
<div>Duration</div>
|
||||
<div>Resolution</div>
|
||||
<div>Codec</div>
|
||||
<div>Size</div>
|
||||
<div>Updated</div>
|
||||
<div></div>
|
||||
</div>
|
||||
{assets.map(a => (
|
||||
<div key={a.id} className="list-row" onClick={() => onOpenAsset(a)}>
|
||||
<div className="thumb"><AssetThumb asset={a} /></div>
|
||||
<div>
|
||||
<div className="name">{a.name}</div>
|
||||
<div style={{ display: "flex", gap: 6, marginTop: 2 }}>
|
||||
<StatusDot status={a.status} />
|
||||
<span style={{ fontSize: 11, color: "var(--text-3)" }}>{a.status}</span>
|
||||
{a.comments > 0 && <span style={{ fontSize: 11, color: "var(--text-3)" }}>· {a.comments} comments</span>}
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-sub">{a.duration}</div>
|
||||
<div className="col-sub">{a.res}</div>
|
||||
<div className="col-sub">{a.codec}</div>
|
||||
<div className="col-sub">{a.size}</div>
|
||||
<div className="col-sub">{a.updated}</div>
|
||||
<button className="icon-btn" onClick={e => e.stopPropagation()}><Icon name="more" /></button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function AssetCard({ asset, onOpen }) {
|
||||
return (
|
||||
<div className="asset-card" onClick={onOpen}>
|
||||
<AssetThumb asset={asset} />
|
||||
<div className="thumb-status">
|
||||
{asset.status === "live" && <span className="badge live">LIVE</span>}
|
||||
{asset.status === "processing" && <span className="badge warning">Proxy {asset.progress}%</span>}
|
||||
{asset.status === "error" && <span className="badge danger">Error</span>}
|
||||
</div>
|
||||
{asset.type === "video" && <div className="thumb-duration">{asset.duration}</div>}
|
||||
{asset.status === "processing" && (
|
||||
<div className="asset-progress-overlay">
|
||||
<div className="asset-progress-bar">
|
||||
<div className="asset-progress-fill" style={{ width: `${asset.progress || 0}%` }} />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className="meta">
|
||||
<div className="name">{asset.name}</div>
|
||||
<div className="sub">
|
||||
<span>{asset.res}</span>
|
||||
<span>·</span>
|
||||
<span>{asset.size}</span>
|
||||
{asset.comments > 0 && (
|
||||
<span style={{ marginLeft: "auto", display: "flex", alignItems: "center", gap: 3 }}>
|
||||
<Icon name="comment" size={10} />{asset.comments}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function binIcon(name) {
|
||||
return { grid: "library", live: "record", film: "film", proxy: "proxy", audio: "audio", package: "package" }[name] || "folder";
|
||||
}
|
||||
|
||||
window.Library = Library;
|
||||
window.AssetCard = AssetCard;
|
||||
Loading…
Reference in a new issue