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;
|
||||
Reference in New Issue
Block a user