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( -