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:
Agent Zero
2026-08-28 23:20:11 +02:00
parent ec7cb4043b
commit 2efbbf9bd2
11 changed files with 2093 additions and 1 deletions
@@ -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;
+23
View File
@@ -1,9 +1,14 @@
import React, { useCallback } from 'react';
import type { RightSidebarProps, RightPanel } from '../types/ui.types';
import type { LibraryProviderExtension } from '../plugins/types';
import PropertiesPanel from './PropertiesPanel';
import LayerPanel from './LayerPanel';
import BlockLibrary from './BlockLibrary';
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';
const MIN_RIGHTBAR_WIDTH = 200;
@@ -39,6 +44,23 @@ const RightSidebar: React.FC<RightSidebarProps> = ({
rightbarWidth = 300,
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) => {
e.preventDefault();
e.stopPropagation();
@@ -138,6 +160,7 @@ const RightSidebar: React.FC<RightSidebarProps> = ({
}}
/>
)}
<LibraryProviderPanel providers={libraryProviders} />
</>
)}
</div>