import { Component, ComponentNature, RectPath, Shape, Properties, sceneComponent } from '@hatiolab/things-scene' import { ParentObjectMixin, ParentObjectMixinProperties } from './features/parent-object-mixin' const NATURE: ComponentNature = { mutable: false, resizable: true, rotatable: true, properties: [ ...ParentObjectMixinProperties, { type: 'select', name: 'orientation', label: 'orientation', property: { options: ['left to right', 'right to left', 'top to bottom', 'bottom to top'] } }, { type: 'select', name: 'labelPosition', label: 'label-position', property: { options: ['left', 'right', 'top', 'bottom'] } }, { type: 'select', name: 'direction', label: 'direction', property: { options: ['IN', 'OUT', 'BOTH'] } }, { type: 'checkbox', name: 'showText', label: 'show-text' } ] } @sceneComponent('PortFlow') export default class PortFlow extends ParentObjectMixin(RectPath(Shape)) { static get nature() { return NATURE } get hasTextProperty() { return this.getState('showText') || false } get textBounds() { var { paddingTop, paddingLeft, paddingRight, paddingBottom, fontSize = 24 } = this.state paddingBottom ||= 0 paddingTop ||= 0 paddingLeft ||= 0 paddingRight ||= 0 var { left, top, width, height } = this.bounds var { labelPosition = 'top' } = this.state left += paddingLeft top += paddingTop width = Math.max(width - paddingLeft - paddingRight, 0) height = Math.max(height - paddingTop - paddingBottom, 0) switch (labelPosition) { case 'top': return { left, top: top - fontSize, width, height: fontSize } case 'bottom': return { left, top: top + height, width, height: fontSize } case 'left': return { left: left - fontSize * 4, top, width: fontSize * 4, height } case 'right': return { left: left + width, top, width: fontSize * 4, height } default: return { left, top: top - fontSize, width, height: fontSize } } } get text() { const { direction = 'IN' } = this.state const { INOUTTYPE } = this.parentObject?.data || this.data || {} return (INOUTTYPE || direction).toUpperCase() } render(ctx) { const { left, top, width, height } = this.bounds var { orientation = 'left to right', direction = 'BOTH' }: { orientation?: string; direction?: string } = this.state const { INOUTTYPE } = this.parentObject?.data || this.data || {} if (INOUTTYPE) { direction = INOUTTYPE /* IN | OUT | BOTH */ } ctx.beginPath() const arrowSize = Math.min(width, height) * 1 const bodyWidth = orientation === 'left to right' || orientation === 'right to left' ? height * 0.1 : width * 0.1 const centerX = left + width / 2 const centerY = top + height / 2 let startX, startY, endX, endY let reverse = 1 switch (orientation) { case 'left to right': startX = left endX = left + width startY = centerY endY = centerY break case 'right to left': startX = left + width endX = left startY = centerY endY = centerY reverse = -1 break case 'top to bottom': startX = centerX endX = centerX startY = top endY = top + height break case 'bottom to top': startX = centerX endX = centerX startY = top + height endY = top reverse = -1 break } ctx.lineTo(startX, startY) if (orientation.includes('left')) { if (direction === 'OUT' || direction === 'BOTH') { ctx.lineTo(startX + arrowSize * reverse, startY - bodyWidth * reverse * 8) } else { ctx.lineTo(startX, startY - bodyWidth * reverse) } ctx.lineTo(startX + arrowSize * reverse, startY - bodyWidth * reverse) ctx.lineTo(endX - arrowSize * reverse, endY - bodyWidth * reverse) if (direction === 'IN' || direction === 'BOTH') { ctx.lineTo(endX - arrowSize * reverse, endY - bodyWidth * 8 * reverse) } else { ctx.lineTo(endX, endY - bodyWidth * reverse) } ctx.lineTo(endX, endY) if (direction === 'IN' || direction === 'BOTH') { ctx.lineTo(endX - arrowSize * reverse, endY + bodyWidth * 8 * reverse) } else { ctx.lineTo(endX, endY + bodyWidth * reverse) } ctx.lineTo(endX - arrowSize * reverse, endY + bodyWidth * reverse) ctx.lineTo(startX + arrowSize * reverse, startY + bodyWidth * reverse) if (direction === 'OUT' || direction === 'BOTH') { ctx.lineTo(startX + arrowSize * reverse, startY + bodyWidth * 8 * reverse) } else { ctx.lineTo(startX, startY + bodyWidth * reverse) } } else { if (direction === 'OUT' || direction === 'BOTH') { ctx.lineTo(startX - bodyWidth * reverse * 8, startY + arrowSize * reverse) } else { ctx.lineTo(startX - bodyWidth * reverse, startY) } ctx.lineTo(startX - bodyWidth * reverse, startY + arrowSize * reverse) ctx.lineTo(endX - bodyWidth * reverse, endY - arrowSize * reverse) if (direction === 'IN' || direction === 'BOTH') { ctx.lineTo(endX - bodyWidth * 8 * reverse, endY - arrowSize * reverse) } else { ctx.lineTo(endX - bodyWidth * reverse, endY) } ctx.lineTo(endX, endY) if (direction === 'IN' || direction === 'BOTH') { ctx.lineTo(endX + bodyWidth * 8 * reverse, endY - arrowSize * reverse) } else { ctx.lineTo(endX + bodyWidth * reverse, endY) } ctx.lineTo(endX + bodyWidth * reverse, endY - arrowSize * reverse) ctx.lineTo(startX + bodyWidth * reverse, startY + arrowSize * reverse) if (direction === 'OUT' || direction === 'BOTH') { ctx.lineTo(startX + bodyWidth * 8 * reverse, startY + arrowSize * reverse) } else { ctx.lineTo(startX + bodyWidth * reverse, startY) } } ctx.closePath() this.drawStroke(ctx) } onchangeData(after: Record, before: Record): void { const { INOUTTYPE } = after.data this.setState('direction', INOUTTYPE) } }