task(F4): library providers - builtin catalogs (event/architecture/landscape 15 blocks each), user-library and global-library via LibraryProviderExtension, panel UI with existing Drop-Channel
This commit is contained in:
@@ -0,0 +1,161 @@
|
||||
/**
|
||||
* 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}`);
|
||||
},
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user