import { computed, observable, makeObservable } from "mobx"; import { ClassInfo, EezObject, getParent, IMessage, MessageType, PropertyType, registerClass } from "project-editor/core/object"; import { getLabel, Message } from "project-editor/store"; import type { Component } from "project-editor/flow/component"; import { getInputDisplayName, getOutputDisplayName } from "project-editor/flow/helper"; import { ProjectEditor } from "project-editor/project-editor-interface"; import { activateConnectionLine } from "project-editor/flow/connection-line/real-time-traffic-visualizer"; import { isImplicitConversionPossible } from "project-editor/flow/expression/type"; import type { Flow } from "project-editor/flow/flow"; //////////////////////////////////////////////////////////////////////////////// export class ConnectionLine extends EezObject { description: string; source: string; output: string; target: string; input: string; disabled: boolean; static classInfo: ClassInfo = { label: (connectionLine: ConnectionLine) => { const source = connectionLine.sourceComponent ? getLabel(connectionLine.sourceComponent) : "UNKNOWN"; const from = connectionLine.output == "@seqout" ? source : `${source}@${getOutputDisplayName( connectionLine.sourceComponent, connectionLine.output )}`; const target = connectionLine.targetComponent ? getLabel(connectionLine.targetComponent) : "UNKNOWN"; const to = connectionLine.input == "@seqin" ? target : `${target}@${getInputDisplayName( connectionLine.targetComponent, connectionLine.input )}`; return `${from} ➝ ${to}`; }, properties: [ { name: "description", type: PropertyType.String }, { name: "source", type: PropertyType.String, hideInPropertyGrid: true }, { name: "output", type: PropertyType.String, hideInPropertyGrid: true }, { name: "target", type: PropertyType.String, hideInPropertyGrid: true }, { name: "input", type: PropertyType.String, hideInPropertyGrid: true }, { name: "disabled", type: PropertyType.Boolean, checkboxStyleSwitch: true } ], beforeLoadHook(object, jsObject) { if (jsObject.disabled == "undefined") { jsObject.disabled = false; } }, isSelectable: () => true, deleteObjectFilterHook: (connectionLine: ConnectionLine) => { const page = getParent(getParent(connectionLine)) as Flow; return page.connectionLines.indexOf(connectionLine) != -1; }, check: (connectionLine: ConnectionLine, messages: IMessage[]) => { if ( !connectionLine.sourceComponent && !connectionLine.targetComponent ) { messages.push( new Message( MessageType.ERROR, `Connection line ${getLabel( connectionLine )}: no source and target component`, connectionLine ) ); } else if (!connectionLine.sourceComponent) { messages.push( new Message( MessageType.ERROR, `Connection line ${getLabel( connectionLine )}: no source component`, connectionLine ) ); } else if (!connectionLine.targetComponent) { messages.push( new Message( MessageType.ERROR, `Connection line ${getLabel( connectionLine )}: no target component`, connectionLine ) ); } else { const componentOutput = connectionLine.sourceComponent.outputs.find( componentOutput => componentOutput.name == connectionLine.output ); if (!componentOutput) { messages.push( new Message( MessageType.ERROR, `Connection line ${getLabel( connectionLine )}: no source component output`, connectionLine ) ); } const componentInput = connectionLine.targetComponent.inputs.find( componentInput => componentInput.name == connectionLine.input ); if (!componentInput) { messages.push( new Message( MessageType.ERROR, `Connection line ${getLabel( connectionLine )}: no target component input`, connectionLine ) ); } if (componentOutput && componentInput) { if ( !isImplicitConversionPossible( componentOutput.type, componentInput.type ) ) { messages.push( new Message( MessageType.WARNING, `Connection line incompatible data types: ${componentOutput.type} -> ${componentInput.type}`, connectionLine ) ); } } } }, objectsToClipboardData: (components: Component[]) => { const flow = ProjectEditor.getFlow(components[0]); if (flow) { return flow.objectsToClipboardData(components); } return undefined; } }; constructor() { super(); makeObservable(this, { sourceComponent: computed, targetComponent: computed, _sourcePosition: computed, _targetPosition: computed, sourceAndTargetPositions: computed, sourcePosition: computed, targetPosition: computed, sourceRect: computed, targetRect: computed }); } override makeEditable() { super.makeEditable(); makeObservable(this, { description: observable, source: observable, output: observable, target: observable, input: observable, disabled: observable, isVisible: computed }); } get isValidTarget() { return ( this.targetComponent && this.targetComponent.inputs.find(input => input.name == this.input) ); } get isValidSource() { return ( this.sourceComponent && this.sourceComponent.outputs.find( output => output.name == this.output ) ); } get sourceComponent() { const project = ProjectEditor.getProject(this); return project._objectsMap.get(this.source) as Component; } get targetComponent() { const project = ProjectEditor.getProject(this); return project._objectsMap.get(this.target) as Component; } get _sourcePosition() { if (!this.sourceComponent) { return undefined; } const outputGeometry = this.sourceComponent.geometry.outputs[this.output]; if (!outputGeometry) { return undefined; } return { x: this.sourceComponent.absolutePositionPoint.x + outputGeometry.position.x, y: this.sourceComponent.absolutePositionPoint.y + outputGeometry.position.y }; } get _targetPosition() { if (!this.targetComponent) { return undefined; } const inputGeometry = this.targetComponent.geometry.inputs[this.input]; if (!inputGeometry) { return undefined; } return { x: this.targetComponent.absolutePositionPoint.x + inputGeometry.position.x, y: this.targetComponent.absolutePositionPoint.y + inputGeometry.position.y }; } get sourceAndTargetPositions() { let sourcePositionX = 0; let sourcePositionY = 0; let targetPositionX = 100; let targetPositionY = 100; if (this._sourcePosition) { sourcePositionX = this._sourcePosition.x; sourcePositionY = this._sourcePosition.y; } if (this._targetPosition) { targetPositionX = this._targetPosition.x; targetPositionY = this._targetPosition.y; } if (this._sourcePosition && !this._targetPosition) { targetPositionX = sourcePositionX + 50; targetPositionY = sourcePositionY; } if (!this._sourcePosition && this._targetPosition) { sourcePositionX = targetPositionX - 50; sourcePositionY = targetPositionY; } return { sourcePosition: { x: sourcePositionX, y: sourcePositionY }, targetPosition: { x: targetPositionX, y: targetPositionY } }; } get sourcePosition() { return this.sourceAndTargetPositions.sourcePosition; } get targetPosition() { return this.sourceAndTargetPositions.targetPosition; } get sourceRect() { if (!this.sourceComponent) { return { left: 0, top: 0, width: 1, height: 1 }; } return this.sourceComponent.geometry; } get targetRect() { if (!this.targetComponent) { return { left: 0, top: 0, width: 1, height: 1 }; } return this.targetComponent.geometry; } setActive() { activateConnectionLine(this); } get isVisible() { return !( (this.sourceComponent instanceof ProjectEditor.WidgetClass && this.sourceComponent.isHiddenInEditorDeep) || (this.targetComponent instanceof ProjectEditor.WidgetClass && this.targetComponent.isHiddenInEditorDeep) ); } } registerClass("ConnectionLine", ConnectionLine);