@selenite/graph-editor
Version:
A graph editor for visual programming, based on rete and svelte.
189 lines (188 loc) • 6.13 kB
JavaScript
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;
}