rete-angular-jellytech-plugin
Version:
Angular Jellytech ==== Plugin for displaying SCADA and overview elements
937 lines (919 loc) • 96.7 kB
JavaScript
import { getUID, ClassicPreset, Scope } from 'rete';
import { getDOMSocketPosition, loopConnectionPath, classicConnectionPath } from 'rete-render-utils';
import * as i0 from '@angular/core';
import { Directive, Input, Component, HostBinding, HostListener, EventEmitter, Output, NgModule } from '@angular/core';
import * as i1 from '@angular/common';
import { CommonModule } from '@angular/common';
import { BaseAreaPlugin } from 'rete-area-plugin';
import { createCustomElement } from '@angular/elements';
class RefDirective {
el;
data;
emit;
constructor(el) {
this.el = el;
}
ngOnChanges() {
this.emit({ type: 'render', data: { ...this.data, element: this.el.nativeElement } });
}
ngOnDestroy() {
this.emit({ type: 'unmount', data: { element: this.el.nativeElement } });
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.4", ngImport: i0, type: RefDirective, deps: [{ token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Directive });
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "18.2.4", type: RefDirective, selector: "[refComponent]", inputs: { data: "data", emit: "emit" }, usesOnChanges: true, ngImport: i0 });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.4", ngImport: i0, type: RefDirective, decorators: [{
type: Directive,
args: [{
selector: '[refComponent]'
}]
}], ctorParameters: () => [{ type: i0.ElementRef }], propDecorators: { data: [{
type: Input
}], emit: [{
type: Input
}] } });
class NodeComponent {
cdr;
data;
emit;
rendered;
seed = 0;
get width() {
return this.data.width;
}
get height() {
return this.data.height;
}
get selected() {
return this.data.selected;
}
constructor(cdr) {
this.cdr = cdr;
this.cdr.detach();
}
ngOnChanges() {
this.cdr.detectChanges();
requestAnimationFrame(() => this.rendered());
this.seed++; // force render sockets
}
sortByIndex(a, b) {
const ai = a.value?.index || 0;
const bi = b.value?.index || 0;
return ai - bi;
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.4", ngImport: i0, type: NodeComponent, deps: [{ token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.4", type: NodeComponent, selector: "ng-component", inputs: { data: "data", emit: "emit", rendered: "rendered" }, host: { attributes: { "data-testid": "node" }, properties: { "style.width.px": "this.width", "style.height.px": "this.height", "class.selected": "this.selected" } }, usesOnChanges: true, ngImport: i0, template: "<div class=\"title\" data-testid=\"title\">{{ data.label }}</div>\n<div\n class=\"output\"\n *ngFor=\"let output of data.outputs | keyvalue : sortByIndex\"\n [attr.data-testid]=\"'output-' + output.key\"\n>\n <div class=\"output-title\" data-testid=\"output-title\">\n {{ output.value?.label }}\n </div>\n <div\n class=\"output-socket\"\n refComponent\n [data]=\"{\n type: 'socket',\n side: 'output',\n key: output.key,\n nodeId: data.id,\n payload: output.value?.socket,\n seed: seed\n }\"\n [emit]=\"emit\"\n data-testid=\"output-socket\"\n ></div>\n</div>\n<div\n class=\"control\"\n *ngFor=\"let control of data.controls | keyvalue : sortByIndex\"\n refComponent\n [data]=\"{ type: 'control', payload: control.value }\"\n [emit]=\"emit\"\n [attr.data-testid]=\"'control-' + control.key\"\n></div>\n<div\n class=\"input\"\n *ngFor=\"let input of data.inputs | keyvalue : sortByIndex\"\n [attr.data-testid]=\"'input-' + input.key\"\n>\n <div\n class=\"input-socket\"\n refComponent\n [data]=\"{\n type: 'socket',\n side: 'input',\n key: input.key,\n nodeId: data.id,\n payload: input.value?.socket,\n seed: seed\n }\"\n [emit]=\"emit\"\n data-testid=\"input-socket\"\n ></div>\n <div\n class=\"input-title\"\n data-testid=\"input-title\"\n *ngIf=\"!input.value?.control || !input.value?.showControl\"\n >\n {{ input.value?.label }}\n </div>\n <div\n class=\"input-control\"\n [style.display]=\"\n input.value?.control && input.value?.showControl ? '' : 'none'\n \"\n refComponent\n [data]=\"{ type: 'control', payload: input.value?.control }\"\n [emit]=\"emit\"\n data-testid=\"input-control\"\n ></div>\n</div>\n", styles: [":host{display:block;background:#6e88ffcc;border:2px solid #4e58bf;border-radius:10px;cursor:pointer;box-sizing:border-box;width:180px;height:auto;padding-bottom:6px;position:relative;-webkit-user-select:none;user-select:none;line-height:initial;font-family:Arial}:host:hover{background:#8299ffcc}:host.selected{background:#ffd92c;border-color:#e3c000}:host .title{color:#fff;font-family:sans-serif;font-size:18px;padding:8px}:host .output{text-align:right}:host .input{text-align:left}:host .input-title,:host .output-title{vertical-align:middle;color:#fff;display:inline-block;font-family:sans-serif;font-size:14px;margin:6px;line-height:24px}:host .input-title[hidden],:host .output-title[hidden]{display:none}:host .output-socket{text-align:right;margin-right:-18px;display:inline-block}:host .input-socket{text-align:left;margin-left:-18px;display:inline-block}:host .input-control{z-index:1;width:calc(100% - 36px);vertical-align:middle;display:inline-block}:host .control{padding:6px 18px}\n"], dependencies: [{ kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: RefDirective, selector: "[refComponent]", inputs: ["data", "emit"] }, { kind: "pipe", type: i1.KeyValuePipe, name: "keyvalue" }] });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.4", ngImport: i0, type: NodeComponent, decorators: [{
type: Component,
args: [{ host: {
'data-testid': 'node'
}, template: "<div class=\"title\" data-testid=\"title\">{{ data.label }}</div>\n<div\n class=\"output\"\n *ngFor=\"let output of data.outputs | keyvalue : sortByIndex\"\n [attr.data-testid]=\"'output-' + output.key\"\n>\n <div class=\"output-title\" data-testid=\"output-title\">\n {{ output.value?.label }}\n </div>\n <div\n class=\"output-socket\"\n refComponent\n [data]=\"{\n type: 'socket',\n side: 'output',\n key: output.key,\n nodeId: data.id,\n payload: output.value?.socket,\n seed: seed\n }\"\n [emit]=\"emit\"\n data-testid=\"output-socket\"\n ></div>\n</div>\n<div\n class=\"control\"\n *ngFor=\"let control of data.controls | keyvalue : sortByIndex\"\n refComponent\n [data]=\"{ type: 'control', payload: control.value }\"\n [emit]=\"emit\"\n [attr.data-testid]=\"'control-' + control.key\"\n></div>\n<div\n class=\"input\"\n *ngFor=\"let input of data.inputs | keyvalue : sortByIndex\"\n [attr.data-testid]=\"'input-' + input.key\"\n>\n <div\n class=\"input-socket\"\n refComponent\n [data]=\"{\n type: 'socket',\n side: 'input',\n key: input.key,\n nodeId: data.id,\n payload: input.value?.socket,\n seed: seed\n }\"\n [emit]=\"emit\"\n data-testid=\"input-socket\"\n ></div>\n <div\n class=\"input-title\"\n data-testid=\"input-title\"\n *ngIf=\"!input.value?.control || !input.value?.showControl\"\n >\n {{ input.value?.label }}\n </div>\n <div\n class=\"input-control\"\n [style.display]=\"\n input.value?.control && input.value?.showControl ? '' : 'none'\n \"\n refComponent\n [data]=\"{ type: 'control', payload: input.value?.control }\"\n [emit]=\"emit\"\n data-testid=\"input-control\"\n ></div>\n</div>\n", styles: [":host{display:block;background:#6e88ffcc;border:2px solid #4e58bf;border-radius:10px;cursor:pointer;box-sizing:border-box;width:180px;height:auto;padding-bottom:6px;position:relative;-webkit-user-select:none;user-select:none;line-height:initial;font-family:Arial}:host:hover{background:#8299ffcc}:host.selected{background:#ffd92c;border-color:#e3c000}:host .title{color:#fff;font-family:sans-serif;font-size:18px;padding:8px}:host .output{text-align:right}:host .input{text-align:left}:host .input-title,:host .output-title{vertical-align:middle;color:#fff;display:inline-block;font-family:sans-serif;font-size:14px;margin:6px;line-height:24px}:host .input-title[hidden],:host .output-title[hidden]{display:none}:host .output-socket{text-align:right;margin-right:-18px;display:inline-block}:host .input-socket{text-align:left;margin-left:-18px;display:inline-block}:host .input-control{z-index:1;width:calc(100% - 36px);vertical-align:middle;display:inline-block}:host .control{padding:6px 18px}\n"] }]
}], ctorParameters: () => [{ type: i0.ChangeDetectorRef }], propDecorators: { data: [{
type: Input
}], emit: [{
type: Input
}], rendered: [{
type: Input
}], width: [{
type: HostBinding,
args: ['style.width.px']
}], height: [{
type: HostBinding,
args: ['style.height.px']
}], selected: [{
type: HostBinding,
args: ['class.selected']
}] } });
class SocketComponent {
cdr;
data;
side;
rendered;
get title() {
return this.data.name;
}
get color() {
return this.side === "input" ? "red" : "blue";
}
constructor(cdr) {
this.cdr = cdr;
this.cdr.detach();
}
ngOnChanges() {
this.cdr.detectChanges();
requestAnimationFrame(() => this.rendered());
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.4", ngImport: i0, type: SocketComponent, deps: [{ token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.4", type: SocketComponent, selector: "ng-component", inputs: { data: "data", side: "side", rendered: "rendered" }, host: { properties: { "title": "this.title", "style.background": "this.color" } }, usesOnChanges: true, ngImport: i0, template: ``, isInline: true, styles: [":host{display:inline-block;cursor:pointer;border:1px solid white;border-radius:12px;width:24px;height:24px;margin:6px;vertical-align:middle;z-index:2;box-sizing:border-box}:host:hover{border-width:4px}:host.multiple{border-color:#ff0}\n"] });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.4", ngImport: i0, type: SocketComponent, decorators: [{
type: Component,
args: [{ template: ``, styles: [":host{display:inline-block;cursor:pointer;border:1px solid white;border-radius:12px;width:24px;height:24px;margin:6px;vertical-align:middle;z-index:2;box-sizing:border-box}:host:hover{border-width:4px}:host.multiple{border-color:#ff0}\n"] }]
}], ctorParameters: () => [{ type: i0.ChangeDetectorRef }], propDecorators: { data: [{
type: Input
}], side: [{
type: Input
}], rendered: [{
type: Input
}], title: [{
type: HostBinding,
args: ["title"]
}], color: [{
type: HostBinding,
args: ["style.background"]
}] } });
class ControlComponent {
cdr;
data;
rendered;
pointerdown(event) {
event.stopPropagation();
}
constructor(cdr) {
this.cdr = cdr;
this.cdr.detach();
}
ngOnChanges(changes) {
const seed = changes['seed'];
const data = changes['data'];
if ((seed && seed.currentValue !== seed.previousValue)
|| (data && data.currentValue !== data.previousValue)) {
this.cdr.detectChanges();
}
requestAnimationFrame(() => this.rendered());
}
onChange(e) {
const target = e.target;
const value = (this.data.type === 'number'
? +target.value
: target.value);
this.data.setValue(value);
this.cdr.detectChanges();
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.4", ngImport: i0, type: ControlComponent, deps: [{ token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.4", type: ControlComponent, selector: "ng-component", inputs: { data: "data", rendered: "rendered" }, host: { listeners: { "pointerdown": "pointerdown($event)" } }, usesOnChanges: true, ngImport: i0, template: "<input\n [value]=\"data.value\"\n [readonly]=\"data.readonly\"\n [type]=\"data.type\"\n (input)=\"onChange($event)\"\n/>\n", styles: ["input{width:100%;border-radius:30px;background-color:#fff;padding:2px 6px;border:1px solid #999;font-size:110%;box-sizing:border-box}\n"] });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.4", ngImport: i0, type: ControlComponent, decorators: [{
type: Component,
args: [{ template: "<input\n [value]=\"data.value\"\n [readonly]=\"data.readonly\"\n [type]=\"data.type\"\n (input)=\"onChange($event)\"\n/>\n", styles: ["input{width:100%;border-radius:30px;background-color:#fff;padding:2px 6px;border:1px solid #999;font-size:110%;box-sizing:border-box}\n"] }]
}], ctorParameters: () => [{ type: i0.ChangeDetectorRef }], propDecorators: { data: [{
type: Input
}], rendered: [{
type: Input
}], pointerdown: [{
type: HostListener,
args: ['pointerdown', ['$event']]
}] } });
class ConnectionComponent {
data;
start;
end;
path;
set color(value) {
this._color = value;
}
set direction(value) {
this._pathClass =
value === "forward"
? "animated-path"
: value === "backward"
? "reverse-ants"
: "static-dash";
}
set value(value) {
this._value = value;
}
_value = 0;
_color = "rgba(188,202,210,0.3)";
_pathClass = "static-dash";
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.4", ngImport: i0, type: ConnectionComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.4", type: ConnectionComponent, selector: "connection", inputs: { data: "data", start: "start", end: "end", path: "path", color: "color", direction: "direction", value: "value" }, ngImport: i0, template: "<svg data-testid=\"connection\">\n <path\n [attr.d]=\"path\"\n [attr.stroke]=\"_color\"\n fill=\"none\"\n [ngClass]=\"_pathClass\"\n />\n</svg>\n", styles: ["svg{overflow:visible!important;position:absolute;pointer-events:none;width:9999px;height:9999px}svg path{fill:none;stroke-width:.15rem;pointer-events:auto}.animated-path{stroke-dasharray:20 10;stroke-dashoffset:0;animation:dashmove 1s linear infinite}.reverse-ants{stroke-dasharray:20 10;stroke-dashoffset:0;animation:dashmove-reverse 1s linear infinite}.static-dash{animation:none}@keyframes dashmove{to{stroke-dashoffset:-30}}@keyframes dashmove-reverse{to{stroke-dashoffset:30}}\n"], dependencies: [{ kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }] });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.4", ngImport: i0, type: ConnectionComponent, decorators: [{
type: Component,
args: [{ selector: "connection", template: "<svg data-testid=\"connection\">\n <path\n [attr.d]=\"path\"\n [attr.stroke]=\"_color\"\n fill=\"none\"\n [ngClass]=\"_pathClass\"\n />\n</svg>\n", styles: ["svg{overflow:visible!important;position:absolute;pointer-events:none;width:9999px;height:9999px}svg path{fill:none;stroke-width:.15rem;pointer-events:auto}.animated-path{stroke-dasharray:20 10;stroke-dashoffset:0;animation:dashmove 1s linear infinite}.reverse-ants{stroke-dasharray:20 10;stroke-dashoffset:0;animation:dashmove-reverse 1s linear infinite}.static-dash{animation:none}@keyframes dashmove{to{stroke-dashoffset:-30}}@keyframes dashmove-reverse{to{stroke-dashoffset:30}}\n"] }]
}], propDecorators: { data: [{
type: Input
}], start: [{
type: Input
}], end: [{
type: Input
}], path: [{
type: Input
}], color: [{
type: Input
}], direction: [{
type: Input
}], value: [{
type: Input
}] } });
class ConnectionWrapperComponent {
cdr;
viewContainerRef;
componentFactoryResolver;
data;
start;
end;
path;
rendered;
connectionComponent;
color;
ref;
startOb = null;
get _start() {
return "x" in this.start ? this.start : this.startOb;
}
endOb = null;
get _end() {
return "x" in this.end ? this.end : this.endOb;
}
_path;
constructor(cdr, viewContainerRef, componentFactoryResolver) {
this.cdr = cdr;
this.viewContainerRef = viewContainerRef;
this.componentFactoryResolver = componentFactoryResolver;
this.cdr.detach();
}
async ngOnChanges() {
await this.updatePath();
requestAnimationFrame(() => this.rendered());
this.cdr.detectChanges();
this.update();
}
async updatePath() {
if (this._start && this._end) {
this._path = await this.path(this._start, this._end);
}
}
ngOnInit() {
if (typeof this.start === "function") {
this.start(async (value) => {
this.startOb = value;
await this.updatePath();
this.cdr.detectChanges();
this.update();
});
}
if (typeof this.end === "function") {
this.end(async (value) => {
this.endOb = value;
await this.updatePath();
this.cdr.detectChanges();
this.update();
});
}
const componentFactory = this.componentFactoryResolver.resolveComponentFactory(this.connectionComponent);
this.viewContainerRef.clear();
this.ref = this.viewContainerRef.createComponent(componentFactory);
this.update();
}
update() {
this.ref.instance.data = this.data;
this.ref.instance.start = this._start;
this.ref.instance.end = this._end;
this.ref.instance.path = this._path;
this.ref.instance.color = this.data.color || "rgba(188,202,210,0.3)";
this.ref.instance.direction = this.data.direction || "static-dash";
this.ref.instance.value = this.data.value || 0;
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.4", ngImport: i0, type: ConnectionWrapperComponent, deps: [{ token: i0.ChangeDetectorRef }, { token: i0.ViewContainerRef }, { token: i0.ComponentFactoryResolver }], target: i0.ɵɵFactoryTarget.Component });
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.4", type: ConnectionWrapperComponent, selector: "ng-component", inputs: { data: "data", start: "start", end: "end", path: "path", rendered: "rendered", connectionComponent: "connectionComponent", color: "color" }, usesOnChanges: true, ngImport: i0, template: "", isInline: true });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.4", ngImport: i0, type: ConnectionWrapperComponent, decorators: [{
type: Component,
args: [{
template: "",
}]
}], ctorParameters: () => [{ type: i0.ChangeDetectorRef }, { type: i0.ViewContainerRef }, { type: i0.ComponentFactoryResolver }], propDecorators: { data: [{
type: Input
}], start: [{
type: Input
}], end: [{
type: Input
}], path: [{
type: Input
}], rendered: [{
type: Input
}], connectionComponent: [{
type: Input
}], color: [{
type: Input
}] } });
/**
* Classic preset for rendering nodes, connections, controls and sockets.
*/
function setup$3(props) {
const positionWatcher = typeof props?.socketPositionWatcher === "undefined"
? getDOMSocketPosition() // fix Type instantiation is excessively deep and possibly infinite.
: props?.socketPositionWatcher;
const { node, connection, socket, control } = props?.customize || {};
return {
attach(plugin) {
positionWatcher.attach(plugin);
},
update(context) {
const data = context.data.payload;
if (context.data.type === "connection") {
const { start, end } = context.data;
return {
data,
...(start ? { start } : {}),
...(end ? { end } : {}),
};
}
return { data };
},
updateNode(context, plugin) {
const parent = plugin.parentScope();
const emit = parent.emit.bind(parent);
const rendered = () => {
emit({ type: "rendered", data: context.data });
};
if (context.data.type === "node") {
const component = node ? node(context.data) : NodeComponent;
return {
key: `node-${context.data.payload.id}`,
component,
props: {
data: context.data.payload,
emit,
rendered,
},
};
}
return;
},
mount(context, plugin) {
const parent = plugin.parentScope();
const emit = parent.emit.bind(parent);
const rendered = () => {
emit({ type: "rendered", data: context.data });
};
if (context.data.type === "node") {
const component = node ? node(context.data) : NodeComponent;
return {
key: `node-${context.data.payload.id}`,
component,
props: {
data: context.data.payload,
emit,
rendered,
},
};
}
if (context.data.type === "connection") {
const component = connection
? connection(context.data)
: ConnectionComponent;
const id = context.data.payload.id;
const { sourceOutput, targetInput, source, target } = context.data.payload;
const { start, end, payload } = context.data;
return {
key: `connection-${id}`,
component: ConnectionWrapperComponent,
props: {
connectionComponent: component,
data: payload,
start: start ||
((change) => positionWatcher.listen(source, "output", sourceOutput, change)),
end: end ||
((change) => positionWatcher.listen(target, "input", targetInput, change)),
path: async (start, end) => {
const response = await plugin.emit({
type: "connectionpath",
data: { payload, points: [start, end] },
});
if (!response)
return "";
let { path, points } = response.data;
const curvature = 0.3;
if (payload.type === "straight")
path = buildStepStraightPath(start, end);
if (payload.type === "90deg") {
path = buildStep90DegPath(start, end, payload.sourceDirection, payload.targetDirection);
}
if (payload.type === "curve") {
path = buildCurvePath(start, end, payload.sourceDirection, payload.targetDirection, curvature, payload.curveDirection, payload.curvatureOffset);
}
if (!path && points.length !== 2)
throw new Error("cannot render connection with a custom number of points");
if (!path)
return payload.isLoop
? loopConnectionPath(points, curvature, 120)
: classicConnectionPath(points, curvature);
return path;
},
rendered,
},
};
}
if (context.data.type === "socket") {
const component = socket ? socket(context.data) : SocketComponent;
return {
key: `socket-${getUID()}`,
component,
props: {
data: context.data.payload,
side: context.data.side,
rendered,
},
};
}
if (context.data.type === "control") {
const component = control
? control(context.data)
: context.data.payload instanceof ClassicPreset.InputControl
? ControlComponent
: null;
if (component) {
return {
key: `control-${context.data.payload.id}`,
component,
props: {
data: context.data.payload,
rendered,
},
};
}
return;
}
return;
},
};
}
function buildStepStraightPath(start, end) {
const f = (n) => Math.round(n * 10) / 10;
const segments = [];
segments.push(`M${f(start.x)},${f(start.y)}`);
segments.push(`L${f(end.x)},${f(end.y)}`);
return segments.join("");
}
function buildStep90DegPath(start, end, sourceDirection, targetDirection) {
const preX = sourceDirection === "left"
? start.x - 50
: sourceDirection === "right"
? start.x + 50
: start.x;
const postX = targetDirection === "left"
? end.x - 50
: targetDirection === "right"
? end.x + 50
: end.x;
const preY = sourceDirection === "up"
? start.y - 50
: sourceDirection === "down"
? start.y + 50
: start.y;
const postY = targetDirection === "up"
? end.y - 50
: targetDirection === "down"
? end.y + 50
: end.y;
const midX = (preX + postX) / 2;
const midY = (preY + postY) / 2;
const f = (n) => Math.round(n * 10) / 10;
const segments = [];
segments.push(`M${f(start.x)},${f(start.y)}`);
segments.push(`L${f(preX)},${f(preY)}`);
{
segments.push(`L${f(midX)},${f(preY)}`);
segments.push(`L${f(midX)},${f(midY)}`);
segments.push(`L${f(postX)},${f(midY)}`);
segments.push(`L${f(postX)},${f(postY)}`);
}
segments.push(`L${f(postX)},${f(postY)}`);
segments.push(`L${f(end.x)},${f(end.y)}`);
return segments.join("");
}
function buildCurvePath(start, end, sourceDirection, targetDirection, curvature, curveDirection, curvatureOffset) {
const preX = start.x;
const postX = end.x;
const preY = start.y;
const postY = end.y;
const f = (n) => Math.round(n * 10) / 10;
const curv = typeof curvature === "number" ? curvature : 0.3;
const curvOffset = typeof curvatureOffset === "number" ? curvatureOffset : 3;
const midX = (preX + postX) / 2;
const midY = (preY + postY) / 2;
let ctrlX = midX;
let ctrlY = midY;
const remSize = parseFloat(getComputedStyle(document.documentElement).fontSize);
const dx = Math.abs(postX - preX);
const dy = Math.abs(postY - preY);
switch (curveDirection) {
case "right":
ctrlX = Math.max(preX, postX) + dx * curv + curvOffset * remSize;
break;
case "left":
ctrlX = Math.max(preX, postX) + dx * curv - curvOffset * remSize;
break;
case "up":
ctrlY = Math.max(preY, postY) + dy * curv - curvOffset * remSize;
break;
case "down":
ctrlY = Math.max(preY, postY) + dy * curv + curvOffset * remSize;
break;
}
// Quadratic Bezier for smooth parenthesis-like curve
return `M${f(preX)} ${f(preY)} Q ${f(ctrlX)} ${f(ctrlY)} ${f(postX)} ${f(postY)} `;
}
var index$4 = /*#__PURE__*/Object.freeze({
__proto__: null,
buildCurvePath: buildCurvePath,
buildStep90DegPath: buildStep90DegPath,
buildStepStraightPath: buildStepStraightPath,
setup: setup$3
});
function debounce(cb) {
return {
timeout: null,
cancel() {
if (this.timeout) {
window.clearTimeout(this.timeout);
this.timeout = null;
}
},
call(delay) {
this.timeout = window.setTimeout(() => {
cb();
}, delay);
}
};
}
class ContextMenuSearchComponent {
value;
update = new EventEmitter();
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.4", ngImport: i0, type: ContextMenuSearchComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.4", type: ContextMenuSearchComponent, selector: "context-menu-search", inputs: { value: "value" }, outputs: { update: "update" }, ngImport: i0, template: "<input class=\"search\" [value]=\"value\" (input)=\"update.emit($any($event.target)?.value || '')\"\n data-testid=\"context-menu-search-input\" />\n", styles: [".search{color:#fff;padding:1px 8px;border:1px solid white;border-radius:10px;font-size:16px;font-family:serif;width:100%;box-sizing:border-box;background:transparent}\n"] });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.4", ngImport: i0, type: ContextMenuSearchComponent, decorators: [{
type: Component,
args: [{ selector: 'context-menu-search', template: "<input class=\"search\" [value]=\"value\" (input)=\"update.emit($any($event.target)?.value || '')\"\n data-testid=\"context-menu-search-input\" />\n", styles: [".search{color:#fff;padding:1px 8px;border:1px solid white;border-radius:10px;font-size:16px;font-family:serif;width:100%;box-sizing:border-box;background:transparent}\n"] }]
}], propDecorators: { value: [{
type: Input
}], update: [{
type: Output
}] } });
// [imports]
class ContextMenuItemComponent {
cdr;
subitems;
delay;
select = new EventEmitter();
hide = new EventEmitter();
get block() { return true; }
get hasSubitems() { return this.subitems; }
click(event) {
event.stopPropagation();
this.select.emit();
this.hide.emit();
}
pointerdown(event) {
event.stopPropagation();
}
wheel(event) {
event.stopPropagation();
}
hideSubitems = debounce(() => {
this.visibleSubitems = false;
this.cdr.detectChanges();
});
visibleSubitems = false;
pointerover() {
this.hideSubitems.cancel();
this.visibleSubitems = true;
this.cdr.detectChanges();
}
pointerleave() {
this.hideSubitems.call(this.delay);
this.cdr.detectChanges();
}
constructor(cdr) {
this.cdr = cdr;
this.cdr.detach();
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.4", ngImport: i0, type: ContextMenuItemComponent, deps: [{ token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.4", type: ContextMenuItemComponent, selector: "context-menu-item", inputs: { subitems: "subitems", delay: "delay" }, outputs: { select: "select", hide: "hide" }, host: { attributes: { "data-testid": "context-menu-item" }, listeners: { "click": "click($event)", "pointerdown": "pointerdown($event)", "wheel": "wheel($event)", "pointerover": "pointerover()", "pointerleave": "pointerleave()" }, properties: { "class.block": "this.block", "class.hasSubitems": "this.hasSubitems" } }, ngImport: i0, template: "<ng-content></ng-content>\n<div class=\"subitems\" *ngIf=\"subitems && visibleSubitems\">\n <context-menu-item\n *ngFor=\"let item of subitems\"\n [delay]=\"delay\"\n (select)=\"item.handler()\"\n (hide)=\"hide.emit()\"\n >\n {{ item.label }}\n </context-menu-item>\n</div>\n", styles: ["@charset \"UTF-8\";:host(.hasSubitems):after{content:\"\\25ba\";position:absolute;opacity:.6;right:5px;top:5px;font-family:initial}.subitems{position:absolute;top:0;left:100%;width:120px}\n", ".block{display:block;color:#fff;padding:4px;border-bottom:1px solid rgba(69,103,255,.8);background-color:#6e88ffcc;cursor:pointer;width:100%;position:relative}.block:first-child{border-top-left-radius:5px;border-top-right-radius:5px}.block:last-child{border-bottom-left-radius:5px;border-bottom-right-radius:5px}.block:hover{background-color:#8299ffcc}\n"], dependencies: [{ kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: ContextMenuItemComponent, selector: "context-menu-item", inputs: ["subitems", "delay"], outputs: ["select", "hide"] }] });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.4", ngImport: i0, type: ContextMenuItemComponent, decorators: [{
type: Component,
args: [{ selector: 'context-menu-item', host: {
'data-testid': 'context-menu-item'
}, template: "<ng-content></ng-content>\n<div class=\"subitems\" *ngIf=\"subitems && visibleSubitems\">\n <context-menu-item\n *ngFor=\"let item of subitems\"\n [delay]=\"delay\"\n (select)=\"item.handler()\"\n (hide)=\"hide.emit()\"\n >\n {{ item.label }}\n </context-menu-item>\n</div>\n", styles: ["@charset \"UTF-8\";:host(.hasSubitems):after{content:\"\\25ba\";position:absolute;opacity:.6;right:5px;top:5px;font-family:initial}.subitems{position:absolute;top:0;left:100%;width:120px}\n", ".block{display:block;color:#fff;padding:4px;border-bottom:1px solid rgba(69,103,255,.8);background-color:#6e88ffcc;cursor:pointer;width:100%;position:relative}.block:first-child{border-top-left-radius:5px;border-top-right-radius:5px}.block:last-child{border-bottom-left-radius:5px;border-bottom-right-radius:5px}.block:hover{background-color:#8299ffcc}\n"] }]
}], ctorParameters: () => [{ type: i0.ChangeDetectorRef }], propDecorators: { subitems: [{
type: Input
}], delay: [{
type: Input
}], select: [{
type: Output
}], hide: [{
type: Output
}], block: [{
type: HostBinding,
args: ['class.block']
}], hasSubitems: [{
type: HostBinding,
args: ['class.hasSubitems']
}], click: [{
type: HostListener,
args: ['click', ['$event']]
}], pointerdown: [{
type: HostListener,
args: ['pointerdown', ['$event']]
}], wheel: [{
type: HostListener,
args: ['wheel', ['$event']]
}], pointerover: [{
type: HostListener,
args: ['pointerover']
}], pointerleave: [{
type: HostListener,
args: ['pointerleave']
}] } });
// [imports]
class ContextMenuComponent {
cdr;
items;
delay;
searchBar;
onHide;
rendered;
filter = '';
hide = debounce(() => {
this.onHide();
this.cdr.detectChanges();
});
customAttribute = '';
pointerover() {
this.hide.cancel();
this.cdr.detectChanges();
}
pointerleave() {
this.hide.call(this.delay);
this.cdr.detectChanges();
}
constructor(cdr) {
this.cdr = cdr;
this.cdr.detach();
}
ngOnChanges() {
this.cdr.detectChanges();
requestAnimationFrame(() => this.rendered());
}
setFilter(value) {
this.filter = value;
this.cdr.detectChanges();
}
getItems() {
const filterRegexp = new RegExp(this.filter, 'i');
const filteredList = this.items.filter(item => (item.label.match(filterRegexp)));
return filteredList;
}
ngOnDestroy() {
if (this.hide)
this.hide.cancel();
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.4", ngImport: i0, type: ContextMenuComponent, deps: [{ token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.4", type: ContextMenuComponent, selector: "ng-component", inputs: { items: "items", delay: "delay", searchBar: "searchBar", onHide: "onHide", rendered: "rendered" }, host: { attributes: { "data-testid": "context-menu" }, listeners: { "mouseover": "pointerover()", "mouseleave": "pointerleave()" }, properties: { "attr.rete-context-menu": "this.customAttribute" } }, usesOnChanges: true, ngImport: i0, template: "<div class=\"block\" *ngIf=\"searchBar\">\n <context-menu-search\n [value]=\"filter\"\n (update)=\"setFilter($event)\"\n ></context-menu-search>\n</div>\n\n<context-menu-item\n *ngFor=\"let item of getItems()\"\n [delay]=\"delay\"\n (select)=\"item.handler()\"\n [subitems]=\"item.subitems\"\n (hide)=\"onHide()\"\n>\n {{ item.label }}\n</context-menu-item>\n", styles: [":host{display:block;padding:10px;width:120px;margin-top:-20px;margin-left:-60px}\n", ".block{display:block;color:#fff;padding:4px;border-bottom:1px solid rgba(69,103,255,.8);background-color:#6e88ffcc;cursor:pointer;width:100%;position:relative}.block:first-child{border-top-left-radius:5px;border-top-right-radius:5px}.block:last-child{border-bottom-left-radius:5px;border-bottom-right-radius:5px}.block:hover{background-color:#8299ffcc}\n"], dependencies: [{ kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: ContextMenuSearchComponent, selector: "context-menu-search", inputs: ["value"], outputs: ["update"] }, { kind: "component", type: ContextMenuItemComponent, selector: "context-menu-item", inputs: ["subitems", "delay"], outputs: ["select", "hide"] }] });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.4", ngImport: i0, type: ContextMenuComponent, decorators: [{
type: Component,
args: [{ host: {
'data-testid': 'context-menu'
}, template: "<div class=\"block\" *ngIf=\"searchBar\">\n <context-menu-search\n [value]=\"filter\"\n (update)=\"setFilter($event)\"\n ></context-menu-search>\n</div>\n\n<context-menu-item\n *ngFor=\"let item of getItems()\"\n [delay]=\"delay\"\n (select)=\"item.handler()\"\n [subitems]=\"item.subitems\"\n (hide)=\"onHide()\"\n>\n {{ item.label }}\n</context-menu-item>\n", styles: [":host{display:block;padding:10px;width:120px;margin-top:-20px;margin-left:-60px}\n", ".block{display:block;color:#fff;padding:4px;border-bottom:1px solid rgba(69,103,255,.8);background-color:#6e88ffcc;cursor:pointer;width:100%;position:relative}.block:first-child{border-top-left-radius:5px;border-top-right-radius:5px}.block:last-child{border-bottom-left-radius:5px;border-bottom-right-radius:5px}.block:hover{background-color:#8299ffcc}\n"] }]
}], ctorParameters: () => [{ type: i0.ChangeDetectorRef }], propDecorators: { items: [{
type: Input
}], delay: [{
type: Input
}], searchBar: [{
type: Input
}], onHide: [{
type: Input
}], rendered: [{
type: Input
}], customAttribute: [{
type: HostBinding,
args: ['attr.rete-context-menu']
}], pointerover: [{
type: HostListener,
args: ['mouseover']
}], pointerleave: [{
type: HostListener,
args: ['mouseleave']
}] } });
/**
* Preset for rendering context menu.
*/
function setup$2(props) {
const delay = typeof props?.delay === "undefined" ? 1000 : props.delay;
return {
update(context) {
if (context.data.type === "contextmenu") {
return {
items: context.data.items,
delay,
searchBar: context.data.searchBar,
onHide: context.data.onHide,
};
}
return null;
},
mount(context, plugin) {
const parent = plugin.parentScope();
const emit = parent.emit.bind(parent);
const rendered = () => {
emit({ type: "rendered", data: context.data });
};
if (context.data.type === "contextmenu") {
return {
key: "context-menu",
component: ContextMenuComponent,
props: {
items: context.data.items,
delay,
searchBar: context.data.searchBar,
onHide: context.data.onHide,
rendered,
},
};
}
return null;
},
};
}
var index$3 = /*#__PURE__*/Object.freeze({
__proto__: null,
setup: setup$2
});
function useDrag(translate, getPointer) {
return {
start(e) {
let previous = { ...getPointer(e) };
function move(moveEvent) {
const current = { ...getPointer(moveEvent) };
const dx = current.x - previous.x;
const dy = current.y - previous.y;
previous = current;
translate(dx, dy);
}
function up() {
window.removeEventListener('pointermove', move);
window.removeEventListener('pointerup', up);
window.removeEventListener('pointercancel', up);
}
window.addEventListener('pointermove', move);
window.addEventListener('pointerup', up);
window.addEventListener('pointercancel', up);
}
};
}
class MiniViewportComponent {
left;
top;
width;
height;
containerWidth;
translate;
drag = useDrag((dx, dy) => this.onDrag(dx, dy), e => ({ x: e.pageX, y: e.pageY }));
get styleLeft() {
return this.px(this.scale(this.left));
}
get styleTop() {
return this.px(this.scale(this.top));
}
get styleWidth() {
return this.px(this.scale(this.width));
}
get styleHeight() {
return this.px(this.scale(this.height));
}
pointerdown(event) {
event.stopPropagation();
this.drag.start(event);
}
px(value) {
return `${value}px`;
}
scale(v) {
return v * this.containerWidth;
}
invert(v) {
return v / this.containerWidth;
}
onDrag(dx, dy) {
this.translate(this.invert(-dx), this.invert(-dy));
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.4", ngImport: i0, type: MiniViewportComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.4", type: MiniViewportComponent, selector: "minimap-mini-viewport", inputs: { left: "left", top: "top", width: "width", height: "height", containerWidth: "containerWidth", translate: "translate" }, host: { attributes: { "data-testid": "minimap-viewport" }, listeners: { "pointerdown": "pointerdown($event)" }, properties: { "style.left": "this.styleLeft", "style.top": "this.styleTop", "style.width": "this.styleWidth", "style.height": "this.styleHeight" } }, ngImport: i0, template: "", styles: [":host{display:block;position:absolute;background:#fffb8052;border:1px solid #ffe52b}\n"] });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.4", ngImport: i0, type: MiniViewportComponent, decorators: [{
type: Component,
args: [{ selector: 'minimap-mini-viewport', host: {
'data-testid': 'minimap-viewport'
}, template: "", styles: [":host{display:block;position:absolute;background:#fffb8052;border:1px solid #ffe52b}\n"] }]
}], propDecorators: { left: [{
type: Input
}], top: [{
type: Input
}], width: [{
type: Input
}], height: [{
type: Input
}], containerWidth: [{
type: Input
}], translate: [{
type: Input
}], styleLeft: [{
type: HostBinding,
args: ['style.left']
}], styleTop: [{
type: HostBinding,
args: ['style.top']
}], styleWidth: [{
type: HostBinding,
args: ['style.width']
}], styleHeight: [{
type: HostBinding,
args: ['style.height']
}], pointerdown: [{
type: HostListener,
args: ['pointerdown', ['$event']]
}] } });
class MiniNodeComponent {
left;
top;
width;
height;
get styleLeft() {
return this.px(this.left);
}
get styleTop() {
return this.px(this.top);
}
get styleWidth() {
return this.px(this.width);
}
get styleHeight() {
return this.px(this.height);
}
px(value) {
return `${value}px`;
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.4", ngImport: i0, type: MiniNodeComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.4", type: MiniNodeComponent, selector: "minimap-mini-node", inputs: { left: "left", top: "top", width: "width", height: "height" }, host: { attributes: { "data-testid": "minimap-node" }, properties: { "style.left": "this.styleLeft", "style.top": "this.styleTop", "style.width": "this.styleWidth", "style.height": "this.styleHeight" } }, ngImport: i0, template: "", styles: [":host{display:block;position:absolute;background:#6e88ffcc;border:1px solid rgba(192,206,212,.6)}\n"] });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.4", ngImport: i0, type: MiniNodeComponent, decorators: [{
type: Component,
args: [{ selector: 'minimap-mini-node', host: {
'data-testid': 'minimap-node'
}, template: "", styles: [":host{display:block;position:absolute;background:#6e88ffcc;border:1px solid rgba(192,206,212,.6)}\n"] }]
}], propDecorators: { left: [{
type: Input
}], top: [{
type: Input
}], width: [{
type: Input
}], height: [{
type: Input
}], styleLeft: [{
type: HostBinding,
args: ['style.left']
}], styleTop: [{
type: HostBinding,
args: ['style.top']
}], styleWidth: [{
type: HostBinding,
args: ['style.width']
}], styleHeight: [{
type: HostBinding,
args: ['style.height']
}] } });
// [imports]
class MinimapComponent {
el;
cdr;
rendered;
size;
ratio;
nodes;
viewport;
translate;
point;
get width() {
return this.px(this.size * this.ratio);
}
get height() {
return this.px(this.size);
}
pointerdown(event) {
event.stopPropagation();
event.preventDefault();
}
dblclick(event) {
event.stopPropagation();
event.preventDefault();
if (!this.el.nativeElement)
return;
const box = this.el.nativeElement.getBoundingClientRect();
const x = (event.c