feat: bulk ZIP download, bulk move-to-folder, folder rename
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNQ8ghPfzAfsyVYd6HgFb6
This commit is contained in:
@@ -47,6 +47,19 @@ export default function LibraryApp() {
|
||||
setPick(new Set()); load(); notify(`${r.delivered}/${r.total} nach Picdrop ausgeliefert.`);
|
||||
};
|
||||
const toggle = (id: string) => setPick((s) => { const n = new Set(s); n.has(id) ? n.delete(id) : n.add(id); return n; });
|
||||
const moveSelected = async (folder_id: string | null) => {
|
||||
for (const id of pick) await fetch(`/api/items/${id}`, { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ folder_id }) });
|
||||
setPick(new Set()); load(); notify(folder_id ? 'In Ordner verschoben.' : 'Aus Ordner entfernt.');
|
||||
};
|
||||
const zipSelected = async () => {
|
||||
notify('Packe ZIP …');
|
||||
const res = await fetch('/api/items/zip', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ itemIds: [...pick] }) });
|
||||
if (!res.ok) { notify('ZIP fehlgeschlagen.'); return; }
|
||||
const blob = await res.blob();
|
||||
const a = document.createElement('a'); a.href = URL.createObjectURL(blob);
|
||||
a.download = `klarbild_${new Date().toISOString().slice(0, 10)}.zip`; a.click();
|
||||
URL.revokeObjectURL(a.href); notify('ZIP geladen.');
|
||||
};
|
||||
const alternative = async (id: string) => {
|
||||
notify('Erzeuge Alternative …');
|
||||
const r = await fetch(`/api/items/${id}/alternative`, { method: 'POST' }).then((x) => x.json());
|
||||
@@ -68,6 +81,13 @@ export default function LibraryApp() {
|
||||
if (folderFilter === id) setFolderFilter(null);
|
||||
load();
|
||||
};
|
||||
const renameFolder = async (o: any) => {
|
||||
const name = prompt('Ordner umbenennen:', o.name); if (name == null) return;
|
||||
const gallery = prompt('Picdrop-Galerie (leer = Standard):', o.picdrop_gallery || '');
|
||||
await fetch('/api/folders', { method: 'PATCH', headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ id: o.id, name: name.trim() || o.name, picdrop_gallery: (gallery || '').trim() || null }) });
|
||||
load();
|
||||
};
|
||||
|
||||
// Nach Ordner filtern, dann Alternativen unter ihrem Ursprung gruppieren.
|
||||
const filtered = folderFilter ? items.filter((v) => v.folder_id === folderFilter) : items;
|
||||
@@ -89,6 +109,12 @@ export default function LibraryApp() {
|
||||
{pick.size > 0 && <>
|
||||
<span className="fein">{pick.size} gewählt</span>
|
||||
<button className="mini stark" onClick={deliverSelected}>Nach Picdrop</button>
|
||||
<button className="mini" onClick={zipSelected}>ZIP laden</button>
|
||||
<select className="mini-select" value="" onChange={(e) => { if (e.target.value) moveSelected(e.target.value === '__none' ? null : e.target.value); }}>
|
||||
<option value="" disabled>In Ordner …</option>
|
||||
{folders.map((o) => <option key={o.id} value={o.id}>{o.name}</option>)}
|
||||
<option value="__none">Kein Ordner</option>
|
||||
</select>
|
||||
<button className="mini" onClick={delSelected}>Löschen</button>
|
||||
</>}
|
||||
<button className="mini" onClick={newFolder}>Neuer Ordner</button>
|
||||
@@ -103,7 +129,10 @@ export default function LibraryApp() {
|
||||
<button className="chip" onClick={() => setFolderFilter(folderFilter === o.id ? null : o.id)}>
|
||||
{o.name} · {o.count}{o.picdrop_gallery ? ' →' : ''}
|
||||
</button>
|
||||
{folderFilter === o.id && <button className="chip-x" title="Ordner löschen" onClick={() => delFolder(o.id)}>✕</button>}
|
||||
{folderFilter === o.id && <>
|
||||
<button className="chip-x" title="Ordner umbenennen" onClick={() => renameFolder(o)}>✎</button>
|
||||
<button className="chip-x del" title="Ordner löschen" onClick={() => delFolder(o.id)}>✕</button>
|
||||
</>}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
@@ -189,7 +218,8 @@ function LibStyles() {
|
||||
.chip-wrap{display:inline-flex;align-items:center;border-radius:20px;}
|
||||
.chip{background:var(--card);border:1px solid var(--line);border-radius:20px;padding:6px 13px;cursor:pointer;font-family:var(--font-mono);font-size:11px;letter-spacing:.04em;color:var(--soft);white-space:nowrap;}
|
||||
.chip.an,.chip-wrap.an .chip{border-color:var(--accent);background:var(--accent-bg);color:var(--accent);}
|
||||
.chip-x{border:none;background:none;color:var(--err);cursor:pointer;font-size:12px;padding:0 8px 0 2px;}
|
||||
.chip-x{border:none;background:none;color:var(--accent);cursor:pointer;font-size:12px;padding:0 4px;}
|
||||
.chip-x.del{color:var(--err);padding:0 8px 0 2px;}
|
||||
.leer{padding:60px 24px;text-align:center;display:flex;flex-direction:column;align-items:center;gap:10px;}
|
||||
.leer h3{margin:0;font-size:17px;}.leer p{margin:0;color:var(--soft);max-width:38ch;}
|
||||
.galerie{display:grid;grid-template-columns:repeat(auto-fill,minmax(160px,1fr));gap:12px;align-items:start;}
|
||||
|
||||
Reference in New Issue
Block a user