85 lines
2.7 KiB
TypeScript
85 lines
2.7 KiB
TypeScript
/**
|
||
* 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;
|