Add radix cwl graph visualization
[arvados-workbench2.git] / src / lib / cwl-svg / plugins / validate / validate.ts
diff --git a/src/lib/cwl-svg/plugins/validate/validate.ts b/src/lib/cwl-svg/plugins/validate/validate.ts
new file mode 100644 (file)
index 0000000..333af15
--- /dev/null
@@ -0,0 +1,91 @@
+import {Edge}          from "cwlts/models";
+import {PluginBase}    from "../plugin-base";
+import { Workflow } from "~/lib/cwl-svg";
+
+export class SVGValidatePlugin extends PluginBase {
+
+    private modelDisposers: any[] = [];
+
+    /** Map of CSS classes attached by this plugin */
+    private css = {
+        plugin: "__plugin-validate",
+        invalid: "__validate-invalid"
+    };
+
+    registerWorkflow(workflow: Workflow): void {
+        super.registerWorkflow(workflow);
+
+        // add plugin specific class to the svgRoot for scoping
+        this.workflow.svgRoot.classList.add(this.css.plugin);
+    }
+
+    afterModelChange(): void {
+
+        this.disposeModelListeners();
+
+        // add listener for all subsequent edge validation
+        const update = this.workflow.model.on("connections.updated", this.renderEdgeValidation.bind(this));
+        const create = this.workflow.model.on("connection.create", this.renderEdgeValidation.bind(this));
+
+        this.modelDisposers.concat([update.dispose, create.dispose]);
+    }
+
+    destroy(): void {
+        this.disposeModelListeners();
+    }
+
+    afterRender(): void {
+        // do initial validation rendering for edges
+        this.renderEdgeValidation();
+    }
+
+    onEditableStateChange(enabled: boolean): void {
+
+        if (enabled) {
+            // only show validation if workflow is editable
+            this.renderEdgeValidation();
+        } else {
+            this.removeClasses(this.workflow.workflow.querySelectorAll(".edge"))
+        }
+    }
+
+    private disposeModelListeners(): void {
+        for (let disposeListener of this.modelDisposers) {
+            disposeListener();
+        }
+        this.modelDisposers = [];
+    }
+
+    private removeClasses(edges: NodeListOf<Element>): void {
+        // remove validity class on all edges
+        for (const e of (edges as any)) {
+            e.classList.remove(this.css.invalid);
+        }
+    }
+
+    private renderEdgeValidation(): void {
+        const graphEdges: any = this.workflow.workflow.querySelectorAll(".edge") as NodeListOf<Element>;
+
+        this.removeClasses(graphEdges);
+
+        // iterate through all modal connections
+        this.workflow.model.connections.forEach((e: Edge) => {
+            // if the connection isn't valid (should be colored on graph)
+            if (!e.isValid) {
+
+                // iterate through edges on the svg
+                for (const ge of graphEdges) {
+                    const sourceNodeID      = ge.getAttribute("data-source-connection");
+                    const destinationNodeID = ge.getAttribute("data-destination-connection");
+
+                    // compare invalid edge source/destination with svg edge
+                    if (e.source.id === sourceNodeID && e.destination.id === destinationNodeID) {
+                        // if its a match, tag it with the appropriate class and break from the loop
+                        ge.classList.add(this.css.invalid);
+                        break;
+                    }
+                }
+            }
+        });
+    }
+}