From 66658f6cc458e622c2cb6e4b6362f8d998acdf0e Mon Sep 17 00:00:00 2001
From: Max <m.giller.dev@gmail.com>
Date: Fri, 9 Jul 2021 16:30:51 +0200
Subject: [PATCH] Basic node-adding tool

---
 editor/editor.html              |   1 +
 editor/images/tools/addnode.png | Bin 0 -> 3165 bytes
 editor/js/editor.js             |  10 ++++-
 editor/js/graph.js              |  65 ++++++++++++++++++++++++++++++--
 editor/js/state.js              |   2 +-
 editor/js/tools/addnodetool.js  |  22 +++++++++++
 6 files changed, 94 insertions(+), 6 deletions(-)
 create mode 100644 editor/images/tools/addnode.png
 create mode 100644 editor/js/tools/addnodetool.js

diff --git a/editor/editor.html b/editor/editor.html
index d45958e..527abd4 100644
--- a/editor/editor.html
+++ b/editor/editor.html
@@ -29,6 +29,7 @@
     <script src="%WWW%editor/js/tools/selecttool.js"></script>
     <script src="%WWW%editor/js/tools/collecttool.js"></script>
     <script src="%WWW%editor/js/tools/deletetool.js"></script>
+    <script src="%WWW%editor/js/tools/addnodetool.js"></script>
     <script src="%WWW%editor/js/display.js"></script>
     <script src="%WWW%editor/js/state.js"></script>
     <script src="%WWW%editor/js/editor.js"></script>
diff --git a/editor/images/tools/addnode.png b/editor/images/tools/addnode.png
new file mode 100644
index 0000000000000000000000000000000000000000..a899e0a587dd2538612438fbac1f40e060ece5b0
GIT binary patch
literal 3165
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
DSJ3f_

literal 0
HcmV?d00001

diff --git a/editor/js/editor.js b/editor/js/editor.js
index c9b0694..be00eea 100644
--- a/editor/js/editor.js
+++ b/editor/js/editor.js
@@ -35,7 +35,7 @@ function downloadJson() {
 
 function extractPositions(event) {
     return {
-        graph: { x: event.layerX, y: event.layerY },
+        graph: graphObj.screen2GraphCoords(event.layerX, event.layerY),
         window: { x: event.clientX, y: event.clientY },
     };
 }
@@ -57,8 +57,14 @@ function load() {
         .linkDirectionalParticleWidth((link) =>
             state.linkDirectionalParticleWidth(link)
         )
-        .onBackgroundClick((event) => state.onBackgroundClick(event, extractPositions(event)))
+        .onBackgroundClick((event) =>
+            state.onBackgroundClick(event, extractPositions(event))
+        )
         .nodeCanvasObjectMode((node) => state.nodeCanvasObjectMode(node))
         .nodeCanvasObject((node, ctx) => state.nodeCanvasObject(node, ctx))
         .onLinkClick((link) => state.onLinkClick(link));
+
+    graph.externUpdate.push(() => {
+        graphObj.graphData(graph.data);
+    });
 }
diff --git a/editor/js/graph.js b/editor/js/graph.js
index 52fffdc..a8d3d40 100644
--- a/editor/js/graph.js
+++ b/editor/js/graph.js
@@ -13,7 +13,7 @@ const GRAPH_NODES = "nodes";
 const GRAPH_LINKS = "links";
 
 const IMAGE_SIZE = 12;
-const IMAGE_SRC = PLUGIN_PATH + "datasets/images/"
+const IMAGE_SRC = PLUGIN_PATH + "datasets/images/";
 
 const LINK_PARAMS = [LINK_TYPE];
 const NODE_PARAMS = [NODE_ID, NODE_LABEL, NODE_IMAGE, NODE_DESCRIPTION];
@@ -22,10 +22,13 @@ const JSON_CONFIG = PLUGIN_PATH + "datasets/aud1.json";
 
 const STOP_PHYSICS_DELAY = 5000; // ms
 
-
-
 const graph = {
     data: undefined,
+    externUpdate: [],
+
+    update() {
+        graph.externUpdate.forEach((fn) => fn());
+    },
 
     deleteNode(nodeId) {
         // Delete node from nodes
@@ -159,4 +162,60 @@ const graph = {
 
         return cleanLink;
     },
+
+    existsNodeId(nodeId) {
+        var nodes = graph.data[GRAPH_NODES];
+        for (var i = 0; i < nodes.length; i++) {
+            if (nodes[i][NODE_ID] === nodeId) {
+                return true;
+            }
+        }
+        return false;
+    },
+
+    getUnusedNodeId() {
+        var id;
+        do {
+            id = graph.getRandomString();
+        } while (graph.existsNodeId(id));
+        return id;
+    },
+
+    getRandomString(length = 8) {
+        // Based on: https://stackoverflow.com/a/1349426/7376120
+        var characters =
+            "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
+        var charactersLength = characters.length;
+
+        var result = "";
+        for (var i = 0; i < length; i++) {
+            result += characters.charAt(
+                Math.floor(Math.random() * charactersLength)
+            );
+        }
+        return result;
+    },
+
+    addNode(nodeDetails) {
+        // Copy params
+        var newNode = nodeDetails;
+
+        // Make sure the ID is set and unique
+        if (newNode[NODE_ID] === undefined) {
+            newNode[NODE_ID] = graph.getUnusedNodeId();
+        } else if (graph.existsNodeId(newNode[NODE_ID])) {
+            return;
+        }
+
+        // Basic node properties
+        newNode.node = true;
+        newNode.link = false;
+        newNode.index = graph.data[GRAPH_NODES].length;
+
+        // Add node
+        graph.data[GRAPH_NODES].push(newNode);
+        graph.update();
+
+        return newNode;
+    },
 };
diff --git a/editor/js/state.js b/editor/js/state.js
index 9370f8d..a58d253 100644
--- a/editor/js/state.js
+++ b/editor/js/state.js
@@ -2,6 +2,7 @@ const TOOLS = {
     select: new SelectTool("select"),
     collect: new CollectTool("collect"),
     delete: new DeleteTool("delete"),
+    addnode: new AddNodeTool("addnode"),
 };
 
 const CONTEXT = {
@@ -184,7 +185,6 @@ class State extends Tool {
     }
 
     onBackgroundClick(event, positions) {
-        console.log(event);
         this.tool.onBackgroundClick(event, positions);
     }
 
diff --git a/editor/js/tools/addnodetool.js b/editor/js/tools/addnodetool.js
new file mode 100644
index 0000000..c72c189
--- /dev/null
+++ b/editor/js/tools/addnodetool.js
@@ -0,0 +1,22 @@
+class AddNodeTool extends Tool {
+    constructor(key) {
+        super("Add node", "addnode", key);
+    }
+
+    onBackgroundClick(event, positions) {
+        var node = {};
+
+        // Set position
+        node.fx = positions.graph.x;
+        node.fy = positions.graph.y;
+
+        var node = graph.addNode(node);
+
+        if (node === undefined) {
+            console.error("Couldn't add new node");
+            return;
+        }
+
+        state.setSelectedItem(node);
+    }
+}
\ No newline at end of file
-- 
GitLab