From e47f3e0fcd8b34c7fe55b916ab0ebff8ec24d2d2 Mon Sep 17 00:00:00 2001 From: Tommy Phoenix Gatti Date: Mon, 10 May 2021 15:17:45 +1000 Subject: [PATCH 01/16] Added stub top bar for navigation and loaded examples, initial nodes are now a little further down so they aren't stuck behind the top bar --- src/App.js | 33 ++++++++++++++++++--------------- src/MenuBar.css | 21 +++++++++++++++++++++ src/MenuBar.js | 10 ++++++++++ src/canvas/Canvas.js | 4 ++-- src/sidebar/Sidebar.css | 2 +- 5 files changed, 52 insertions(+), 18 deletions(-) create mode 100644 src/MenuBar.css create mode 100644 src/MenuBar.js diff --git a/src/App.js b/src/App.js index c473ca2..464576f 100644 --- a/src/App.js +++ b/src/App.js @@ -1,5 +1,6 @@ import React from 'react'; import './App.css'; +import MenuBar from "./MenuBar"; import Canvas from "./canvas/Canvas"; import SideBar from "./sidebar/SideBar"; import {AnimateSharedLayout} from "framer-motion"; @@ -79,7 +80,6 @@ class App extends React.Component { * @param {string} [iri] - optional iri for nodes that have type 'nodeUri' * @returns {number} - id of node just created. */ - //todo: add iri to created nodes so it's in the graph!! And edges too! so queryExecutor can do it's thing! createNode = (x, y, type, content, iri) => { const { nodeCounter } = this.state; const variant = Node.variants[type](false); @@ -267,20 +267,23 @@ class App extends React.Component { return (
- - + +
+ + +
); diff --git a/src/MenuBar.css b/src/MenuBar.css new file mode 100644 index 0000000..256e89d --- /dev/null +++ b/src/MenuBar.css @@ -0,0 +1,21 @@ +.navbar-default { + background-color: #3e3f3a; + border-color: #3e3f3a; +} +.navbar-fixed-top { + top: 0; + width: 100%; + border-radius: 0; + position: fixed; + overflow: hidden; + right: 0; + left: 0; + z-index: 2000; +} +.navbar { + border-radius: 4px; + position: fixed; + min-height: 50px; + margin-bottom: 20px; + border: 1px solid transparent; +} \ No newline at end of file diff --git a/src/MenuBar.js b/src/MenuBar.js new file mode 100644 index 0000000..4367056 --- /dev/null +++ b/src/MenuBar.js @@ -0,0 +1,10 @@ +import './MenuBar.css'; + +export default function MenuBar(props) { + + return ( +
+ +
+ ); +} \ No newline at end of file diff --git a/src/canvas/Canvas.js b/src/canvas/Canvas.js index e7ce082..eb4f59d 100644 --- a/src/canvas/Canvas.js +++ b/src/canvas/Canvas.js @@ -88,7 +88,7 @@ export default class Canvas extends React.Component { this.props.updateEdgeIntersections(selectedEdge, currentUnfNode); this.props.onSelectedItemChange(type, currentUnfNode.id, prefixedNodeLabel, null); } else { // it must be a base class and we would need to create a new one! - const newNodeId = this.props.createNode(50, 50, type, suggestion.label, suggestion.iri); + const newNodeId = this.props.createNode(50, 100, type, suggestion.label, suggestion.iri); this.props.onSelectedItemChange(type, newNodeId, suggestion.label, null); } } @@ -110,7 +110,7 @@ export default class Canvas extends React.Component { this.props.updateEdgeIntersections(selectedEdge, currentUnfNode); this.props.onSelectedItemChange('nodeUnknown', currentUnfNode.id, suggestion.label, null); } else { - const newNodeId = this.props.createNode(50, 50, 'nodeUnknown', suggestion.label, null); + const newNodeId = this.props.createNode(50, 100, 'nodeUnknown', suggestion.label, null); this.props.onSelectedItemChange('nodeUnknown', newNodeId, suggestion.label); } } diff --git a/src/sidebar/Sidebar.css b/src/sidebar/Sidebar.css index 2b52198..c813009 100644 --- a/src/sidebar/Sidebar.css +++ b/src/sidebar/Sidebar.css @@ -11,7 +11,7 @@ grid-template-rows: minmax(100px, max-content) auto min-content; z-index: 1; float: right; - top: 0; + top: 50px; right: 0; background-color: darkgrey; overflow: hidden; From 4e13d61a43419e2e297e9e01e9a7e90ad3e094ed Mon Sep 17 00:00:00 2001 From: Tommy Phoenix Gatti Date: Mon, 10 May 2021 15:55:01 +1000 Subject: [PATCH 02/16] Added other items to the navbar, updated name --- public/index.html | 2 +- src/MenuBar.css | 31 +++++++++++++++++++++++++++++++ src/MenuBar.js | 12 +++++++++++- 3 files changed, 43 insertions(+), 2 deletions(-) diff --git a/public/index.html b/public/index.html index 016984e..e218e23 100644 --- a/public/index.html +++ b/public/index.html @@ -26,7 +26,7 @@ work correctly both with client-side routing and a non-root public URL. Learn how to configure a non-root public URL by running `npm run build`. --> - Liberal Sydney SPARQL UI + LMB SPARQL Explorer diff --git a/src/MenuBar.css b/src/MenuBar.css index 256e89d..d0aa95a 100644 --- a/src/MenuBar.css +++ b/src/MenuBar.css @@ -18,4 +18,35 @@ min-height: 50px; margin-bottom: 20px; border: 1px solid transparent; +} + +.nav { + list-style-type: none; + padding: 0 20px; + overflow: hidden; +} + +.nav-header { + float: left; + padding-left: 20px; + padding-right: 20px; +} + +.nav-header-text { + font-family: 'Roboto Condensed', sans-serif; + color: white; + font-size: medium; +} + +.nav-item { + float: right; + padding-left: 20px; + padding-right: 20px; +} + +.nav-item-text { + display: block; + color: white; + text-align: center; + font-family: 'Roboto Condensed', sans-serif; } \ No newline at end of file diff --git a/src/MenuBar.js b/src/MenuBar.js index 4367056..0a5d642 100644 --- a/src/MenuBar.js +++ b/src/MenuBar.js @@ -4,7 +4,17 @@ export default function MenuBar(props) { return (
- +
+

LMB SPARQL Explorer

+
+
); } \ No newline at end of file From 07ea08506a5e50f263ad25f3ddc5a9750848e1b1 Mon Sep 17 00:00:00 2001 From: Tommy Phoenix Gatti Date: Mon, 10 May 2021 16:11:56 +1000 Subject: [PATCH 03/16] Menubar items now similar size --- src/MenuBar.css | 1 + 1 file changed, 1 insertion(+) diff --git a/src/MenuBar.css b/src/MenuBar.css index d0aa95a..75f6bfb 100644 --- a/src/MenuBar.css +++ b/src/MenuBar.css @@ -49,4 +49,5 @@ color: white; text-align: center; font-family: 'Roboto Condensed', sans-serif; + margin: 0; } \ No newline at end of file From 066719cd945a4009a88596c9782f174e30fb8921 Mon Sep 17 00:00:00 2001 From: Tommy Phoenix Gatti Date: Mon, 10 May 2021 19:04:10 +1000 Subject: [PATCH 04/16] Added pointers for buttons --- src/MenuBar.css | 1 + src/sidebar/ItemViewerComponents.css | 1 + src/sidebar/Sidebar.css | 2 +- src/sidebar/SuggestiveSearch.css | 1 + 4 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/MenuBar.css b/src/MenuBar.css index 75f6bfb..b8603d2 100644 --- a/src/MenuBar.css +++ b/src/MenuBar.css @@ -50,4 +50,5 @@ text-align: center; font-family: 'Roboto Condensed', sans-serif; margin: 0; + cursor: pointer; } \ No newline at end of file diff --git a/src/sidebar/ItemViewerComponents.css b/src/sidebar/ItemViewerComponents.css index 71d8b85..8a73664 100644 --- a/src/sidebar/ItemViewerComponents.css +++ b/src/sidebar/ItemViewerComponents.css @@ -3,6 +3,7 @@ margin: 0.5em 0; padding: 0 8px 0 8px; border-radius: 10px; + cursor: pointer; } .button p { diff --git a/src/sidebar/Sidebar.css b/src/sidebar/Sidebar.css index c813009..86d7666 100644 --- a/src/sidebar/Sidebar.css +++ b/src/sidebar/Sidebar.css @@ -3,7 +3,7 @@ } .sidebar { - height: 100%; + height: 92%; width: var(--sidebarSize); position: fixed; display: grid; diff --git a/src/sidebar/SuggestiveSearch.css b/src/sidebar/SuggestiveSearch.css index 9b3d442..062f11d 100644 --- a/src/sidebar/SuggestiveSearch.css +++ b/src/sidebar/SuggestiveSearch.css @@ -26,6 +26,7 @@ ul { font-family: 'Roboto Condensed', sans-serif; margin-top: 5px; margin-bottom: 5px; + cursor: pointer; } .extra { From 7c1dd095c6e401017a87586c138eed48ee1a7a4d Mon Sep 17 00:00:00 2001 From: Tommy Phoenix Gatti Date: Mon, 10 May 2021 19:05:17 +1000 Subject: [PATCH 05/16] Staged prod uris --- src/sidebar/UtilityFunctions.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/sidebar/UtilityFunctions.js b/src/sidebar/UtilityFunctions.js index d74dd31..eb033bb 100644 --- a/src/sidebar/UtilityFunctions.js +++ b/src/sidebar/UtilityFunctions.js @@ -4,12 +4,11 @@ * @returns {Promise} */ export async function submitQuery(query) { - const url = "http://localhost:9999/blazegraph/sparql"; //todo: "https://lmb.cdhr.anu.edu.au/blazegraph/sparql"; + const url = "https://lmb.cdhr.anu.edu.au/blazegraph/sparql"; const response = await fetch(url, { method: "POST", headers: { 'Content-Type': 'application/x-www-form-urlencoded', - Authorization: 'blaze_lmb:yu%9R92Dps6GWPC+', Accept: 'application/sparql-results+json', 'Access-Control-Allow-Origin': 'https://lmb.cdhr.anu.edu.au' }, From 292d4434db509a19870937f159ac8a2d3f3b1ad0 Mon Sep 17 00:00:00 2001 From: Tommy Phoenix Gatti Date: Mon, 10 May 2021 21:19:08 +1000 Subject: [PATCH 06/16] Added examples! --- public/examples.json | 126 +++++++++++++++++++++++++++++++++++++++++++ src/App.js | 16 +++++- src/MenuBar.js | 44 +++++++++------ 3 files changed, 170 insertions(+), 16 deletions(-) create mode 100644 public/examples.json diff --git a/public/examples.json b/public/examples.json new file mode 100644 index 0000000..760d489 --- /dev/null +++ b/public/examples.json @@ -0,0 +1,126 @@ +{ + "examples": [ + { + "nodes": [ + { + "x": 0, + "y": 50, + "midX": 50, + "midY": 100, + "id": 1, + "type": "nodeUnknown", + "content": "?", + "isOptional": false, + "amalgam": { + "type": "UnknownClassAmalgam", + "inferredClass": { + "iri": "http://lmb.cdhr.anu.edu.au/Person", + "expansion": "http://lmb.cdhr.anu.edu.au", + "name": "Person", + "label": "Person" + } + } + }, + { + "x": 318, + "y": 265, + "midX": 338, + "midY": 285, + "id": 3, + "type": "nodeUnknown", + "content": "?", + "isOptional": false, + "amalgam": { + "type": "UnknownClassAmalgam", + "inferredClass": { + "iri": "http://lmb.cdhr.anu.edu.au/Costume", + "expansion": "http://lmb.cdhr.anu.edu.au", + "name": "Costume", + "label": "Costume" + } + } + }, + { + "x": 389, + "y": 110, + "midX": 409, + "midY": 130, + "id": 5, + "type": "nodeLiteral", + "content": "\"Mrs\"", + "isOptional": false, + "amalgam": null + }, + { + "x": 123, + "y": 342, + "midX": 143, + "midY": 362, + "id": 6, + "type": "nodeSelectedUnknown", + "content": "?", + "isOptional": false, + "amalgam": null + } + ], + "nodeCounter": 5, + "edges": [ + { + "id": 2, + "content": "has costume", + "type": "edgeKnown", + "isOptional": false, + "subject": { + "id": 1, + "intersectX": 50, + "intersectY": 100 + }, + "object": { + "id": 3, + "intersectX": 326.5787187994942, + "intersectY": 286.99504572921774 + }, + "complete": true, + "iri": "http://lmb.cdhr.anu.edu.au/has_costume" + }, + { + "id": 4, + "content": "has title", + "type": "edgeKnown", + "isOptional": false, + "subject": { + "id": 1, + "intersectX": 50, + "intersectY": 100 + }, + "object": { + "id": 5, + "intersectX": 395.343641168507, + "intersectY": 128.8588000976468 + }, + "complete": true, + "iri": "http://lmb.cdhr.anu.edu.au/has_title" + }, + { + "id": 5, + "content": "has surname", + "type": "edgeKnown", + "isOptional": false, + "subject": { + "id": 1, + "intersectX": 50, + "intersectY": 100 + }, + "object": { + "id": 6, + "intersectX": 153.59009545284934, + "intersectY": 345.9212022132683 + }, + "complete": true, + "iri": "http://lmb.cdhr.anu.edu.au/has_surname" + } + ], + "edgeCounter": 4 + } + ] +} \ No newline at end of file diff --git a/src/App.js b/src/App.js index 464576f..42d90f8 100644 --- a/src/App.js +++ b/src/App.js @@ -261,13 +261,27 @@ class App extends React.Component { })); } + /** + * + * @param {Object} example - object containing edge/node definitions, as well as current ids + */ + loadExampleIntoCanvas = (example) => { + const { nodes, edges, nodeCount, edgeCount } = example; + + this.setState({ + nodeCounter: nodeCount, + edgeCounter: edgeCount, + graph: {nodes: nodes, edges: edges} + }); + } + render(){ const { selected, transferredSuggestion, graph, tempEdge, canvasStateSnapshot } = this.state; return (
- +
{ + fetch('examples.json', { + 'Content-Type': 'application/json', + 'Accept': 'application/json' + }).then( + response => response.json().then( + json => this.props.loadExampleIntoCanvas(json.examples[0]), + error => console.warn("JSON is malformed", error)), + error => console.warn("Couldn't get the local .json examples:", error) + ); + }; - return ( -
-
-

LMB SPARQL Explorer

+ render() { + return ( +
+
+

LMB SPARQL Explorer

+
+
- -
- ); + ); + } } \ No newline at end of file From eec060bc397866abbbf09f4d3758558d75bf4cfe Mon Sep 17 00:00:00 2001 From: Tommy Phoenix Gatti Date: Mon, 10 May 2021 22:58:12 +1000 Subject: [PATCH 07/16] Added stub delete method for each item (deletes items recursively) --- src/App.js | 46 +++++++++++++++++++++++++++++ src/canvas/Node.js | 3 +- src/sidebar/ItemViewerComponents.js | 14 +++++++++ src/sidebar/SelectedItemViewer.js | 44 +++++++++++++++++++-------- src/sidebar/SideBar.js | 1 + 5 files changed, 93 insertions(+), 15 deletions(-) diff --git a/src/App.js b/src/App.js index 42d90f8..479f39c 100644 --- a/src/App.js +++ b/src/App.js @@ -261,6 +261,51 @@ class App extends React.Component { })); } + /** + * + * @param id + * @param type + */ + deleteItemCascade = (id, type) => { + const { graph } = this.state; + + if (type.startsWith('node')) { + const deletedNodeOutgoingEdges = graph.edges.filter(edge => edge.subject.id === id); + for (const outgoingEdge of deletedNodeOutgoingEdges) { + this.deleteItemRecursively(outgoingEdge.id, outgoingEdge.type); + } + if (deletedNodeOutgoingEdges.length === 0) { + this.changeNodeState(id, {type: 'nodeUnf', content: '', amalgam: null}); + } else { + this.deleteNode(id); + } + } else { + const deletedEdgeNode = graph.nodes.find(node => node.id === id); + if (deletedEdgeNode) { + this.deleteItemRecursively(deletedEdgeNode.id, deletedEdgeNode.type); + } + this.deleteEdge(id); + } + } + + deleteItemRecursively = (id, type) => { + const { graph } = this.state; + + if (type.startsWith('node')) { + const deletedNodeOutgoingEdges = graph.edges.filter(edge => edge.subject.id === id); + for (const outgoingEdge of deletedNodeOutgoingEdges) { + this.deleteItemRecursively(outgoingEdge.id, outgoingEdge.type); + } + this.deleteNode(id); + } else { + const deletedEdgeNode = graph.nodes.find(node => node.id === id); + if (deletedEdgeNode) { + this.deleteItemRecursively(deletedEdgeNode.id, deletedEdgeNode.type); + } + this.deleteEdge(id); + } + } + /** * * @param {Object} example - object containing edge/node definitions, as well as current ids @@ -294,6 +339,7 @@ class App extends React.Component { acknowledgeTransferredSuggestion={this.handleAcknowledgedSuggestion}/> diff --git a/src/canvas/Node.js b/src/canvas/Node.js index d6525b5..d3d89c4 100644 --- a/src/canvas/Node.js +++ b/src/canvas/Node.js @@ -94,7 +94,7 @@ export default class Node extends React.Component { if (changedText.match(/".*".*|true|false|[+-]?\d+|[+-]?\d*\.\d+|[+-]?(\d+\.\d*[eE][+-]?\d+|\d+[eE][+-]?\d+)/)){ changedType = 'nodeLiteral'; - } else if (type === 'nodeSelectedUnknown' || changedText.match(/\?.+/)) { //todo: consider control flow + } else if (type === 'nodeSelectedUnknown' || changedText.match(/\?.+/)) { changedType = 'nodeSelectedUnknown'; } else if (changedText === '?') { changedType = 'nodeUnknown'; @@ -105,7 +105,6 @@ export default class Node extends React.Component { this.props.onChangeNodeState(id, {content: changedText, type: changedType}); } - //todo: selectedItemViewer checkbox for selected? render(){ const { type, isOptional, content, x, y, amalgam } = this.props; diff --git a/src/sidebar/ItemViewerComponents.js b/src/sidebar/ItemViewerComponents.js index 0a9a129..baa3c20 100644 --- a/src/sidebar/ItemViewerComponents.js +++ b/src/sidebar/ItemViewerComponents.js @@ -116,4 +116,18 @@ export function BoundUnknownCheckbox(props) { ); +} + +export function DeleteItemButton(props) { + const { id } = props; + const deleteItem = (id) => props.deleteItemCascade(id); + + return ( + <> +

Delete Node

+
deleteItem(id)}> +

Delete

+
+ + ); } \ No newline at end of file diff --git a/src/sidebar/SelectedItemViewer.js b/src/sidebar/SelectedItemViewer.js index b49c8f8..e90ea61 100644 --- a/src/sidebar/SelectedItemViewer.js +++ b/src/sidebar/SelectedItemViewer.js @@ -8,7 +8,8 @@ import { ItemDesc, ItemInferredProps, ItemLiteralType, - BoundUnknownCheckbox + BoundUnknownCheckbox, + DeleteItemButton } from "./ItemViewerComponents"; export default class SelectedItemViewer extends React.Component { @@ -52,7 +53,7 @@ export default class SelectedItemViewer extends React.Component { } render() { - const { type, content, basePrefix, info, infoLoaded, meta } = this.props; + const { id, type, content, basePrefix, info, infoLoaded, meta } = this.props; const { expandedPrefix, expandedPrefixLoaded } = this.state; if (type === "nodeUri" || type === "edgeKnown") { @@ -64,19 +65,31 @@ export default class SelectedItemViewer extends React.Component { if (expandedPrefixLoaded) prefix = expandedPrefix; if (type === "nodeUri") { - return (); + return ( + + ); } else { - return (); + return (); } } else if (type === "nodeUnknown" || type === "nodeSelectedUnknown") { return ( - this.notifySelectedStateChange(newType)} /> + this.notifySelectedStateChange(newType)} + deleteItemCascade={this.props.deleteItemCascade}/> ); } else if (type === "nodeLiteral") { - return (); + return ( + + ); } else if (type === "edgeUnknown") { - return(); + return( + + ); } else { return (); } @@ -84,13 +97,14 @@ export default class SelectedItemViewer extends React.Component { } function SelectedUriNodeViewer(props) { - const { type, prefix, name, info, infoLoaded } = props; + const { id, type, prefix, name, info, infoLoaded } = props; //todo: might need to take into account different delimiters such as '.', '#', '/'. const selectedUriInfo = info[prefix + '#' + name] ? info[prefix + '#' + name].comment : false; return (
+ props.deleteItemCascade(id, type)}/> {infoLoaded && selectedUriInfo && @@ -100,11 +114,12 @@ function SelectedUriNodeViewer(props) { } function SelectedUnknownNodeViewer(props) { - const { type, content, meta } = props; + const { id, type, content, meta } = props; return (
+ props.deleteItemCascade(id, type)}/> props.onBoundChange(type)} /> {meta && @@ -114,36 +129,39 @@ function SelectedUnknownNodeViewer(props) { } function SelectedLiteralNodeViewer(props) { - const { type, content } = props; + const { id, type, content } = props; const name = content.match(/".*".*/) ? content.split(/(?=[^"]*$)/)[0] : content; return (
+ props.deleteItemCascade(id, type)}/>
); } function SelectedUnknownEdgeViewer(props) { - const { type, content } = props; + const { id, type, content } = props; return (
+ props.deleteItemCascade(id, type)}/>
); } function SelectedKnownEdgeViewer(props) { - const { type, prefix, name, info, infoLoaded } = props; + const { id, type, prefix, name, info, infoLoaded } = props; const selectedUriInfo = info[prefix + '#' + name] ? info[prefix + '#' + name].comment : false; return (
+ props.deleteItemCascade(id, type)}/> {infoLoaded && selectedUriInfo && diff --git a/src/sidebar/SideBar.js b/src/sidebar/SideBar.js index d8fb117..125f3e0 100644 --- a/src/sidebar/SideBar.js +++ b/src/sidebar/SideBar.js @@ -73,6 +73,7 @@ export default class SideBar extends React.Component { Date: Mon, 10 May 2021 22:58:42 +1000 Subject: [PATCH 08/16] MenuBar.js method now can load different examples --- src/MenuBar.js | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/src/MenuBar.js b/src/MenuBar.js index cec4557..d3a9704 100644 --- a/src/MenuBar.js +++ b/src/MenuBar.js @@ -2,13 +2,19 @@ import React from "react"; import './MenuBar.css'; export default class MenuBar extends React.Component { - loadExample = () => { + /** + * + * @param {number} i - index of example to load + */ + loadExample = (i) => { fetch('examples.json', { - 'Content-Type': 'application/json', - 'Accept': 'application/json' + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json' + } }).then( response => response.json().then( - json => this.props.loadExampleIntoCanvas(json.examples[0]), + json => this.props.loadExampleIntoCanvas(json.examples[i]), error => console.warn("JSON is malformed", error)), error => console.warn("Couldn't get the local .json examples:", error) ); @@ -24,8 +30,14 @@ export default class MenuBar extends React.Component {
  • Return to LMB Main
  • + {/*
  • */} + {/*

    this.loadExample(2)}>Load Example 3

    */} + {/*
  • */} + {/*
  • */} + {/*

    this.loadExample(1)}>Load Example 2

    */} + {/*
  • */}
  • -

    Load Examples

    +

    this.loadExample(0)}>Load Example 1

  • From 422c353eba24fc9fed45a20d60e2a2e2e080b6ab Mon Sep 17 00:00:00 2001 From: Tommy Phoenix Gatti Date: Mon, 10 May 2021 23:36:17 +1000 Subject: [PATCH 09/16] Added commenting for recursive delete, selected item is incoming item from deleted item --- src/App.js | 57 ++++++++++++++++++++++++++++++++---------------------- 1 file changed, 34 insertions(+), 23 deletions(-) diff --git a/src/App.js b/src/App.js index 479f39c..6d4f688 100644 --- a/src/App.js +++ b/src/App.js @@ -262,47 +262,58 @@ class App extends React.Component { } /** - * - * @param id - * @param type + * Recursively deletes an item and all it's outgoing connections + * @param {number} id - id of the item to delete + * @param {string} type - type of the item to delete, allows calling of correct graph modification methods + * @param {boolean} isFirst - if it's the most shallow iteration, change the node to an 'unf' node if theres no + * further connections */ - deleteItemCascade = (id, type) => { + deleteItemCascade = (id, type, isFirst) => { const { graph } = this.state; - if (type.startsWith('node')) { + if (type.startsWith('node')) { // find all the outgoing edges and recursively delete const deletedNodeOutgoingEdges = graph.edges.filter(edge => edge.subject.id === id); for (const outgoingEdge of deletedNodeOutgoingEdges) { - this.deleteItemRecursively(outgoingEdge.id, outgoingEdge.type); + this.deleteItemCascade(outgoingEdge.id, outgoingEdge.type, false); } - if (deletedNodeOutgoingEdges.length === 0) { + if (isFirst && deletedNodeOutgoingEdges.length === 0) { this.changeNodeState(id, {type: 'nodeUnf', content: '', amalgam: null}); } else { this.deleteNode(id); } - } else { - const deletedEdgeNode = graph.nodes.find(node => node.id === id); + } else { // it is an edge and we find the connected node and recursively delete + const deletedEdge = graph.edges.find(edge => edge.id === id); + const deletedEdgeNode = graph.nodes.find(node => node.id === deletedEdge.object.id); if (deletedEdgeNode) { - this.deleteItemRecursively(deletedEdgeNode.id, deletedEdgeNode.type); + this.deleteItemCascade(deletedEdgeNode.id, deletedEdgeNode.type, false); } this.deleteEdge(id); } - } - deleteItemRecursively = (id, type) => { - const { graph } = this.state; + if (isFirst) { // on most shallow recursion, set selected item to incoming item or empty. + this.findSuitableSelectedItemChange(id, type, graph); + } + } + /** + * Finds the incoming node or edge off the deleted on, setting it as the selected item + * @param id - id of the deleted item + * @param type - type of the deleted item + * @param graph - graph snapshot at lowest level of recursion (no items deleted yet, so we can access + * deleted item data) + */ + findSuitableSelectedItemChange = (id, type, graph) => { if (type.startsWith('node')) { - const deletedNodeOutgoingEdges = graph.edges.filter(edge => edge.subject.id === id); - for (const outgoingEdge of deletedNodeOutgoingEdges) { - this.deleteItemRecursively(outgoingEdge.id, outgoingEdge.type); + const selectedEdge = graph.edges.find(edge => edge.object.id === id); + if (selectedEdge) { + this.handleSelectedItemChange(selectedEdge.type, selectedEdge.id, selectedEdge.content, null); + } else { + this.handleSelectedItemChange('', -1, '', ''); } - this.deleteNode(id); } else { - const deletedEdgeNode = graph.nodes.find(node => node.id === id); - if (deletedEdgeNode) { - this.deleteItemRecursively(deletedEdgeNode.id, deletedEdgeNode.type); - } - this.deleteEdge(id); + const deletedEdge = graph.edges.find(edge => edge.id === id); + const selectedNode = graph.nodes.find(node => node.id === deletedEdge.subject.id); + this.handleSelectedItemChange(selectedNode.type, selectedNode.id, selectedNode.content, selectedNode.meta) } } @@ -339,7 +350,7 @@ class App extends React.Component { acknowledgeTransferredSuggestion={this.handleAcknowledgedSuggestion}/> this.deleteItemCascade(id, type, true)} onSelectedItemChange={this.handleSelectedItemChange} onTransferSuggestionToCanvas={this.handleTransferSuggestionToCanvas} onRequestCanvasState={this.handleRequestCanvasState}/> From 9aab01098917fe08ab5aedc936b8763d12a8c8c1 Mon Sep 17 00:00:00 2001 From: Tommy Phoenix Gatti Date: Tue, 11 May 2021 00:21:56 +1000 Subject: [PATCH 10/16] Examples node/edgeCounter are now non-conflicting, added todo, fixed variable names --- public/examples.json | 4 ++-- src/App.js | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/public/examples.json b/public/examples.json index 760d489..acb1f26 100644 --- a/public/examples.json +++ b/public/examples.json @@ -63,7 +63,7 @@ "amalgam": null } ], - "nodeCounter": 5, + "nodeCounter": 6, "edges": [ { "id": 2, @@ -120,7 +120,7 @@ "iri": "http://lmb.cdhr.anu.edu.au/has_surname" } ], - "edgeCounter": 4 + "edgeCounter": 5 } ] } \ No newline at end of file diff --git a/src/App.js b/src/App.js index 6d4f688..8e49c96 100644 --- a/src/App.js +++ b/src/App.js @@ -276,7 +276,7 @@ class App extends React.Component { for (const outgoingEdge of deletedNodeOutgoingEdges) { this.deleteItemCascade(outgoingEdge.id, outgoingEdge.type, false); } - if (isFirst && deletedNodeOutgoingEdges.length === 0) { + if (isFirst && deletedNodeOutgoingEdges.length === 0) { //todo: edge case where if only one node, turns small this.changeNodeState(id, {type: 'nodeUnf', content: '', amalgam: null}); } else { this.deleteNode(id); @@ -322,11 +322,11 @@ class App extends React.Component { * @param {Object} example - object containing edge/node definitions, as well as current ids */ loadExampleIntoCanvas = (example) => { - const { nodes, edges, nodeCount, edgeCount } = example; + const { nodes, edges, nodeCounter, edgeCounter } = example; this.setState({ - nodeCounter: nodeCount, - edgeCounter: edgeCount, + nodeCounter: nodeCounter, + edgeCounter: edgeCounter, graph: {nodes: nodes, edges: edges} }); } From 537bb98f50356c25057cf19ed9bb6980f1a9f402 Mon Sep 17 00:00:00 2001 From: Tommy Phoenix Gatti Date: Tue, 11 May 2021 00:30:36 +1000 Subject: [PATCH 11/16] finding new selected item on delete now has the correct `meta` object definition --- package.json | 1 - src/App.js | 4 +++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 950c205..9b7c1e9 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,6 @@ "name": "liberal-sydney-sparql-ui", "version": "0.9.0", "private": true, - "homepage": "https://lmb.cdhr.anu.edu.au/explorer", "dependencies": { "framer-motion": "^3.3.0", "react": "^17.0.1", diff --git a/src/App.js b/src/App.js index 8e49c96..b86ec1d 100644 --- a/src/App.js +++ b/src/App.js @@ -313,7 +313,9 @@ class App extends React.Component { } else { const deletedEdge = graph.edges.find(edge => edge.id === id); const selectedNode = graph.nodes.find(node => node.id === deletedEdge.subject.id); - this.handleSelectedItemChange(selectedNode.type, selectedNode.id, selectedNode.content, selectedNode.meta) + this.handleSelectedItemChange( + selectedNode.type, selectedNode.id, selectedNode.content, {amalgam: selectedNode.amalgam} + ); } } From 51852d744c72050c6c1ce1e653756ea9d6cf48af Mon Sep 17 00:00:00 2001 From: Tommy Phoenix Gatti Date: Tue, 11 May 2021 01:07:01 +1000 Subject: [PATCH 12/16] css now uses calc() to get variable widths/heights --- src/MenuBar.css | 2 +- src/sidebar/Sidebar.css | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/MenuBar.css b/src/MenuBar.css index b8603d2..0d205ac 100644 --- a/src/MenuBar.css +++ b/src/MenuBar.css @@ -15,7 +15,7 @@ .navbar { border-radius: 4px; position: fixed; - min-height: 50px; + height: 50px; margin-bottom: 20px; border: 1px solid transparent; } diff --git a/src/sidebar/Sidebar.css b/src/sidebar/Sidebar.css index 86d7666..e790295 100644 --- a/src/sidebar/Sidebar.css +++ b/src/sidebar/Sidebar.css @@ -3,7 +3,7 @@ } .sidebar { - height: 92%; + height: calc(100% - 50px); width: var(--sidebarSize); position: fixed; display: grid; From 4a7a4c0e10cd30adf9928d549861ec6377ccc420 Mon Sep 17 00:00:00 2001 From: Tommy Phoenix Gatti Date: Tue, 11 May 2021 01:13:56 +1000 Subject: [PATCH 13/16] Now displays the SPARQL at the bottom bar --- src/sidebar/QueryExecutor.css | 23 +++++++++++++++++++++++ src/sidebar/QueryExecutor.js | 34 ++++++++++++++++++++++++---------- src/sidebar/SideBar.js | 4 ++-- 3 files changed, 49 insertions(+), 12 deletions(-) diff --git a/src/sidebar/QueryExecutor.css b/src/sidebar/QueryExecutor.css index 668fc5f..763d58f 100644 --- a/src/sidebar/QueryExecutor.css +++ b/src/sidebar/QueryExecutor.css @@ -6,6 +6,29 @@ div.executequery-wrapper { border-top: 2px solid black; } +.results-container { + position: fixed; + bottom: 0; + left: 0; + float: left; + height: min-content; + width: calc(100% - var(--sidebarSize)); + background-color: darkgray; + border-top-left-radius: 15px; + border-top-right-radius: 15px; +} + +.results-header { + font-family: 'Roboto Condensed', sans-serif; + font-size: larger; + padding-left: 20px; +} +.sparql { + font-family: "Courier New", sans-serif; + white-space: pre-wrap; + padding-left: 20px; +} + .small { font-size: small; font-family: 'Roboto Condensed', sans-serif; diff --git a/src/sidebar/QueryExecutor.js b/src/sidebar/QueryExecutor.js index f34ca7f..9afcdc9 100644 --- a/src/sidebar/QueryExecutor.js +++ b/src/sidebar/QueryExecutor.js @@ -4,7 +4,7 @@ import './ItemViewerComponents.css'; import './QueryExecutor.css'; import {submitQuery} from "./UtilityFunctions"; -export default class ExecuteQueryButton extends React.Component { +export default class ExecuteQuerySection extends React.Component { static variants = { ready: {backgroundColor: '#b3b3b3'}, loading: {backgroundColor: '#9c9c9c'} @@ -165,23 +165,37 @@ export default class ExecuteQueryButton extends React.Component { } render() { - const { gettingCanvasState, convertingGraphToSparql, error } = this.state; + const { query, gettingCanvasState, convertingGraphToSparql, error } = this.state; const animation = gettingCanvasState || convertingGraphToSparql ? 'loading' : 'ready'; return ( -
    - -

    Execute Query

    -
    - {gettingCanvasState &&

    Getting Canvas State...

    } - {convertingGraphToSparql &&

    Converting Graph to SPARQL...

    } - {error &&

    {error}

    } +
    +
    + +

    Execute Query

    +
    + {gettingCanvasState &&

    Getting Canvas State...

    } + {convertingGraphToSparql &&

    Converting Graph to SPARQL...

    } + {error &&

    {error}

    } +
    + {query !== '' && }
    + ); } } +function QueryResultsViewer(props) { + + return ( + +

    Results Viewer

    +

    {props.query}

    +
    + ); +} + class ErrorMessages { static noSelectedUnknowns = "Query failed: There are no nodes selected to display as results. Click on a " + "node and then click the 'Show in results' button to have it come up in the query, then try again."; diff --git a/src/sidebar/SideBar.js b/src/sidebar/SideBar.js index 125f3e0..6b2a88b 100644 --- a/src/sidebar/SideBar.js +++ b/src/sidebar/SideBar.js @@ -3,7 +3,7 @@ import { submitQuery } from './UtilityFunctions' import "./Sidebar.css"; import SelectedItemViewer from "./SelectedItemViewer"; import SuggestiveSearch from "./SuggestiveSearch"; -import ExecuteQueryButton from "./QueryExecutor"; +import ExecuteQuerySection from "./QueryExecutor"; export default class SideBar extends React.Component { constructor(props) { @@ -80,7 +80,7 @@ export default class SideBar extends React.Component { basePrefix={basePrefix} basePrefixLoaded={basePrefixLoaded} info={info} infoLoaded={infoLoaded} onTransferSuggestionToCanvas={this.props.onTransferSuggestionToCanvas} /> -
    ); From 0301c093cf98ab6ca2b90cc9fb215854f248fa8c Mon Sep 17 00:00:00 2001 From: Tommy Phoenix Gatti Date: Tue, 11 May 2021 01:29:33 +1000 Subject: [PATCH 14/16] Can open and close the results viewer --- src/sidebar/QueryExecutor.css | 9 +++++++++ src/sidebar/QueryExecutor.js | 9 ++++++--- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/sidebar/QueryExecutor.css b/src/sidebar/QueryExecutor.css index 763d58f..2292a36 100644 --- a/src/sidebar/QueryExecutor.css +++ b/src/sidebar/QueryExecutor.css @@ -22,6 +22,8 @@ div.executequery-wrapper { font-family: 'Roboto Condensed', sans-serif; font-size: larger; padding-left: 20px; + margin-left: 20px; + margin-right: 20px; } .sparql { font-family: "Courier New", sans-serif; @@ -29,6 +31,13 @@ div.executequery-wrapper { padding-left: 20px; } +.close { + position: fixed; + top: 0; + right: 0; + float: right; +} + .small { font-size: small; font-family: 'Roboto Condensed', sans-serif; diff --git a/src/sidebar/QueryExecutor.js b/src/sidebar/QueryExecutor.js index 9afcdc9..4cd8a1f 100644 --- a/src/sidebar/QueryExecutor.js +++ b/src/sidebar/QueryExecutor.js @@ -1,4 +1,4 @@ -import React from 'react'; +import React, {useState} from 'react'; import { motion } from 'framer-motion'; import './ItemViewerComponents.css'; import './QueryExecutor.css'; @@ -187,10 +187,13 @@ export default class ExecuteQuerySection extends React.Component { } function QueryResultsViewer(props) { + const [ isOpen, setIsOpen ] = useState(true); + const toggleViewer = () => setIsOpen(!isOpen); return ( - -

    Results Viewer

    + toggleViewer()} + initial={{height: 0}} animate={{height: isOpen ? 'min-content' : '50px'}}> +

    Results Viewer

    {props.query}

    ); From 3a4d83f3bd7dbe4c099ea0c5188e6a8055ec281c Mon Sep 17 00:00:00 2001 From: Tommy Phoenix Gatti Date: Tue, 11 May 2021 01:49:05 +1000 Subject: [PATCH 15/16] Added more examples --- public/examples.json | 209 ++++++++++++++++++++++++++++++++++++++++++- src/MenuBar.js | 12 +-- 2 files changed, 214 insertions(+), 7 deletions(-) diff --git a/public/examples.json b/public/examples.json index acb1f26..8d32849 100644 --- a/public/examples.json +++ b/public/examples.json @@ -63,7 +63,7 @@ "amalgam": null } ], - "nodeCounter": 6, + "nodeCounter": 7, "edges": [ { "id": 2, @@ -120,7 +120,214 @@ "iri": "http://lmb.cdhr.anu.edu.au/has_surname" } ], + "edgeCounter": 6 + }, + { + "nodes": [ + { + "x": 0, + "y": 50, + "midX": 50, + "midY": 100, + "id": 1, + "type": "nodeUnknown", + "content": "?", + "isOptional": false, + "amalgam": { + "type": "UnknownClassAmalgam", + "inferredClass": { + "iri": "http://lmb.cdhr.anu.edu.au/Person", + "expansion": "http://lmb.cdhr.anu.edu.au", + "name": "Person", + "label": "Person" + } + } + }, + { + "x": 352, + "y": 129, + "midX": 372, + "midY": 149, + "id": 4, + "type": "nodeLiteral", + "content": "\"Mrs\"", + "isOptional": false, + "amalgam": null + }, + { + "x": 111, + "y": 286, + "midX": 131, + "midY": 306, + "id": 5, + "type": "nodeSelectedUnknown", + "content": "?surname", + "isOptional": false, + "amalgam": null + } + ], + "nodeCounter": 6, + "edges": [ + { + "id": 3, + "content": "has title", + "type": "edgeKnown", + "isOptional": false, + "subject": { + "id": 1, + "intersectX": 50, + "intersectY": 100 + }, + "object": { + "id": 4, + "intersectX": 358.6223376099571, + "intersectY": 146.9642687667326 + }, + "complete": true, + "iri": "http://lmb.cdhr.anu.edu.au/has_title" + }, + { + "id": 4, + "content": "has surname", + "type": "edgeKnown", + "isOptional": false, + "subject": { + "id": 1, + "intersectX": 50, + "intersectY": 100 + }, + "object": { + "id": 5, + "intersectX": 139.7193814705676, + "intersectY": 290.75472096445003 + }, + "complete": true, + "iri": "http://lmb.cdhr.anu.edu.au/has_surname" + } + ], "edgeCounter": 5 + }, + { + "nodes": [ + { + "x": 0, + "y": 50, + "midX": 50, + "midY": 100, + "id": 1, + "type": "nodeUnknown", + "content": "?", + "isOptional": false, + "amalgam": { + "type": "UnknownClassAmalgam", + "inferredClass": { + "iri": "http://lmb.cdhr.anu.edu.au/Person", + "expansion": "http://lmb.cdhr.anu.edu.au", + "name": "Person", + "label": "Person" + } + } + }, + { + "x": 234, + "y": 296, + "midX": 254, + "midY": 316, + "id": 3, + "type": "nodeUnknown", + "content": "?", + "isOptional": false, + "amalgam": { + "type": "UnknownClassAmalgam", + "inferredClass": { + "iri": "http://lmb.cdhr.anu.edu.au/Costume", + "expansion": "http://lmb.cdhr.anu.edu.au", + "name": "Costume", + "label": "Costume" + } + } + }, + { + "x": 603, + "y": 236, + "midX": 623, + "midY": 256, + "id": 5, + "type": "nodeSelectedUnknown", + "content": "?category", + "isOptional": false, + "amalgam": null + }, + { + "x": 379, + "y": 103, + "midX": 399, + "midY": 123, + "id": 6, + "type": "nodeSelectedUnknown", + "content": "?surname", + "isOptional": false, + "amalgam": null + } + ], + "nodeCounter": 7, + "edges": [ + { + "id": 2, + "content": "has costume", + "type": "edgeKnown", + "isOptional": false, + "subject": { + "id": 1, + "intersectX": 50, + "intersectY": 100 + }, + "object": { + "id": 3, + "intersectX": 249.5393117001242, + "intersectY": 309.7720969155152 + }, + "complete": true, + "iri": "http://lmb.cdhr.anu.edu.au/has_costume" + }, + { + "id": 4, + "content": "has costume category", + "type": "edgeKnown", + "isOptional": false, + "subject": { + "id": 3, + "intersectX": 254, + "intersectY": 316 + }, + "object": { + "id": 5, + "intersectX": 603.1407343480364, + "intersectY": 289.74881696631303 + }, + "complete": true, + "iri": "http://lmb.cdhr.anu.edu.au/has_costume_category" + }, + { + "id": 5, + "content": "has surname", + "type": "edgeKnown", + "isOptional": false, + "subject": { + "id": 1, + "intersectX": 50, + "intersectY": 100 + }, + "object": { + "id": 6, + "intersectX": 379.4818368604998, + "intersectY": 146.07529644751054 + }, + "complete": true, + "iri": "http://lmb.cdhr.anu.edu.au/has_surname" + } + ], + "edgeCounter": 6 } ] } \ No newline at end of file diff --git a/src/MenuBar.js b/src/MenuBar.js index d3a9704..23a665a 100644 --- a/src/MenuBar.js +++ b/src/MenuBar.js @@ -30,12 +30,12 @@ export default class MenuBar extends React.Component {
  • Return to LMB Main
  • - {/*
  • */} - {/*

    this.loadExample(2)}>Load Example 3

    */} - {/*
  • */} - {/*
  • */} - {/*

    this.loadExample(1)}>Load Example 2

    */} - {/*
  • */} +
  • +

    this.loadExample(2)}>Load Example 3

    +
  • +
  • +

    this.loadExample(1)}>Load Example 2

    +
  • this.loadExample(0)}>Load Example 1

  • From 83d83903ff32c93b722cf3f3183e4ec521e1fa1a Mon Sep 17 00:00:00 2001 From: Tommy Phoenix Gatti Date: Tue, 11 May 2021 01:54:56 +1000 Subject: [PATCH 16/16] updated package.json version and homepage --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 9b7c1e9..42322c9 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,8 @@ { "name": "liberal-sydney-sparql-ui", - "version": "0.9.0", + "version": "0.9.3", "private": true, + "homepage": "https://lmb.cdhr.anu.edu.au/explorer", "dependencies": { "framer-motion": "^3.3.0", "react": "^17.0.1",