From e2bb8907040588f8987c45843eb5fb0b0563edf4 Mon Sep 17 00:00:00 2001 From: Ugo Date: Sun, 8 Sep 2019 01:01:08 +0200 Subject: [PATCH] Frontend requiremets finished --- frontend/assets/css/src/style.scss | 8 + frontend/assets/css/style.min.css | 2 +- frontend/assets/js/canvas.js | 580 ++++++++++++++++------------- frontend/index.html | 28 +- 4 files changed, 336 insertions(+), 282 deletions(-) diff --git a/frontend/assets/css/src/style.scss b/frontend/assets/css/src/style.scss index 91e31a1..15a3f8f 100644 --- a/frontend/assets/css/src/style.scss +++ b/frontend/assets/css/src/style.scss @@ -37,6 +37,14 @@ main{ position: relative; height: 100%; + .background{ + position: absolute; + top:0; + left:0; + background-color: rgba(0,0,0,0.5); + width: 100%; + height: 100%; + } input{ position: absolute; border: none; diff --git a/frontend/assets/css/style.min.css b/frontend/assets/css/style.min.css index aee426d..36d714c 100644 --- a/frontend/assets/css/style.min.css +++ b/frontend/assets/css/style.min.css @@ -1 +1 @@ -*{margin:0;padding:0;box-sizing:border-box}body{font-family:Roboto}main{position:relative;height:100vh}main #infobar{position:absolute;top:0;width:100%;padding:0.4em;background-color:rgba(232,9,9,0.4);border:4px solid rgba(232,9,9,0.8);text-align:center;font-weight:bold;font-size:1.8em;z-index:1;transform:translateY(-100%);transition:transform 1s}main #infobar.active{transform:translateY(0%)}main .canvas{background-color:lightgrey;position:relative;height:100%}main .canvas input{position:absolute;border:none;padding:0.6em 1em;width:50px;transform:translateX(-50%) translateY(-50%)}main nav,main footer{display:flex;position:absolute;top:1em;left:1em;opacity:0.9}main nav button,main footer button{display:block;background-color:#939292;border:none;border-radius:1em;padding:1em 1em;min-width:8em;margin:0.4em;color:black;border:solid 2px #131313;box-shadow:2px 2px 8px rgba(0,0,0,0.5)}main nav button:focus,main footer button:focus{outline:none}main nav button::-moz-focus-inner,main footer button::-moz-focus-inner{border:0}main nav button:hover,main footer button:hover{background-color:#acacac;border-color:#000;cursor:pointer}main nav button.active,main footer button.active{background-color:#f08d19}main nav button h1,main footer button h1{text-align:center;font-size:1.4em;font-weight:normal;white-space:nowrap}main nav button img,main footer button img{display:block;margin:0 auto 0.5em auto;width:2em}main footer{top:auto;left:auto;bottom:1em;right:1em} +*{margin:0;padding:0;box-sizing:border-box}body{font-family:Roboto}main{position:relative;height:100vh}main #infobar{position:absolute;top:0;width:100%;padding:0.4em;background-color:rgba(232,9,9,0.4);border:4px solid rgba(232,9,9,0.8);text-align:center;font-weight:bold;font-size:1.8em;z-index:1;transform:translateY(-100%);transition:transform 1s}main #infobar.active{transform:translateY(0%)}main .canvas{background-color:lightgrey;position:relative;height:100%}main .canvas .background{position:absolute;top:0;left:0;background-color:rgba(0,0,0,0.5);width:100%;height:100%}main .canvas input{position:absolute;border:none;padding:0.6em 1em;width:50px;transform:translateX(-50%) translateY(-50%)}main nav,main footer{display:flex;position:absolute;top:1em;left:1em;opacity:0.9}main nav button,main footer button{display:block;background-color:#939292;border:none;border-radius:1em;padding:1em 1em;min-width:8em;margin:0.4em;color:black;border:solid 2px #131313;box-shadow:2px 2px 8px rgba(0,0,0,0.5)}main nav button:focus,main footer button:focus{outline:none}main nav button::-moz-focus-inner,main footer button::-moz-focus-inner{border:0}main nav button:hover,main footer button:hover{background-color:#acacac;border-color:#000;cursor:pointer}main nav button.active,main footer button.active{background-color:#f08d19}main nav button h1,main footer button h1{text-align:center;font-size:1.4em;font-weight:normal;white-space:nowrap}main nav button img,main footer button img{display:block;margin:0 auto 0.5em auto;width:2em}main footer{top:auto;left:auto;bottom:1em;right:1em} diff --git a/frontend/assets/js/canvas.js b/frontend/assets/js/canvas.js index 913644f..3868975 100644 --- a/frontend/assets/js/canvas.js +++ b/frontend/assets/js/canvas.js @@ -1,6 +1,8 @@ const canvas = document.getElementById("canvas"); -// Layouts +//----------------------------------------------------------------------------- +//-----------------------------Layouts----------------------------------------- +//----------------------------------------------------------------------------- var width = canvas.offsetWidth; var height = canvas.offsetHeight; @@ -31,8 +33,8 @@ const node_text_layout = { y: 2 - node_size, text: name, fontSize: 22, - width: 2 * node_size-1, - height: 2 * node_size-1, + width: 2 * node_size - 1, + height: 2 * node_size - 1, fontFamily: 'Roboto', fill: 'black', verticalAlign: 'middle', @@ -54,6 +56,7 @@ const edge_text_layout = { align: 'center' }; + //----------------------------------------------------------------------------- //-------------------------------Init------------------------------------------ //----------------------------------------------------------------------------- @@ -62,6 +65,7 @@ var node_layer = new Konva.Layer(); var konva_edges = []; var konva_nodes = []; + var graph_copy = {}; var state = "idle"; @@ -73,267 +77,340 @@ var path_elements = []; // (additions) auto layout var auto_layout = false; -function initialisation(){ - stage.add(edge_layer); - stage.add(node_layer); +function initialization() { + stage.add(edge_layer); + stage.add(node_layer); + // avoid flickering + edge_layer.hide(); - // avoid flickering - edge_layer.hide(); - - // draw initial graph - for (id in nodes) { + // draw initial graph + for (id in nodes) { draw_new_node(nodes[id], node_size + Math.random() * (width - 2 * node_size), node_size + Math.random() * (height - 2 * node_size)); - } - for (id in edges) { + } + for (id in edges) { draw_new_edge(edges[id]); - } - - // (additions) auto layout + } - if ( sessionStorage.getItem("auto_layout") !== null) { + // (additions) auto layout + if (sessionStorage.getItem("auto_layout") !== null) { // Restore the contents of the text field auto_layout = (sessionStorage.getItem("auto_layout") == 'true'); - if (auto_layout){ + if (auto_layout) { document.querySelector("[data-activeon=auto_layout]").classList.add("active"); } - } - // Precalculate a good graph presentation - for (var i = 0; i < 1000; i++) { + } + // Precalculate a good graph presentation + for (var i = 0; i < 1000; i++) { force_directed_graph(); - } - node_layer.draw(); + } + edge_layer.show(); +} +initialization(); - edge_layer.show(); -} -initialisation(); //----------------------------------------------------------------------------- //------------------------------utils------------------------------------------ //----------------------------------------------------------------------------- function sort(array) { - return array.sort(function(a, b) { - return a - b; - }) + return array.sort(function(a, b) { + return a - b; + }) } + +function show_error(msg) { + let infobar = document.querySelector("#infobar"); + infobar.innerHTML = msg; + infobar.classList.add("active"); + window.setTimeout(function() { + infobar.classList.remove("active"); + }, 4000); +} + + //----------------------------------------------------------------------------- //---------------------------draw methods-------------------------------------- //----------------------------------------------------------------------------- function draw_new_node(name, x, y) { - const pos = { - x: x, - y: y - }; - let group = new Konva.Group({ - ...pos, - draggable: true, - dragBoundFunc: function(pos) { - return { - x: sort([node_size, pos.x, width - node_size])[1], - y: sort([node_size, pos.y, height - node_size])[1], - }; - }, - id: name - }); - let box = new Konva.Circle(node_layout); - let simpleText = new Konva.Text({...node_text_layout, text: name}); - group.add(box); - group.add(simpleText); - group.on("click", on_node_click); - group.on('dragstart', function() { - group.attrs.dragging = true; - }); - group.on('dragend', function() { - group.attrs.dragging = false; - }); - group.on('mouseover', function() { - document.body.style.cursor = 'pointer'; - }); - group.on('mouseout', function() { - document.body.style.cursor = 'default'; - }); - graph_copy[name] = []; - konva_nodes.push(group); - - node_layer.add(group); - node_layer.draw(); + let group = new Konva.Group({ + x: x, + y: y, + draggable: true, + dragBoundFunc: function(pos) { + return { + x: sort([node_size, pos.x, width - node_size])[1], + y: sort([node_size, pos.y, height - node_size])[1], + }; + }, + id: name + }); + let box = new Konva.Circle(node_layout); + let simpleText = new Konva.Text({ + ...node_text_layout, + text: name + }); + group.add(box); + group.add(simpleText); + group.on("click", on_node_click); + group.on('dragstart', function() { + group.attrs.dragging = true; + }); + group.on('dragend', function() { + group.attrs.dragging = false; + }); + group.on('mouseover', function() { + document.body.style.cursor = 'pointer'; + }); + group.on('mouseout', function() { + document.body.style.cursor = 'default'; + }); + graph_copy[name] = []; + konva_nodes.push(group); + + node_layer.add(group); + node_layer.draw(); } function draw_new_edge(edge) { - let start_node = stage.findOne('#' + edge["start"]); - let end_node = stage.findOne('#' + edge["end"]); - let weight = edge["weight"]; - let group = new Konva.Group({ - start_node: start_node, - end_node: end_node, - weight: weight, - id: edge["start"]+edge["end"] - }); - let line = new Konva.Line(edge_layout); - let simpleText = new Konva.Text({...edge_text_layout, text: weight}); - - graph_copy[edge["start"]].push(edge["end"]); - graph_copy[edge["end"]].push(edge["start"]); - - group.add(line); - group.add(simpleText); - konva_edges.push(group); - update_edge(group); - - edge_layer.add(group); - edge_layer.draw(); - - return group; + let start_node = stage.findOne('#' + edge["start"]); + let end_node = stage.findOne('#' + edge["end"]); + let weight = edge["weight"]; + let group = new Konva.Group({ + start_node: start_node, + end_node: end_node, + weight: weight, + id: edge["start"] + edge["end"] + }); + let line = new Konva.Line(edge_layout); + let simpleText = new Konva.Text({ + ...edge_text_layout, + text: weight + }); + + graph_copy[edge["start"]].push(edge["end"]); + graph_copy[edge["end"]].push(edge["start"]); + + group.add(line); + group.add(simpleText); + konva_edges.push(group); + update_edge(group); + + edge_layer.add(group); + edge_layer.draw(); + + return group; } function update_edge(konva_edge) { - let line = konva_edge.children[0]; - let text = konva_edge.children[1]; - let start_node = konva_edge.attrs.start_node; - let end_node = konva_edge.attrs.end_node; - let size = 15+text.text().length*15; - let line_length = Math.sqrt((start_node.getX() - end_node.getX()) ** 2 + (start_node.getY() - end_node.getY()) ** 2) - size; - line.points([start_node.getX(), start_node.getY(), end_node.getX(), end_node.getY()]); - line.dash([line_length / 2, size, line_length / 2]); - text.x((start_node.getX() + end_node.getX()) / 2 - node_size); - text.y((start_node.getY() + end_node.getY()) / 2 - node_size), - - edge_layer.draw(); + let line = konva_edge.children[0]; + let text = konva_edge.children[1]; + let start_node = konva_edge.attrs.start_node; + let end_node = konva_edge.attrs.end_node; + let size = 15 + text.text().length * 15; + let line_length = Math.sqrt((start_node.getX() - end_node.getX()) ** 2 + (start_node.getY() - end_node.getY()) ** 2) - size; + + line.points([start_node.getX(), start_node.getY(), end_node.getX(), end_node.getY()]); + line.dash([line_length / 2, size, line_length / 2]); + text.x((start_node.getX() + end_node.getX()) / 2 - node_size); + text.y((start_node.getY() + end_node.getY()) / 2 - node_size), + + edge_layer.draw(); } -function reset_node_style(){ +function reset_node_style() { for (let id in konva_nodes) { - konva_nodes[id].children[0].strokeWidth(node_layout.strokeWidth); - konva_nodes[id].children[0].stroke("black"); + konva_nodes[id].children[0].strokeWidth(node_layout.strokeWidth); + konva_nodes[id].children[0].stroke("black"); } } -function reset_edge_style(){ +function reset_edge_style() { for (let id in konva_edges) { - konva_edges[id].children[0].strokeWidth(edge_layout.strokeWidth); - konva_edges[id].children[0].stroke("black"); + konva_edges[id].children[0].strokeWidth(edge_layout.strokeWidth); + konva_edges[id].children[0].stroke("black"); } } function draw_selected_nodes() { - reset_node_style(); - reset_edge_style(); - for (let id in selected) { - selected[id].children[0].strokeWidth(node_layout.strokeWidth + 1); - selected[id].children[0].stroke("rgb(240, 141, 25)"); - } - node_layer.draw(); + reset_node_style(); + reset_edge_style(); + for (let id in selected) { + selected[id].children[0].strokeWidth(node_layout.strokeWidth + 1); + selected[id].children[0].stroke("rgb(240, 141, 25)"); + } + node_layer.draw(); } -function draw_path(path){ +function draw_path(path) { let path_elements = []; - for (let id in path){ - path_elements.push(stage.findOne("#"+path[id])); + for (let id in path) { + path_elements.push(stage.findOne("#" + path[id])); if (id == 0) { continue } - let current_edge = stage.findOne("#"+path[id-1]+path[id]); - if (current_edge){ + let current_edge = stage.findOne("#" + path[id - 1] + path[id]); + if (current_edge) { path_elements.push(current_edge); - }else{ - path_elements.push(stage.findOne("#"+path[id]+path[id-1])); + } else { + path_elements.push(stage.findOne("#" + path[id] + path[id - 1])); } } reset_node_style(); reset_edge_style(); for (let id in path_elements) { - path_elements[id].children[0].strokeWidth(node_layout.strokeWidth + 1); - path_elements[id].children[0].stroke("rgb(240, 141, 25)"); + path_elements[id].children[0].strokeWidth(node_layout.strokeWidth + 1); + path_elements[id].children[0].stroke("rgb(240, 141, 25)"); } } function on_node_click(e) { - e.cancelBubble = true; - // let mousePos = stage.getPointerPosition(); - let idx = selected.indexOf(this); - if (idx >= 0) { - selected.splice(idx, 1); - } else if (selected.length < 2) { - selected.push(this); - } else { - selected.shift(); - selected.push(this); - } - draw_selected_nodes(); + e.cancelBubble = true; + // let mousePos = stage.getPointerPosition(); + let idx = selected.indexOf(this); + if (idx >= 0) { + selected.splice(idx, 1); + } else if (selected.length < 2) { + selected.push(this); + } else { + selected.shift(); + selected.push(this); + } + draw_selected_nodes(); }; +//----------------------------------------------------------------------------- +//---------------------------REST methods-------------------------------------- +//----------------------------------------------------------------------------- +function post(endpoint, data) { + return fetch(url + endpoint, { + method: 'POST', + body: JSON.stringify(data), + headers: { + 'Content-Type': 'application/json' + } + }).then(res => res.json()) +} - -function create_node(){ - if (state != "create_node"){ +function create_node() { + if (state != "create_node") { state = "create_node"; // let mousePos = stage.getPointerPosition(); - let pos = {x: width/2, y: height/2}; - create_input(pos.x, pos.y).then(function(text) { - draw_new_node(text, pos.x, pos.y); - state = "idle"; - }, function(){state = "idle"}); - } + let pos = { + x: width / 2, + y: height / 2 + }; + create_input(pos.x, pos.y).then(function(text) { + post("nodes", { + name: text + }) + .then(function(res) { + if (res["error"]) { + show_error(res["error"]) + } else { + draw_new_node(text, pos.x, pos.y); + } + state = "idle"; + }) + .catch(error => console.error('Error:', error)); + }, function() {state = "idle"}); + } } - function create_edge() { - if (selected.length < 2) { - show_error("Select at least 2 nodes."); - return; - } - let x = (selected[0].getX() + selected[1].getX()) / 2; - let y = (selected[0].getY() + selected[1].getY()) / 2; - create_input(x, y).then(function(text) { - draw_new_edge({ + if (selected.length < 2) { + show_error("Select at least 2 nodes."); + return; + } + let x = (selected[0].getX() + selected[1].getX()) / 2; + let y = (selected[0].getY() + selected[1].getY()) / 2; + create_input(x, y).then(function(text) { + post("edges", { + start: selected[0].id(), + end: selected[1].id(), + weight: text + }) + .then(function(res) { + if (res["error"]) { + show_error(res["error"]) + } else { + draw_new_edge({ "start": selected[0].children[1].text(), "end": selected[1].children[1].text(), "weight": text - }); - selected = []; - draw_selected_nodes(); - },function(){}); + }); + selected = []; + draw_selected_nodes(); + } + }) + .catch(error => console.error('Error:', error)); + + }, function() {}); } - function create_input(x, y) { - return new Promise(function(resolve, reject) { - var input = document.createElement("input"); - input.setAttribute("type", "text"); - input.style.left = x + "px"; - input.style.top = y + "px"; - canvas.appendChild(input); - input.focus(); - stage.listening(false); - window.addEventListener("keyup", function(event) { - if (event.key === "Escape") { - input.remove(); - stage.listening(true); - reject(); - } - }); - input.addEventListener("keyup", function(event) { - if (event.key === "Enter") { - resolve(input.value); - input.remove(); - stage.listening(true); - } - }); + return new Promise(function(resolve, reject) { + var container = document.createElement("div"); + container.classList.add("background"); + var input = document.createElement("input"); + var clear = function() { + container.remove(); + stage.listening(true); + focus = true; + } + input.setAttribute("type", "text"); + input.style.left = x + "px"; + input.style.top = y + "px"; + canvas.appendChild(container); + container.appendChild(input); + input.focus(); + stage.listening(false); + focus = false; + window.addEventListener("keyup", function(event) { + if (event.key === "Escape") { + reject(); + clear(); + } + }); + input.addEventListener("keyup", function(event) { + if (event.key === "Enter") { + resolve(input.value); + clear(); + } }); + }); } -function find_path(){ - draw_path(elem); +function find_path(data) { + if (data.length < 2) { + show_error("Select at least 2 nodes."); + return; + } + console.log(url + "paths/" + data[0].attrs.id + "/" + data[1].attrs.id); + fetch(url + "paths/" + data[0].attrs.id + "/" + data[1].attrs.id).then(res => res.json()) + .then(function(res) { + if (res["error"]) { + show_error(res["error"]); + } else { + selected = []; + draw_path(res["path"]); + } + }) + .catch(error => console.error('Error:', error)); } + +//----------------------------------------------------------------------------- +//------------------------background methods----------------------------------- +//----------------------------------------------------------------------------- function update_edges(edges) { - for (id in edges) { - update_edge(edges[id]); - } + for (id in edges) { + update_edge(edges[id]); + } } + node_layer.on('beforeDraw', function() { - update_edges(konva_edges); + update_edges(konva_edges); }); // adapt the stage on any window resize @@ -346,91 +423,80 @@ function fitStageIntoParentContainer() { } window.addEventListener('resize', fitStageIntoParentContainer); -//----------------------------------------------------------------------------- -//---------------------(additions) auto layout--------------------------------- -//----------------------------------------------------------------------------- +//---------------------(additions) auto layout--------------------------------- function toggle_auto_layout() { - auto_layout = !auto_layout; - document.querySelector("[data-activeon=auto_layout]").classList.toggle("active"); - sessionStorage.setItem("auto_layout", auto_layout); + auto_layout = !auto_layout; + document.querySelector("[data-activeon=auto_layout]").classList.toggle("active"); + sessionStorage.setItem("auto_layout", auto_layout); } function normalize(vec) { - let l = Math.sqrt(vec[0]**2+vec[1]**2); - return [vec[0]/l,vec[1]/l]; + let l = Math.sqrt(vec[0] ** 2 + vec[1] ** 2); + return [vec[0] / l, vec[1] / l]; } -function in_edges(start_node, end_node){ - let idx = graph_copy[start_node.attrs.id].indexOf(end_node.attrs.id); - if (idx < 0){ - return false; - } - return true; +function in_edges(start_node, end_node) { + let idx = graph_copy[start_node.attrs.id].indexOf(end_node.attrs.id); + if (idx < 0) { + return false; + } + return true; } -function force_directed_graph(spring_length = 300, step = 0.004){ - for (var idx in konva_nodes){ - if (konva_nodes[idx].attrs.dragging){ - continue - } - var x1 = konva_nodes[idx].getX(); - var y1 = konva_nodes[idx].getY(); - - var rep = [0,0]; - var attr = [0,0]; - for (let jdx in konva_nodes){ - if (idx == jdx){ - continue; - } - - let x2 = konva_nodes[jdx].getX(); - let y2 = konva_nodes[jdx].getY(); - let dir = normalize([x2-x1,y2-y1]); - let dist = ((x1-x2)**2+(y1-y2)**2); - if (in_edges(konva_nodes[idx], konva_nodes[jdx])){ - let f = (dist/spring_length); - attr[0] += f*dir[0]; - attr[1] += f*dir[1]; - } - let f = (spring_length**2/Math.sqrt(dist)); - rep[0] -= f*dir[0]; - rep[1] -= f*dir[1]; - } - let new_x = x1+step*(rep[0]+attr[0]); - let new_y = y1+step*(rep[1]+attr[1]); - if (new_x < 2*node_size) { - new_x += 10*step*((2*node_size-new_x)); - } else if (new_x > width-2*node_size) { - new_x += 10*step*((width-2*node_size-new_x)); - } - if (new_y < 2*node_size) { - new_y += 10*step*((2*node_size-new_y)); - } else if (new_y > height-2*node_size) { - new_y += 10*step*((height-2*node_size-new_y)); - } - konva_nodes[idx].x(new_x); - konva_nodes[idx].y(new_y); +function force_directed_graph(spring_length = 300, step = 0.004) { + for (var idx in konva_nodes) { + if (konva_nodes[idx].attrs.dragging) { + continue + } + var x1 = konva_nodes[idx].getX(); + var y1 = konva_nodes[idx].getY(); + + var rep = [0, 0]; + var attr = [0, 0]; + for (let jdx in konva_nodes) { + if (idx == jdx) { + continue; + } + + let x2 = konva_nodes[jdx].getX(); + let y2 = konva_nodes[jdx].getY(); + let dir = normalize([x2 - x1, y2 - y1]); + let dist = ((x1 - x2) ** 2 + (y1 - y2) ** 2); + if (in_edges(konva_nodes[idx], konva_nodes[jdx])) { + let f = (dist / spring_length); + attr[0] += f * dir[0]; + attr[1] += f * dir[1]; } + let f = (spring_length ** 2 / Math.sqrt(dist)); + rep[0] -= f * dir[0]; + rep[1] -= f * dir[1]; + } + let new_x = x1 + step * (rep[0] + attr[0]); + let new_y = y1 + step * (rep[1] + attr[1]); + if (new_x < 2 * node_size) { + new_x += 10 * step * ((2 * node_size - new_x)); + } else if (new_x > width - 2 * node_size) { + new_x += 10 * step * ((width - 2 * node_size - new_x)); + } + if (new_y < 2 * node_size) { + new_y += 10 * step * ((2 * node_size - new_y)); + } else if (new_y > height - 2 * node_size) { + new_y += 10 * step * ((height - 2 * node_size - new_y)); + } + konva_nodes[idx].x(new_x); + konva_nodes[idx].y(new_y); + } } var anim = new Konva.Animation(function(frame) { - if (auto_layout) { - const spring_length = 300; - const step = 0.00025*frame.timeDiff; - force_directed_graph(spring_length, step) + if (auto_layout && focus) { + const step = 0.00025 * frame.timeDiff; + force_directed_graph(300, step) } }, node_layer); anim.start(); - -function show_error(msg){ - let infobar = document.querySelector("#infobar"); - infobar.innerHTML = msg; - infobar.classList.add("active"); - window.setTimeout(function(){ infobar.classList.remove("active"); }, 4000); -} - // bug: this avoids glitches -window.onfocus = function () {auto_layout = true;}; -window.onblur = function () {auto_layout = false;}; +window.onfocus = function() {focus = true;}; +window.onblur = function() {focus = false;}; diff --git a/frontend/index.html b/frontend/index.html index d6659db..38813ff 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -20,7 +20,7 @@

Connect

- @@ -32,29 +32,9 @@