UNPKG

@selenite/graph-editor

Version:

A graph editor for visual programming, based on rete and svelte.

189 lines (188 loc) 6.13 kB
import { AreaPlugin } from 'rete-area-plugin'; import {} from './Setup'; import { getDraggedGraph, getDraggedVariableId, isDraggedGraph, isDraggedVariable } from '../utils'; import { animationFrame, PointerDownWatcher, posFromClient } from '@selenite/commons'; import { GraphNode, MacroNode, VariableNode } from '..'; import { clientToSurfacePos } from '../../utils/html'; import { tick } from 'svelte'; import { cloneDeep, throttle } from 'lodash-es'; // export class AreaSetup extends SetupClass { // private lastClicked: HTMLElement | null = null; // setup(editor: NodeEditor, area: AreaPlugin<Schemes, AreaExtra>, factory: NodeFactory): void { // area.addPipe((ctx) => { // const ignored: (AreaExtra | Area2D<Schemes> | Root<Schemes>)['type'][] = [ // 'unmount', // 'pointermove', // 'render', // 'rendered', // 'zoom', // 'zoomed', // 'translate', // 'translated' // ]; // if (ignored.includes(ctx.type)) { // return ctx; // } // return ctx; // }); // } // } function isXml(e) { if (!e.dataTransfer) return false; const dt = e.dataTransfer; if (dt.types.includes('text/xml')) return true; if (dt.types.includes('Files')) { const files = dt.files; for (let i = 0; i < files.length; i++) { const file = files[i]; if (file.type === 'text/xml') return true; } } return false; } export const setupArea = (params) => { console.log('Setting up area plugin'); const { factory, editor } = params; const container = params.container; if (!container) { console.warn("Container is not defined, can't setup area plugin."); return params; } const area = new AreaPlugin(container); // area.area.setZoomHandler(new SmoothZoom()) // Update nodes position area.addPipe((ctx) => { // if (ctx.type === 'node') if (ctx.type !== 'nodetranslated') return ctx; const { id, position } = ctx.data; const node = editor.getNode(id); if (!node) return ctx; node.pos = position; return ctx; }); function dragHandler(e) { if (isDraggedGraph(e) || isDraggedVariable(e) || isXml(e) || e.dataTransfer?.types.includes('Files')) e.preventDefault(); } let deltaX = 0; let deltaY = 0; const throttledTranslate = throttle(() => { const newX = area.area.transform.x + deltaX; const newY = area.area.transform.y + deltaY; area.area.translate(newX, newY); deltaX = 0; deltaY = 0; }, 5); // Area movement container.addEventListener('pointermove', async (e) => { if (!PointerDownWatcher.instance.isPointerDown) return; const pDownE = PointerDownWatcher.instance.lastEvent; if (!pDownE) return; if (pDownE.button !== 1 && pDownE.button !== 2) return; deltaX += e.movementX; deltaY += e.movementY; throttledTranslate(); }, { passive: true }); container.addEventListener('dragover', dragHandler); container.addEventListener('dragenter', dragHandler); container.addEventListener('drop', async (e) => { const pos = posFromClient(e); const surfacePos = clientToSurfacePos({ pos, factory }); let addedNode; if (isXml(e)) { e.preventDefault(); const xml = await getDraggedXml(e); if (!xml) return; const schema = factory.xmlSchemas.get('geos'); if (!schema) return; factory.codeIntegration.toGraph({ text: xml, schema }); } if (isDraggedVariable(e)) { const vId = getDraggedVariableId(e); if (vId) { addedNode = new VariableNode({ factory, variableId: vId }); } } else if (isDraggedGraph(e)) { const graph = getDraggedGraph(e); if (!graph) return; console.log('Adding macro block', graph); addedNode = new MacroNode({ factory, graph }); } if (!addedNode) return; addedNode.visible = false; await editor.addNode(addedNode); await tick(); await animationFrame(2); if (addedNode instanceof VariableNode) { surfacePos.x = surfacePos.x - addedNode.width; } else if (addedNode instanceof MacroNode) { surfacePos.x = surfacePos.x - addedNode.width / 2; } surfacePos.y = surfacePos.y - addedNode.height / 2; area.translate(addedNode.id, surfacePos); addedNode.visible = true; e.preventDefault(); }); factory.area = area; params.editor.use(area); return { ...params, area }; }; export const arrangeSetup = { name: 'Arrange', type: 'area', setup: async ({ area, factory }) => { const { AutoArrangePlugin, Presets: ArrangePresets } = await import('rete-auto-arrange-plugin'); const arrange = new AutoArrangePlugin(); arrange.addPreset(ArrangePresets.classic.setup()); area.use(arrange); factory.arrange = arrange; } }; async function getDraggedXml(e) { const dt = e.dataTransfer; if (!dt) return; if (dt.types.includes('text/xml')) { return dt.getData('text/xml'); } if (dt.types.includes('Files')) { const files = dt.files; for (let i = 0; i < files.length; i++) { const file = files[i]; if (file.type === 'text/xml') { return await new Promise((resolve, reject) => { const reader = new FileReader(); reader.onload = () => { resolve(reader.result); }; reader.onerror = reject; reader.readAsText(file); }); } } } return; }