From 0ea29f544e1de6f8665e366b8246c31f92529121 Mon Sep 17 00:00:00 2001
From: Matthias Konitzny <konitzny@ibr.cs.tu-bs.de>
Date: Mon, 28 Mar 2022 15:58:13 +0200
Subject: [PATCH] The FilterOverlay is now a React component. Renamed the
 overlays directory to components. Typescript now compiles to es6 instead of
 es5 (es6 content gets translated by babel)

---
 src/display/components/filteroverlay.tsx      | 140 ++++++++++++++++++
 .../{overlays => components}/neighbors.js     |   0
 .../{overlays => components}/nodeinfo.js      |   0
 src/display/display.css                       |   1 +
 src/display/display.tsx                       |  27 +++-
 src/display/overlays/filteroverlay.js         |  79 ----------
 tsconfig.json                                 |   2 +-
 7 files changed, 164 insertions(+), 85 deletions(-)
 create mode 100644 src/display/components/filteroverlay.tsx
 rename src/display/{overlays => components}/neighbors.js (100%)
 rename src/display/{overlays => components}/nodeinfo.js (100%)
 delete mode 100644 src/display/overlays/filteroverlay.js

diff --git a/src/display/components/filteroverlay.tsx b/src/display/components/filteroverlay.tsx
new file mode 100644
index 0000000..c087fd6
--- /dev/null
+++ b/src/display/components/filteroverlay.tsx
@@ -0,0 +1,140 @@
+import React from "react";
+import Graph from "../graph";
+import PropTypes, { InferType } from "prop-types";
+
+export { FilterOverlay };
+
+class Link extends React.Component<
+    InferType<typeof Link.propTypes>,
+    InferType<typeof Link.stateTypes>
+> {
+    static propTypes = {
+        type: PropTypes.string.isRequired,
+        color: PropTypes.string.isRequired,
+        width: PropTypes.number.isRequired,
+        toggledCallback: PropTypes.func,
+    };
+
+    static stateTypes = {
+        visible: PropTypes.bool,
+    };
+
+    constructor(props: InferType<typeof Link.propTypes>) {
+        super(props);
+        this.state = { visible: true };
+        this.handleClick = this.handleClick.bind(this);
+    }
+
+    handleClick() {
+        const callback = this.props.toggledCallback || null;
+        if (callback) {
+            callback(this.props.type);
+        }
+        this.setState(
+            (
+                state: InferType<typeof Link.stateTypes>,
+                props: InferType<typeof Link.propTypes>
+            ) => ({ visible: !state.visible })
+        );
+    }
+
+    render() {
+        const opacity = this.state.visible ? 1.0 : 0.4;
+        return (
+            <div
+                className={"relation"}
+                style={{ opacity: opacity }}
+                onClick={this.handleClick}
+            >
+                <p>{this.props.type}</p>
+                <div
+                    className={"rel-container"}
+                    style={{
+                        width: this.props.width + "px",
+                        backgroundColor: this.props.color,
+                    }}
+                />
+            </div>
+        );
+    }
+}
+
+/**
+ * Represents an overlay showing the meaning of different link/node colors.
+ * Offers the ability to toggle certain types.
+ */
+class FilterOverlay extends React.Component<
+    InferType<typeof FilterOverlay.propTypes>,
+    unknown
+> {
+    static propTypes = {
+        graph: PropTypes.instanceOf(Graph).isRequired,
+        type: PropTypes.string.isRequired,
+    };
+
+    /**
+     * Callback which gets called when the visibility of a category has changed.
+     */
+    filterChangedCallback: (type: string) => void;
+
+    constructor(props: InferType<typeof FilterOverlay.propTypes>) {
+        super(props);
+        this.filterChangedCallback = (type) => void type;
+        this.toggleVisibility = this.toggleVisibility.bind(this);
+    }
+
+    /**
+     * Renders an element for each category, which enables the user to toggle
+     * the corresponding visibility of assigned nodes.
+     */
+    renderLinks() {
+        const classes =
+            this.props.type === "link"
+                ? this.props.graph.getLinkClasses()
+                : this.props.graph.getNodeClasses();
+
+        const chars = Math.max(
+            ...classes.map(function (c: string) {
+                return c.length;
+            })
+        );
+
+        const links = [];
+        for (const link of classes) {
+            links.push(
+                <Link
+                    type={link}
+                    color={
+                        this.props.type == "link"
+                            ? this.props.graph.edgeColors[link]
+                            : this.props.graph.nodeColors[link]
+                    }
+                    width={10 * chars}
+                    toggledCallback={this.toggleVisibility}
+                    key={link}
+                />
+            );
+        }
+        return links;
+    }
+
+    render() {
+        return <div className={"link-overlay"}>{this.renderLinks()}</div>;
+    }
+
+    /**
+     * Event handler for the click event of the link type divs.
+     * Toggles the visibility of certain edge types.
+     */
+    toggleVisibility(type: string) {
+        if (this.props.type === "link") {
+            this.props.graph.toggleLinkVisibility(type);
+        } else {
+            this.props.graph.toggleNodeVisibility(type);
+        }
+        this.filterChangedCallback(type);
+
+        // TODO: This is really ugly.
+        this.props.graph.infoOverlay.bottomMenu.toggleCategory(type);
+    }
+}
diff --git a/src/display/overlays/neighbors.js b/src/display/components/neighbors.js
similarity index 100%
rename from src/display/overlays/neighbors.js
rename to src/display/components/neighbors.js
diff --git a/src/display/overlays/nodeinfo.js b/src/display/components/nodeinfo.js
similarity index 100%
rename from src/display/overlays/nodeinfo.js
rename to src/display/components/nodeinfo.js
diff --git a/src/display/display.css b/src/display/display.css
index 21d8b9d..7aeadfc 100644
--- a/src/display/display.css
+++ b/src/display/display.css
@@ -153,6 +153,7 @@
     left: 0;
     display: block;
     background-color: rgba(0, 0, 0, 0.6);
+    z-index: 1;
 }
 
 .relation {
diff --git a/src/display/display.tsx b/src/display/display.tsx
index 60bf7aa..c7a33aa 100644
--- a/src/display/display.tsx
+++ b/src/display/display.tsx
@@ -1,13 +1,12 @@
 import * as Config from "../config";
-import { FilterOverlay } from "./overlays/filteroverlay";
-import { NodeInfoOverlay } from "./overlays/nodeinfo";
+import { FilterOverlay } from "./components/filteroverlay";
+import { NodeInfoOverlay } from "./components/nodeinfo";
 import Graph from "./graph";
 import React from "react";
 import screenfull from "screenfull";
 
 class Display extends React.PureComponent {
     graph: Graph;
-    filterOverlay: FilterOverlay;
     infoOverlay: NodeInfoOverlay;
     sceneNode: HTMLElement;
 
@@ -16,16 +15,30 @@ class Display extends React.PureComponent {
             Config.SPACE,
             this.loadGraphComponents.bind(this)
         );
-        this.filterOverlay = new FilterOverlay(this.graph, "node");
         this.infoOverlay = new NodeInfoOverlay(this.graph);
         this.graph.infoOverlay = this.infoOverlay;
     }
 
+    /**
+     * Callback which is called when the underlying graph has finished loading
+     * its content
+     */
     loadGraphComponents() {
-        this.filterOverlay.create();
         this.infoOverlay.create();
 
         this.sceneNode = document.getElementById("kg-display");
+
+        /*
+         * This call is necessary since the graph can only load after the
+         * component has mounted (the graph requires a fixed <div> element),
+         * but some components rely on the graph being loaded.
+         * The fix is to exclude all components from rendering until the graph
+         * finished loading.
+         * TODO: Once all components have been transformed to react components
+         *  this call could be substituted by changing the object state (which
+         *  causes a re-render)
+         */
+        this.forceUpdate();
         // filterOverlay.filterChangedCallback = (cls) =>
         //     infoOverlay.bottomMenu.toggleTabVisibility(cls);
         // createFullScreenButton();
@@ -50,6 +63,10 @@ class Display extends React.PureComponent {
                 >
                     <p>&#10530;</p>
                 </div>
+                {this.graph && (
+                    <FilterOverlay graph={this.graph} type={"node"} />
+                )}
+
                 <div id="3d-graph" />
             </div>
         );
diff --git a/src/display/overlays/filteroverlay.js b/src/display/overlays/filteroverlay.js
deleted file mode 100644
index 06d6804..0000000
--- a/src/display/overlays/filteroverlay.js
+++ /dev/null
@@ -1,79 +0,0 @@
-import * as Helpers from "../helpers";
-import jQuery from "jquery";
-
-export { FilterOverlay };
-
-/**
- * Represents an overlay showing the meaning of different link/node colors.
- * Offers the ability to toggle certain types.
- */
-class FilterOverlay {
-    /**
-     * Creates the overlay for a given graph object.
-     * @param {Graph} graph The graph object.
-     * @param {String} type The selection type. Can be "link" or "node".
-     */
-    constructor(graph, type = "link") {
-        this.graph = graph;
-        this.type = type;
-        this.filterChangedCallback = (type) => void type;
-    }
-
-    /**
-     * Creates the overlay based on the edges and nodes of the graph object.
-     * Must be called after the graph has been initialized.
-     */
-    create() {
-        const sceneNode = Helpers.getCanvasDivNode();
-        const overlayNode = document.createElement("div");
-        overlayNode.className = "link-overlay";
-        sceneNode.insertBefore(overlayNode, sceneNode.childNodes[2]);
-
-        const classes =
-            this.type === "link"
-                ? this.graph.getLinkClasses()
-                : this.graph.getNodeClasses();
-        const chars = Math.max.apply(
-            Math,
-            classes.map(function (c) {
-                return c.length;
-            })
-        );
-
-        for (const link of classes) {
-            const relation = Helpers.createDiv("relation", overlayNode, {
-                type: link, // Attach the link type to the relation div object
-                innerHTML: "<p>" + link + "</p>",
-            });
-            jQuery(relation).click((event) => this.toggleVisibility(event));
-
-            const colorStrip = Helpers.createDiv("rel-container", relation);
-            colorStrip.style.backgroundColor =
-                this.type === "link"
-                    ? this.graph.edgeColors[link]
-                    : this.graph.nodeColors[link];
-            colorStrip.style.width = 10 * chars + "px";
-        }
-    }
-
-    /**
-     * Event handler for the click event of the link type divs.
-     * Toggles the visibility of certain edge types.
-     * @param {MouseEvent} event
-     */
-    toggleVisibility(event) {
-        const target = event.currentTarget;
-        if (this.type === "link") {
-            this.graph.toggleLinkVisibility(target.type);
-        } else {
-            this.graph.toggleNodeVisibility(target.type);
-        }
-        this.filterChangedCallback(target.type);
-        if (getComputedStyle(target).opacity == 1.0) {
-            target.style.opacity = 0.4;
-        } else {
-            target.style.opacity = 1.0;
-        }
-        this.graph.infoOverlay.bottomMenu.toggleCategory(target.type);
-    }
-}
diff --git a/tsconfig.json b/tsconfig.json
index 00c7971..f187ed7 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -4,7 +4,7 @@
     "noImplicitAny": true,
     "strictNullChecks": false,
     "module": "es6",
-    "target": "es5",
+    "target": "es6",
     "jsx": "preserve",
     "allowJs": true,
     "moduleResolution": "node",
-- 
GitLab