diff --git a/components/VFBMain.js b/components/VFBMain.js
index e16e44f06..28ddfc53a 100644
--- a/components/VFBMain.js
+++ b/components/VFBMain.js
@@ -9,6 +9,7 @@ import VFBTermInfoWidget from './interface/VFBTermInfo/VFBTermInfo';
import Logo from '@geppettoengine/geppetto-client/components/interface/logo/Logo';
import Canvas from '@geppettoengine/geppetto-client/components/interface/3dCanvas/Canvas';
import QueryBuilder from '@geppettoengine/geppetto-client/components/interface/query/queryBuilder';
+import VFBDownloadContents from './interface/VFBDownloadContents/VFBDownloadContents';
import VFBUploader from './interface/VFBUploader/VFBUploader';
import HTMLViewer from '@geppettoengine/geppetto-ui/html-viewer/HTMLViewer';
import VFBListViewer from './interface/VFBListViewer/VFBListViewer';
@@ -52,6 +53,7 @@ class VFBMain extends React.Component {
quickHelpVisible: undefined,
UIUpdated: true,
wireframeVisible: false,
+ downloadContentsVisible : true,
uploaderContentsVisible : true
};
@@ -488,6 +490,9 @@ class VFBMain extends React.Component {
[buttonState]: !this.state[buttonState]
});
break;
+ case 'downloadContentsVisible':
+ this.refs.downloadContentsRef?.openDialog();
+ break;
case 'quickHelpVisible':
if (this.state[buttonState] === undefined) {
this.setState({
@@ -527,6 +532,9 @@ class VFBMain extends React.Component {
case 'triggerSetTermInfo':
this.handlerInstanceUpdate(click.value[0]);
break;
+ case 'downloadContentsVisible':
+ this.refs.downloadContentsRef?.openDialog();
+ break;
case 'uploaderContentsVisible':
this.refs.uploaderContentsRef?.openDialog();
break;
@@ -1752,7 +1760,10 @@ class VFBMain extends React.Component {
searchConfiguration={this.searchConfiguration}
datasourceConfiguration={this.datasourceConfiguration} />
+
+
+
{this.htmlToolbarRender}
);
diff --git a/components/configuration/VFBDownloadContents/configuration.json b/components/configuration/VFBDownloadContents/configuration.json
new file mode 100644
index 000000000..e8898542a
--- /dev/null
+++ b/components/configuration/VFBDownloadContents/configuration.json
@@ -0,0 +1,30 @@
+{
+ "postURL":"https://zip.virtualflybrain.org/download",
+ "contentType": "application/json",
+ "zipName" : "VFB Files.zip",
+ "options" :{
+ "obj": {
+ "label" : "OBJ"
+ },
+ "swc": {
+ "label" : "SWC"
+ },
+ "nrrd": {
+ "label" : "NRRD"
+ },
+ "reference": {
+ "label" : "References"
+ }
+ },
+ "text" : {
+ "title" : "Download Data",
+ "typesSubtitle" : "Please select the desired types",
+ "variablesSubtitle" : "Please select Variables:",
+ "noVariablesSubtitle" : "No loaded variables",
+ "errorMessage" : "Something went wrong... We were not able to download the data. Please try again.",
+ "noEntriesFound" : "No entries found for the types and variables selected.",
+ "cancelButton" : "Cancel",
+ "downloadButton" : "Download",
+ "tryAgainButton" : "Try Again"
+ }
+}
\ No newline at end of file
diff --git a/components/configuration/VFBDownloadContents/nrrd.png b/components/configuration/VFBDownloadContents/nrrd.png
new file mode 100644
index 000000000..beee12a5c
Binary files /dev/null and b/components/configuration/VFBDownloadContents/nrrd.png differ
diff --git a/components/configuration/VFBDownloadContents/obj.png b/components/configuration/VFBDownloadContents/obj.png
new file mode 100644
index 000000000..fbcc914c7
Binary files /dev/null and b/components/configuration/VFBDownloadContents/obj.png differ
diff --git a/components/configuration/VFBDownloadContents/reference.png b/components/configuration/VFBDownloadContents/reference.png
new file mode 100644
index 000000000..b821fe7e1
Binary files /dev/null and b/components/configuration/VFBDownloadContents/reference.png differ
diff --git a/components/configuration/VFBDownloadContents/swc.png b/components/configuration/VFBDownloadContents/swc.png
new file mode 100644
index 000000000..3eb5d02b6
Binary files /dev/null and b/components/configuration/VFBDownloadContents/swc.png differ
diff --git a/components/configuration/VFBToolbar/vfbtoolbarMenuConfiguration.js b/components/configuration/VFBToolbar/vfbtoolbarMenuConfiguration.js
index a85df3499..1310a57c3 100644
--- a/components/configuration/VFBToolbar/vfbtoolbarMenuConfiguration.js
+++ b/components/configuration/VFBToolbar/vfbtoolbarMenuConfiguration.js
@@ -253,6 +253,14 @@ var toolbarMenu = {
parameters: ["circuitBrowserVisible"]
}
},
+ {
+ label: "Download Contents",
+ icon: "fa fa-download",
+ action: {
+ handlerAction: "downloadContentsVisible",
+ parameters: []
+ }
+ },
{
label: "NBLAST Uploader",
icon: "fa fa-upload",
diff --git a/components/interface/VFBDownloadContents/VFBDownloadContents.js b/components/interface/VFBDownloadContents/VFBDownloadContents.js
new file mode 100644
index 000000000..aed98437b
--- /dev/null
+++ b/components/interface/VFBDownloadContents/VFBDownloadContents.js
@@ -0,0 +1,563 @@
+import React from "react";
+import Button from "@material-ui/core/Button";
+import Grid from "@material-ui/core/Grid";
+import Dialog from "@material-ui/core/Dialog";
+import DialogActions from "@material-ui/core/DialogActions";
+import DialogContent from "@material-ui/core/DialogContent";
+import DialogTitle from "@material-ui/core/DialogTitle";
+import Typography from "@material-ui/core/Typography";
+import FormControlLabel from "@material-ui/core/FormControlLabel";
+import CircularProgress from '@material-ui/core/CircularProgress';
+import ChevronRightIcon from "@material-ui/icons/ChevronRight";
+import ExpandMoreIcon from "@material-ui/icons/ExpandMore";
+import { Checkbox, Divider, IconButton } from "@material-ui/core";
+import Box from "@material-ui/core/Box";
+import { createMuiTheme, ThemeProvider } from "@material-ui/core/styles";
+import { withStyles } from "@material-ui/styles";
+import axios from "axios";
+import TreeView from "@material-ui/lab/TreeView";
+import TreeItem from "@material-ui/lab/TreeItem";
+import NRRDIcon from "../../configuration/VFBDownloadContents/nrrd.png";
+import OBJIcon from "../../configuration/VFBDownloadContents/obj.png";
+import SWCIcon from "../../configuration/VFBDownloadContents/swc.png";
+import ReferenceIcon from "../../configuration/VFBDownloadContents/reference.png";
+import CloseIcon from "@material-ui/icons/Close";
+import { connect } from "react-redux";
+
+const iconsMap = {
+ obj: OBJIcon,
+ swc: SWCIcon,
+ reference: ReferenceIcon,
+ nrrd: NRRDIcon,
+};
+
+const ALL_INSTANCES = { id: "ALL_INSTANCES", name: "All Instances" };
+
+const styles = theme => ({
+ downloadButton: { backgroundColor: "#0AB7FE", color: "white !important" },
+ downloadErrorButton: { backgroundColor: "#FCE7E7", color: "#E53935", border : "1px solid #E53935" },
+ error: { color: "#E53935" },
+ errorMessage: { wordWrap: "break-word" },
+ downloadButtonText: { color: "white !important" },
+ checkedBox: { borderColor: "#0AB7FE" },
+ footer: { backgroundColor: "#EEF9FF" },
+ errorFooter: { backgroundColor: "#FCE7E7" },
+ listItemText: { fontSize: "1em" },
+ customizedButton: {
+ position: "absolute",
+ left: "95%",
+ top: "2%",
+ backgroundColor: "#F5F5F5",
+ color: "gray",
+ },
+ dialog: {
+ overflow: "unset",
+ margin: "0 auto",
+ },
+ dialogContent: { overflow: "hidden" },
+ checked: { "&$checked": { color: "#0AB7FE" } },
+ "@global": {
+ ".MuiTreeItem-root.Mui-selected > .MuiTreeItem-content .MuiTreeItem-label": { backgroundColor: "white" },
+ ".MuiTreeItem-root.Mui-selected > .MuiTreeItem-content .MuiTreeItem-label:hover, .MuiTreeItem-root.Mui-selected:focus > .MuiTreeItem-content .MuiTreeItem-label": { backgroundColor: "white" }
+ },
+});
+
+const theme = createMuiTheme({
+ typography: {
+ h2: {
+ fontSize: 22,
+ fontWeight: 400,
+ fontStyle: "normal",
+ lineHeight: "26.4px",
+ color: "#181818",
+ fontFamily: "Barlow",
+ },
+ h5: {
+ fontSize: 11,
+ fontWeight: 500,
+ fontStyle: "normal",
+ lineHeight: "13.2px",
+ fontFamily: "Barlow",
+ color: "rgba(0, 0, 0, 0.54)",
+ },
+ subtitle2: {
+ fontSize: 11,
+ fontWeight: 500,
+ fontStyle: "normal",
+ lineHeight: "13.2px",
+ fontFamily: "Barlow",
+ color: "rgba(0, 0, 0, 0.24)",
+ },
+ error: {
+ fontSize: 11,
+ fontWeight: 500,
+ fontStyle: "normal",
+ lineHeight: "13.2px",
+ fontFamily: "Barlow",
+ color: "#E53935",
+ },
+ button: {
+ fontSize: 11,
+ fontWeight: 600,
+ fontStyle: "normal",
+ lineHeight: "13.2px",
+ fontFamily: "Barlow",
+ color: "#0AB7FE",
+ },
+ },
+ Button: {
+ "&:hover": {
+ backgroundColor: "#0AB7FE",
+ boxShadow: "none",
+ },
+ "&:active": {
+ boxShadow: "none",
+ backgroundColor: "#0AB7FE",
+ },
+ },
+});
+
+/**
+ * Component to download files contents
+ */
+class VFBDownloadContents extends React.Component {
+ constructor (props) {
+ super(props);
+
+ this.state = {
+ open: false,
+ typesChecked: [],
+ downloadError: false,
+ downloading: false,
+ selectedVariables: [],
+ allVariablesSelectedFlag: false,
+ errorMessage : ""
+ };
+
+ this.configuration = require("../../configuration/VFBDownloadContents/configuration");
+ this.configurationOptions = this.configuration.options;
+ this.handleCloseDialog = this.handleCloseDialog.bind(this);
+ this.openDialog = this.openDialog.bind(this);
+ this.handleTypeSelection = this.handleTypeSelection.bind(this);
+ this.handleDownload = this.handleDownload.bind(this);
+ this.extractVariableFileMeta = this.extractVariableFileMeta.bind(this);
+ this.getAllLoadedVariables = this.getAllLoadedVariables.bind(this);
+ this.requestZipDownload = this.requestZipDownload.bind(this);
+ this.getVariableById = this.getVariableById.bind(this);
+ this.toggleVariable = this.toggleVariable.bind(this);
+ this.variables = [ALL_INSTANCES];
+ }
+
+ handleCloseDialog () {
+ this.setState({ open: false });
+ }
+
+ openDialog () {
+ this.variables = this.getAllLoadedVariables();
+ this.setState({
+ open: true,
+ downloadError : false,
+ downloading : false,
+ downloadEnabled : this.state.typesChecked.length > 0 && this.state.selectedVariables.length > 0
+ });
+ }
+
+ handleDownload () {
+ if ( this.state.downloading ) {
+ return;
+ }
+
+ let json = { entries: [] };
+
+ this.state.selectedVariables.map( variable => {
+ const filemeta = this.extractVariableFileMeta(variable);
+ json.entries = json.entries.concat(filemeta);
+ });
+
+ json.entries.length > 0 ? this.requestZipDownload(json) : this.setState({ downloadError : true, errorMessage : this.configuration.text.noEntriesFound });
+ }
+
+ /**
+ * Extract filemeta from geppetto model, using variable id to find it
+ */
+ extractVariableFileMeta (variable) {
+ let filemetaText = variable.filemeta?.values[0]?.value?.text;
+ filemetaText = filemetaText?.replace(/'/g, '"');
+
+ const filemetaObject = JSON.parse(filemetaText);
+ let filesArray = [];
+
+ this.state.typesChecked.map( check => {
+ filemetaObject[check]
+ && filesArray.push({
+ Url: filemetaObject[check]?.url,
+ ZipPath: filemetaObject[check]?.local,
+ });
+ });
+
+ return filesArray;
+ }
+
+ /**
+ * Get array of all loaded variables in application
+ */
+ getAllLoadedVariables () {
+ let entities = GEPPETTO.ModelFactory.allPaths;
+ var visuals = [];
+
+ for (var i = 0; i < entities.length; i++) {
+ if ( entities[i].metaType === "VisualType" || entities[i].metaType === "CompositeVisualType" ) {
+ const variable = entities[i]?.path?.split(".")[0];
+ const instance = window.Instances[variable];
+ const filemeta = instance[variable + "_meta"]?.variable?.types[0]?.filemeta;
+ visuals.push({ id: variable, name: instance?.name, filemeta: filemeta });
+ }
+ }
+
+ return visuals;
+ }
+
+ /**
+ * Make axios call to download the zip
+ */
+ requestZipDownload (jsonRequest) {
+ let self = this;
+
+ this.setState({ downloading: true, downloadEnabled : false });
+ // Axios HTTP Post request with post query
+ axios({
+ method: "post",
+ url: this.configuration.postURL,
+ headers: { "content-type": this.configuration.contentType },
+ data: jsonRequest,
+ responseType: "arraybuffer",
+ })
+ .then(function (response) {
+ const url = window.URL.createObjectURL(new Blob([response.data]));
+ const link = document.createElement("a");
+ link.href = url;
+ link.setAttribute("download", self.configuration.zipName);
+ document.body.appendChild(link);
+ link.click();
+ setTimeout(
+ () =>
+ self.setState({
+ downloading: false,
+ open: false,
+ downloadEnabled : true
+ }),
+ 500
+ );
+ })
+ .catch(function (error) {
+ self.setState({
+ downloadError: true,
+ downloading: false,
+ errorMessage : this.props.classes.errorMessage
+ });
+ });
+ }
+
+ /**
+ * Handle checkbox selection of different types to download
+ */
+ handleTypeSelection (value) {
+ const currentIndex = this.state.typesChecked.indexOf(value);
+ const newTypesChecked = [...this.state.typesChecked];
+
+ if (currentIndex === -1) {
+ newTypesChecked.push(value);
+ } else {
+ newTypesChecked.splice(currentIndex, 1);
+ }
+
+ this.setState({ typesChecked: newTypesChecked, downloadEnabled : newTypesChecked.length > 0 && this.state.selectedVariables.length > 0 });
+ }
+
+ /**
+ * Get variable by id, trigger by checkbox selection of variables
+ */
+ getVariableById (nodes, id) {
+ let variablesMatched = [];
+
+ if (id === ALL_INSTANCES.id) {
+ variablesMatched = nodes;
+ } else {
+ nodes.forEach(node => {
+ if (node.id === id) {
+ variablesMatched.push(node);
+ }
+ });
+ }
+
+ return variablesMatched;
+ }
+
+ /**
+ * Toggle variable selection from checklist
+ */
+ toggleVariable (checked, node) {
+ const allNode = this.getVariableById(this.variables, node.id);
+ let updatedVariables = checked
+ ? [...this.state.selectedVariables, ...allNode]
+ : this.state.selectedVariables.filter(
+ value => !allNode.find( node => node.id === value.id )
+ );
+
+ updatedVariables = updatedVariables.filter((v, i) => updatedVariables.indexOf(v) === i);
+
+ this.setState({
+ selectedVariables: updatedVariables,
+ allVariablesSelectedFlag: updatedVariables.length > 0,
+ downloadEnabled : this.state.typesChecked.length > 0 && updatedVariables.length > 0
+ });
+ }
+
+ render () {
+ let self = this;
+ const { idsMap } = this.props;
+ this.variables = this.getAllLoadedVariables();
+
+ return (
+
+
+
+ );
+ }
+}
+
+function mapStateToProps (state) {
+ return {
+ instanceDeleted : state.generals.ui.canvas.instanceDeleted,
+ instanceOnFocus : state.generals.instanceOnFocus,
+ idsMap : state.generals.idsMap,
+ idsList : state.generals.idsList
+ }
+}
+
+export default connect(mapStateToProps, null, null, { forwardRef : true } )(withStyles(styles)(VFBDownloadContents));
\ No newline at end of file
diff --git a/components/interface/VFBFocusTerm/VFBFocusTerm.js b/components/interface/VFBFocusTerm/VFBFocusTerm.js
index 482347f9b..dd8a10262 100644
--- a/components/interface/VFBFocusTerm/VFBFocusTerm.js
+++ b/components/interface/VFBFocusTerm/VFBFocusTerm.js
@@ -568,6 +568,13 @@ class VFBFocusTerm extends React.Component {
:
}
+
+ {
+ this.props.UIUpdateManager("downloadContentsVisible");
+ }} />
+