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,84 @@
|
|||||||
|
/**
|
||||||
|
* Task F4 – UI für LibraryProviderExtensions.
|
||||||
|
*
|
||||||
|
* Lädt alle übergebenen Provider (builtin/user/global) und rendert je Ordner
|
||||||
|
* eine Sektion mit Thumbnail-Blöcken. Jeder Block ist als DragSource an den
|
||||||
|
* bestehenden 'text/global-block-data'-Kanal angebunden (DragDrop → Canvas).
|
||||||
|
*/
|
||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import type { LibraryProviderExtension, LibBlock } from '../plugins/types';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
providers: LibraryProviderExtension[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Section { key: string; label: string; blocks: LibBlock[]; }
|
||||||
|
|
||||||
|
const LibraryProviderPanel: React.FC<Props> = ({ providers }) => {
|
||||||
|
const [sections, setSections] = useState<Section[]>([]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let cancelled = false;
|
||||||
|
(async () => {
|
||||||
|
const out: Section[] = [];
|
||||||
|
for (const provider of providers) {
|
||||||
|
try {
|
||||||
|
const folders = await provider.listFolders();
|
||||||
|
for (const folder of folders) {
|
||||||
|
try {
|
||||||
|
const blocks = await provider.listBlocks(folder.id);
|
||||||
|
if (blocks.length > 0) {
|
||||||
|
out.push({ key: `${provider.id}/${folder.id}`, label: folder.label, blocks });
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// Ordner bei Fehler überspringen
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// Provider bei Fehler überspringen
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!cancelled) setSections(out);
|
||||||
|
})();
|
||||||
|
return () => {
|
||||||
|
cancelled = true;
|
||||||
|
};
|
||||||
|
}, [providers]);
|
||||||
|
|
||||||
|
const handleDragStart = (e: React.DragEvent, block: LibBlock) => {
|
||||||
|
const payload = typeof block.payload === 'string' ? block.payload : JSON.stringify(block.payload ?? []);
|
||||||
|
e.dataTransfer.setData('text/global-block-data', payload);
|
||||||
|
e.dataTransfer.effectAllowed = 'copy';
|
||||||
|
};
|
||||||
|
|
||||||
|
if (sections.length === 0) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="lib-provider-panel">
|
||||||
|
{sections.map((section) => (
|
||||||
|
<div className="lprov-section" key={section.key}>
|
||||||
|
<div className="lprov-title">{section.label}</div>
|
||||||
|
<div className="lprov-grid">
|
||||||
|
{section.blocks.map((block) => (
|
||||||
|
<button
|
||||||
|
key={block.id}
|
||||||
|
type="button"
|
||||||
|
className="lprov-block"
|
||||||
|
aria-label={block.name}
|
||||||
|
title={block.name}
|
||||||
|
draggable
|
||||||
|
onDragStart={(e) => handleDragStart(e, block)}
|
||||||
|
>
|
||||||
|
{block.thumbnail
|
||||||
|
? <span className="tree-thumb" dangerouslySetInnerHTML={{ __html: block.thumbnail }} />
|
||||||
|
: <span className="lib-grid-placeholder">📦</span>}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default LibraryProviderPanel;
|
||||||
@@ -1,9 +1,14 @@
|
|||||||
import React, { useCallback } from 'react';
|
import React, { useCallback } from 'react';
|
||||||
import type { RightSidebarProps, RightPanel } from '../types/ui.types';
|
import type { RightSidebarProps, RightPanel } from '../types/ui.types';
|
||||||
|
import type { LibraryProviderExtension } from '../plugins/types';
|
||||||
import PropertiesPanel from './PropertiesPanel';
|
import PropertiesPanel from './PropertiesPanel';
|
||||||
import LayerPanel from './LayerPanel';
|
import LayerPanel from './LayerPanel';
|
||||||
import BlockLibrary from './BlockLibrary';
|
import BlockLibrary from './BlockLibrary';
|
||||||
import BlockLibraryTree from './BlockLibraryTree';
|
import BlockLibraryTree from './BlockLibraryTree';
|
||||||
|
import LibraryProviderPanel from './LibraryProviderPanel';
|
||||||
|
import { pluginRegistry, builtinCatalogProvider } from '../plugins';
|
||||||
|
import { makeUserLibraryProvider, makeGlobalLibraryProvider } from '../plugins/builtin/library/providers';
|
||||||
|
import { useMemo } from 'react';
|
||||||
import KICopilot from './KICopilot';
|
import KICopilot from './KICopilot';
|
||||||
|
|
||||||
const MIN_RIGHTBAR_WIDTH = 200;
|
const MIN_RIGHTBAR_WIDTH = 200;
|
||||||
@@ -39,6 +44,23 @@ const RightSidebar: React.FC<RightSidebarProps> = ({
|
|||||||
rightbarWidth = 300,
|
rightbarWidth = 300,
|
||||||
onWidthChange,
|
onWidthChange,
|
||||||
}) => {
|
}) => {
|
||||||
|
const libraryProviders = useMemo(() => {
|
||||||
|
const list: LibraryProviderExtension[] = [];
|
||||||
|
const seen = new Set<string>();
|
||||||
|
const push = (p: LibraryProviderExtension) => {
|
||||||
|
if (p && !seen.has(p.id)) {
|
||||||
|
seen.add(p.id);
|
||||||
|
list.push(p);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
for (const p of pluginRegistry.getLibraryProviders()) push(p);
|
||||||
|
push(builtinCatalogProvider);
|
||||||
|
if (blocks) push(makeUserLibraryProvider(blocks));
|
||||||
|
if (token) push(makeGlobalLibraryProvider(token));
|
||||||
|
return list;
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [blocks, token]);
|
||||||
|
|
||||||
const handleResizeMouseDown = useCallback((e: React.MouseEvent) => {
|
const handleResizeMouseDown = useCallback((e: React.MouseEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
@@ -138,6 +160,7 @@ const RightSidebar: React.FC<RightSidebarProps> = ({
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
<LibraryProviderPanel providers={libraryProviders} />
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -0,0 +1,587 @@
|
|||||||
|
{
|
||||||
|
"folderId": "cat-arch",
|
||||||
|
"label": "Architektur-Katalog",
|
||||||
|
"blocks": [
|
||||||
|
{
|
||||||
|
"id": "arch-1",
|
||||||
|
"name": "Tür 900",
|
||||||
|
"elements": [
|
||||||
|
{
|
||||||
|
"id": "a1a",
|
||||||
|
"type": "line",
|
||||||
|
"layerId": "layer-0",
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"width": 0,
|
||||||
|
"height": 0,
|
||||||
|
"properties": {
|
||||||
|
"x1": 0,
|
||||||
|
"y1": 0,
|
||||||
|
"x2": 900,
|
||||||
|
"y2": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "a1b",
|
||||||
|
"type": "arc",
|
||||||
|
"layerId": "layer-0",
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"width": 1800,
|
||||||
|
"height": 1800,
|
||||||
|
"properties": {
|
||||||
|
"radius": 900,
|
||||||
|
"startAngle": 0,
|
||||||
|
"endAngle": 90
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "arch-2",
|
||||||
|
"name": "Doppel-Tür 1800",
|
||||||
|
"elements": [
|
||||||
|
{
|
||||||
|
"id": "a2a",
|
||||||
|
"type": "line",
|
||||||
|
"layerId": "layer-0",
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"width": 0,
|
||||||
|
"height": 0,
|
||||||
|
"properties": {
|
||||||
|
"x1": -900,
|
||||||
|
"y1": 0,
|
||||||
|
"x2": -450,
|
||||||
|
"y2": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "a2b",
|
||||||
|
"type": "arc",
|
||||||
|
"layerId": "layer-0",
|
||||||
|
"x": -900,
|
||||||
|
"y": 0,
|
||||||
|
"width": 900,
|
||||||
|
"height": 900,
|
||||||
|
"properties": {
|
||||||
|
"radius": 450,
|
||||||
|
"startAngle": 0,
|
||||||
|
"endAngle": 90
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "a2c",
|
||||||
|
"type": "line",
|
||||||
|
"layerId": "layer-0",
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"width": 0,
|
||||||
|
"height": 0,
|
||||||
|
"properties": {
|
||||||
|
"x1": 450,
|
||||||
|
"y1": 0,
|
||||||
|
"x2": 900,
|
||||||
|
"y2": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "a2d",
|
||||||
|
"type": "arc",
|
||||||
|
"layerId": "layer-0",
|
||||||
|
"x": 900,
|
||||||
|
"y": 0,
|
||||||
|
"width": 900,
|
||||||
|
"height": 900,
|
||||||
|
"properties": {
|
||||||
|
"radius": 450,
|
||||||
|
"startAngle": 90,
|
||||||
|
"endAngle": 180
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "arch-3",
|
||||||
|
"name": "Tür 1200",
|
||||||
|
"elements": [
|
||||||
|
{
|
||||||
|
"id": "a3a",
|
||||||
|
"type": "line",
|
||||||
|
"layerId": "layer-0",
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"width": 0,
|
||||||
|
"height": 0,
|
||||||
|
"properties": {
|
||||||
|
"x1": 0,
|
||||||
|
"y1": 0,
|
||||||
|
"x2": 1200,
|
||||||
|
"y2": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "a3b",
|
||||||
|
"type": "arc",
|
||||||
|
"layerId": "layer-0",
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"width": 2400,
|
||||||
|
"height": 2400,
|
||||||
|
"properties": {
|
||||||
|
"radius": 1200,
|
||||||
|
"startAngle": 0,
|
||||||
|
"endAngle": 90
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "arch-4",
|
||||||
|
"name": "Fenster 1200",
|
||||||
|
"elements": [
|
||||||
|
{
|
||||||
|
"id": "a4a",
|
||||||
|
"type": "rect",
|
||||||
|
"layerId": "layer-0",
|
||||||
|
"x": 0,
|
||||||
|
"y": -30,
|
||||||
|
"width": 1200,
|
||||||
|
"height": 20,
|
||||||
|
"properties": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "a4b",
|
||||||
|
"type": "rect",
|
||||||
|
"layerId": "layer-0",
|
||||||
|
"x": 0,
|
||||||
|
"y": 30,
|
||||||
|
"width": 1200,
|
||||||
|
"height": 20,
|
||||||
|
"properties": {}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "arch-5",
|
||||||
|
"name": "Fenster 2400",
|
||||||
|
"elements": [
|
||||||
|
{
|
||||||
|
"id": "a5a",
|
||||||
|
"type": "rect",
|
||||||
|
"layerId": "layer-0",
|
||||||
|
"x": 0,
|
||||||
|
"y": -30,
|
||||||
|
"width": 2400,
|
||||||
|
"height": 20,
|
||||||
|
"properties": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "a5b",
|
||||||
|
"type": "rect",
|
||||||
|
"layerId": "layer-0",
|
||||||
|
"x": 0,
|
||||||
|
"y": 30,
|
||||||
|
"width": 2400,
|
||||||
|
"height": 20,
|
||||||
|
"properties": {}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "arch-6",
|
||||||
|
"name": "Wandstück 2m",
|
||||||
|
"elements": [
|
||||||
|
{
|
||||||
|
"id": "a6a",
|
||||||
|
"type": "rect",
|
||||||
|
"layerId": "layer-0",
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"width": 2000,
|
||||||
|
"height": 240,
|
||||||
|
"properties": {
|
||||||
|
"fill": "#8a8f98"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "arch-7",
|
||||||
|
"name": "Wandstück vertikal 2m",
|
||||||
|
"elements": [
|
||||||
|
{
|
||||||
|
"id": "a7a",
|
||||||
|
"type": "rect",
|
||||||
|
"layerId": "layer-0",
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"width": 240,
|
||||||
|
"height": 2000,
|
||||||
|
"properties": {
|
||||||
|
"fill": "#8a8f98"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "arch-8",
|
||||||
|
"name": "Treppe (8 Stufen)",
|
||||||
|
"elements": [
|
||||||
|
{
|
||||||
|
"id": "a8s0",
|
||||||
|
"type": "line",
|
||||||
|
"layerId": "layer-0",
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"width": 0,
|
||||||
|
"height": 0,
|
||||||
|
"properties": {
|
||||||
|
"x1": -350,
|
||||||
|
"y1": 0,
|
||||||
|
"x2": -350,
|
||||||
|
"y2": 1200
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "a8s1",
|
||||||
|
"type": "line",
|
||||||
|
"layerId": "layer-0",
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"width": 0,
|
||||||
|
"height": 0,
|
||||||
|
"properties": {
|
||||||
|
"x1": -250,
|
||||||
|
"y1": 0,
|
||||||
|
"x2": -250,
|
||||||
|
"y2": 1200
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "a8s2",
|
||||||
|
"type": "line",
|
||||||
|
"layerId": "layer-0",
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"width": 0,
|
||||||
|
"height": 0,
|
||||||
|
"properties": {
|
||||||
|
"x1": -150,
|
||||||
|
"y1": 0,
|
||||||
|
"x2": -150,
|
||||||
|
"y2": 1200
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "a8s3",
|
||||||
|
"type": "line",
|
||||||
|
"layerId": "layer-0",
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"width": 0,
|
||||||
|
"height": 0,
|
||||||
|
"properties": {
|
||||||
|
"x1": -50,
|
||||||
|
"y1": 0,
|
||||||
|
"x2": -50,
|
||||||
|
"y2": 1200
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "a8s4",
|
||||||
|
"type": "line",
|
||||||
|
"layerId": "layer-0",
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"width": 0,
|
||||||
|
"height": 0,
|
||||||
|
"properties": {
|
||||||
|
"x1": 50,
|
||||||
|
"y1": 0,
|
||||||
|
"x2": 50,
|
||||||
|
"y2": 1200
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "a8s5",
|
||||||
|
"type": "line",
|
||||||
|
"layerId": "layer-0",
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"width": 0,
|
||||||
|
"height": 0,
|
||||||
|
"properties": {
|
||||||
|
"x1": 150,
|
||||||
|
"y1": 0,
|
||||||
|
"x2": 150,
|
||||||
|
"y2": 1200
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "a8s6",
|
||||||
|
"type": "line",
|
||||||
|
"layerId": "layer-0",
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"width": 0,
|
||||||
|
"height": 0,
|
||||||
|
"properties": {
|
||||||
|
"x1": 250,
|
||||||
|
"y1": 0,
|
||||||
|
"x2": 250,
|
||||||
|
"y2": 1200
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "a8s7",
|
||||||
|
"type": "line",
|
||||||
|
"layerId": "layer-0",
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"width": 0,
|
||||||
|
"height": 0,
|
||||||
|
"properties": {
|
||||||
|
"x1": 350,
|
||||||
|
"y1": 0,
|
||||||
|
"x2": 350,
|
||||||
|
"y2": 1200
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "a8la",
|
||||||
|
"type": "line",
|
||||||
|
"layerId": "layer-0",
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"width": 0,
|
||||||
|
"height": 0,
|
||||||
|
"properties": {
|
||||||
|
"x1": -450,
|
||||||
|
"y1": 0,
|
||||||
|
"x2": -450,
|
||||||
|
"y2": 1200
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "a8lb",
|
||||||
|
"type": "line",
|
||||||
|
"layerId": "layer-0",
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"width": 0,
|
||||||
|
"height": 0,
|
||||||
|
"properties": {
|
||||||
|
"x1": 450,
|
||||||
|
"y1": 0,
|
||||||
|
"x2": 450,
|
||||||
|
"y2": 1200
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "arch-9",
|
||||||
|
"name": "Säule Ø50",
|
||||||
|
"elements": [
|
||||||
|
{
|
||||||
|
"id": "a9a",
|
||||||
|
"type": "circle",
|
||||||
|
"layerId": "layer-0",
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"width": 500,
|
||||||
|
"height": 500,
|
||||||
|
"properties": {
|
||||||
|
"radius": 250,
|
||||||
|
"fill": "#9aa0a6"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "arch-10",
|
||||||
|
"name": "Schreibtisch",
|
||||||
|
"elements": [
|
||||||
|
{
|
||||||
|
"id": "a10a",
|
||||||
|
"type": "rect",
|
||||||
|
"layerId": "layer-0",
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"width": 1800,
|
||||||
|
"height": 800,
|
||||||
|
"properties": {
|
||||||
|
"fill": "#6b7280"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "arch-11",
|
||||||
|
"name": "Bürostuhl",
|
||||||
|
"elements": [
|
||||||
|
{
|
||||||
|
"id": "a11a",
|
||||||
|
"type": "chair",
|
||||||
|
"layerId": "layer-0",
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"width": 60,
|
||||||
|
"height": 60,
|
||||||
|
"properties": {
|
||||||
|
"fill": "#5a7d9a",
|
||||||
|
"outlineColor": "#2a5a99"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "arch-12",
|
||||||
|
"name": "Sofa 3-Sitzer",
|
||||||
|
"elements": [
|
||||||
|
{
|
||||||
|
"id": "a12a",
|
||||||
|
"type": "rect",
|
||||||
|
"layerId": "layer-0",
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"width": 2200,
|
||||||
|
"height": 900,
|
||||||
|
"properties": {
|
||||||
|
"fill": "#5a7d9a"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "arch-13",
|
||||||
|
"name": "Einzelbett",
|
||||||
|
"elements": [
|
||||||
|
{
|
||||||
|
"id": "a13a",
|
||||||
|
"type": "rect",
|
||||||
|
"layerId": "layer-0",
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"width": 900,
|
||||||
|
"height": 2000,
|
||||||
|
"properties": {
|
||||||
|
"fill": "#8fa3bf"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "a13b",
|
||||||
|
"type": "rect",
|
||||||
|
"layerId": "layer-0",
|
||||||
|
"x": 0,
|
||||||
|
"y": -750,
|
||||||
|
"width": 700,
|
||||||
|
"height": 300,
|
||||||
|
"properties": {
|
||||||
|
"fill": "#d5dbdb"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "arch-14",
|
||||||
|
"name": "Waschbecken",
|
||||||
|
"elements": [
|
||||||
|
{
|
||||||
|
"id": "a14a",
|
||||||
|
"type": "rect",
|
||||||
|
"layerId": "layer-0",
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"width": 600,
|
||||||
|
"height": 450,
|
||||||
|
"properties": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "a14b",
|
||||||
|
"type": "circle",
|
||||||
|
"layerId": "layer-0",
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"width": 340,
|
||||||
|
"height": 340,
|
||||||
|
"properties": {
|
||||||
|
"radius": 170,
|
||||||
|
"fill": "#7f9db9"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "arch-15",
|
||||||
|
"name": "Küchenzeile 2.4m",
|
||||||
|
"elements": [
|
||||||
|
{
|
||||||
|
"id": "a15a",
|
||||||
|
"type": "rect",
|
||||||
|
"layerId": "layer-0",
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"width": 2400,
|
||||||
|
"height": 600,
|
||||||
|
"properties": {
|
||||||
|
"fill": "#3c4043"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "a15p0",
|
||||||
|
"type": "circle",
|
||||||
|
"layerId": "layer-0",
|
||||||
|
"x": -1800,
|
||||||
|
"y": 0,
|
||||||
|
"width": 180,
|
||||||
|
"height": 180,
|
||||||
|
"properties": {
|
||||||
|
"radius": 90,
|
||||||
|
"fill": "#202124"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "a15p1",
|
||||||
|
"type": "circle",
|
||||||
|
"layerId": "layer-0",
|
||||||
|
"x": -1320,
|
||||||
|
"y": 0,
|
||||||
|
"width": 180,
|
||||||
|
"height": 180,
|
||||||
|
"properties": {
|
||||||
|
"radius": 90,
|
||||||
|
"fill": "#202124"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "a15p2",
|
||||||
|
"type": "circle",
|
||||||
|
"layerId": "layer-0",
|
||||||
|
"x": -840,
|
||||||
|
"y": 0,
|
||||||
|
"width": 180,
|
||||||
|
"height": 180,
|
||||||
|
"properties": {
|
||||||
|
"radius": 90,
|
||||||
|
"fill": "#202124"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "a15p3",
|
||||||
|
"type": "circle",
|
||||||
|
"layerId": "layer-0",
|
||||||
|
"x": -360,
|
||||||
|
"y": 0,
|
||||||
|
"width": 180,
|
||||||
|
"height": 180,
|
||||||
|
"properties": {
|
||||||
|
"radius": 90,
|
||||||
|
"fill": "#202124"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,619 @@
|
|||||||
|
{
|
||||||
|
"folderId": "cat-event",
|
||||||
|
"label": "Event-Katalog",
|
||||||
|
"blocks": [
|
||||||
|
{
|
||||||
|
"id": "evt-1",
|
||||||
|
"name": "Stuhl",
|
||||||
|
"elements": [
|
||||||
|
{
|
||||||
|
"id": "e1",
|
||||||
|
"type": "chair",
|
||||||
|
"layerId": "layer-0",
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"width": 45,
|
||||||
|
"height": 45,
|
||||||
|
"properties": {
|
||||||
|
"fill": "#4a90d9",
|
||||||
|
"outlineColor": "#2a5a99"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "evt-2",
|
||||||
|
"name": "Tisch rund 180cm",
|
||||||
|
"elements": [
|
||||||
|
{
|
||||||
|
"id": "e2a",
|
||||||
|
"type": "table",
|
||||||
|
"layerId": "layer-0",
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"width": 1800,
|
||||||
|
"height": 1800,
|
||||||
|
"properties": {
|
||||||
|
"fill": "#8b7355"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "evt-3",
|
||||||
|
"name": "Tisch rund 120cm",
|
||||||
|
"elements": [
|
||||||
|
{
|
||||||
|
"id": "e3a",
|
||||||
|
"type": "table",
|
||||||
|
"layerId": "layer-0",
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"width": 1200,
|
||||||
|
"height": 1200,
|
||||||
|
"properties": {
|
||||||
|
"fill": "#8b7355"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "evt-4",
|
||||||
|
"name": "Stehtisch 80cm",
|
||||||
|
"elements": [
|
||||||
|
{
|
||||||
|
"id": "e4a",
|
||||||
|
"type": "table",
|
||||||
|
"layerId": "layer-0",
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"width": 800,
|
||||||
|
"height": 800,
|
||||||
|
"properties": {
|
||||||
|
"fill": "#a08f6a"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "evt-5",
|
||||||
|
"name": "Bühne 3x2m",
|
||||||
|
"elements": [
|
||||||
|
{
|
||||||
|
"id": "e5a",
|
||||||
|
"type": "stage",
|
||||||
|
"layerId": "layer-0",
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"width": 3000,
|
||||||
|
"height": 2000,
|
||||||
|
"properties": {}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "evt-6",
|
||||||
|
"name": "Bühne 4x3m",
|
||||||
|
"elements": [
|
||||||
|
{
|
||||||
|
"id": "e6a",
|
||||||
|
"type": "stage",
|
||||||
|
"layerId": "layer-0",
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"width": 4000,
|
||||||
|
"height": 3000,
|
||||||
|
"properties": {}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "evt-7",
|
||||||
|
"name": "Podest 1x1m",
|
||||||
|
"elements": [
|
||||||
|
{
|
||||||
|
"id": "e7a",
|
||||||
|
"type": "stage",
|
||||||
|
"layerId": "layer-0",
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"width": 1000,
|
||||||
|
"height": 1000,
|
||||||
|
"properties": {}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "evt-8",
|
||||||
|
"name": "Sitzreihe (10 Stühle)",
|
||||||
|
"elements": [
|
||||||
|
{
|
||||||
|
"id": "e2",
|
||||||
|
"type": "chair",
|
||||||
|
"layerId": "layer-0",
|
||||||
|
"x": -405,
|
||||||
|
"y": 0,
|
||||||
|
"width": 45,
|
||||||
|
"height": 45,
|
||||||
|
"properties": {
|
||||||
|
"fill": "#4a90d9",
|
||||||
|
"outlineColor": "#2a5a99"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "e3",
|
||||||
|
"type": "chair",
|
||||||
|
"layerId": "layer-0",
|
||||||
|
"x": -315,
|
||||||
|
"y": 0,
|
||||||
|
"width": 45,
|
||||||
|
"height": 45,
|
||||||
|
"properties": {
|
||||||
|
"fill": "#4a90d9",
|
||||||
|
"outlineColor": "#2a5a99"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "e4",
|
||||||
|
"type": "chair",
|
||||||
|
"layerId": "layer-0",
|
||||||
|
"x": -225,
|
||||||
|
"y": 0,
|
||||||
|
"width": 45,
|
||||||
|
"height": 45,
|
||||||
|
"properties": {
|
||||||
|
"fill": "#4a90d9",
|
||||||
|
"outlineColor": "#2a5a99"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "e5",
|
||||||
|
"type": "chair",
|
||||||
|
"layerId": "layer-0",
|
||||||
|
"x": -135,
|
||||||
|
"y": 0,
|
||||||
|
"width": 45,
|
||||||
|
"height": 45,
|
||||||
|
"properties": {
|
||||||
|
"fill": "#4a90d9",
|
||||||
|
"outlineColor": "#2a5a99"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "e6",
|
||||||
|
"type": "chair",
|
||||||
|
"layerId": "layer-0",
|
||||||
|
"x": -45,
|
||||||
|
"y": 0,
|
||||||
|
"width": 45,
|
||||||
|
"height": 45,
|
||||||
|
"properties": {
|
||||||
|
"fill": "#4a90d9",
|
||||||
|
"outlineColor": "#2a5a99"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "e7",
|
||||||
|
"type": "chair",
|
||||||
|
"layerId": "layer-0",
|
||||||
|
"x": 45,
|
||||||
|
"y": 0,
|
||||||
|
"width": 45,
|
||||||
|
"height": 45,
|
||||||
|
"properties": {
|
||||||
|
"fill": "#4a90d9",
|
||||||
|
"outlineColor": "#2a5a99"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "e8",
|
||||||
|
"type": "chair",
|
||||||
|
"layerId": "layer-0",
|
||||||
|
"x": 135,
|
||||||
|
"y": 0,
|
||||||
|
"width": 45,
|
||||||
|
"height": 45,
|
||||||
|
"properties": {
|
||||||
|
"fill": "#4a90d9",
|
||||||
|
"outlineColor": "#2a5a99"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "e9",
|
||||||
|
"type": "chair",
|
||||||
|
"layerId": "layer-0",
|
||||||
|
"x": 225,
|
||||||
|
"y": 0,
|
||||||
|
"width": 45,
|
||||||
|
"height": 45,
|
||||||
|
"properties": {
|
||||||
|
"fill": "#4a90d9",
|
||||||
|
"outlineColor": "#2a5a99"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "e10",
|
||||||
|
"type": "chair",
|
||||||
|
"layerId": "layer-0",
|
||||||
|
"x": 315,
|
||||||
|
"y": 0,
|
||||||
|
"width": 45,
|
||||||
|
"height": 45,
|
||||||
|
"properties": {
|
||||||
|
"fill": "#4a90d9",
|
||||||
|
"outlineColor": "#2a5a99"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "e11",
|
||||||
|
"type": "chair",
|
||||||
|
"layerId": "layer-0",
|
||||||
|
"x": 405,
|
||||||
|
"y": 0,
|
||||||
|
"width": 45,
|
||||||
|
"height": 45,
|
||||||
|
"properties": {
|
||||||
|
"fill": "#4a90d9",
|
||||||
|
"outlineColor": "#2a5a99"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "evt-9",
|
||||||
|
"name": "Bankett-Rundtisch (8 Stühle)",
|
||||||
|
"elements": [
|
||||||
|
{
|
||||||
|
"id": "e9a",
|
||||||
|
"type": "circle",
|
||||||
|
"layerId": "layer-0",
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"width": 1800,
|
||||||
|
"height": 1800,
|
||||||
|
"properties": {
|
||||||
|
"radius": 900,
|
||||||
|
"fill": "#8b7355"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "e12",
|
||||||
|
"type": "chair",
|
||||||
|
"layerId": "layer-0",
|
||||||
|
"x": 1200,
|
||||||
|
"y": 0,
|
||||||
|
"width": 45,
|
||||||
|
"height": 45,
|
||||||
|
"properties": {
|
||||||
|
"fill": "#4a90d9",
|
||||||
|
"outlineColor": "#2a5a99"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "e13",
|
||||||
|
"type": "chair",
|
||||||
|
"layerId": "layer-0",
|
||||||
|
"x": 848,
|
||||||
|
"y": 848,
|
||||||
|
"width": 45,
|
||||||
|
"height": 45,
|
||||||
|
"properties": {
|
||||||
|
"fill": "#4a90d9",
|
||||||
|
"outlineColor": "#2a5a99"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "e14",
|
||||||
|
"type": "chair",
|
||||||
|
"layerId": "layer-0",
|
||||||
|
"x": 0,
|
||||||
|
"y": 1200,
|
||||||
|
"width": 45,
|
||||||
|
"height": 45,
|
||||||
|
"properties": {
|
||||||
|
"fill": "#4a90d9",
|
||||||
|
"outlineColor": "#2a5a99"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "e15",
|
||||||
|
"type": "chair",
|
||||||
|
"layerId": "layer-0",
|
||||||
|
"x": -848,
|
||||||
|
"y": 848,
|
||||||
|
"width": 45,
|
||||||
|
"height": 45,
|
||||||
|
"properties": {
|
||||||
|
"fill": "#4a90d9",
|
||||||
|
"outlineColor": "#2a5a99"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "e16",
|
||||||
|
"type": "chair",
|
||||||
|
"layerId": "layer-0",
|
||||||
|
"x": -1200,
|
||||||
|
"y": 0,
|
||||||
|
"width": 45,
|
||||||
|
"height": 45,
|
||||||
|
"properties": {
|
||||||
|
"fill": "#4a90d9",
|
||||||
|
"outlineColor": "#2a5a99"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "e17",
|
||||||
|
"type": "chair",
|
||||||
|
"layerId": "layer-0",
|
||||||
|
"x": -848,
|
||||||
|
"y": -848,
|
||||||
|
"width": 45,
|
||||||
|
"height": 45,
|
||||||
|
"properties": {
|
||||||
|
"fill": "#4a90d9",
|
||||||
|
"outlineColor": "#2a5a99"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "e18",
|
||||||
|
"type": "chair",
|
||||||
|
"layerId": "layer-0",
|
||||||
|
"x": 0,
|
||||||
|
"y": -1200,
|
||||||
|
"width": 45,
|
||||||
|
"height": 45,
|
||||||
|
"properties": {
|
||||||
|
"fill": "#4a90d9",
|
||||||
|
"outlineColor": "#2a5a99"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "e19",
|
||||||
|
"type": "chair",
|
||||||
|
"layerId": "layer-0",
|
||||||
|
"x": 848,
|
||||||
|
"y": -848,
|
||||||
|
"width": 45,
|
||||||
|
"height": 45,
|
||||||
|
"properties": {
|
||||||
|
"fill": "#4a90d9",
|
||||||
|
"outlineColor": "#2a5a99"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "evt-10",
|
||||||
|
"name": "Tischgruppe (4 Stühle)",
|
||||||
|
"elements": [
|
||||||
|
{
|
||||||
|
"id": "e10a",
|
||||||
|
"type": "rect",
|
||||||
|
"layerId": "layer-0",
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"width": 1600,
|
||||||
|
"height": 800,
|
||||||
|
"properties": {
|
||||||
|
"fill": "#8b7355"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "e20",
|
||||||
|
"type": "chair",
|
||||||
|
"layerId": "layer-0",
|
||||||
|
"x": -400,
|
||||||
|
"y": -580,
|
||||||
|
"width": 45,
|
||||||
|
"height": 45,
|
||||||
|
"properties": {
|
||||||
|
"fill": "#4a90d9",
|
||||||
|
"outlineColor": "#2a5a99"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "e21",
|
||||||
|
"type": "chair",
|
||||||
|
"layerId": "layer-0",
|
||||||
|
"x": 400,
|
||||||
|
"y": -580,
|
||||||
|
"width": 45,
|
||||||
|
"height": 45,
|
||||||
|
"properties": {
|
||||||
|
"fill": "#4a90d9",
|
||||||
|
"outlineColor": "#2a5a99"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "e22",
|
||||||
|
"type": "chair",
|
||||||
|
"layerId": "layer-0",
|
||||||
|
"x": -400,
|
||||||
|
"y": 580,
|
||||||
|
"width": 45,
|
||||||
|
"height": 45,
|
||||||
|
"properties": {
|
||||||
|
"fill": "#4a90d9",
|
||||||
|
"outlineColor": "#2a5a99"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "e23",
|
||||||
|
"type": "chair",
|
||||||
|
"layerId": "layer-0",
|
||||||
|
"x": 400,
|
||||||
|
"y": 580,
|
||||||
|
"width": 45,
|
||||||
|
"height": 45,
|
||||||
|
"properties": {
|
||||||
|
"fill": "#4a90d9",
|
||||||
|
"outlineColor": "#2a5a99"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "evt-11",
|
||||||
|
"name": "Flipchart",
|
||||||
|
"elements": [
|
||||||
|
{
|
||||||
|
"id": "e11a",
|
||||||
|
"type": "rect",
|
||||||
|
"layerId": "layer-0",
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"width": 700,
|
||||||
|
"height": 500,
|
||||||
|
"properties": {
|
||||||
|
"fill": "#546b7a"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "e11b",
|
||||||
|
"type": "line",
|
||||||
|
"layerId": "layer-0",
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"width": 0,
|
||||||
|
"height": 0,
|
||||||
|
"properties": {
|
||||||
|
"x1": 0,
|
||||||
|
"y1": 250,
|
||||||
|
"x2": 0,
|
||||||
|
"y2": 500
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "evt-12",
|
||||||
|
"name": "Pinnwand 120cm",
|
||||||
|
"elements": [
|
||||||
|
{
|
||||||
|
"id": "e12a",
|
||||||
|
"type": "rect",
|
||||||
|
"layerId": "layer-0",
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"width": 1200,
|
||||||
|
"height": 150,
|
||||||
|
"properties": {
|
||||||
|
"fill": "#7a6550"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "evt-13",
|
||||||
|
"name": "Kaffeetafel 2m",
|
||||||
|
"elements": [
|
||||||
|
{
|
||||||
|
"id": "e13a",
|
||||||
|
"type": "rect",
|
||||||
|
"layerId": "layer-0",
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"width": 2000,
|
||||||
|
"height": 600,
|
||||||
|
"properties": {
|
||||||
|
"fill": "#6b5340"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "evt-14",
|
||||||
|
"name": "Garderobe 1m",
|
||||||
|
"elements": [
|
||||||
|
{
|
||||||
|
"id": "e14a",
|
||||||
|
"type": "rect",
|
||||||
|
"layerId": "layer-0",
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"width": 1000,
|
||||||
|
"height": 300,
|
||||||
|
"properties": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "e14h0",
|
||||||
|
"type": "circle",
|
||||||
|
"layerId": "layer-0",
|
||||||
|
"x": -300,
|
||||||
|
"y": 0,
|
||||||
|
"width": 180,
|
||||||
|
"height": 180,
|
||||||
|
"properties": {
|
||||||
|
"radius": 90
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "e14h1",
|
||||||
|
"type": "circle",
|
||||||
|
"layerId": "layer-0",
|
||||||
|
"x": -150,
|
||||||
|
"y": 0,
|
||||||
|
"width": 180,
|
||||||
|
"height": 180,
|
||||||
|
"properties": {
|
||||||
|
"radius": 90
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "e14h2",
|
||||||
|
"type": "circle",
|
||||||
|
"layerId": "layer-0",
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"width": 180,
|
||||||
|
"height": 180,
|
||||||
|
"properties": {
|
||||||
|
"radius": 90
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "e14h3",
|
||||||
|
"type": "circle",
|
||||||
|
"layerId": "layer-0",
|
||||||
|
"x": 150,
|
||||||
|
"y": 0,
|
||||||
|
"width": 180,
|
||||||
|
"height": 180,
|
||||||
|
"properties": {
|
||||||
|
"radius": 90
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "e14h4",
|
||||||
|
"type": "circle",
|
||||||
|
"layerId": "layer-0",
|
||||||
|
"x": 300,
|
||||||
|
"y": 0,
|
||||||
|
"width": 180,
|
||||||
|
"height": 180,
|
||||||
|
"properties": {
|
||||||
|
"radius": 90
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "evt-15",
|
||||||
|
"name": "Teppichläufer 2m",
|
||||||
|
"elements": [
|
||||||
|
{
|
||||||
|
"id": "e15a",
|
||||||
|
"type": "rect",
|
||||||
|
"layerId": "layer-0",
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"width": 2000,
|
||||||
|
"height": 200,
|
||||||
|
"properties": {
|
||||||
|
"fill": "#7a5230"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,406 @@
|
|||||||
|
{
|
||||||
|
"folderId": "cat-land",
|
||||||
|
"label": "Landschafts-Katalog",
|
||||||
|
"blocks": [
|
||||||
|
{
|
||||||
|
"id": "land-1",
|
||||||
|
"name": "Baum (groß)",
|
||||||
|
"elements": [
|
||||||
|
{
|
||||||
|
"id": "l1a",
|
||||||
|
"type": "circle",
|
||||||
|
"layerId": "layer-0",
|
||||||
|
"x": 0,
|
||||||
|
"y": -200,
|
||||||
|
"width": 2200,
|
||||||
|
"height": 2200,
|
||||||
|
"properties": {
|
||||||
|
"radius": 1100,
|
||||||
|
"fill": "#6aa84f"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "l1b",
|
||||||
|
"type": "rect",
|
||||||
|
"layerId": "layer-0",
|
||||||
|
"x": 0,
|
||||||
|
"y": 600,
|
||||||
|
"width": 200,
|
||||||
|
"height": 800,
|
||||||
|
"properties": {
|
||||||
|
"fill": "#8b5a2b"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "land-2",
|
||||||
|
"name": "Baum (mittel)",
|
||||||
|
"elements": [
|
||||||
|
{
|
||||||
|
"id": "l2a",
|
||||||
|
"type": "circle",
|
||||||
|
"layerId": "layer-0",
|
||||||
|
"x": 0,
|
||||||
|
"y": -150,
|
||||||
|
"width": 1500,
|
||||||
|
"height": 1500,
|
||||||
|
"properties": {
|
||||||
|
"radius": 750,
|
||||||
|
"fill": "#6aa84f"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "l2b",
|
||||||
|
"type": "rect",
|
||||||
|
"layerId": "layer-0",
|
||||||
|
"x": 0,
|
||||||
|
"y": 450,
|
||||||
|
"width": 150,
|
||||||
|
"height": 600,
|
||||||
|
"properties": {
|
||||||
|
"fill": "#8b5a2b"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "land-3",
|
||||||
|
"name": "Strauch",
|
||||||
|
"elements": [
|
||||||
|
{
|
||||||
|
"id": "l3a",
|
||||||
|
"type": "circle",
|
||||||
|
"layerId": "layer-0",
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"width": 1000,
|
||||||
|
"height": 1000,
|
||||||
|
"properties": {
|
||||||
|
"radius": 500,
|
||||||
|
"fill": "#85b75f"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "land-4",
|
||||||
|
"name": "Hecke horizontal 4m",
|
||||||
|
"elements": [
|
||||||
|
{
|
||||||
|
"id": "l4a",
|
||||||
|
"type": "rect",
|
||||||
|
"layerId": "layer-0",
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"width": 4000,
|
||||||
|
"height": 500,
|
||||||
|
"properties": {
|
||||||
|
"fill": "#4c7a3f"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "land-5",
|
||||||
|
"name": "Hecke vertikal 4m",
|
||||||
|
"elements": [
|
||||||
|
{
|
||||||
|
"id": "l5a",
|
||||||
|
"type": "rect",
|
||||||
|
"layerId": "layer-0",
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"width": 500,
|
||||||
|
"height": 4000,
|
||||||
|
"properties": {
|
||||||
|
"fill": "#4c7a3f"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "land-6",
|
||||||
|
"name": "Blumenbeet",
|
||||||
|
"elements": [
|
||||||
|
{
|
||||||
|
"id": "l6a",
|
||||||
|
"type": "circle",
|
||||||
|
"layerId": "layer-0",
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"width": 1600,
|
||||||
|
"height": 1600,
|
||||||
|
"properties": {
|
||||||
|
"radius": 800,
|
||||||
|
"fill": "#c76fb6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "l6b",
|
||||||
|
"type": "circle",
|
||||||
|
"layerId": "layer-0",
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"width": 600,
|
||||||
|
"height": 600,
|
||||||
|
"properties": {
|
||||||
|
"radius": 300,
|
||||||
|
"fill": "#e6a23c"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "land-7",
|
||||||
|
"name": "Teich",
|
||||||
|
"elements": [
|
||||||
|
{
|
||||||
|
"id": "l7a",
|
||||||
|
"type": "circle",
|
||||||
|
"layerId": "layer-0",
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"width": 3000,
|
||||||
|
"height": 3000,
|
||||||
|
"properties": {
|
||||||
|
"radius": 1500,
|
||||||
|
"fill": "#4a90d9"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "l7b",
|
||||||
|
"type": "circle",
|
||||||
|
"layerId": "layer-0",
|
||||||
|
"x": 0,
|
||||||
|
"y": 100,
|
||||||
|
"width": 2700,
|
||||||
|
"height": 2700,
|
||||||
|
"properties": {
|
||||||
|
"radius": 1350,
|
||||||
|
"fill": "#5fa8e0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "land-8",
|
||||||
|
"name": "Rasenfläche 5x3m",
|
||||||
|
"elements": [
|
||||||
|
{
|
||||||
|
"id": "l8a",
|
||||||
|
"type": "rect",
|
||||||
|
"layerId": "layer-0",
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"width": 5000,
|
||||||
|
"height": 3000,
|
||||||
|
"properties": {
|
||||||
|
"fill": "#7cb342"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "land-9",
|
||||||
|
"name": "Bodenplatte",
|
||||||
|
"elements": [
|
||||||
|
{
|
||||||
|
"id": "l9a",
|
||||||
|
"type": "rect",
|
||||||
|
"layerId": "layer-0",
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"width": 1200,
|
||||||
|
"height": 1200,
|
||||||
|
"properties": {
|
||||||
|
"fill": "#b8a888"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "land-10",
|
||||||
|
"name": "Weg (Gerade)",
|
||||||
|
"elements": [
|
||||||
|
{
|
||||||
|
"id": "l10a",
|
||||||
|
"type": "rect",
|
||||||
|
"layerId": "layer-0",
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"width": 600,
|
||||||
|
"height": 4000,
|
||||||
|
"properties": {
|
||||||
|
"fill": "#b8a888"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "land-11",
|
||||||
|
"name": "Sonnenliege",
|
||||||
|
"elements": [
|
||||||
|
{
|
||||||
|
"id": "l11a",
|
||||||
|
"type": "rect",
|
||||||
|
"layerId": "layer-0",
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"width": 600,
|
||||||
|
"height": 2000,
|
||||||
|
"properties": {
|
||||||
|
"fill": "#d2b48c"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "land-12",
|
||||||
|
"name": "Feuerschale",
|
||||||
|
"elements": [
|
||||||
|
{
|
||||||
|
"id": "l12a",
|
||||||
|
"type": "circle",
|
||||||
|
"layerId": "layer-0",
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"width": 1200,
|
||||||
|
"height": 1200,
|
||||||
|
"properties": {
|
||||||
|
"radius": 600,
|
||||||
|
"fill": "#5a5a5a"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "l12b",
|
||||||
|
"type": "circle",
|
||||||
|
"layerId": "layer-0",
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"width": 600,
|
||||||
|
"height": 600,
|
||||||
|
"properties": {
|
||||||
|
"radius": 300,
|
||||||
|
"fill": "#cc4125"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "land-13",
|
||||||
|
"name": "Zaun 3m",
|
||||||
|
"elements": [
|
||||||
|
{
|
||||||
|
"id": "l13a",
|
||||||
|
"type": "line",
|
||||||
|
"layerId": "layer-0",
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"width": 0,
|
||||||
|
"height": 0,
|
||||||
|
"properties": {
|
||||||
|
"x1": -1500,
|
||||||
|
"y1": 0,
|
||||||
|
"x2": -1500,
|
||||||
|
"y2": 900
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "l13b",
|
||||||
|
"type": "line",
|
||||||
|
"layerId": "layer-0",
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"width": 0,
|
||||||
|
"height": 0,
|
||||||
|
"properties": {
|
||||||
|
"x1": 1500,
|
||||||
|
"y1": 0,
|
||||||
|
"x2": 1500,
|
||||||
|
"y2": 900
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "l13c",
|
||||||
|
"type": "line",
|
||||||
|
"layerId": "layer-0",
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"width": 0,
|
||||||
|
"height": 0,
|
||||||
|
"properties": {
|
||||||
|
"x1": -1500,
|
||||||
|
"y1": 450,
|
||||||
|
"x2": 1500,
|
||||||
|
"y2": 450
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "land-14",
|
||||||
|
"name": "Laterne",
|
||||||
|
"elements": [
|
||||||
|
{
|
||||||
|
"id": "l14a",
|
||||||
|
"type": "circle",
|
||||||
|
"layerId": "layer-0",
|
||||||
|
"x": 0,
|
||||||
|
"y": -1000,
|
||||||
|
"width": 400,
|
||||||
|
"height": 400,
|
||||||
|
"properties": {
|
||||||
|
"radius": 200,
|
||||||
|
"fill": "#f5d76e"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "l14b",
|
||||||
|
"type": "line",
|
||||||
|
"layerId": "layer-0",
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"width": 0,
|
||||||
|
"height": 0,
|
||||||
|
"properties": {
|
||||||
|
"x1": 0,
|
||||||
|
"y1": -800,
|
||||||
|
"x2": 0,
|
||||||
|
"y2": 1000
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "l14c",
|
||||||
|
"type": "rect",
|
||||||
|
"layerId": "layer-0",
|
||||||
|
"x": 0,
|
||||||
|
"y": 1100,
|
||||||
|
"width": 300,
|
||||||
|
"height": 200,
|
||||||
|
"properties": {
|
||||||
|
"fill": "#5a5a5a"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "land-15",
|
||||||
|
"name": "Gartenbank",
|
||||||
|
"elements": [
|
||||||
|
{
|
||||||
|
"id": "l15a",
|
||||||
|
"type": "rect",
|
||||||
|
"layerId": "layer-0",
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"width": 1500,
|
||||||
|
"height": 400,
|
||||||
|
"properties": {
|
||||||
|
"fill": "#8b5a2b"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
/**
|
||||||
|
* Task F4 – Builtin-Bibliotheks-Plugin.
|
||||||
|
*
|
||||||
|
* Registriert den builtin-Katalog-Provider über die Plugin-API v2. Die Provider
|
||||||
|
* für user-library (zeichnungsscoped) und global-library (API/Token) sind als
|
||||||
|
* Fabriken (makeUserLibraryProvider / makeGlobalLibraryProvider) umgesetzt und
|
||||||
|
* werden vom UI je Token/Zeichnung instanziert.
|
||||||
|
*/
|
||||||
|
import type { PluginV2 } from '../../types';
|
||||||
|
import { builtinCatalogProvider } from './providers';
|
||||||
|
|
||||||
|
export const builtinLibraryPlugin: PluginV2 = {
|
||||||
|
manifest: {
|
||||||
|
id: 'builtin-library',
|
||||||
|
name: 'Block-Bibliothek',
|
||||||
|
version: '1.0.0',
|
||||||
|
author: 'WebCAD Project',
|
||||||
|
description: 'Bibliothek: builtin-Kataloge (Event/Architektur/Landschaft) als LibraryProvider (Task F4).',
|
||||||
|
category: 'library',
|
||||||
|
enabledByDefault: true,
|
||||||
|
permissions: ['storage'],
|
||||||
|
},
|
||||||
|
libraryProviders: [builtinCatalogProvider],
|
||||||
|
};
|
||||||
@@ -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}`);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -32,6 +32,8 @@ export { coreModifyPlugin } from './builtin/core-modify';
|
|||||||
export { coreAnnotatePlugin } from './builtin/core-annotate';
|
export { coreAnnotatePlugin } from './builtin/core-annotate';
|
||||||
export { coreMeasurePlugin } from './builtin/core-measure';
|
export { coreMeasurePlugin } from './builtin/core-measure';
|
||||||
export { eventToolsV2Plugin } from './builtin/event-tools';
|
export { eventToolsV2Plugin } from './builtin/event-tools';
|
||||||
|
export { builtinLibraryPlugin } from './builtin/library';
|
||||||
|
export { builtinCatalogProvider, makeUserLibraryProvider, makeGlobalLibraryProvider } from './builtin/library/providers';
|
||||||
|
|
||||||
import { pluginRegistry } from './PluginRegistry';
|
import { pluginRegistry } from './PluginRegistry';
|
||||||
import { eventToolsPlugin } from './builtin/eventTools';
|
import { eventToolsPlugin } from './builtin/eventTools';
|
||||||
@@ -40,6 +42,7 @@ import { coreModifyPlugin } from './builtin/core-modify';
|
|||||||
import { coreAnnotatePlugin } from './builtin/core-annotate';
|
import { coreAnnotatePlugin } from './builtin/core-annotate';
|
||||||
import { coreMeasurePlugin } from './builtin/core-measure';
|
import { coreMeasurePlugin } from './builtin/core-measure';
|
||||||
import { eventToolsV2Plugin } from './builtin/event-tools';
|
import { eventToolsV2Plugin } from './builtin/event-tools';
|
||||||
|
import { builtinLibraryPlugin } from './builtin/library';
|
||||||
|
|
||||||
/** Register all built-in plugins */
|
/** Register all built-in plugins */
|
||||||
export function registerBuiltinPlugins() {
|
export function registerBuiltinPlugins() {
|
||||||
@@ -50,4 +53,5 @@ export function registerBuiltinPlugins() {
|
|||||||
pluginRegistry.registerV2(coreAnnotatePlugin);
|
pluginRegistry.registerV2(coreAnnotatePlugin);
|
||||||
pluginRegistry.registerV2(coreMeasurePlugin);
|
pluginRegistry.registerV2(coreMeasurePlugin);
|
||||||
pluginRegistry.registerV2(eventToolsV2Plugin);
|
pluginRegistry.registerV2(eventToolsV2Plugin);
|
||||||
|
pluginRegistry.registerV2(builtinLibraryPlugin);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -208,7 +208,9 @@ export interface LibBlock {
|
|||||||
folderId: string;
|
folderId: string;
|
||||||
name: string;
|
name: string;
|
||||||
tags?: string[];
|
tags?: string[];
|
||||||
thumbnail?: string; // dataURL
|
thumbnail?: string; // svg/dataURL
|
||||||
|
/** Payload für Canvas-Drop: i.d.R. JSON-String eines CADElement-Arrays (Task F4). */
|
||||||
|
payload?: unknown;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Liefert Kataloge — Kern der Plugin-erweiterbaren Library (Phase F). */
|
/** Liefert Kataloge — Kern der Plugin-erweiterbaren Library (Phase F). */
|
||||||
|
|||||||
@@ -2796,3 +2796,42 @@ body.drawer-open { overflow: hidden; }
|
|||||||
background: var(--color-accent);
|
background: var(--color-accent);
|
||||||
opacity: 0.7;
|
opacity: 0.7;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* ─── Task F4 – LibraryProviderPanel ─── */
|
||||||
|
.lib-provider-panel {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 8px 0 4px;
|
||||||
|
border-top: 1px solid rgba(255, 255, 255, 0.08);
|
||||||
|
}
|
||||||
|
.lprov-section { display: flex; flex-direction: column; gap: 4px; }
|
||||||
|
.lprov-title {
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #9aa0a6;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.04em;
|
||||||
|
padding: 2px 8px;
|
||||||
|
}
|
||||||
|
.lprov-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(56px, 1fr));
|
||||||
|
gap: 6px;
|
||||||
|
padding: 0 8px;
|
||||||
|
}
|
||||||
|
.lprov-block {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 6px 4px;
|
||||||
|
background: transparent;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
border-radius: 6px;
|
||||||
|
cursor: grab;
|
||||||
|
color: inherit;
|
||||||
|
min-height: 44px;
|
||||||
|
}
|
||||||
|
.lprov-block:hover { background: rgba(255, 255, 255, 0.06); border-color: rgba(255, 255, 255, 0.12); }
|
||||||
|
.lprov-block .tree-thumb svg { width: 44px; height: 36px; display: block; }
|
||||||
|
|||||||
@@ -0,0 +1,143 @@
|
|||||||
|
/**
|
||||||
|
* Task F4 – LibraryProviderExtension Tests.
|
||||||
|
*
|
||||||
|
* Testet die drei Bibliotheksquellen:
|
||||||
|
* - builtin-catalog (statische JSON-Startersets event/architecture/landscape)
|
||||||
|
* - user-library (zeichnungsscoped Blöcke via /api/drawings/:id/blocks)
|
||||||
|
* - global-library (globale Blöcke via /api/global-blocks)
|
||||||
|
*/
|
||||||
|
import { describe, it, expect, vi, afterEach } from 'vitest';
|
||||||
|
import { render, screen, waitFor, fireEvent } from '@testing-library/react';
|
||||||
|
|
||||||
|
import { builtinCatalogProvider } from '../src/plugins/builtin/library/providers';
|
||||||
|
import { makeUserLibraryProvider, makeGlobalLibraryProvider } from '../src/plugins/builtin/library/providers';
|
||||||
|
import { builtinLibraryPlugin } from '../src/plugins/builtin/library';
|
||||||
|
import { pluginRegistry } from '../src/plugins/PluginRegistry';
|
||||||
|
import LibraryProviderPanel from '../src/components/LibraryProviderPanel';
|
||||||
|
|
||||||
|
describe('F4: builtin catalog provider', () => {
|
||||||
|
it('listet genau die 3 Katalog-Ordner', async () => {
|
||||||
|
const folders = await builtinCatalogProvider.listFolders();
|
||||||
|
expect(folders.map(f => f.id).sort()).toEqual(['cat-arch', 'cat-event', 'cat-land']);
|
||||||
|
expect(folders.every(f => f.parentId === null)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Startersets: je 15-30 Blöcke mit gültiger Payload und SVG-Thumbnail', async () => {
|
||||||
|
const folders = await builtinCatalogProvider.listFolders();
|
||||||
|
for (const folder of folders) {
|
||||||
|
const blocks = await builtinCatalogProvider.listBlocks(folder.id);
|
||||||
|
expect(blocks.length).toBeGreaterThanOrEqual(15);
|
||||||
|
expect(blocks.length).toBeLessThanOrEqual(30);
|
||||||
|
expect(blocks.every(b => b.folderId === folder.id)).toBe(true);
|
||||||
|
for (const block of blocks) {
|
||||||
|
expect(block.thumbnail).toContain('<svg');
|
||||||
|
expect(block.payload).toBeTruthy();
|
||||||
|
const elements = JSON.parse(block.payload as string);
|
||||||
|
expect(Array.isArray(elements)).toBe(true);
|
||||||
|
expect(elements.length).toBeGreaterThan(0);
|
||||||
|
for (const el of elements) {
|
||||||
|
expect(typeof el.id).toBe('string');
|
||||||
|
expect(typeof el.type).toBe('string');
|
||||||
|
expect('x' in el && 'y' in el).toBe(true);
|
||||||
|
expect(el.properties).toBeTypeOf('object');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('listBlocks filtert nach Ordner, getBlock liefert den Block mit Payload', async () => {
|
||||||
|
const eventBlocks = await builtinCatalogProvider.listBlocks('cat-event');
|
||||||
|
expect(eventBlocks.length).toBeGreaterThanOrEqual(15);
|
||||||
|
const first = eventBlocks[0];
|
||||||
|
const fetched = await builtinCatalogProvider.getBlock(first.id);
|
||||||
|
expect(fetched.id).toBe(first.id);
|
||||||
|
expect(fetched.payload).toBe(first.payload);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('F4: Registry-Anbindung', () => {
|
||||||
|
afterEach(() => {
|
||||||
|
pluginRegistry.unregisterV2(builtinLibraryPlugin.manifest.id);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Plugin registriert Provider, enableV2 macht ihn sichtbar, disableV2 entfernt ihn', () => {
|
||||||
|
pluginRegistry.registerV2(builtinLibraryPlugin);
|
||||||
|
pluginRegistry.enableV2(builtinLibraryPlugin.manifest.id);
|
||||||
|
const providers = pluginRegistry.getLibraryProviders();
|
||||||
|
expect(providers.some(p => p.id === 'builtin-catalog')).toBe(true);
|
||||||
|
|
||||||
|
pluginRegistry.disableV2(builtinLibraryPlugin.manifest.id);
|
||||||
|
expect(pluginRegistry.getLibraryProviders().some(p => p.id === 'builtin-catalog')).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('F4: user-library Provider', () => {
|
||||||
|
it('mappt zeichnungsscoped Blöcke auf LibBlock mit Thumbnail+Payload', async () => {
|
||||||
|
const provider = makeUserLibraryProvider([
|
||||||
|
{
|
||||||
|
id: 'b1', drawing_id: 'd1', name: 'Bühne A', description: null, category: 'stage',
|
||||||
|
elements_json: JSON.stringify([
|
||||||
|
{ id: 'e1', type: 'rect', layerId: 'layer-0', x: 0, y: 0, width: 100, height: 50, properties: { fill: '#3a3a4a' } },
|
||||||
|
]),
|
||||||
|
thumbnail: null,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
const folders = await provider.listFolders();
|
||||||
|
expect(folders.length).toBe(1);
|
||||||
|
const blocks = await provider.listBlocks(folders[0].id);
|
||||||
|
expect(blocks).toHaveLength(1);
|
||||||
|
expect(blocks[0].name).toBe('Bühne A');
|
||||||
|
expect(blocks[0].thumbnail).toContain('<svg');
|
||||||
|
const els = JSON.parse(blocks[0].payload as string);
|
||||||
|
expect(els[0].id).toBe('e1');
|
||||||
|
expect((await provider.getBlock(blocks[0].id)).name).toBe('Bühne A');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('F4: global-library Provider', () => {
|
||||||
|
afterEach(() => {
|
||||||
|
vi.unstubAllGlobals();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('lädt Ordner und Blöcke der globalen Bibliothek über die API', async () => {
|
||||||
|
const mockFetch = vi.fn()
|
||||||
|
.mockResolvedValueOnce({ ok: true, json: async () => [
|
||||||
|
{ id: 'gf1', name: 'Event', parent_id: null, created_at: '2026-01-01' },
|
||||||
|
] })
|
||||||
|
.mockResolvedValueOnce({ ok: true, json: async () => [
|
||||||
|
{ id: 'gb1', folder_id: 'gf1', name: 'Stuhl-Reihe', block_data: '[]', svg_data: null, is_favorite: false, created_at: '2026-01-01', updated_at: '2026-01-01' },
|
||||||
|
] });
|
||||||
|
vi.stubGlobal('fetch', mockFetch);
|
||||||
|
|
||||||
|
const provider = makeGlobalLibraryProvider('tok');
|
||||||
|
const folders = await provider.listFolders();
|
||||||
|
expect(folders).toHaveLength(1);
|
||||||
|
expect(folders[0].label).toBe('Event');
|
||||||
|
const blocks = await provider.listBlocks('gf1');
|
||||||
|
expect(blocks).toHaveLength(1);
|
||||||
|
expect(blocks[0].name).toBe('Stuhl-Reihe');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('F4: LibraryProviderPanel UI', () => {
|
||||||
|
it('rendert Provider-Sektion mit Katalog-Ordnern und Blöcken', async () => {
|
||||||
|
render(<LibraryProviderPanel providers={[builtinCatalogProvider]} />);
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.getAllByText(/Event-Katalog/).length).toBeGreaterThan(0);
|
||||||
|
});
|
||||||
|
// Jede Sektion listet Blöcke
|
||||||
|
expect(screen.getAllByRole('button', { name: /Stuhl/ }).length).toBeGreaterThan(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('DragStart legt die Element-Array-Payload in den Datentransfer', async () => {
|
||||||
|
const setData = vi.fn();
|
||||||
|
render(<LibraryProviderPanel providers={[builtinCatalogProvider]} />);
|
||||||
|
const blockBtn = await screen.findByRole('button', { name: /Stuhl/ });
|
||||||
|
fireEvent.dragStart(blockBtn, { dataTransfer: { setData } });
|
||||||
|
expect(setData).toHaveBeenCalledWith('text/global-block-data', expect.any(String));
|
||||||
|
const payload = JSON.parse(setData.mock.calls[0][1] as string);
|
||||||
|
expect(Array.isArray(payload)).toBe(true);
|
||||||
|
|
||||||
|
expect(payload[0].id).toBeTypeOf('string');
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user