83 lines
2.7 KiB
TypeScript
83 lines
2.7 KiB
TypeScript
/**
|
|
* Left panel: node types grouped by category. Items are dragged onto the
|
|
* canvas (HTML5 drag & drop carrying the type id) or double-clicked to drop
|
|
* at the viewport center.
|
|
*/
|
|
|
|
import { useMemo, useState } from 'react';
|
|
import { nodeTypesByCategory, type NodeTypeDef } from '../model/nodeTypes';
|
|
import { svgDataUri } from '../editor/shapes';
|
|
import { getColorScheme } from '../editor/colorSchemes';
|
|
|
|
export const PALETTE_MIME = 'application/x-pipeline-node-type';
|
|
|
|
interface PaletteProps {
|
|
schemeId: string;
|
|
onQuickAdd(typeId: string): void;
|
|
}
|
|
|
|
function PaletteItem({ type, schemeId, onQuickAdd }: {
|
|
type: NodeTypeDef;
|
|
schemeId: string;
|
|
onQuickAdd(typeId: string): void;
|
|
}) {
|
|
const scheme = getColorScheme(schemeId);
|
|
return (
|
|
<div
|
|
className="pf-palette-item"
|
|
draggable
|
|
title={`${type.label} — drag onto canvas`}
|
|
data-testid={`palette-item-${type.id}`}
|
|
onDragStart={(e) => {
|
|
e.dataTransfer.setData(PALETTE_MIME, type.id);
|
|
e.dataTransfer.effectAllowed = 'copy';
|
|
}}
|
|
onDoubleClick={() => onQuickAdd(type.id)}
|
|
>
|
|
<img src={svgDataUri(type, scheme.nodeStroke)} alt={type.label} width={34} height={34} />
|
|
<span>{type.label}</span>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export function Palette({ schemeId, onQuickAdd }: PaletteProps) {
|
|
const [filter, setFilter] = useState('');
|
|
const [collapsed, setCollapsed] = useState<Record<string, boolean>>({});
|
|
const groups = useMemo(() => nodeTypesByCategory(), []);
|
|
|
|
const query = filter.trim().toLowerCase();
|
|
return (
|
|
<aside className="pf-palette" data-testid="palette">
|
|
<input
|
|
className="pf-palette-search"
|
|
placeholder="Search nodes…"
|
|
value={filter}
|
|
onChange={(e) => setFilter(e.target.value)}
|
|
/>
|
|
{groups.map(({ category, types }) => {
|
|
const visible = query
|
|
? types.filter((t) => t.label.toLowerCase().includes(query))
|
|
: types;
|
|
if (visible.length === 0) return null;
|
|
const isCollapsed = collapsed[category.id] && !query;
|
|
return (
|
|
<section key={category.id} className="pf-palette-group">
|
|
<header
|
|
onClick={() => setCollapsed((c) => ({ ...c, [category.id]: !c[category.id] }))}
|
|
>
|
|
<span className="pf-caret">{isCollapsed ? '▸' : '▾'}</span> {category.label}
|
|
</header>
|
|
{!isCollapsed && (
|
|
<div className="pf-palette-items">
|
|
{visible.map((type) => (
|
|
<PaletteItem key={type.id} type={type} schemeId={schemeId} onQuickAdd={onQuickAdd} />
|
|
))}
|
|
</div>
|
|
)}
|
|
</section>
|
|
);
|
|
})}
|
|
</aside>
|
|
);
|
|
}
|