diff --git a/services/web-ui/public/screens-library.jsx b/services/web-ui/public/screens-library.jsx index 17a4bc0..1b318d5 100644 --- a/services/web-ui/public/screens-library.jsx +++ b/services/web-ui/public/screens-library.jsx @@ -77,6 +77,9 @@ function Library({ navigate, onOpenAsset, openProject, onClearProject }) { // Rename project state const [renamingProject, setRenamingProject] = React.useState(null); const [projVersion, setProjVersion] = React.useState(0); + // Multi-select state + const [selectedAssets, setSelectedAssets] = React.useState(new Set()); + const [selectionMode, setSelectionMode] = React.useState(false); const refreshAssets = React.useCallback(() => { window.ZAMPP_API.refreshAssets() @@ -86,6 +89,66 @@ function Library({ navigate, onOpenAsset, openProject, onClearProject }) { .catch(() => {}); }, []); + const toggleSelection = function(assetId, e) { + if (e) e.stopPropagation(); + setSelectedAssets(function(prev) { + var next = new Set(prev); + if (next.has(assetId)) next.delete(assetId); + else next.add(assetId); + return next; + }); + }; + + const selectAll = function() { + setSelectedAssets(new Set(assets.map(function(a) { return a.id; }))); + }; + + const clearSelection = function() { + setSelectedAssets(new Set()); + setSelectionMode(false); + }; + + const bulkMoveToBin = async function(binId) { + var ids = Array.from(selectedAssets); + if (ids.length === 0) return; + var targetBin = bins.find(function(b) { return b.id === binId; }); + for (var i = 0; i < ids.length; i++) { + var asset = allAssets.find(function(a) { return a.id === ids[i]; }); + if (asset && targetBin && asset.project_id && targetBin.project_id && asset.project_id !== targetBin.project_id) { + alert('Cannot move assets to a bin in a different project.'); + return; + } + } + try { + await Promise.all(ids.map(function(id) { + return window.ZAMPP_API.fetch('/assets/' + id, { method: 'PATCH', body: JSON.stringify({ bin_id: binId }) }); + })); + refreshAssets(); + window.dispatchEvent(new Event('df:bins-changed')); + clearSelection(); + } catch (e) { + alert('Bulk move failed: ' + e.message); + } + }; + + const bulkDelete = async function() { + var ids = Array.from(selectedAssets); + if (ids.length === 0) return; + if (!(await confirm({ + title: 'Delete ' + ids.length + ' assets?', + message: 'Delete ' + ids.length + ' assets permanently?\nThis removes the database rows and S3 objects.\nThis cannot be undone.', + }))) return; + try { + await Promise.all(ids.map(function(id) { + return window.ZAMPP_API.fetch('/assets/' + id + '?hard=true', { method: 'DELETE' }); + })); + refreshAssets(); + clearSelection(); + } catch (e) { + alert('Bulk delete failed: ' + e.message); + } + }; + const deleteAsset = React.useCallback(async (asset) => { if (!(await confirm({ title: 'Delete asset?', @@ -322,6 +385,29 @@ function Library({ navigate, onOpenAsset, openProject, onClearProject }) {