@selenite/graph-editor
Version:
A graph editor for visual programming, based on rete and svelte.
144 lines (143 loc) • 5.11 kB
JavaScript
import { assignConnectionPath } from '../../connection-path';
import { Scope } from 'rete';
import { ConnectionPathPlugin } from 'rete-connection-path-plugin';
import { get } from 'svelte/store';
import { getSvelteRenderer } from './renderer.svelte';
import { ButtonControl, InputControl, Socket } from '../../socket';
import { getDOMSocketPosition } from '../sockets-position.ts/dom-socket-position';
class SveltePlugin extends Scope {
renderer;
presets = [];
owners = new WeakMap();
constructor() {
super('svelte-render');
this.renderer = getSvelteRenderer();
this.addPipe((context) => {
if (!context || typeof context !== 'object' || !('type' in context))
return context;
if (context.type === 'unmount') {
this.unmount(context.data.element);
}
else if (context.type === 'render') {
if ('filled' in context.data && context.data.filled) {
return context;
}
if (this.mount(context.data.element, context)) {
return {
...context,
data: {
...context.data,
filled: true
}
};
}
}
return context;
});
}
apps = new Map();
setParent(scope) {
super.setParent(scope);
this.presets.forEach((preset) => {
if (preset.attach)
preset.attach(this);
});
}
unmount(element) {
this.owners.delete(element);
this.renderer.unmount(element);
}
// apps = new Map<HTMLElement, Node>();
mount(element, context) {
// console.log('mount', element);
const existing = this.renderer.get(element);
const parent = this.parentScope();
if (existing) {
this.presets.forEach((preset) => {
if (this.owners.get(element) !== preset)
return;
const result = preset.update(context, this);
if (result) {
this.renderer.update(existing, result);
}
});
return true;
}
for (const preset of this.presets) {
const result = preset.render(context, this);
if (!result)
continue;
this.renderer.mount(element, result.component, result.props, () => parent?.emit({ type: 'rendered', data: context.data }));
this.owners.set(element, preset);
return true;
}
return false;
}
/**
* Adds a preset to the plugin.
* @param preset Preset that can render nodes, connections and other elements.
*/
addPreset(preset) {
const local = preset;
if (local.attach)
local.attach(this);
this.presets.push(local);
}
}
export const setupSvelteRender = async (params) => {
const { area, factory, editor } = params;
if (!area) {
console.warn('Could not setup svelte render, area not found');
return params;
}
console.log('Setting up svelte render');
const sveltePlugin = new SveltePlugin();
const pathPlugin = new ConnectionPathPlugin({
curve: (conn) => assignConnectionPath(get(factory.connectionPathType))
});
factory.connectionPathType.subscribe(() => {
const area = factory.getArea();
if (!area) {
console.warn('Area not found');
return;
}
for (const conn of editor.getConnections()) {
area.update('connection', conn.id);
}
});
// @ts-expect-error: Ignore type error
sveltePlugin.use(pathPlugin);
const { AddXmlAttributeControl } = await import('../../nodes/XML');
const Presets = await import('./presets');
// sveltePlugin.addPreset(Presets.contextMenu.setup())
sveltePlugin.addPreset(Presets.classic.setup({
socketPositionWatcher: getDOMSocketPosition(),
customize: {
socket(context) {
if (context.payload instanceof Socket) {
// if (context.payload.type === 'exec') return Presets.classic.ExecSocket;
return Presets.classic.Socket;
}
return Presets.classic.Socket;
},
control(data) {
if (data.payload instanceof InputControl) {
return Presets.classic.InputControl;
}
if (data.payload instanceof ButtonControl) {
return Presets.classic.Button;
}
if (data.payload instanceof AddXmlAttributeControl)
return Presets.classic.AddXmlAttributeControl;
return Presets.classic.Control;
}
}
}));
area.use(sveltePlugin);
return params;
};
export const svelteRenderSetup = {
name: 'Svelte Render',
type: 'area',
setup: setupSvelteRender
};