diff --git a/services/web-ui/public/screens-library.jsx b/services/web-ui/public/screens-library.jsx index 6a19443..daf8972 100644 --- a/services/web-ui/public/screens-library.jsx +++ b/services/web-ui/public/screens-library.jsx @@ -23,18 +23,18 @@ function Library({ navigate, onOpenAsset, openProject }) { }, [openProject]); const createBin = () => { - if (!openProject) { - window.alert('Open a project first (Projects → click a project), then create a bin inside it.'); - return; - } - const name = prompt('Bin name', ''); - if (!name || !name.trim()) return; + if (!openProject) { window.alert('Open a project first (Projects → click a project), then create a bin inside it.'); return; } + setNewBinName(''); setCreatingBin(true); + }; + + const submitBin = (name) => { + if (!name || !name.trim()) { setCreatingBin(false); return; } + setCreatingBin(false); window.ZAMPP_API.fetch('/bins', { method: 'POST', body: JSON.stringify({ project_id: openProject.id, name: name.trim() }), }) .then(() => { - // Re-fetch project-scoped list to get the count column. window.ZAMPP_API.fetch('/bins?project_id=' + openProject.id) .then(list => setBins((list || []).map(b => ({ ...b, count: b.asset_count || 0, icon: b.type || 'grid' })))); }) @@ -48,6 +48,9 @@ function Library({ navigate, onOpenAsset, openProject }) { // a full app reload — keeps ZAMPP_DATA in sync as the cache of record. const [allAssets, setAllAssets] = React.useState(window.ZAMPP_DATA.ASSETS || []); const [ctxMenu, setCtxMenu] = React.useState(null); // { asset, x, y } + const [renamingAsset, setRenamingAsset] = React.useState(null); + const [creatingBin, setCreatingBin] = React.useState(false); + const [newBinName, setNewBinName] = React.useState(''); const refreshAssets = React.useCallback(() => { window.ZAMPP_API.fetch('/assets?limit=500') @@ -83,14 +86,21 @@ function Library({ navigate, onOpenAsset, openProject }) { setCtxMenu({ asset, x: e.clientX, y: e.clientY }); }; + const [selectedBinId, setSelectedBinId] = React.useState(null); + // Clear bin filter on project change so a stale id doesn't hide everything. + React.useEffect(() => { setSelectedBinId(null); }, [openProject?.id]); let assets = openProject ? allAssets.filter(function(a) { return a.project_id === openProject.id; }) : allAssets; const ALL_ASSETS = allAssets; if (filter !== 'all') assets = assets.filter(function(a) { return a.status === filter; }); if (search) assets = assets.filter(function(a) { return a.name.toLowerCase().includes(search.toLowerCase()); }); + if (selectedBinId) assets = assets.filter(function(a) { return a.bin_id === selectedBinId; }); - const displayTitle = openProject ? openProject.name : 'All Assets'; + const activeBin = selectedBinId ? BINS.find(b => b.id === selectedBinId) : null; + const displayTitle = activeBin + ? (openProject ? openProject.name + ' · ' : '') + activeBin.name + : (openProject ? openProject.name : 'All Assets'); const errorCount = ALL_ASSETS.filter(function(a) { return a.status === 'error'; }).length; const recentCount = ALL_ASSETS.filter(function(a) { return (Date.now() - new Date(a.created_at)) < 86400000; }).length; @@ -127,13 +137,35 @@ function Library({ navigate, onOpenAsset, openProject }) {
- {BINS.length === 0 ? ( + {creatingBin && ( +
+ +
+ )} + {!creatingBin && BINS.length === 0 ? (
{openProject ? 'No bins yet — click + to create one.' : 'Open a project to manage bins.'}
) : BINS.map(function(b) { + const isActive = selectedBinId === b.id; return ( -
+
{b.name} {b.count} @@ -224,13 +256,21 @@ function Library({ navigate, onOpenAsset, openProject }) { onClose={function() { setCtxMenu(null); }} onChanged={refreshAssets} onOpen={function() { onOpenAsset(ctxMenu.asset); }} + onRename={function(a) { setCtxMenu(null); setRenamingAsset(a); }} + /> + )} + {renamingAsset && ( + )}
); } -function AssetContextMenu({ asset, x, y, bins, onClose, onChanged, onOpen }) { +function AssetContextMenu({ asset, x, y, bins, onClose, onChanged, onOpen, onRename }) { const ref = React.useRef(null); // Pin the menu inside the viewport even if the user right-clicked near // the bottom-right edge of the grid. @@ -245,16 +285,7 @@ function AssetContextMenu({ asset, x, y, bins, onClose, onChanged, onOpen }) { setPos({ left: Math.max(margin, nx), top: Math.max(margin, ny) }); }, [x, y]); - const rename = function() { - onClose(); - const next = prompt('Rename asset', asset.display_name || asset.name || ''); - if (next == null) return; - const trimmed = next.trim(); - if (!trimmed || trimmed === (asset.display_name || asset.name)) return; - window.ZAMPP_API.fetch('/assets/' + asset.id, { method: 'PATCH', body: JSON.stringify({ display_name: trimmed }) }) - .then(onChanged) - .catch(function(e) { alert('Rename failed: ' + e.message); }); - }; + const rename = function() { if (onRename) onRename(asset); else onClose(); }; const moveToBin = function(binId) { onClose(); @@ -398,5 +429,43 @@ function binIcon(name) { return { grid: 'library', live: 'record', film: 'film', proxy: 'proxy', audio: 'audio', package: 'package' }[name] || 'folder'; } +function RenameAssetModal({ asset, onClose, onSaved }) { + const [name, setName] = React.useState(asset.display_name || asset.name || ''); + const [saving, setSaving] = React.useState(false); + const [err, setErr] = React.useState(null); + const original = asset.display_name || asset.name || ''; + const submit = function() { + const trimmed = name.trim(); + if (!trimmed || trimmed === original) { onClose(); return; } + setSaving(true); setErr(null); + window.ZAMPP_API.fetch('/assets/' + asset.id, { method: 'PATCH', body: JSON.stringify({ display_name: trimmed }) }) + .then(onSaved) + .catch(function(e) { setSaving(false); setErr(e.message); }); + }; + return ( +
+
+
+
Rename asset
+ +
+
+
+ + +
+ {err &&
{err}
} +
+
+ + +
+
+
+ ); +} + window.Library = Library; window.AssetCard = AssetCard;