feat(library,bins): inline bin creation in the left rail

Library's Bins section now always renders (not just when bins exist)
with a + button that prompts for a name and POSTs /api/v1/bins with
the open project's id. Bins re-fetch on project change so the rail
shows project-scoped bins when a project is open, or global view
otherwise.

Bins list now hydrates from local state instead of stale ZAMPP_DATA
so newly-created bins appear without a full reload. Without an open
project the + button is dimmed with a helpful tooltip — "Open a
project to create a bin".
This commit is contained in:
claude 2026-05-23 04:27:23 +00:00
parent 7170a9945c
commit 13906cd0fe

View file

@ -1,7 +1,45 @@
// screens-library.jsx // screens-library.jsx
function Library({ navigate, onOpenAsset, openProject }) { function Library({ navigate, onOpenAsset, openProject }) {
const { BINS, PROJECTS } = window.ZAMPP_DATA; const PROJECTS = window.ZAMPP_DATA?.PROJECTS || [];
const [bins, setBins] = React.useState(window.ZAMPP_DATA?.BINS || []);
const BINS = bins; // legacy local name; keep so the rest of the function reads unchanged
// Re-fetch bins on mount + whenever the open project changes; surfaces
// every-project bins when the global view is on, project-scoped otherwise.
React.useEffect(() => {
const qs = openProject ? '?project_id=' + openProject.id : '';
window.ZAMPP_API.fetch('/bins' + qs)
.then(list => {
const normalized = (list || []).map(b => ({
...b,
count: b.asset_count != null ? b.asset_count : (b.count || 0),
icon: b.type || 'grid',
}));
if (!openProject) window.ZAMPP_DATA.BINS = normalized;
setBins(normalized);
})
.catch(() => {});
}, [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;
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' }))));
})
.catch(e => window.alert('Could not create bin: ' + e.message));
};
const [view, setView] = React.useState('grid'); const [view, setView] = React.useState('grid');
const [filter, setFilter] = React.useState('all'); const [filter, setFilter] = React.useState('all');
const [search, setSearch] = React.useState(window._dfPendingSearch || ''); const [search, setSearch] = React.useState(window._dfPendingSearch || '');
@ -79,22 +117,31 @@ function Library({ navigate, onOpenAsset, openProject }) {
})} })}
</div> </div>
</div> </div>
{BINS.length > 0 && ( <div>
<div> <div style={{ display: 'flex', alignItems: 'center' }}>
<h4>Bins</h4> <h4 style={{ flex: 1, margin: 0 }}>Bins</h4>
<div className="rail-list"> <button className="icon-btn" onClick={createBin}
{BINS.map(function(b) { title={openProject ? 'Create bin in this project' : 'Open a project to create a bin'}
return ( style={{ opacity: openProject ? 1 : 0.5 }}>
<div key={b.id} className="rail-item"> <Icon name="plus" size={11} />
<Icon name={binIcon(b.icon)} size={13} className="rail-icon" /> </button>
<span>{b.name}</span>
<span className="rail-count">{b.count}</span>
</div>
);
})}
</div>
</div> </div>
)} <div className="rail-list">
{BINS.length === 0 ? (
<div style={{ fontSize: 11, color: 'var(--text-3)', padding: '6px 8px', fontStyle: 'italic' }}>
{openProject ? 'No bins yet — click + to create one.' : 'Open a project to manage bins.'}
</div>
) : BINS.map(function(b) {
return (
<div key={b.id} className="rail-item">
<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> <div>
<h4>Smart filters</h4> <h4>Smart filters</h4>
<div className="rail-list"> <div className="rail-list">