20609: Change subprocess progress bar to combine relevant process statuses
[arvados.git] / src / lib / cwl-svg / graph / edge.ts
1 import {Edge as ModelEdge} from "cwlts/models";
2 import {Geometry} from "../utils/geometry";
3 import {IOPort} from "./io-port";
4 import {Workflow} from "./workflow";
5
6 export class Edge {
7
8     static makeTemplate(edge: ModelEdge, containerNode: SVGGElement, connectionStates?: string): string | undefined {
9         if (!edge.isVisible || edge.source.type === "Step" || edge.destination.type === "Step") {
10             return "";
11         }
12
13         const [, sourceStepId, sourcePort] = edge.source.id.split("/");
14         const [, destStepId, destPort]       = edge.destination.id.split("/");
15
16         const sourceVertex = containerNode.querySelector(`.node[data-id="${sourceStepId}"] .output-port[data-port-id="${sourcePort}"] .io-port`) as SVGGElement;
17         const destVertex   = containerNode.querySelector(`.node[data-id="${destStepId}"] .input-port[data-port-id="${destPort}"] .io-port`) as SVGGElement;
18
19         if (edge.source.type === edge.destination.type) {
20             console.error("Can't update edge between nodes of the same type.", edge);
21             return;
22         }
23
24         if (!sourceVertex) {
25             console.error("Source vertex not found for edge " + edge.source.id, edge);
26             return;
27         }
28
29         if (!destVertex) {
30             console.error("Destination vertex not found for edge " + edge.destination.id, edge);
31             return;
32         }
33
34         const sourceCTM = sourceVertex.getCTM() as SVGMatrix;
35         const destCTM   = destVertex.getCTM() as SVGMatrix;
36
37         const wfMatrix = containerNode.transform.baseVal.getItem(0).matrix;
38
39         const pathStr = Workflow.makeConnectionPath(
40             (sourceCTM.e - wfMatrix.e) / sourceCTM.a,
41             (sourceCTM.f - wfMatrix.f) / sourceCTM.a,
42             (destCTM.e - wfMatrix.e) / sourceCTM.a,
43             (destCTM.f - wfMatrix.f) / sourceCTM.a
44         );
45
46         return `
47             <g tabindex="-1" class="edge ${connectionStates}"
48                data-source-port="${sourcePort}"
49                data-destination-port="${destPort}"
50                data-source-node="${sourceStepId}"
51                data-source-connection="${edge.source.id}"
52                data-destination-connection="${edge.destination.id}"
53                data-destination-node="${destStepId}">
54                 <path class="sub-edge outer" d="${pathStr}"></path>
55                 <path class="sub-edge inner" d="${pathStr}"></path>
56             </g>
57         `;
58     }
59
60     static spawn(pathStr = "", connectionIDs: {
61         source?: string,
62         destination?: string,
63     }                    = {}) {
64
65         const ns   = "http://www.w3.org/2000/svg";
66         const edge = document.createElementNS(ns, "g");
67
68         const [, sourceStepId, sourcePort] = (connectionIDs.source || "//").split("/");
69         const [, destStepId, destPort]       = (connectionIDs.destination || "//").split("/");
70
71         edge.classList.add("edge");
72         if (sourceStepId) {
73             edge.classList.add(sourceStepId);
74         }
75         if (destStepId) {
76             edge.classList.add(destStepId);
77         }
78         edge.setAttribute("tabindex", "-1");
79         edge.setAttribute("data-destination-node", destStepId);
80         edge.setAttribute("data-destination-port", destPort);
81         edge.setAttribute("data-source-port", sourcePort);
82         edge.setAttribute("data-source-node", sourceStepId);
83         edge.setAttribute("data-source-connection", connectionIDs.source!);
84         edge.setAttribute("data-destination-connection", connectionIDs.destination!);
85
86         edge.innerHTML = `
87             <path class="sub-edge outer" d="${pathStr}"></path>
88             <path class="sub-edge inner" d="${pathStr}"></path>
89         `;
90
91         return edge;
92     }
93
94     static spawnBetweenConnectionIDs(root: SVGElement, source: string, destination: string) {
95
96         if (source.startsWith("in")) {
97             const tmp   = source;
98             source      = destination;
99             destination = tmp;
100         }
101
102         const sourceNode      = root.querySelector(`.port[data-connection-id="${source}"]`) as SVGGElement;
103         const destinationNode = root.querySelector(`.port[data-connection-id="${destination}"]`) as SVGAElement;
104
105         const sourceCTM = Geometry.getTransformToElement(sourceNode, root);
106         const destCTM   = Geometry.getTransformToElement(destinationNode, root);
107         const path      = IOPort.makeConnectionPath(sourceCTM.e, sourceCTM.f, destCTM.e, destCTM.f);
108
109         // If there is already a connection between these ports, update that one instead
110         const existingEdge = root.querySelector(`.edge[data-source-connection="${source}"][data-destination-connection="${destination}"]`);
111         if (existingEdge) {
112             existingEdge.querySelectorAll(".sub-edge").forEach(sub => sub.setAttribute("d", path!));
113             return existingEdge;
114         }
115
116         const edge = Edge.spawn(path, {
117             source,
118             destination
119         });
120
121         const firstNode = root.querySelector(".node");
122         root.insertBefore(edge, firstNode);
123
124         return edge;
125     }
126
127     static findEdge(root: any, sourceConnectionID: string, destinationConnectionID: string) {
128         return root.querySelector(`[data-source-connection="${sourceConnectionID}"][data-destination-connection="${destinationConnectionID}"]`);
129     }
130
131     static parseConnectionID(cid: string) {
132         const [side, stepID, portID] = (cid || "//").split("/");
133         return {side, stepID, portID};
134     }
135 }