From 6a97f6aef5bb410afad2f8f059841e13d9a7f1d9 Mon Sep 17 00:00:00 2001
From: Matthias Konitzny <konitzny@ibr.cs.tu-bs.de>
Date: Wed, 4 Aug 2021 14:50:40 +0200
Subject: [PATCH] Introduction of npm toolchain including prettier, eslint and
 parcel.

---
 .eslintrc.json         |  14 ++++
 .prettierrc.json       |   3 +
 config.js              |  18 ++---
 display/graph.js       | 169 ++++++++++++++++++++++++++---------------
 display/helpers.js     |  12 +--
 display/infooverlay.js | 122 ++++++++++++++---------------
 display/linkoverlay.js |  39 ++++++----
 index.js               |   3 +
 kg-style.css           |  26 +++----
 knowledge-space.php    |  41 ++++------
 package.json           |  44 +++++++++++
 11 files changed, 300 insertions(+), 191 deletions(-)
 create mode 100644 .eslintrc.json
 create mode 100644 .prettierrc.json
 create mode 100644 index.js
 create mode 100644 package.json

diff --git a/.eslintrc.json b/.eslintrc.json
new file mode 100644
index 0000000..812cc42
--- /dev/null
+++ b/.eslintrc.json
@@ -0,0 +1,14 @@
+{
+  "env": {
+    "es6": true,
+    "browser": true
+  },
+  "extends": "eslint:recommended",
+  "parserOptions": {
+    "ecmaVersion": 8,
+    "sourceType": "module"
+  },
+  "rules": {
+    "no-console": 0
+  }
+}
\ No newline at end of file
diff --git a/.prettierrc.json b/.prettierrc.json
new file mode 100644
index 0000000..b6c5694
--- /dev/null
+++ b/.prettierrc.json
@@ -0,0 +1,3 @@
+{
+  "tabWidth": 4
+}
\ No newline at end of file
diff --git a/config.js b/config.js
index a1070ec..9d779ce 100644
--- a/config.js
+++ b/config.js
@@ -1,9 +1,9 @@
-const COLOR_PALETTE = [
-    'rgb(104, 169, 77)',
-    'rgb(102, 75, 154)',
-    'rgb(41, 171, 226)',
-    'rgb(224, 133, 35)',
-    'rgb(214, 207, 126)',
-    'rgb(239, 65, 35)',
-    'rgb(255, 255, 255)'
-];
\ No newline at end of file
+export const COLOR_PALETTE = [
+    "rgb(104, 169, 77)",
+    "rgb(102, 75, 154)",
+    "rgb(41, 171, 226)",
+    "rgb(224, 133, 35)",
+    "rgb(214, 207, 126)",
+    "rgb(239, 65, 35)",
+    "rgb(255, 255, 255)",
+];
diff --git a/display/graph.js b/display/graph.js
index da72f0b..e6eb010 100644
--- a/display/graph.js
+++ b/display/graph.js
@@ -1,3 +1,13 @@
+import * as Config from "../config";
+import * as Helpers from "./helpers";
+import { InfoOverlay } from "./infooverlay";
+import { LinkOverlay } from "./linkoverlay";
+
+import * as THREE from 'three';
+import ForceGraph3D from '3d-force-graph';
+
+import { CSS2DRenderer } from 'three/examples/jsm/renderers/CSS2DRenderer.js'
+import { CSS3DRenderer, CSS3DSprite } from 'three/examples/jsm/renderers/CSS3DRenderer.js'
 
 class Graph {
     constructor(dataUrl) {
@@ -16,34 +26,37 @@ class Graph {
         this.edgeTypeVisibility = {};
 
         this.loadGraph(dataUrl);
-
     }
 
     async loadGraph(dataUrl) {
-        this.gData = await fetch(dataUrl).then(res => res.json())
-        this.graph = ForceGraph3D({extraRenderers: [new THREE.CSS2DRenderer(), new THREE.CSS3DRenderer()]})
-        (document.getElementById('3d-graph'))
+        this.gData = await fetch(dataUrl).then((res) => res.json());
+        this.graph = ForceGraph3D({
+            extraRenderers: [
+                new CSS2DRenderer(),
+                new CSS3DRenderer(),
+            ],
+        })(document.getElementById("3d-graph"))
             .graphData(this.gData)
-            .nodeLabel('id')
-            .nodeAutoColorBy('group')
-            .nodeColor(node => this.getNodeColor(node))
-            .linkWidth(link => this.getLinkWidth(link))
-            .onNodeClick(node => {
+            .nodeLabel("id")
+            .nodeAutoColorBy("group")
+            .nodeColor((node) => this.getNodeColor(node))
+            .linkWidth((link) => this.getLinkWidth(link))
+            .onNodeClick((node) => {
                 this.focusOnNode(node);
                 this.infooverlay.updateInfoOverlay(node);
             })
-            .onNodeHover(node => {
+            .onNodeHover((node) => {
                 this.onNodeHover(node);
                 this.updateHighlight();
             })
-            .onLinkHover(link => this.onLinkHover(link))
-            .linkColor(link => this.getLinkColor(link))
+            .onLinkHover((link) => this.onLinkHover(link))
+            .linkColor((link) => this.getLinkColor(link))
             .linkOpacity(0.8)
             .nodeThreeObjectExtend(false)
-            .nodeThreeObject(node => this.drawNode(node))
+            .nodeThreeObject((node) => this.drawNode(node))
             .onEngineTick(() => this.initializeModel())
-            .width(getWidth())
-            .height(getHeight());
+            .width(Helpers.getWidth())
+            .height(Helpers.getHeight());
     }
 
     initializeModel() {
@@ -59,20 +72,26 @@ class Graph {
             document.addEventListener("fullscreenchange", () => this.resize());
             window.addEventListener("resize", () => this.resize());
 
-            this.getLinkClasses().forEach(item => this.edgeTypeVisibility[item] = true);
+            this.getLinkClasses().forEach(
+                (item) => (this.edgeTypeVisibility[item] = true)
+            );
             this.firstTick = false;
         }
     }
 
     getNodeColor(node) {
-        return this.highlightNodes.has(node) ? node === hoverNode ? 'rgb(255,0,0,1)' : 'rgba(255,160,0,0.8)' : 'rgba(0,255,255,0.6)';
+        return this.highlightNodes.has(node)
+            ? node === hoverNode
+                ? "rgb(255,0,0,1)"
+                : "rgba(255,160,0,0.8)"
+            : "rgba(0,255,255,0.6)";
     }
 
     getLinkColor(link) {
-        if ('type' in link) {
-            return this.edgeColors[link.type]
+        if ("type" in link) {
+            return this.edgeColors[link.type];
         }
-        return 'rgb(255, 255, 255)'
+        return "rgb(255, 255, 255)";
     }
 
     getLinkWidth(link) {
@@ -81,20 +100,28 @@ class Graph {
 
     getLinkClasses() {
         const linkClasses = [];
-        this.graph.graphData().links.forEach(link => linkClasses.push(link.type));
-        return [... new Set(linkClasses)];
+        this.graph
+            .graphData()
+            .links.forEach((link) => linkClasses.push(link.type));
+        return [...new Set(linkClasses)];
     }
 
     onNodeHover(node) {
         // no state change
-        if ((!node && !this.highlightNodes.size) || (node && this.hoverNode === node)) return;
+        if (
+            (!node && !this.highlightNodes.size) ||
+            (node && this.hoverNode === node)
+        )
+            return;
 
         this.highlightNodes.clear();
         this.highlightLinks.clear();
         if (node) {
             this.highlightNodes.add(node);
-            node.neighbors.forEach(neighbor => this.highlightNodes.add(neighbor));
-            node.links.forEach(link => this.highlightLinks.add(link));
+            node.neighbors.forEach((neighbor) =>
+                this.highlightNodes.add(neighbor)
+            );
+            node.links.forEach((link) => this.highlightLinks.add(link));
         }
 
         this.hoverNode = node || null;
@@ -120,12 +147,16 @@ class Graph {
 
         // Aim at node from outside it
         const distance = 250;
-        const distRatio = 1 + distance/Math.hypot(node.x, node.y, node.z);
+        const distRatio = 1 + distance / Math.hypot(node.x, node.y, node.z);
 
         this.graph.cameraPosition(
-            { x: node.x * distRatio, y: node.y * distRatio, z: node.z * distRatio }, // new position
+            {
+                x: node.x * distRatio,
+                y: node.y * distRatio,
+                z: node.z * distRatio,
+            }, // new position
             node, // lookAt ({ x, y, z })
-            1000  // ms transition duration
+            1000 // ms transition duration
         );
     }
 
@@ -153,10 +184,10 @@ class Graph {
 
     removeFloatingNodes() {
         const gData = this.graph.graphData();
-        const nodes = gData.nodes.filter(node => node.neighbors.length > 0);
+        const nodes = gData.nodes.filter((node) => node.neighbors.length > 0);
         const data = {
             nodes: nodes,
-            links: gData.links
+            links: gData.links,
         };
         this.graph.graphData(data);
     }
@@ -164,7 +195,9 @@ class Graph {
     updateGraphData() {
         const data = {
             nodes: this.gData.nodes,
-            links: this.gData.links.filter(l => this.edgeTypeVisibility[l.type])
+            links: this.gData.links.filter(
+                (l) => this.edgeTypeVisibility[l.type]
+            ),
         };
         this.graph.graphData(data);
     }
@@ -182,7 +215,7 @@ class Graph {
         // cross-link node objects
         this.resetNodeData();
 
-        gData.links.forEach(link => {
+        gData.links.forEach((link) => {
             const a = link.source;
             const b = link.target;
             a.neighbors.push(b);
@@ -190,7 +223,7 @@ class Graph {
             a.links.push(link);
             b.links.push(link);
         });
-        this.graph.graphData(gData)
+        this.graph.graphData(gData);
     }
 
     updateHighlight() {
@@ -203,18 +236,18 @@ class Graph {
 
     updateNodeMap() {
         const gData = this.graph.graphData();
-        gData.nodes.forEach(node => {
+        gData.nodes.forEach((node) => {
             this.idToNode[node.id] = node;
-        })
+        });
     }
 
     resize() {
-        if(document.fullscreenElement == getCanvasDivNode()) {
+        if (document.fullscreenElement == Helpers.getCanvasDivNode()) {
             this.graph.height(screen.height);
             this.graph.width(screen.width);
         } else {
             this.graph.height(window.innerHeight - 200);
-            this.graph.width(getWidth());
+            this.graph.width(Helpers.getWidth());
         }
     }
 
@@ -223,7 +256,10 @@ class Graph {
         //const planeGeometry = new THREE.PlaneGeometry(1000, 1000, 1, 1);
         const loader = new THREE.TextureLoader();
         //const planeMaterial = new THREE.MeshLambertMaterial({color: 0xFF0000, side: THREE.DoubleSide}); //THREE.BackSide
-        const planeMaterial = new THREE.MeshBasicMaterial({map: loader.load(plugin_path + 'backgrounds/background_4.jpg'), side: THREE.DoubleSide}); //THREE.BackSide
+        const planeMaterial = new THREE.MeshBasicMaterial({
+            map: loader.load(plugin_path + "backgrounds/background_4.jpg"),
+            side: THREE.DoubleSide,
+        }); //THREE.BackSide
         const mesh = new THREE.Mesh(sphereGeometry, planeMaterial);
         mesh.position.set(0, 0, 0);
         //mesh.rotation.set(0.5 * Math.PI, 0, 0);
@@ -232,55 +268,67 @@ class Graph {
     }
 
     mapEdgeColors() {
-        const linkClasses = this.getLinkClasses()
+        const linkClasses = this.getLinkClasses();
         for (let i = 0; i < linkClasses.length; i++) {
-            this.edgeColors[linkClasses[i]] = COLOR_PALETTE[i % COLOR_PALETTE.length]
+            this.edgeColors[linkClasses[i]] =
+                Config.COLOR_PALETTE[i % Config.COLOR_PALETTE.length];
         }
     }
 
     drawNode(node) {
         // Draw node as label + image
-        const nodeDiv = document.createElement('div');
+        const nodeDiv = document.createElement("div");
         const group = new THREE.Group();
 
-        const labelDiv = document.createElement('div')
+        const labelDiv = document.createElement("div");
         labelDiv.textContent = node.name;
         labelDiv.style.color = node.color;
-        labelDiv.className = 'node-label';
+        labelDiv.className = "node-label";
         nodeDiv.appendChild(labelDiv);
 
-        const cssobj = new THREE.CSS3DSprite(nodeDiv);
+        const cssobj = new CSS3DSprite(nodeDiv);
         cssobj.scale.set(0.25, 0.25, 0.25);
         cssobj.position.set(0, -6, 0);
-        group.add(cssobj)
+        group.add(cssobj);
 
         // Draw node circle image
         const textureLoader = new THREE.TextureLoader();
-        const imageAlpha = textureLoader.load(plugin_path + 'datasets/images/alpha.png');
+        const imageAlpha = textureLoader.load(
+            plugin_path + "datasets/images/alpha.png"
+        );
         let imageTexture = null;
 
-        if ('image' in node) {
-            imageTexture = textureLoader.load(plugin_path + 'datasets/images/' + node.image);
+        if ("image" in node) {
+            imageTexture = textureLoader.load(
+                plugin_path + "datasets/images/" + node.image
+            );
         } else {
-            imageTexture = textureLoader.load(plugin_path + 'datasets/images/default.jpg');
+            imageTexture = textureLoader.load(
+                plugin_path + "datasets/images/default.jpg"
+            );
         }
 
-        const material = new THREE.SpriteMaterial({map: imageTexture, alphaMap: imageAlpha, transparent: true,
-            alphaTest: 0.2, depthWrite:false, depthTest: false});
+        const material = new THREE.SpriteMaterial({
+            map: imageTexture,
+            alphaMap: imageAlpha,
+            transparent: true,
+            alphaTest: 0.2,
+            depthWrite: false,
+            depthTest: false,
+        });
         const sprite = new THREE.Sprite(material);
         sprite.renderOrder = 999; // This may not be optimal. But it allows us to render the sprite on top of everything else.
 
-        if ('image' in node) {
+        if ("image" in node) {
             sprite.scale.set(20, 20);
         } else {
             sprite.scale.set(5, 5);
         }
 
-        group.add(sprite)
+        group.add(sprite);
 
         return group;
     }
-
 }
 
 function loadComponents() {
@@ -289,15 +337,14 @@ function loadComponents() {
     createFullScreenButton();
 }
 
-
 function createFullScreenButton() {
-    const sceneNode = getCanvasDivNode();
-    const overlayNode = document.createElement('div');
-    overlayNode.className = 'fullscreen-button';
-    overlayNode.innerHTML = '<p>&#10530;</p>';
+    const sceneNode = Helpers.getCanvasDivNode();
+    const overlayNode = document.createElement("div");
+    overlayNode.className = "fullscreen-button";
+    overlayNode.innerHTML = "<p>&#10530;</p>";
     overlayNode.addEventListener("click", function () {
         if (!document.fullscreenElement) {
-            getCanvasDivNode().requestFullscreen();
+            Helpers.getCanvasDivNode().requestFullscreen();
         } else {
             document.exitFullscreen();
         }
@@ -306,7 +353,7 @@ function createFullScreenButton() {
     sceneNode.appendChild(overlayNode);
 }
 
-const dataUrl = plugin_path + 'datasets/aud1.json'
+const dataUrl = plugin_path + "datasets/aud1.json";
 G = new Graph(dataUrl);
 linkoverlay = new LinkOverlay(G);
 infooverlay = new InfoOverlay(G);
diff --git a/display/helpers.js b/display/helpers.js
index 38d9e76..e4b7d94 100644
--- a/display/helpers.js
+++ b/display/helpers.js
@@ -1,15 +1,15 @@
+export {getWidth, getHeight, getCanvasDivNode}
+
 
 function getWidth() {
-    return document.getElementById('3d-graph').offsetWidth;
+    return document.getElementById("3d-graph").offsetWidth;
 }
 
-
 function getHeight() {
-    return window.innerHeight - 200
+    return window.innerHeight - 200;
 }
 
-
 function getCanvasDivNode() {
-    const domNode = document.getElementById('3d-graph');
+    const domNode = document.getElementById("3d-graph");
     return domNode.firstChild.firstChild.firstChild;
-}
\ No newline at end of file
+}
diff --git a/display/infooverlay.js b/display/infooverlay.js
index 3f8598f..d0a3c7d 100644
--- a/display/infooverlay.js
+++ b/display/infooverlay.js
@@ -1,4 +1,6 @@
+import * as Helpers from "./helpers";
 
+export { InfoOverlay };
 
 class InfoOverlay {
     constructor(graph) {
@@ -13,40 +15,36 @@ class InfoOverlay {
     create() {
         const overlayDiv = this.createOverlayMainDiv();
         this.createOverlayElements(overlayDiv);
-        this.createBottomMenu(overlayDiv)
+        this.createBottomMenu(overlayDiv);
 
-        jQuery('#infoOverlayCloseButton').click(function () {
-            jQuery('#infoOverlay').slideUp('fast');
+        jQuery("#infoOverlayCloseButton").click(function () {
+            jQuery("#infoOverlay").slideUp("fast");
         });
-
     }
 
     createBottomMenu(overlayNode) {
-
-        const bottomContainerDiv = document.createElement('div');
-        bottomContainerDiv.className = 'bottom-container';
+        const bottomContainerDiv = document.createElement("div");
+        bottomContainerDiv.className = "bottom-container";
         overlayNode.appendChild(bottomContainerDiv);
 
-        const bottomContainerNavDiv = document.createElement('div');
-        bottomContainerNavDiv.className = 'bottom-container-nav';
+        const bottomContainerNavDiv = document.createElement("div");
+        bottomContainerNavDiv.className = "bottom-container-nav";
         bottomContainerDiv.appendChild(bottomContainerNavDiv);
 
-        const bottomContainerLinkDiv = document.createElement('div');
-        bottomContainerLinkDiv.className = 'bottom-container-links';
+        const bottomContainerLinkDiv = document.createElement("div");
+        bottomContainerLinkDiv.className = "bottom-container-links";
         bottomContainerDiv.appendChild(bottomContainerLinkDiv);
 
-        for(const [cls, color] of Object.entries(this.graph.edgeColors)) {
-
-            const navTab = document.createElement('div');
-            navTab.className = 'bottom-container-nav-tab';
+        for (const [cls, color] of Object.entries(this.graph.edgeColors)) {
+            const navTab = document.createElement("div");
+            navTab.className = "bottom-container-nav-tab";
             navTab.innerText = cls.slice(0, 3);
             navTab.style = "background-color: " + color;
             navTab.edgeType = cls;
-            //tablink.setAttribute("onclick", "openTab(event, " + linkType + "_id)"); //unusual function call
             bottomContainerNavDiv.appendChild(navTab);
-            jQuery(navTab).click(event => this.openTab(event))
+            jQuery(navTab).click((event) => this.openTab(event));
 
-            const tabContent = document.createElement('div');
+            const tabContent = document.createElement("div");
             tabContent.className = "bottom-container-tab-content";
             tabContent.id = cls + "_id_content";
             tabContent.style = "background-color: " + color;
@@ -60,7 +58,6 @@ class InfoOverlay {
         this.activeTabContent.classList.add("active-tab-content");
         this.activeTabNav.classList.add("active-tab-nav");
         this.activeTabNav.innerText = this.activeTabNav.edgeType;
-
     }
 
     openTab(event) {
@@ -80,70 +77,76 @@ class InfoOverlay {
     }
 
     createOverlayMainDiv() {
-        const sceneNode = getCanvasDivNode();
-        const overlayNode = document.createElement('div');
-        overlayNode.id = 'infoOverlay'
-        overlayNode.className = 'detail-view';
+        const sceneNode = Helpers.getCanvasDivNode();
+        const overlayNode = document.createElement("div");
+        overlayNode.id = "infoOverlay";
+        overlayNode.className = "detail-view";
         //overlayNode.innerText = 'Hello there!';
         sceneNode.insertBefore(overlayNode, sceneNode.childNodes[2]);
         return overlayNode;
     }
 
     createOverlayElements(overlayNode) {
-        const close = document.createElement('div');
-        close.innerHTML = '<p>&#10006;</p>';
-        close.id = 'infoOverlayCloseButton';
-        close.className = 'close-button';
+        const close = document.createElement("div");
+        close.innerHTML = "<p>&#10006;</p>";
+        close.id = "infoOverlayCloseButton";
+        close.className = "close-button";
         overlayNode.appendChild(close);
 
-        const infoArea = document.createElement('div');
-        infoArea.className = 'detail-view-info-area';
+        const infoArea = document.createElement("div");
+        infoArea.className = "detail-view-info-area";
         overlayNode.appendChild(infoArea);
 
-        const topArea = document.createElement('div');
-        topArea.className = 'detail-view-top-area';
+        const topArea = document.createElement("div");
+        topArea.className = "detail-view-top-area";
         infoArea.appendChild(topArea);
 
-        const nodeImage = document.createElement('img');
-        nodeImage.id = 'infoOverlayImage';
-        nodeImage.src = plugin_path + 'datasets/images/default.jpg';
-        nodeImage.className = 'detail-view-image';
+        const nodeImage = document.createElement("img");
+        nodeImage.id = "infoOverlayImage";
+        nodeImage.src = plugin_path + "datasets/images/default.jpg";
+        nodeImage.className = "detail-view-image";
         topArea.appendChild(nodeImage);
 
-        const headline = document.createElement('h2');
-        headline.id = 'infoOverlayHeadline';
-        headline.innerText = 'Default Text';
-        headline.className = 'detail-view-headline';
+        const headline = document.createElement("h2");
+        headline.id = "infoOverlayHeadline";
+        headline.innerText = "Default Text";
+        headline.className = "detail-view-headline";
         topArea.appendChild(headline);
 
-        const textArea = document.createElement('div');
-        textArea.className = 'detail-view-text-area';
+        const textArea = document.createElement("div");
+        textArea.className = "detail-view-text-area";
         infoArea.appendChild(textArea);
 
-        const description = document.createElement('p');
-        description.id = 'infoOverlayDescription';
-        description.innerText = 'Default Text'
+        const description = document.createElement("p");
+        description.id = "infoOverlayDescription";
+        description.innerText = "Default Text";
         description.setAttribute("overflow-y", "scroll");
         textArea.appendChild(description);
     }
 
     updateInfoOverlay(node) {
-        jQuery('#infoOverlayHeadline').text(node.name);
-        if ('image' in node) {
-            jQuery('#infoOverlayImage').attr('src', plugin_path + 'datasets/images/' + node.image);
+        jQuery("#infoOverlayHeadline").text(node.name);
+        if ("image" in node) {
+            jQuery("#infoOverlayImage").attr(
+                "src",
+                plugin_path + "datasets/images/" + node.image
+            );
         } else {
-            jQuery('#infoOverlayImage').attr('src', plugin_path + 'datasets/images/default.jpg');
+            jQuery("#infoOverlayImage").attr(
+                "src",
+                plugin_path + "datasets/images/default.jpg"
+            );
         }
 
-        if ('description' in node) {
-            jQuery('#infoOverlayDescription').text(node.description);
+        if ("description" in node) {
+            jQuery("#infoOverlayDescription").text(node.description);
         } else {
-            jQuery('#infoOverlayDescription').text('Default Text');
+            jQuery("#infoOverlayDescription").text("Default Text");
         }
 
         this.updateTabs(node);
 
-        jQuery('#infoOverlay').slideDown('fast');
+        jQuery("#infoOverlay").slideDown("fast");
     }
 
     clearTabContentPages() {
@@ -153,23 +156,23 @@ class InfoOverlay {
     }
 
     createReference(target) {
-        const linkDiv = document.createElement('div');      //and if so a new element is created
+        const linkDiv = document.createElement("div");
         linkDiv.className = "link-img";
 
-        if ('image' in target) {
-            const linkImage = document.createElement('img');
-            linkImage.src = plugin_path + 'datasets/images/' + target.image;
+        if ("image" in target) {
+            const linkImage = document.createElement("img");
+            linkImage.src = plugin_path + "datasets/images/" + target.image;
             linkDiv.appendChild(linkImage);
         }
 
-        jQuery(linkDiv).on('click', () => {
+        jQuery(linkDiv).on("click", () => {
             this.graph.focusOnNode(target);
             this.updateInfoOverlay(target);
         });
         return linkDiv;
     }
 
-    updateTabs(node){
+    updateTabs(node) {
         this.clearTabContentPages();
 
         for (const link of node.links) {
@@ -178,5 +181,4 @@ class InfoOverlay {
             this.tabContentPages[link.type].appendChild(reference);
         }
     }
-
-}
\ No newline at end of file
+}
diff --git a/display/linkoverlay.js b/display/linkoverlay.js
index 5d0f084..18e3912 100644
--- a/display/linkoverlay.js
+++ b/display/linkoverlay.js
@@ -1,43 +1,54 @@
+import * as Helpers from "./helpers";
 
+export { LinkOverlay };
 
 class LinkOverlay {
     constructor(graph) {
-        this.graph = graph
+        this.graph = graph;
     }
 
     create() {
-        const sceneNode = getCanvasDivNode();
-        const overlayNode = document.createElement('div');
-        overlayNode.className = 'link-overlay';
+        const sceneNode = Helpers.getCanvasDivNode();
+        const overlayNode = document.createElement("div");
+        overlayNode.className = "link-overlay";
         //overlayNode.innerText = 'Hello there!';
-        sceneNode.insertBefore(overlayNode, sceneNode.childNodes[2])
+        sceneNode.insertBefore(overlayNode, sceneNode.childNodes[2]);
 
         const linkClasses = this.graph.getLinkClasses();
-        const chars = Math.max.apply(Math, linkClasses.map(function (c) { return c.length; }));
+        const chars = Math.max.apply(
+            Math,
+            linkClasses.map(function (c) {
+                return c.length;
+            })
+        );
 
         for (const link of linkClasses) {
-            const relation = document.createElement('div');
-            relation.className = 'relation';
+            const relation = document.createElement("div");
+            relation.className = "relation";
             relation.edgeType = link;
             relation.innerHTML = "<p>" + link + "</p>";
             jQuery(relation).click((event) => this.toggleLinkVisibility(event));
             overlayNode.appendChild(relation);
 
-            const colorStrip = document.createElement('div');
-            colorStrip.className = 'rel-container';
-            colorStrip.style = "background-color: " + this.graph.edgeColors[link] + "; width: " + 10 * chars + "px;";
+            const colorStrip = document.createElement("div");
+            colorStrip.className = "rel-container";
+            colorStrip.style =
+                "background-color: " +
+                this.graph.edgeColors[link] +
+                "; width: " +
+                10 * chars +
+                "px;";
             relation.appendChild(colorStrip);
         }
     }
 
     toggleLinkVisibility(event) {
         const target = event.currentTarget;
-        this.graph.toggleLinkVisibility(target.edgeType)
+        this.graph.toggleLinkVisibility(target.edgeType);
         if (getComputedStyle(target).opacity == 1.0) {
             target.style.opacity = 0.4;
         } else {
             target.style.opacity = 1.0;
         }
     }
-
-}
\ No newline at end of file
+}
diff --git a/index.js b/index.js
new file mode 100644
index 0000000..b9e98c5
--- /dev/null
+++ b/index.js
@@ -0,0 +1,3 @@
+import "./kg-style.css";
+
+import "./display/graph";
diff --git a/kg-style.css b/kg-style.css
index 3549aeb..58f0b45 100644
--- a/kg-style.css
+++ b/kg-style.css
@@ -4,12 +4,12 @@
 
 .detail-view-info-area::-webkit-scrollbar {
     width: 6px;
-    background-color: #F5F5F5;
+    background-color: #f5f5f5;
 }
 
 .detail-view-info-area::-webkit-scrollbar-track {
-    -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.3);
-    background-color: #F5F5F5;
+    -webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3);
+    background-color: #f5f5f5;
 }
 
 .detail-view-info-area::-webkit-scrollbar-thumb {
@@ -19,12 +19,12 @@
 .bottom-container-tab-content::-webkit-scrollbar {
     width: 6px;
     height: 8px;
-    background-color: #F5F5F5;
+    background-color: #f5f5f5;
 }
 
 .bottom-container-tab-content::-webkit-scrollbar-track {
-    -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.3);
-    background-color: #F5F5F5;
+    -webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3);
+    background-color: #f5f5f5;
 }
 
 .bottom-container-tab-content::-webkit-scrollbar-thumb {
@@ -38,7 +38,7 @@
     /*text-align: center;*/
     padding: 1px 4px;
     border-radius: 4px;
-    background-color: rgba(0,0,0,0.7);
+    background-color: rgba(0, 0, 0, 0.7);
     user-select: none;
     z-index: 1;
 }
@@ -109,13 +109,12 @@
 .detail-view-headline {
     font-weight: bold;
     /*text-align: center;*/
-    font-family: CuratorRegular,Helvetica Neue,Helvetica,Arial,sans-serif;
+    font-family: CuratorRegular, Helvetica Neue, Helvetica, Arial, sans-serif;
     word-wrap: break-word;
     hyphens: auto;
-    font-size: 2.0vw;
+    font-size: 2vw;
 }
 
-
 .link-overlay {
     position: absolute;
     bottom: 0;
@@ -125,7 +124,7 @@
     padding: 10px;
     left: 0;
     display: block;
-    background-color: rgba(0,0,0,.6);
+    background-color: rgba(0, 0, 0, 0.6);
 }
 
 .relation {
@@ -134,14 +133,14 @@
     color: #fff;
     font-size: 13px;
     line-height: 20px;
-    font-family: CuratorRegular,Helvetica Neue,Helvetica,Arial,sans-serif;
+    font-family: CuratorRegular, Helvetica Neue, Helvetica, Arial, sans-serif;
     text-transform: uppercase;
     margin-bottom: 6px;
     height: 17px;
     z-index: 100;
     cursor: pointer;
     pointer-events: all;
-    opacity: 1.0;
+    opacity: 1;
 }
 
 .rel-container {
@@ -192,7 +191,6 @@
     display: block;
 }
 
-
 .link-img {
     min-width: 70px;
     width: 70px;
diff --git a/knowledge-space.php b/knowledge-space.php
index a620ae4..a7299cf 100644
--- a/knowledge-space.php
+++ b/knowledge-space.php
@@ -7,12 +7,10 @@ Version: 1.0
 Author: Matthias Konitzny
 */
 
+$GLOBALS['build'] = 'debug';
+
 function ks_add_graph(): string
 {
-    $graph = '<script src="//unpkg.com/3d-force-graph"></script>';
-    $three = '<script src="//unpkg.com/three@0.130.0"></script>';
-    $renderer = '<script src="//unpkg.com/three@0.130.0/examples/js/renderers/CSS2DRenderer.js"></script>';
-    $renderer2 = '<script src="//unpkg.com/three@0.130.0/examples/js/renderers/CSS3DRenderer.js"></script>';
     $div = '<div id="3d-graph"></div>';
     $plugin_dir = plugin_dir_url(__FILE__);
     //$dataset = $plugin_dir.'datasets/miserables.json';
@@ -20,22 +18,11 @@ function ks_add_graph(): string
                   var plugin_path = '$plugin_dir';
                   </script>";
 
-    // Yeah this is pretty ugly - need packaging asap.
-
-    $script_path0 = $plugin_dir.'config.js';
-    $script_path1 = $plugin_dir.'display'.DIRECTORY_SEPARATOR.'helpers.js';
-    $script_path2 = $plugin_dir.'display'.DIRECTORY_SEPARATOR.'infooverlay.js';
-    $script_path3 = $plugin_dir.'display'.DIRECTORY_SEPARATOR.'linkoverlay.js';
-    $script_path4 = $plugin_dir.'display'.DIRECTORY_SEPARATOR.'graph.js';
-
-    $script0 = "<script src='$script_path0'></script>";
-    $script1 = "<script src='$script_path1'></script>";
-    $script2 = "<script src='$script_path2'></script>";
-    $script3 = "<script src='$script_path3'></script>";
-    $script4 = "<script src='$script_path4'></script>";
-    
-    
-    return $three . $renderer . $renderer2 . $graph . $div . $variables . $script0 . $script1 . $script2 . $script3 . $script4;
+    $script_path = $plugin_dir.'build'.DIRECTORY_SEPARATOR.$GLOBALS['build'].DIRECTORY_SEPARATOR.'graph.js';
+    $script = "<script src='$script_path'></script>";
+    //wp_enqueue_script('kg-script', $script_path);
+
+    return $div . $variables. $script;
 }
 
 function ks_add_editor()
@@ -72,14 +59,14 @@ function ks_enqueue_script($relative_path, $dependencies = array())
     $type = "script";
     $script_name = end(explode("/", $relative_path));
     $script_name = explode(".", $script_name)[0];
-    
+
     for ($i = 0; $i < sizeof($dependencies); $i++) {
         $dependencies[$i] = $prefix . "-" . $dependencies[$i] . "-" . $type;
     }
-    
+
     // Source: https://developer.wordpress.org/reference/functions/wp_enqueue_script/ a comment from Andrija Naglic
     // $file_version = date("ymd-Gis", filemtime(plugin_dir_path(__FILE__) . $relative_path));
-    
+
     wp_enqueue_script($prefix .  "-" . $script_name . "-" . $type, plugins_url($relative_path, __FILE__), $dependencies, false);
 }
 
@@ -89,21 +76,21 @@ function ks_enqueue_style($relative_path, $dependencies = array())
     $type = "style";
     $style_name = end(explode("/", $relative_path));
     $style_name = explode(".", $style_name)[0];
-    
+
     for ($i = 0; $i < sizeof($dependencies); $i++) {
         $dependencies[$i] = $prefix . "-" . $dependencies[$i] . "-" . $type;
     }
-    
+
     // Source: https://developer.wordpress.org/reference/functions/wp_enqueue_script/ a comment from Andrija Naglic
     $file_version = date("ymd-Gis", filemtime(plugin_dir_path(__FILE__) . $relative_path));
-    
+
     wp_enqueue_style($prefix . "-" . $style_name . "-" . $type, plugins_url($relative_path, __FILE__), $dependencies, $file_version);
 }
 
 function kg_load_css()
 {
     $plugin_dir = plugin_dir_url(__FILE__);
-    wp_enqueue_style('kg-style', $plugin_dir . 'kg-style.css');
+    wp_enqueue_style('kg-style', $plugin_dir.'kg-style.css');
 }
 
 add_action('wp_enqueue_scripts', 'kg_load_css');
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..072b05e
--- /dev/null
+++ b/package.json
@@ -0,0 +1,44 @@
+{
+  "name": "knowledge-space",
+  "version": "1.0.0",
+  "description": "A Wordpress-Plugin to display knowledge in a graph-structured way.",
+  "main": "index.js",
+  "debug": "build/debug/graph.js",
+  "release": "build/debug/graph.js",
+  "targets": {
+    "main": false,
+    "debug": {
+      "distDir": "build/debug/"
+    },
+    "release": {
+      "distDir": "build/release/"
+    }
+  },
+  "scripts": {
+    "test": "echo \"Error: no test specified\" && exit 1",
+    "watch": "parcel watch index.js"
+  },
+  "repository": {
+    "type": "git",
+    "url": "https://gitlab.ibr.cs.tu-bs.de/alg/knowledge-space-wp-plugin.git"
+  },
+  "keywords": [
+    "Knowledge",
+    "Graph",
+    "Wordpress",
+    "Plugin"
+  ],
+  "author": "",
+  "license": "ISC",
+  "devDependencies": {
+    "@babel/core": "^7.14.8",
+    "@babel/eslint-parser": "^7.14.9",
+    "eslint": "^7.32.0",
+    "parcel": "^2.0.0-rc.0",
+    "prettier": "^2.3.2"
+  },
+  "dependencies": {
+    "3d-force-graph": "^1.70.5",
+    "three": "^0.131.2"
+  }
+}
-- 
GitLab