import go from "gojs"; import { parse } from "mathjs"; import { SPLITCHAR } from "../../../../lib/helpers/objectMethods"; import { replaceAll } from "../../../../lib/helpers/stringMethods"; function analyseEquation(equation: string) { let hasOperators = false; let hasConstants = false; let varsRoot = new Array(); const vars = new Array(); /** Fill the List => with all vars and select only the Single Elements: */ parse(equation).filter(function (node) { if (node.isOperatorNode) { hasOperators = true; } if (node.isConstantNode) { hasConstants = true; } return node.isSymbolNode; }).forEach(function (node) { /** Store the Name */ varsRoot.push(node.name); }); // Extract all contained Vars. parse(replaceAll(equation, SPLITCHAR, "_SPLITCHAR_")).filter(function (node) { return node.isSymbolNode; }).forEach(function (node) { /** Store the Name */ vars.push(replaceAll(node.name, "_SPLITCHAR_", SPLITCHAR)); }); varsRoot = Array.from(new Set(varsRoot)); return { hasConstants, hasOperators, varsRoot, vars }; } export function transitionTemplate(selctionExtension = []): go.Node { const make = go.GraphObject.make; // for conciseness in defining templates // some constants that will be reused within templates const mtb8 = new go.Margin(8, 0, 8, 0); const mr8 = new go.Margin(0, 8, 0, 0); const ml8 = new go.Margin(0, 0, 0, 8); const mrl8 = new go.Margin(0, 8, 0, 8); const roundedRectangleParams = { parameter1: 2, // set the rounded corner spot1: go.Spot.TopLeft, spot2: go.Spot.BottomRight // make content go all the way to inside edges of rounded corners }; // define Converters to be used for Bindings function iconConverter(value) { return value; } // Create a Vertical Panel. function makeVerticalPanel( binding: string, side: "left" | "right", portSize = new go.Size(8, 8) ) { let order: any = []; if (side === "left") { order = [ make( go.Shape, "RoundedRectangle", { stroke: null, strokeWidth: 0, desiredSize: portSize, margin: new go.Margin(1, 0) }, new go.Binding("fill", "portColor") ), make( go.TextBlock, { row: 0, alignment: go.Spot.Left, font: "16px Roboto, sans-serif", stroke: "rgba(0, 0, 0, .87)", margin: mrl8 }, new go.Binding("text", "label") ) ]; } else { order = [ make( go.TextBlock, { row: 0, alignment: go.Spot.Left, font: "16px Roboto, sans-serif", stroke: "rgba(0, 0, 0, .87)", maxSize: new go.Size(160, NaN), margin: mrl8 }, new go.Binding("text", "label") ), make( go.Shape, "RoundedRectangle", { stroke: null, strokeWidth: 0, desiredSize: portSize, margin: new go.Margin(1, 0) }, new go.Binding("fill", "portColor") ) ]; } return make(go.Panel, "Vertical", new go.Binding("itemArray", binding), { alignment: side === "left" ? go.Spot.Left : go.Spot.Right, itemTemplate: make( go.Panel, "Horizontal", Object.assign({ _side: side, // internal property to make it easier to tell which side it's on fromLinkable: side !== "left", toLinkable: side === "left", cursor: "pointer", alignment: side === "left" ? go.Spot.Left : go.Spot.Right, mouseEnter: function (e, port) { // the PORT argument will be this Shape if (!e.diagram.isReadOnly) { port.fill = "rgba(255,0,255,0.5)"; e.diagram.linkTemplate = e.diagram.linkTemplateMap.get("dataFlow"); e.diagram.toolManager.linkingTool.archetypeLinkData = { category: "dataFlow" }; } }, }, side !== "left" ? { fromSpot: go.Spot.RightSide } : { toSpot: go.Spot.Left }) , new go.Binding("portId", "portId"), ...order ) // end itemTemplate }); // end Vertical Panel } // This function provides a common style for most of the TextBlocks. // Some of these values may be overridden in a particular TextBlock. function textStyle(field) { return [ { font: "12px Roboto, sans-serif", stroke: "rgba(0, 0, 0, .60)", visible: false // only show textblocks when there is corresponding data for them }, new go.Binding("visible", field, function (val) { return val !== undefined; }) ]; } // Define a function for creating a "port" that is normally transparent. // The "name" is used as the GraphObject.portId, // the "align" is used to determine where to position the port relative to the body of the node, // the "spot" is used to control how links connect with the port and whether the port // stretches along the side of the node, // and the boolean "output" and "input" arguments control whether the user can draw links from or to the port. function makePort(name, align, spot, output, input) { const horizontal = align.equals(go.Spot.Top) || align.equals(go.Spot.Bottom); // the port is basically just a transparent rectangle that stretches along the side of the node, // and becomes colored when the mouse passes over it return make(go.Shape, Object.assign({ fill: "transparent", // changed to a color in the mouseEnter event handler strokeWidth: 0, // no stroke width: horizontal ? NaN : 8, // if not stretching horizontally, just 8 wide height: !horizontal ? NaN : 8, // if not stretching vertically, just 8 tall alignment: align, // align the port on the main Shape stretch: horizontal ? go.GraphObject.Horizontal : go.GraphObject.Vertical, portId: name, // declare this object to be a "port" // fromSpot: spot, // declare where links may connect at this port fromLinkable: output, // declare whether the user may draw links from here // toSpot: spot, // declare where links may connect at this port toLinkable: input, // declare whether the user may draw links to here cursor: "pointer", // show a different cursor to indicate potential link point mouseEnter: function (e, port) { // the PORT argument will be this Shape if (!e.diagram.isReadOnly) { port.fill = "rgba(255,0,255,0.5)"; e.diagram.linkTemplate = e.diagram.linkTemplateMap.get("logicFlow"); e.diagram.toolManager.linkingTool.archetypeLinkData = { category: "logicFlow" }; } }, mouseLeave: function (e, port) { port.fill = "transparent"; } }, // output ? { // fromSpot: spot, // declare where links may connect at this port // } : {}, input ? { // toSpot: spot, // declare where links may connect at this port // } : {} ) ); } return make( go.Node, "Auto", { locationSpot: go.Spot.Top, isShadowed: true, shadowBlur: 1, shadowOffset: new go.Point(0, 1), shadowColor: "rgba(0, 0, 0, .14)", // selection adornment to match shape of nodes selectionAdornmentTemplate: make( go.Adornment, "Auto", make(go.Shape, "RoundedRectangle", roundedRectangleParams, { fill: null, stroke: "#7986cb", strokeWidth: 3 }), make(go.Placeholder), ...selctionExtension ) // end Adornment }, make( go.Shape, "RoundedRectangle", roundedRectangleParams, { name: "SHAPE", fill: "#ffffff", strokeWidth: 1 }, // gold if highlighted, white otherwise new go.Binding("fill", "isHighlighted", function (h) { return h ? "gold" : "#ffffff"; }).ofObject() ), make( go.Panel, "Vertical", { stretch: go.GraphObject.Fill }, make( go.Panel, "Horizontal", { margin: 8 }, make( go.Picture, // flag image, only visible if a nation is specified { margin: mr8, visible: false, desiredSize: new go.Size(50, 50) }, new go.Binding("source", "icon", iconConverter), new go.Binding("visible", "icon", function (icon) { return icon !== undefined; }) ), make( go.Panel, "Table", { stretch: go.GraphObject.Horizontal }, make( go.TextBlock, { row: 0, alignment: go.Spot.Left, font: "16px Roboto, sans-serif", stroke: "rgba(0, 0, 0, .87)", editable: true }, new go.Binding("text", "label") ), make( go.TextBlock, textStyle("description"), { row: 1, alignment: go.Spot.Left, editable: true, isMultiline: true }, new go.Binding("text", "description") ), // make("PanelExpanderButton", "GUARD", { // row: 0, // column: 1, // rowSpan: 2, // margin: ml8 // }), // // Expander for the Service // make("PanelExpanderButton", "SERVICE", { // row: 1, // column: 1, // rowSpan: 2, // margin: ml8 // }) ) ), // Horizontal Lane as devider: make( go.Shape, "LineH", { stroke: "rgba(0, 0, 0, .60)", strokeWidth: 1, height: 1, stretch: go.GraphObject.Horizontal }, new go.Binding("visible", "showDetails") ), // Vertiacal Layout Holding the Guard make( go.Panel, "Auto", { margin: mtb8, stretch: go.GraphObject.Fill, name: "GUARD" }, makeVerticalPanel("guardInputs", "left"), make( go.TextBlock, { row: 0, alignment: go.Spot.Center, font: "16px Roboto, sans-serif", stroke: "rgba(0, 0, 0, .87)", margin: mrl8, editable: true, maxLines: 1, textEdited: (textblock: go.TextBlock, oldText, newText) => { const node = textblock.part; // Perform a Test. try { const result = analyseEquation(newText); // Now that we know which variable is contained, // we try to creat them. node.diagram.startTransaction("add_ports"); node.diagram.model.setDataProperty(node.data, "guardInputs", result.vars.map(label => { return { label, portId: "guard." + label }; })); node.diagram.model.setDataProperty(node.data, "guard", newText); node.diagram.commitTransaction("add_ports"); } catch (e) { // The Equation is wrong, give a hint return false; } }, textValidation: (textblock: go.TextBlock, oldText, newText) => { const node = textblock.part; // Perform a Test. try { const result = analyseEquation(newText); return true; } catch (e) { // The Equation is wrong, give a hint return false; } } }, new go.Binding("text", "guard") ), makeVerticalPanel("guardOutputs", "right"), new go.Binding("visible", "showDetails") ), make( go.Shape, "LineH", { stroke: "rgba(0, 0, 0, .60)", strokeWidth: 1, height: 1, stretch: go.GraphObject.Horizontal }, new go.Binding("visible", "showDetails") ), make( go.Panel, "Auto", { margin: mtb8, stretch: go.GraphObject.Fill, name: "SERVICE" }, makeVerticalPanel("serviceInputs", "left"), make( go.TextBlock, { row: 0, alignment: go.Spot.Center, font: "16px Roboto, sans-serif", stroke: "rgba(0, 0, 0, .87)", margin: mrl8 }, new go.Binding("text", "serviceName") ), makeVerticalPanel("serviceOutputs", "right"), new go.Binding("visible", "showDetails") ) ), makePort("LogicFlowIn", go.Spot.Top, go.Spot.Top, false, true), makePort("LogicFlowOut", go.Spot.Bottom, go.Spot.Bottom, true, false), ); }