From 26b11a06c5bc1103da371c8ec269de9bb09fb107 Mon Sep 17 00:00:00 2001 From: Ben Loh Date: Thu, 6 Dec 2018 14:02:40 -0800 Subject: [PATCH 01/31] dev-bl/zoombuttons: Centralize zoom handler and add zoom buttons. --- .../view/netcreate/components/NetGraph.jsx | 36 +++++++++++-- .../netcreate/components/d3-simplenetgraph.js | 50 +++++++++++++++++-- 2 files changed, 78 insertions(+), 8 deletions(-) diff --git a/build/app/view/netcreate/components/NetGraph.jsx b/build/app/view/netcreate/components/NetGraph.jsx index c26be8fe7..a4a9dbdce 100644 --- a/build/app/view/netcreate/components/NetGraph.jsx +++ b/build/app/view/netcreate/components/NetGraph.jsx @@ -33,6 +33,8 @@ var DBG = false; /// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - const React = require('react') const ReactDOM = require('react-dom') +const ReactStrap = require('reactstrap'); +const { Button } = ReactStrap; const D3NetGraph = require('./d3-simplenetgraph') const UNISYS = require('unisys/client'); @@ -47,16 +49,35 @@ class NetGraph extends UNISYS.Component { this.state = { d3NetGraph: {} } + + this.onZoomReset = this.onZoomReset.bind(this); + this.onZoomIn = this.onZoomIn.bind(this); + this.onZoomOut = this.onZoomOut.bind(this); + } // constructor +/// CLASS PRIVATE METHODS ///////////////////////////////////////////////////// +/// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +/*/ +/*/ onZoomReset() { + this.AppCall('ZOOM_RESET', {}); + } +/*/ +/*/ onZoomIn() { + this.AppCall('ZOOM_IN', {}); + } +/*/ +/*/ onZoomOut() { + this.AppCall('ZOOM_OUT', {}); + } /// REACT LIFECYCLE /////////////////////////////////////////////////////////// /// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - /*/ /*/ componentDidMount () { // D3NetGraph Constructor - let el = ReactDOM.findDOMNode( this ) + let el = ReactDOM.findDOMNode(this); let d3NetGraph = new D3NetGraph(el); this.setState({ d3NetGraph }); } @@ -67,12 +88,21 @@ class NetGraph extends UNISYS.Component { // allowing D3 to handle the simulation animation updates // This is also necessary for D3 to handle the // drag events. - return false + return false; } /// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - /*/ /*/ render () { - return (
NETGRAPH
) + return ( +
+
NETGRAPH
+
+   +   + +
+
+ ) } } // class NetGraph diff --git a/build/app/view/netcreate/components/d3-simplenetgraph.js b/build/app/view/netcreate/components/d3-simplenetgraph.js index 538d89a06..b29bb3596 100644 --- a/build/app/view/netcreate/components/d3-simplenetgraph.js +++ b/build/app/view/netcreate/components/d3-simplenetgraph.js @@ -29,6 +29,8 @@ https://bl.ocks.org/mbostock/3808218 * Coderwall's zoom and pan method https://coderwall.com/p/psogia/simplest-way-to-add-zoom-pan-on-d3-js + * Vladyslav Babenko's zoom buttons example + https://jsfiddle.net/vbabenko/jcsqqu6j/9/ \*\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ * //////////////////////////////////////*/ @@ -92,6 +94,7 @@ class D3NetGraph { this.rootElement = rootElement; this.d3svg = {}; + this.zoom = {}; this.zoomWrapper = {}; this.simulation = {}; this.data = {}; @@ -107,6 +110,10 @@ class D3NetGraph { /// D3 CODE /////////////////////////////////////////////////////////////////// /// note: this is all inside the class constructor function! /// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + // Set up Zoom + this.zoom = d3.zoom().on("zoom", this._HandleZoom); + /*/ Create svg element which will contain our D3 DOM elements. Add default click handler so when clicking empty space, deselect all. NOTE: the svg element is actualy d3.selection object, not an svg obj. @@ -118,11 +125,11 @@ class D3NetGraph { UDATA.LocalCall('SOURCE_SELECT',{ nodeLabels: [] }); } ) - .call(d3.zoom().on("zoom", function () { - d3.select('.zoomer').attr("transform", d3.event.transform); - })); - this.zoomWrapper = this.d3svg.append('g').attr("class","zoomer"); + .call(this.zoom); + + this.zoomWrapper = this.d3svg.append('g').attr("class", "zoomer"); this.simulation = d3.forceSimulation(); + /// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - /// END D3 CODE /////////////////////////////////////////////////////////////// @@ -132,7 +139,8 @@ class D3NetGraph { this._Initialize = this._Initialize.bind(this); this._UpdateGraph = this._UpdateGraph.bind(this); this._UpdateForces = this._UpdateForces.bind(this); - this._Tick = this._Tick.bind(this); + this._Tick = this._Tick.bind(this); + this._HandleZoom = this._HandleZoom.bind(this); this._Dragstarted = this._Dragstarted.bind(this); this._Dragged = this._Dragged.bind(this); this._Dragended = this._Dragended.bind(this); @@ -152,6 +160,23 @@ class D3NetGraph { this._UpdateGraph(); }); + UDATA.HandleMessage('ZOOM_RESET', (data) => { + if (DBG) console.log(PR, 'ZOOM_RESET got state D3DATA', data); + this.d3svg.transition() + .duration(200) + .call(this.zoom.scaleTo, 1); + }); + + UDATA.HandleMessage('ZOOM_IN', (data) => { + if (DBG) console.log(PR, 'ZOOM_IN got state D3DATA', data); + this._Transition(1.2); + }); + + UDATA.HandleMessage('ZOOM_OUT', (data) => { + if (DBG) console.log(PR, 'ZOOM_OUT got state D3DATA', data); + this._Transition(0.8); + }); + } @@ -449,6 +474,21 @@ class D3NetGraph { /// UI EVENT HANDLERS ///////////////////////////////////////////////////////// +/// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +/*/ This primarily handles mousewheel zooms +/*/ +_HandleZoom() { + d3.select('.zoomer').attr("transform", d3.event.transform); +} +/*/ This handles zoom button zooms. +/*/ +_Transition(zoomLevel) { + this.d3svg.transition() + //.delay(100) + .duration(200) + .call(this.zoom.scaleBy, zoomLevel); +} + /// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - /*/ /*/ _Dragstarted (d, self) { From 81129bea6280aa8a9ffce9b1ffae6547f0704a0f Mon Sep 17 00:00:00 2001 From: Ben Loh Date: Thu, 6 Dec 2018 14:24:59 -0800 Subject: [PATCH 02/31] dev-bl/zoombuttons: Move zoom buttons to upper right and stack them vertically. --- build/app/view/netcreate/components/NetGraph.jsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/build/app/view/netcreate/components/NetGraph.jsx b/build/app/view/netcreate/components/NetGraph.jsx index a4a9dbdce..f21fc10f4 100644 --- a/build/app/view/netcreate/components/NetGraph.jsx +++ b/build/app/view/netcreate/components/NetGraph.jsx @@ -96,10 +96,10 @@ class NetGraph extends UNISYS.Component { return (
NETGRAPH
-
-   -   - +
+   +   +
) From 60c0b7bb3bdec4d0b65f78b699850b6828acd16d Mon Sep 17 00:00:00 2001 From: Ben Loh Date: Thu, 6 Dec 2018 19:52:28 -0800 Subject: [PATCH 03/31] dev-bl/edge-highlight: Highlight edges adjacent to selected node on node click. --- .../netcreate/components/d3-simplenetgraph.js | 35 ++++++++++++++----- build/app/view/netcreate/nc-logic.js | 21 ++++++++++- 2 files changed, 46 insertions(+), 10 deletions(-) diff --git a/build/app/view/netcreate/components/d3-simplenetgraph.js b/build/app/view/netcreate/components/d3-simplenetgraph.js index b29bb3596..d438027a4 100644 --- a/build/app/view/netcreate/components/d3-simplenetgraph.js +++ b/build/app/view/netcreate/components/d3-simplenetgraph.js @@ -390,21 +390,38 @@ class D3NetGraph { // TELL D3 what to do when a data node goes away nodeElements.exit().remove() + + // Used by linElements.enter() and linkElements.merge() below + function updateLinkStrokeWidth(edge) { + if (edge.selected) { + console.log('d3 enter edge', edge.id, 'selected'); + return edge.size ** 2; // Use **2 to make size differences more noticeable + } else { + return 0.05; // Barely visible if not selected + } + } + // NOW TELL D3 HOW TO HANDLE NEW EDGE DATA // .insert will add an svg `line` before the objects classed `.node` + // .enter() sets the initial state of links as they are created linkElements.enter() .insert("line",".node") - .classed('edge', true) - .style('stroke-width', (d) => { return d.size**2 } ) // Use **2 to make size differences more noticeable - .on("click", (d) => { - if (DBG) console.log('clicked on',d.label,d.id) - this.edgeClickFn( d ) - }) - + .classed('edge', true) + .style('stroke', '#999') + // .style('stroke', 'rgba(0,0,0,0.1)') // don't use alpha unless we're prepared to handle layering -- reveals unmatching links + .style('stroke-width', updateLinkStrokeWidth ) + // old stroke setting + // .style('stroke-width', (d) => { return d.size**2 } ) // Use **2 to make size differences more noticeable + // Edge selection disabled. + // .on("click", (d) => { + // if (DBG) console.log('clicked on',d.label,d.id) + // this.edgeClickFn( d ) + // }) + + // .merge() updates the visuals whenever the data is updated. linkElements.merge(linkElements) .classed("selected", (d) => { return d.selected }) - // .style('stroke', 'rgba(0,0,0,0.1)') // don't use alpha unless we're prepared to handle layering -- reveals unmatching links - .style('stroke-width', (d) => { return d.size**2 } ) + .style('stroke-width', updateLinkStrokeWidth) linkElements.exit().remove() diff --git a/build/app/view/netcreate/nc-logic.js b/build/app/view/netcreate/nc-logic.js index fbb20d59b..822bce86e 100644 --- a/build/app/view/netcreate/nc-logic.js +++ b/build/app/view/netcreate/nc-logic.js @@ -228,7 +228,8 @@ const TARGET_COLOR = '#FF0000'; if (nodes.length>0) { let color = '#0000DD'; nodes.forEach( node => { - m_MarkNodeById(node.id,color); + m_MarkNodeById(node.id, color); + m_MarkSelectedEdges(edges, node); UNISYS.Log('select node',node.id,node.label); }); } else { @@ -740,6 +741,24 @@ const TARGET_COLOR = '#FF0000'; m_SetMatchingNodesByLabel(searchString, matched, notmatched); UDATA.SetAppState('D3DATA',D3DATA); } +/// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +/*/ Sets the 'selected' state of edges that are attached to the node +/*/ +function m_MarkSelectedEdges(edges, node) { + // Delesect all edges first + edges.forEach(edge => { edge.selected = false; }); + // Find connected edges + console.log('marking edges for node', node); + let id = node.id; + D3DATA.edges.forEach(edge => { + if ( (edge.source.id === id) || (edge.target.id === id) ) { + edge.selected = true; + } else { + edge.selected = false; + } + }) + UDATA.SetAppState('D3DATA', D3DATA); +} /// COMMAND LINE UTILITIES //////////////////////////////////////////////////// /// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - From adbd44d1ec1741813bcb0a96dab0ec4436d126e3 Mon Sep 17 00:00:00 2001 From: Ben Loh Date: Thu, 6 Dec 2018 22:01:09 -0800 Subject: [PATCH 04/31] dev-bl/edge-highlight: Hilight edges on mouseover. --- .../netcreate/components/d3-simplenetgraph.js | 55 +++++++++++++------ 1 file changed, 39 insertions(+), 16 deletions(-) diff --git a/build/app/view/netcreate/components/d3-simplenetgraph.js b/build/app/view/netcreate/components/d3-simplenetgraph.js index d438027a4..d1ed29e86 100644 --- a/build/app/view/netcreate/components/d3-simplenetgraph.js +++ b/build/app/view/netcreate/components/d3-simplenetgraph.js @@ -49,7 +49,8 @@ var UDATA = null; /// PRIVATE VARS ////////////////////////////////////////////////////////////// /// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - let m_width = 1024; -let m_height = 1024; +let m_height = 1024; +let mouseoverNodeId = -1; // id of the node the mouse is currently over let m_forceProperties = { // values for all forces center: { x: 0.5, @@ -125,6 +126,15 @@ class D3NetGraph { UDATA.LocalCall('SOURCE_SELECT',{ nodeLabels: [] }); } ) + .on("mouseover", (d) => { + // Deselect edges + mouseoverNodeId = -1; + d3.selectAll('.edge') + .transition() + .duration(1500) + .style('stroke-width', this._UpdateLinkStrokeWidth) + d3.event.stopPropagation(); + }) .call(this.zoom); this.zoomWrapper = this.d3svg.append('g').attr("class", "zoomer"); @@ -268,7 +278,15 @@ class D3NetGraph { if (DBG) console.log('clicked on',d.label,d.id) UDATA.LocalCall('SOURCE_SELECT',{ nodeIDs: [d.id] }); d3.event.stopPropagation(); - }); + }) + .on("mouseover", (d) => { + mouseoverNodeId = d.id; + d3.selectAll('.edge') + .transition() + .duration(500) + .style('stroke-width', this._UpdateLinkStrokeWidth) + d3.event.stopPropagation(); + }) // enter node: also append 'circle' element of a calculated size elementG @@ -390,17 +408,6 @@ class D3NetGraph { // TELL D3 what to do when a data node goes away nodeElements.exit().remove() - - // Used by linElements.enter() and linkElements.merge() below - function updateLinkStrokeWidth(edge) { - if (edge.selected) { - console.log('d3 enter edge', edge.id, 'selected'); - return edge.size ** 2; // Use **2 to make size differences more noticeable - } else { - return 0.05; // Barely visible if not selected - } - } - // NOW TELL D3 HOW TO HANDLE NEW EDGE DATA // .insert will add an svg `line` before the objects classed `.node` // .enter() sets the initial state of links as they are created @@ -409,7 +416,7 @@ class D3NetGraph { .classed('edge', true) .style('stroke', '#999') // .style('stroke', 'rgba(0,0,0,0.1)') // don't use alpha unless we're prepared to handle layering -- reveals unmatching links - .style('stroke-width', updateLinkStrokeWidth ) + .style('stroke-width', this._UpdateLinkStrokeWidth ) // old stroke setting // .style('stroke-width', (d) => { return d.size**2 } ) // Use **2 to make size differences more noticeable // Edge selection disabled. @@ -421,7 +428,7 @@ class D3NetGraph { // .merge() updates the visuals whenever the data is updated. linkElements.merge(linkElements) .classed("selected", (d) => { return d.selected }) - .style('stroke-width', updateLinkStrokeWidth) + .style('stroke-width', this._UpdateLinkStrokeWidth) linkElements.exit().remove() @@ -473,7 +480,7 @@ class D3NetGraph { gets drawn first -- the drawing order is determined by the ordering in the DOM. See the notes under link_update.enter() above for one technique for setting the ordering in the DOM. -/*/ _Tick() { +/*/ _Tick () { // Drawing the nodes: Update the location of each node group element // from the x, y fields of the corresponding node object. this.zoomWrapper.selectAll(".node") @@ -488,6 +495,22 @@ class D3NetGraph { .attr("y2", (d) => { return d.target.y; }) } +/// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +/*/ Sets the width of the links during update cycles + Used by linElements.enter() and linkElements.merge() + and mouseover events. +/*/ +_UpdateLinkStrokeWidth (edge) { + if (edge.selected || + (edge.source.id === mouseoverNodeId) || + (edge.target.id === mouseoverNodeId) || + (mouseoverNodeId === -1) + ) { + return edge.size ** 2; // Use **2 to make size differences more noticeable + } else { + return 0.05; // Barely visible if not selected + } +} /// UI EVENT HANDLERS ///////////////////////////////////////////////////////// From 61edda94e250ce4d353735d5bc75ef668ba98c18 Mon Sep 17 00:00:00 2001 From: Ben Loh Date: Thu, 6 Dec 2018 23:51:33 -0800 Subject: [PATCH 05/31] dev-bl/edge-counts: Add edge counts to Node table. --- .../view/netcreate/components/NodeTable.jsx | 51 ++++++++++++++++--- 1 file changed, 44 insertions(+), 7 deletions(-) diff --git a/build/app/view/netcreate/components/NodeTable.jsx b/build/app/view/netcreate/components/NodeTable.jsx index e21e376d6..333d17910 100644 --- a/build/app/view/netcreate/components/NodeTable.jsx +++ b/build/app/view/netcreate/components/NodeTable.jsx @@ -35,7 +35,8 @@ class NodeTable extends UNISYS.Component { this.state = { nodePrompts: this.AppState('TEMPLATE').nodePrompts, - nodes: [], + nodes: [], + edgeCounts: {}, // {nodeID:count,...} isExpanded: false, sortkey: 'label' }; @@ -65,12 +66,25 @@ class NodeTable extends UNISYS.Component { /// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - /*/ Handle updated SELECTION -/*/ handleDataUpdate ( data ) { - if (data && data.nodes) { - this.setState({nodes: data.nodes}); - this.sortTable(); - } - } +/*/ +handleDataUpdate(data) { + if (data && data.nodes) { + this.countEdges(); + this.setState({nodes: data.nodes}); + this.sortTable(); + } +} +/// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +/*/ Build table of counts +/*/ +countEdges() { + let edgeCounts = this.state.edgeCounts; + this.AppState('D3DATA').edges.forEach( edge => { + edgeCounts[edge.source] = edgeCounts[edge.source]!==undefined ? edgeCounts[edge.source]+1 : 1; + edgeCounts[edge.target] = edgeCounts[edge.target]!== undefined ? edgeCounts[edge.target]+1 : 1; + }) + this.setState({ edgeCounts: edgeCounts }); +} /// UTILITIES ///////////////////////////////////////////////////////////////// @@ -89,6 +103,21 @@ class NodeTable extends UNISYS.Component { } /// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - /*/ +/*/ sortByEdgeCount(nodes) { + if (nodes) { + let edgeCounts = this.state.edgeCounts; + return nodes.sort( (a, b) => { + let akey = edgeCounts[a.id] || 0, + bkey = edgeCounts[b.id] || 0; + // sort descending + if (akey > bkey) return -1; + if (akey < bkey) return 1; + return 0; + }); + } + } +/// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +/*/ /*/ sortByLabel (nodes) { if (nodes) { return nodes.sort( (a,b) => { @@ -121,6 +150,9 @@ class NodeTable extends UNISYS.Component { case 'id': this.sortByID(nodes); break; + case 'edgeCount': + this.sortByEdgeCount(nodes); + break; case 'type': this.sortByAttribute(nodes, 'Node_Type'); break; @@ -204,6 +236,10 @@ class NodeTable extends UNISYS.Component { onClick={()=>this.setSortKey("id")} >ID + + {this.state.edgeCounts[node.id]} this.selectNode(node.id,e)} >{node.label} {node.attributes["Node_Type"]} From 1db293b71826dee298c88f1ecc4b88375e4ca6b7 Mon Sep 17 00:00:00 2001 From: Ben Loh Date: Thu, 6 Dec 2018 23:56:01 -0800 Subject: [PATCH 06/31] dev-bl/edge-counts: Add "Degrees" label to template. --- build/app/assets/templates/alexander.json | 5 +++++ build/app/view/netcreate/components/NodeTable.jsx | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/build/app/assets/templates/alexander.json b/build/app/assets/templates/alexander.json index af9766a0f..eecd91266 100644 --- a/build/app/assets/templates/alexander.json +++ b/build/app/assets/templates/alexander.json @@ -59,6 +59,11 @@ } ] }, + "degrees": { + "label": "Degrees", + "help": "Number of edges.", + "hidden": false + }, "notes": { "label": "Significance", "help": "Add some details.", diff --git a/build/app/view/netcreate/components/NodeTable.jsx b/build/app/view/netcreate/components/NodeTable.jsx index 333d17910..bf50681d3 100644 --- a/build/app/view/netcreate/components/NodeTable.jsx +++ b/build/app/view/netcreate/components/NodeTable.jsx @@ -239,7 +239,7 @@ countEdges() { + >{nodePrompts.degrees.label} diff --git a/build/app/view/netcreate/components/NodeTable.jsx b/build/app/view/netcreate/components/NodeTable.jsx index bf50681d3..f27451e50 100644 --- a/build/app/view/netcreate/components/NodeTable.jsx +++ b/build/app/view/netcreate/components/NodeTable.jsx @@ -221,13 +221,24 @@ countEdges() { /*/ /*/ render () { let { nodePrompts } = this.state; + let styles = `thead, tbody { display: block; } + thead { position: relative; } + tbody { overflow: auto; max-height: 40vh; } + .nodetable td:nth-child(1), .nodetable th:nth-child(1) {width: 2em; } + .nodetable td:nth-child(2), .nodetable th:nth-child(2) {width: 2em; } + .nodetable td:nth-child(3), .nodetable th:nth-child(3) {width: 4em; } + .nodetable td:nth-child(4), .nodetable th:nth-child(4) {width: 12em; } + .nodetable td:nth-child(5), .nodetable th:nth-child(5) {min-width: 4em; } + .nodetable td:nth-child(6), .nodetable th:nth-child(6) {min-width: 2em; }` return ( -
+
+
From 885692ed06d083e862f3ca768c0d12984edc11f1 Mon Sep 17 00:00:00 2001 From: Ben Loh Date: Fri, 7 Dec 2018 14:48:28 -0800 Subject: [PATCH 08/31] dev-bl/table-format: Add tab navigation for tables and help. --- build/app/view/netcreate/NetCreate.jsx | 80 +++++++++++++++++++++++++- 1 file changed, 77 insertions(+), 3 deletions(-) diff --git a/build/app/view/netcreate/NetCreate.jsx b/build/app/view/netcreate/NetCreate.jsx index 124f2cdab..959598236 100644 --- a/build/app/view/netcreate/NetCreate.jsx +++ b/build/app/view/netcreate/NetCreate.jsx @@ -44,6 +44,9 @@ const PR = PROMPTS.Pad('ACD'); /// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - const React = require('react'); const { Route } = require('react-router-dom'); +const ReactStrap = require('reactstrap'); +const { TabContent, TabPane, Nav, NavItem, NavLink, Row, Col } = ReactStrap; +const classnames = require('classnames'); const NetGraph = require('./components/NetGraph'); const Search = require('./components/Search'); const NodeSelector = require('./components/NodeSelector'); @@ -75,6 +78,18 @@ const NCLOGIC = require('./nc-logic'); // require to bootstrap data loading this.OnRun(()=>{ if (DBG) console.log(PR,'OnRun'); }); + + this.toggle = this.toggle.bind(this); + + this.state = { + activeTab: '1' + } + } + + toggle (tab) { + if (this.state.activeTab !== tab) { + this.setState({ activeTab: tab }); + } } /// REACT LIFECYCLE METHODS /////////////////////////////////////////////////// @@ -100,9 +115,68 @@ const NCLOGIC = require('./nc-logic'); // require to bootstrap data loading
- - - + + + + + + + +
+

Tab 1 Contents

+ + + + + + + + + + + + + + + + + + + +
Please contact Professor Kalani Craig, Institute for Digital Arts & Humanities at From 3bd86ca30b3f648a0d2cf2c158291a739af5144e Mon Sep 17 00:00:00 2001 From: Ben Loh Date: Fri, 7 Dec 2018 15:10:45 -0800 Subject: [PATCH 09/31] dev-bl/table-format: Show expanded table by default. Remove "Show/Hide" buttons. --- build/app/view/netcreate/components/EdgeTable.jsx | 11 +++++++++-- build/app/view/netcreate/components/NodeTable.jsx | 12 ++++++++++-- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/build/app/view/netcreate/components/EdgeTable.jsx b/build/app/view/netcreate/components/EdgeTable.jsx index 7874afd99..7251e5643 100644 --- a/build/app/view/netcreate/components/EdgeTable.jsx +++ b/build/app/view/netcreate/components/EdgeTable.jsx @@ -14,6 +14,13 @@ Set `DBG` to true to show the `ID` column. + ## 2018-12-07 Update + + Since we're not using tab navigation: + 1. The table isExpanded is now true by default. + 2. The "Show/Hide Table" button is hidden. + + Reset these to restore previous behavior. \*\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ * //////////////////////////////////////*/ @@ -38,7 +45,7 @@ class EdgeTable extends UNISYS.Component { this.state = { edgePrompts: this.AppState('TEMPLATE').edgePrompts, edges: [], - isExpanded: false, + isExpanded: true, sortkey: 'Citations' }; @@ -273,7 +280,7 @@ class EdgeTable extends UNISYS.Component { return (
-
+ ## 2018-12-07 Update + + Since we're not using tab navigation: + 1. The table isExpanded is now true by default. + 2. The "Show/Hide Table" button is hidden. + + Reset these to restore previous behavior. + \*\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ * //////////////////////////////////////*/ @@ -37,7 +45,7 @@ class NodeTable extends UNISYS.Component { nodePrompts: this.AppState('TEMPLATE').nodePrompts, nodes: [], edgeCounts: {}, // {nodeID:count,...} - isExpanded: false, + isExpanded: true, sortkey: 'label' }; @@ -233,7 +241,7 @@ countEdges() { return (
-
Date: Sat, 8 Dec 2018 12:13:30 -0800 Subject: [PATCH 10/31] dev-bl/table-resize: Add new resizable InfoPanel for tables. --- build/app/view/netcreate/NetCreate.jsx | 88 +------ .../view/netcreate/components/EdgeTable.jsx | 11 +- build/app/view/netcreate/components/Help.jsx | 10 +- .../view/netcreate/components/InfoPanel.jsx | 214 ++++++++++++++++++ .../view/netcreate/components/NodeTable.jsx | 7 +- 5 files changed, 237 insertions(+), 93 deletions(-) create mode 100644 build/app/view/netcreate/components/InfoPanel.jsx diff --git a/build/app/view/netcreate/NetCreate.jsx b/build/app/view/netcreate/NetCreate.jsx index 959598236..bac32b373 100644 --- a/build/app/view/netcreate/NetCreate.jsx +++ b/build/app/view/netcreate/NetCreate.jsx @@ -44,15 +44,10 @@ const PR = PROMPTS.Pad('ACD'); /// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - const React = require('react'); const { Route } = require('react-router-dom'); -const ReactStrap = require('reactstrap'); -const { TabContent, TabPane, Nav, NavItem, NavLink, Row, Col } = ReactStrap; -const classnames = require('classnames'); const NetGraph = require('./components/NetGraph'); const Search = require('./components/Search'); const NodeSelector = require('./components/NodeSelector'); -const Help = require('./components/Help'); -const NodeTable = require('./components/NodeTable'); -const EdgeTable = require('./components/EdgeTable'); +const InfoPanel = require('./components/InfoPanel'); const NCLOGIC = require('./nc-logic'); // require to bootstrap data loading @@ -78,25 +73,19 @@ const NCLOGIC = require('./nc-logic'); // require to bootstrap data loading this.OnRun(()=>{ if (DBG) console.log(PR,'OnRun'); }); - - this.toggle = this.toggle.bind(this); - - this.state = { - activeTab: '1' - } - } - - toggle (tab) { - if (this.state.activeTab !== tab) { - this.setState({ activeTab: tab }); - } } + + + /// REACT LIFECYCLE METHODS /////////////////////////////////////////////////// /// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - /*/ This is the root component, so this fires after all subcomponents have been fully rendered by render(). /*/ componentDidMount () { + // Init dragger + let dragger = document.getElementById('dragger'); + dragger.onmousedown = this.handleMouseDown; } /// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - /*/ Define the component structure of the web application @@ -115,68 +104,7 @@ const NCLOGIC = require('./nc-logic'); // require to bootstrap data loading
- - - - - - - -
-

Tab 1 Contents

- - - - - - - - - - - - - - - - - - - - +
Please contact Professor Kalani Craig, Institute for Digital Arts & Humanities at diff --git a/build/app/view/netcreate/components/EdgeTable.jsx b/build/app/view/netcreate/components/EdgeTable.jsx index 7251e5643..66575296a 100644 --- a/build/app/view/netcreate/components/EdgeTable.jsx +++ b/build/app/view/netcreate/components/EdgeTable.jsx @@ -15,11 +15,11 @@ Set `DBG` to true to show the `ID` column. ## 2018-12-07 Update - + Since we're not using tab navigation: 1. The table isExpanded is now true by default. 2. The "Show/Hide Table" button is hidden. - + Reset these to restore previous behavior. \*\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ * //////////////////////////////////////*/ @@ -266,9 +266,10 @@ class EdgeTable extends UNISYS.Component { /*/ /*/ render () { let { edgePrompts } = this.state; + let { tableHeight } = this.props; let styles = `thead, tbody { display: block; } thead { position: relative; } - tbody { overflow: auto; max-height: 40vh; } + tbody { overflow: auto; } .edgetable td:nth-child(1), .edgetable th:nth-child(1) {width: 2em; } .edgetable td:nth-child(2), .edgetable th:nth-child(2) {width: 2em; } .edgetable td:nth-child(3), .edgetable th:nth-child(3) {width: 4em; } @@ -278,7 +279,7 @@ class EdgeTable extends UNISYS.Component { .edgetable td:nth-child(7), .edgetable th:nth-child(7) {min-width: 6em; } .edgetable td:nth-child(8), .edgetable th:nth-child(8) {min-width: 6em; }` return ( -
+
-
+ {this.state.edges.map( (edge,i) => ( diff --git a/build/app/view/netcreate/components/Help.jsx b/build/app/view/netcreate/components/Help.jsx index 1a5e25594..5ed33aa05 100644 --- a/build/app/view/netcreate/components/Help.jsx +++ b/build/app/view/netcreate/components/Help.jsx @@ -25,7 +25,7 @@ const UNISYS = require('unisys/client'); class Help extends UNISYS.Component { constructor (props) { super(props); - this.state = {isExpanded: false}; + this.state = {isExpanded: true}; this.onToggleExpanded = this.onToggleExpanded.bind(this); } // constructor @@ -54,15 +54,15 @@ class Help extends UNISYS.Component { /*/ render () { return (
-
+ + + + + + + + + + + + + + + + + + + + + + + + + + ); + } + +} // class InfoPanel + + +/// EXPORT REACT COMPONENT //////////////////////////////////////////////////// +/// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +module.exports = InfoPanel; diff --git a/build/app/view/netcreate/components/NodeTable.jsx b/build/app/view/netcreate/components/NodeTable.jsx index 7f1946973..664ce17f8 100644 --- a/build/app/view/netcreate/components/NodeTable.jsx +++ b/build/app/view/netcreate/components/NodeTable.jsx @@ -229,9 +229,10 @@ countEdges() { /*/ /*/ render () { let { nodePrompts } = this.state; + let { tableHeight } = this.props; let styles = `thead, tbody { display: block; } thead { position: relative; } - tbody { overflow: auto; max-height: 40vh; } + tbody { overflow: auto; } .nodetable td:nth-child(1), .nodetable th:nth-child(1) {width: 2em; } .nodetable td:nth-child(2), .nodetable th:nth-child(2) {width: 2em; } .nodetable td:nth-child(3), .nodetable th:nth-child(3) {width: 4em; } @@ -239,7 +240,7 @@ countEdges() { .nodetable td:nth-child(5), .nodetable th:nth-child(5) {min-width: 4em; } .nodetable td:nth-child(6), .nodetable th:nth-child(6) {min-width: 2em; }` return ( -
+
-
+ {this.state.nodes.map( (node,i) => From a34134f7894b243161750df0e85591deec2e29cf Mon Sep 17 00:00:00 2001 From: Ben Loh Date: Mon, 10 Dec 2018 09:19:16 -0800 Subject: [PATCH 11/31] dev-bl/delete-node: Show/hide delete button based on template. --- build/app/assets/templates/alexander.json | 5 ++++- build/app/view/netcreate/components/NodeSelector.jsx | 12 +++++++++++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/build/app/assets/templates/alexander.json b/build/app/assets/templates/alexander.json index eecd91266..ff029f0c3 100644 --- a/build/app/assets/templates/alexander.json +++ b/build/app/assets/templates/alexander.json @@ -73,7 +73,10 @@ "label": "Geocode or Date", "help": "Use latitude/longitude or a date mm/dd/yyy", "hidden": true - } + }, + "delete": { + "hidden": false + } }, diff --git a/build/app/view/netcreate/components/NodeSelector.jsx b/build/app/view/netcreate/components/NodeSelector.jsx index 4e7fbf9a4..e15763763 100644 --- a/build/app/view/netcreate/components/NodeSelector.jsx +++ b/build/app/view/netcreate/components/NodeSelector.jsx @@ -8,7 +8,7 @@ NodeSelector does not modify any data. It passes all events (text updates, highlights, and suggestion selections) up to nc-logic. it should process the events and update the data accordingly. The - updated data is then rendered by NodeSelect. + updated data is then rendered by NodeSelector. ## USAGE @@ -438,6 +438,12 @@ class NodeSelector extends UNISYS.Component { } // onNewNodeButtonClick /// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - /*/ +/*/ onDeleteButtonClick() { + this.clearForm(); + this.AppCall('DB_UPDATE', { edgeID: this.props.edgeID }); + } +/// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +/*/ /*/ onEditButtonClick (event) { event.preventDefault(); this.setState({ isEditable: true }); @@ -599,6 +605,10 @@ class NodeSelector extends UNISYS.Component { +     + + + Re-link edges to this Node ID (leave blank to delete edge) + + + + Invalid Node ID! + + +
EDGES From 4c7f1bdbb9331f9eaee31f43e259f347b3692d35 Mon Sep 17 00:00:00 2001 From: Ben Loh Date: Wed, 12 Dec 2018 11:10:27 -0800 Subject: [PATCH 17/31] dev-bl/table-format: Remove test click button. --- build/app/view/netcreate/components/InfoPanel.jsx | 1 - 1 file changed, 1 deletion(-) diff --git a/build/app/view/netcreate/components/InfoPanel.jsx b/build/app/view/netcreate/components/InfoPanel.jsx index c258f3699..0bf6ce79e 100644 --- a/build/app/view/netcreate/components/InfoPanel.jsx +++ b/build/app/view/netcreate/components/InfoPanel.jsx @@ -200,7 +200,6 @@ class InfoPanel extends UNISYS.Component { }} onMouseDown={this.handleMouseDown} >
- ); From 72390b9ee7211ffd420ff3f371604f5c2bf5f49d Mon Sep 17 00:00:00 2001 From: Ben Loh Date: Wed, 12 Dec 2018 17:00:57 -0800 Subject: [PATCH 18/31] dev-bl/delete-node: Show delete button only on localhost. --- build/app/view/netcreate/components/NodeSelector.jsx | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/build/app/view/netcreate/components/NodeSelector.jsx b/build/app/view/netcreate/components/NodeSelector.jsx index 3a3d2c85e..5300bc750 100644 --- a/build/app/view/netcreate/components/NodeSelector.jsx +++ b/build/app/view/netcreate/components/NodeSelector.jsx @@ -42,6 +42,11 @@ isEditable The form fields are active and can be edited. + Delete Button + The Delete button is only displayed for an admin user. Right now we are detecting + this by displaying it only when the user is on `localhost`, + + ## STATES formData Node data that is shown in the form @@ -112,9 +117,12 @@ const EdgeEditor = require('./EdgeEditor'); const UNISYS = require('unisys/client'); const DATASTORE = require('system/datastore'); +const SETTINGS = require('settings'); const thisIdentifier = 'nodeSelector'; // SELECTION identifier +const isLocalHost = (SETTINGS.EJSProp('client').ip === '127.0.0.1'); + /// REACT COMPONENT /////////////////////////////////////////////////////////// /// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - /// export a class object for consumption by brunch/require @@ -651,7 +659,9 @@ onDeleteButtonClick() { Re-link edges to this Node ID (leave blank to delete edge) From 05092aae3ece85e31d82ec3b90a20b50f5ec9cf7 Mon Sep 17 00:00:00 2001 From: Ben Loh Date: Wed, 12 Dec 2018 17:11:14 -0800 Subject: [PATCH 19/31] dev-bl/delete-node: Bug Fix: DB was not deleting nodes because string Node ID didn't match data. Node IDs are integers. --- build/app/unisys/server-database.js | 7 +--- .../netcreate/components/NodeSelector.jsx | 40 ++++++++++--------- 2 files changed, 23 insertions(+), 24 deletions(-) diff --git a/build/app/unisys/server-database.js b/build/app/unisys/server-database.js index 60a81d701..566e42b69 100644 --- a/build/app/unisys/server-database.js +++ b/build/app/unisys/server-database.js @@ -201,31 +201,26 @@ let DB = {}; // handle edges let edgesToProcess = EDGES.where((e) => { - console.log('...evaluating', e.id, 'source', e.source, 'target', e.target, 'against', nodeID); return e.source === nodeID || e.target === nodeID; }); + // `NaN` is not valid JSON, so we use `` if (replacementNodeID !== '') { // re-link edges to replacementNodeID EDGES.findAndUpdate({ source: nodeID }, (e) => { - console.log('...updating edge', e.id, 'source', e.source, 'to', nodeID) LOGGER.Write(`...`, pkt.Info(), `relinking edge`, e.id, `to`, replacementNodeID); e.source = replacementNodeID; }); EDGES.findAndUpdate({ target: nodeID }, (e) => { - console.log('...updating edge', e.id, 'target', e.target, 'to', nodeID) LOGGER.Write(`...`, pkt.Info(), `relinking edge`, e.id, `to`, replacementNodeID); e.target = replacementNodeID; }); } else { // delete edges - console.log('edges to delete', edgesToProcess); EDGES.findAndRemove({ source: nodeID }, (e) => { - console.log('...deleting edge', e.id, 'source', e.source, 'to', nodeID) LOGGER.Write(`...`, pkt.Info(), `deleting edge`, e.id, `from`, nodeID); e.source = nodeID; }); EDGES.findAndRemove({ target: nodeID }, (e) => { - console.log('...deleting edge', e.id, 'target', e.target, 'to', nodeID) LOGGER.Write(`...`, pkt.Info(), `deleting edge`, e.id, `from`, nodeID); e.target = nodeID; }); diff --git a/build/app/view/netcreate/components/NodeSelector.jsx b/build/app/view/netcreate/components/NodeSelector.jsx index 5300bc750..e67a11132 100644 --- a/build/app/view/netcreate/components/NodeSelector.jsx +++ b/build/app/view/netcreate/components/NodeSelector.jsx @@ -136,7 +136,7 @@ class NodeSelector extends UNISYS.Component { type: '', info: '', notes: '', - id: '', + id: '', // Always convert this to a Number isNewNode: true }, edges: [], @@ -198,7 +198,7 @@ class NodeSelector extends UNISYS.Component { type: '', info: '', notes: '', - id: '', + id: '', // Always convert this to a Number isNewNode: true }, edges: [], @@ -344,7 +344,9 @@ class NodeSelector extends UNISYS.Component { // Clean data // REVIEW: Basic data structure probably needs updating let node = {attributes:{}}; - if (newNode.attributes===undefined) { newNode.attributes = {} } + if (newNode.attributes === undefined) { newNode.attributes = {} } + // Backward Compatibility: Always convert ids to a Number or loki lookups will fail. + if (isNaN(newNode.id)) { newNode.id = parseInt(newNode.id); } // node.label = newNode.label || ''; node.id = newNode.id || ''; @@ -466,21 +468,23 @@ class NodeSelector extends UNISYS.Component { this.validateForm(); }); } // onNewNodeButtonClick -/// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -/*/ -/*/ -onDeleteButtonClick() { - let nodeID = this.state.formData.id; - - // Re-link edges or delete edges? - let replacementNodeID = this.state.replacementNodeID==='' ? '' : parseInt( this.state.replacementNodeID ); // '' = Delete edges by default - - this.clearForm(); - this.AppCall('DB_UPDATE', { - nodeID: nodeID, - replacementNodeID: replacementNodeID - }); -} + /// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + /*/ + /*/ + onDeleteButtonClick() { + // nodeID needs to be a Number. It should have been set in loadFormFromNode + let nodeID = this.state.formData.id; + + // Re-link edges or delete edges? + // `NaN` is not valid JSON, so we need to pass `` + let replacementNodeID = this.state.replacementNodeID==='' ? '' : parseInt( this.state.replacementNodeID ); // '' = Delete edges by default + + this.clearForm(); + this.AppCall('DB_UPDATE', { + nodeID: nodeID, + replacementNodeID: replacementNodeID + }); + } /// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - /*/ /*/ onEditButtonClick (event) { From 7c9089b16857bf242b1743e3f48a07d03726fdc8 Mon Sep 17 00:00:00 2001 From: Ben Loh Date: Wed, 12 Dec 2018 17:13:21 -0800 Subject: [PATCH 20/31] dev-bl/delete-ndoe: Bug Fix: Convert any legacy edge-related string ids to a Number. --- .../view/netcreate/components/EdgeEditor.jsx | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/build/app/view/netcreate/components/EdgeEditor.jsx b/build/app/view/netcreate/components/EdgeEditor.jsx index cd01ca032..a7d7979fd 100644 --- a/build/app/view/netcreate/components/EdgeEditor.jsx +++ b/build/app/view/netcreate/components/EdgeEditor.jsx @@ -313,12 +313,16 @@ class EdgeEditor extends UNISYS.Component { /// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - /*/ populate formdata from D3DATA /*/ loadSourceAndTarget () { - if (DBG) console.log('EdgeEditor.loadSourceAndTarget!') + if (DBG) console.log('EdgeEditor.loadSourceAndTarget!') + let edgeID = this.props.edgeID || ''; - + // Clean Data + if (isNaN(edgeID)) { edgeID = parseInt(edgeID); } + let D3DATA = this.AppState('D3DATA'); - let edges = D3DATA.edges.filter( edge=>edge.id===edgeID ); + // parseInt in case of old bad string id + let edges = D3DATA.edges.filter( edge=>parseInt(edge.id)===edgeID ); if (!edges) { throw 'EdgeEditor: Passed edgeID'+edgeID+'not found!'; } @@ -344,7 +348,7 @@ class EdgeEditor extends UNISYS.Component { // Define `edge` so it can be loaded later during setState. edge = { id: edgeID, - source: sourceNodes[0].id, // REVIEW: d3data 'source' is id, rename this to 'sourceId'? + source: parseInt(sourceNodes[0].id), // REVIEW: d3data 'source' is id, rename this to 'sourceId'? // though after d3 processes, source does become an object. target: undefined, attributes: { @@ -365,8 +369,8 @@ class EdgeEditor extends UNISYS.Component { // LOAD EXISTING EDGE - sourceNodes = D3DATA.nodes.filter( node => node.id===edge.source.id ); - targetNodes = D3DATA.nodes.filter( node => node.id===edge.target.id ); + sourceNodes = D3DATA.nodes.filter( node => parseInt(node.id)===parseInt(edge.source.id) ); + targetNodes = D3DATA.nodes.filter( node => parseInt(node.id)===parseInt(edge.target.id) ); // Assume we have a valid target node this.setState({ @@ -388,7 +392,7 @@ class EdgeEditor extends UNISYS.Component { if (DBG) console.log('...EdgeEditor.loadSourceAndTarget: Setting formData sourceID to',edge.source,'and sourceNode to',sourceNode,'and targetNode to',targetNode); this.setState({ formData: { - id: edge.id || '', + id: parseInt(edge.id) || '', sourceId: edge.source, targetId: edge.target, relationship: edge.attributes["Relationship"] || '', // Make sure there's valid data From 9392ce28bba57354ac197ea0b4a6d999854e5f7c Mon Sep 17 00:00:00 2001 From: Ben Loh Date: Wed, 12 Dec 2018 20:45:39 -0800 Subject: [PATCH 21/31] dev-bl/duplicate-label: Add duplicate node label warning. --- .../view/netcreate/components/NodeSelector.jsx | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/build/app/view/netcreate/components/NodeSelector.jsx b/build/app/view/netcreate/components/NodeSelector.jsx index e67a11132..018f06d52 100644 --- a/build/app/view/netcreate/components/NodeSelector.jsx +++ b/build/app/view/netcreate/components/NodeSelector.jsx @@ -143,6 +143,7 @@ class NodeSelector extends UNISYS.Component { isLocked: true, isEditable: false, isValid: false, + isDuplicateNodeLabel: false, replacementNodeID: '', isValidReplacementNodeID: true }; @@ -204,6 +205,7 @@ class NodeSelector extends UNISYS.Component { edges: [], isEditable: false, isValid: false, + isDuplicateNodeLabel: false, replacementNodeID: '', isValidReplacementNodeID: true }); @@ -328,8 +330,18 @@ class NodeSelector extends UNISYS.Component { let formData = this.state.formData; formData.label = data.searchLabel; + + // "Duplicate Node Label" is only a warning, not an error. + // We want to allow students to enter a duplicate label if necessary + let isDuplicateNodeLabel = false; + if (formData.label !== '' && + this.AppState('D3DATA').nodes.find(node => { return node.label === formData.label; })) { + isDuplicateNodeLabel = true; + } + this.setState({ - formData + formData, + isDuplicateNodeLabel }); this.validateForm(); @@ -602,6 +614,7 @@ class NodeSelector extends UNISYS.Component { inactiveMode={'disabled'} shouldIgnoreSelection={this.state.isEditable} /> +
From 4fae68e72cd20582620e452034d3cc9e9c8874b8 Mon Sep 17 00:00:00 2001 From: Ben Loh Date: Wed, 12 Dec 2018 21:04:57 -0800 Subject: [PATCH 22/31] dev-bl/duplicate-label: Read duplicate warning from template. --- build/app/assets/templates/alexander.json | 1 + build/app/view/netcreate/components/NodeSelector.jsx | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/build/app/assets/templates/alexander.json b/build/app/assets/templates/alexander.json index ff029f0c3..c7d5ecb73 100644 --- a/build/app/assets/templates/alexander.json +++ b/build/app/assets/templates/alexander.json @@ -19,6 +19,7 @@ "label": { "label": "Label", "help": "A short title for the node", + "duplicateWarning": "You’re entering a duplicate. Do you want to edit the existing, or is this an actual entry?", "_cmt4": "/// `Label` is always required and cannot be hidden" }, "type": { diff --git a/build/app/view/netcreate/components/NodeSelector.jsx b/build/app/view/netcreate/components/NodeSelector.jsx index 018f06d52..99cc65605 100644 --- a/build/app/view/netcreate/components/NodeSelector.jsx +++ b/build/app/view/netcreate/components/NodeSelector.jsx @@ -614,7 +614,7 @@ class NodeSelector extends UNISYS.Component { inactiveMode={'disabled'} shouldIgnoreSelection={this.state.isEditable} /> - +
From 3664be4a22868d42a9bfc1bc7e74545e1e1a3295 Mon Sep 17 00:00:00 2001 From: Ben Loh Date: Thu, 13 Dec 2018 10:57:37 -0800 Subject: [PATCH 23/31] dev-bl/table-format: Fix bug where a second click of EdgeTable "Edit" would reset AutoComplete field target. --- build/app/view/netcreate/components/EdgeEditor.jsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/build/app/view/netcreate/components/EdgeEditor.jsx b/build/app/view/netcreate/components/EdgeEditor.jsx index a7d7979fd..f82447982 100644 --- a/build/app/view/netcreate/components/EdgeEditor.jsx +++ b/build/app/view/netcreate/components/EdgeEditor.jsx @@ -249,7 +249,7 @@ class EdgeEditor extends UNISYS.Component { // as a handler, otherwise object context is lost /// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - /*/ SESSION is called by SessionSHell when the ID changes + /*/ SESSION is called by SessionShell when the ID changes set system-wide. data: { classId, projId, hashedId, groupId, isValid } /*/ this.OnAppStateChange('SESSION',this.onStateChange_SESSION); /// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -434,7 +434,7 @@ class EdgeEditor extends UNISYS.Component { // SOURCE if (DBG) console.log('EdgeEditor.handleSelection:',this.props.edgeID,'setting source node to',node); - // Set sourceNpde state + // Set sourceNode state this.setState({ sourceNode: node }); @@ -451,7 +451,7 @@ class EdgeEditor extends UNISYS.Component { sourceIsEditable: false }); - } else { + } else if (this.state.targetIsEditable) { // TARGET if (DBG) console.log('EdgeEditor.handleSelection:',this.props.edgeID,'setting target node to',node); From 18377c75f8d45310dbc0b9d5ecf3f90cdeba7f0b Mon Sep 17 00:00:00 2001 From: Ben Loh Date: Thu, 13 Dec 2018 13:11:35 -0800 Subject: [PATCH 24/31] dev-bl/table-format: Fix bug where AutoComplete field would use outdated inactive mode during render. (Field would show as disabled during Edge Edits when it should be static) --- .../netcreate/components/AutoComplete.jsx | 97 +++++++++++++------ 1 file changed, 69 insertions(+), 28 deletions(-) diff --git a/build/app/view/netcreate/components/AutoComplete.jsx b/build/app/view/netcreate/components/AutoComplete.jsx index 5e940718f..46b201714 100644 --- a/build/app/view/netcreate/components/AutoComplete.jsx +++ b/build/app/view/netcreate/components/AutoComplete.jsx @@ -340,7 +340,7 @@ class AutoComplete extends UNISYS.Component { https://reactjs.org/blog/2015/12/16/ismounted-antipattern.html /*/ componentDidMount () { _IsMounted = true; - this.setState({ mode: this.props.inactiveMode }) + this.setState({ mode: this.props.inactiveMode }); } /*/ /*/ componentWillUnmount () { @@ -357,34 +357,75 @@ class AutoComplete extends UNISYS.Component { onChange : this.onInputChange }; let jsx; - switch (this.state.mode) { - case MODE_STATIC: - jsx = (

{this.props.disabledValue}

); - break; - case MODE_DISABLED: - jsx = ( ); - break; - case MODE_ACTIVE: - jsx = ( - - ); - break; - default: - throw Error(`AutoComplete: Unhandled mode '${this.state.mode}'`); + + // Show different widgets depending on mode. + // If MODE_ACTIVE is just show the active state, + // otherwise, use the current inactive mode in this.props.inactiveMode + // to define the inactive state + // because this.state.mode may not be up to date if the mode is inactive + // due to prop changes not triggering mode updates. + // e.g. if the parent container changed props from a disabled to + // static state, it does not trigger a mode update in AUTOCOMPLETE. + // This is mostly an edge case with EDGE_EDITs which will update props + // without a corresponding UNISYS message call to trigger the mode + // change. + if (this.state.mode === MODE_ACTIVE) { + jsx = ( + + ); + } else if (this.props.inactiveMode === MODE_STATIC) { + jsx = (

{this.props.disabledValue}

); + } else if (this.props.inactiveMode === MODE_DISABLED) { + jsx = (); + } else { + throw Error(`AutoComplete: Unhandled mode '${this.state.mode}'`); } + + // OLD METHOD + // This relied on mode being updated, but a change in props does not + // trigger a corresponding change in mode. + // switch (this.state.mode) { + // case MODE_STATIC: + // jsx = (

{this.props.disabledValue}

); + // break; + // case MODE_DISABLED: + // jsx = ( ); + // break; + // case MODE_ACTIVE: + // jsx = ( + // + // ); + // break; + // default: + // throw Error(`AutoComplete: Unhandled mode '${this.state.mode}'`); + // } + return jsx; } // render() From 03bbca7614f2e1e8081bdba60ec1e4dc2396e07b Mon Sep 17 00:00:00 2001 From: Ben Loh Date: Thu, 13 Dec 2018 13:28:33 -0800 Subject: [PATCH 25/31] dev-bl/table-format: Set min widths of Node and EdgeTable so columns won't collapse. --- build/app/view/netcreate/components/EdgeTable.jsx | 14 +++++++------- build/app/view/netcreate/components/NodeTable.jsx | 10 +++++----- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/build/app/view/netcreate/components/EdgeTable.jsx b/build/app/view/netcreate/components/EdgeTable.jsx index 66575296a..b95cd3220 100644 --- a/build/app/view/netcreate/components/EdgeTable.jsx +++ b/build/app/view/netcreate/components/EdgeTable.jsx @@ -270,13 +270,13 @@ class EdgeTable extends UNISYS.Component { let styles = `thead, tbody { display: block; } thead { position: relative; } tbody { overflow: auto; } - .edgetable td:nth-child(1), .edgetable th:nth-child(1) {width: 2em; } - .edgetable td:nth-child(2), .edgetable th:nth-child(2) {width: 2em; } - .edgetable td:nth-child(3), .edgetable th:nth-child(3) {width: 4em; } - .edgetable td:nth-child(4), .edgetable th:nth-child(4) {width: 6em; } - .edgetable td:nth-child(5), .edgetable th:nth-child(5) {min-width: 14em; } - .edgetable td:nth-child(6), .edgetable th:nth-child(6) {min-width: 6em; } - .edgetable td:nth-child(7), .edgetable th:nth-child(7) {min-width: 6em; } + .edgetable td:nth-child(1), .edgetable th:nth-child(1) {width: 2em; min-width: 2em;} + .edgetable td:nth-child(2), .edgetable th:nth-child(2) {width: 2em; min-width: 2em;} + .edgetable td:nth-child(3), .edgetable th:nth-child(3) {width: 4em; min-width: 4em;} + .edgetable td:nth-child(4), .edgetable th:nth-child(4) {width: 6em; min-width: 6em;} + .edgetable td:nth-child(5), .edgetable th:nth-child(5) {width: 14em; min-width: 14em;} + .edgetable td:nth-child(6), .edgetable th:nth-child(6) {width: 6em; min-width: 6em;} + .edgetable td:nth-child(7), .edgetable th:nth-child(7) {width: 6em; min-width: 6em;} .edgetable td:nth-child(8), .edgetable th:nth-child(8) {min-width: 6em; }` return (
diff --git a/build/app/view/netcreate/components/NodeTable.jsx b/build/app/view/netcreate/components/NodeTable.jsx index 664ce17f8..88283edf7 100644 --- a/build/app/view/netcreate/components/NodeTable.jsx +++ b/build/app/view/netcreate/components/NodeTable.jsx @@ -233,11 +233,11 @@ countEdges() { let styles = `thead, tbody { display: block; } thead { position: relative; } tbody { overflow: auto; } - .nodetable td:nth-child(1), .nodetable th:nth-child(1) {width: 2em; } - .nodetable td:nth-child(2), .nodetable th:nth-child(2) {width: 2em; } - .nodetable td:nth-child(3), .nodetable th:nth-child(3) {width: 4em; } - .nodetable td:nth-child(4), .nodetable th:nth-child(4) {width: 12em; } - .nodetable td:nth-child(5), .nodetable th:nth-child(5) {min-width: 4em; } + .nodetable td:nth-child(1), .nodetable th:nth-child(1) {width: 2em; min-width: 2em;} + .nodetable td:nth-child(2), .nodetable th:nth-child(2) {width: 2em; min-width: 2em;} + .nodetable td:nth-child(3), .nodetable th:nth-child(3) {width: 4em; min-width: 4em;} + .nodetable td:nth-child(4), .nodetable th:nth-child(4) {width: 12em; min-width: 12em;} + .nodetable td:nth-child(5), .nodetable th:nth-child(5) {width: 4em; min-width: 4em;} .nodetable td:nth-child(6), .nodetable th:nth-child(6) {min-width: 2em; }` return (
From 58a34a59f3fd1e29d589100a06f285239e59674a Mon Sep 17 00:00:00 2001 From: Ben Loh Date: Thu, 13 Dec 2018 13:40:28 -0800 Subject: [PATCH 26/31] dev-bl/table-format: Second click on tab now 'closes' the tab. --- build/app/view/netcreate/components/InfoPanel.jsx | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/build/app/view/netcreate/components/InfoPanel.jsx b/build/app/view/netcreate/components/InfoPanel.jsx index 0bf6ce79e..d959e0b6b 100644 --- a/build/app/view/netcreate/components/InfoPanel.jsx +++ b/build/app/view/netcreate/components/InfoPanel.jsx @@ -79,6 +79,14 @@ class InfoPanel extends UNISYS.Component { hideDragger: false }); } + } else { + // Second click on currently open tab + // so select tab 1 + this.setState({ activeTab: `1` }); + this.setState({ + tabpanelHeight: '50px', // show only tab buttons + hideDragger: true + }); } } From be58c8e7400478793182690afafc4b8099392c8f Mon Sep 17 00:00:00 2001 From: Ben Loh Date: Thu, 13 Dec 2018 13:42:49 -0800 Subject: [PATCH 27/31] dev-bl/duplicate-label: Duplicate warning is now only shown when editing a second node and cleared if editing is canceled. --- build/app/view/netcreate/components/NodeSelector.jsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/build/app/view/netcreate/components/NodeSelector.jsx b/build/app/view/netcreate/components/NodeSelector.jsx index 99cc65605..8384dda84 100644 --- a/build/app/view/netcreate/components/NodeSelector.jsx +++ b/build/app/view/netcreate/components/NodeSelector.jsx @@ -335,7 +335,7 @@ class NodeSelector extends UNISYS.Component { // We want to allow students to enter a duplicate label if necessary let isDuplicateNodeLabel = false; if (formData.label !== '' && - this.AppState('D3DATA').nodes.find(node => { return node.label === formData.label; })) { + this.AppState('D3DATA').nodes.find(node => { return (node.label === formData.label) && (node.id!==formData.id); })) { isDuplicateNodeLabel = true; } @@ -375,7 +375,8 @@ class NodeSelector extends UNISYS.Component { id: node.id, isNewNode: false }, - isEditable: false + isEditable: false, + isDuplicateNodeLabel: false }); this.validateForm(); From b2cc3cb761da86e22e2f5d500ea58b2b1cf183a6 Mon Sep 17 00:00:00 2001 From: Ben Loh Date: Thu, 13 Dec 2018 16:18:36 -0800 Subject: [PATCH 28/31] dev-bl/table-format: Cusor is now a pointer over InfoPanel tabs. --- build/app/view/netcreate/components/AutoComplete.css | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/build/app/view/netcreate/components/AutoComplete.css b/build/app/view/netcreate/components/AutoComplete.css index e7e3b8f46..1261fedad 100644 --- a/build/app/view/netcreate/components/AutoComplete.css +++ b/build/app/view/netcreate/components/AutoComplete.css @@ -92,6 +92,11 @@ td { hyphens: auto; } +/* Bootstrap override */ +.nav-item { + cursor: pointer; +} + /* SVG styles */ /* REVIEW!!! These are global!!! */ svg { From ec031f52b76050117fb433376424e733e3e8a735 Mon Sep 17 00:00:00 2001 From: Ben Loh Date: Thu, 13 Dec 2018 22:08:57 -0800 Subject: [PATCH 29/31] dev-bl/duplicate-label: Case-insensitive search for duplicate labels. --- build/app/view/netcreate/components/NodeSelector.jsx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/build/app/view/netcreate/components/NodeSelector.jsx b/build/app/view/netcreate/components/NodeSelector.jsx index 8384dda84..a1f998e2c 100644 --- a/build/app/view/netcreate/components/NodeSelector.jsx +++ b/build/app/view/netcreate/components/NodeSelector.jsx @@ -327,15 +327,18 @@ class NodeSelector extends UNISYS.Component { // otherwise an Edge might be active let { activeAutoCompleteId } = this.AppState('ACTIVEAUTOCOMPLETE'); if ( activeAutoCompleteId!==thisIdentifier ) return; - let formData = this.state.formData; formData.label = data.searchLabel; // "Duplicate Node Label" is only a warning, not an error. // We want to allow students to enter a duplicate label if necessary + // This is a case insensitive search let isDuplicateNodeLabel = false; if (formData.label !== '' && - this.AppState('D3DATA').nodes.find(node => { return (node.label === formData.label) && (node.id!==formData.id); })) { + this.AppState('D3DATA').nodes.find(node => { + return ( (node.id !== formData.id) && + (node.label.localeCompare( formData.label,'en', { usage: 'search', sensitivity: 'base' } ) )===0 ) + })) { isDuplicateNodeLabel = true; } From 4d2d3355e76cecd0e895d43a90506843caa9a9b7 Mon Sep 17 00:00:00 2001 From: Ben Loh Date: Mon, 17 Dec 2018 19:11:28 -0800 Subject: [PATCH 30/31] dev-bl/center-zoom: Maximize svg and center the graph. --- .../view/netcreate/components/NetGraph.jsx | 2 +- .../netcreate/components/d3-simplenetgraph.js | 21 ++++++++++++------- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/build/app/view/netcreate/components/NetGraph.jsx b/build/app/view/netcreate/components/NetGraph.jsx index f21fc10f4..3e4c85b22 100644 --- a/build/app/view/netcreate/components/NetGraph.jsx +++ b/build/app/view/netcreate/components/NetGraph.jsx @@ -94,7 +94,7 @@ class NetGraph extends UNISYS.Component { /*/ /*/ render () { return ( -
+
NETGRAPH
  diff --git a/build/app/view/netcreate/components/d3-simplenetgraph.js b/build/app/view/netcreate/components/d3-simplenetgraph.js index bdf52d298..e48aba407 100644 --- a/build/app/view/netcreate/components/d3-simplenetgraph.js +++ b/build/app/view/netcreate/components/d3-simplenetgraph.js @@ -48,8 +48,8 @@ var UDATA = null; /// PRIVATE VARS ////////////////////////////////////////////////////////////// /// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -let m_width = 1024; -let m_height = 1024; +let m_width = 800; +let m_height = 800; let mouseoverNodeId = -1; // id of the node the mouse is currently over let m_forceProperties = { // values for all forces center: { @@ -119,8 +119,9 @@ class D3NetGraph { Add default click handler so when clicking empty space, deselect all. NOTE: the svg element is actualy d3.selection object, not an svg obj. /*/ this.d3svg = d3.select(rootElement).append('svg') - .attr('width', "100%") // overrride m_width so SVG is wide - .attr('height',m_height) + .attr('id', 'netgraph') + .attr('width', "100%") // maximize width and height + .attr('height', "100%") // then set center dynamically below .on("click", ( e, event ) => { // Deselect UDATA.LocalCall('SOURCE_SELECT',{ nodeLabels: [] }); @@ -137,7 +138,13 @@ class D3NetGraph { }) .call(this.zoom); - this.zoomWrapper = this.d3svg.append('g').attr("class", "zoomer"); + this.zoomWrapper = this.d3svg.append('g').attr("class", "zoomer") + + // Set SVG size and centering. + let svg = document.getElementById('netgraph'); + m_width = svg.clientWidth; + m_height = svg.clientHeight; + this.simulation = d3.forceSimulation(); /// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -149,8 +156,8 @@ class D3NetGraph { this._Initialize = this._Initialize.bind(this); this._UpdateGraph = this._UpdateGraph.bind(this); this._UpdateForces = this._UpdateForces.bind(this); - this._Tick = this._Tick.bind(this); - this._HandleZoom = this._HandleZoom.bind(this); + this._Tick = this._Tick.bind(this); + this._HandleZoom = this._HandleZoom.bind(this); this._Dragstarted = this._Dragstarted.bind(this); this._Dragged = this._Dragged.bind(this); this._Dragended = this._Dragended.bind(this); From cf20d21385268bc12b7457d956cf9341ca43163d Mon Sep 17 00:00:00 2001 From: Ben Loh Date: Mon, 17 Dec 2018 19:24:34 -0800 Subject: [PATCH 31/31] dev-bl/duplicate-label: Show duplicate lable warning with recovery options. --- build/app/assets/templates/alexander.json | 2 +- .../netcreate/components/NodeSelector.jsx | 55 +++++++++++++++++-- 2 files changed, 50 insertions(+), 7 deletions(-) diff --git a/build/app/assets/templates/alexander.json b/build/app/assets/templates/alexander.json index c7d5ecb73..c74ae36dd 100644 --- a/build/app/assets/templates/alexander.json +++ b/build/app/assets/templates/alexander.json @@ -19,7 +19,7 @@ "label": { "label": "Label", "help": "A short title for the node", - "duplicateWarning": "You’re entering a duplicate. Do you want to edit the existing, or is this an actual entry?", + "duplicateWarning": "You’re entering a duplicate node. Do you want to View the Existing node, or Continue creating?", "_cmt4": "/// `Label` is always required and cannot be hidden" }, "type": { diff --git a/build/app/view/netcreate/components/NodeSelector.jsx b/build/app/view/netcreate/components/NodeSelector.jsx index a1f998e2c..f0fa14c8e 100644 --- a/build/app/view/netcreate/components/NodeSelector.jsx +++ b/build/app/view/netcreate/components/NodeSelector.jsx @@ -121,7 +121,10 @@ const SETTINGS = require('settings'); const thisIdentifier = 'nodeSelector'; // SELECTION identifier -const isLocalHost = (SETTINGS.EJSProp('client').ip === '127.0.0.1'); +const isLocalHost = (SETTINGS.EJSProp('client').ip === '127.0.0.1'); + +var UDATA = null; + /// REACT COMPONENT /////////////////////////////////////////////////////////// /// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -144,6 +147,7 @@ class NodeSelector extends UNISYS.Component { isEditable: false, isValid: false, isDuplicateNodeLabel: false, + duplicateNodeID: '', replacementNodeID: '', isValidReplacementNodeID: true }; @@ -165,11 +169,17 @@ class NodeSelector extends UNISYS.Component { this.onEditButtonClick = this.onEditButtonClick.bind(this); this.onAddNewEdgeButtonClick = this.onAddNewEdgeButtonClick.bind(this); this.onCancelButtonClick = this.onCancelButtonClick.bind(this); + this.onEditOriginal = this.onEditOriginal.bind(this); + this.onCloseDuplicateDialog = this.onCloseDuplicateDialog.bind(this); this.onSubmit = this.onSubmit.bind(this); // NOTE: assign UDATA handlers AFTER functions have been bind()'ed // otherwise they will lose context + /// Initialize UNISYS DATA LINK for REACT + UDATA = UNISYS.NewDataLink(this); + + /// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - /*/ SESSION is called by SessionSHell when the ID changes set system-wide. data: { classId, projId, hashedId, groupId, isValid } @@ -206,6 +216,7 @@ class NodeSelector extends UNISYS.Component { isEditable: false, isValid: false, isDuplicateNodeLabel: false, + duplicateNodeID: '', replacementNodeID: '', isValidReplacementNodeID: true }); @@ -298,7 +309,7 @@ class NodeSelector extends UNISYS.Component { // * force exit? // * prevent load? // * prevent selection? - if (DBG) console.log('NodeSelector: Already editing, ignoring SELECTION'); + if (DBG) console.error('NodeSelector: Already editing, ignoring SELECTION'); } this.validateForm(); @@ -334,17 +345,22 @@ class NodeSelector extends UNISYS.Component { // We want to allow students to enter a duplicate label if necessary // This is a case insensitive search let isDuplicateNodeLabel = false; + let duplicateNodeID; if (formData.label !== '' && this.AppState('D3DATA').nodes.find(node => { - return ( (node.id !== formData.id) && - (node.label.localeCompare( formData.label,'en', { usage: 'search', sensitivity: 'base' } ) )===0 ) + if ((node.id !== formData.id) && + (node.label.localeCompare(formData.label, 'en', { usage: 'search', sensitivity: 'base' })) === 0) { + duplicateNodeID = node.id; + return true; + } })) { isDuplicateNodeLabel = true; } this.setState({ formData, - isDuplicateNodeLabel + isDuplicateNodeLabel, + duplicateNodeID }); this.validateForm(); @@ -563,6 +579,28 @@ class NodeSelector extends UNISYS.Component { this.AppCall('AUTOCOMPLETE_SELECT', {id:'search'}); } } // onCancelButtonClick +/// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + /*/ Select the node for editing + /*/ + onEditOriginal(event) { + event.preventDefault(); + let duplicateNodeID = parseInt(this.state.duplicateNodeID); + this.clearForm(); + this.setState({ + isEditable: false, + isDuplicateNodeLabel: false + }, () => { + // Wait for the edit state to clear, then open up the original node + UDATA.LocalCall('SOURCE_SELECT', { nodeIDs: [duplicateNodeID] }); + }); + this.AppCall('AUTOCOMPLETE_SELECT', { id: 'search' }); + } +/// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + /*/ User confirms they want to edit the existing node. + /*/ + onCloseDuplicateDialog() { + this.setState({ isDuplicateNodeLabel: false }); + } /// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - /*/ /*/ onSubmit ( event ) { @@ -618,8 +656,13 @@ class NodeSelector extends UNISYS.Component { inactiveMode={'disabled'} shouldIgnoreSelection={this.state.isEditable} /> - +