/** * Task F4 – Bibliotheksquellen als LibraryProviderExtension. * * Drei Quellen: * - builtinCatalogProvider: statische JSON-Startersets (event/architecture/landscape) * - makeUserLibraryProvider: zeichnungsscoped Blockdefinitionen (Drawing-Blöcke) * - makeGlobalLibraryProvider: persistente globale Blockbibliothek (global-blocks API) * * Payload-Konvention: String-Payload = JSON-Array von CADElementen — identisch zum * 'text/global-block-data' Drop-Kanal von BlockLibraryTree (F2/F3), damit der * Canvas-Drop-Pfad (CanvasArea) ohne Änderung funktioniert. */ import catalogEvent from './catalogs/event.json'; import catalogArch from './catalogs/architecture.json'; import catalogLand from './catalogs/landscape.json'; import { generateBlockSvg } from '../../../utils/blockThumbnail'; import type { CADElement } from '../../../types/cad.types'; import type { LibraryProviderExtension, LibBlock, LibFolder } from '../../types'; import { getGlobalFolders, getGlobalBlocks, type GlobalBlock } from '../../../services/api'; interface CatalogBlock { id: string; name: string; elements: CADElement[]; } interface CatalogFile { folderId: string; label: string; blocks: CatalogBlock[]; } const CATALOGS: CatalogFile[] = [catalogEvent, catalogArch, catalogLand].map( (c) => c as unknown as CatalogFile, ); const thumbCache = new Map(); function toLibBlock(folderId: string, block: CatalogBlock): LibBlock { let thumbnail = thumbCache.get(block.id); if (!thumbnail) { thumbnail = generateBlockSvg(block.elements); thumbCache.set(block.id, thumbnail); } return { id: block.id, folderId, name: block.name, thumbnail, payload: JSON.stringify(block.elements), }; } /** Statischer builtin-Katalog: Event / Architektur / Landschaft Startersets. */ export const builtinCatalogProvider: LibraryProviderExtension = { id: 'builtin-catalog', label: 'Builtin-Kataloge', async listFolders(): Promise { return CATALOGS.map((c) => ({ id: c.folderId, label: c.label, parentId: null })); }, async listBlocks(folderId: string): Promise { const cat = CATALOGS.find((c) => c.folderId === folderId); return cat ? cat.blocks.map((b) => toLibBlock(folderId, b)) : []; }, async getBlock(id: string): Promise { for (const cat of CATALOGS) { const found = cat.blocks.find((b) => b.id === id); if (found) return toLibBlock(cat.folderId, found); } throw new Error(`Block not found: ${id}`); }, }; export interface UserBlockLike { id: string; name: string; elements?: unknown; elements_json?: string; thumbnail?: string | null; drawing_id?: string; description?: string | null; category?: string; } function extractElements(block: UserBlockLike): CADElement[] { try { if (Array.isArray(block.elements)) return block.elements as CADElement[]; if (typeof block.elements_json === 'string') { const parsed: unknown = JSON.parse(block.elements_json); if (Array.isArray(parsed)) return parsed as CADElement[]; const wrapped = parsed as { elements?: unknown } | null; if (wrapped && Array.isArray(wrapped.elements)) return wrapped.elements as CADElement[]; } } catch { // invalider Inhalt → leerer Block wird beim Auflisten gefiltert } return []; } /** Zeichnungsscoped Blöcke (Drawing-Blöcke) als Bibliothek anbinden. */ export function makeUserLibraryProvider(userBlocks: readonly UserBlockLike[]): LibraryProviderExtension { const folderId = 'user-blocks'; const toBlock = (block: UserBlockLike): LibBlock => { const elements = extractElements(block); return { id: `user:${block.id}`, folderId, name: block.name, thumbnail: elements.length ? generateBlockSvg(elements) : undefined, payload: elements.length ? JSON.stringify(elements) : undefined, }; }; return { id: 'user-library', label: 'Benutzer-Bibliothek', async listFolders(): Promise { return [{ id: folderId, label: 'Zeichnungs-Blöcke', parentId: null }]; }, async listBlocks(id: string): Promise { return id === folderId ? userBlocks.filter((b) => extractElements(b).length > 0).map(toBlock) : []; }, async getBlock(id: string): Promise { const found = userBlocks.find((b) => `user:${b.id}` === id && extractElements(b).length > 0); if (found) return toBlock(found); throw new Error(`Block not found: ${id}`); }, }; } /** Persistente globale Blockbibliothek über die global-blocks API anbinden. */ export function makeGlobalLibraryProvider(token: string): LibraryProviderExtension { const toBlock = (block: GlobalBlock): LibBlock => ({ id: `global:${block.id}`, folderId: block.folder_id ?? 'global-root', name: block.name, thumbnail: block.svg_data ?? undefined, payload: block.block_data, }); const loadAll = async (): Promise => { try { return await getGlobalBlocks(token); } catch { return []; } }; return { id: 'global-library', label: 'Globale Bibliothek', async listFolders(): Promise { try { const folders = await getGlobalFolders(token); return folders.map((f) => ({ id: f.id, label: f.name, parentId: f.parent_id })); } catch { return []; } }, async listBlocks(folderId: string): Promise { const blocks = await loadAll(); return blocks.filter((b) => (b.folder_id ?? 'global-root') === folderId).map(toBlock); }, async getBlock(id: string): Promise { const blocks = await loadAll(); const found = blocks.find((b) => `global:${b.id}` === id); if (found) return toBlock(found); throw new Error(`Block not found: ${id}`); }, }; }