task(F7): wcadlib as real zip package - jszip export-zip/import-zip endpoints with manifest/blocks/thumbs, frontend zip buttons

This commit is contained in:
Agent Zero
2026-08-29 02:25:10 +02:00
parent bdf3baf4bd
commit d311820351
7 changed files with 453 additions and 0 deletions
@@ -12,6 +12,8 @@ import {
setGlobalBlockFavorite,
exportLibrary,
importLibrary,
exportLibraryZip,
importLibraryZip,
} from '../services/api';
import { getActiveDocument } from '../kernel/document/documentService';
import { generateBlockSvg } from '../utils/blockThumbnail';
@@ -45,6 +47,7 @@ const BlockLibraryTree: React.FC<BlockLibraryTreeProps> = ({ token, onBlockDragS
const [loading, setLoading] = useState(true);
const fileInputRef = useRef<HTMLInputElement>(null);
const wcadInputRef = useRef<HTMLInputElement>(null);
const zipInputRef = useRef<HTMLInputElement>(null);
const [dragOverFolderId, setDragOverFolderId] = useState<string | null>(null);
// ─── Task F2: Suche, Favoriten, Thumbnail-Grid ───────────
@@ -113,6 +116,34 @@ const BlockLibraryTree: React.FC<BlockLibraryTreeProps> = ({ token, onBlockDragS
// ─── Task F1: Library-Paket Export/Import ────────────────
const handleZipExport = useCallback(async () => {
try {
const blob = await exportLibraryZip(token);
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `web-cad-library-${new Date().toISOString().slice(0, 10)}.wcadlib.zip`;
a.click();
URL.revokeObjectURL(url);
} catch (err) {
alert(`ZIP-Export fehlgeschlagen: ${err instanceof Error ? err.message : String(err)}`);
}
}, [token]);
const handleZipImport = useCallback(async (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0];
if (!file) return;
try {
const result = await importLibraryZip(token, file);
alert(`Bibliothek importiert: ${result.imported_blocks} Blöcke, ${result.imported_folders} Ordner`);
await loadData();
} catch (err) {
alert(`ZIP-Import fehlgeschlagen: ${err instanceof Error ? err.message : String(err)}`);
} finally {
e.target.value = '';
}
}, [token, loadData]);
const handleLibraryExport = useCallback(async () => {
try {
const pkg = await exportLibrary(token);
@@ -515,6 +546,20 @@ const BlockLibraryTree: React.FC<BlockLibraryTreeProps> = ({ token, onBlockDragS
>
<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>
</button>
<button
className="global-lib-add-btn"
title="Bibliothek als ZIP-Paket exportieren (Task F7)"
onClick={handleZipExport}
>
<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M21 8v13H3V8"/><rect x="1" y="3" width="22" height="5"/><path d="M10 12h4"/></svg>
</button>
<button
className="global-lib-add-btn"
title=".wcadlib-ZIP-Paket importieren (Task F7)"
onClick={() => zipInputRef.current?.click()}
>
<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M21 8v13H3V8"/><rect x="1" y="3" width="22" height="5"/><path d="M15 12l-3-3-3 3"/><path d="M12 9v8"/></svg>
</button>
<button
className="global-lib-add-btn"
title="SVG in globale Bibliothek importieren"
@@ -524,6 +569,13 @@ const BlockLibraryTree: React.FC<BlockLibraryTreeProps> = ({ token, onBlockDragS
</button>
<input ref={fileInputRef} type="file" accept=".svg,image/svg+xml" style={{ display: 'none' }} onChange={handleSvgImport} />
<input ref={wcadInputRef} type="file" accept=".wcadlib,application/json" style={{ display: 'none' }} onChange={handleWcadFile} />
<input
type="file"
accept=".zip,.wcadlib.zip,application/zip"
ref={zipInputRef}
style={{ display: 'none' }}
onChange={handleZipImport}
/>
</div>
<div className="global-lib-toolbar">
<input
+25
View File
@@ -854,3 +854,28 @@ export async function importLibrary(token: string, pkg: { format: string; versio
}
return res.json();
}
// ─── Task F7: .wcadlib als echtes ZIP-Paket ──────────────
/** Library als ZIP-Paket (manifest.json, blocks.json, thumbs/) herunterladen. */
export async function exportLibraryZip(token: string): Promise<Blob> {
const res = await fetch(`${API_BASE}/api/global-blocks/export-zip`, {
headers: authHeaders(token),
});
if (!res.ok) throw new Error(`Failed to export library zip: ${res.status}`);
return res.blob();
}
/** .wcadlib-ZIP einspielen (Ordner-Mapping, Blöcke, Thumbnails). */
export async function importLibraryZip(token: string, file: Blob): Promise<{ imported_blocks: number; imported_folders: number }> {
const res = await fetch(`${API_BASE}/api/global-blocks/import-zip`, {
method: 'POST',
headers: { ...authHeaders(token), 'content-type': 'application/zip' },
body: file,
});
if (!res.ok) {
const err = await res.text();
throw new Error(`Failed to import library zip: ${res.status} ${err}`);
}
return res.json();
}