rete-angular-jellytech-plugin
Version:
Angular Jellytech ==== Plugin for displaying SCADA and overview elements
1 lines • 120 kB
Source Map (JSON)
{"version":3,"file":"rete-angular-jellytech-plugin.mjs","sources":["../../src/ref.ts","../../src/presets/classic/components/node/node.component.ts","../../src/presets/classic/components/node/node.component.html","../../src/presets/classic/components/socket/socket.component.ts","../../src/presets/classic/components/control/control.component.ts","../../src/presets/classic/components/control/control.component.html","../../src/presets/classic/components/connection/connection.component.ts","../../src/presets/classic/components/connection/connection.component.html","../../src/presets/classic/components/connection/connection-wrapper.component.ts","../../src/presets/classic/index.ts","../../src/presets/context-menu/debounce.ts","../../src/presets/context-menu/components/search/search.component.ts","../../src/presets/context-menu/components/search/search.component.html","../../src/presets/context-menu/components/item/item.component.ts","../../src/presets/context-menu/components/item/item.component.html","../../src/presets/context-menu/components/menu/menu.component.ts","../../src/presets/context-menu/components/menu/menu.component.html","../../src/presets/context-menu/index.ts","../../src/shared/drag.ts","../../src/presets/minimap/components/mini-viewport/mini-viewport.component.ts","../../src/presets/minimap/components/mini-viewport/mini-viewport.component.html","../../src/presets/minimap/components/mini-node/mini-node.component.ts","../../src/presets/minimap/components/mini-node/mini-node.component.html","../../src/presets/minimap/components/minimap/minimap.component.ts","../../src/presets/minimap/components/minimap/minimap.component.html","../../src/presets/minimap/index.ts","../../src/presets/reroute/components/pin/pin.component.ts","../../src/presets/reroute/components/pins/pins.component.ts","../../src/presets/reroute/components/pins/pins.component.html","../../src/presets/reroute/index.ts","../../src/presets/index.ts","../../src/presets/classic/components/scada-node/scada-node.component.ts","../../src/presets/classic/components/scada-node/scada-node.component.html","../../src/presets/classic/components/scada-overview-node/scada-overview-node.component.ts","../../src/presets/classic/components/scada-overview-node/scada-overview-node.component.html","../../src/module.ts","../../src/presets/context-menu/module.ts","../../src/presets/minimap/module.ts","../../src/presets/reroute/module.ts","../../src/presets/classic/components/scada-socket/scada-socket.ts","../../src/presets/classic/components/scada-connection/scada-connection.ts","../../src/presets/classic/components/scada-overview-socket/scada-overview-socket.component.ts","../../src/reflect.ts","../../src/core.ts","../../src/rete-angular-jellytech-plugin.ts"],"sourcesContent":["import { Input, ElementRef, OnChanges, OnDestroy } from '@angular/core';\nimport { Directive } from '@angular/core';\n\n@Directive({\n selector: '[refComponent]'\n})\nexport class RefDirective implements OnChanges, OnDestroy {\n @Input() data!: any\n @Input() emit!: any\n\n constructor(private el: ElementRef) { }\n\n ngOnChanges() {\n this.emit({ type: 'render', data: { ...this.data, element: this.el.nativeElement } })\n }\n\n ngOnDestroy() {\n this.emit({ type: 'unmount', data: { element: this.el.nativeElement } })\n }\n}\n","import { Component, Input, HostBinding, ChangeDetectorRef, OnChanges } from '@angular/core';\nimport { ClassicPreset as Classic } from 'rete';\nimport { KeyValue } from '@angular/common';\n// [imports]\n\ntype NodeExtraData = { width?: number, height?: number }\ntype SortValue<N extends Classic.Node> = (N['controls'] | N['inputs'] | N['outputs'])[string]\n\n@Component({\n // [component-directive]\n templateUrl: './node.component.html',\n styleUrls: ['./node.component.sass'],\n host: {\n 'data-testid': 'node'\n }\n})\nexport class NodeComponent implements OnChanges {\n @Input() data!: Classic.Node & NodeExtraData;\n @Input() emit!: (data: any) => void\n @Input() rendered!: () => void\n\n seed = 0\n\n @HostBinding('style.width.px') get width() {\n return this.data.width\n }\n\n @HostBinding('style.height.px') get height() {\n return this.data.height\n }\n\n @HostBinding('class.selected') get selected() {\n return this.data.selected\n }\n\n constructor(private cdr: ChangeDetectorRef) {\n this.cdr.detach()\n }\n\n ngOnChanges(): void {\n this.cdr.detectChanges()\n requestAnimationFrame(() => this.rendered())\n this.seed++ // force render sockets\n }\n\n sortByIndex<N extends Classic.Node, I extends KeyValue<string, SortValue<N>>>(a: I, b: I) {\n const ai = a.value?.index || 0\n const bi = b.value?.index || 0\n\n return ai - bi\n }\n}\n","<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","import {\n Component,\n Input,\n HostBinding,\n AfterViewChecked,\n ChangeDetectorRef,\n OnChanges,\n} from \"@angular/core\";\n\n@Component({\n template: ``,\n styleUrls: [\"./socket.component.scss\"],\n})\nexport class SocketComponent implements OnChanges {\n @Input() data!: any;\n @Input() side!: any;\n @Input() rendered!: any;\n\n @HostBinding(\"title\") get title() {\n return this.data.name;\n }\n\n @HostBinding(\"style.background\") get color() {\n return this.side === \"input\" ? \"red\" : \"blue\";\n }\n\n constructor(private cdr: ChangeDetectorRef) {\n this.cdr.detach();\n }\n\n ngOnChanges(): void {\n this.cdr.detectChanges();\n requestAnimationFrame(() => this.rendered());\n }\n}\n","import { Component, Input, OnChanges, SimpleChanges, ChangeDetectorRef, HostListener } from '@angular/core';\nimport { ClassicPreset } from 'rete';\n\n@Component({\n templateUrl: `./control.component.html`,\n styleUrls: ['./control.component.sass']\n})\nexport class ControlComponent<T extends 'text' | 'number'> implements OnChanges {\n @Input() data!: ClassicPreset.InputControl<T>;\n @Input() rendered!: any;\n\n\n @HostListener('pointerdown', ['$event'])\n public pointerdown(event: any) {\n event.stopPropagation();\n }\n\n constructor(private cdr: ChangeDetectorRef) {\n this.cdr.detach()\n }\n\n ngOnChanges(changes: SimpleChanges): void {\n const seed = changes['seed']\n const data = changes['data']\n\n if ((seed && seed.currentValue !== seed.previousValue)\n || (data && data.currentValue !== data.previousValue)) {\n this.cdr.detectChanges()\n }\n requestAnimationFrame(() => this.rendered())\n }\n\n onChange(e: Event) {\n const target = e.target as HTMLInputElement\n const value = (this.data.type === 'number'\n ? +target.value\n : target.value) as ClassicPreset.InputControl<T>['value']\n\n this.data.setValue(value)\n this.cdr.detectChanges()\n }\n}\n","<input\n [value]=\"data.value\"\n [readonly]=\"data.readonly\"\n [type]=\"data.type\"\n (input)=\"onChange($event)\"\n/>\n","import { Component, Input } from \"@angular/core\";\nimport { ClassicPreset } from \"rete\";\nimport { Position } from \"../../../../types\";\n\n@Component({\n selector: \"connection\",\n templateUrl: \"./connection.component.html\",\n styleUrls: [\"./connection.component.scss\"],\n})\nexport class ConnectionComponent {\n @Input() data!: ClassicPreset.Connection<\n ClassicPreset.Node,\n ClassicPreset.Node\n >;\n @Input() start!: Position;\n @Input() end!: Position;\n @Input() path!: string;\n @Input() set color(value: string) {\n this._color = value;\n }\n @Input() set direction(value: string) {\n this._pathClass =\n value === \"forward\"\n ? \"animated-path\"\n : value === \"backward\"\n ? \"reverse-ants\"\n : \"static-dash\";\n }\n @Input() set value(value: number) {\n this._value = value;\n }\n\n _value: number = 0;\n _color: string = \"rgba(188,202,210,0.3)\";\n _pathClass: \"animated-path\" | \"reverse-ants\" | \"static-dash\" = \"static-dash\";\n}\n","<svg data-testid=\"connection\">\n <path\n [attr.d]=\"path\"\n [attr.stroke]=\"_color\"\n fill=\"none\"\n [ngClass]=\"_pathClass\"\n />\n</svg>\n","import {\n Component,\n Input,\n OnInit,\n ChangeDetectorRef,\n OnChanges,\n ViewContainerRef,\n ComponentFactoryResolver,\n ComponentRef,\n} from \"@angular/core\";\nimport { ClassicPreset } from \"rete\";\nimport { Position } from \"../../../../types\";\n\ntype PositionWatcher = (cb: (value: Position) => void) => () => void;\n\n@Component({\n template: \"\",\n})\nexport class ConnectionWrapperComponent implements OnInit, OnChanges {\n @Input() data!: ClassicPreset.Connection<\n ClassicPreset.Node,\n ClassicPreset.Node\n > & {\n color?: string;\n direction?: string;\n value?: number;\n };\n @Input() start!: Position | PositionWatcher;\n @Input() end!: Position | PositionWatcher;\n @Input() path!: (start: Position, end: Position) => Promise<string>;\n @Input() rendered!: any;\n @Input() connectionComponent!: any;\n @Input() color!: string;\n ref!: ComponentRef<any>;\n\n startOb: Position | null = null;\n get _start(): Position | null {\n return \"x\" in this.start ? this.start : this.startOb;\n }\n endOb: Position | null = null;\n get _end(): Position | null {\n return \"x\" in this.end ? this.end : this.endOb;\n }\n _path!: string;\n\n constructor(\n private cdr: ChangeDetectorRef,\n public viewContainerRef: ViewContainerRef,\n private componentFactoryResolver: ComponentFactoryResolver\n ) {\n this.cdr.detach();\n }\n\n async ngOnChanges(): Promise<void> {\n await this.updatePath();\n requestAnimationFrame(() => this.rendered());\n this.cdr.detectChanges();\n this.update();\n }\n\n async updatePath() {\n if (this._start && this._end) {\n this._path = await this.path(this._start, this._end);\n }\n }\n\n ngOnInit() {\n if (typeof this.start === \"function\") {\n this.start(async (value) => {\n this.startOb = value;\n await this.updatePath();\n this.cdr.detectChanges();\n this.update();\n });\n }\n if (typeof this.end === \"function\") {\n this.end(async (value) => {\n this.endOb = value;\n await this.updatePath();\n this.cdr.detectChanges();\n this.update();\n });\n }\n const componentFactory =\n this.componentFactoryResolver.resolveComponentFactory(\n this.connectionComponent\n );\n this.viewContainerRef.clear();\n\n this.ref = this.viewContainerRef.createComponent(componentFactory);\n this.update();\n }\n\n update() {\n this.ref.instance.data = this.data;\n this.ref.instance.start = this._start;\n this.ref.instance.end = this._end;\n this.ref.instance.path = this._path;\n this.ref.instance.color = this.data.color || \"rgba(188,202,210,0.3)\";\n this.ref.instance.direction = this.data.direction || \"static-dash\";\n this.ref.instance.value = this.data.value || 0;\n }\n}\n","import { Type } from \"@angular/core\";\nimport { BaseSchemes, ClassicPreset, getUID, Scope } from \"rete\";\nimport {\n classicConnectionPath,\n loopConnectionPath,\n SocketPositionWatcher,\n getDOMSocketPosition,\n getElementCenter,\n BaseSocketPosition,\n} from \"rete-render-utils\";\nimport {\n AngularArea2D,\n ClassicScheme,\n ConnectionTypes,\n ExtractPayload,\n Side,\n} from \"./types\";\nimport { NodeComponent } from \"./components/node/node.component\";\nimport { SocketComponent } from \"./components/socket/socket.component\";\nimport { ControlComponent } from \"./components/control/control.component\";\nimport { ConnectionComponent } from \"./components/connection/connection.component\";\nimport { ConnectionWrapperComponent } from \"./components/connection/connection-wrapper.component\";\nimport { Position } from \"../../types\";\nimport { RenderPreset } from \"../types\";\nimport { Props } from \"rete-render-utils/_types/sockets-position/dom-socket-position\";\n\ntype AngularComponent = Type<any>;\ntype CustomizationProps<Schemes extends ClassicScheme> = {\n node?: (data: ExtractPayload<Schemes, \"node\">) => AngularComponent | null;\n connection?: (\n data: ExtractPayload<Schemes, \"connection\">\n ) => AngularComponent | null;\n socket?: (data: ExtractPayload<Schemes, \"socket\">) => AngularComponent | null;\n control?: (\n data: ExtractPayload<Schemes, \"control\">\n ) => AngularComponent | null;\n};\n\ntype ClassicProps<Schemes extends ClassicScheme, K> = {\n socketPositionWatcher?: SocketPositionWatcher<Scope<never, [K]>>;\n customize?: CustomizationProps<Schemes>;\n};\n\n/**\n * Classic preset for rendering nodes, connections, controls and sockets.\n */\nexport function setup<\n Schemes extends ClassicScheme,\n K extends AngularArea2D<Schemes>\n>(props?: ClassicProps<Schemes, K>): RenderPreset<Schemes, K> {\n const positionWatcher =\n typeof props?.socketPositionWatcher === \"undefined\"\n ? getDOMSocketPosition<Schemes, any>() // fix Type instantiation is excessively deep and possibly infinite.\n : props?.socketPositionWatcher;\n const { node, connection, socket, control } = props?.customize || {};\n\n return {\n attach(plugin) {\n positionWatcher.attach(plugin as unknown as Scope<never, [K]>);\n },\n update(context) {\n const data = context.data.payload;\n\n if (context.data.type === \"connection\") {\n const { start, end } = context.data;\n\n return {\n data,\n ...(start ? { start } : {}),\n ...(end ? { end } : {}),\n };\n }\n return { data };\n },\n updateNode(context, plugin) {\n const parent = plugin.parentScope();\n const emit = parent.emit.bind(parent);\n const rendered = () => {\n emit({ type: \"rendered\", data: context.data } as any);\n };\n\n if (context.data.type === \"node\") {\n const component = node ? node(context.data) : NodeComponent;\n\n return {\n key: `node-${context.data.payload.id}`,\n component,\n props: {\n data: context.data.payload,\n emit,\n rendered,\n },\n };\n }\n return;\n },\n mount(context, plugin) {\n const parent = plugin.parentScope();\n const emit = parent.emit.bind(parent);\n const rendered = () => {\n emit({ type: \"rendered\", data: context.data } as any);\n };\n\n if (context.data.type === \"node\") {\n const component = node ? node(context.data) : NodeComponent;\n\n return {\n key: `node-${context.data.payload.id}`,\n component,\n props: {\n data: context.data.payload,\n emit,\n rendered,\n },\n };\n }\n if (context.data.type === \"connection\") {\n const component = connection\n ? connection(context.data)\n : ConnectionComponent;\n const id = context.data.payload.id;\n const { sourceOutput, targetInput, source, target } =\n context.data.payload;\n const { start, end, payload } = context.data;\n\n return {\n key: `connection-${id}`,\n component: ConnectionWrapperComponent,\n props: {\n connectionComponent: component,\n data: payload,\n start:\n start ||\n ((change: any) =>\n positionWatcher.listen(source, \"output\", sourceOutput, change)),\n end:\n end ||\n ((change: any) =>\n positionWatcher.listen(target, \"input\", targetInput, change)),\n path: async (start: Position, end: Position) => {\n const response = await plugin.emit({\n type: \"connectionpath\",\n data: { payload, points: [start, end] },\n });\n\n if (!response) return \"\";\n\n let { path, points } = response.data;\n\n const curvature = 0.3;\n\n if (payload.type === \"straight\")\n path = buildStepStraightPath(start, end);\n\n if (payload.type === \"90deg\") {\n path = buildStep90DegPath(\n start,\n end,\n payload.sourceDirection,\n payload.targetDirection\n );\n }\n\n if (payload.type === \"curve\") {\n path = buildCurvePath(\n start,\n end,\n payload.sourceDirection,\n payload.targetDirection,\n curvature,\n payload.curveDirection,\n payload.curvatureOffset\n );\n }\n\n if (!path && points.length !== 2)\n throw new Error(\n \"cannot render connection with a custom number of points\"\n );\n if (!path)\n return payload.isLoop\n ? loopConnectionPath(\n points as [Position, Position],\n curvature,\n 120\n )\n : classicConnectionPath(\n points as [Position, Position],\n curvature\n );\n\n return path;\n },\n rendered,\n },\n };\n }\n if (context.data.type === \"socket\") {\n const component = socket ? socket(context.data) : SocketComponent;\n return {\n key: `socket-${getUID()}`,\n component,\n props: {\n data: context.data.payload,\n side: context.data.side,\n rendered,\n },\n };\n }\n if (context.data.type === \"control\") {\n const component = control\n ? control(context.data)\n : context.data.payload instanceof ClassicPreset.InputControl\n ? ControlComponent\n : null;\n\n if (component) {\n return {\n key: `control-${context.data.payload.id}`,\n component,\n props: {\n data: context.data.payload,\n rendered,\n },\n };\n }\n return;\n }\n return;\n },\n };\n}\n\nexport function buildStepStraightPath(start: Position, end: Position): string {\n const f = (n: number) => Math.round(n * 10) / 10;\n\n const segments: string[] = [];\n segments.push(`M${f(start.x)},${f(start.y)}`);\n segments.push(`L${f(end.x)},${f(end.y)}`);\n\n return segments.join(\"\");\n}\n\nexport function buildStep90DegPath(\n start: Position,\n end: Position,\n sourceDirection?: string,\n targetDirection?: string\n): string {\n const preX =\n sourceDirection === \"left\"\n ? start.x - 50\n : sourceDirection === \"right\"\n ? start.x + 50\n : start.x;\n const postX =\n targetDirection === \"left\"\n ? end.x - 50\n : targetDirection === \"right\"\n ? end.x + 50\n : end.x;\n const preY =\n sourceDirection === \"up\"\n ? start.y - 50\n : sourceDirection === \"down\"\n ? start.y + 50\n : start.y;\n const postY =\n targetDirection === \"up\"\n ? end.y - 50\n : targetDirection === \"down\"\n ? end.y + 50\n : end.y;\n const midX = (preX + postX) / 2;\n const midY = (preY + postY) / 2;\n const f = (n: number) => Math.round(n * 10) / 10;\n\n const segments: string[] = [];\n segments.push(`M${f(start.x)},${f(start.y)}`);\n segments.push(`L${f(preX)},${f(preY)}`);\n\n {\n segments.push(`L${f(midX)},${f(preY)}`);\n segments.push(`L${f(midX)},${f(midY)}`);\n segments.push(`L${f(postX)},${f(midY)}`);\n segments.push(`L${f(postX)},${f(postY)}`);\n }\n segments.push(`L${f(postX)},${f(postY)}`);\n segments.push(`L${f(end.x)},${f(end.y)}`);\n\n return segments.join(\"\");\n}\n\nexport function buildCurvePath(\n start: Position,\n end: Position,\n sourceDirection?: string,\n targetDirection?: string,\n curvature?: number,\n curveDirection?: string,\n curvatureOffset?: number\n): string {\n const preX = start.x;\n const postX = end.x;\n const preY = start.y;\n const postY = end.y;\n\n const f = (n: number) => Math.round(n * 10) / 10;\n const curv = typeof curvature === \"number\" ? curvature : 0.3;\n\n const curvOffset = typeof curvatureOffset === \"number\" ? curvatureOffset : 3;\n\n const midX = (preX + postX) / 2;\n const midY = (preY + postY) / 2;\n let ctrlX = midX;\n let ctrlY = midY;\n\n const remSize = parseFloat(\n getComputedStyle(document.documentElement).fontSize\n );\n\n const dx = Math.abs(postX - preX);\n const dy = Math.abs(postY - preY);\n\n switch (curveDirection) {\n case \"right\":\n ctrlX = Math.max(preX, postX) + dx * curv + curvOffset * remSize;\n break;\n case \"left\":\n ctrlX = Math.max(preX, postX) + dx * curv - curvOffset * remSize;\n break;\n case \"up\":\n ctrlY = Math.max(preY, postY) + dy * curv - curvOffset * remSize;\n break;\n case \"down\":\n ctrlY = Math.max(preY, postY) + dy * curv + curvOffset * remSize;\n break;\n }\n // Quadratic Bezier for smooth parenthesis-like curve\n return `M${f(preX)} ${f(preY)} Q ${f(ctrlX)} ${f(ctrlY)} ${f(postX)} ${f(\n postY\n )} `;\n}\n","export function debounce(cb: () => void) {\n return {\n timeout: null as null | number,\n cancel() {\n if (this.timeout) {\n window.clearTimeout(this.timeout)\n this.timeout = null\n }\n },\n call(delay: number) {\n this.timeout = window.setTimeout(() => {\n cb()\n }, delay)\n }\n }\n}\n","import { Component, EventEmitter, Input, Output } from '@angular/core';\n\n@Component({\n selector: 'context-menu-search',\n templateUrl: './search.component.html',\n styleUrls: ['./search.component.sass']\n})\nexport class ContextMenuSearchComponent {\n @Input() value!: string\n @Output() update = new EventEmitter<string>()\n}\n","<input class=\"search\" [value]=\"value\" (input)=\"update.emit($any($event.target)?.value || '')\"\n data-testid=\"context-menu-search-input\" />\n","import { ChangeDetectorRef, Component, EventEmitter, HostBinding, HostListener, Input, Output } from '@angular/core';\nimport { Item } from '../../types';\nimport { debounce } from '../../debounce';\n// [imports]\n\n@Component({\n // [component-directive]\n selector: 'context-menu-item',\n templateUrl: './item.component.html',\n styleUrls: ['./item.component.sass', '../../block.sass'],\n host: {\n 'data-testid': 'context-menu-item'\n }\n})\nexport class ContextMenuItemComponent {\n @Input() subitems?: Item[]\n @Input() delay!: number\n @Output() select = new EventEmitter<void>();\n @Output() hide = new EventEmitter<void>();\n\n @HostBinding('class.block') get block() { return true }\n @HostBinding('class.hasSubitems') get hasSubitems() { return this.subitems }\n\n @HostListener('click', ['$event']) click(event: MouseEvent) {\n event.stopPropagation()\n this.select.emit()\n this.hide.emit()\n }\n @HostListener('pointerdown', ['$event']) pointerdown(event: PointerEvent) {\n event.stopPropagation()\n }\n @HostListener('wheel', ['$event']) wheel(event: MouseEvent) {\n event.stopPropagation()\n }\n\n hideSubitems = debounce(() => {\n this.visibleSubitems = false\n this.cdr.detectChanges()\n })\n visibleSubitems = false\n\n @HostListener('pointerover') pointerover() {\n this.hideSubitems.cancel()\n this.visibleSubitems = true\n this.cdr.detectChanges()\n }\n @HostListener('pointerleave') pointerleave() {\n this.hideSubitems.call(this.delay)\n this.cdr.detectChanges()\n }\n\n constructor(private cdr: ChangeDetectorRef) {\n this.cdr.detach()\n }\n}\n","<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","import { Component, Input, ChangeDetectorRef, OnChanges, OnDestroy, HostListener, HostBinding } from '@angular/core';\nimport { Item } from '../../types';\nimport { debounce } from '../../debounce';\n// [imports]\n\n@Component({\n // [component-directive]\n templateUrl: './menu.component.html',\n styleUrls: ['./menu.component.sass', '../../block.sass'],\n host: {\n 'data-testid': 'context-menu'\n }\n})\nexport class ContextMenuComponent implements OnChanges, OnDestroy {\n @Input() items!: Item[]\n @Input() delay!: number\n @Input() searchBar?: boolean\n @Input() onHide!: () => void\n @Input() rendered!: () => void\n\n public filter: string = ''\n\n hide = debounce(() => {\n this.onHide()\n this.cdr.detectChanges()\n })\n\n @HostBinding('attr.rete-context-menu') customAttribute = ''\n\n @HostListener('mouseover') pointerover() {\n this.hide.cancel()\n this.cdr.detectChanges()\n }\n @HostListener('mouseleave') pointerleave() {\n this.hide.call(this.delay)\n this.cdr.detectChanges()\n }\n\n constructor(private cdr: ChangeDetectorRef) {\n this.cdr.detach()\n }\n\n ngOnChanges(): void {\n this.cdr.detectChanges()\n requestAnimationFrame(() => this.rendered())\n }\n\n setFilter(value: string) {\n this.filter = value\n this.cdr.detectChanges()\n }\n\n getItems() {\n const filterRegexp = new RegExp(this.filter, 'i')\n const filteredList = this.items.filter(item => (\n item.label.match(filterRegexp)\n ))\n\n return filteredList\n }\n\n ngOnDestroy(): void {\n if (this.hide) this.hide.cancel()\n }\n}\n","<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","import { BaseSchemes } from \"rete\";\n\nimport { ContextMenuRender } from \"./types\";\nimport { ContextMenuComponent } from \"./components/menu/menu.component\";\nimport { RenderPreset } from \"../types\";\n\n/**\n * Preset for rendering context menu.\n */\nexport function setup<\n Schemes extends BaseSchemes,\n K extends ContextMenuRender\n>(props?: { delay?: number }): RenderPreset<Schemes, K> {\n const delay = typeof props?.delay === \"undefined\" ? 1000 : props.delay;\n\n return {\n update(context) {\n if (context.data.type === \"contextmenu\") {\n return {\n items: context.data.items,\n delay,\n searchBar: context.data.searchBar,\n onHide: context.data.onHide,\n };\n }\n return null;\n },\n mount(context, plugin) {\n const parent = plugin.parentScope();\n const emit = parent.emit.bind(parent);\n const rendered = () => {\n emit({ type: \"rendered\", data: context.data } as any);\n };\n\n if (context.data.type === \"contextmenu\") {\n return {\n key: \"context-menu\",\n component: ContextMenuComponent,\n props: {\n items: context.data.items,\n delay,\n searchBar: context.data.searchBar,\n onHide: context.data.onHide,\n rendered,\n },\n };\n }\n return null;\n },\n };\n}\n","import { Position } from '../types'\n\ntype Translate = (dx: number, dy: number) => void\ntype StartEvent = { pageX: number, pageY: number }\n\nexport function useDrag(translate: Translate, getPointer: (e: StartEvent) => Position) {\n return {\n start(e: StartEvent) {\n let previous = { ...getPointer(e) }\n\n function move(moveEvent: MouseEvent) {\n const current = { ...getPointer(moveEvent) }\n const dx = current.x - previous.x\n const dy = current.y - previous.y\n\n previous = current\n\n translate(dx, dy)\n }\n function up() {\n window.removeEventListener('pointermove', move)\n window.removeEventListener('pointerup', up)\n window.removeEventListener('pointercancel', up)\n }\n\n window.addEventListener('pointermove', move)\n window.addEventListener('pointerup', up)\n window.addEventListener('pointercancel', up)\n }\n }\n}\n","import { Component, Input, HostBinding, HostListener } from '@angular/core';\nimport { useDrag } from '../../../../shared/drag';\nimport { MinimapData } from '../../types';\n\n@Component({\n selector: 'minimap-mini-viewport',\n templateUrl: './mini-viewport.component.html',\n styleUrls: ['./mini-viewport.component.sass'],\n host: {\n 'data-testid': 'minimap-viewport'\n }\n})\nexport class MiniViewportComponent {\n @Input() left!: number\n @Input() top!: number\n @Input() width!: number\n @Input() height!: number\n @Input() containerWidth!: number\n @Input() translate!: MinimapData['translate']\n\n drag = useDrag((dx, dy) => this.onDrag(dx, dy), e => ({ x: e.pageX, y: e.pageY }))\n\n @HostBinding('style.left') get styleLeft() {\n return this.px(this.scale(this.left))\n }\n @HostBinding('style.top') get styleTop() {\n return this.px(this.scale(this.top))\n }\n @HostBinding('style.width') get styleWidth() {\n return this.px(this.scale(this.width))\n }\n @HostBinding('style.height') get styleHeight() {\n return this.px(this.scale(this.height))\n }\n\n @HostListener('pointerdown', ['$event']) pointerdown(event: PointerEvent) {\n event.stopPropagation()\n this.drag.start(event)\n }\n\n px(value: number) {\n return `${value}px`\n }\n\n scale(v: number) {\n return v * this.containerWidth\n }\n\n invert(v: number) {\n return v / this.containerWidth\n }\n\n onDrag(dx: number, dy: number) {\n this.translate(this.invert(-dx), this.invert(-dy))\n }\n}\n","","import { Component, Input, HostBinding } from '@angular/core';\n\n@Component({\n selector: 'minimap-mini-node',\n templateUrl: './mini-node.component.html',\n styleUrls: ['./mini-node.component.sass'],\n host: {\n 'data-testid': 'minimap-node'\n }\n})\nexport class MiniNodeComponent {\n @Input() left!: number\n @Input() top!: number\n @Input() width!: number\n @Input() height!: number\n\n @HostBinding('style.left') get styleLeft() {\n return this.px(this.left)\n }\n @HostBinding('style.top') get styleTop() {\n return this.px(this.top)\n }\n @HostBinding('style.width') get styleWidth() {\n return this.px(this.width)\n }\n @HostBinding('style.height') get styleHeight() {\n return this.px(this.height)\n }\n\n px(value: number) {\n return `${value}px`\n }\n\n}\n","","import { Component, Input, ChangeDetectorRef, OnChanges, HostListener, ElementRef, HostBinding } from '@angular/core';\nimport { MinimapData } from '../../types';\n// [imports]\n\n@Component({\n // [component-directive]\n templateUrl: './minimap.component.html',\n styleUrls: ['./minimap.component.sass'],\n host: {\n 'data-testid': 'minimap'\n }\n})\nexport class MinimapComponent implements OnChanges {\n @Input() rendered!: () => void\n @Input() size!: number\n @Input() ratio!: MinimapData['ratio']\n @Input() nodes!: MinimapData['nodes']\n @Input() viewport!: MinimapData['viewport']\n @Input() translate!: MinimapData['translate']\n @Input() point!: MinimapData['point']\n\n @HostBinding('style.width') get width() {\n return this.px(this.size * this.ratio)\n }\n @HostBinding('style.height') get height() {\n return this.px(this.size)\n }\n\n @HostListener('pointerdown', ['$event']) pointerdown(event: PointerEvent) {\n event.stopPropagation()\n event.preventDefault()\n }\n\n @HostListener('dblclick', ['$event']) dblclick(event: MouseEvent) {\n event.stopPropagation()\n event.preventDefault()\n\n if (!this.el.nativeElement) return\n const box = this.el.nativeElement.getBoundingClientRect()\n const x = (event.clientX - box.left) / (this.size * this.ratio)\n const y = (event.clientY - box.top) / (this.size * this.ratio)\n\n this.point(x, y)\n }\n\n constructor(public el: ElementRef, private cdr: ChangeDetectorRef) {\n this.cdr.detach()\n }\n\n ngOnChanges(): void {\n this.cdr.detectChanges()\n requestAnimationFrame(() => this.rendered())\n }\n\n px(value: number) {\n return `${value}px`\n }\n\n scale(value: number) {\n if (!this.el.nativeElement) return 0\n\n return value * this.el.nativeElement.clientWidth\n }\n\n identifyMiniNode(_: number, item: MinimapData['nodes'][number]) {\n return [item.top, item.left].join('_')\n }\n}\n","<minimap-mini-node *ngFor=\"let node of nodes; trackBy: identifyMiniNode\" [left]=\"scale(node.left)\"\n [top]=\"scale(node.top)\" [width]=\"scale(node.width)\" [height]=\"scale(node.height)\">\n\n</minimap-mini-node>\n<minimap-mini-viewport [left]=\"viewport.left\" [top]=\"viewport.top\" [width]=\"viewport.width\" [height]=\"viewport.height\"\n [containerWidth]=\"el.nativeElement?.clientWidth\" [translate]=\"translate\"></minimap-mini-viewport>\n","import { BaseSchemes } from 'rete';\n\nimport { RenderPreset } from '../types';\nimport { MinimapRender } from './types';\nimport { MinimapComponent } from './components/minimap/minimap.component';\n\n/**\n * Preset for rendering minimap.\n */\nexport function setup<Schemes extends BaseSchemes, K extends MinimapRender>(props?: { size?: number }): RenderPreset<Schemes, K> {\n return {\n update(context) {\n if (context.data.type === 'minimap') {\n return {\n nodes: context.data.nodes,\n size: props?.size || 200,\n ratio: context.data.ratio,\n viewport: context.data.viewport,\n translate: context.data.translate,\n point: context.data.point\n }\n }\n return null\n },\n mount(context, plugin) {\n const parent = plugin.parentScope()\n const emit = parent.emit.bind(parent)\n const rendered = () => {\n emit({ type: 'rendered', data: context.data } as any)\n }\n\n if (context.data.type === 'minimap') {\n return {\n key: 'rete-minimap',\n component: MinimapComponent,\n props: {\n nodes: context.data.nodes,\n size: props?.size || 200,\n ratio: context.data.ratio,\n viewport: context.data.viewport,\n translate: context.data.translate,\n point: context.data.point,\n rendered\n }\n }\n }\n return null\n }\n }\n}\n","import { Component, Input, ChangeDetectorRef, OnChanges, HostListener, HostBinding, Output, EventEmitter } from '@angular/core';\nimport { Position } from '../../types';\nimport { useDrag } from '../../../../shared/drag'\n\nconst pinSize = 20\n\n@Component({\n selector: 'reroute-pin',\n template: '',\n styleUrls: ['./pin.component.sass'],\n host: {\n 'data-testid': 'pin'\n }\n})\nexport class PinComponent implements OnChanges {\n @Input() position!: Position\n @Input() selected?: boolean\n @Input() getPointer!: () => Position\n @Output() menu = new EventEmitter<void>()\n @Output() translate = new EventEmitter<{ dx: number, dy: number }>()\n @Output() down = new EventEmitter<void>()\n\n drag = useDrag((dx, dy) => {\n this.translate.emit({ dx, dy })\n }, () => this.getPointer())\n\n @HostBinding('class.selected') get _selected() {\n return this.selected\n }\n @HostBinding('style.top') get top() {\n return `${this.position.y - pinSize / 2}px`\n }\n @HostBinding('style.left') get left() {\n return `${this.position.x - pinSize / 2}px`\n }\n @HostListener('pointerdown', ['$event']) pointerdown(event: PointerEvent) {\n event.stopPropagation()\n event.preventDefault()\n\n this.drag.start(event)\n this.down.emit()\n }\n @HostListener('contextmenu', ['$event']) contextmenu(event: MouseEvent) {\n event.stopPropagation()\n event.preventDefault()\n\n this.menu.emit()\n }\n\n constructor(private cdr: ChangeDetectorRef) {\n // this.cdr.detach()\n }\n\n ngOnChanges(): void {\n // this.cdr.detectChanges()\n // requestAnimationFrame(() => this.rendered())\n }\n\n}\n","import { Component, Input, ChangeDetectorRef, OnChanges } from \"@angular/core\";\nimport { Pin, PinData, Position } from \"../../types\";\n\n@Component({\n // [component-directive]\n templateUrl: \"./pins.component.html\",\n})\nexport class PinsComponent implements OnChanges {\n @Input() rendered!: () => void;\n @Input() pins!: PinData[\"pins\"];\n @Input() down?: (id: string) => void;\n @Input() translate?: (id: string, dx: number, dy: number) => void;\n @Input() menu?: (id: string) => void;\n @Input() getPointer: () => Position = () => ({ x: 0, y: 0 });\n\n constructor(private cdr: ChangeDetectorRef) {\n this.cdr.detach();\n }\n\n ngOnChanges(): void {\n this.cdr.detectChanges();\n requestAnimationFrame(() => this.rendered());\n }\n\n track(_: number, item: Pin) {\n return item.id;\n }\n}\n","<reroute-pin *ngFor=\"let pin of pins; trackBy: track\" [position]=\"pin.position\" [selected]=\"pin.selected\"\n (menu)=\"menu && menu(pin.id)\" (translate)=\"translate && translate(pin.id, $event.dx, $event.dy)\"\n (down)=\"down && down(pin.id)\" [getPointer]=\"getPointer\"></reroute-pin>\n","import { BaseSchemes } from 'rete';\nimport { BaseAreaPlugin } from 'rete-area-plugin';\n\nimport { RenderPreset } from '../types'\nimport { PinsRender } from './types';\nimport { PinsComponent } from './components/pins/pins.component';\n\ntype Props = {\n translate?: (id: string, dx: number, dy: number) => void\n contextMenu?: (id: string) => void\n pointerdown?: (id: string) => void\n}\n\n/**\n * Preset for rendering pins.\n */\nexport function setup<Schemes extends BaseSchemes, K extends PinsRender>(props?: Props): RenderPreset<Schemes, K> {\n const getProps = () => ({\n menu: props?.contextMenu || (() => null),\n translate: props?.translate || (() => null),\n down: props?.pointerdown || (() => null)\n })\n\n return {\n update(context) {\n if (context.data.type === 'reroute-pins') {\n return {\n ...getProps(),\n pins: context.data.data.pins\n }\n }\n return null\n },\n mount(context, plugin) {\n const area = plugin.parentScope<BaseAreaPlugin<Schemes, PinsRender>>(BaseAreaPlugin)\n const rendered = () => {\n area.emit({ type: 'rendered', data: context.data })\n }\n\n if (context.data.type === 'reroute-pins') {\n return {\n key: 'rete-reroute',\n component: PinsComponent,\n props: {\n ...getProps(),\n pins: context.data.data.pins,\n rendered,\n getPointer: () => area.area.pointer\n }\n }\n }\n return null\n }\n }\n}\n","/**\n * Built-in presets, responsible for rendering different parts of the editor.\n * @module\n */\nexport * as classic from './classic'\nexport * as contextMenu from './context-menu'\nexport * as minimap from './minimap'\nexport * as reroute from './reroute'\n","import {\n Component,\n Input,\n HostBinding,\n ChangeDetectorRef,\n OnChanges,\n} from \"@angular/core\";\nimport { ClassicPreset as Classic } from \"rete\";\nimport { KeyValue } from \"@angular/common\";\n// [imports]\n\ntype NodeExtraData = {\n width?: number;\n height?: number;\n image?: string;\n color?: string;\n borderColor?: string;\n backgroundColor?: string;\n dynamicData?: Record<string, { value: number; unit: string }>;\n};\ntype SortValue<N extends Classic.Node> = (\n | N[\"controls\"]\n | N[\"inputs\"]\n | N[\"outputs\"]\n)[string];\n\n@Component({\n // [component-directive]\n templateUrl: \"./scada-node.component.html\",\n styleUrls: [\"./scada-node.component.scss\"],\n host: {\n \"data-testid\": \"node\",\n },\n})\nexport class ScadaNodeComponent implements OnChanges {\n private _data!: Classic.Node & NodeExtraData;\n @Input() set data(value: Classic.Node & NodeExtraData) {\n this._data = value;\n this.inputsOutputs = {\n ...this.data.inputs,\n ...this.data.outputs,\n };\n }\n get data() {\n return this._data;\n }\n @Input() emit!: (data: any) => void;\n @Input() rendered!: () => void;\n @Input() version!: number;\n\n seed = 0;\n\n inputsOutputs: {\n [x: string]: Classic.Output<any> | Classic.Input<any> | undefined;\n } = {};\n\n @HostBinding(\"style.width.px\") get width() {\n return this.data.width;\n }\n\n @HostBinding(\"style.height.px\") get height() {\n return this.data.height;\n }\n\n @HostBinding(\"class.selected\") get selected() {\n return this.data.selected;\n }\n\n @HostBinding(\"class.dynamic\") get dynamic() {\n return this.data.dynamicData;\n }\n\n // Dynamically set border color; inline style overrides SCSS default\n\n @HostBinding(\"style.color\") get color() {\n return this.data.color || \"black\";\n }\n\n @HostBinding(\"style.--node-border-color\") get borderColor() {\n return this.data.borderColor || \"#4e58bf\";\n }\n\n @HostBinding(\"style.--node-bg-color\") get backgroundColor() {\n return this.data.backgroundColor || \"rgba(110,136,255,0.8)\";\n }\n\n constructor(private cdr: ChangeDetectorRef) {\n this.cdr.detach();\n }\n\n ngOnChanges(): void {\n this.cdr.detectChanges();\n requestAnimationFrame(() => this.rendered());\n this.seed++; // force render sockets\n }\n\n public forceUpdate() {\n this.cdr.detectChanges();\n }\n\n sortByIndex<N extends Classic.Node, I extends KeyValue<string, SortValue<N>>>(\n a: I,\n b: I\n ) {\n const ai = a.value?.index || 0;\n const bi = b.value?.index || 0;\n\n return ai - bi;\n }\n}\n","<div class=\"title\" data-testid=\"title\">{{ data.label }}</div>\n<div class=\"image\" *ngIf=\"data.image\">\n <img [src]=\"data.image\" />\n</div>\n@for (inOutSocket of inputsOutputs | keyvalue : sortByIndex; track\ninOutSocket.key; let i = $index) {\n<div\n class=\"input\"\n [attr.data-testid]=\"'input-' + inOutSocket.key\"\n style=\"position: absolute; z-index: 1000\"\n [style.left.px]=\"inOutSocket.value?.socket?.positionX\"\n [style.top.px]=\"inOutSocket.value?.socket.positionY\"\n>\n <div\n class=\"socket\"\n refComponent\n [data]=\"{\n type: 'socket',\n side: inOutSocket.value?.socket.type,\n key: inOutSocket.key,\n nodeId: data.id,\n payload: inOutSocket.value?.socket,\n seed: seed\n }\"\n [emit]=\"emit\"\n data-testid=\"input-socket\"\n ></div>\n</div>\n} @if (data.dynamicData) {\n<div class=\"dynamic-data\">\n @for (metric of data.dynamicData | keyvalue; track metric.key) {\n <div class=\"dynamic-row\">\n <div class=\"data-title\">\n {{ metric.key }}\n </div>\n <div class=\"data-value\">\n {{ metric.value.value }} {{ metric.value.unit }}\n </div>\n </div>\n }\n</div>\n}\n","import {\n Component,\n Input,\n HostBinding,\n ChangeDetectorRef,\n OnChanges,\n} from \"@angular/core\";\nimport { ClassicPreset as Classic } from \"rete\";\nimport { KeyValue } from \"@angular/common\";\n// [imports]\n\ntype NodeExtraData = {\n width?: number;\n height?: number;\n positionTop?: boolean;\n image?: string;\n color?: string;\n borderColor?: string;\n backgroundColor?: string;\n fontMainStyle?: string;\n fontStyle?: string;\n dynamicData?:\n | Record<string, { value: number | string; unit: string }>\n | Array<{ value: number | string; unit?: string }>;\n onClick?: () => void;\n};\ntype SortValue<N extends Classic.Node> = (\n | N[\"controls\"]\n | N[\"inputs\"]\n | N[\"outputs\"]\n)[string];\n\n@Component({\n // [component-directive]\n templateUrl: \"./scada-overview-node.component.html\",\n styleUrls: [\"./scada-overview-node.component.scss\"],\n host: {\n \"data-testid\": \"node\",\n },\n})\nexport class ScadaOverviewNodeComponent implements OnChanges {\n private _data!: Classic.Node & NodeExtraData;\n @Input() set data(value: Classic.Node & NodeExtraData) {\n this._data = value;\n this.inputsOutputs = {\n ...this.data.inputs,\n ...this.data.outputs,\n };\n }\n get data() {\n return this._data;\n }\n @Input() emit!: (data: any) => void;\n @Input() rendered!: () => void;\n @Input() version!: number;\n\n seed = 0;\n\n inputsOutputs: {\n [x: string]: Classic.Output<any> | Classic.Input<any> | undefined;\n } = {};\n\n @HostBinding(\"style.width\") get width() {\n return `${this.data.width}rem`;\n }\n\n @HostBinding(\"style.height\") get height() {\n return `${this.data.height}rem`;\n }\n\n @HostBinding(\"class.selected\") get selected() {\n return this.data.selected;\n }\n\n @HostBinding(\"class.dynamic\") get dynamic() {\n return this.data.dynamicData;\n }\n\n @HostBinding(\"style.color\") get color() {\n return this.data.color || \"black\";\n }\n\n @HostBinding(\"style.--node-bg-color\") get backgroundColor() {\n if (\n this.data.dynamicData &&\n Array.isArray(this.data.dynamicData) &&\n (this.data.dynamicData[0].value == 0 ||\n this.data.dynamicData[0].value == undefined ||\n this.data.dynamicData[0].value == \"-\")\n ) {\n return \"#7d94a1\";\n }\n return this.data.backgroundColor || \"rgba(110,136,255,0.8)\";\n }\n\n constructor(private cdr: ChangeDetectorRef) {\n this.cdr.detach();\n }\n\n ngOnChanges(): void {\n this.cdr.detectChanges();\n requestAnimationFrame(() => this.rendered());\n this.seed++; // force render sockets\n }\n\n public forceUpdate() {\n this.cdr.detectChanges();\n }\n\n public isArray<T>(value: unknown): value is Array<T> {\n return Array.isArray(value);\n }\n\n sortByIndex<N extends Classic.Node, I extends KeyValue<string, SortValue<N>>>(\n a: I,\n b: I\n ) {\n const ai = a.value?.index || 0;\n const bi = b.value?.index || 0;\n\n return ai - bi;\n }\n\n toPx(number: number): number {\n const rootFontSize =\n parseFloat(getComputedStyle(document.documentElement).fontSize) || 16;\n return number * rootFontSize;\n }\n\n onClick() {\n this.data.onClick?.();\n }\n}\n","<div class=\"image\" *ngIf=\"data.image\" (click)=\"onClick()\">\n <img [src]=\"data.image\" />\n</div>\n@for (inOutSocket of inputsOutputs | keyvalue : sortByIndex; track\ninOutSocket.key; let i = $index) {\n<div\n class=\"input\"\n [attr.data-testid]=\"'input-' + inOutSocket.key\"\n style=\"position: absolute; z-index: 1000\"\n [style.left.px]=\"toPx(inOutSocket.value?.socket?.positionX)\"\n [style.top.px]=\"toPx(inOutSocket.value?.socket.positionY)\"\n>\n <div\n class=\"socket\"\n refComponent\n [data]=\"{\n type: 'socket',\n side: inOutSocket.value?.socket.type,\n key: inOutSocket.key,\n nodeId: data.id,\n payload: inOutSocket.value?.socket,\n seed: seed\n }\"\n [emit]=\"emit\"\n data-testid=\"input-socket\"\n ></div>\n</div>\n} @if (data.dynamicData) {\n<div\n class=\"dynamic-data\"\n [ngStyle]=\"{\n 'top.px': data.positionTop ? -toPx(1) : toPx(data.height!) - toPx(data.height!) * 0.3,\n }\"\n>\n @if (isArray(data.dynamicData)) {\n <div class=\"dynamic-row\">\n <div class=\"data-value\">\n <span [ngClass]=\"data.fontMainStyle\">{{\n data.dynamicData[0].value\n }}</span>\n <span [ngClass]=\"data.fontStyle\">\n {{ data.dynamicData[0].unit ?? \"\" }}\n </span>\n </div>\n <div class=\"data-value\">\n <span [ngClass]=\"data.fontMainStyle\">{{\n data.dynamicData[1].value\n }}</span>\n <span [ngClass]=\"data.fontStyle\">\n {{ data.dynamicData[1].unit ?? \"\" }}</span\n >\n </div>\n </div>\n }\n</div>\n}\n","import { NgModule } from \"@angular/core\";\nimport { CommonModule } from \"@angular/common\";\n\nimport { NodeComponent } from \"./presets/classic/components/node/node.component\";\nimport { ConnectionComponent } from \"./pres