diff --git a/.gitignore b/.gitignore index 8e8498bb..e3d873d8 100644 --- a/.gitignore +++ b/.gitignore @@ -24,4 +24,5 @@ utilities/x86_64 .idea *.iml x86_64 -.jupyter-config \ No newline at end of file +.jupyter-config +venv \ No newline at end of file diff --git a/netpyne_ui/__init__.py b/netpyne_ui/__init__.py index cb0bf72e..40ebdb1a 100644 --- a/netpyne_ui/__init__.py +++ b/netpyne_ui/__init__.py @@ -1,6 +1,7 @@ import logging + from jupyter_geppetto.webapi import RouteManager from netpyne_ui import api -RouteManager.add_controller(api.NetPyNEController) \ No newline at end of file +RouteManager.add_controller(api.NetPyNEController) diff --git a/netpyne_ui/api.py b/netpyne_ui/api.py index 91cac361..de8fdd76 100644 --- a/netpyne_ui/api.py +++ b/netpyne_ui/api.py @@ -8,7 +8,7 @@ from tempfile import TemporaryDirectory from jupyter_geppetto.webapi import get, post from notebook.base.handlers import IPythonHandler -from netpyne_ui.constants import NETPYNE_WORKDIR, UPLOAD_FOLDER_NAME, ALLOWED_EXTENSIONS, UPLOAD_FOLDER_PATH +from netpyne_ui.constants import ALLOWED_EXTENSIONS, UPLOAD_FOLDER_PATH def allowed_file(filename, allowed_extensions=ALLOWED_EXTENSIONS): return '.' in filename and \ @@ -18,7 +18,8 @@ def allowed_file(filename, allowed_extensions=ALLOWED_EXTENSIONS): def send_files(handler, file_path, filename): with open(file_path, "rb") as f: handler.set_header('Content-Type', 'application/force-download') - handler.set_header('Content-Disposition', f"attachment; filename={filename}") + handler.set_header('Content-Disposition', + f"attachment; filename={filename}") try: while True: @@ -26,7 +27,7 @@ def send_files(handler, file_path, filename): if _buffer: handler.write(_buffer) else: - return + return except: handler.set_status(500, f"Error sending files") @@ -35,36 +36,40 @@ def get_file_paths(handler): file_paths = False if 'uri' in handler.request.arguments: file_paths = [] - tmp_file_paths = [path.decode('utf-8') for path in handler.request.arguments['uri']] + tmp_file_paths = [path.decode('utf-8') + for path in handler.request.arguments['uri']] for path in tmp_file_paths: if os.path.exists(path): file_paths.append(path) - + return file_paths + class NetPyNEController: # pytest: no cover @post('/uploads') def uploads(handler: IPythonHandler): files = handler.request.files files_saved = 0 - + if len(files) == 0 or 'file' not in files: - handler.set_status(400, f"Can't find 'file' or filename is empty. Files received {len(files)}") + handler.set_status( + 400, f"Can't find 'file' or filename is empty. Files received {len(files)}") else: for f in files['file']: if not allowed_file(f.filename): - logging.warn(f"Can't store file {f.filename}. Extension not allowed") + logging.warn( + f"Can't store file {f.filename}. Extension not allowed") continue - ## Save to file + # Save to file filename = f.filename file_path = os.path.join(UPLOAD_FOLDER_PATH, filename) - + with open(file_path, 'wb') as zf: zf.write(f['body']) - + files_saved += 1 if filename.endswith('.zip'): @@ -78,34 +83,70 @@ def uploads(handler: IPythonHandler): elif filename.endswith('.gz'): with gzip.open(file_path, "rb") as gz, open(file_path.replace('.gz', ''), 'wb') as ff: shutil.copyfileobj(gz, ff) - - handler.set_status(200, f"Number of files saved: {files_saved}. Number of files sent: {len(files['file'])}") + + handler.set_status( + 200, f"Number of files saved: {files_saved}. Number of files sent: {len(files['file'])}") handler.finish() - + @get('/downloads') def downloads(handler: IPythonHandler): file_paths = get_file_paths(handler) - + if file_paths: - + if len(file_paths) == 0: handler.set_status(400, f"Files not found.") handler.finish() return if len(file_paths) == 1: - send_files(handler, file_paths[0], file_paths[0].split('/')[-1]) - - else : + send_files(handler, file_paths[0], + file_paths[0].split('/')[-1]) + + else: with TemporaryDirectory() as dir_path: tar_gz_file_name = f'{str(uuid.uuid4())}.tar.gz' tar_gz_file_path = os.path.join(dir_path, tar_gz_file_name) with tarfile.open(tar_gz_file_path, mode='w:gz') as tar: for file_path in file_paths: - tar.add(file_path, os.path.join('download', file_path.split('/')[-1])) + tar.add(file_path, os.path.join( + 'download', file_path.split('/')[-1])) send_files(handler, tar_gz_file_path, tar_gz_file_name) - + handler.finish() + + +def create_notebook(filename): + import nbformat as nbf + from nbformat.v4.nbbase import new_notebook + from nbformat import sign + import codecs + + directory = os.path.dirname(filename) + if not os.path.exists(directory): + os.makedirs(directory) + nb0 = new_notebook(cells=[nbf.v4.new_markdown_cell("""# Welcome to the NetPyNE-ui! + + """), + nbf.v4.new_code_cell( + 'netpyne_geppetto.netParams'), + nbf.v4.new_code_cell( + 'netpyne_geppetto.simConfig') + ], metadata={"kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }}) + + f = codecs.open(filename, encoding='utf-8', mode='w') + + nbf.write(nb0, filename) + f.close() + + +# TODO move to jupyter geppetto, using notebook dir path +if os.path.exists('workspace') and not os.path.exists('workspace/notebook.ipynb'): + create_notebook('workspace/notebook.ipynb') diff --git a/netpyne_ui/constants.py b/netpyne_ui/constants.py index 283d781c..adb20518 100644 --- a/netpyne_ui/constants.py +++ b/netpyne_ui/constants.py @@ -3,7 +3,7 @@ UPLOAD_FOLDER_NAME = 'uploads' NETPYNE_WORKDIR = 'workspace' -ALLOWED_EXTENSIONS = ["py", "zip", "gz", ".tar.gz", "pdf", "txt", "xls", "png", "jpeg", "hoc"] +ALLOWED_EXTENSIONS = ["py", "zip", "gz", ".tar.gz", "pdf", "txt", "xls", "png", "jpeg", "hoc", "json"] HERE = os.path.dirname(os.path.abspath(__file__)) ROOT = os.path.dirname(HERE) UPLOAD_FOLDER_PATH = os.path.join(ROOT, NETPYNE_WORKDIR, UPLOAD_FOLDER_NAME) diff --git a/netpyne_ui/netpyne_geppetto.py b/netpyne_ui/netpyne_geppetto.py index bd0a6a64..deb71231 100644 --- a/netpyne_ui/netpyne_geppetto.py +++ b/netpyne_ui/netpyne_geppetto.py @@ -9,6 +9,7 @@ import subprocess import logging import re +import base64 from netpyne import specs, sim, analysis from netpyne.specs.utils import validateFunction @@ -16,7 +17,6 @@ from netpyne.metadata import metadata from netpyne_ui.netpyne_model_interpreter import NetPyNEModelInterpreter from pygeppetto.model.model_serializer import GeppettoModelSerializer -import matplotlib.pyplot as plt from pygeppetto import ui import numpy as np import neuron @@ -231,6 +231,12 @@ def importModel(self, modelParameters): # Import Model attributes self.netParams = getattr(net_params_module_name, str(modelParameters["netParamsVariable"])) + if isinstance(self.netParams, dict): + self.netParams = specs.NetParams(self.netParams) + + if isinstance(self.simConfig, dict): + self.simConfig = specs.SimConfig(self.simConfig) + for key, value in self.netParams.cellParams.items(): if hasattr(value, 'todict'): self.netParams.cellParams[key] = value.todict() @@ -329,12 +335,9 @@ def deleteModel(self, modelParams): return utils.getJSONError("Error while exporting the NetPyNE model", sys.exc_info()) try: - # This function fails is some keys don't exists - # sim.clearAll() - # TODO: as part of #264 we should remove the method and use clearAll intstead - self.clearSim() + sim.clearAll() except: - pass + logging.exception("Failed to clear simulation") return utils.getJSONReply() @@ -432,7 +435,7 @@ def getPlot(self, plotName, LFPflavour, theme='gui'): # This arg brings dark theme. But some plots are broken by it args['theme'] = theme - if plotName in ("iplotConn", "iplot2Dnet") and sim.net.allCells: + if plotName in ("iplotConn", "iplot2Dnet") and hasattr(sim, 'net') and sim.net.allCells: # To prevent unresponsive kernel, we don't show conns if they become too many num_conn = sum([len(cell.conns) for cell in sim.net.allCells if cell.conns]) if num_conn > NUM_CONN_LIMIT: @@ -597,42 +600,51 @@ def header(title, spacer='-'): params = ['popParams', 'cellParams', 'synMechParams'] params += ['connParams', 'stimSourceParams', 'stimTargetParams'] - fname = args['fileName'] if args['fileName'][-3:] == '.py' else args['fileName'] + '.py' + fname = args['fileName'] + if not fname: + # default option + fname = 'output.py' + + if not fname[-3:] == '.py': + fname = f"{fname}.py" + # TODO: use methods offered by netpyne to create this script! with open(fname, 'w') as script: - script.write('from netpyne import specs, sim\n') - script.write(header('documentation')) - script.write("''' Script generated with NetPyNE-UI. Please visit:\n") - script.write(" - https://www.netpyne.org\n - https://github.com/MetaCell/NetPyNE-UI\n'''\n") - script.write(header('script', spacer='=')) - script.write('netParams = specs.NetParams()\n') - script.write('simConfig = specs.SimConfig()\n') - script.write(header('single value attributes')) + script.write("from netpyne import specs, sim\n") + script.write(header("documentation")) + script.write("Script generated with NetPyNE-UI. Please visit:\n") + script.write(" - https://www.netpyne.org\n - https://github.com/MetaCell/NetPyNE-UI\n\n") + script.write(header("script", spacer="=")) + script.write("netParams = specs.NetParams()\n") + script.write("simConfig = specs.SimConfig()\n") + script.write(header("single value attributes")) for attr, value in list(self.netParams.__dict__.items()): if attr not in params: if value != getattr(specs.NetParams(), attr): - script.write('netParams.' + attr + ' = ') - script.write(convert2bool(json.dumps(value, indent=4)) + '\n') + script.write("netParams." + attr + " = ") + script.write(convert2bool(json.dumps(value, indent=4)) + "\n") - script.write(header('network attributes')) + script.write(header("network attributes")) for param in params: for key, value in list(getattr(self.netParams, param).items()): - script.write("netParams." + param + "['" + key + "'] = ") - script.write(convert2bool(json.dumps(value, indent=4)) + '\n') + script.write("netParams." + param + "[" + key + "] = ") + script.write(convert2bool(json.dumps(value, indent=4)) + "\n") - script.write(header('network configuration')) + script.write(header("network configuration")) for attr, value in list(self.simConfig.__dict__.items()): if value != getattr(specs.SimConfig(), attr): - script.write('simConfig.' + attr + ' = ') - script.write(convert2bool(json.dumps(value, indent=4)) + '\n') + script.write("simConfig." + attr + " = ") + script.write(convert2bool(json.dumps(value, indent=4)) + "\n") - script.write(header('create simulate analyze network')) - script.write('# sim.createSimulateAnalyze(netParams=netParams, simConfig=simConfig)\n') + script.write(header("create simulate analyze network")) + script.write("# sim.createSimulateAnalyze(netParams=netParams, simConfig=simConfig)\n") - script.write(header('end script', spacer='=')) + script.write(header("end script", spacer="=")) with open(fname) as f: - return f.read() + file_b64 = base64.b64encode(bytes(f.read(), 'utf-8')).decode() + export_info = {"fileContent": file_b64, "fileName": fname} + return export_info except: return utils.getJSONError("Error while importing the NetPyNE model", sys.exc_info()) @@ -740,50 +752,6 @@ def propagate_syn_mech_rename(self, new, old): else: self.netParams.stimTargetParams[label]['synMech'] = new - def clearSim(self): - # clean up - sim.pc.barrier() - sim.pc.gid_clear() # clear previous gid settings - - # clean cells and simData in all nodes - sim.clearObj([cell.__dict__ if hasattr(cell, '__dict__') else cell for cell in sim.net.cells]) - if 'stims' in list(sim.simData.keys()): - sim.clearObj([stim for stim in sim.simData['stims']]) - - for key in list(sim.simData.keys()): del sim.simData[key] - - if hasattr(sim, 'net'): - for c in sim.net.cells: del c - for p in sim.net.pops: del p - if hasattr(sim.net, 'params'): - del sim.net.params - - # clean cells and simData gathered in master node - if sim.rank == 0: - if hasattr(sim.net, 'allCells'): - sim.clearObj([cell.__dict__ if hasattr(cell, '__dict__') else cell for cell in sim.net.allCells]) - if hasattr(sim, 'allSimData'): - if 'stims' in list(sim.allSimData.keys()): - sim.clearObj([stim for stim in sim.allSimData['stims']]) - for key in list(sim.allSimData.keys()): del sim.allSimData[key] - del sim.allSimData - - import matplotlib - matplotlib.pyplot.clf() - matplotlib.pyplot.close('all') - - if hasattr(sim, 'net'): - if hasattr(sim.net, 'allCells'): - for c in sim.net.allCells: del c - del sim.net.allCells - if hasattr(sim.net, 'allPops'): - for p in sim.net.allPops: del p - - del sim.net - - import gc; - gc.collect() - def create_celltype_from_template(self, label="CellType", conds={}, cell_template_name="Blank"): try: with redirect_stdout(sys.__stdout__): diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 00000000..cfdf7274 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,21 @@ +{ + "requires": true, + "lockfileVersion": 1, + "dependencies": { + "@types/js-base64": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@types/js-base64/-/js-base64-3.3.1.tgz", + "integrity": "sha512-Zw33oQNAvDdAN9b0IE5stH0y2MylYvtU7VVTKEJPxhyM2q57CVaNJhtJW258ah24NRtaiA23tptUmVn3dmTKpw==", + "dev": true, + "requires": { + "js-base64": "*" + } + }, + "js-base64": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-3.6.1.tgz", + "integrity": "sha512-Frdq2+tRRGLQUIQOgsIGSCd1VePCS2fsddTG5dTCqR0JHgltXWfsxnY0gIXPoMeRmdom6Oyq+UMOFg5suduOjQ==", + "dev": true + } + } +} diff --git a/requirements.txt b/requirements.txt index 7d232b46..911f818c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -44,7 +44,7 @@ nbconvert==5.6.1 nbformat==5.0.6 neuromllite==0.1.9 netpyne==1.0.0.2 -NEURON==7.8.1.1 +NEURON==8.0.0 numpy==1.18.5 oauthlib==3.0.1 ordered-set==4.0.2 @@ -85,7 +85,7 @@ scipy==1.4.1 Send2Trash==1.5.0 terminado==0.8.3 testpath==0.4.4 -tornado==6.0.4 +tornado==6.1.0 traitlets==4.3.3 typing-extensions==3.7.4.2 urllib3==1.25.9 diff --git a/tests/backend/models/gui_import_dict.py b/tests/backend/models/gui_import_dict.py new file mode 100644 index 00000000..de26e84c --- /dev/null +++ b/tests/backend/models/gui_import_dict.py @@ -0,0 +1,69 @@ +from netpyne import specs + +# Network parameters +netParams = {} # object of class NetParams to store the network parameters + +netParams['cellParams'] = {} +netParams['popParams'] = {} +netParams['stimSourceParams'] = {} +netParams['stimTargetParams'] = {} +netParams['synMechParams'] = {} +netParams['connParams'] = {} + +## Cell parameters +secs = {} # dict with section info +secs['soma'] = {'geom': {}, 'mechs': {}} +secs['soma']['geom'] = {'diam': 12, 'L': 12, 'Ra': 100.0, 'cm': 1} # soma geometry +secs['soma']['mechs']['hh'] = {'gnabar': 0.12, 'gkbar': 0.036, 'gl': 0.0003, 'el': -54.3} # soma hh mechanism + +secs['dend'] = {'geom': {}, 'mechs': {}} +secs['dend']['geom'] = {'diam': 1.0, 'L': 200.0, 'Ra': 100.0, 'cm': 1} +secs['dend']['topol'] = {'parentSec': 'soma', 'parentX': 1.0, 'childX': 0} # dend geometry +secs['dend']['mechs']['pas'] = {'g': 0.001, 'e': -70} # dend pas mechanism + +netParams['cellParams']['pyr'] = {'secs': secs} # add dict to list of cell parameters + +## Population parameters +netParams['popParams']['E'] = {'cellType': 'pyr', 'numCells': 40} + + # Stimulation parameters +netParams['stimSourceParams']['IClamp1'] = {'type': 'IClamp', 'dur': 5, 'del': 20, 'amp': 0.1} +netParams['stimTargetParams']['IClamp1->cell0'] = {'source': 'IClamp1', 'conds': {'cellList':[0]}, 'sec':'dend', 'loc':1.0} + + +# Synaptic mechanism parameters +netParams['synMechParams']['exc'] = {'mod': 'Exp2Syn', 'tau1': 0.1, 'tau2': 1.0, 'e': 0} + + +# Connectivity parameters +netParams['connParams']['E->E'] = { + 'preConds': {'pop': 'E'}, + 'postConds': {'pop': 'E'}, + 'weight': 0.005, # weight of each connection + 'probability': 0.1, + 'delay': 5, # delay min=0.2, mean=13.0, var = 1.4 + 'synMech': 'exc', + 'sec': 'dend'} + +# Simulation options +simConfig = specs.SimConfig() # object of class SimConfig to store simulation configuration + +simConfig.duration = 0.2*1e3 # Duration of the simulation, in ms +simConfig.dt = 0.1 # Internal integration timestep to use +simConfig.verbose = False # Show detailed messages +simConfig.recordTraces = {'V_soma':{'sec':'soma','loc':0.5,'var':'v'}, + 'V_dend': {'sec': 'dend', 'loc': 1.0, 'var':'v'}} # Dict with traces to record +simConfig.recordCells = [0] +simConfig.recordStep = 0.1 # Step size in ms to save data (eg. V traces, LFP, etc) +simConfig.filename = 'gui_tut1' # Set file output name +simConfig.saveJson = False # Save params, network and sim output to pickle file +simConfig.analysis['iplotTraces'] = {'include': [0], 'overlay': True} +simConfig.analysis['iplotRaster'] = {'markerSize': 5, 'showFig': True} + + +if __name__ == '__main__': + netpyne_geppetto.netParams=netParams + netpyne_geppetto.simConfig=simConfig + +#from netpyne import sim +#sim.createSimulateAnalyze(netParams, simConfig) diff --git a/tests/backend/netypne_model_importer_test.py b/tests/backend/netypne_model_importer_test.py new file mode 100644 index 00000000..73093584 --- /dev/null +++ b/tests/backend/netypne_model_importer_test.py @@ -0,0 +1,68 @@ +import unittest +import os +import sys +import logging +import json, urllib.request + +import netpyne + +from netpyne import specs, sim +from netpyne_ui.netpyne_model_interpreter import NetPyNEModelInterpreter +import neuron +import subprocess + +from netpyne_ui.netpyne_geppetto import NETPYNE_WORKDIR_PATH +from netpyne_ui.netpyne_geppetto import NetPyNEGeppetto + +netpyne.__gui__ = False +sys.path.insert(0, NETPYNE_WORKDIR_PATH) + +class TestNetPyNEModelInterpreter(unittest.TestCase): + + @classmethod + def setUpClass(cls): + HERE = os.path.dirname(os.path.realpath(__file__)) + ROOT = os.path.dirname(HERE) + cls.path = NETPYNE_WORKDIR_PATH + modelpath = os.path.join(NETPYNE_WORKDIR_PATH, 'mod') + subprocess.call(["rm", "-r", os.path.join(modelpath, "x86_64")]) + owd = os.getcwd() + os.chdir(modelpath) + p = subprocess.check_output(["nrnivmodl"]) + os.chdir(owd) + try: + neuron.load_mechanisms(modelpath) + except: + logging.error("Error loading mechanisms", exc_info=True) + + def test_dict_import_1(self): + print("------------------------------------") + print("Dictionary transform importModel:") + print("------------------------------------") + + # response = urllib.request.urlopen("https://www.opensourcebrain.org/projects/netpyneshowcase/repository/revisions/test_py36/raw/NetPyNE/UI/HHCellNetwork.txt.json") + netpyne_info = {} #json.loads(response.read()) + + # print(type(netpyne_info['net']['params'])) + + path = next(filter(lambda p: os.path.join('tests', 'backend') in p, sys.path), None) + + netpyne_info['compileMod'] = False + netpyne_info['loadMod'] = False + netpyne_info['modFolder'] = "mod" + netpyne_info['netParamsPath'] = os.path.join(path, 'models') + netpyne_info['netParamsModuleName'] = "gui_import_dict" + netpyne_info['netParamsVariable'] = "netParams" + netpyne_info['simConfigPath'] = os.path.join(path, 'models') + netpyne_info['simConfigModuleName'] = "gui_import_dict" + netpyne_info['simConfigVariable'] = "simConfig" + + netpyne = NetPyNEGeppetto() + netpyne.importModel(netpyne_info) + +if __name__ == '__main__': + try: + unittest.main() + except SystemExit as inst: + if inst.args[0]: # raised by sys.exit(True) when tests failed + raise diff --git a/tests/frontend/e2e/.eslintrc.js b/tests/frontend/e2e/.eslintrc.js index 7e6f09b8..9310b33f 100644 --- a/tests/frontend/e2e/.eslintrc.js +++ b/tests/frontend/e2e/.eslintrc.js @@ -17,6 +17,6 @@ module.exports = { pvdr: true, net1: true, CanvasContainer: true, - patchRequire: true + patchRequire: true, } -}; \ No newline at end of file +}; diff --git a/utilities/install.py b/utilities/install.py index 8075fb79..1df73599 100644 --- a/utilities/install.py +++ b/utilities/install.py @@ -34,7 +34,7 @@ def cprint(string): def execute(cmd, cwd='.', *args, **kwargs): exit_code = subprocess.call(cmd, cwd=cwd, *args, **kwargs) if exit_code != 0: - raise SystemExit('Error installing NetPyNE-UI') + raise SystemExit(f'Error installing NetPyNE-UI - Command {cmd} failed with code {exit_code} in directory {cwd}') # by default clones branch (which can be passed as a parameter python install.py branch test_branch) @@ -46,13 +46,22 @@ def clone(repository, folder=None, branch_or_tag=None, cwdp=DEPS_DIR, recursive= if folder and os.path.exists(os.path.join(cwdp, folder)): print(f'Skipping clone of {repository}: folder exists') else: + exit_code = 0 if recursive: - subprocess.call(['git', 'clone', '--recursive', repository], cwd=cwdp) + exit_code = subprocess.call(['git', 'clone', '--recursive', repository], cwd=cwdp) else: if folder: - subprocess.call(['git', 'clone', repository, folder], cwd=cwdp) + exit_code = subprocess.call(['git', 'clone', repository, folder], cwd=cwdp) else: - subprocess.call(['git', 'clone', repository], cwd=cwdp) + exit_code = subprocess.call(['git', 'clone', repository], cwd=cwdp) + + if exit_code != 0: + raise SystemExit(f'Failed to clone repository {repository} into {folder}') + + if not os.path.exists(os.path.join(cwdp, folder, '.git')): + print(f'Skipping checkout of {repository}: folder is not a git repository') + return + if branch_or_tag: checkout(folder, branch_or_tag, cwdp) @@ -75,6 +84,7 @@ def compile_mod(): def main(netpyne_branch, workspace_branch, pygeppetto_branch=None, jupyter_geppetto_branch=None, skipNpm=False, skipTest=False, development=False): cprint("Installing requirements") + print(workspace_branch) execute(cmd=['pip', 'install', '-r', 'requirements.txt'], cwd=ROOT_DIR) if not os.path.exists(DEPS_DIR): @@ -107,15 +117,25 @@ def main(netpyne_branch, workspace_branch, pygeppetto_branch=None, jupyter_geppe execute(cmd=['pip', 'install', '-e', '.'], cwd=ROOT_DIR) else: + + if netpyne_branch and netpyne_branch != 'master': + cprint("Installing netpyne") + clone(repository=NETPYNE, branch_or_tag=netpyne_branch) + execute(cmd=['pip', 'install', '-e', '.'], cwd=os.path.join(DEPS_DIR, NETPYNE_DIR)) # install requirements + if netpyne_branch and netpyne_branch != 'master': + cprint("Installing netpyne") + clone(repository=NETPYNE, branch_or_tag=netpyne_branch) + execute(cmd=['pip', 'install', '-e', '.'], cwd=os.path.join(DEPS_DIR, NETPYNE_DIR)) cprint("Installing UI python package...") execute(cmd=['pip', 'install', '-e', '.', '--no-deps'], cwd=ROOT_DIR) os.chdir(ROOT_DIR) - cprint("Cloning workspace") - clone(repository=WORKSPACE, branch_or_tag=workspace_branch, folder=WORKSPACE_DIR, cwdp=ROOT_DIR) - cprint("Compiling workspace modules") - compile_mod() + if workspace_branch: + cprint("Cloning workspace") + clone(repository=WORKSPACE, branch_or_tag=workspace_branch, folder=WORKSPACE_DIR, cwdp=ROOT_DIR) + cprint("Compiling workspace modules") + compile_mod() if not skipNpm and os.path.exists(os.path.join(DEPS_DIR, JUPYTER_DIR)): cprint("Building Jupyter Geppetto extension...") diff --git a/webapp/.eslintignore b/webapp/.eslintignore index 1d81b61d..f38f43ea 100644 --- a/webapp/.eslintignore +++ b/webapp/.eslintignore @@ -6,4 +6,5 @@ META-INF/* **/tests/jest-puppeteer/**/*.js **/*min.js geppetto-client/** -node_modules/** \ No newline at end of file +node_modules/** +webpack.*.js diff --git a/webapp/.eslintrc.js b/webapp/.eslintrc.js index f95a2637..e8a4ea8d 100644 --- a/webapp/.eslintrc.js +++ b/webapp/.eslintrc.js @@ -10,7 +10,52 @@ module.exports = { // Tweak some rules to our preferences 'space-before-function-paren': ['error', 'always'], + 'react/prop-types': 1, 'no-console': 0, + 'import/extensions': 0, + 'max-len': ['warn', { code: 140 }], + 'no-eval': 1, + eqeqeq: 1, + 'no-plusplus': 1, + 'react/destructuring-assignment': 1, + 'no-param-reassign': 1, + 'vars-on-top': 1, + 'no-use-before-define': 1, + 'no-unused-vars': 1, + 'import/no-unresolved': 1, + 'import/named': 1, + 'react/jsx-props-no-spreading': 1, + 'no-nested-ternary': 1, + 'no-return-assign': 1, + 'react/no-array-index-key': 1, + 'react/sort-comp': 1, + 'consistent-return': 1, + 'import/prefer-default-export': 1, + 'react/no-unused-state': 1, + 'jsx-a11y/alt-text': 1, + 'no-restricted-syntax': 1, + 'no-underscore-dangle': 1, + 'no-restricted-globals': 1, + camelcase: 1, + 'max-classes-per-file': 1, + 'jsx-a11y/click-events-have-key-events': 1, + 'jsx-a11y/no-static-element-interactions': 1, + 'prefer-destructuring': 1, + 'no-redeclare': 1, + 'import/no-webpack-loader-syntax': 1, + 'global-require': 1, + 'default-case': 1, + 'react/prefer-stateless-function': 1, + 'block-scoped-var': 1, + 'no-var': 1, + 'no-shadow': 1, + 'react/no-did-update-set-state': 1, + 'react/no-string-refs': 1, + 'no-multi-assign': 1, + 'guard-for-in': 1, + 'no-unused-expressions': 1, + 'no-control-regex': 1, + 'no-loop-func': 1, }, globals: { page: true, @@ -23,6 +68,17 @@ module.exports = { CanvasContainer: true, patchRequire: true, window: true, + document: true, + $: true, + IPython: true, + fetch: true, + Blob: true, + FormData: true, + Event: true, + jQuery: true, + define: true, + // geppetto + Instances: true, GEPPETTO: true, }, }; diff --git a/webapp/components/definition/cellRules/NetPyNECellRules.js b/webapp/components/definition/cellRules/NetPyNECellRules.js index adc161a4..52f9549b 100644 --- a/webapp/components/definition/cellRules/NetPyNECellRules.js +++ b/webapp/components/definition/cellRules/NetPyNECellRules.js @@ -86,6 +86,7 @@ export default class NetPyNECellRules extends React.Component { handleNewCellRule (defaultCellRules) { const key = Object.keys(defaultCellRules)[0]; const value = defaultCellRules[key]; + // eslint-disable-next-line react/no-access-state-in-setstate const model = { ...this.state.value }; // Get New Available ID @@ -608,6 +609,7 @@ export default class NetPyNECellRules extends React.Component { name, ]) .then((response) => { + // eslint-disable-next-line react/no-access-state-in-setstate const model = this.state.value; delete model[this.state.selectedCellRule].secs[ this.state.selectedSection @@ -627,6 +629,7 @@ export default class NetPyNECellRules extends React.Component { name, ]) .then((response) => { + // eslint-disable-next-line react/no-access-state-in-setstate const model = this.state.value; delete model[this.state.selectedCellRule].secs[name]; this.setState({ @@ -804,6 +807,7 @@ export default class NetPyNECellRules extends React.Component { break; } default: { + break; } } return 'undefined'; diff --git a/webapp/components/definition/cellRules/SelectCellTemplate.js b/webapp/components/definition/cellRules/SelectCellTemplate.js index ff38d6cd..8b000a25 100644 --- a/webapp/components/definition/cellRules/SelectCellTemplate.js +++ b/webapp/components/definition/cellRules/SelectCellTemplate.js @@ -79,7 +79,6 @@ export default class NetPyNENewPlot extends React.Component { id="emptyCellTemplate" key="Empty" value="Empty" - onClick={() => this.handleSelection('Empty')} > Empty cell diff --git a/webapp/components/definition/cellRules/sections/NetPyNESection.js b/webapp/components/definition/cellRules/sections/NetPyNESection.js index 67f7dc2c..ce33e805 100644 --- a/webapp/components/definition/cellRules/sections/NetPyNESection.js +++ b/webapp/components/definition/cellRules/sections/NetPyNESection.js @@ -276,7 +276,6 @@ export default class NetPyNESection extends React.Component { showLabels style={{ borderRadius: '4px' }} value={this.state.selectedIndex} - showLabels > {bottomNavigationItems} diff --git a/webapp/components/definition/connectivity/NetPyNEConnectivityRules.js b/webapp/components/definition/connectivity/NetPyNEConnectivityRules.js index b1992425..34716a26 100644 --- a/webapp/components/definition/connectivity/NetPyNEConnectivityRules.js +++ b/webapp/components/definition/connectivity/NetPyNEConnectivityRules.js @@ -34,7 +34,7 @@ export default class NetPyNEConnectivityRules extends Component { this.handleRenameChildren = this.handleRenameChildren.bind(this); } - handleToggle = () => this.setState({ drawerOpen: !this.state.drawerOpen }); + handleToggle = () => this.setState((prevState) => ({ drawerOpen: !prevState.drawerOpen })); selectPage (page) { this.setState({ page }); @@ -55,6 +55,7 @@ export default class NetPyNEConnectivityRules extends Component { // Get Key and Value const key = Object.keys(defaultConnectivityRules)[0]; const value = defaultConnectivityRules[key]; + // eslint-disable-next-line react/no-access-state-in-setstate const model = { ...this.state.value }; // Get New Available ID @@ -246,7 +247,7 @@ export default class NetPyNEConnectivityRules extends Component { /> )); - var selectedConnectivityRule = undefined; + var selectedConnectivityRule; if ( this.state.selectedConnectivityRule !== undefined && Object.keys(model) diff --git a/webapp/components/definition/plots/NetPyNEInclude.js b/webapp/components/definition/plots/NetPyNEInclude.js index 07d36fcf..fbf7b8c6 100644 --- a/webapp/components/definition/plots/NetPyNEInclude.js +++ b/webapp/components/definition/plots/NetPyNEInclude.js @@ -67,11 +67,12 @@ export default class NetPyNEInclude extends Component { let answer = ''; if (include.exclusive) { return `${include.exclusive} -- ${data.gids} cells -- all NetStims`; - } if (include.groups.indexOf('allCells') > -1) { + } + if (include.groups.indexOf('allCells') > -1) { if (include.groups.indexOf('allNetStims') == -1) { return `allCells -- ${data.gids} cells`; } - return `${'all' + ' -- '}${data.gids} cells -- all NetStims`; + return `all -- ${data.gids} cells -- all NetStims`; } include.groups.forEach((group) => { if (group != 'allNetStims') { @@ -171,15 +172,15 @@ export default class NetPyNEInclude extends Component { }); if ((open || this.state.mainPopoverOpen) && !(open && this.state.mainPopoverOpen)) { - this.setState({ + this.setState((prevState) => ({ mainPopoverOpen: open, secondPopoverOpen: clone, - anchorEl: target || this.state.anchorEl, - }); + anchorEl: target || prevState.anchorEl, + })); } if (!open) { this.sendToPython(); - this.setState({ label: this.whoIsIncluded(this.state.include, this.state.data) }); + this.setState((prevState) => ({ label: this.whoIsIncluded(prevState.include, prevState.data) })); } }; @@ -195,11 +196,10 @@ export default class NetPyNEInclude extends Component { }); if (!this.checkEqual(clone, this.state.secondPopoverOpen)) { - this.setState({ + this.setState((prevState) => ({ secondPopoverOpen: clone, - anchorEl2: target || this.state.anchorEl, - }); - } else { + anchorEl2: target || prevState.anchorEl, + })); } }; @@ -318,6 +318,7 @@ export default class NetPyNEInclude extends Component { }; handleSecondaryMenusClick = (group, name, item) => { + // eslint-disable-next-line react/no-access-state-in-setstate const clone = { ...this.state.include }; if (group == 'gids') { clone[group].indexOf(item) == -1 ? clone[group].push(item) : clone[group].splice(clone[group].indexOf(item), 1); @@ -330,7 +331,6 @@ export default class NetPyNEInclude extends Component { if (clone.groups.indexOf(name) > -1) { // when selecting individuals, remove population selection clone.groups.splice(clone.groups.indexOf(name), 1); } - } else { } clone.exclusive = false; if (clone.groups.indexOf('allCells') > -1 && name != 'allNetStims') { @@ -342,7 +342,8 @@ export default class NetPyNEInclude extends Component { IsSecondaryMenuChecked = (group, name, index) => { if (group == 'gids') { return this.state.include[group].indexOf(index) > -1; - } if (group == 'popids') { + } + if (group == 'popids') { if (name in this.state.include[group]) { return this.state.include[group][name].indexOf(index) > -1; } diff --git a/webapp/components/definition/populations/Dimensions.js b/webapp/components/definition/populations/Dimensions.js index 798f42ab..8ad00df6 100644 --- a/webapp/components/definition/populations/Dimensions.js +++ b/webapp/components/definition/populations/Dimensions.js @@ -74,8 +74,8 @@ class DimensionsComponent extends Component { updateLayout () { const requests = this.popDimensionsOptions.map((popDimensionsOption) => // FIXME Better to wrap calls rather than directly accessing objects - Utils - .evalPythonMessage(`'${popDimensionsOption.value}' in netpyne_geppetto.netParams.popParams['${this.state.modelName}']`)); + // eslint-disable-next-line implicit-arrow-linebreak + Utils.evalPythonMessage(`'${popDimensionsOption.value}' in netpyne_geppetto.netParams.popParams['${this.state.modelName}']`)); // Get population dimension by asking each for each key Promise.all(requests) diff --git a/webapp/components/definition/populations/NetPyNEPopulations.js b/webapp/components/definition/populations/NetPyNEPopulations.js index 07e4d08a..14a65022 100644 --- a/webapp/components/definition/populations/NetPyNEPopulations.js +++ b/webapp/components/definition/populations/NetPyNEPopulations.js @@ -33,7 +33,7 @@ export default class NetPyNEPopulations extends React.Component { this.handleRenameChildren = this.handleRenameChildren.bind(this); } - handleToggle = () => this.setState({ drawerOpen: !this.state.drawerOpen }); + handleToggle = () => this.setState((prevState) => ({ drawerOpen: !prevState.drawerOpen })); hasSelectedPopulationBeenRenamed (prevState, currentState) { const currentModel = prevState.value; @@ -145,6 +145,7 @@ export default class NetPyNEPopulations extends React.Component { // Get Key and Value const key = Object.keys(defaultPopulationValues)[0]; const value = defaultPopulationValues[key]; + // eslint-disable-next-line react/no-access-state-in-setstate const model = { ...this.state.value }; // Get New Available ID @@ -247,7 +248,7 @@ export default class NetPyNEPopulations extends React.Component { /> )); - var selectedPopulation = undefined; + var selectedPopulation; if ( this.state.selectedPopulation !== undefined && Object.keys(model) diff --git a/webapp/components/definition/stimulationSources/NetPyNEStimulationSources.js b/webapp/components/definition/stimulationSources/NetPyNEStimulationSources.js index bb277db8..4ee95d64 100644 --- a/webapp/components/definition/stimulationSources/NetPyNEStimulationSources.js +++ b/webapp/components/definition/stimulationSources/NetPyNEStimulationSources.js @@ -45,6 +45,7 @@ export default class NetPyNEStimulationSources extends Component { const defaultStimulationSources = { stim_source: { type: 'NetStim' } }; const key = Object.keys(defaultStimulationSources)[0]; const value = defaultStimulationSources[key]; + // eslint-disable-next-line react/no-access-state-in-setstate const model = { ...this.state.value }; const StimulationSourceId = Utils.getAvailableKey(model, key); const newStimulationSource = { diff --git a/webapp/components/definition/stimulationTargets/NetPyNEStimulationTarget.js b/webapp/components/definition/stimulationTargets/NetPyNEStimulationTarget.js index 87558155..590a1828 100644 --- a/webapp/components/definition/stimulationTargets/NetPyNEStimulationTarget.js +++ b/webapp/components/definition/stimulationTargets/NetPyNEStimulationTarget.js @@ -57,7 +57,7 @@ export default class NetPyNEStimulationTarget extends React.Component { } async handleStimSourceSelection (selectedStimSourceName) { - return await Utils.evalPythonMessage( + return Utils.evalPythonMessage( `'NetStim' == netpyne_geppetto.netParams.stimSourceParams['${ selectedStimSourceName }']['type']`, @@ -147,7 +147,7 @@ export default class NetPyNEStimulationTarget extends React.Component { }; postProcessMenuItems = (pythonData, selectedStimSourceName) => { - if ((selectedStimSourceName != Object) & (selectedStimSourceName != '')) { + if ((selectedStimSourceName != Object) && (selectedStimSourceName != '')) { this.isStimSourceTypeNetStim(selectedStimSourceName); } return pythonData.map((name) => ( diff --git a/webapp/components/definition/stimulationTargets/NetPyNEStimulationTargets.js b/webapp/components/definition/stimulationTargets/NetPyNEStimulationTargets.js index 27a1d5a7..5a7131cb 100644 --- a/webapp/components/definition/stimulationTargets/NetPyNEStimulationTargets.js +++ b/webapp/components/definition/stimulationTargets/NetPyNEStimulationTargets.js @@ -50,6 +50,7 @@ export default class NetPyNEStimulationTargets extends Component { }; const key = Object.keys(defaultStimulationTargets)[0]; const value = defaultStimulationTargets[key]; + // eslint-disable-next-line react/no-access-state-in-setstate const model = { ...this.state.value }; const StimulationTargetId = Utils.getAvailableKey(model, key); const newStimulationTarget = { diff --git a/webapp/components/definition/synapses/NetPyNESynapses.js b/webapp/components/definition/synapses/NetPyNESynapses.js index aedee597..5a726cff 100644 --- a/webapp/components/definition/synapses/NetPyNESynapses.js +++ b/webapp/components/definition/synapses/NetPyNESynapses.js @@ -43,6 +43,7 @@ export default class NetPyNESynapses extends Component { const defaultSynapses = { Synapse: { mod: 'Exp2Syn' } }; const key = Object.keys(defaultSynapses)[0]; const value = defaultSynapses[key]; + // eslint-disable-next-line react/no-access-state-in-setstate const model = { ...this.state.value }; const SynapseId = Utils.getAvailableKey(model, key); const newSynapse = { name: SynapseId, ...value }; diff --git a/webapp/components/drawer/Drawer.js b/webapp/components/drawer/Drawer.js index c1969959..33b31d2e 100644 --- a/webapp/components/drawer/Drawer.js +++ b/webapp/components/drawer/Drawer.js @@ -37,7 +37,6 @@ const DrawerItem = ({ button key={id} dense - disableGutters disabled={disabled} className={widget ? classes.selected : classes.unselected} diff --git a/webapp/components/general/Dialog.js b/webapp/components/general/Dialog.js index 6f2c413b..c293a38f 100644 --- a/webapp/components/general/Dialog.js +++ b/webapp/components/general/Dialog.js @@ -11,8 +11,8 @@ import Link from '@material-ui/core/Link'; import Icon from '@material-ui/core/Icon'; import { withStyles } from '@material-ui/core/styles'; import { secondaryColor, bgLight } from '../../theme'; -import logo_netpyne from '../../static/netpyne-logo_white.png'; -import logo_metacell from '../../static/metacell_new.png'; +import logoNetpyne from '../../static/netpyne-logo_white.png'; +import logoMetaCell from '../../static/metacell_new.png'; const styles = (theme) => ({ paper: { @@ -23,11 +23,17 @@ const styles = (theme) => ({ const AboutContent = withStyles(styles)(({ classes }) => ( - + - + NetPyNE-UI v0.7.0 - + + + NetPyNE v01.0.0.2 + + + NEURON v8.0.0 + @@ -58,7 +64,10 @@ const AboutContent = withStyles(styles)(({ classes }) => ( - NetPyNE-UI is being developed in collaboration with: + NetPyNE-UI is being developed by the State University of New York Downstate ( + Dura-Bernal Lab + ) + in collaboration with: ( width: 150, padding: '10px', }} - src={logo_metacell} + src={logoMetaCell} /> diff --git a/webapp/components/general/ExpansionPanel.js b/webapp/components/general/ExpansionPanel.js index f8cf2708..76b6807d 100644 --- a/webapp/components/general/ExpansionPanel.js +++ b/webapp/components/general/ExpansionPanel.js @@ -10,7 +10,10 @@ import Tooltip from './Tooltip'; const styles = ({ spacing }) => ({ button: { marginRight: 0 } }); class NetPyNEAccordion extends Component { - state = { expanded: false }; + constructor (props) { + super(props); + this.state = { expanded: false }; + } render () { const { @@ -24,7 +27,7 @@ class NetPyNEAccordion extends Component { this.setState({ expanded: !this.state.expanded }), + onClick: () => this.setState((prevState) => ({ expanded: !prevState.expanded })), className: classes.button, }} expandIcon={( diff --git a/webapp/components/general/Filter.js b/webapp/components/general/Filter.js index 8f0b1924..f407a421 100644 --- a/webapp/components/general/Filter.js +++ b/webapp/components/general/Filter.js @@ -18,7 +18,10 @@ const styles = ({ spacing, shape }) => ({ listbox: { color: 'white', maxHeight: '20vh' }, }); class Filter extends Component { - state = { open: false } + constructor (props) { + super(props); + this.state = { open: false }; + } render () { const { @@ -31,7 +34,6 @@ class Filter extends Component { onClose={() => this.setState({ open: false })} onOpen={() => this.setState({ open: true })} className={classes.filter} - clearOnEscape autoComplete openOnFocus diff --git a/webapp/components/general/HTMLViewer.js b/webapp/components/general/HTMLViewer.js index c078e83f..63738554 100644 --- a/webapp/components/general/HTMLViewer.js +++ b/webapp/components/general/HTMLViewer.js @@ -27,17 +27,13 @@ class CustomHTMLViewer extends Component { this.resizeIfNeeded(); } - componentWillUnmount () { - clearTimeout(this.timer); - window.removeEventListener('resize', this.delayedResize); - } - componentDidUpdate () { this.resizeIfNeeded(); } - wasParentResized (dimensions) { - return dimensions.width !== this.dimensions.width || dimensions.height !== this.dimensions.height; + componentWillUnmount () { + clearTimeout(this.timer); + window.removeEventListener('resize', this.delayedResize); } getParentSize () { @@ -52,6 +48,10 @@ class CustomHTMLViewer extends Component { return this.containerRef.current.children[0].children[0].children[0]; } + wasParentResized (dimensions) { + return dimensions.width !== this.dimensions.width || dimensions.height !== this.dimensions.height; + } + adjustSVGSize () { const svg = this.getSvgComponent(); if (svg) { diff --git a/webapp/components/general/List.js b/webapp/components/general/List.js index 48af67ed..8cc08327 100644 --- a/webapp/components/general/List.js +++ b/webapp/components/general/List.js @@ -41,11 +41,12 @@ class ListComponent extends Component { break; case 'list(list(float))': var valid = true; - value.split(',').forEach((element) => { - if (!element.match(/^-?\d*(\.\d+)?$/)) { - valid = false; - } - }); + value.split(',') + .forEach((element) => { + if (!element.match(/^-?\d*(\.\d+)?$/)) { + valid = false; + } + }); if (value.endsWith(',')) { valid = false; } @@ -63,35 +64,36 @@ class ListComponent extends Component { valid = false; } else if ( (value.match(/{/g) || []).length != 1 - || (value.match(/}/g) || []).length != 1 + || (value.match(/}/g) || []).length != 1 ) { valid = false; } else if ( value.indexOf('{') > value.indexOf('}') - || !value.endsWith('}') + || !value.endsWith('}') ) { valid = false; } else { const subDict = value.match(/\{(.*?)\}/)[1]; if ( (subDict.match(/:/g) || []).length - 1 - != (subDict.match(/,/g) || []).length + != (subDict.match(/,/g) || []).length ) { valid = false; } else { - subDict.split(',').forEach((element) => { - if ( - (element.match(/:/g) || []).length != 1 - || element.startsWith(':') - || element.endsWith(':') - ) { - valid = false; - } - }); + subDict.split(',') + .forEach((element) => { + if ( + (element.match(/:/g) || []).length != 1 + || element.startsWith(':') + || element.endsWith(':') + ) { + valid = false; + } + }); const reminder = value.replace(`{${subDict}}`, ''); if ( (reminder.match(/:/g) || []).length != 1 - || !reminder.endsWith(':') + || !reminder.endsWith(':') ) { valid = false; } @@ -106,28 +108,33 @@ class ListComponent extends Component { } getErrorMessage () { - switch (this.props.realType) { + let message; + const { realType } = this.props; + switch (realType) { case 'list(float)': - var message = 'Only float numbers are allowed.'; + message = 'Only float numbers are allowed.'; break; case 'list(list(float))': - var message = 'Only comma separated float numbers are allowed.'; + message = 'Only comma separated float numbers are allowed.'; break; case 'dict': - var message = 'Key:Value pairs must be separated by colon : '; + message = 'Key:Value pairs must be separated by colon : '; break; case 'dict(dict)': - var message = 'Incorrect format. Example -> v_soma : { sec: soma, loc: 0.5, var: v}'; + message = 'Incorrect format. Example -> v_soma : { sec: soma, loc: 0.5, var: v}'; break; default: - var message = 'No a valid value'; + message = 'No a valid value'; break; } return message; } handleNewItemChange (event) { - this.setState({ newItemValue: event.target.value, newItemErrorText: '' }); + this.setState({ + newItemValue: event.target.value, + newItemErrorText: '', + }); } addChild () { @@ -190,7 +197,10 @@ class ListComponent extends Component { convertToPython (children) { // Update State - this.setState({ children, newItemValue: '' }); + this.setState({ + children, + newItemValue: '', + }); if (this.props.realType == 'dict' || this.props.realType == 'dict(dict)') { var newValue = children; @@ -247,32 +257,33 @@ class ListComponent extends Component { } render () { - const childrenWithExtraProp = Object.keys(this.state.children).map( - (key, index) => { - key = key.toString(); - if (this.props.realType == 'dict') { - var value = `${key} : ${JSON.stringify(this.state.children[key])}`; - } else if (this.props.realType == 'dict(dict)') { - var value = `${key - }: ${ - JSON.stringify(this.state.children[key]) - .replace(/["']/g, '') - .replace(/[:]/g, ': ') - .replace(/[,]/g, ', ')}`; - } else { - var value = this.state.children[key]; - } - return ( - this.removeChild(key)} - color="primary" - /> - ); - }, - ); + const childrenWithExtraProp = Object.keys(this.state.children) + .map( + (key, index) => { + key = key.toString(); + if (this.props.realType == 'dict') { + var value = `${key} : ${JSON.stringify(this.state.children[key])}`; + } else if (this.props.realType == 'dict(dict)') { + var value = `${key + }: ${ + JSON.stringify(this.state.children[key]) + .replace(/["']/g, '') + .replace(/[:]/g, ': ') + .replace(/[,]/g, ', ')}`; + } else { + var value = this.state.children[key]; + } + return ( + this.removeChild(key)} + color="primary" + /> + ); + }, + ); const { classes } = this.props; return ( diff --git a/webapp/components/general/NetPyNECoordsRange.js b/webapp/components/general/NetPyNECoordsRange.js index eacf42c4..d206efb8 100644 --- a/webapp/components/general/NetPyNECoordsRange.js +++ b/webapp/components/general/NetPyNECoordsRange.js @@ -22,6 +22,11 @@ export default class NetPyNECoordsRange extends Component { this.updateTimer = setTimeout(updateMethod, 1000); } + componentDidMount () { + this._isMounted = true; + this.updateLayout(); + } + componentDidUpdate (prevProps, prevState) { if (this.props.name != prevProps.name) { this.triggerUpdate(() => { @@ -43,20 +48,23 @@ export default class NetPyNECoordsRange extends Component { } } - componentDidMount () { - this._isMounted = true; - this.updateLayout(); - } - updateLayout () { - const message = `netpyne_geppetto.${this.props.model}['${this.props.name}']${(this.props.conds != undefined) ? `['${this.props.conds}']` : ''}`; + const { + items, + model, + conds, + name, + } = this.props; + + const message = `netpyne_geppetto.${model}['${name}']${(conds !== undefined) + ? `['${conds}']` : ''}`; Utils - .evalPythonMessage(`[key in ${message} for key in ['${this.props.items[0].value}', '${this.props.items[1].value}']]`) + .evalPythonMessage(`[key in ${message} for key in ['${items[0].value}', '${items[1].value}']]`) .then((response) => { if (response[0] && this._isMounted === true) { - this.setState({ rangeType: this.props.items[0].value }); + this.setState({ rangeType: items[0].value }); } else if (response[1] && this._isMounted === true) { - this.setState({ rangeType: this.props.items[1].value }); + this.setState({ rangeType: items[1].value }); } else if (this._isMounted === true) { this.setState({ rangeType: undefined }); } @@ -107,7 +115,7 @@ export default class NetPyNECoordsRange extends Component { convertToPython={(state) => { if (!state[state.lastUpdated].toString() .endsWith('.') - && ((!isNaN(parseFloat(state[min]))) && (!isNaN(parseFloat(state[max]))))) { + && ((!isNaN(parseFloat(state[min]))) && (!isNaN(parseFloat(state[max]))))) { return [parseFloat(state[min]), parseFloat(state[max])]; } }} diff --git a/webapp/components/general/NetPyNEField.js b/webapp/components/general/NetPyNEField.js index eb3bd89f..76a3ea0f 100644 --- a/webapp/components/general/NetPyNEField.js +++ b/webapp/components/general/NetPyNEField.js @@ -138,19 +138,18 @@ export default class NetPyNEField extends Component { extraProps.type = type; } - if (name == 'PythonControlledControl') { - var realType = Utils.getMetadataField(this.props.id, 'type'); + let realType; + if (name === 'PythonControlledControl') { + realType = Utils.getMetadataField(this.props.id, 'type'); extraProps.realType = realType; } - const hintText = Utils.getMetadataField(this.props.id, 'hintText'); - - let default_value = Utils.getMetadataField(this.props.id, 'default'); - if (default_value) { + let defaultValue = Utils.getMetadataField(this.props.id, 'default'); + if (defaultValue) { if (realType === 'dict' || realType === 'dict(dict)') { - default_value = JSON.parse(default_value); + defaultValue = JSON.parse(defaultValue); } - extraProps.default = default_value; + extraProps.default = defaultValue; } const options = Utils.getMetadataField(this.props.id, 'options'); diff --git a/webapp/components/general/NetPyNEHome.js b/webapp/components/general/NetPyNEHome.js index a3bc2426..8cc23873 100644 --- a/webapp/components/general/NetPyNEHome.js +++ b/webapp/components/general/NetPyNEHome.js @@ -1,5 +1,4 @@ import React from 'react'; -import Icon from '@material-ui/core/Icon'; import { makeStyles } from '@material-ui/core/styles'; import Tooltip from './Tooltip'; @@ -33,5 +32,3 @@ export default ({ handleClick, selection }) => { ); }; - -const styles = {}; diff --git a/webapp/components/general/NetPyNEIcons.js b/webapp/components/general/NetPyNEIcons.js index ef0433a2..36710a83 100644 --- a/webapp/components/general/NetPyNEIcons.js +++ b/webapp/components/general/NetPyNEIcons.js @@ -23,7 +23,6 @@ export function MechIcon (props) { - ); } @@ -32,6 +31,5 @@ export function ArrowRightIcon (props) { - ); } diff --git a/webapp/components/general/NetPyNEPythonConsole.js b/webapp/components/general/NetPyNEPythonConsole.js index ffe17bae..d519d05d 100644 --- a/webapp/components/general/NetPyNEPythonConsole.js +++ b/webapp/components/general/NetPyNEPythonConsole.js @@ -4,19 +4,22 @@ import PythonConsole from '@geppettoengine/geppetto-client/js/components/interface/pythonConsole/PythonConsole'; export class NetPyNEPythonConsole extends Component { - componentDidMount () { - } - shouldComponentUpdate () { + shouldComponentUpdate() { return false; } - componentWillUnmount () { - console.info('unmounting python console'); + componentWillUnmount() { + console.info("unmounting python console"); + } + + componentDidMount() { + } - render () { - return ; + render() { + const notebookName = GEPPETTO_CONFIGURATION.notebookName || "notebook.ipynb"; + return } } diff --git a/webapp/components/general/NetPyNEThumbnail.js b/webapp/components/general/NetPyNEThumbnail.js index eab82311..709d7964 100644 --- a/webapp/components/general/NetPyNEThumbnail.js +++ b/webapp/components/general/NetPyNEThumbnail.js @@ -50,7 +50,9 @@ export default class NetPyNEThumbnail extends React.Component { paramPath, paramName: name, }); - tonDelete && onDelete(); + if (onDelete) { + onDelete(); + } } this.setState({ dialogOpen: false }); } diff --git a/webapp/components/general/PythonControlledCapability.js b/webapp/components/general/PythonControlledCapability.js index 20f25c89..5ce8abc3 100644 --- a/webapp/components/general/PythonControlledCapability.js +++ b/webapp/components/general/PythonControlledCapability.js @@ -317,7 +317,11 @@ define((require) => { break; default: wrappedComponentProps.onChange = this.handleChange; - wrappedComponentProps.value = (typeof this.state.value === 'object' && this.state.value !== null && !Array.isArray(this.state.value)) ? JSON.stringify(this.state.value) : this.state.value; + wrappedComponentProps.value = (typeof this.state.value === 'object' + && this.state.value !== null + && !Array.isArray(this.state.value)) + ? JSON.stringify(this.state.value) + : this.state.value; // Fix case with multiple values: need to set an empty list in case the value is undefined wrappedComponentProps.value = (wrappedComponentProps.multiple && wrappedComponentProps.value !== undefined @@ -452,7 +456,9 @@ define((require) => { wrappedComponentProps.id = cleanAttributeValue(wrappedComponentProps.id); wrappedComponentProps.onChange = this.handleChange; - wrappedComponentProps.value = wrappedComponentProps.multiple && this.state.value !== undefined && !this.state.value ? [] : this.state.value; + wrappedComponentProps.value = wrappedComponentProps.multiple + && this.state.value !== undefined + && !this.state.value ? [] : this.state.value; delete wrappedComponentProps.model; delete wrappedComponentProps.postProcessItems; delete wrappedComponentProps.validate; @@ -486,5 +492,5 @@ function getNameFromWrappedComponent (WrappedComponent) { * Due to close integration with Python commands, characters []'". can be part of an id attribute. */ function cleanAttributeValue (value) { - return value.replace(/[\[\]'.]+/g, ''); + return value.replace(/[[\]'.]+/g, ''); } diff --git a/webapp/components/general/RulePath.js b/webapp/components/general/RulePath.js index 0c260c9c..faef9cc2 100644 --- a/webapp/components/general/RulePath.js +++ b/webapp/components/general/RulePath.js @@ -25,10 +25,13 @@ const styles = ({ }); class RulePath extends Component { - state = { open: false }; - textAreaRef = createRef(); + constructor (props) { + super(props); + this.state = { open: false }; + } + copyCodeToClipboard = () => { const el = this.textAreaRef.current; diff --git a/webapp/components/general/Select.js b/webapp/components/general/Select.js index 61ee8a74..170c738c 100644 --- a/webapp/components/general/Select.js +++ b/webapp/components/general/Select.js @@ -1,9 +1,9 @@ -import React, { Component } from 'react'; +import React from 'react'; import InputLabel from '@material-ui/core/InputLabel'; import FormControl from '@material-ui/core/FormControl'; import MuiSelect from '@material-ui/core/Select'; -export default class Select extends Component { +class Select extends React.Component { render () { let value = this.props.value || ''; if (this.props.multiple && value.constructor.name != 'Array') { @@ -30,3 +30,5 @@ export default class Select extends Component { ); } } + +export default Select; diff --git a/webapp/components/general/Slider.js b/webapp/components/general/Slider.js deleted file mode 100644 index a04909e9..00000000 --- a/webapp/components/general/Slider.js +++ /dev/null @@ -1,57 +0,0 @@ -import React, { Component } from 'react'; -import Slider from '@material-ui/core/Slider'; -import TextField from '@material-ui/core/TextField'; - -/** - * The slider bar can have a set minimum and maximum, and the value can be - * obtained through the value parameter fired on an onChange event. - */ -export default class NetPyNESlider extends Component { - constructor (props) { - super(props); - this.state = { value: 0.5 }; - } - - componentDidUpdate (prevProps, prevState) { - if (this.props.value != undefined && prevProps.value != this.props.value) { - this.setState({ value: this.props.value }); - } - } - - handleSlider = (event, value) => { - this.setState({ value }); - - this.props.onChange(event, null, value); - }; - - render () { - return ( -
-

- {this.props.label} -

- - - this.handleSlider(event, event.target.value)} - /> - -
- ); - } -} diff --git a/webapp/components/general/Splash.js b/webapp/components/general/Splash.js index 4e78356c..c18d8cd3 100644 --- a/webapp/components/general/Splash.js +++ b/webapp/components/general/Splash.js @@ -1,12 +1,9 @@ -import React, { Component } from 'react'; +import React from 'react'; import Box from '@material-ui/core/Box'; -export default class Splash extends Component { - render () { - return ( - - - - ); - } -} +const Splash = () => ( + + + +); +export default Splash; diff --git a/webapp/components/general/SplitButton.js b/webapp/components/general/SplitButton.js new file mode 100644 index 00000000..ce895eb6 --- /dev/null +++ b/webapp/components/general/SplitButton.js @@ -0,0 +1,91 @@ +import React from 'react'; +import Grid from '@material-ui/core/Grid'; +import Button from '@material-ui/core/Button'; +import ButtonGroup from '@material-ui/core/ButtonGroup'; +import ArrowDropDownIcon from '@material-ui/icons/ArrowDropDown'; +import ClickAwayListener from '@material-ui/core/ClickAwayListener'; +import Grow from '@material-ui/core/Grow'; +import Paper from '@material-ui/core/Paper'; +import Popper from '@material-ui/core/Popper'; +import MenuItem from '@material-ui/core/MenuItem'; +import MenuList from '@material-ui/core/MenuList'; + +export default function SplitButton (props) { + const { + options, handleClick, icon, skipIconFor, + } = props; + const [open, setOpen] = React.useState(false); + const anchorRef = React.useRef(null); + const [selectedIndex, setSelectedIndex] = React.useState(0); + + const handleItemClick = () => { + handleClick(options[selectedIndex]); + }; + + const handleMenuItemClick = (index) => { + setSelectedIndex(index); + setOpen(false); + }; + + const handleToggle = () => { + setOpen((prevOpen) => !prevOpen); + }; + + const handleClose = (event) => { + if (anchorRef.current && anchorRef.current.contains(event.target)) { + return; + } + setOpen(false); + }; + + return ( + + + + + + + + {({ TransitionProps, placement }) => ( + + + + + {options.map((option, index) => ( + { + handleMenuItemClick(index); + }} + > + {option} + + ))} + + + + + )} + + + + ); +} diff --git a/webapp/components/index.js b/webapp/components/index.js index f4efa798..6ab2bc24 100644 --- a/webapp/components/index.js +++ b/webapp/components/index.js @@ -123,7 +123,9 @@ export const NetPyNECellRule = connect( updates: state.general.updates, }), (dispatch) => ({ - openTopbarDialog: (cellTemplateName) => dispatch(openTopbarDialog(TOPBAR_CONSTANTS.IMPORT_CELL_TEMPLATE, { cellRuleName: cellTemplateName })), + openTopbarDialog: (cellTemplateName) => dispatch( + openTopbarDialog(TOPBAR_CONSTANTS.IMPORT_CELL_TEMPLATE, { cellRuleName: cellTemplateName }), + ), updateCards: () => dispatch(updateCards), }), )(_NetPyNECellRule); @@ -301,7 +303,11 @@ export const Dialog = connect( export const SelectCellTemplate = connect( null, - (dispatch) => ({ openTopbarDialog: (cellTemplateName) => dispatch(openTopbarDialog(TOPBAR_CONSTANTS.IMPORT_CELL_TEMPLATE, { cellRuleName: cellTemplateName })) }), + (dispatch) => ({ + openTopbarDialog: (cellTemplateName) => dispatch( + openTopbarDialog(TOPBAR_CONSTANTS.IMPORT_CELL_TEMPLATE, { cellRuleName: cellTemplateName }), + ), + }), )(_SelectCellTemplate); // ---------------------------------------------------------------------------------------- // diff --git a/webapp/components/instantiation/NetPyNEInstantiated.js b/webapp/components/instantiation/NetPyNEInstantiated.js index ed4b0dd4..fd91c0d2 100644 --- a/webapp/components/instantiation/NetPyNEInstantiated.js +++ b/webapp/components/instantiation/NetPyNEInstantiated.js @@ -28,6 +28,19 @@ export default class NetPyNEInstantiated extends React.Component { this.controlPanelToggle = this.controlPanelToggle.bind(this); } + componentDidMount () { + this.canvasRef.current.engine.setLinesThreshold(10000); + this.canvasRef.current.displayAllInstances(); + this.updateBtnsWithTheme('', this.state.canvasBtnCls); + window.addEventListener('resize', this.delayedResize.bind(this)); + this.resizeIfNeeded(); + this.updateInstances(); + + GEPPETTO.on(GEPPETTO.Events.Control_panel_close, () => { + this.setState({ bringItToFront: 0 }); + }); + } + componentDidUpdate (prevProps, prevState) { this.resizeIfNeeded(); const { theme } = this.props; @@ -43,27 +56,23 @@ export default class NetPyNEInstantiated extends React.Component { } } - componentDidMount () { - this.canvasRef.current.engine.setLinesThreshold(10000); - this.canvasRef.current.displayAllInstances(); - this.updateBtnsWithTheme('', this.state.canvasBtnCls); - window.addEventListener('resize', this.delayedResize.bind(this)); - this.resizeIfNeeded(); - this.updateInstances(); - - GEPPETTO.on(GEPPETTO.Events.Control_panel_close, () => { - this.setState({ bringItToFront: 0 }); - }); - } - componentWillUnmount () { GEPPETTO.off(GEPPETTO.Events.Control_panel_close); clearTimeout(this.timer); window.removeEventListener('resize', this.delayedResize); } + updateBtnsWithTheme = (removeClass, addClass) => { + const element = document.getElementById('CanvasContainer_component'); + if (removeClass) { + element.classList.remove(removeClass); + } + element.classList.add(addClass); + this.setState({ canvasBtnCls: addClass }); + }; + updateInstances () { - if (Instances.network) { + if (window.Instances != null && window.Instances.network) { // update canvas only if there are instances to show this.canvasRef.current.engine.setLinesThreshold(25000); this.canvasRef.current.engine.updateSceneWithNewInstances( @@ -82,7 +91,7 @@ export default class NetPyNEInstantiated extends React.Component { } resizeCanvas () { - this.setState({ update: this.state.update++ }); + this.setState((prevState) => ({ update: prevState.update + 1 })); } resizeIfNeeded () { @@ -105,17 +114,11 @@ export default class NetPyNEInstantiated extends React.Component { if (this.canvasRef.current === null) { return false; } + // eslint-disable-next-line react/no-find-dom-node const node = ReactDOM.findDOMNode(this); return node.parentNode.getBoundingClientRect(); } - updateBtnsWithTheme = (removeClass, addClass) => { - const element = document.getElementById('CanvasContainer_component'); - removeClass && element.classList.remove(removeClass); - element.classList.add(addClass); - this.setState({ canvasBtnCls: addClass }); - }; - controlPanelToggle () { if (!this.state.controlPanelInitialized) { this.setState({ controlPanelInitialized: true }); diff --git a/webapp/components/topbar/SwitchPageButton.js b/webapp/components/topbar/SwitchPageButton.js index d66aacb0..a126a853 100644 --- a/webapp/components/topbar/SwitchPageButton.js +++ b/webapp/components/topbar/SwitchPageButton.js @@ -2,18 +2,22 @@ import React, { Component } from 'react'; import { withStyles } from '@material-ui/core/styles'; import Button from '@material-ui/core/Button'; -import IconButton from '@material-ui/core/IconButton'; -import { Tooltip } from 'netpyne/components'; import Icon from '../general/NetPyNEIcons'; import { TOPBAR_CONSTANTS, MODEL_STATE } from '../../constants'; +import SplitButton from '../general/SplitButton'; const styles = ({ palette, - shape, spacing, - typography, }) => ({ - container: {}, + container: { + display: 'flex', + height: '100%', + '& .MuiButton-root': { + borderRadius: 0, + marginBottom: 0, + }, + }, button: { textTransform: 'uppercase', letterSpacing: 2, @@ -24,68 +28,34 @@ const styles = ({ icon: { color: palette.common.white }, }); +const CREATE_NETWORK = 'CREATE NETWORK'; +const CREATE_AND_SIMULATE = 'CREATE AND SIMULATE'; +const SIMULATE = 'SIMULATE'; +const BACK_TO_EDIT = 'BACK TO EDIT'; + +const editOptions = [CREATE_NETWORK, CREATE_AND_SIMULATE, SIMULATE]; +const exploreOptions = [SIMULATE, CREATE_AND_SIMULATE]; + class SwitchPageButton extends Component { - handleClick = (event) => { - const instantiate = this.props.automaticInstantiation || this.props.modelState === MODEL_STATE.NOT_INSTANTIATED; - if (!this.props.editModelPage) { - this.props.switchToEditModelPage(); - } else if (instantiate && this.props.automaticSimulation) { - this.props.createAndSimulateNetwork(); - } else if (instantiate) { + constructor (props) { + super(props); + this.handleClick = this.handleClick.bind(this); + } + + handleClick = (selectedOption) => { + if (selectedOption === CREATE_NETWORK) { this.props.createNetwork(); + } else if (selectedOption === SIMULATE) { + this.props.simulateNetwork(); + } else if (selectedOption === CREATE_AND_SIMULATE) { + this.props.createAndSimulateNetwork(); + } else if (selectedOption === BACK_TO_EDIT) { + this.props.switchToEditModelPage(); } else { this.props.showNetwork(); } }; - render () { - const { - classes, - modelState, - editModelPage, - } = this.props; - const disableSimulate = modelState === MODEL_STATE.SIMULATED; - return ( -
- { - editModelPage ? null - - : ( - - - this.props.simulateNetwork()} - disabled={disableSimulate} - style={{ opacity: disableSimulate ? 0.5 : 1 }} - > - - - - - - ) - } - - -
- ); - } - getExploreLabel () { const { automaticInstantiation, @@ -103,6 +73,50 @@ class SwitchPageButton extends Component { } return TOPBAR_CONSTANTS.EXPLORE_EXISTING_NETWORK; } + + render () { + const { + classes, + editModelPage, + } = this.props; + return ( +
+ {editModelPage + ? ( + this.handleClick(selectedOption)} + icon={( + + + + )} + skipIconFor={CREATE_NETWORK} + /> + ) + : ( + <> + + this.handleClick(selectedOption)} + icon={( + + + + )} + /> + + )} +
+ ); + } } export default withStyles(styles)(SwitchPageButton); diff --git a/webapp/components/topbar/Topbar.js b/webapp/components/topbar/Topbar.js index 0dbe5433..b6549aea 100644 --- a/webapp/components/topbar/Topbar.js +++ b/webapp/components/topbar/Topbar.js @@ -32,10 +32,14 @@ const styles = () => ({ }); class Topbar extends Component { - state = { openSnackBar: false }; - snackBarMessage = ''; + constructor (props) { + super(props); + this.state = { openSnackBar: false }; + this.menuHandler = this.menuHandler.bind(this); + } + menuHandler (click) { if (!click) { return; @@ -172,6 +176,9 @@ class Topbar extends Component { /> ); break; + default: + content =
; + break; } } @@ -180,7 +187,7 @@ class Topbar extends Component {
diff --git a/webapp/components/topbar/dialogs/ActionDialog.js b/webapp/components/topbar/dialogs/ActionDialog.js index a1e635ca..8e1969b7 100644 --- a/webapp/components/topbar/dialogs/ActionDialog.js +++ b/webapp/components/topbar/dialogs/ActionDialog.js @@ -17,7 +17,10 @@ const styles = () => ({ }); class ActionDialog extends React.Component { - state = { hide: !this.props.openErrorDialogBox && !this.props.openDialog }; + constructor (props) { + super(props); + this.state = { hide: !this.props.openErrorDialogBox && !this.props.openDialog }; + } performAction = () => { if (this.props.command) { diff --git a/webapp/components/topbar/dialogs/ImportCellParams.js b/webapp/components/topbar/dialogs/ImportCellParams.js index b8c79e6d..5d7ae803 100644 --- a/webapp/components/topbar/dialogs/ImportCellParams.js +++ b/webapp/components/topbar/dialogs/ImportCellParams.js @@ -75,7 +75,7 @@ class ImportCellParams extends React.Component { newState.modFolder = fieldValue.path; break; default: - throw ('Not a valid parameter!'); + throw Error('Not a valid parameter!'); } } this.setState(newState); diff --git a/webapp/components/topbar/dialogs/ImportExportHLS.js b/webapp/components/topbar/dialogs/ImportExportHLS.js index 8afd9c3a..533ebfa9 100644 --- a/webapp/components/topbar/dialogs/ImportExportHLS.js +++ b/webapp/components/topbar/dialogs/ImportExportHLS.js @@ -120,14 +120,14 @@ class ImportExportHLS extends React.Component { newState.loadMod = true; break; default: - throw ('Not a valid parameter!'); + throw Error('Not a valid parameter!'); } } this.setState({ ...newState }); } getDirAndModuleFromPath (fullpath) { - const fileName = fullpath.replace(/^.*[\\\/]/, ''); + const fileName = fullpath.replace(/^.*[\\/]/, ''); const moduleName = fileName.replace(/\.[^/.]+$/, ''); const dirPath = fullpath.split(fileName) .slice(0, -1) diff --git a/webapp/components/topbar/dialogs/LoadFile.js b/webapp/components/topbar/dialogs/LoadFile.js index af100ece..483a091c 100644 --- a/webapp/components/topbar/dialogs/LoadFile.js +++ b/webapp/components/topbar/dialogs/LoadFile.js @@ -74,7 +74,7 @@ class LoadFile extends React.Component { closeExplorerDialog (fieldValue) { const newState = { explorerDialogOpen: false }; if (fieldValue) { - const fileName = fieldValue.path.replace(/^.*[\\\/]/, ''); + const fileName = fieldValue.path.replace(/^.*[\\/]/, ''); const path = fieldValue.path .split(fileName) .slice(0, -1) @@ -89,7 +89,7 @@ class LoadFile extends React.Component { newState.jsonPath = path; break; default: - throw 'Not a valid parameter!'; + throw Error('Not a valid parameter!'); } } this.setState(newState); @@ -211,7 +211,7 @@ class LoadFile extends React.Component { noBackground label="Compile mod files" checked={this.state.compileMod} - onChange={() => this.setState({ compileMod: !this.state.compileMod })} + onChange={() => this.setState((prevState) => ({ compileMod: !prevState.compileMod }))} /> diff --git a/webapp/components/topbar/dialogs/UploadDownloadFiles.js b/webapp/components/topbar/dialogs/UploadDownloadFiles.js index 80d0eef2..7e6a8c69 100644 --- a/webapp/components/topbar/dialogs/UploadDownloadFiles.js +++ b/webapp/components/topbar/dialogs/UploadDownloadFiles.js @@ -15,6 +15,8 @@ import { withStyles } from '@material-ui/core/styles'; import InputAdornment from '@material-ui/core/InputAdornment'; import FileBrowser from '../../general/FileBrowser'; +const ACCEPTED_EXTENSIONS = '.py .zip .gz .tar.gz .pdf .txt .xls .png .jpeg .json'; + const styles = ({ spacing, typography, @@ -57,6 +59,13 @@ class UploadDownloadFile extends React.Component { this.message = ''; } + onUploadFileArrayChange = (event) => { + const { files } = event.target; + if (this.maxSelectFile(files) && this.checkMimeType(files) && this.checkFileSize(files)) { + this.setState({ uploadFiles: files }); + } + }; + initialState () { return { open: true, @@ -71,7 +80,6 @@ class UploadDownloadFile extends React.Component { async uploadFiles () { const { uploadFiles } = this.state; const formData = new FormData(); - const data = {}; this.setState({ open: false }); GEPPETTO.trigger(GEPPETTO.Events.Show_spinner, 'UPLOADING FILES'); @@ -185,13 +193,6 @@ class UploadDownloadFile extends React.Component { return true; } - onUploadFileArrayChange = (event) => { - const { files } = event.target; - if (this.maxSelectFile(files) && this.checkMimeType(files) && this.checkFileSize(files)) { - this.setState({ uploadFiles: files }); - } - }; - closeExplorerDialog (selectedNodes) { const state = { explorerDialogOpen: false }; if (selectedNodes) { @@ -216,17 +217,28 @@ class UploadDownloadFile extends React.Component { this.setState({ downloadPaths: [text], downloadPathsDisplayText: text, - }); } render () { + let buttonLabel; + let title; + let content; const { classes } = this.props; const { mode } = this.props; + const { + open, + explorerDialogOpen, + uploadFiles: uploadFiles1, + downloadPathsDisplayText, + downloadPaths, + filterFiles, + } = this.state; + switch (mode) { case 'UPLOAD': - var content = ( + content = (
@@ -239,20 +251,19 @@ class UploadDownloadFile extends React.Component {
-

Accept: .py .zip .gz .tar.gz .pdf .txt .xls .png .jpeg

+

{`Accept: ${ACCEPTED_EXTENSIONS}`}

); - var buttonLabel = 'Upload'; - var title = 'Upload files'; + buttonLabel = 'Upload'; + title = 'Upload files'; break; case 'DOWNLOAD': - var content = ( - + content = ( this.changeDownloadFilePathsDisplayText(event.target.value)} label="Files:" helperText="Select files to download" @@ -272,8 +283,11 @@ class UploadDownloadFile extends React.Component { /> ); - var buttonLabel = 'DOWNLOAD'; - var title = 'Download files'; + buttonLabel = 'DOWNLOAD'; + title = 'Download files'; + break; + + default: break; } @@ -282,7 +296,7 @@ class UploadDownloadFile extends React.Component { this.closeDialog()} > {title} @@ -294,7 +308,9 @@ class UploadDownloadFile extends React.Component { this.closeExplorerDialog(multiSelection)} /> diff --git a/webapp/components/topbar/menuConfiguration.js b/webapp/components/topbar/menuConfiguration.js index fdf5a220..2ab0c0f8 100644 --- a/webapp/components/topbar/menuConfiguration.js +++ b/webapp/components/topbar/menuConfiguration.js @@ -279,14 +279,15 @@ export default { }; export const getViewMenu = (props) => { - const instantiate = props.automaticInstantiation || props.modelState === MODEL_STATE.NOT_INSTANTIATED; const networkAction = () => { - if (instantiate && props.automaticSimulation) { + if (props.automaticInstantiation && props.automaticSimulation) { return createAndSimulateNetwork; } - if (instantiate) { + + if (props.automaticInstantiation) { return createNetwork; } + return showNetwork; }; @@ -331,44 +332,12 @@ export const getModelMenu = (props) => ( }, }, { - label: 'Explore view options', - list: [ - { - label: 'Automatic creation', - icon: props.automaticInstantiation ? checkedIcon : 'fa', - action: { - handlerAction: 'redux', - parameters: [changeAutomaticInstantiation, true], - }, - }, - { - label: 'Manual creation', - icon: !props.automaticInstantiation ? checkedIcon : 'fa', - action: { - handlerAction: 'redux', - parameters: [changeAutomaticInstantiation, false], - }, - }, - , - { - label: 'Automatic simulation', - icon: props.automaticSimulation ? checkedIcon : 'fa', - action: { - handlerAction: 'redux', - parameters: [changeAutomaticSimulation, true], - }, - }, - { - label: 'Manual simulation', - icon: !props.automaticSimulation ? checkedIcon : 'fa', - action: { - handlerAction: 'redux', - parameters: [changeAutomaticSimulation, false], - }, - }, - ], + label: TOPBAR_CONSTANTS.CREATE_AND_SIMULATE_NETWORK, + action: { + handlerAction: 'redux', + parameters: [createAndSimulateNetwork], + }, }, - ] ); diff --git a/webapp/constants.js b/webapp/constants.js index 6002c4c1..f7a9d740 100644 --- a/webapp/constants.js +++ b/webapp/constants.js @@ -59,7 +59,7 @@ export const TOPBAR_CONSTANTS = { CREATE_AND_SIMULATE_NETWORK: 'Create and simulate network', SIMULATE: 'Simulate network', EXPLORE_EXISTING_NETWORK: 'Explore model', - BACK_TO_EDITION: 'Back to edit', + BACK_TO_EDITION: 'BACK TO EDIT', NEW_PAGE: 'NEW_PAGE', }; diff --git a/webapp/package-lock.json b/webapp/package-lock.json index f1d9480e..456ab4ea 100644 --- a/webapp/package-lock.json +++ b/webapp/package-lock.json @@ -1271,6 +1271,17 @@ "object-assign": "^4.1.1", "prop-types": "^15.6.2" } + }, + "react-dom": { + "version": "16.14.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.14.0.tgz", + "integrity": "sha512-1gCeQXDLoIqMgqD3IO2Ah9bnf0w9kzhwN5q4FGnHZ67hBm9yePzB5JJAIQCc8x3pFnNlwFq4RidZggNAAkzWWw==", + "requires": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1", + "prop-types": "^15.6.2", + "scheduler": "^0.19.1" + } } } }, @@ -2436,15 +2447,6 @@ "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=" }, - "asn1": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", - "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", - "dev": true, - "requires": { - "safer-buffer": "~2.1.0" - } - }, "asn1.js": { "version": "5.4.1", "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz", @@ -2484,12 +2486,6 @@ } } }, - "assert-plus": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.2.0.tgz", - "integrity": "sha1-104bh+ev/A24qttwIfP+SBAasjQ=", - "dev": true - }, "assign-symbols": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", @@ -2575,12 +2571,6 @@ "webpack-log": "^1.2.0" } }, - "aws-sign2": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.6.0.tgz", - "integrity": "sha1-FDQt0428yU0OW4fXY81jYSwOeU8=", - "dev": true - }, "axe-core": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.2.0.tgz", @@ -3036,15 +3026,6 @@ "integrity": "sha1-3DQxT05nkxgJP8dgJyUl+UvyXBY=", "dev": true }, - "bcrypt-pbkdf": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", - "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", - "dev": true, - "requires": { - "tweetnacl": "^0.14.3" - } - }, "bezier-js": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/bezier-js/-/bezier-js-4.1.1.tgz", @@ -3195,15 +3176,6 @@ "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=", "dev": true }, - "boom": { - "version": "2.10.1", - "resolved": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz", - "integrity": "sha1-OciRjO/1eZ+D+UkqhI9iWt0Mdm8=", - "dev": true, - "requires": { - "hoek": "2.x.x" - } - }, "bootstrap": { "version": "3.4.1", "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-3.4.1.tgz", @@ -3531,12 +3503,6 @@ "rsvp": "^4.8.4" } }, - "caseless": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.11.0.tgz", - "integrity": "sha1-cVuW6phBWTzDMGeSP17GDr2k99c=", - "dev": true - }, "casperjs": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/casperjs/-/casperjs-1.1.4.tgz", @@ -4371,15 +4337,6 @@ } } }, - "cryptiles": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz", - "integrity": "sha1-O9/s3GCBR8HGcgL6KR59ylnqo7g=", - "dev": true, - "requires": { - "boom": "2.x.x" - } - }, "crypto-browserify": { "version": "3.12.0", "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz", @@ -4899,23 +4856,6 @@ "integrity": "sha512-VvdQIPGdWP0SqFXghj79Wf/5LArmreyMsGLa6FG6iC4t3j7j5s71TrwWmT/4akbDQIqjfACkLZmjXhA7g2oUZw==", "dev": true }, - "dashdash": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", - "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", - "dev": true, - "requires": { - "assert-plus": "^1.0.0" - }, - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", - "dev": true - } - } - }, "dat.gui": { "version": "0.7.7", "resolved": "https://registry.npmjs.org/dat.gui/-/dat.gui-0.7.7.tgz", @@ -5330,16 +5270,6 @@ "resolved": "https://registry.npmjs.org/earcut/-/earcut-2.2.2.tgz", "integrity": "sha512-eZoZPPJcUHnfRZ0PjLvx2qBordSiO8ofC3vt+qACLM95u+4DovnbYNpQtJh0DNsWj8RnxrQytD4WA8gj5cRIaQ==" }, - "ecc-jsbn": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", - "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", - "dev": true, - "requires": { - "jsbn": "~0.1.0", - "safer-buffer": "^2.1.0" - } - }, "edges-to-adjacency-list": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/edges-to-adjacency-list/-/edges-to-adjacency-list-1.0.0.tgz", @@ -6529,84 +6459,6 @@ "resolved": "https://registry.npmjs.org/extract-frustum-planes/-/extract-frustum-planes-1.0.0.tgz", "integrity": "sha1-l9VwP/BWTIw8aDjKxF+ee8UsnvU=" }, - "extract-zip": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-1.5.0.tgz", - "integrity": "sha1-ksz22B73Cp+kwXRxFMzvbYaIpsQ=", - "dev": true, - "requires": { - "concat-stream": "1.5.0", - "debug": "0.7.4", - "mkdirp": "0.5.0", - "yauzl": "2.4.1" - }, - "dependencies": { - "concat-stream": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.5.0.tgz", - "integrity": "sha1-U/fUPFHF5D+ByP3QMyHGMb5o1hE=", - "dev": true, - "requires": { - "inherits": "~2.0.1", - "readable-stream": "~2.0.0", - "typedarray": "~0.0.5" - } - }, - "debug": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-0.7.4.tgz", - "integrity": "sha1-BuHqgILCyxTjmAbiLi9vdX+Srzk=", - "dev": true - }, - "minimist": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", - "dev": true - }, - "mkdirp": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.0.tgz", - "integrity": "sha1-HXMHam35hs2TROFecfzAWkyavxI=", - "dev": true, - "requires": { - "minimist": "0.0.8" - } - }, - "process-nextick-args": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", - "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=", - "dev": true - }, - "readable-stream": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz", - "integrity": "sha1-j5A0HmilPMySh4jaz80Rs265t44=", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", - "isarray": "~1.0.0", - "process-nextick-args": "~1.0.6", - "string_decoder": "~0.10.x", - "util-deprecate": "~1.0.1" - } - }, - "string_decoder": { - "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", - "dev": true - } - } - }, - "extsprintf": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", - "dev": true - }, "falafel": { "version": "2.2.4", "resolved": "https://registry.npmjs.org/falafel/-/falafel-2.2.4.tgz", @@ -6686,15 +6538,6 @@ } } }, - "fd-slicer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.0.1.tgz", - "integrity": "sha1-i1vL2ewyfFBBv5qwI/1nUPEXfmU=", - "dev": true, - "requires": { - "pend": "~1.2.0" - } - }, "fetch-jsonp": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/fetch-jsonp/-/fetch-jsonp-1.1.3.tgz", @@ -7046,12 +6889,6 @@ "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz", "integrity": "sha1-C+4AUBiusmDQo6865ljdATbsG5k=" }, - "forever-agent": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", - "dev": true - }, "form-data": { "version": "2.5.1", "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.1.tgz", @@ -7127,22 +6964,20 @@ "object-assign": "^4.1.1", "prop-types": "^15.6.2" } + }, + "react-dom": { + "version": "16.14.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.14.0.tgz", + "integrity": "sha512-1gCeQXDLoIqMgqD3IO2Ah9bnf0w9kzhwN5q4FGnHZ67hBm9yePzB5JJAIQCc8x3pFnNlwFq4RidZggNAAkzWWw==", + "requires": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1", + "prop-types": "^15.6.2", + "scheduler": "^0.19.1" + } } } }, - "fs-extra": { - "version": "0.26.7", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-0.26.7.tgz", - "integrity": "sha1-muH92UiXeY7at20JGM9C0MMYT6k=", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "jsonfile": "^2.1.0", - "klaw": "^1.0.0", - "path-is-absolute": "^1.0.0", - "rimraf": "^2.2.8" - } - }, "fs-write-stream-atomic": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz", @@ -7185,24 +7020,6 @@ "resolved": "https://registry.npmjs.org/gamma/-/gamma-0.1.0.tgz", "integrity": "sha1-MxVkNAO/J5BsqAqzfDbs6UQO8zA=" }, - "generate-function": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz", - "integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==", - "dev": true, - "requires": { - "is-property": "^1.0.2" - } - }, - "generate-object-property": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/generate-object-property/-/generate-object-property-1.2.0.tgz", - "integrity": "sha1-nA4cQDCM6AT0eDYYuTf6iPmdUNA=", - "dev": true, - "requires": { - "is-property": "^1.0.0" - } - }, "gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -7247,23 +7064,6 @@ "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=" }, - "getpass": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", - "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", - "dev": true, - "requires": { - "assert-plus": "^1.0.0" - }, - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", - "dev": true - } - } - }, "gl-axes3d": { "version": "1.5.3", "resolved": "https://registry.npmjs.org/gl-axes3d/-/gl-axes3d-1.5.3.tgz", @@ -7991,6 +7791,17 @@ "react-is": "^16.6.0", "react-lifecycles-compat": "^3.0.0" } + }, + "redux": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/redux/-/redux-3.7.2.tgz", + "integrity": "sha512-pNqnf9q1hI5HHZRBkj3bAngGZW/JMCmexDlOxw4XagXY2o1327nHH54LoTjiPJ0gizoqPDRqWyX/00g0hD6w+A==", + "requires": { + "lodash": "^4.2.1", + "lodash-es": "^4.2.1", + "loose-envify": "^1.1.0", + "symbol-observable": "^1.0.3" + } } } }, @@ -8040,45 +7851,6 @@ } } }, - "har-validator": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-2.0.6.tgz", - "integrity": "sha1-zcvAgYgmWtEZtqWnyKtw7s+10n0=", - "dev": true, - "requires": { - "chalk": "^1.1.1", - "commander": "^2.9.0", - "is-my-json-valid": "^2.12.4", - "pinkie-promise": "^2.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", - "dev": true - }, - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "dev": true, - "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - } - }, - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true - } - } - }, "has": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", @@ -8196,28 +7968,6 @@ "minimalistic-assert": "^1.0.1" } }, - "hasha": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/hasha/-/hasha-2.2.0.tgz", - "integrity": "sha1-eNfL/B5tZjA/55g3NlmEUXsvbuE=", - "dev": true, - "requires": { - "is-stream": "^1.0.1", - "pinkie-promise": "^2.0.0" - } - }, - "hawk": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz", - "integrity": "sha1-B4REvXwWQLD+VA0sm3PVlnjo4cQ=", - "dev": true, - "requires": { - "boom": "2.x.x", - "cryptiles": "2.x.x", - "hoek": "2.x.x", - "sntp": "1.x.x" - } - }, "he": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", @@ -8254,12 +8004,6 @@ "minimalistic-crypto-utils": "^1.0.1" } }, - "hoek": { - "version": "2.16.3", - "resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz", - "integrity": "sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0=", - "dev": true - }, "hoist-non-react-statics": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", @@ -8519,17 +8263,6 @@ "micromatch": "^3.1.10" } }, - "http-signature": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.1.1.tgz", - "integrity": "sha1-33LiZwZs0Kxn+3at+OE0qPvPkb8=", - "dev": true, - "requires": { - "assert-plus": "^0.2.0", - "jsprim": "^1.2.2", - "sshpk": "^1.7.0" - } - }, "https-browserify": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz", @@ -8981,25 +8714,6 @@ "resolved": "https://registry.npmjs.org/is-mobile/-/is-mobile-2.2.2.tgz", "integrity": "sha512-wW/SXnYJkTjs++tVK5b6kVITZpAZPtUrt9SF80vvxGiF/Oywal+COk1jlRkiVq15RFNEQKQY31TkV24/1T5cVg==" }, - "is-my-ip-valid": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-my-ip-valid/-/is-my-ip-valid-1.0.0.tgz", - "integrity": "sha512-gmh/eWXROncUzRnIa1Ubrt5b8ep/MGSnfAUI3aRp+sqTCs1tv1Isl8d8F6JmkN3dXKc3ehZMrtiPN9eL03NuaQ==", - "dev": true - }, - "is-my-json-valid": { - "version": "2.20.5", - "resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.20.5.tgz", - "integrity": "sha512-VTPuvvGQtxvCeghwspQu1rBgjYUT6FGxPlvFKbYuFtgc4ADsX3U5ihZOYN0qyU6u+d4X9xXb0IT5O6QpXKt87A==", - "dev": true, - "requires": { - "generate-function": "^2.0.0", - "generate-object-property": "^1.1.0", - "is-my-ip-valid": "^1.0.0", - "jsonpointer": "^4.0.0", - "xtend": "^4.0.0" - } - }, "is-negative-zero": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.1.tgz", @@ -9070,12 +8784,6 @@ "isobject": "^3.0.1" } }, - "is-property": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", - "integrity": "sha1-V/4cTkhHTt1lsJkR8msc1Ald2oQ=", - "dev": true - }, "is-regex": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.3.tgz", @@ -9113,12 +8821,6 @@ "has-symbols": "^1.0.2" } }, - "is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", - "dev": true - }, "is-what": { "version": "3.14.1", "resolved": "https://registry.npmjs.org/is-what/-/is-what-3.14.1.tgz", @@ -9164,12 +8866,6 @@ "whatwg-fetch": ">=0.10.0" } }, - "isstream": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", - "dev": true - }, "istanbul-lib-coverage": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.5.tgz", @@ -9322,6 +9018,11 @@ "resolved": "https://registry.npmjs.org/jquery-ui-bundle/-/jquery-ui-bundle-1.12.1.tgz", "integrity": "sha1-1r4uTDd0lOI3ixyuKSCpHRGC2MQ=" }, + "js-base64": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-3.6.1.tgz", + "integrity": "sha512-Frdq2+tRRGLQUIQOgsIGSCd1VePCS2fsddTG5dTCqR0JHgltXWfsxnY0gIXPoMeRmdom6Oyq+UMOFg5suduOjQ==" + }, "js-cookie": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-2.2.1.tgz", @@ -9342,12 +9043,6 @@ "esprima": "^4.0.0" } }, - "jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", - "dev": true - }, "jsesc": { "version": "2.5.2", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", @@ -9364,12 +9059,6 @@ "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==" }, - "json-schema": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", - "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", - "dev": true - }, "json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", @@ -9381,12 +9070,6 @@ "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", "dev": true }, - "json-stringify-safe": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", - "dev": true - }, "json2mq": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/json2mq/-/json2mq-0.2.0.tgz", @@ -9409,41 +9092,6 @@ "minimist": "^1.2.5" } }, - "jsonfile": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz", - "integrity": "sha1-NzaitCi4e72gzIO1P6PWM6NcKug=", - "dev": true, - "requires": { - "graceful-fs": "^4.1.6" - } - }, - "jsonpointer": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-4.1.0.tgz", - "integrity": "sha512-CXcRvMyTlnR53xMcKnuMzfCA5i/nfblTnnr74CZb6C4vG39eu6w51t7nKmU5MfLfbTgGItliNyjO/ciNPDqClg==", - "dev": true - }, - "jsprim": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", - "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", - "dev": true, - "requires": { - "assert-plus": "1.0.0", - "extsprintf": "1.3.0", - "json-schema": "0.2.3", - "verror": "1.10.0" - }, - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", - "dev": true - } - } - }, "jss": { "version": "10.6.0", "resolved": "https://registry.npmjs.org/jss/-/jss-10.6.0.tgz", @@ -9564,12 +9212,6 @@ "resolved": "https://registry.npmjs.org/kdbush/-/kdbush-3.0.0.tgz", "integrity": "sha512-hRkd6/XW4HTsA9vjVpY9tuXJYLSlelnkTmVFu4M9/7MIYQtFcHpbugAU7UbOfjOiVSVYl2fqgBuJ32JUmRo5Ew==" }, - "kew": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/kew/-/kew-0.7.0.tgz", - "integrity": "sha1-edk9LTM2PW/dKXCzNdkUGtWR15s=", - "dev": true - }, "killable": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/killable/-/killable-1.0.1.tgz", @@ -9581,15 +9223,6 @@ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==" }, - "klaw": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/klaw/-/klaw-1.3.1.tgz", - "integrity": "sha1-QIhDO0azsbolnXh4XY6W9zugJDk=", - "dev": true, - "requires": { - "graceful-fs": "^4.1.9" - } - }, "language-subtag-registry": { "version": "0.3.21", "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.21.tgz", @@ -10689,12 +10322,6 @@ "resolved": "https://registry.npmjs.org/numeric/-/numeric-1.2.6.tgz", "integrity": "sha1-dlsCvvl5iPz4gNTrPza4D6MTNao=" }, - "oauth-sign": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", - "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=", - "dev": true - }, "object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -11136,12 +10763,6 @@ "sha.js": "^2.4.8" } }, - "pend": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", - "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=", - "dev": true - }, "performance-now": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", @@ -11164,39 +10785,6 @@ "typedarray-pool": "^1.0.0" } }, - "phantomjs": { - "version": "2.1.7", - "resolved": "https://registry.npmjs.org/phantomjs/-/phantomjs-2.1.7.tgz", - "integrity": "sha1-xpEPZ5NcNyhbYRQyn8LyfV8+MTQ=", - "dev": true, - "requires": { - "extract-zip": "~1.5.0", - "fs-extra": "~0.26.4", - "hasha": "^2.2.0", - "kew": "~0.7.0", - "progress": "~1.1.8", - "request": "~2.67.0", - "request-progress": "~2.0.1", - "which": "~1.2.2" - }, - "dependencies": { - "progress": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/progress/-/progress-1.1.8.tgz", - "integrity": "sha1-4mDHj2Fhzdmw5WzD4Khd4Xx6V74=", - "dev": true - }, - "which": { - "version": "1.2.14", - "resolved": "https://registry.npmjs.org/which/-/which-1.2.14.tgz", - "integrity": "sha1-mofEN48D6CfOyvGs31bHNsAcFOU=", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - } - } - }, "pick-by-alias": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/pick-by-alias/-/pick-by-alias-1.2.0.tgz", @@ -12350,14 +11938,6 @@ "requires": { "dnd-core": "^11.1.3" } - }, - "redux": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/redux/-/redux-4.1.0.tgz", - "integrity": "sha512-uI2dQN43zqLWCt6B/BMGRMY6db7TTY4qeHHfGeKb3EOhmOKjU3KdWvNLJyqaHRksv/ErdNH7cFZWg9jXtewy4g==", - "requires": { - "@babel/runtime": "^7.9.2" - } } } }, @@ -12513,14 +12093,11 @@ } }, "redux": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/redux/-/redux-3.7.2.tgz", - "integrity": "sha512-pNqnf9q1hI5HHZRBkj3bAngGZW/JMCmexDlOxw4XagXY2o1327nHH54LoTjiPJ0gizoqPDRqWyX/00g0hD6w+A==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/redux/-/redux-4.1.0.tgz", + "integrity": "sha512-uI2dQN43zqLWCt6B/BMGRMY6db7TTY4qeHHfGeKb3EOhmOKjU3KdWvNLJyqaHRksv/ErdNH7cFZWg9jXtewy4g==", "requires": { - "lodash": "^4.2.1", - "lodash-es": "^4.2.1", - "loose-envify": "^1.1.0", - "symbol-observable": "^1.0.3" + "@babel/runtime": "^7.9.2" } }, "regenerate": { @@ -12736,103 +12313,6 @@ "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=" }, - "request": { - "version": "2.67.0", - "resolved": "https://registry.npmjs.org/request/-/request-2.67.0.tgz", - "integrity": "sha1-ivdHgOK/EeoK6aqWXBHxGv0nJ0I=", - "dev": true, - "requires": { - "aws-sign2": "~0.6.0", - "bl": "~1.0.0", - "caseless": "~0.11.0", - "combined-stream": "~1.0.5", - "extend": "~3.0.0", - "forever-agent": "~0.6.1", - "form-data": "~1.0.0-rc3", - "har-validator": "~2.0.2", - "hawk": "~3.1.0", - "http-signature": "~1.1.0", - "is-typedarray": "~1.0.0", - "isstream": "~0.1.2", - "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.7", - "node-uuid": "~1.4.7", - "oauth-sign": "~0.8.0", - "qs": "~5.2.0", - "stringstream": "~0.0.4", - "tough-cookie": "~2.2.0", - "tunnel-agent": "~0.4.1" - }, - "dependencies": { - "bl": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/bl/-/bl-1.0.3.tgz", - "integrity": "sha1-/FQhoo/UImA2w7OJGmaiW8ZNIm4=", - "dev": true, - "requires": { - "readable-stream": "~2.0.5" - } - }, - "form-data": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-1.0.1.tgz", - "integrity": "sha1-rjFduaSQf6BlUCMEpm13M0de43w=", - "dev": true, - "requires": { - "async": "^2.0.1", - "combined-stream": "^1.0.5", - "mime-types": "^2.1.11" - } - }, - "node-uuid": { - "version": "1.4.8", - "resolved": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.8.tgz", - "integrity": "sha1-sEDrCSOWivq/jTL7HxfxFn/auQc=", - "dev": true - }, - "process-nextick-args": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", - "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=", - "dev": true - }, - "qs": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/qs/-/qs-5.2.1.tgz", - "integrity": "sha1-gB/uAw4LlFDWOFrcSKTMVbRK7fw=", - "dev": true - }, - "readable-stream": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz", - "integrity": "sha1-j5A0HmilPMySh4jaz80Rs265t44=", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", - "isarray": "~1.0.0", - "process-nextick-args": "~1.0.6", - "string_decoder": "~0.10.x", - "util-deprecate": "~1.0.1" - } - }, - "string_decoder": { - "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", - "dev": true - } - } - }, - "request-progress": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/request-progress/-/request-progress-2.0.1.tgz", - "integrity": "sha1-XTa7V5YcZzqlt4jbyBQf3yO0Tgg=", - "dev": true, - "requires": { - "throttleit": "^1.0.0" - } - }, "require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -13652,15 +13132,6 @@ } } }, - "sntp": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz", - "integrity": "sha1-ZUEYTMkK7qbG57NeJlkIJEPGYZg=", - "dev": true, - "requires": { - "hoek": "2.x.x" - } - }, "sockjs": { "version": "0.3.21", "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.21.tgz", @@ -13852,31 +13323,6 @@ "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.2.tgz", "integrity": "sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==" }, - "sshpk": { - "version": "1.16.1", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", - "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", - "dev": true, - "requires": { - "asn1": "~0.2.3", - "assert-plus": "^1.0.0", - "bcrypt-pbkdf": "^1.0.0", - "dashdash": "^1.12.0", - "ecc-jsbn": "~0.1.1", - "getpass": "^0.1.1", - "jsbn": "~0.1.0", - "safer-buffer": "^2.0.2", - "tweetnacl": "~0.14.0" - }, - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", - "dev": true - } - } - }, "ssri": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/ssri/-/ssri-5.3.0.tgz", @@ -14090,12 +13536,6 @@ "safe-buffer": "~5.1.0" } }, - "stringstream": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.6.tgz", - "integrity": "sha512-87GEBAkegbBcweToUrdzf3eLhWNg06FJTebl4BVJz/JgWy8CvEr9dRtX5qWphiynMSQlxxi+QqN0z5T32SLlhA==", - "dev": true - }, "strip-ansi": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", @@ -14605,12 +14045,6 @@ "resolved": "https://registry.npmjs.org/three-trackballcontrols/-/three-trackballcontrols-0.0.8.tgz", "integrity": "sha512-9my4rB/ILaJw8E6khOcC6d8F8kEe6BvRtCer9bFJGZOh9txy4BqzwPrG7d8YCKK9MfcyTUoLsB5LKIsbtsZRTg==" }, - "throttleit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-1.0.0.tgz", - "integrity": "sha1-nnhYNtr0Z0MUWlmEtiaNgoUorGw=", - "dev": true - }, "through2": { "version": "0.6.5", "resolved": "https://registry.npmjs.org/through2/-/through2-0.6.5.tgz", @@ -14791,12 +14225,6 @@ "integrity": "sha1-LmhELZ9k7HILjMieZEOsbKqVACk=", "dev": true }, - "tough-cookie": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.2.2.tgz", - "integrity": "sha1-yDoYMPTl7wuT7yo0iOck+N4Basc=", - "dev": true - }, "triangulate-hypercube": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/triangulate-hypercube/-/triangulate-hypercube-1.0.1.tgz", @@ -14849,12 +14277,6 @@ "integrity": "sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY=", "dev": true }, - "tunnel-agent": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.4.3.tgz", - "integrity": "sha1-Y3PbdpCf5XDgjXNYM2Xtgop07us=", - "dev": true - }, "turntable-camera-controller": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/turntable-camera-controller/-/turntable-camera-controller-3.0.1.tgz", @@ -14865,12 +14287,6 @@ "gl-vec3": "^1.0.2" } }, - "tweetnacl": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", - "dev": true - }, "two-product": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/two-product/-/two-product-1.0.2.tgz", @@ -15458,25 +14874,6 @@ "resolved": "https://registry.npmjs.org/velocity.java/-/velocity.java-1.3.1.tgz", "integrity": "sha1-pv64A5jLVkzkKslrShjlKDc6Ld4=" }, - "verror": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", - "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", - "dev": true, - "requires": { - "assert-plus": "^1.0.0", - "core-util-is": "1.0.2", - "extsprintf": "^1.2.0" - }, - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", - "dev": true - } - } - }, "vm-browserify": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz", @@ -16191,15 +15588,6 @@ "decamelize": "^1.2.0" } }, - "yauzl": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.4.1.tgz", - "integrity": "sha1-lSj0QtqxsihOWLQ3m7GU4i4MQAU=", - "dev": true, - "requires": { - "fd-slicer": "~1.0.1" - } - }, "zero-crossings": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/zero-crossings/-/zero-crossings-1.0.1.tgz", diff --git a/webapp/package.json b/webapp/package.json index 529cd3a1..160ed395 100644 --- a/webapp/package.json +++ b/webapp/package.json @@ -15,11 +15,17 @@ "dependencies": { "@babel/plugin-proposal-class-properties": "^7.8.3", "@geppettoengine/geppetto-client": "git+https://git@github.com/openworm/geppetto-client.git#hotfix/html-viewer", + "@material-ui/core": "^4.11.4", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "^4.0.0-alpha.51", + "jquery": "^3.6.0", + "js-base64": "^3.6.1", "less-vars-to-js": "^1.3.0", "react": "^17.0.1", - "react-redux": "^7.2.0" + "react-dom": "^16.4.0", + "react-redux": "^7.2.0", + "react-sortable-tree": "^2.8.0", + "redux": "^4.1.0" }, "devDependencies": { "@babel/core": "^7.4.5", @@ -53,7 +59,6 @@ "json-loader": "^0.5.4", "less-loader": "^5.0.0", "mini-css-extract-plugin": "^0.7.0", - "phantomjs": "^2.1.7", "raw-loader": "^0.5.1", "slimerjs": "^1.0.0", "source-map-loader": "^0.2.3", diff --git a/webapp/redux/actions/general.js b/webapp/redux/actions/general.js index 53ce0f68..fdef3776 100644 --- a/webapp/redux/actions/general.js +++ b/webapp/redux/actions/general.js @@ -1,5 +1,5 @@ import { setWidgets } from '../../components/layout/actions'; -import { EDIT_WIDGETS, PYTHON_CONSOLE_WIDGET, WidgetStatus } from '../../constants'; +import { PYTHON_CONSOLE_WIDGET, WidgetStatus } from '../../constants'; // Action Types export const UPDATE_CARDS = 'UPDATE_CARDS'; @@ -34,18 +34,46 @@ export const editModel = { type: EDIT_MODEL }; export const resetModel = { type: RESET_MODEL }; -export const pythonCall = (cmd, args) => ({ type: PYTHON_CALL, cmd, args }); +export const pythonCall = (cmd, args) => ({ + type: PYTHON_CALL, + cmd, + args, +}); -export const deleteNetParamsObj = (payload) => ({ type: DELETE_NETPARAMS_OBJ, payload }); +export const deleteNetParamsObj = (payload) => ({ + type: DELETE_NETPARAMS_OBJ, + payload, +}); export const closeDialog = { type: CLOSE_DIALOG }; -export const openDialog = (payload) => ({ type: OPEN_DIALOG, payload }); +export const openDialog = (payload) => ({ + type: OPEN_DIALOG, + payload, +}); -export const setTheme = (themeName) => ({ type: SET_THEME, payload: themeName }); +export const setTheme = (themeName) => ({ + type: SET_THEME, + payload: themeName, +}); -export const loadTutorial = (tutFile) => ({ type: LOAD_TUTORIAL, payload: tutFile }); +export const loadTutorial = (tutFile) => ({ + type: LOAD_TUTORIAL, + payload: tutFile, +}); -export const changeAutomaticInstantiation = (payload) => ({ type: AUTOMATIC_INSTANTIATION, payload }); -export const changeAutomaticSimulation = (payload) => ({ type: AUTOMATIC_SIMULATION, payload }); +export const changeAutomaticInstantiation = (payload) => ({ + type: AUTOMATIC_INSTANTIATION, + payload, +}); +export const changeAutomaticSimulation = (payload) => ({ + type: AUTOMATIC_SIMULATION, + payload, +}); -export const setDefaultWidgets = setWidgets({ [PYTHON_CONSOLE_WIDGET.id]: { ...PYTHON_CONSOLE_WIDGET, panelName: PYTHON_CONSOLE_WIDGET.defaultPanel, status: WidgetStatus.ACTIVE } }); +export const setDefaultWidgets = setWidgets({ + [PYTHON_CONSOLE_WIDGET.id]: { + ...PYTHON_CONSOLE_WIDGET, + panelName: PYTHON_CONSOLE_WIDGET.defaultPanel, + status: WidgetStatus.ACTIVE, + }, +}); diff --git a/webapp/redux/middleware/plotMiddleware.js b/webapp/redux/middleware/plotMiddleware.js index 0c78fee2..8c861042 100644 --- a/webapp/redux/middleware/plotMiddleware.js +++ b/webapp/redux/middleware/plotMiddleware.js @@ -18,6 +18,64 @@ window.plotCache = {}; const isDisabled = (widget, plots) => !plots[widget.method.plotKey] ?? true; +const setPlotToWindow = (plotId, data) => { + if (data === '') { + console.log('No plot to show'); + return; + } + window.plotCache[plotId] = data; +}; + +const plotFigure = async (plotId, plotMethod, plotType = false, theme) => { + try { + let response = await Promise.race([ + Utils.evalPythonMessage(NETPYNE_COMMANDS.plotFigure, [plotMethod, plotType, theme], false), + new Promise((resolve, reject) => { + setTimeout(() => { + resolve(null); + }, 30000); + })]); + + console.log('Plot response received for', plotId); + if (!response) { + return null; + } + + // TODO Fix this, use just JSON + if (typeof response === 'string') { + if (response.startsWith('{') && response.endsWith('}')) { + if (processError(response, plotId)) { + console.error(processError(response, plotId)); + return null; + } + } + if (response.startsWith('[') && response.endsWith(']')) { + response = eval(response); + } + } + if (plotMethod.startsWith('iplot')) { + let htmlText = response.replace ? response.replace(/\\n/g, '') + .replace(/\\/g, '') : ''; + if (plotId === 'rxdConcentrationPlot') { + // FIXME: How can we center the bokeh plots when sizing_mode='scale_height' + htmlText = htmlText.replace('', ''); + } + return htmlText; + } + if (response?.length !== undefined) { + return response[0]; + } + if (response === -1) { + return null; + } + return response; + } catch (error) { + console.error(error); + } + + return null; +}; + export default (store) => (next) => (action) => { async function setWidget (widget) { const { @@ -46,7 +104,7 @@ export default (store) => (next) => (action) => { && widget.status === WidgetStatus.ACTIVE && !widget.initialized) { setWidget(widget) - .then((widget) => (widget ? next(action) : null)); + .then((w) => (w ? next(action) : null)); } next(action); break; @@ -97,7 +155,7 @@ export default (store) => (next) => (action) => { if (!store.getState().general.editMode) { for (const widget of Object.values(PLOT_WIDGETS)) { setWidget(widget) - .then((widget) => (widget ? next(addWidget(widget)) : null)); + .then((w) => (w ? next(addWidget(w)) : null)); } } break; @@ -107,59 +165,3 @@ export default (store) => (next) => (action) => { } } }; - -const plotFigure = async (plotId, plotMethod, plotType = false, theme) => { - try { - let response = await Promise.race([ - Utils.evalPythonMessage(NETPYNE_COMMANDS.plotFigure, [plotMethod, plotType, theme], false), - new Promise((resolve, reject) => { - setTimeout(() => { - resolve(null); - }, 30000); - })]); - - console.log('Plot response received for', plotId); - if (!response) { - return null; - } - - // TODO Fix this, use just JSON - if (typeof response === 'string') { - if (response.startsWith('{') && response.endsWith('}')) { - if (processError(response, plotId)) { - console.error(processError(response, plotId)); - return; - } - } - if (response.startsWith('[') && response.endsWith(']')) { - response = eval(response); - } - } - if (plotMethod.startsWith('iplot')) { - let htmlText = response.replace ? response.replace(/\\n/g, '') - .replace(/\\/g, '') : ''; - if (plotId === 'rxdConcentrationPlot') { - // FIXME: How can we center the bokeh plots when sizing_mode='scale_height' - htmlText = htmlText.replace('', ''); - } - return htmlText; - } - if (response?.length !== undefined) { - return response[0]; - } - if (response === -1) { - return null; - } - return response; - } catch (error) { - console.error(error); - } -}; - -const setPlotToWindow = (plotId, data) => { - if (data === '') { - console.log('No plot to show'); - return; - } - window.plotCache[plotId] = data; -}; diff --git a/webapp/redux/middleware/rulesOperationsMiddleware.js b/webapp/redux/middleware/rulesOperationsMiddleware.js index 7cdb9edc..455b8faa 100644 --- a/webapp/redux/middleware/rulesOperationsMiddleware.js +++ b/webapp/redux/middleware/rulesOperationsMiddleware.js @@ -2,6 +2,19 @@ import { DELETE_NETPARAMS_OBJ, updateCards } from '../actions/general'; import Utils from '../../Utils'; import { NETPYNE_COMMANDS } from '../../constants'; +function deleteNetPyNEObj (paramName, paramPath, next, action) { + Utils.evalPythonMessage(NETPYNE_COMMANDS.deleteParam, [paramPath, paramName]).then((response) => { + if (response) { + /* + * var model = this.state.value; + * delete model[name]; + * this.setState({ value: model, selectedPopulation: undefined, populationDeleted: name }, () => this.props.updateCards()); + */ + next(action); + } + }); +} + export default (store) => (next) => (action) => { switch (action.type) { case DELETE_NETPARAMS_OBJ: { @@ -17,16 +30,3 @@ export default (store) => (next) => (action) => { } } }; - -function deleteNetPyNEObj (paramName, paramPath, next, action) { - Utils.evalPythonMessage(NETPYNE_COMMANDS.deleteParam, [paramPath, paramName]).then((response) => { - if (response) { - /* - * var model = this.state.value; - * delete model[name]; - * this.setState({ value: model, selectedPopulation: undefined, populationDeleted: name }, () => this.props.updateCards()); - */ - next(action); - } - }); -} diff --git a/webapp/redux/middleware/utils.js b/webapp/redux/middleware/utils.js index 49fc6cb4..c36a25a0 100644 --- a/webapp/redux/middleware/utils.js +++ b/webapp/redux/middleware/utils.js @@ -1,13 +1,8 @@ -const createFileName = (name) => name + getTimeStamp(); +import { Base64 } from 'js-base64'; const getTimeStamp = () => new Date().toGMTString().replace(',', '').replace(/[ ,:]/g, '_'); -const unescapeText = (text) => { - text = text.replace(/\\\\/g, '\\').replace(/\\\'/g, "'").replace(/\\\"/g, '"').split('\\n') - .join('\n') - .substring(1); - return text.substring(0, text.length - 1); -}; +const createFileName = (name) => name + getTimeStamp(); const forceBlobDownload = (blob, filename) => { const url = window.URL.createObjectURL(blob); @@ -32,8 +27,8 @@ export const downloadJsonResponse = (jsonData) => { forceBlobDownload(blob, filename); }; -export const downloadPythonResponse = (textData) => { - const filename = `${createFileName('NetPyNE_init_')}.py`; - const blob = new Blob([unescapeText(textData)], { type: 'text/plain;charset=utf-8' }); - forceBlobDownload(blob, filename); +export const downloadPythonResponse = (exportInfo) => { + const content = Base64.decode(exportInfo.fileContent); + const blob = new Blob([content], { type: 'text/plain;charset=utf-8' }); + forceBlobDownload(blob, exportInfo.fileName); }; diff --git a/webapp/redux/reducers/drawer.js b/webapp/redux/reducers/drawer.js index 2869817f..d50b5af1 100644 --- a/webapp/redux/reducers/drawer.js +++ b/webapp/redux/reducers/drawer.js @@ -4,12 +4,6 @@ import { OPEN_DRAWER_DIALOG_BOX, CLOSE_DRAWER_DIALOG_BOX } from '../actions/draw // Default state for general export const DRAWER_DEFAULT_STATE = { dialogBoxOpen: false }; -// reducer -export default (state = DRAWER_DEFAULT_STATE, action) => ({ - ...state, - ...reduceError(state, action), -}); - // reducer function function reduceError (state, action) { switch (action.type) { @@ -22,3 +16,9 @@ function reduceError (state, action) { } } } + +// reducer +export default (state = DRAWER_DEFAULT_STATE, action) => ({ + ...state, + ...reduceError(state, action), +}); diff --git a/webapp/redux/reducers/general.js b/webapp/redux/reducers/general.js index 04843381..7556fbfa 100644 --- a/webapp/redux/reducers/general.js +++ b/webapp/redux/reducers/general.js @@ -12,7 +12,7 @@ export const GENERAL_DEFAULT_STATE = { dialogTitle: '', dialogMessage: '', automaticSimulation: false, - automaticInstantiation: true, + automaticInstantiation: false, theme: 'gui', }; diff --git a/webapp/redux/reducers/notebook.js b/webapp/redux/reducers/notebook.js index 8660cc17..b463523b 100644 --- a/webapp/redux/reducers/notebook.js +++ b/webapp/redux/reducers/notebook.js @@ -5,8 +5,6 @@ export const NOTEBOOK_DEFAULT_STATE = { isNotebookReady: false, }; -export default (state = {}, action) => ({ ...state, ...reduceNotebook(state, action) }); - function reduceNotebook (state = {}, action) { switch (action.type) { case TYPES.LOAD_NOTEBOOK: @@ -29,3 +27,5 @@ function reduceNotebook (state = {}, action) { return state; } } + +export default (state = {}, action) => ({ ...state, ...reduceNotebook(state, action) }); diff --git a/webapp/redux/reducers/topbar.js b/webapp/redux/reducers/topbar.js index 4dc78903..36e084eb 100644 --- a/webapp/redux/reducers/topbar.js +++ b/webapp/redux/reducers/topbar.js @@ -12,12 +12,6 @@ export const TOPBAR_DEFAULT_STATE = { }; // reducer -export default (state = { ...TOPBAR_DEFAULT_STATE }, action) => ({ - ...state, - ...reduceTopbar(state, action), -}); - -// reducer function function reduceTopbar (state, action) { switch (action.type) { case OPEN_TOPBAR_DIALOG: @@ -33,3 +27,9 @@ function reduceTopbar (state, action) { } } } + +export default (state = { ...TOPBAR_DEFAULT_STATE }, action) => ({ + ...state, + ...reduceTopbar(state, action), +}); +// reducer function diff --git a/workspace b/workspace deleted file mode 160000 index f1d5dac9..00000000 --- a/workspace +++ /dev/null @@ -1 +0,0 @@ -Subproject commit f1d5dac95ab6c4a2868f072490b239bb1842d789