Files
web-cad/frontend/src/plugins/builtin/library/providers.ts
T

162 lines
5.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 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<string, string>();
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<LibFolder[]> {
return CATALOGS.map((c) => ({ id: c.folderId, label: c.label, parentId: null }));
},
async listBlocks(folderId: string): Promise<LibBlock[]> {
const cat = CATALOGS.find((c) => c.folderId === folderId);
return cat ? cat.blocks.map((b) => toLibBlock(folderId, b)) : [];
},
async getBlock(id: string): Promise<LibBlock> {
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<LibFolder[]> {
return [{ id: folderId, label: 'Zeichnungs-Blöcke', parentId: null }];
},
async listBlocks(id: string): Promise<LibBlock[]> {
return id === folderId
? userBlocks.filter((b) => extractElements(b).length > 0).map(toBlock)
: [];
},
async getBlock(id: string): Promise<LibBlock> {
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<GlobalBlock[]> => {
try {
return await getGlobalBlocks(token);
} catch {
return [];
}
};
return {
id: 'global-library',
label: 'Globale Bibliothek',
async listFolders(): Promise<LibFolder[]> {
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<LibBlock[]> {
const blocks = await loadAll();
return blocks.filter((b) => (b.folder_id ?? 'global-root') === folderId).map(toBlock);
},
async getBlock(id: string): Promise<LibBlock> {
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}`);
},
};
}