diff --git a/actions/generals.js b/actions/generals.js index 16f387427..c730d6f33 100644 --- a/actions/generals.js +++ b/actions/generals.js @@ -58,9 +58,9 @@ export const instanceAdded = instance => ({ data: instance }); -export const instanceDeleted = (type, instance) => ({ +export const instanceDeleted = (type, id) => ({ type: type, - data: instance + data: id }); export const instanceSelected = instance => ({ diff --git a/components/VFBMain.js b/components/VFBMain.js index dd1f2d660..0f77cef02 100644 --- a/components/VFBMain.js +++ b/components/VFBMain.js @@ -823,7 +823,7 @@ class VFBMain extends React.Component { this.restoreUIComponent("vfbCircuitBrowser"); } this.setState({ UIUpdated: false }); - break; + break; case 'treeBrowserVisible': if (this.treeBrowserReference !== undefined && this.treeBrowserReference !== null) { this.restoreUIComponent("treeBrowser"); @@ -1176,6 +1176,16 @@ class VFBMain extends React.Component { this.quickHelpRender = ; } } + // Set default page metadata + document.querySelector('meta[name="description"]').setAttribute("content","VFB integrates data curated from the literature with image data from many bulk sources. The search system allows you to search for neurons and neuroanatomical structures using almost any name found in the literature. The query system can identify neurons innervating any specified neuropil or fasciculating with any specified tract. It also allows queries for genes, transgenes and phenotypes expressed in any brain region or neuron. Search and query results combine referenced textual descriptions with 3D images and links to originating data sources. VFB features tens of thousands of 3D images of neurons, clones and expression patterns, registered to standard template brains. Any combination of these images can be viewed together. A BLAST-type query system (NBLAST) allows you to find similar neurons and drivers starting from a registered neuron."); + document.querySelector('meta[property="og:description"]').setAttribute("content","VFB integrates data curated from the literature with image data from many bulk sources. The search system allows you to search for neurons and neuroanatomical structures using almost any name found in the literature. The query system can identify neurons innervating any specified neuropil or fasciculating with any specified tract. It also allows queries for genes, transgenes and phenotypes expressed in any brain region or neuron. Search and query results combine referenced textual descriptions with 3D images and links to originating data sources. VFB features tens of thousands of 3D images of neurons, clones and expression patterns, registered to standard template brains. Any combination of these images can be viewed together. A BLAST-type query system (NBLAST) allows you to find similar neurons and drivers starting from a registered neuron."); + document.title = "Virtual Fly Brain, a data integrator for Drosophila neurobiology"; + document.querySelector('meta[property="og:title"]').setAttribute("content",document.title); + setTimeout(function () { + if (window.templateID == undefined) { + window.location.reload(true); + } + }, 20000); } componentWillUnmount () { @@ -1238,6 +1248,32 @@ class VFBMain extends React.Component { idsList += ","; } idsList += this.idFromURL; + // populate page meta for this term for indexing + try { + if ( window.XMLHttpRequest ) { + var xhr = new XMLHttpRequest(); + xhr.onload = function () { + document.title = 'Virtual Fly Brain (' + this.responseXML.title + ')'; + document.querySelector('meta[property="og:title"]').setAttribute("content",this.responseXML.title); + document.querySelector('meta[name="description"]').setAttribute("content",this.responseXML.body.innerText); + document.querySelector('meta[property="og:description"]').setAttribute("content",this.responseXML.body.innerText); + } + xhr.open( 'GET', 'https://virtualflybrain.org/data/VFB/json/' + this.idFromURL + '.html') + xhr.responseType = 'document'; + xhr.send(); + } + } catch (err) { + console.error(err); + } + try { + var link = !!document.querySelector("link[rel='amphtml']"); + link = link ? document.querySelector("link[rel='amphtml']") : document.createElement('link'); + link.setAttribute('rel', 'amphtml'); + link.setAttribute('href', 'https://virtualflybrain.org/data/VFB/json/' + this.idFromURL + '.html'); + document.head.appendChild(link); + } catch (err) { + console.error(err); + } } else if (idList[list].indexOf("i=") > -1) { if (idsList.length > 0) { idsList = "," + idsList; @@ -1282,6 +1318,11 @@ class VFBMain extends React.Component { GEPPETTO.on(GEPPETTO.Events.Instance_added, function (instance) { that.props.instanceAdded(instance); }); + + GEPPETTO.on(GEPPETTO.Events.Instance_deleted, function (instancePath) { + let id = instancePath.split(".")[0]; + that.props.instanceDeleted(ACTIONS.INSTANCE_DELETED, id); + }); GEPPETTO.on(GEPPETTO.Events.Model_loaded, function () { that.addVfbId(that.idsFinalList); diff --git a/components/configuration/VFBListViewer/listViewerConfiguration.js b/components/configuration/VFBListViewer/listViewerConfiguration.js index 5cd84ae9d..47cce86a4 100644 --- a/components/configuration/VFBListViewer/listViewerConfiguration.js +++ b/components/configuration/VFBListViewer/listViewerConfiguration.js @@ -75,13 +75,17 @@ const conf = [ return t.type.getInitialValue().value })[0].html; + let htmlLabels = instance.getTypes().map(function (t) { + return t.label.getInitialValue().value + })[0].html; + // Extract HTML element anchor from html string var matchAnchor = /]*>([\s\S]*?)<\/a>/g , type = html.match(matchAnchor); // Extract HTML element anchor from html string var matchSpan = /]*>([\s\S]*?)<\/span>/g - , tags = html.match(matchSpan); + , tags = htmlLabels.match(matchSpan); var matchID = /data-instancepath\=\"([A-Za-z0-9 _]*)\"/ , classID = type[0].match(matchID)[1]; diff --git a/components/configuration/VFBMain/queryBuilderConfiguration.js b/components/configuration/VFBMain/queryBuilderConfiguration.js index 0de1ea90f..564ac1caa 100644 --- a/components/configuration/VFBMain/queryBuilderConfiguration.js +++ b/components/configuration/VFBMain/queryBuilderConfiguration.js @@ -25,6 +25,19 @@ var queryResultsColMeta = [ "cssClassName": "query-results-name-column", "sortDirectionCycle": ['asc', 'desc', null] }, + { + "columnName": "neuron_A", + "order": 2, + "locked": false, + "visible": true, + "customComponent": QueryLinkComponent, + "actions": "window.addVfbId('$entity$');", + "entityIndex": 0, + "entityDelimiter": "----", + "displayName": "Neuron_A", + "cssClassName": "query-results-name-column", + "sortDirectionCycle": ['asc', 'desc', null] + }, { "columnName": "type", "order": 2, @@ -38,6 +51,19 @@ var queryResultsColMeta = [ "cssClassName": "query-results-type-column", "sortDirectionCycle": ['asc', 'desc', null] }, + { + "columnName": "parent", + "order": 12, + "locked": false, + "visible": true, + "customComponent": QueryLinkComponent, + "actions": "window.addVfbId('$entity$');", + "entityIndex": 2, + "entityDelimiter": "----", + "displayName": "Parent", + "cssClassName": "query-results-type-column", + "sortDirectionCycle": ['asc', 'desc', null] + }, { "columnName": "expressed_in", "order": 3, @@ -92,6 +118,81 @@ var queryResultsColMeta = [ "cssClassName": "query-results-stage-column", "sortDirectionCycle": ['asc', 'desc', null] }, + { + "columnName": "downstream", + "order": 7, + "locked": false, + "visible": true, + "displayName": "Downstream", + "cssClassName": "query-results-stage-column", + "sortDirectionCycle": ['asc', 'desc', null] + }, + { + "columnName": "tbars", + "order": 8, + "locked": false, + "visible": true, + "displayName": "Tbars", + "cssClassName": "query-results-stage-column", + "sortDirectionCycle": ['asc', 'desc', null] + }, + { + "columnName": "upstream", + "order": 9, + "locked": false, + "visible": true, + "displayName": "Upstream", + "cssClassName": "query-results-stage-column", + "sortDirectionCycle": ['asc', 'desc', null] + }, + { + "columnName": "weight", + "order": 10, + "locked": false, + "visible": true, + "displayName": "Weight", + "cssClassName": "query-results-stage-column", + "sortDirectionCycle": ['asc', 'desc', null] + }, + { + "columnName": "neuron_B", + "order": 11, + "locked": false, + "visible": true, + "customComponent": QueryLinkComponent, + "actions": "window.addVfbId('$entity$');", + "entityIndex": 1, + "entityDelimiter": "----", + "displayName": "Neuron_B", + "cssClassName": "query-results-name-column", + "sortDirectionCycle": ['asc', 'desc', null] + }, + { + "columnName": "region", + "order": 11, + "locked": false, + "visible": true, + "customComponent": QueryLinkComponent, + "actions": "window.addVfbId('$entity$');", + "entityIndex": 1, + "entityDelimiter": "----", + "displayName": "Region", + "cssClassName": "query-results-name-column", + "sortDirectionCycle": ['asc', 'desc', null] + }, + { + "columnName": "traget", + "order": 11, + "locked": false, + "visible": true, + "customComponent": QueryLinkComponent, + "actions": "window.addVfbId('$entity$');", + "entityIndex": 1, + "entityDelimiter": "----", + "displayName": "Target", + "cssClassName": "query-results-name-column", + "sortDirectionCycle": ['asc', 'desc', null] + }, { "columnName": "license", "order": 8, @@ -166,7 +267,7 @@ var queryResultsColMeta = [ ]; // which columns to display in the results -var queryResultsColumns = ['name', 'type', 'expressed_in', 'description', 'reference', 'gross_type', 'stage', 'license', 'template', 'technique', 'controls', 'images', 'score','image_count']; +var queryResultsColumns = ['name', 'neuron_A', 'type', 'downstream', 'tbars', 'upstream', 'weight', 'neuron_B', 'region', 'target', 'parent', 'expressed_in', 'description', 'reference', 'gross_type', 'stage', 'license', 'template', 'technique', 'controls', 'images', 'score', 'image_count']; var queryResultsControlConfig = { "Common": { @@ -194,7 +295,7 @@ var queryResultsControlConfig = { var queryBuilderDatasourceConfig = { VFB: { - url: 'https://solr.p2.virtualflybrain.org/solr/ontology/select?q=$SEARCH_TERM$+OR+$SEARCH_TERM$*+OR+*$SEARCH_TERM$*&defType=edismax&qf=label^100+synonym^100+label_autosuggest_ws+label_autosuggest_e+label_autosuggest+synonym_autosuggest_ws+synonym_autosuggest+shortform_autosuggest&indent=true&fl=short_form+label+synonym+id+facets_annotation+type:"class"&start=0&pf=true&rows=100&wt=json&bq=shortform_autosuggest:VFB*^110.0+shortform_autosuggest:FBbt*^100.0+label_s:""^2+synonym_s:""+short_form=FBbt_00003982^2+facets_annotation:Deprecated^0.001', + url: 'https://solr.virtualflybrain.org/solr/ontology/select?q=$SEARCH_TERM$+OR+$SEARCH_TERM$*+OR+*$SEARCH_TERM$*&defType=edismax&qf=label^100+synonym^100+label_autosuggest_ws+label_autosuggest_e+label_autosuggest+synonym_autosuggest_ws+synonym_autosuggest+shortform_autosuggest&indent=true&fl=short_form+label+synonym+id+facets_annotation+type:"class"&start=0&pf=true&rows=100&wt=json&bq=shortform_autosuggest:VFB*^110.0+shortform_autosuggest:FBbt*^100.0+label_s:""^2+synonym_s:""+short_form=FBbt_00003982^2+facets_annotation:Deprecated^0.001', crossDomain: true, id: "short_form", label: { field: "label", formatting: "$VALUE$" }, @@ -330,6 +431,10 @@ var queryBuilderDatasourceConfig = { }; var sorterColumns = [ + { + column: "downstream", + order: "DESC" + }, { column: "score", order: "DESC" diff --git a/components/configuration/VFBMain/searchConfiguration.js b/components/configuration/VFBMain/searchConfiguration.js index aee51bd25..d610b3897 100644 --- a/components/configuration/VFBMain/searchConfiguration.js +++ b/components/configuration/VFBMain/searchConfiguration.js @@ -84,7 +84,7 @@ var searchStyle = { }; var datasourceConfiguration = { - "url": "https://solr.p2.virtualflybrain.org/solr/ontology/select", + "url": "https://solr.virtualflybrain.org/solr/ontology/select", "query_settings": { "q": "$SEARCH_TERM$ OR $SEARCH_TERM$* OR *$SEARCH_TERM$*", diff --git a/components/interface/VFBListViewer/ListViewerControlsMenu.js b/components/interface/VFBListViewer/ListViewerControlsMenu.js index 1b2868f88..c25a13e2e 100644 --- a/components/interface/VFBListViewer/ListViewerControlsMenu.js +++ b/components/interface/VFBListViewer/ListViewerControlsMenu.js @@ -1,7 +1,7 @@ import React, { Component } from "react"; import Menu from "@geppettoengine/geppetto-ui/menu/Menu"; import { connect } from 'react-redux'; -import { SliderPicker } from 'react-color'; +import { ChromePicker } from 'react-color'; import { setTermInfo, SHOW_LIST_VIEWER, INSTANCE_DELETED } from './../../../actions/generals'; const controlsConfiguration = require('../../configuration/VFBListViewer/controlsMenuConfiguration').default; @@ -62,7 +62,6 @@ class ListViewerControlsMenu extends Component { break; case ACTIONS.DELETE: this.props.instance.delete(); - this.props.instanceDeleted(INSTANCE_DELETED, this.props.instance); break; case ACTIONS.INFO: var self = this; @@ -228,7 +227,7 @@ class ListViewerControlsMenu extends Component { className="btnBar-color-picker" ref={ref => this.colorPickerContainer = ref} style={{ left: this.state.pickerPosition }}> - { this.props.instance.setColor(color.hex); @@ -247,10 +246,7 @@ function mapStateToProps (state) { } function mapDispatchToProps (dispatch) { - return { - setTermInfo: (instance, visible) => dispatch(setTermInfo(instance, visible )), - instanceDeleted : ( type, instance ) => dispatch({ type : type , instance : instance }) - } + return { setTermInfo: (instance, visible) => dispatch(setTermInfo(instance, visible )) } } export default connect(mapStateToProps, mapDispatchToProps)(ListViewerControlsMenu); \ No newline at end of file diff --git a/components/interface/VFBTermInfo/ButtonBarComponent.js b/components/interface/VFBTermInfo/ButtonBarComponent.js new file mode 100644 index 000000000..eec3845d2 --- /dev/null +++ b/components/interface/VFBTermInfo/ButtonBarComponent.js @@ -0,0 +1,287 @@ +import React from 'react'; +import { ChromePicker } from 'react-color'; +import Tooltip from '@material-ui/core/Tooltip'; +import { + createMuiTheme, + MuiThemeProvider +} from "@material-ui/core/styles"; + +require("./../../../css/ButtonBarComponent.less"); + +export default class ButtonBarComponent extends React.Component { + constructor (props) { + super(props); + + this.state = { + displayColorPicker: false, + pickerPosition: "220px" + }; + + this.monitorMouseClick = this.monitorMouseClick.bind(this); + + this.colorPickerBtnId = ''; + this.colorPickerActionFn = ''; + this.theme = createMuiTheme({ overrides: { MuiTooltip: { tooltip: { fontSize: "12px" } } } }); + this.colorPickerContainer = undefined; + } + + monitorMouseClick (e) { + if ((this.colorPickerContainer !== undefined && this.colorPickerContainer !== null) && !this.colorPickerContainer.contains(e.target) && this.state.displayColorPicker === true) { + this.setState({ displayColorPicker: false }); + } + } + + componentDidMount () { + var that = this; + + document.addEventListener('mousedown', this.monitorMouseClick, false); + + if (that.props.instance != null || that.props.instance != undefined){ + that.props.resize(); + } + + if (this.props.buttonBarConfig.Events != null || this.props.buttonBarConfig.Events != undefined){ + this.props.geppetto.on(GEPPETTO.Events.Visibility_changed, function (instance) { + if (!$.isEmptyObject(that.props) || that.props != undefined){ + if (instance.getInstancePath() == that.props.instancePath){ + that.forceUpdate(); + } else { + if ((that.props.instance != null || that.props.instance != undefined) + && (instance.getParent() != null || instance.getParent() != undefined)){ + if (that.props.instance.getInstancePath() == instance.getParent().getInstancePath()){ + that.forceUpdate(); + } + } + } + } + }); + this.props.geppetto.on(GEPPETTO.Events.Select, function (instance) { + if (!$.isEmptyObject(that.props) || that.props != undefined){ + if (instance.getInstancePath() == that.props.instancePath){ + that.forceUpdate(); + } else { + if ((that.props.instance != null || that.props.instance != undefined) + && (instance.getParent() != null || instance.getParent() != undefined)){ + if (that.props.instance.getInstancePath() == instance.getParent().getInstancePath()){ + that.forceUpdate(); + } + } + } + } + }); + this.props.geppetto.on(GEPPETTO.Events.Color_set, function (instance) { + if (that.props != null || that.props != undefined){ + if (instance.instance.getInstancePath() == that.props.instancePath){ + that.forceUpdate(); + if (that.props.instance != null || that.props.instance != undefined){ + that.props.resize(); + } + } + } + }); + } + } + + componentWillUnmount () { + this.props = {}; + document.removeEventListener('mousedown', this.monitorMouseClick, false); + } + + replaceTokensWithPath (inputStr, path) { + return inputStr.replace(/\$instance\$/gi, path).replace(/\$instances\$/gi, '[' + path + ']'); + } + + getActionString (control, path) { + var actionStr = ''; + + if (control.actions.length > 0) { + for (var i = 0; i < control.actions.length; i++) { + actionStr += ((i != 0) ? ";" : "") + this.replaceTokensWithPath(control.actions[i], path); + } + } + + return actionStr; + } + + resolveCondition (control, path, negateCondition) { + if (negateCondition == undefined) { + negateCondition = false; + } + + var resolvedConfig = control; + + if (Object.prototype.hasOwnProperty.call(resolvedConfig, 'condition')) { + // evaluate condition and reassign control depending on results + var conditionStr = this.replaceTokensWithPath(control.condition, path); + if (eval(conditionStr)) { + resolvedConfig = negateCondition ? resolvedConfig.false : resolvedConfig.true; + } else { + resolvedConfig = negateCondition ? resolvedConfig.true : resolvedConfig.false; + } + } + return resolvedConfig; + } + + refresh () { + this.forceUpdate(); + } + + render () { + var showControls = this.props.showControls; + var config = this.props.buttonBarConfig; + var path = this.props.instancePath; + var ctrlButtons = []; + + // retrieve entity/instance + var entity = undefined; + try { + // need to eval because this is a nested path - not simply a global on window + entity = eval(path) + } catch (e) { + throw ( "The instance " + path + " does not exist in the current model" ); + } + + // Add common control buttons to list + for (var control in config.Common) { + if ($.inArray(control.toString(), showControls.Common) != -1) { + var add = true; + + // check show condition + if (config.Common[control].showCondition != undefined){ + var condition = this.replaceTokensWithPath(config.Common[control].showCondition, path); + add = eval(condition); + } + + if (add) { + ctrlButtons.push(config.Common[control]); + } + } + } + + if (entity != null || entity != undefined){ + if (entity.hasCapability(GEPPETTO.Resources.VISUAL_CAPABILITY)) { + // Add visual capability controls to list + for (var control in config.VisualCapability) { + if ($.inArray(control.toString(), showControls.VisualCapability) != -1) { + var add = true; + + // check show condition + if (config.VisualCapability[control].showCondition != undefined){ + var condition = this.replaceTokensWithPath(config.VisualCapability[control].showCondition, path); + add = eval(condition); + } + + if (add) { + ctrlButtons.push(config.VisualCapability[control]); + } + } + } + } + } + + var that = this; + + return ( +
+ + {ctrlButtons.map(function (control, id) { + // grab attributes to init button attributes + var controlConfig = that.resolveCondition(control, path); + var idVal = path.replace(/\./g, '_').replace(/\[/g, '_').replace(/\]/g, '_') + "_" + controlConfig.id + "_buttonBar_btn"; + var tooltip = controlConfig.tooltip; + var classVal = "btn buttonBar-button fa " + controlConfig.icon; + var styleVal = {}; + + // define action function + var actionFn = function (param) { + // NOTE: there is a closure on 'control' so it's always the right one + var controlConfig = that.resolveCondition(control, path); + + // take out action string + var actionStr = that.getActionString(controlConfig, path); + + if (param != undefined) { + actionStr = actionStr.replace(/\$param\$/gi, param); + } + + // run action + if (actionStr != '' && actionStr != undefined) { + GEPPETTO.CommandController.execute(actionStr); + that.refresh(); + } + + // if conditional, swap icon with the other condition outcome + if (Object.prototype.hasOwnProperty.call(control, 'condition')) { + var otherConfig = that.resolveCondition(control, path); + var element = $('#' + idVal); + element.removeClass(); + element.addClass("btn buttonBar-button fa " + otherConfig.icon); + } + }; + + // if conditional, swap icon with the other condition outcome + if (Object.prototype.hasOwnProperty.call(control, 'condition')) { + var otherConfig = that.resolveCondition(control, path); + var element = $('#' + idVal); + element.removeClass(); + element.addClass("btn buttonBar-button fa " + otherConfig.icon); + } + + // figure out if we need to include the color picker (hook it up in didMount) + if (controlConfig.id == "color") { + that.colorPickerBtnId = idVal; + that.colorPickerActionFn = actionFn; + // set style val to color tint icon + if (entity !== undefined) { + var colorVal = String(entity.getColor().replace(/0X/i, "#") + "0000").slice(0, 7); + styleVal = { color: colorVal.startsWith('#') ? colorVal : ('#' + colorVal) }; + classVal += " color-picker-button"; + } + } + + return ( + + + + + {/* 2 ternary conditions concatenad to check first if the controlconfig.id is color + * so we can attach the color picker component to the button, and then the second + * to check wether the colorPicker button has been clicked or not and open that. + */} + {controlConfig.id === "color" + ? (that.state.displayColorPicker === true + ?
that.colorPickerContainer = ref} + style={{ left: that.state.pickerPosition }}> + { + Instances[path].setColor(color.hex); + that.setState({ displayColorPicker: true }); + }} + style={{ zIndex: 10 }}/> +
+ : undefined) + : undefined} +
+ ) + })} +
+
+ ) + } +} diff --git a/components/interface/VFBTermInfo/VFBTermInfo.js b/components/interface/VFBTermInfo/VFBTermInfo.js index 460a5c18e..98c35936c 100644 --- a/components/interface/VFBTermInfo/VFBTermInfo.js +++ b/components/interface/VFBTermInfo/VFBTermInfo.js @@ -3,7 +3,7 @@ import ReactDOM from 'react-dom'; import Slider from "react-slick"; import Collapsible from 'react-collapsible'; import HTMLViewer from '@geppettoengine/geppetto-ui/html-viewer/HTMLViewer'; -import ButtonBarComponent from '@geppettoengine/geppetto-client/components/widgets/popup/ButtonBarComponent'; +import ButtonBarComponent from './ButtonBarComponent'; import { SHOW_GRAPH, UPDATE_CIRCUIT_QUERY } from './../../../actions/generals'; import { connect } from "react-redux"; diff --git a/components/interface/VFBTree/VFBTree.js b/components/interface/VFBTree/VFBTree.js index 23e522592..19ef9b601 100644 --- a/components/interface/VFBTree/VFBTree.js +++ b/components/interface/VFBTree/VFBTree.js @@ -1,6 +1,6 @@ /* eslint-disable no-prototype-builtins */ import React from 'react'; -import { SliderPicker } from 'react-color'; +import { ChromePicker } from 'react-color'; import Tree from '@geppettoengine/geppetto-ui/tree-viewer/Tree'; import CircularProgress from '@material-ui/core/CircularProgress'; import Tooltip from '@material-ui/core/Tooltip'; @@ -396,7 +396,7 @@ class VFBTree extends React.Component { ?
this.colorPickerContainer = ref}> - { Instances[rowInfo.node.instanceId].setColor(color.hex); diff --git a/containers/VFBMainContainer.js b/containers/VFBMainContainer.js index b47a86a7c..2eea90c8a 100644 --- a/containers/VFBMainContainer.js +++ b/containers/VFBMainContainer.js @@ -5,6 +5,7 @@ import { vfbUIUpdated, instanceAdded, instanceSelected, + instanceDeleted, instanceVisibilityChanged, setTermInfo, vfbGraph, @@ -22,6 +23,7 @@ const mapDispatchToProps = dispatch => ({ vfbCircuitBrowser: (type, path, visible) => dispatch (vfbCircuitBrowser(type, path, visible)), instanceAdded: instance => dispatch(instanceAdded(instance)), instanceSelected : instance => dispatch(instanceSelected(instance)), + instanceDeleted : (type, instanceID) => dispatch(instanceDeleted(type, instanceID)), instanceVisibilityChanged : instance => dispatch(instanceVisibilityChanged(instance)), setTermInfo: (instance, visible) => dispatch (setTermInfo(instance, visible)) }); diff --git a/css/ButtonBarComponent.less b/css/ButtonBarComponent.less new file mode 100644 index 000000000..3cca4f0b9 --- /dev/null +++ b/css/ButtonBarComponent.less @@ -0,0 +1,49 @@ +@import "./colors"; +.buttonbar-colorpicker { + background: @background_color_widget; + opacity: 0.7; + border: 1px solid rgba(255, 255, 255, 0.25) !important; + border-radius: 0px !important; +} + +.btnBar-color-picker { + position: absolute; + z-index: 1; + width: 80px; + background-color: #252323; + padding: 3px; + border-radius: 2px; +} + +.colorpicker:after { + border-bottom: 6px solid rgba(255, 255, 255, 0.27) !important; +} + +.buttonBar-button { + height: 25px; + width: 33px; + margin: 0px; + background: none !important; + color: @primary_color; + font-size: 18px !important; + border: 0 !important; +} + +.buttonBar-button:hover { + font-size: 20px !important; + background: none !important; + color: #fc4b19; +} + +.buttonBarComponentDiv{ + margin-bottom: 5px; +} + +.button-bar-container { + width : 100%; + text-align: center; +} + +.button-bar-div { + height: 100%; +} diff --git a/dockerFiles/startup.sh b/dockerFiles/startup.sh index fb074d840..be806fd56 100755 --- a/dockerFiles/startup.sh +++ b/dockerFiles/startup.sh @@ -74,7 +74,7 @@ then # check for any memory issues: match="java.lang.OutOfMemoryError" - while sleep 60; do if fgrep --quiet "$match" "$log"; then cp -fv "$log" "/tmp/error/$(date '+%Y-%m-%d_%H-%M').log" ; kill 17; kill 18; kill 19; kill 20; kill 21; exit 0; fi; done & + while sleep 60; do if fgrep --quiet "$match" "$log"; then cp -fv "$log" "/tmp/error/$(date '+%Y-%m-%d_%H-%M').log" ; pkill -u developer; exit 0; fi; done & # start virgo server $SERVER_HOME/bin/startup.sh diff --git a/model/vfb.xmi b/model/vfb.xmi index 9cabd22ba..fafb0040d 100644 --- a/model/vfb.xmi +++ b/model/vfb.xmi @@ -141,6 +141,15 @@ + + + + + + + + + + + + + + + + + diff --git a/reducers/generals.js b/reducers/generals.js index 7cf700e26..207cf62b5 100644 --- a/reducers/generals.js +++ b/reducers/generals.js @@ -293,9 +293,13 @@ function generalReducer (state, action) { } case INSTANCE_DELETED: var newMap = [ ...state.idsList ]; - var index = newMap.indexOf(action.instance.id); - if ( index > -1 ) { - newMap.splice(index, 1); + var id = action.data; + // Delete all matching instances, e.g. instances of same name endign in obj, meta, swc + for ( var i = 0; i < newMap.length; i++ ){ + if ( newMap[i].includes(id) ) { + newMap.splice(i, 1); + i--; + } } ui.canvas.instanceDeleted = action.instance; return { diff --git a/tests/jest/vfb/batch1/layer-component-tests.js b/tests/jest/vfb/batch1/layer-component-tests.js index bb7bb8f8f..a37d586f2 100644 --- a/tests/jest/vfb/batch1/layer-component-tests.js +++ b/tests/jest/vfb/batch1/layer-component-tests.js @@ -191,26 +191,26 @@ describe('VFB Layer Component Tests', () => { it('Color Picker Appears for VFB_jrchk4wj', async () => { await openControls(page, "PVLP142_R - 5812987602"); await clickLayerControlsElement(page, 'Color'); - await wait4selector(page, 'div.slider-picker', { visible: true, timeout : 500000 }) + await wait4selector(page, 'div.chrome-picker', { visible: true, timeout : 500000 }) }) // Chance color of VFB_jrchk4wj instance using controls it('Use color picker to change color of VFB_jrchk4wj', async () => { // Retrieve old color in mesh - let originalColor = await page.evaluate(async () => { + let meshColor = await page.evaluate(async () => { return CanvasContainer.engine.meshes["VFB_jrchk4wj.VFB_jrchk4wj_swc"].material.color.getHexString(); }); - // Select color in color picker box, index 17 belongs to last available color in picker - await page.evaluate(async () => document.querySelectorAll("div.slider-picker div")[16].click()); - // Wait couple of seconds for mesh to reflect new color - await page.waitFor(20000); + + expect(meshColor).toEqual("ffcc00"); + + await expect(page).toFill('input[value="#00FF00"]', '#f542e6') + await page.waitFor(15000); // Retrieve new color in mesh let newColor = await page.evaluate(async () => { return CanvasContainer.engine.meshes["VFB_jrchk4wj.VFB_jrchk4wj_swc"].material.color.getHexString(); }); - // Compare RGB's of original color and new color - expect(originalColor).not.toEqual(newColor); + expect(newColor).toEqual('f542e6'); }) // Click on control's option to delete VFB_jrchk4wj instance and check is now gone diff --git a/tests/jest/vfb/batch1/menu-component-tests.js b/tests/jest/vfb/batch1/menu-component-tests.js index fbbd96449..9f65e6a63 100644 --- a/tests/jest/vfb/batch1/menu-component-tests.js +++ b/tests/jest/vfb/batch1/menu-component-tests.js @@ -44,7 +44,7 @@ describe('VFB Menu Component Tests', () => { it('VFB Title shows up', async () => { const title = await page.title(); - expect(title).toBe("Virtual Fly Brain"); + expect(title).toMatch("Virtual Fly Brain"); }) it('Deselect button for VFB_00017894 appears in button bar inside the term info component', async () => { diff --git a/tests/jest/vfb/batch1/spotlight-tests.js b/tests/jest/vfb/batch1/spotlight-tests.js index e2358af9b..dd13c25a6 100644 --- a/tests/jest/vfb/batch1/spotlight-tests.js +++ b/tests/jest/vfb/batch1/spotlight-tests.js @@ -28,7 +28,7 @@ describe('VFB Spotlight Tests', () => { it('VFB Title shows up', async () => { const title = await page.title(); - expect(title).toBe("Virtual Fly Brain"); + expect(title).toMatch("Virtual Fly Brain"); }) it('Deselect button for VFB_00017894 appears in button bar inside the term info component', async () => { diff --git a/tests/jest/vfb/batch1/term-info-tests.js b/tests/jest/vfb/batch1/term-info-tests.js index deb8a055c..3a5ac6558 100644 --- a/tests/jest/vfb/batch1/term-info-tests.js +++ b/tests/jest/vfb/batch1/term-info-tests.js @@ -25,7 +25,7 @@ describe('VFB Term Info Component Tests', () => { it('VFB Title shows up', async () => { const title = await page.title(); - expect(title).toBe("Virtual Fly Brain"); + expect(title).toMatch("Virtual Fly Brain"); }) }) diff --git a/tests/jest/vfb/batch2/3d-viewer-tests.js b/tests/jest/vfb/batch2/3d-viewer-tests.js index 7444963b6..86853565e 100644 --- a/tests/jest/vfb/batch2/3d-viewer-tests.js +++ b/tests/jest/vfb/batch2/3d-viewer-tests.js @@ -27,7 +27,7 @@ describe('VFB 3D Viewer Component Tests', () => { it('VFB Title shows up', async () => { const title = await page.title(); - expect(title).toBe("Virtual Fly Brain"); + expect(title).toMatch("Virtual Fly Brain"); }) it('Deselect button for VFB_00017894 appears in button bar inside the term info component', async () => { diff --git a/tests/jest/vfb/batch2/slice-viewer-tests.js b/tests/jest/vfb/batch2/slice-viewer-tests.js index e164b697e..a7da29926 100644 --- a/tests/jest/vfb/batch2/slice-viewer-tests.js +++ b/tests/jest/vfb/batch2/slice-viewer-tests.js @@ -28,7 +28,7 @@ describe('VFB Slice Viewer Component Tests', () => { it('VFB Title shows up', async () => { const title = await page.title(); - expect(title).toBe("Virtual Fly Brain"); + expect(title).toMatch("Virtual Fly Brain"); }) it('Deselect button for VFB_00017894 appears in button bar inside the term info component', async () => { diff --git a/tests/jest/vfb/batch3/batch-request-tests.js b/tests/jest/vfb/batch3/batch-request-tests.js index 4f7d67bda..5404c2959 100644 --- a/tests/jest/vfb/batch3/batch-request-tests.js +++ b/tests/jest/vfb/batch3/batch-request-tests.js @@ -30,7 +30,7 @@ describe('VFB Batch Requests Tests', () => { it('VFB Title shows up', async () => { const title = await page.title(); - expect(title).toBe("Virtual Fly Brain"); + expect(title).toMatch("Virtual Fly Brain"); }) it('Deselect button for VFB_00030880 appears in button bar inside the term info component', async () => { diff --git a/tests/jest/vfb/batch3/loader-tests.js b/tests/jest/vfb/batch3/loader-tests.js index 412d04141..36b5cde25 100644 --- a/tests/jest/vfb/batch3/loader-tests.js +++ b/tests/jest/vfb/batch3/loader-tests.js @@ -34,7 +34,7 @@ describe('VFB Loader Component Tests', () => { // Check Page was loaded by checking on the page title it('Test Landing Page', async () => { const title = await page.title(); - expect(title).toBe("Virtual Fly Brain"); + expect(title).toMatch("Virtual Fly Brain"); }) // Check for appearance of Loading component on top right corner @@ -52,7 +52,7 @@ describe('VFB Loader Component Tests', () => { }) // Once Loader is done, check all is correctly loaded - describe('Loader Finished, Test 1 Instance was Loaded', () => { + describe('Loader Finished, Test 1 Instance was Loaded', () => { // Check that progress bar has disappeared it('Progress Bar Hidden After Loading of Instances', async () => { await wait4selector(page, 'div.progress-bar', { hidden: true, timeout : 800000 }); @@ -90,7 +90,7 @@ describe('VFB Loader Component Tests', () => { it('Test Landing Page', async () => { const title = await page.title(); - expect(title).toBe("Virtual Fly Brain"); + expect(title).toMatch("Virtual Fly Brain"); }) // Check that the Loading component appears in top right corner @@ -107,7 +107,7 @@ describe('VFB Loader Component Tests', () => { }) // 2 instances are finished loading, check they were loaded in other components too - describe('Loader Finished, Test 2 Instances were Loaded', () => { + describe('Loader Finished, Test 2 Instances were Loaded', () => { // Check that the progress bar is gone after 2 instances are done loading it('Progress Bar Hidden After Loading of Instances', async () => { await wait4selector(page, 'div.progress-bar', { hidden: true, timeout : 800000 }); @@ -149,7 +149,7 @@ describe('VFB Loader Component Tests', () => { // Test landing page was reached by checking title it('Test Landing Page', async () => { const title = await page.title(); - expect(title).toBe("Virtual Fly Brain"); + expect(title).toMatch("Virtual Fly Brain"); }) // Check the existence of a loading bar in top right corner @@ -166,13 +166,13 @@ describe('VFB Loader Component Tests', () => { }) // Once 3 instances are done loading, check all components are loaded with them - describe('Loader Finished, Test 3 Instances Were Loaded', () => { + describe('Loader Finished, Test 3 Instances Were Loaded', () => { // Check that the progress bar is gone, which means instances have loaded it('Progress Bar Hidden After Loading of Instances', async () => { await wait4selector(page, 'div.progress-bar', { hidden: true, timeout : 300000 }); }) - // Check Term Info has loaded the last instance added + // Check Term Info has loaded the last instance added it('Term info component created and populated after load with FBbt_00003678', async () => { await wait4selector(page, 'div#bar-div-vfbterminfowidget', { visible: true , timeout : 60000 }) let element = await findElementByText(page, "ellipsoid body"); diff --git a/tests/jest/vfb/batch3/url-params-tests.js b/tests/jest/vfb/batch3/url-params-tests.js index e28e5fadc..3468c16c0 100644 --- a/tests/jest/vfb/batch3/url-params-tests.js +++ b/tests/jest/vfb/batch3/url-params-tests.js @@ -22,7 +22,7 @@ const testLandingPage = function(){ it('VFB Title shows up', async () => { const title = await page.title(); - expect(title).toBe("Virtual Fly Brain"); + expect(title).toMatch("Virtual Fly Brain"); }) }; diff --git a/tests/jest/vfb/batch2/tree-browser-tests.js b/tests/jest/vfb/review/tree-browser-tests.js similarity index 78% rename from tests/jest/vfb/batch2/tree-browser-tests.js rename to tests/jest/vfb/review/tree-browser-tests.js index 57ce16459..2a5d1c207 100644 --- a/tests/jest/vfb/batch2/tree-browser-tests.js +++ b/tests/jest/vfb/review/tree-browser-tests.js @@ -152,17 +152,8 @@ describe('VFB Tree Browser Component Tests', () => { let adultCerebralGanglionColor = await page.evaluate(async () => { return CanvasContainer.engine.meshes["VFB_00030867.VFB_00030867_obj"].children[0].material.color.getHexString(); }); - // Select color in color picker box, index 17 belongs to last available color in picker - await page.evaluate(async () => document.querySelectorAll("#tree-color-picker div")[17].click()); - // Wait couple of seconds for mesh to reflect new color - await page.waitFor(20000); - // Retrieve new color in mesh - let adultCerebralGanglionNewColor = await page.evaluate(async () => { - return CanvasContainer.engine.meshes["VFB_00030867.VFB_00030867_obj"].children[0].material.color.getHexString(); - }); - - // Compare RGB's of original color and new color - expect(adultCerebralGanglionColor).not.toEqual(adultCerebralGanglionNewColor); + expect(adultCerebralGanglionColor).toEqual("ffcc00"); + await expect(page).toFill('input[value="#00FF00"]', '#f542e6'); }) it('Click on Node "adult mushroom body"', async () => { @@ -178,39 +169,41 @@ describe('VFB Tree Browser Component Tests', () => { describe('Add "Medulla"', () => { // Load Medulla using search component it('Search and Load "Medulla"', async () => { - // Open search component and search for Medulla - await wait4selector(page, 'i.fa-search', { visible: true, timeout : 10000 }) - await page.waitFor(10000); await click(page, 'i.fa-search'); - await wait4selector(page, ST.SPOT_LIGHT_SELECTOR, { visible: true , timeout : 10000}); - await page.focus(ST.SPOT_LIGHT_SEARCH_INPUT_SELECTOR); - await page.keyboard.type('FBbt_00003748'); - await page.waitFor(10000); - await page.keyboard.type(' '); - await page.waitFor(5000); - await wait4selector(page, '#paperResults', { visible: true , timeout : 50000 }) - - // Click on Medulla from results page - await page.evaluate(async () => { - let tabs = document.getElementsByClassName('MuiListItem-root '); - for ( var i = 0; i < tabs.length ; i ++ ) { - if ( tabs[i].innerText === "medulla (FBbt_00003748)" ) { - tabs[i].click(); - } - } + await wait4selector(page, ST.SPOT_LIGHT_SELECTOR, { visible: true }); + await page.focus(ST.SPOT_LIGHT_SEARCH_INPUT_SELECTOR); + await page.keyboard.type('FBbt_00003748'); + await page.waitFor(10000); + await page.keyboard.type(' '); + await page.waitFor(5000); + await wait4selector(page, '#paperResults', { visible: true , timeout : 50000 }) + + await page.evaluate(async () => { + let tabs = document.getElementsByClassName('MuiListItem-root '); + for ( var i = 0; i < tabs.length ; i ++ ) { + if ( tabs[i].innerText === "medulla (FBbt_00003748)" ) { + tabs[i].click(); + } + } }); + await wait4selector(page, ST.SPOT_LIGHT_SELECTOR, { hidden: true, timeout : 50000 }); + }) - // Wait for drop down menu in searchs component to go away - await wait4selector(page, ST.SPOT_LIGHT_SELECTOR, { hidden: true, timeout : 50000 }); + it('Open Tree Browser', async () => { + await page.waitFor(2000); + await selectTab(page, "Template ROI Browser"); + + // Check that the Tree Browser is visible + await wait4selector(page, 'div.rst__tree', { visible: true, timeout : 800000 }); }) it('Adult Brain remains root node after Medulla selection', async () => { - await page.waitFor(10000); + await page.waitFor(2000); // Retrieve text from first node in Tree Browser let firstNode = await page.evaluate(async () => { - return document.querySelectorAll(".rst__rowContents.rst__rowContentsDragDisabled span")[0].innerText; + return document.querySelector(".nodeFound").innerText; }); - expect(firstNode).toEqual("adult brain"); + expect(firstNode).toEqual("medulla"); }) }) }) diff --git a/tests/jest/vfb/utils.js b/tests/jest/vfb/utils.js index 7554142de..761a5861f 100644 --- a/tests/jest/vfb/utils.js +++ b/tests/jest/vfb/utils.js @@ -32,7 +32,7 @@ export const testLandingPage = async (page, ID) => { // Check page title const title = await page.title(); - expect(title).toBe("Virtual Fly Brain"); + expect(title).toMatch("Virtual Fly Brain"); // Check that the Term Info has a button for deselecting instance, this means it's done loading await wait4selector(page, '#' + ID + '_deselect_buttonBar_btn', { visible: true , timeout : 120000 })