diff --git a/GeppettoConfiguration.json b/GeppettoConfiguration.json
index 4e5bdf966..b893c476c 100644
--- a/GeppettoConfiguration.json
+++ b/GeppettoConfiguration.json
@@ -13,6 +13,18 @@
"type": "website",
"url": "http://v2.virtualflybrain.org",
"icon" :"https://v2.virtualflybrain.org/images/vfbbrain_icon.png",
- "image": "https://v2.virtualflybrain.org/images/vfbbrain_icon.png"
+ "image": "https://v2.virtualflybrain.org/images/vfbbrain_icon.png",
+ "errorDialog": {
+ "message" : "I broke geppetto, ops!",
+ "githubButton": {
+ "enabled" : true,
+ "url" : "https://github.com/VirtualFlyBrain/geppetto-vfb/issues"
+ },
+ "twitterButton": {
+ "enabled" : true,
+ "url" : "https://twitter.com/openworm",
+ "message" : "I broke geppetto, ops!"
+ }
+ }
}
}
\ No newline at end of file
diff --git a/components/VFBMain.js b/components/VFBMain.js
index 9ae19034f..09abb64ed 100644
--- a/components/VFBMain.js
+++ b/components/VFBMain.js
@@ -47,6 +47,7 @@ export default class VFBMain extends React.Component {
idSelected: undefined
};
+ this.addVfbId = this.addVfbId.bind(this);
this.menuHandler = this.menuHandler.bind(this);
this.setWrapperRef = this.setWrapperRef.bind(this);
this.htmlToolbarRef = this.htmlToolbarRef.bind(this);
@@ -786,7 +787,8 @@ export default class VFBMain extends React.Component {
id="treeWidget"
instance={this.instanceOnFocus}
size={{ height: _height, width: _width }}
- ref={ref => this.treeBrowserReference = ref}/>
+ ref={ref => this.treeBrowserReference = ref}
+ selectionHandler={this.addVfbId}/>
);
}
}
diff --git a/components/configuration/treeWidgetConfiguration.js b/components/configuration/treeWidgetConfiguration.js
new file mode 100644
index 000000000..37a299831
--- /dev/null
+++ b/components/configuration/treeWidgetConfiguration.js
@@ -0,0 +1,22 @@
+var restPostConfig = {
+ url: "http://pdb.virtualflybrain.org/db/data/transaction/commit",
+ contentType: "application/json"
+};
+
+var treeCypherQuery = instance => ({
+ "statements": [
+ {
+ "statement": "MATCH (root:Class)<-[:INSTANCEOF]-(t:Individual {short_form:'" + instance + "'})"
+ + "<-[:depicts]-(tc:Individual)<-[ie:in_register_with]-(c:Individual)-[:depicts]->(image:"
+ + "Individual)-[r:INSTANCEOF]->(anat:Class) WHERE has(ie.index) WITH root, anat,r,image"
+ + " MATCH p=allShortestPaths((root)<-[:SUBCLASSOF|part_of*..]-(anat:Class)) RETURN p,r,image",
+ "resultDataContents": ["graph"]
+ }
+ ]
+});
+
+
+module.exports = {
+ restPostConfig,
+ treeCypherQuery
+};
\ No newline at end of file
diff --git a/components/interface/FocusTerm.js b/components/interface/FocusTerm.js
index b69b563fa..df1a78606 100644
--- a/components/interface/FocusTerm.js
+++ b/components/interface/FocusTerm.js
@@ -1,6 +1,11 @@
import React, { Component } from 'react';
import { Tab, Tabs, TabList, TabPanel } from 'react-tabs';
import Menu from 'geppetto-client/js/components/interface/menu/Menu';
+import Tooltip from '@material-ui/core/Tooltip';
+import {
+ createMuiTheme,
+ MuiThemeProvider
+} from "@material-ui/core/styles";
var GEPPETTO = require('geppetto');
var Rnd = require('react-rnd').default;
@@ -15,6 +20,7 @@ export default class FocusTerm extends React.Component {
this.focusTermConfiguration = require('../configuration/focusTermConfiguration.js').focusTermConfiguration;
this.labels = require('../configuration/focusTermConfiguration.js').subMenusGrouping;
+ this.theme = createMuiTheme({ overrides: { MuiTooltip: { tooltip: { fontSize: "12px" } } } });
this.clearAll = this.clearAll.bind(this);
this.menuHandler = this.menuHandler.bind(this);
@@ -122,7 +128,7 @@ export default class FocusTerm extends React.Component {
if (allQueries2.length > 0) {
focusSubMenu.push(
{
- label: "Search for",
+ label: "Query for",
icon: "",
action: "",
position: "left",
@@ -139,7 +145,7 @@ export default class FocusTerm extends React.Component {
} else {
focusSubMenu.push(
{
- label: "Search for",
+ label: "Query for",
icon: "",
action: "",
position: "left",
@@ -160,7 +166,7 @@ export default class FocusTerm extends React.Component {
if (allQueries.length > 0) {
focusSubMenu.push(
{
- label: "Search for",
+ label: "Query for",
icon: "",
action: "",
position: "left",
@@ -178,7 +184,7 @@ export default class FocusTerm extends React.Component {
if (allQueries.length > 0) {
focusSubMenu.push(
{
- label: "Search for",
+ label: "Query for",
icon: "",
action: "",
position: "left",
@@ -476,55 +482,63 @@ export default class FocusTerm extends React.Component {
- {
- this.clearAll();
- }}>
- Clear all
-
- {
- this.props.UIUpdateManager("spotlightVisible");
- }}>
- Open the spotlight
-
- {
- this.props.UIUpdateManager("queryBuilderVisible");
- }}>
- Open the query builder
-
- {
- this.props.UIUpdateManager("controlPanelVisible");
- }}>
- Open the control panel
-
- { window.history.state !== null && window.history.state.b !== undefined && window.history.state.b !== ""
- ? {
- if (window.vfbUpdatingHistory == false) {
- window.history.back();
- }
- }}>
- {tooltipPrevious}
-
- :
- }
- { window.history.state !== null && window.history.state.f !== undefined && window.history.state.f !== ""
- ? {
- if (window.vfbUpdatingHistory == false) {
- window.history.forward();
- }
- }}>
- {tooltipNext}
-
- :
- }
-
+
+
+ {
+ this.clearAll();
+ }} />
+
+
+ {
+ this.props.UIUpdateManager("spotlightVisible");
+ }} />
+
+
+ {
+ this.props.UIUpdateManager("queryBuilderVisible");
+ }} />
+
+
+ {
+ this.props.UIUpdateManager("controlPanelVisible");
+ }} />
+
+ { window.history.state !== null && window.history.state.b !== undefined && window.history.state.b !== ""
+ ?
+ {
+ if (window.vfbUpdatingHistory == false) {
+ window.history.back();
+ }
+ }} />
+
+ :
+ }
+ { window.history.state !== null && window.history.state.f !== undefined && window.history.state.f !== ""
+ ?
+ {
+ if (window.vfbUpdatingHistory == false) {
+ window.history.forward();
+ }
+ }} />
+
+ :
+ }
+
+
diff --git a/components/interface/TreeWidget.js b/components/interface/TreeWidget.js
index 8ba2e6250..85ba985ee 100644
--- a/components/interface/TreeWidget.js
+++ b/components/interface/TreeWidget.js
@@ -1,13 +1,19 @@
/* eslint-disable no-prototype-builtins */
import React from 'react';
+import CompactColor from 'react-color';
import Tree from 'geppetto-client/js/components/interface/tree/Tree';
import CircularProgress from '@material-ui/core/CircularProgress';
import Tooltip from '@material-ui/core/Tooltip';
+import {
+ createMuiTheme,
+ MuiThemeProvider
+} from "@material-ui/core/styles";
import 'react-sortable-tree/style.css';
var $ = require('jquery');
-var GEPPETTO = require('geppetto');
+const restPostConfig = require('../configuration/treeWidgetConfiguration').restPostConfig;
+const treeCypherQuery = require('../configuration/treeWidgetConfiguration').treeCypherQuery;
export default class TreeWidget extends React.Component {
@@ -21,9 +27,11 @@ export default class TreeWidget extends React.Component {
loading: false,
nodes: undefined,
nodeSelected: undefined,
+ displayColorPicker: false
};
this.initTree = this.initTree.bind(this);
+ this.getNodes = this.getNodes.bind(this);
this.sortData = this.sortData.bind(this);
this.restPost = this.restPost.bind(this);
this.nodeClick = this.nodeClick.bind(this);
@@ -35,21 +43,25 @@ export default class TreeWidget extends React.Component {
this.findChildren = this.findChildren.bind(this);
this.searchChildren = this.searchChildren.bind(this);
this.insertChildren = this.insertChildren.bind(this);
+ this.monitorMouseClick = this.monitorMouseClick.bind(this);
this.defaultComparator = this.defaultComparator.bind(this);
this.convertDataForTree = this.convertDataForTree.bind(this);
- this.customSearchMethod = this.customSearchMethod.bind(this);
this.parseGraphResultData = this.parseGraphResultData.bind(this);
+ this.theme = createMuiTheme({ overrides: { MuiTooltip: { tooltip: { fontSize: "12px" } } } });
this.AUTHORIZATION = "Basic " + btoa("neo4j:vfb");
this.styles = {
left_second_column: 395,
column_width_small: 385,
column_width_viewer: "calc(100% - 385px)",
- row_height: 40,
+ row_height: 25,
top: 0,
height: this.props.size.height,
width: this.props.size.width
};
+
+ this.colorPickerNode = undefined;
+ this.colorPickerContainer = undefined;
}
isNumber (variable) {
@@ -68,8 +80,8 @@ export default class TreeWidget extends React.Component {
request.setRequestHeader("Authorization", this.AUTHORIZATION);
}
},
- url: "http://pdb.virtualflybrain.org/db/data/transaction/commit",
- contentType: "application/json",
+ url: restPostConfig.url,
+ contentType: restPostConfig.contentType,
data: strData
});
}
@@ -87,23 +99,23 @@ export default class TreeWidget extends React.Component {
sortData (unsortedArray, key, comparator) {
// Create a sortable array to return.
const sortedArray = [ ...unsortedArray ];
-
+
// Recursively sort sub-arrays.
const recursiveSort = (start, end) => {
-
+
// If this sub-array is empty, it's sorted.
if (end - start < 1) {
return;
}
-
+
const pivotValue = sortedArray[end];
let splitIndex = start;
for (let i = start; i < end; i++) {
const sort = comparator(sortedArray[i], pivotValue, key);
-
+
// This value is less than the pivot value.
if (sort === -1) {
-
+
/*
* If the element just to the right of the split index,
* isn't this element, swap them.
@@ -113,29 +125,29 @@ export default class TreeWidget extends React.Component {
sortedArray[splitIndex] = sortedArray[i];
sortedArray[i] = temp;
}
-
+
/*
* Move the split index to the right by one,
* denoting an increase in the less-than sub-array size.
*/
splitIndex++;
}
-
+
/*
* Leave values that are greater than or equal to
* the pivot value where they are.
*/
}
-
+
// Move the pivot value to between the split.
sortedArray[end] = sortedArray[splitIndex];
sortedArray[splitIndex] = pivotValue;
-
+
// Recursively sort the less-than and greater-than arrays.
recursiveSort(start, splitIndex - 1);
recursiveSort(splitIndex + 1, end);
};
-
+
// Sort the entire array.
recursiveSort(0, unsortedArray.length - 1);
return sortedArray;
@@ -270,17 +282,32 @@ export default class TreeWidget extends React.Component {
nodesList.push(edges[childrenList[i]].to)
}
var uniqNodes = [...new Set(nodesList)];
+
+ for (var j = uniqNodes.length - 1; j >= 0 ; j--) {
+ var node = nodes[this.findChildren({ id: uniqNodes[j] }, "id", nodes)[0]];
+ if (node.instanceId.indexOf("VFB_") > -1) {
+ child.instanceId = node.instanceId;
+ // child.subtitle = child.subtitle + " " + node.instanceId;
+ uniqNodes.splice(j, 1);
+ }
+ }
+
for ( var j = 0; j < uniqNodes.length; j++) {
- var node = nodes[this.findChildren({ id: uniqNodes[j] }, "id", nodes, "part of")[0]];
- child.children.push({
- title: node.title,
- subtitle: node.instanceId,
- description: node.info,
- instanceId: node.instanceId,
- id: node.id,
- children: []
- });
- this.insertChildren(nodes, edges, child.children[j])
+ var node = nodes[this.findChildren({ id: uniqNodes[j] }, "id", nodes)[0]];
+ if (node.instanceId.indexOf("VFB_") > -1) {
+ console.log("child.instanceId contains " + child.instanceId + "but I am overwriting it with " + node.instanceId);
+ child.instanceId = node.instanceId;
+ } else {
+ child.children.push({
+ title: node.title,
+ subtitle: node.instanceId,
+ description: node.info,
+ instanceId: node.instanceId,
+ id: node.id,
+ children: []
+ });
+ this.insertChildren(nodes, edges, child.children[j])
+ }
}
}
@@ -294,9 +321,7 @@ export default class TreeWidget extends React.Component {
description: nodes[i].info,
instanceId: nodes[i].instanceId,
id: nodes[i].id,
- isSelected: true,
- children: [],
- images: []
+ children: []
});
break;
}
@@ -308,9 +333,6 @@ export default class TreeWidget extends React.Component {
selectNode (instance) {
if (this.state.nodeSelected !== undefined && this.state.nodeSelected.instanceId !== instance.instanceId) {
- let oldNode = this.state.nodeSelected;
- oldNode.isSelected = false;
- instance.isSelected = true;
this.setState({ nodeSelected: instance, dataTree: this.state.dataTree });
}
}
@@ -323,7 +345,7 @@ export default class TreeWidget extends React.Component {
innerInstance = instance;
}
- if (innerInstance.id !== this.state.instance.id) {
+ if (this.state.instance !== undefined && innerInstance.id !== this.state.instance.id) {
if (innerInstance.id === window.templateID) {
this.selectNode(this.state.dataTree[0])
return;
@@ -346,18 +368,11 @@ export default class TreeWidget extends React.Component {
initTree (instance) {
this.setState({ loading: true });
- this.restPost({
- "statements": [
- {
- "statement": "MATCH (root:Class)<-[:INSTANCEOF]-(t:Individual {short_form:'" + instance + "'})<-[:depicts]-(tc:Individual)<-[ie:in_register_with]-(c:Individual)-[:depicts]->(image:Individual)-[r:INSTANCEOF]->(anat:Class) WHERE has(ie.index) WITH root, anat,r,image MATCH p=allShortestPaths((root)<-[:SUBCLASSOF|part_of*..]-(anat:Class)) RETURN p,r,image",
- "resultDataContents": ["graph"]
- }
- ]
- }).done(data => {
+ this.restPost(treeCypherQuery(instance)).done(data => {
// If I need to edit the data I can call this here and then assign it to this.state.dataTree
if (data.results[0].data.length > 0) {
var dataTree = this.parseGraphResultData(data);
- var vertix = data.results[0].data[0].graph.nodes[0].id;
+ var vertix = this.findRoot(data.results[0].data[0].graph.nodes);
var nodes = this.sortData(this.convertNodes(dataTree.nodes), "id", this.defaultComparator);
var edges = this.sortData(this.convertEdges(dataTree.edges), "from", this.defaultComparator);
var treeData = this.convertDataForTree(nodes, edges, vertix);
@@ -367,7 +382,11 @@ export default class TreeWidget extends React.Component {
root: vertix,
loading: false,
nodes: nodes,
- nodeSelected: treeData[0]
+ nodeSelected: (this.props.instance === undefined
+ ? treeData[0]
+ : (this.props.instance.getParent() === null
+ ? { subtitle: this.props.instance.getId() }
+ : { subtitle: this.props.instance.getParent().getId() }))
});
} else {
var treeData = [{
@@ -385,53 +404,137 @@ export default class TreeWidget extends React.Component {
});
}
+ findRoot (nodes) {
+ let min = nodes[0].id;
+ for ( let i = 1; i < nodes.length; i++) {
+ if (nodes[i].id < min) {
+ min = nodes[i].id;
+ }
+ }
+ return min;
+ }
+
componentWillMount () {
if (window.templateID !== undefined) {
this.initTree(window.templateID);
}
}
+ componentWillUnmount () {
+ document.removeEventListener('mousedown', this.monitorMouseClick, false);
+ }
+
nodeClick (event, rowInfo) {
- console.log("clicked on the tree node");
- console.log(rowInfo);
this.selectNode(rowInfo.node);
}
+ monitorMouseClick (e) {
+ if (this.colorPickerContainer !== undefined && this.colorPickerContainer !== null && this.colorPickerContainer.contains(e.target)) {
+ return;
+ } else {
+ this.colorPickerContainer = undefined;
+ this.setState({ displayColorPicker: false });
+ }
+ }
+
getButtons (rowInfo) {
var buttons = [];
- if (rowInfo.node.title !== "No data available.") {
- buttons.push(
- {
+ Instances[rowInfo.node.instanceId].hide();
+ this.setState({ nodeSelected: rowInfo.node });
+ }} />);
+ buttons.push( {
+ this.setState({ displayColorPicker: !this.state.displayColorPicker });
+ }}>
+ { (this.state.displayColorPicker
+ && this.state.nodeSelected.subtitle === rowInfo.node.subtitle
+ && this.colorPickerNode === undefined)
+ ? this.colorPickerContainer = ref}>
+ {
+ Instances[rowInfo.node.instanceId].setColor(color.hex);
+ this.setState({ displayColorPicker: true });
+ }}
+ style={{ zIndex: 10 }}/>
+
+ : null}
+ );
+ } else {
+ if (rowInfo.node.instanceId.indexOf("VFB_") > -1) {
+ buttons.push( {
+ Instances[rowInfo.node.instanceId].show();
+ this.setState({ nodeSelected: rowInfo.node });
+ }} />);
+ }
+ }
+ } else {
+ if (rowInfo.node.instanceId.indexOf("VFB_") > -1) {
+ buttons.push( {
- window.addVfbId(rowInfo.node.instanceId);
- }}>);
+ rowInfo.node.subtitle = rowInfo.node.instanceId;
+ this.props.selectionHandler(rowInfo.node.instanceId);
+ this.setState({ nodeSelected: rowInfo.node });
+ }} />);
+ }
}
return buttons;
}
getNodes (rowInfo) {
if (rowInfo.node.title !== "No data available.") {
- var title =
- {rowInfo.node.title}
-
;
+ var title =
+ -1)
+ ? (
+
{rowInfo.node.description}
+
+
+
)
+ : (
+
{rowInfo.node.description}
+
)}>
+ {
+ this.colorPickerNode = undefined;
+ this.props.selectionHandler(rowInfo.node.subtitle);
+ this.setState({ displayColorPicker: false });
+ }}>
+ {rowInfo.node.title}
+
+
+ ;
}
return title;
}
- searchDone (matches) {
- if (matches.length > 0) {
- matches.map(item => {
- item.isSelected = true;
- })
- }
+ componentDidMount () {
+ var that = this;
+ document.addEventListener('mousedown', this.monitorMouseClick, false);
+ GEPPETTO.on(GEPPETTO.Events.Select, function (instance) {
+ that.setState({ displayColorPicker: false });
+ });
}
- customSearchMethod = ({ node, searchQuery }) =>
- searchQuery && node.instanceId.toLowerCase().indexOf(searchQuery.toLowerCase()) > -1;
-
render () {
if (this.state.dataTree === undefined) {
var treeData = [{
@@ -469,8 +572,8 @@ export default class TreeWidget extends React.Component {
rowHeight={this.styles.row_height}
getButtons={this.getButtons}
getNodesProps={this.getNodes}
- searchQuery={this.state.nodeSelected !== undefined ? this.state.nodeSelected.subtitle : null}
- searchFinishCallback={this.searchDone}
+ searchQuery={this.state.nodeSelected.subtitle}
+ onlyExpandSearchedNodes={false}
/>
}
diff --git a/components/interface/VFBTermInfo.js b/components/interface/VFBTermInfo.js
index 4de4540fb..ae60ab826 100644
--- a/components/interface/VFBTermInfo.js
+++ b/components/interface/VFBTermInfo.js
@@ -3,12 +3,12 @@ import ReactDOM from 'react-dom';
import Slider from "react-slick";
import Collapsible from 'react-collapsible';
import HTMLViewer from 'geppetto-client/js/components/interface/htmlViewer/HTMLViewer';
+import ButtonBarComponent from 'geppetto-client/js/components/widgets/popup/ButtonBarComponent';
var $ = require('jquery');
var GEPPETTO = require('geppetto');
var anchorme = require('anchorme');
var Type = require('geppetto-client/js/geppettoModel/model/Type');
-var ButtonBarComponent = require('geppetto-client/js/components/widgets/popup/ButtonBarComponent');
require('../../css/VFBTermInfo.less');
require('geppetto-client/js/components/widgets/popup/Popup.less');
diff --git a/css/VFBMain.less b/css/VFBMain.less
index dbb8b1437..40b856ec6 100644
--- a/css/VFBMain.less
+++ b/css/VFBMain.less
@@ -1124,7 +1124,7 @@
* Nodes matching the search conditions are highlighted
*/
.rst__rowSearchMatch {
- outline: solid 3px #0080ff;
+ //outline: solid 3px #0080ff;
}
/**
@@ -1341,8 +1341,8 @@
position: absolute;
border-radius: 100%;
box-shadow: 0 0 0 1px #000;
- width: 16px;
- height: 16px;
+ width: 14px;
+ height: 14px;
padding: 0;
top: 50%;
transform: translate(-50%, -50%);
@@ -1355,7 +1355,7 @@
.rst__collapseButton:focus,
.rst__expandButton:focus {
outline: none;
- box-shadow: 0 0 0 1px #000, 0 0 1px 3px #83bef9;
+ box-shadow: none; //0 0 0 1px #000, 0 0 1px 3px #83bef9;
}
.rst__collapseButton:hover:not(:active),
.rst__expandButton:hover:not(:active) {
@@ -1549,7 +1549,7 @@
// Tree component styling
.rst__rowContents {
- background: rgb(25, 25, 25);
+ background: transparent;
color: #ffffff;
cursor: pointer;
border-bottom: 1px solid rgb(25, 25, 25);
@@ -1603,17 +1603,24 @@
}
.nodeSelected {
- color: #11bffe;
+ color: #ffffff;
font-size: 14px;
font-family: 'Khand', sans-serif;
border: 0px solid;
- background: #191919;
+ background: transparent;
}
-.nodeUnselected {
- color: #ffffff;
- font-size: 12px;
- font-family: 'Khand', sans-serif;
- border: 0px solid;
- background: #191919;
+.nodeSelected:hover {
+ color: #11bffe;
+}
+
+.nodeFound {
+ //outline: solid 3px #0080ff;
+ color: #0080ff;
+
+}
+
+.chrome-picker {
+ position: absolute;
+ z-index: 1;
}
diff --git a/dockerFiles/geppetto.plan b/dockerFiles/geppetto.plan
index 5875db165..1091a3101 100755
--- a/dockerFiles/geppetto.plan
+++ b/dockerFiles/geppetto.plan
@@ -1,10 +1,10 @@
-
-
-
-
-
-
-
+
+
+
+
+
+
+
diff --git a/package.json b/package.json
index a5a0e6cc6..6f1da3c94 100644
--- a/package.json
+++ b/package.json
@@ -18,11 +18,12 @@
"start": "node --max_old_space_size=2048 node_modules/webpack-dev-server/bin/webpack-dev-server.js --progress --config webpack.config.dev.js"
},
"dependencies": {
- "@geppettoengine/geppetto-client": "file:./geppetto-client",
"@babel/core": "^7.4.5",
+ "@babel/plugin-proposal-class-properties": "^7.1.0",
"@babel/preset-env": "^7.4.5",
"@babel/preset-react": "^7.0.0",
- "@babel/plugin-proposal-class-properties": "^7.1.0",
+ "@geppettoengine/geppetto-client": "file:./geppetto-client",
+ "@material-ui/icons": "3.0.1",
"babel-loader": "^8.0.6",
"copy-webpack-plugin": "^4.6.0",
"css-loader": "^3.0.0",
@@ -32,13 +33,13 @@
"imports-loader": "^0.7.1",
"less-loader": "^5.0.0",
"mini-css-extract-plugin": "^0.7.0",
+ "react-collapsible": "^2.3.1",
+ "react-color": "^2.17.3",
+ "react-tabs": "3.0.0",
"style-loader": "^0.13.2",
"url-loader": "^0.5.8",
"webpack": "4.35.0",
- "webpack-cli": "^3.3.5",
- "@material-ui/icons": "3.0.1",
- "react-collapsible": "^2.3.1",
- "react-tabs": "3.0.0"
+ "webpack-cli": "^3.3.5"
},
"devDependencies": {
"@babel/preset-stage-2": "^7.0.0",