Ilya Ashikhmin 6585cfb32c feat(ui): app shell — palette, property panel, toolbar, settings, canvas, store, persistence
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 23:15:38 +02:00

59 lines
1.8 KiB
TypeScript

/**
* Canvas host: mounts the JointJS paper via EditorController and accepts
* palette drops.
*/
import { useEffect, useRef } from 'react';
import { EditorController } from '../editor/paper';
import { PALETTE_MIME } from './Palette';
import { getState, setState, bumpGraphVersion, applySimulationResult } from '../state/store';
interface CanvasProps {
onReady(controller: EditorController): void;
}
export function Canvas({ onReady }: CanvasProps) {
const hostRef = useRef<HTMLDivElement>(null);
const controllerRef = useRef<EditorController | null>(null);
useEffect(() => {
const host = hostRef.current;
if (!host || controllerRef.current) return;
const controller = new EditorController(host, getState().config, {
onSelectionChange: (selection) => setState({ selection }),
onGraphChange: () => bumpGraphVersion(),
onHistoryChange: (canUndo, canRedo) => setState({ canUndo, canRedo }),
onSimulationChange: (running, result) => applySimulationResult(running, result),
});
controllerRef.current = controller;
onReady(controller);
return () => {
controller.dispose();
controllerRef.current = null;
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return (
<div
ref={hostRef}
className="pf-canvas"
data-testid="canvas"
onDragOver={(e) => {
if (e.dataTransfer.types.includes(PALETTE_MIME)) {
e.preventDefault();
e.dataTransfer.dropEffect = 'copy';
}
}}
onDrop={(e) => {
const typeId = e.dataTransfer.getData(PALETTE_MIME);
const controller = controllerRef.current;
if (!typeId || !controller) return;
e.preventDefault();
const p = controller.clientToLocal(e.clientX, e.clientY);
controller.addNode(typeId, p.x, p.y);
}}
/>
);
}