From 93e5b39db19797694a68f0cd62872e35bbd527e7 Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Mon, 2 Aug 2021 13:56:59 -0400 Subject: [PATCH 001/186] Moved the job presentation function from the stochss folder to stochss job. Refactored the publish presentation function to generate a single file presetation, use the md5 hash of the job without results as the file, and generate links for the user. Refactored the publish presentation api handler to use the new location for the function and return the links to the user. --- stochss/handlers/util/stochss_folder.py | 25 ------ stochss/handlers/util/stochss_job.py | 114 +++++++++++++++++------- stochss/handlers/workflows.py | 15 ++-- 3 files changed, 91 insertions(+), 63 deletions(-) diff --git a/stochss/handlers/util/stochss_folder.py b/stochss/handlers/util/stochss_folder.py index 948d795886..2fd0bdef30 100644 --- a/stochss/handlers/util/stochss_folder.py +++ b/stochss/handlers/util/stochss_folder.py @@ -376,31 +376,6 @@ def move(self, location): raise StochSSPermissionsError(message, traceback.format_exc()) from err - def publish_presentation(self, name=None): - ''' - Publish a job, workflow, or project presentation. - - Attributes - ---------- - ''' - present_dir = os.path.join(self.user_dir, ".presentations") - if not os.path.exists(present_dir): - os.mkdir(present_dir) - file = self.get_file() if name is None else name - dst = os.path.join(present_dir, file) - if os.path.exists(dst): - message = "A presentation with this name already exists" - raise StochSSFileExistsError(message) - src = self.get_path(full=True) - try: - shutil.copytree(src, dst) - # INSERT JUPYTER HUB CODE HERE - return {"message": f"Successfully published the {self.get_name()} presentation"} - except PermissionError as err: - message = f"You do not have permission to publish this directory: {str(err)}" - raise StochSSPermissionsError(message, traceback.format_exc()) from err - - def upload(self, file_type, file, body, new_name=None): ''' Upload a file from a remote location to the users file system diff --git a/stochss/handlers/util/stochss_job.py b/stochss/handlers/util/stochss_job.py index b9beeaa818..fe49598675 100644 --- a/stochss/handlers/util/stochss_job.py +++ b/stochss/handlers/util/stochss_job.py @@ -20,9 +20,12 @@ import json import shutil import pickle +import string +import hashlib import datetime import traceback +import escape import numpy import plotly @@ -32,7 +35,7 @@ from .stochss_spatial_model import StochSSSpatialModel from .stochss_errors import StochSSJobError, StochSSJobNotCompleteError, \ StochSSFileNotFoundError, StochSSFileExistsError, \ - FileNotJSONFormatError, PlotNotAvailableError + FileNotJSONFormatError, PlotNotAvailableError, StochSSPermissionsError class StochSSJob(StochSSBase): ''' @@ -70,7 +73,7 @@ def __create_new_job(self, mdl_path, settings=None): path = self.get_path(full=True) try: os.mkdir(path) - os.mkdir(self.get_results_path(full=True)) + os.mkdir(self.__get_results_path(full=True)) info = {"source_model":mdl_path, "wkfl_model":None, "type":self.type, "start_time":None} self.update_info(new_info=info, new=True) open(os.path.join(path, "logs.txt"), "w").close() @@ -125,10 +128,47 @@ def __get_extract_dst_path(self, mdl_file): return os.path.join(wkgp_path, mdl_file) + def __get_info_path(self, full=False): + ''' + Return the path to the info.json file + + Attributes + ---------- + full : bool + Indicates whether or not to get the full path or local path + ''' + return os.path.join(self.get_path(full=full), "info.json") + + + @classmethod + def __get_presentation_links(cls, file): + safe_chars = set(string.ascii_letters + string.digits) + hostname = escape(os.environ.get('JUPYTERHUB_USER'), safe=safe_chars) + query_str = f"?owner={hostname}&file={file}" + present_link = f"https://live.stochss.org/stochss/present-job{query_str}" + dl_base = "https://live.stochss.org/stochss/job/download_presentation" + downloadlink = os.path.join(dl_base, hostname, file) + open_link = f"https://open.stochss.org?open={downloadlink}" + links = {"presentation": present_link, "download": downloadlink, "open": open_link} + return links + + + def __get_results_path(self, full=False): + ''' + Return the path to the results directory + + Attributes + ---------- + full : bool + Indicates whether or not to get the full path or local path + ''' + return os.path.join(self.get_path(full=full), "results") + + def __is_csv_dir(self, file): if "results_csv" not in file: return False - path = os.path.join(self.get_results_path(), file) + path = os.path.join(self.__get_results_path(), file) if not os.path.isdir(path): return False return True @@ -237,7 +277,7 @@ def get_csv_path(self, full=False): Attributes ---------- ''' - res_path = self.get_results_path(full=full) + res_path = self.__get_results_path(full=full) if not os.path.exists(res_path): message = f"Could not find the results directory: {res_path}" raise StochSSFileNotFoundError(message, traceback.format_exc()) @@ -251,18 +291,6 @@ def get_csv_path(self, full=False): raise StochSSFileNotFoundError(message, traceback.format_exc()) from err - def get_info_path(self, full=False): - ''' - Return the path to the info.json file - - Attributes - ---------- - full : bool - Indicates whether or not to get the full path or local path - ''' - return os.path.join(self.get_path(full=full), "info.json") - - def get_model_path(self, full=False, external=False): ''' Return the path to the model file @@ -335,7 +363,7 @@ def get_plot_from_results(self, plt_key, plt_data, plt_type): Type of plot to generate. ''' self.log("debug", f"Key identifying the plot to generate: {plt_type}") - path = os.path.join(self.get_results_path(full=True), "results.p") + path = os.path.join(self.__get_results_path(full=True), "results.p") try: self.log("info", "Loading the results...") with open(path, "rb") as results_file: @@ -366,18 +394,6 @@ def get_plot_from_results(self, plt_key, plt_data, plt_type): raise PlotNotAvailableError(message, traceback.format_exc()) from err - def get_results_path(self, full=False): - ''' - Return the path to the results directory - - Attributes - ---------- - full : bool - Indicates whether or not to get the full path or local path - ''' - return os.path.join(self.get_path(full=full), "results") - - def get_results_plot(self, plt_key, plt_data, fig=None): ''' Get the plotly figure for the results of a job @@ -391,7 +407,7 @@ def get_results_plot(self, plt_key, plt_data, fig=None): ''' self.log("debug", f"Key identifying the requested plot: {plt_key}") self.log("debug", f"Title and axis data for the plot: {plt_data}") - path = os.path.join(self.get_results_path(full=True), "plots.json") + path = os.path.join(self.__get_results_path(full=True), "plots.json") self.log("debug", f"Path to the job result plot file: {path}") try: if fig is None: @@ -529,7 +545,7 @@ def load_info(self): ---------- ''' try: - path = self.get_info_path(full=True) + path = self.__get_info_path(full=True) self.log("debug", f"The path to the job's info file: {path}") with open(path, "r") as info_file: info = json.load(info_file) @@ -591,6 +607,40 @@ def load_settings(self, model=None): raise FileNotJSONFormatError(message, traceback.format_exc()) from err + def publish_presentation(self, name=None): + ''' + Publish a job, workflow, or project presentation. + + Attributes + ---------- + name : str + Name of the job presentation. + ''' + present_dir = os.path.join(self.user_dir, ".presentations") + if not os.path.exists(present_dir): + os.mkdir(present_dir) + try: + self.load() + job = json.dumps(self.job, sort_keys=True) + file = f"{hashlib.md5(job.encode('utf-8')).hexdigest()}.job" + dst = os.path.join(present_dir, file) + if os.path.exists(dst): + exists = True + else: + exists = False + name = self.get_file() if name is None else name + with open(self.__get_results_path(), "rb") as results_file: + results = pickle.load(results_file) + data = {"name": name, "job": self.job, "results": results} + with open(dst, "wb") as presentation_file: + pickle.dump(data, presentation_file) + links = self.__get_presentation_links(file) + return links, exists + except PermissionError as err: + message = f"You do not have permission to publish this directory: {str(err)}" + raise StochSSPermissionsError(message, traceback.format_exc()) from err + + def save(self, mdl_path, settings, initialize=False): ''' Save the data for a new or ready state job @@ -665,5 +715,5 @@ def update_info(self, new_info, new=False): if "annotation" in new_info.keys(): info['annotation'] = new_info['annotation'] self.log("debug", f"New info: {info}") - with open(self.get_info_path(full=True), "w") as file: + with open(self.__get_info_path(full=True), "w") as file: json.dump(info, file, indent=4, sort_keys=True) diff --git a/stochss/handlers/workflows.py b/stochss/handlers/workflows.py index daa572c3ed..428572da2b 100644 --- a/stochss/handlers/workflows.py +++ b/stochss/handlers/workflows.py @@ -422,12 +422,15 @@ async def get(self): name = self.get_query_argument(name="name") log.debug("Name of the job presentation: %s", name) try: - folder = StochSSFolder(path=path) - log.info("Publishing the %s presentation", folder.get_name()) - resp = folder.publish_presentation(name=name) - log.info(resp['message']) - log.debug("Response Message: %s", resp) - self.write(resp) + job = StochSSJob(path=path) + log.info("Publishing the %s presentation", job.get_name()) + links, exists = job.publish_presentation(name=name) + if exists: + message = f"A presentation for {job.get_name()} already exists." + else: + message = f"Successfully published the {job.get_name()} presentation" + log.info(message) + self.write(links) except StochSSAPIError as err: report_error(self, log, err) self.finish() From 9563c6068eb31b1cb0d8f1ab041f906d6a4ba798 Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Mon, 2 Aug 2021 14:10:39 -0400 Subject: [PATCH 002/186] Refactored the job presentation functions to display the new links that are returned by the new api handler. --- client/views/workflow-results.js | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/client/views/workflow-results.js b/client/views/workflow-results.js index 95dc9fba28..24439aa152 100644 --- a/client/views/workflow-results.js +++ b/client/views/workflow-results.js @@ -51,7 +51,7 @@ module.exports = View.extend({ 'click [data-target=download-json]' : 'handleDownloadJSONClick', 'click [data-hook=convert-to-notebook]' : 'handleConvertToNotebookClick', 'click [data-hook=download-results-csv]' : 'handleDownloadResultsCsvClick', - // 'click [data-hook=job-presentation]' : 'handlePresentationClick' + 'click [data-hook=job-presentation]' : 'handlePresentationClick' }, initialize: function (attrs, options) { View.prototype.initialize.apply(this, arguments); @@ -293,7 +293,16 @@ module.exports = View.extend({ let endpoint = path.join(app.getApiPath(), "job/presentation") + queryStr; app.getXHR(endpoint, { success: function (err, response, body) { - self.endAction(); + self.endAction("publish"); + let title = body.message; + let linkHeaders = "Shareable Presentation Link"; + let links = body.links; + let name = self.model.name + $(modals.presentationLinks(title, name, linkHeaders, links)).modal(); + let copyBtn = document.querySelector('#presentationLinksModal #copy-to-clipboard'); + copyBtn.addEventListener('click', function (e) { + app.copyToClipboard(links.presentation) + }); }, error: function (err, response, body) { self.errorAction(); From d6cd24a47deaebd04522e2e275f043b00d69d0d7 Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Mon, 2 Aug 2021 14:40:24 -0400 Subject: [PATCH 003/186] Fixed import issue. --- client/views/workflow-results.js | 10 +++++----- stochss/handlers/util/stochss_job.py | 3 ++- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/client/views/workflow-results.js b/client/views/workflow-results.js index 24439aa152..b5ad2421b3 100644 --- a/client/views/workflow-results.js +++ b/client/views/workflow-results.js @@ -75,11 +75,11 @@ module.exports = View.extend({ if(!isParameterScan){ $(this.queryByHook("convert-to-notebook")).css("display", "none"); } - }else if(app.getBasePath() === "/") { - $(this.queryByHook("job-presentation")).css("display", "none"); - }else{ - $(this.queryByHook("job-presentation")).prop("disabled", true); - } + }//else if(app.getBasePath() === "/") { + // $(this.queryByHook("job-presentation")).css("display", "none"); + // }//else{ + // $(this.queryByHook("job-presentation")).prop("disabled", true); + // } if(this.parent.model.type === "Ensemble Simulation") { var type = isEnsemble ? "stddevran" : "trajectories"; }else{ diff --git a/stochss/handlers/util/stochss_job.py b/stochss/handlers/util/stochss_job.py index fe49598675..853495f8b5 100644 --- a/stochss/handlers/util/stochss_job.py +++ b/stochss/handlers/util/stochss_job.py @@ -25,10 +25,11 @@ import datetime import traceback -import escape import numpy import plotly +from escapism import escape + from .stochss_base import StochSSBase from .stochss_folder import StochSSFolder from .stochss_model import StochSSModel From 9332f872ea9eb455e94351fb9b0695115acc4ae7 Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Mon, 2 Aug 2021 14:52:57 -0400 Subject: [PATCH 004/186] Fixed job presentation api handlers return. --- stochss/handlers/workflows.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/stochss/handlers/workflows.py b/stochss/handlers/workflows.py index 428572da2b..1e57a9f73d 100644 --- a/stochss/handlers/workflows.py +++ b/stochss/handlers/workflows.py @@ -429,8 +429,10 @@ async def get(self): message = f"A presentation for {job.get_name()} already exists." else: message = f"Successfully published the {job.get_name()} presentation" - log.info(message) - self.write(links) + resp = {"message": message, "links": links} + log.info(resp['message']) + log.debug("Response Message: %s", resp) + self.write(resp) except StochSSAPIError as err: report_error(self, log, err) self.finish() From 3d53279892bbafb25eafa36619d9cc21c1bce8fa Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Mon, 2 Aug 2021 15:01:35 -0400 Subject: [PATCH 005/186] Fixed results file path issue. --- client/views/workflow-results.js | 8 +++----- stochss/handlers/util/stochss_job.py | 3 ++- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/client/views/workflow-results.js b/client/views/workflow-results.js index b5ad2421b3..c5e9492310 100644 --- a/client/views/workflow-results.js +++ b/client/views/workflow-results.js @@ -75,11 +75,9 @@ module.exports = View.extend({ if(!isParameterScan){ $(this.queryByHook("convert-to-notebook")).css("display", "none"); } - }//else if(app.getBasePath() === "/") { - // $(this.queryByHook("job-presentation")).css("display", "none"); - // }//else{ - // $(this.queryByHook("job-presentation")).prop("disabled", true); - // } + }else if(app.getBasePath() === "/") { + $(this.queryByHook("job-presentation")).css("display", "none"); + } if(this.parent.model.type === "Ensemble Simulation") { var type = isEnsemble ? "stddevran" : "trajectories"; }else{ diff --git a/stochss/handlers/util/stochss_job.py b/stochss/handlers/util/stochss_job.py index 853495f8b5..33c57d31ca 100644 --- a/stochss/handlers/util/stochss_job.py +++ b/stochss/handlers/util/stochss_job.py @@ -630,7 +630,8 @@ def publish_presentation(self, name=None): else: exists = False name = self.get_file() if name is None else name - with open(self.__get_results_path(), "rb") as results_file: + path = os.path.join(self.__get_results_path(), "results.p") + with open(path, "rb") as results_file: results = pickle.load(results_file) data = {"name": name, "job": self.job, "results": results} with open(dst, "wb") as presentation_file: From b3fefb004c789b4c6af9db52903da7ba91c841dc Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Wed, 4 Aug 2021 10:46:55 -0400 Subject: [PATCH 006/186] Added handler to get a job presentation from the cache or from the owners container. --- jupyterhub/job_presentation.py | 120 +++++++++++++++++++++++++++++++++ 1 file changed, 120 insertions(+) create mode 100644 jupyterhub/job_presentation.py diff --git a/jupyterhub/job_presentation.py b/jupyterhub/job_presentation.py new file mode 100644 index 0000000000..36e79e6508 --- /dev/null +++ b/jupyterhub/job_presentation.py @@ -0,0 +1,120 @@ +''' +StochSS is a platform for simulating biochemical systems +Copyright (C) 2019-2021 StochSS developers. + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +''' + +import os +import json +import pickle +import logging + +from presentation_base import StochSSBase, get_presentation_from_user +from presentation_error import StochSSAPIError, report_error + +from jupyterhub.handlers.base import BaseHandler + +log = logging.getLogger('stochss') + +# pylint: disable=abstract-method +# pylint: disable=too-few-public-methods +class JobAPIHandler(BaseHandler): + ''' + ################################################################################################ + Base Handler for getting job presentations from user containers. + ################################################################################################ + ''' + async def get(self): + ''' + Load the job presentation from User's presentations directory. + + Attributes + ---------- + ''' + owner = self.get_query_argument(name="owner") + log.debug("Container id of the owner: %s", owner) + file = self.get_query_argument(name="file") + log.debug("Name to the file: %s", file) + self.set_header('Content-Type', 'application/json') + try: + path = os.path.join("/cache/presentation_cache", file, "job.json") + if os.path.exists(path): + job = StochSSJob(path=path).load() + else: + job = get_presentation_from_user(owner=owner, file=file, kwargs={"file": file}, + process_func=process_job_presentation) + log.debug("Contents of the json file: %s", job) + self.write(job) + except StochSSAPIError as load_err: + report_error(self, log, load_err) + self.finish() + + +def process_job_presentation(path, file=None, for_download=False): + ''' + Get the job presentation data from the file. + + Attributes + ---------- + path : str + Path to the job presentation file. + for_download : bool + Whether or not the job presentation is being downloaded. + ''' + with open(path, "rb") as job_file: + job = pickle.load(job_file) + job['job']['name'] = job['name'] + if not for_download: + dirname = "/cache/presentation_cache" + if not os.path.exists(dirname): + os.mkdir(dirname) + job_dir = os.path.join(dirname, file) + os.mkdir(job_dir) + with open(os.path.join(job_dir, "job.json"), "w") as job_file: + json.dump(job['job'], job_file, sort_keys=True, indent=4) + with open(os.path.join(job_dir, "results.p"), "wb") as res_file: + pickle.dump(job['results'], res_file) + return job['job'] + return job + + +class StochSSJob(StochSSBase): + ''' + ################################################################################################ + StochSS model object + ################################################################################################ + ''' + + def __init__(self, path): + ''' + Intitialize a job object + + Attributes + ---------- + path : str + Path to the job presentation. + ''' + super().__init__(path=path) + + + def load(self): + ''' + Loads a job presentation from cache + + Attributes + ---------- + ''' + with open(self.path, "rb") as job_file: + return json.load(job_file) From 6ab64ceba3df2d05f26a06d4b413062cbe703841 Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Wed, 4 Aug 2021 11:43:14 -0400 Subject: [PATCH 007/186] Added route for loading job presentations. --- jupyterhub/jupyterhub_config.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/jupyterhub/jupyterhub_config.py b/jupyterhub/jupyterhub_config.py index 091e9602ab..55ca6442a9 100644 --- a/jupyterhub/jupyterhub_config.py +++ b/jupyterhub/jupyterhub_config.py @@ -28,6 +28,7 @@ # API Handlers from model_presentation import JsonFileAPIHandler, DownModelPresentationAPIHandler from notebook_presentation import NotebookAPIHandler, DownNotebookPresentationAPIHandler +from job_presentation import JobAPIHandler # Page handlers from handlers import ( HomeHandler, JobPresentationHandler, ModelPresentationHandler, NotebookPresentationHandler @@ -92,7 +93,8 @@ (r"/stochss/download_presentation/(\w+)/(.+)\/?", DownModelPresentationAPIHandler), (r"/stochss/api/notebook/load\/?", NotebookAPIHandler), (r"/stochss/notebook/download_presentation/(\w+)/(.+)\/?", - DownNotebookPresentationAPIHandler) + DownNotebookPresentationAPIHandler), + (r"/stochss/api/job/load\/?", JobAPIHandler) ] ## Paths to search for jinja templates, before using the default templates. From 20108862cdb686cd345860bb91b5f93279e5edcf Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Wed, 4 Aug 2021 16:15:56 -0400 Subject: [PATCH 008/186] Added function to create a return a zip archive for the download api handler. --- jupyterhub/job_presentation.py | 62 ++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/jupyterhub/job_presentation.py b/jupyterhub/job_presentation.py index 36e79e6508..8a6028a6a8 100644 --- a/jupyterhub/job_presentation.py +++ b/jupyterhub/job_presentation.py @@ -18,8 +18,12 @@ import os import json +import shutil import pickle import logging +import tempfile + +from pathlib import Path from presentation_base import StochSSBase, get_presentation_from_user from presentation_error import StochSSAPIError, report_error @@ -87,9 +91,67 @@ def process_job_presentation(path, file=None, for_download=False): with open(os.path.join(job_dir, "results.p"), "wb") as res_file: pickle.dump(job['results'], res_file) return job['job'] + job = make_zip_for_download(job) return job +def make_zip_for_download(job): + ''' + Make an editable job for users to download. + + Attributes + ---------- + job : dict + StochSS job presentation + ''' + tmp_dir = tempfile.TemporaryDirectory() + res_path = os.path.join(tmp_dir.name, job['name'], + '/'.join(job['job']['directory'].split('/')[2:]), "results.p") + Path(res_path).mkdir(parents=True) + with open(res_path, "wb") as res_file: + pickle.dump(job['results'], res_file) + job_path = os.path.dirname(res_path) + Path(os.path.join(job_path, "RUNNING")).touch() + Path(os.path.join(job_path, "COMPLETE")).touch() + write_json(path=os.path.join(job_path, "settings.json"), body=job['job']['settings']) + write_json(path=os.path.join(job_path, job['job']['mdlPath'].split('/').pop()), + body=job['job']['model']) + info = {"annotation": "", "wkfl_model": job['job']['mdlPath'].split('/').pop(), + "start_time": job['job']['startTime'], "type": job['job']['startTime'], + "source_model": os.path.join(job['name'], job['job']['mdlPath'].split('/').pop())} + write_json(path=os.path.join(job_path, "info.json"), body=info) + if "No logs" in job['job']['logs']: + Path(os.path.join(job_path, "logs.txt")).touch() + else: + with open(os.path.join(job_path, "logs.txt"), "w") as logs_file: + logs_file.write(job['job']['logs']) + wkfl_path = os.path.dirname(job_path) + settings = {"model": info['source_model'], "settings": job['job']['settings'], + "type": job['job']['titleType']} + write_json(path=os.path.join(wkfl_path, "settings.json"), body=settings) + write_json(path=os.path.join(os.path.dirname(wkfl_path), + job['job']['mdlPath'].split('/').pop()), body=job['job']['model']) + zip_path = os.path.join(tmp_dir.name, job['name']) + shutil.make_archive(zip_path, "zip", tmp_dir.name, job['name']) + with open(zip_path, "rb") as zip_file: + return zip_file.read() + + +def write_json(path, body): + ''' + Write a json file to disc. + + Attributes + ---------- + path : str + Path to the file. + body : dict + Contents of the file. + ''' + with open(path, "r") as file: + json.dump(body, file, sort_keys=True, indent=4) + + class StochSSJob(StochSSBase): ''' ################################################################################################ From 8ddb8545f455be3d5a57764aab8a450979231807 Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Wed, 4 Aug 2021 17:06:39 -0400 Subject: [PATCH 009/186] Added download api handler for job presentations. --- jupyterhub/job_presentation.py | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/jupyterhub/job_presentation.py b/jupyterhub/job_presentation.py index 8a6028a6a8..07c5d2c34c 100644 --- a/jupyterhub/job_presentation.py +++ b/jupyterhub/job_presentation.py @@ -66,6 +66,31 @@ async def get(self): self.finish() +class DownJobPresentationAPIHandler(BaseHandler): + ''' + ################################################################################################ + Base Handler for downloading job presentations from user containers. + ################################################################################################ + ''' + async def get(self, owner, file): + ''' + Download the job presentation from User's presentations directory. + + Attributes + ---------- + ''' + log.debug("Container id of the owner: %s", owner) + log.debug("Name to the file: %s", file) + self.set_header('Content-Type', 'application/zip') + job, name = get_presentation_from_user(owner=owner, file=file, + kwargs={"for_download": True}, + process_func=process_job_presentation) + ext = file.split(".").pop() + self.set_header('Content-Disposition', f'attachment; filename="{name}.zip"') + self.write(job) + self.finish() + + def process_job_presentation(path, file=None, for_download=False): ''' Get the job presentation data from the file. @@ -92,7 +117,7 @@ def process_job_presentation(path, file=None, for_download=False): pickle.dump(job['results'], res_file) return job['job'] job = make_zip_for_download(job) - return job + return job, job['name'] def make_zip_for_download(job): From 96e106a1da0072a16fcdc683c465201ed23c14f6 Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Wed, 4 Aug 2021 17:12:52 -0400 Subject: [PATCH 010/186] Added route for job presentation download. --- jupyterhub/job_presentation.py | 1 - jupyterhub/jupyterhub_config.py | 5 +++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/jupyterhub/job_presentation.py b/jupyterhub/job_presentation.py index 07c5d2c34c..5e0ece1da6 100644 --- a/jupyterhub/job_presentation.py +++ b/jupyterhub/job_presentation.py @@ -85,7 +85,6 @@ async def get(self, owner, file): job, name = get_presentation_from_user(owner=owner, file=file, kwargs={"for_download": True}, process_func=process_job_presentation) - ext = file.split(".").pop() self.set_header('Content-Disposition', f'attachment; filename="{name}.zip"') self.write(job) self.finish() diff --git a/jupyterhub/jupyterhub_config.py b/jupyterhub/jupyterhub_config.py index 55ca6442a9..0dc6340d87 100644 --- a/jupyterhub/jupyterhub_config.py +++ b/jupyterhub/jupyterhub_config.py @@ -28,7 +28,7 @@ # API Handlers from model_presentation import JsonFileAPIHandler, DownModelPresentationAPIHandler from notebook_presentation import NotebookAPIHandler, DownNotebookPresentationAPIHandler -from job_presentation import JobAPIHandler +from job_presentation import JobAPIHandler, DownJobPresentationAPIHandler # Page handlers from handlers import ( HomeHandler, JobPresentationHandler, ModelPresentationHandler, NotebookPresentationHandler @@ -94,7 +94,8 @@ (r"/stochss/api/notebook/load\/?", NotebookAPIHandler), (r"/stochss/notebook/download_presentation/(\w+)/(.+)\/?", DownNotebookPresentationAPIHandler), - (r"/stochss/api/job/load\/?", JobAPIHandler) + (r"/stochss/api/job/load\/?", JobAPIHandler), + (r"/stochss/job/download_presentation/(\w+)/(.+)\/?", DownJobPresentationAPIHandler) ] ## Paths to search for jinja templates, before using the default templates. From eac274794d09404a46025b9700cc49e490d478bd Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Thu, 5 Aug 2021 09:47:06 -0400 Subject: [PATCH 011/186] Added download button to job presentation template. Added StochSS header to job presentation template. --- client/templates/pages/jobPresentation.pug | 48 +++++++++++++++++++--- 1 file changed, 42 insertions(+), 6 deletions(-) diff --git a/client/templates/pages/jobPresentation.pug b/client/templates/pages/jobPresentation.pug index 34e51b0375..7453a3583c 100644 --- a/client/templates/pages/jobPresentation.pug +++ b/client/templates/pages/jobPresentation.pug @@ -1,13 +1,49 @@ section.page - div#job-presentation.container + nav.navbar.navbar-expand-md.p-1.navbar-light.bg-light.fixed-top + a.navbar-brand(href="/hub/spawn") + img(style="height: 38px;margin-left: -1.2em;" src="/hub/static/stochss-logo.png") - h2=this.model.name + button.navbar-toggler(type="button" data-toggle="collapse" data-target="#navbar" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation") + span.navbar-toggler-icon - div(data-hook="job-results") + div#navbar.collapse.navbar-collapse.justify-content-end(role="navigation") + ul.navbar-nav.float-right + li.nav-item: a.nav-link(href="http://www.stochss.org", target="_blank") + | About - div(data-hook="job-settings") + li.nav-item: a.nav-link(href="https://docs.google.com/forms/d/12tAH4f8CJ-3F-lK44Q9uQHFio_mGoK0oY829q5lD7i4/viewform?embedded=true", target="_blank") + | Register - div(data-hook="job-model") + li.nav-item: a.nav-link(href="http://www.stochss.org/documentation", target="_blank") + | Documentation - a.btn.btn-outline-secondary.box-shadow.text-break(href=this.open role="button") Open in StochSS \ No newline at end of file + li.nav-item: a.nav-link(href="https://forms.gle/hpGJ1ruxR7wTT43h8" target="_blank") + | Submit Feedback + + li.nav-item + div.dropdown + button.dropdown-toggle.nav-item.nav-link.dropbtn Contact + div.dropdown-content + a.nav-link(href="https://gitter.im/StochSS/community#", target="_blank") Community Chat + a.nav-link(href="https://github.com/StochSS/stochss/issues/new", target="_blank") Report Issue + + + + div#job-presentation.container + + div.presentation-header + + h1=this.model.name + + div(data-hook="job-results") + + div(data-hook="job-settings") + + div(data-hook="job-model") + + div.mt-3 + + a.btn.btn-outline-secondary.box-shadow.text-break.mr-3(href=this.open role="button") Open in StochSS + + a.btn.btn-outline-secondary.box-shadow.text-break(href=this.downloadLink role="button") Download \ No newline at end of file From 4ad60fd3ea32427c1a90b8d8847fc0d84d1842ad Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Thu, 5 Aug 2021 09:48:21 -0400 Subject: [PATCH 012/186] Updated the job presentation page to match the other presentation pages. --- client/pages/job-presentation.js | 64 ++++++++++++++++++++------------ 1 file changed, 41 insertions(+), 23 deletions(-) diff --git a/client/pages/job-presentation.js b/client/pages/job-presentation.js index 81cdd53f97..0915a51390 100644 --- a/client/pages/job-presentation.js +++ b/client/pages/job-presentation.js @@ -31,49 +31,67 @@ let ResultsView = require('../views/workflow-results'); let SettingsView = require('../views/settings-viewer'); //templates let template = require('../templates/pages/jobPresentation.pug'); +let loadingTemplate = require('../templates/pages/loadingPage.pug'); +let errorTemplate = require('../templates/pages/errorTemplate.pug'); import bootstrapStyles from '../styles/bootstrap.css'; import styles from '../styles/styles.css'; import fontawesomeStyles from '@fortawesome/fontawesome-free/css/svg-with-js.min.css' let JobPresentationPage = PageView.extend({ - template: template, + template: loadingTemplate, initialize: function () { PageView.prototype.initialize.apply(this, arguments); - console.log("TODO: get the path to the job from the url") - // let urlParams = new URLSearchParams(window.location.search) - // this.model = new Job({ - // directory: urlParams.get("path") - // }); - console.log("TODO: get job from file system using the app.getXHR function") - // let self = this; - // let queryStr = "?path=" + this.model.directory; - // let endpoint = path.join(); - // app.getXHR(endpoint, { - // success: function (err, response, body) { - // self.model.set(body); - // self.model.type = body.titleType; - // self.renderSubviews(); - // } - // }); - console.log("TODO: generate the open link and store in this.open") + let owner = urlParams.get("owner"); + let file = urlParams.get("file"); + this.fileType = "Job" + this.model = new Job({ + directory: file, + }); + let self = this; + let queryStr = "?file=" + file + "&owner=" + owner; + let endpoint = "api/job/load" + queryStr; + app.getXHR(endpoint, { + success: function (err, response, body) { + self.model.set(body); + self.renderSubviews(false); + }, + error: function (err, response, body) { + self.renderSubviews(true); + } + }); + let downloadStart = "https://staging.stochss.org/stochss/job/download_presentation"; + this.downloadLink = downloadStart + "/" + owner + "/" + file; + this.openLink = "https://open.stochss.org?open=" + this.downloadLink; }, - renderSubviews: function () { + render: function (attrs, options) { PageView.prototype.render.apply(this, arguments); - this.renderResultsContainer(); - this.renderSettingsContainer(); - this.renderModelContainer(); + $(this.queryByHook("loading-header")).html(`Loading ${this.fileType}`); + $(this.queryByHook("loading-target")).css("display", "none"); + $(this.queryByHook("loading-spinner")).css("display", "block"); + let message = `This ${this.fileType} can be downloaded or opened in your own StochSS Live! account using the buttons at the bottom of the page.`; + $(this.queryByHook("loading-message")).html(message); + }, + renderSubviews: function (notFound) { + this.template = notFound ? errorTemplate : template + PageView.prototype.render.apply(this, arguments); + if(!notFound) { + this.renderResultsContainer(); + this.renderSettingsContainer(); + this.renderModelContainer(); + } }, renderModelContainer: function () { let modelView = new ModelView({ model: this.model.model, + readOnly: true }); app.registerRenderSubview(this, modelView, "job-model"); }, renderResultsContainer: function () { let resultsView = new ResultsView({ model: this.model, - mode: "presentation" + readOnly: true }); app.registerRenderSubview(this, resultsView, "job-results"); }, From 5ee6ef6de4a41248d5bb8ee98aeedfa866c85e99 Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Thu, 5 Aug 2021 10:25:14 -0400 Subject: [PATCH 013/186] Updated the git ignore. --- .gitignore | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.gitignore b/.gitignore index f425602443..331a71bd17 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,10 @@ stochss-loading-page.html stochss-project-browser.html stochss-quick-start.html stochss-user-home.html +stochss-home.html +stochss-model-presentation.html +stochss-notebook-presentation.html +stochss-job-presentation.html *.swp *.swo package-lock.json From 61582decf1b85384a18f3aaa83c00b7f66477cef Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Thu, 5 Aug 2021 10:29:53 -0400 Subject: [PATCH 014/186] Fixed issue with job results view. --- client/pages/job-presentation.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/pages/job-presentation.js b/client/pages/job-presentation.js index 0915a51390..0bdbf04343 100644 --- a/client/pages/job-presentation.js +++ b/client/pages/job-presentation.js @@ -27,7 +27,7 @@ let Job = require("../models/job"); //views let PageView = require('./base'); let ModelView = require('../model-view/model-view'); -let ResultsView = require('../views/workflow-results'); +let ResultsView = require('../views/job-results-view'); let SettingsView = require('../views/settings-viewer'); //templates let template = require('../templates/pages/jobPresentation.pug'); From 6a27bc41fb679436c7fb0eb7ab0e468e3f99d792 Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Thu, 5 Aug 2021 11:35:37 -0400 Subject: [PATCH 015/186] Added missing urlParam variable. --- client/pages/job-presentation.js | 1 + 1 file changed, 1 insertion(+) diff --git a/client/pages/job-presentation.js b/client/pages/job-presentation.js index 0bdbf04343..7ead76de29 100644 --- a/client/pages/job-presentation.js +++ b/client/pages/job-presentation.js @@ -42,6 +42,7 @@ let JobPresentationPage = PageView.extend({ template: loadingTemplate, initialize: function () { PageView.prototype.initialize.apply(this, arguments); + let urlParams = new URLSearchParams(window.location.search); let owner = urlParams.get("owner"); let file = urlParams.get("file"); this.fileType = "Job" From 9aa4e76160bcbaa5212327d41c3a459c5e41f617 Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Thu, 5 Aug 2021 13:58:56 -0400 Subject: [PATCH 016/186] Added gillespy2 dependency. --- jupyterhub/Dockerfile.jupyterhub | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/jupyterhub/Dockerfile.jupyterhub b/jupyterhub/Dockerfile.jupyterhub index eed510b44b..4d3b913ff6 100644 --- a/jupyterhub/Dockerfile.jupyterhub +++ b/jupyterhub/Dockerfile.jupyterhub @@ -41,7 +41,8 @@ RUN python3 -m pip install --no-cache-dir \ dockerspawner==0.11.* \ psycopg2==2.7.* \ nbviewer==1.0.1 \ - notebook + notebook \ + gillespy2==1.6.3 COPY static/* /usr/local/share/jupyterhub/static/ From 369f0780844f60d39756928b34e406fcf9c76435 Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Thu, 5 Aug 2021 14:23:10 -0400 Subject: [PATCH 017/186] Changed the presentation cache directory location to /tmp. Removed html template files. --- jupyterhub/job_presentation.py | 2 +- jupyterhub/templates/stochss-home.html | 1 - jupyterhub/templates/stochss-job-presentation.html | 1 - jupyterhub/templates/stochss-model-presentation.html | 1 - 4 files changed, 1 insertion(+), 4 deletions(-) delete mode 100644 jupyterhub/templates/stochss-home.html delete mode 100644 jupyterhub/templates/stochss-job-presentation.html delete mode 100644 jupyterhub/templates/stochss-model-presentation.html diff --git a/jupyterhub/job_presentation.py b/jupyterhub/job_presentation.py index 5e0ece1da6..b5f2631504 100644 --- a/jupyterhub/job_presentation.py +++ b/jupyterhub/job_presentation.py @@ -105,7 +105,7 @@ def process_job_presentation(path, file=None, for_download=False): job = pickle.load(job_file) job['job']['name'] = job['name'] if not for_download: - dirname = "/cache/presentation_cache" + dirname = "/tmp/presentation_cache" if not os.path.exists(dirname): os.mkdir(dirname) job_dir = os.path.join(dirname, file) diff --git a/jupyterhub/templates/stochss-home.html b/jupyterhub/templates/stochss-home.html deleted file mode 100644 index e1f8a0bd56..0000000000 --- a/jupyterhub/templates/stochss-home.html +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/jupyterhub/templates/stochss-job-presentation.html b/jupyterhub/templates/stochss-job-presentation.html deleted file mode 100644 index 47c4ea2ab8..0000000000 --- a/jupyterhub/templates/stochss-job-presentation.html +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/jupyterhub/templates/stochss-model-presentation.html b/jupyterhub/templates/stochss-model-presentation.html deleted file mode 100644 index 3092353528..0000000000 --- a/jupyterhub/templates/stochss-model-presentation.html +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file From f5f6574e4b2497d7ab1d4c344061053a298e97ad Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Thu, 5 Aug 2021 14:57:10 -0400 Subject: [PATCH 018/186] Fixed issue with write results pickle for for download. --- jupyterhub/job_presentation.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jupyterhub/job_presentation.py b/jupyterhub/job_presentation.py index b5f2631504..9cc12efd4a 100644 --- a/jupyterhub/job_presentation.py +++ b/jupyterhub/job_presentation.py @@ -130,9 +130,9 @@ def make_zip_for_download(job): ''' tmp_dir = tempfile.TemporaryDirectory() res_path = os.path.join(tmp_dir.name, job['name'], - '/'.join(job['job']['directory'].split('/')[2:]), "results.p") + '/'.join(job['job']['directory'].split('/')[2:]), "results") Path(res_path).mkdir(parents=True) - with open(res_path, "wb") as res_file: + with open(os.path.join(res_path, "results.p"), "wb") as res_file: pickle.dump(job['results'], res_file) job_path = os.path.dirname(res_path) Path(os.path.join(job_path, "RUNNING")).touch() From a29f3cb821af43b20256a4cdd61383973ee979f6 Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Thu, 5 Aug 2021 15:13:46 -0400 Subject: [PATCH 019/186] Fixed issue with writing json files for downloading job presentations. --- jupyterhub/job_presentation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jupyterhub/job_presentation.py b/jupyterhub/job_presentation.py index 9cc12efd4a..159cad7c28 100644 --- a/jupyterhub/job_presentation.py +++ b/jupyterhub/job_presentation.py @@ -172,7 +172,7 @@ def write_json(path, body): body : dict Contents of the file. ''' - with open(path, "r") as file: + with open(path, "w") as file: json.dump(body, file, sort_keys=True, indent=4) From dd1e8a82c35885cb5a37123f74c3d13fb0557aa3 Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Thu, 5 Aug 2021 15:33:29 -0400 Subject: [PATCH 020/186] Changed the name of the zip archive so that it does get mistaken for the target directory. --- jupyterhub/job_presentation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jupyterhub/job_presentation.py b/jupyterhub/job_presentation.py index 159cad7c28..8fb06a950d 100644 --- a/jupyterhub/job_presentation.py +++ b/jupyterhub/job_presentation.py @@ -155,7 +155,7 @@ def make_zip_for_download(job): write_json(path=os.path.join(wkfl_path, "settings.json"), body=settings) write_json(path=os.path.join(os.path.dirname(wkfl_path), job['job']['mdlPath'].split('/').pop()), body=job['job']['model']) - zip_path = os.path.join(tmp_dir.name, job['name']) + zip_path = os.path.join(tmp_dir.name, "job-presentation") shutil.make_archive(zip_path, "zip", tmp_dir.name, job['name']) with open(zip_path, "rb") as zip_file: return zip_file.read() From 627c38293260fb13d62585f7d086c21d6e95b486 Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Thu, 5 Aug 2021 15:54:09 -0400 Subject: [PATCH 021/186] Fixed zip file path for reading zip archive. --- jupyterhub/job_presentation.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jupyterhub/job_presentation.py b/jupyterhub/job_presentation.py index 8fb06a950d..825c783b43 100644 --- a/jupyterhub/job_presentation.py +++ b/jupyterhub/job_presentation.py @@ -155,9 +155,9 @@ def make_zip_for_download(job): write_json(path=os.path.join(wkfl_path, "settings.json"), body=settings) write_json(path=os.path.join(os.path.dirname(wkfl_path), job['job']['mdlPath'].split('/').pop()), body=job['job']['model']) - zip_path = os.path.join(tmp_dir.name, "job-presentation") + zip_path = os.path.join(tmp_dir.name, job['name']) shutil.make_archive(zip_path, "zip", tmp_dir.name, job['name']) - with open(zip_path, "rb") as zip_file: + with open(f"{zip_path}.zip", "rb") as zip_file: return zip_file.read() From e0061ce4614765734c065a97d49080c87d443f83 Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Thu, 5 Aug 2021 16:14:43 -0400 Subject: [PATCH 022/186] Fixed issue with refined var. --- jupyterhub/job_presentation.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jupyterhub/job_presentation.py b/jupyterhub/job_presentation.py index 825c783b43..eb4d9cabf3 100644 --- a/jupyterhub/job_presentation.py +++ b/jupyterhub/job_presentation.py @@ -115,8 +115,8 @@ def process_job_presentation(path, file=None, for_download=False): with open(os.path.join(job_dir, "results.p"), "wb") as res_file: pickle.dump(job['results'], res_file) return job['job'] - job = make_zip_for_download(job) - return job, job['name'] + job_zip = make_zip_for_download(job) + return job_zip, job['name'] def make_zip_for_download(job): From 8343474b83c0b5148949761481d9c8b25dac7272 Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Thu, 5 Aug 2021 16:46:22 -0400 Subject: [PATCH 023/186] Fixed value stored in type in info.json file for job presentation download. --- jupyterhub/job_presentation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jupyterhub/job_presentation.py b/jupyterhub/job_presentation.py index eb4d9cabf3..21bd3476a0 100644 --- a/jupyterhub/job_presentation.py +++ b/jupyterhub/job_presentation.py @@ -141,7 +141,7 @@ def make_zip_for_download(job): write_json(path=os.path.join(job_path, job['job']['mdlPath'].split('/').pop()), body=job['job']['model']) info = {"annotation": "", "wkfl_model": job['job']['mdlPath'].split('/').pop(), - "start_time": job['job']['startTime'], "type": job['job']['startTime'], + "start_time": job['job']['startTime'], "type": job['job']['type'], "source_model": os.path.join(job['name'], job['job']['mdlPath'].split('/').pop())} write_json(path=os.path.join(job_path, "info.json"), body=info) if "No logs" in job['job']['logs']: From 1f4f07821e600ed98461a6a851de0ff4963d26de Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Fri, 13 Aug 2021 10:39:51 -0400 Subject: [PATCH 024/186] Updated the job presentation view to use the new job container. --- client/pages/job-presentation.js | 32 ++++++---------------- client/templates/pages/jobPresentation.pug | 6 +--- 2 files changed, 10 insertions(+), 28 deletions(-) diff --git a/client/pages/job-presentation.js b/client/pages/job-presentation.js index 7901069526..c254f27ecf 100644 --- a/client/pages/job-presentation.js +++ b/client/pages/job-presentation.js @@ -26,9 +26,7 @@ let app = require("../app"); let Job = require("../models/job"); //views let PageView = require('./base'); -let ModelView = require('../model-view/model-view'); -let ResultsView = require('../job-view/views/job-results-view'); -let SettingsView = require('../settings-view/settings-view'); +let JobView = require('../job-view/job-view'); //templates let template = require('../templates/pages/jobPresentation.pug'); let loadingTemplate = require('../templates/pages/loadingPage.pug'); @@ -54,6 +52,7 @@ let JobPresentationPage = PageView.extend({ let endpoint = "api/job/load" + queryStr; app.getXHR(endpoint, { success: function (err, response, body) { + self.titleType = body.titleType; self.model.set(body); self.renderSubviews(false); }, @@ -77,31 +76,18 @@ let JobPresentationPage = PageView.extend({ this.template = notFound ? errorTemplate : template PageView.prototype.render.apply(this, arguments); if(!notFound) { - this.renderResultsContainer(); - this.renderSettingsContainer(); - this.renderModelContainer(); + this.renderJobView(); } }, - renderModelContainer: function () { - let modelView = new ModelView({ - model: this.model.model, - readOnly: true - }); - app.registerRenderSubview(this, modelView, "job-model"); - }, - renderResultsContainer: function () { - let resultsView = new ResultsView({ + renderJobView: function () { + let jobView = new JobView({ model: this.model, + wkflName: this.model.name, + titleType: this.titleType, + newFormat: true, readOnly: true }); - app.registerRenderSubview(this, resultsView, "job-results"); - }, - renderSettingsContainer: function () { - let settingsView = new SettingsView({ - model: this.model.settings, - mode: "presentation" - }); - app.registerRenderSubview(this, settingsView, "job-settings"); + app.registerRenderSubview(this, jobView, "job-view"); } }); diff --git a/client/templates/pages/jobPresentation.pug b/client/templates/pages/jobPresentation.pug index 7453a3583c..6965031cf8 100644 --- a/client/templates/pages/jobPresentation.pug +++ b/client/templates/pages/jobPresentation.pug @@ -36,11 +36,7 @@ section.page h1=this.model.name - div(data-hook="job-results") - - div(data-hook="job-settings") - - div(data-hook="job-model") + div(data-hook="job-view") div.mt-3 From 1ec5319e04c464a1a1d0be4ec5cacb413d21e83f Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Fri, 13 Aug 2021 11:13:08 -0400 Subject: [PATCH 025/186] Added job presentations to the feature list. --- client/templates/pages/home.pug | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/templates/pages/home.pug b/client/templates/pages/home.pug index 6adba77cb5..f65481f675 100644 --- a/client/templates/pages/home.pug +++ b/client/templates/pages/home.pug @@ -22,7 +22,7 @@ section.page li Import SBML Models li Parameter Sweep Workflows li Spatial Simulations - li Publish Models and Notebooks + li Publish Models, Jobs, and Notebooks div.col-md-5 From 6c851a95b01eda77549ee1c2d1a4cb3a9059bfec Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Fri, 13 Aug 2021 11:13:33 -0400 Subject: [PATCH 026/186] Fixed issue with open link. --- client/templates/pages/jobPresentation.pug | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/templates/pages/jobPresentation.pug b/client/templates/pages/jobPresentation.pug index 6965031cf8..cda187b4b7 100644 --- a/client/templates/pages/jobPresentation.pug +++ b/client/templates/pages/jobPresentation.pug @@ -40,6 +40,6 @@ section.page div.mt-3 - a.btn.btn-outline-secondary.box-shadow.text-break.mr-3(href=this.open role="button") Open in StochSS + a.btn.btn-outline-secondary.box-shadow.text-break.mr-3(href=this.openLink role="button") Open in StochSS a.btn.btn-outline-secondary.box-shadow.text-break(href=this.downloadLink role="button") Download \ No newline at end of file From 5c6e9bb847943ff3fffdc3fcaf730b38525ed5df Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Fri, 13 Aug 2021 11:14:09 -0400 Subject: [PATCH 027/186] Fixed issue with check for existing presentations. --- jupyterhub/job_presentation.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jupyterhub/job_presentation.py b/jupyterhub/job_presentation.py index 21bd3476a0..55084f5573 100644 --- a/jupyterhub/job_presentation.py +++ b/jupyterhub/job_presentation.py @@ -53,7 +53,7 @@ async def get(self): log.debug("Name to the file: %s", file) self.set_header('Content-Type', 'application/json') try: - path = os.path.join("/cache/presentation_cache", file, "job.json") + path = os.path.join("/tmp/presentation_cache", file, "job.json") if os.path.exists(path): job = StochSSJob(path=path).load() else: @@ -202,5 +202,5 @@ def load(self): Attributes ---------- ''' - with open(self.path, "rb") as job_file: + with open(self.path, "r") as job_file: return json.load(job_file) From 27abdb2923142555024045da9dbc3cc13e3d2b61 Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Fri, 13 Aug 2021 11:45:49 -0400 Subject: [PATCH 028/186] Fixed incorrect page title. --- client/pages/job-presentation.js | 3 ++- client/templates/pages/jobPresentation.pug | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/client/pages/job-presentation.js b/client/pages/job-presentation.js index c254f27ecf..ac698ec8f0 100644 --- a/client/pages/job-presentation.js +++ b/client/pages/job-presentation.js @@ -52,6 +52,7 @@ let JobPresentationPage = PageView.extend({ let endpoint = "api/job/load" + queryStr; app.getXHR(endpoint, { success: function (err, response, body) { + self.title = body.name; self.titleType = body.titleType; self.model.set(body); self.renderSubviews(false); @@ -82,7 +83,7 @@ let JobPresentationPage = PageView.extend({ renderJobView: function () { let jobView = new JobView({ model: this.model, - wkflName: this.model.name, + wkflName: this.title, titleType: this.titleType, newFormat: true, readOnly: true diff --git a/client/templates/pages/jobPresentation.pug b/client/templates/pages/jobPresentation.pug index cda187b4b7..d57af7ed9b 100644 --- a/client/templates/pages/jobPresentation.pug +++ b/client/templates/pages/jobPresentation.pug @@ -34,7 +34,7 @@ section.page div.presentation-header - h1=this.model.name + h1=this.title div(data-hook="job-view") From f457095ab2512b1c664e3b1d8a74a241bef8f00c Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Fri, 13 Aug 2021 11:47:38 -0400 Subject: [PATCH 029/186] Fixed incorrect directory. --- client/templates/pages/jobPresentation.pug | 2 -- jupyterhub/job_presentation.py | 1 + 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/client/templates/pages/jobPresentation.pug b/client/templates/pages/jobPresentation.pug index d57af7ed9b..1dbe692e82 100644 --- a/client/templates/pages/jobPresentation.pug +++ b/client/templates/pages/jobPresentation.pug @@ -28,8 +28,6 @@ section.page a.nav-link(href="https://gitter.im/StochSS/community#", target="_blank") Community Chat a.nav-link(href="https://github.com/StochSS/stochss/issues/new", target="_blank") Report Issue - - div#job-presentation.container div.presentation-header diff --git a/jupyterhub/job_presentation.py b/jupyterhub/job_presentation.py index 55084f5573..d130ebbc48 100644 --- a/jupyterhub/job_presentation.py +++ b/jupyterhub/job_presentation.py @@ -109,6 +109,7 @@ def process_job_presentation(path, file=None, for_download=False): if not os.path.exists(dirname): os.mkdir(dirname) job_dir = os.path.join(dirname, file) + job['job']['directory'] = job_dir os.mkdir(job_dir) with open(os.path.join(job_dir, "job.json"), "w") as job_file: json.dump(job['job'], job_file, sort_keys=True, indent=4) From b74a61884bc94b7f85219819d30184df782451cb Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Fri, 13 Aug 2021 12:33:10 -0400 Subject: [PATCH 030/186] Added StochSS errors used in generating plots. --- jupyterhub/presentation_error.py | 45 ++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/jupyterhub/presentation_error.py b/jupyterhub/presentation_error.py index 79cfe04e07..4f33d6bb78 100644 --- a/jupyterhub/presentation_error.py +++ b/jupyterhub/presentation_error.py @@ -116,3 +116,48 @@ def __init__(self, msg, trace=None): Error traceback for the error ''' super().__init__(406, "File Data Not JSON Format", msg, trace) + +#################################################################################################### +# Job Errors +#################################################################################################### + +class PlotNotAvailableError(StochSSAPIError): + ''' + ################################################################################################ + StochSS Result Plot Not Found + ################################################################################################ + ''' + + def __init__(self, msg, trace=None): + ''' + Indicates that the requested plot was not found in the plots.json file + + Attributes + ---------- + msg : str + Details on what caused the error + trace : str + Error traceback for the error + ''' + super().__init__(406, "Plot Figure Not Available", msg, trace) + + +class StochSSJobResultsError(StochSSAPIError): + ''' + ################################################################################################ + StochSS Job Results Error + ################################################################################################ + ''' + + def __init__(self, msg, trace=None): + ''' + Indicates that the job results object was corrupted + + Attributes + ---------- + msg : str + Details on what caused the error + trace : str + Error traceback for the error + ''' + super().__init__(500, "Job Results Error", msg, trace) From 8b9e2b89d20b746d2b1df28d9b7b6bba27832456 Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Fri, 13 Aug 2021 12:33:45 -0400 Subject: [PATCH 031/186] Added functions to generate plots from results. --- jupyterhub/job_presentation.py | 307 ++++++++++++++++++++++++++++++++- 1 file changed, 304 insertions(+), 3 deletions(-) diff --git a/jupyterhub/job_presentation.py b/jupyterhub/job_presentation.py index d130ebbc48..e02b86ade7 100644 --- a/jupyterhub/job_presentation.py +++ b/jupyterhub/job_presentation.py @@ -22,11 +22,16 @@ import pickle import logging import tempfile +import traceback from pathlib import Path +import numpy +import plotly + from presentation_base import StochSSBase, get_presentation_from_user -from presentation_error import StochSSAPIError, report_error +from presentation_error import StochSSJobResultsError, StochSSFileNotFoundError, report_error, \ + PlotNotAvailableError, StochSSAPIError from jupyterhub.handlers.base import BaseHandler @@ -53,7 +58,7 @@ async def get(self): log.debug("Name to the file: %s", file) self.set_header('Content-Type', 'application/json') try: - path = os.path.join("/tmp/presentation_cache", file, "job.json") + path = os.path.join("/tmp/presentation_cache", file) if os.path.exists(path): job = StochSSJob(path=path).load() else: @@ -90,6 +95,43 @@ async def get(self, owner, file): self.finish() +class PlotJobResultsAPIHandler(BaseHandler): + ''' + ################################################################################################ + Handler for getting result plots based on plot type. + ################################################################################################ + ''' + async def get(self): + ''' + Retrieve a plot figure of the job results based on the plot type in the request body. + + Attributes + ---------- + ''' + self.set_header('Content-Type', 'application/json') + path = self.get_query_argument(name="path") + log.debug("The path to the workflow: %s", path) + body = json.loads(self.get_query_argument(name='data')) + log.debug("Plot args passed to the plot: %s", body) + try: + job = StochSSJob(path=path) + if body['sim_type'] in ("GillesPy2", "GillesPy2_PS"): + fig = job.get_plot_from_results(data_keys=body['data_keys'], + plt_key=body['plt_key'], add_config=True) + job.print_logs(log) + else: + fig = job.get_psweep_plot_from_results(fixed=body['data_keys'], + kwargs=body['plt_key'], add_config=True) + job.print_logs(log) + if "plt_data" in body.keys(): + fig = job.update_fig_layout(fig=fig, plt_data=body['plt_data']) + log.debug("Plot figure: %s", fig) + self.write(fig) + except StochSSAPIError as err: + report_error(self, log, err) + self.finish() + + def process_job_presentation(path, file=None, for_download=False): ''' Get the job presentation data from the file. @@ -196,6 +238,58 @@ def __init__(self, path): super().__init__(path=path) + def __get_filtered_1d_results(self, f_keys): + results = self.__get_pickled_results() + f_results = [] + for key, result in results.items(): + if self.__is_result_valid(f_keys, key): + f_results.append(result) + return f_results + + + def __get_filtered_2d_results(self, f_keys, param): + results = self.__get_pickled_results() + f_results = [] + for value in param['range']: + p_key = f"{param['name']}:{value}" + p_results = [] + for key, result in results.items(): + if p_key in key.split(',') and self.__is_result_valid(f_keys, key): + p_results.append(result) + f_results.append(p_results) + return f_results + + + @classmethod + def __get_fixed_keys_and_dims(cls, settings, fixed): + p_len = len(settings['parameterSweepSettings']['parameters']) + dims = p_len - len(fixed.keys()) + if dims <= 0: + message = "Too many fixed parameters were provided." + message += "At least one variable parameter is required." + raise StochSSJobResultsError(message) + if dims > 2: + message = "Not enough fixed parameters were provided." + message += "Variable parameters cannot exceed 2." + raise StochSSJobResultsError(message) + f_keys = [f"{name}:{value}" for name, value in fixed.items()] + return dims, f_keys + + + def __get_pickled_results(self): + path = os.path.join(self.path, "results.p") + with open(path, "rb") as results_file: + return pickle.load(results_file) + + + @classmethod + def __is_result_valid(cls, f_keys, key): + for f_key in f_keys: + if f_key not in key.split(','): + return False + return True + + def load(self): ''' Loads a job presentation from cache @@ -203,5 +297,212 @@ def load(self): Attributes ---------- ''' - with open(self.path, "r") as job_file: + with open(os.path.join(self.path, "job.json"), "r") as job_file: return json.load(job_file) + + + def get_plot_from_results(self, data_keys, plt_key, add_config=False): + ''' + Get the plotly figure for the results of a job + + Attributes + ---------- + data_keys : dict + Dictionary of param names and values used to identify the correct data. + plt_key : str + Type of plot to generate. + ''' + self.log("debug", f"Key identifying the plot to generate: {plt_key}") + try: + self.log("info", "Loading the results...") + result = self.__get_pickled_results() + if data_keys: + key = [f"{name}:{value}" for name, value in data_keys.items()] + key = ','.join(key) + result = result[key] + self.log("info", "Generating the plot...") + if plt_key == "mltplplt": + fig = result.plotplotly(return_plotly_figure=True, multiple_graphs=True) + elif plt_key == "stddevran": + fig = result.plotplotly_std_dev_range(return_plotly_figure=True) + else: + if plt_key == "stddev": + result = result.stddev_ensemble() + elif plt_key == "avg": + result = result.average_ensemble() + fig = result.plotplotly(return_plotly_figure=True) + if add_config and plt_key != "mltplplt": + fig["config"] = {"responsive":True} + self.log("info", "Loading the plot...") + return json.loads(json.dumps(fig, cls=plotly.utils.PlotlyJSONEncoder)) + except FileNotFoundError as err: + message = f"Could not find the results pickle file: {str(err)}" + raise StochSSFileNotFoundError(message, traceback.format_exc()) from err + except KeyError as err: + message = f"The requested plot is not available: {str(err)}" + raise PlotNotAvailableError(message, traceback.format_exc()) from err + + + def get_psweep_plot_from_results(self, fixed, kwargs, add_config=False): + ''' + Generate and return the parameter sweep plot form the time series results. + + Attributes + ---------- + fixed : dict + Dictionary for parameters that remain at a fixed value. + kwarps : dict + Dictionary of keys used for post proccessing the results. + ''' + self.log("debug", f"Key identifying the plot to generate: {kwargs}") + settings = self.load()['settings'] + try: + self.log("info", "Loading the results...") + dims, f_keys = self.__get_fixed_keys_and_dims(settings, fixed) + params = list(filter(lambda param: param['name'] not in fixed.keys(), + settings['parameterSweepSettings']['parameters'])) + if dims == 1: + kwargs['param'] = params[0] + kwargs['results'] = self.__get_filtered_1d_results(f_keys) + self.log("info", "Generating the plot...") + fig = ParameterSweep1D.plot(**kwargs) + else: + kwargs['params'] = params + kwargs['results'] = self.__get_filtered_2d_results(f_keys, params[0]) + self.log("info", "Generating the plot...") + fig = ParameterSweep2D.plot(**kwargs) + if add_config: + fig['config'] = {"responsive": True} + self.log("info", "Loading the plot...") + return json.loads(json.dumps(fig, cls=plotly.utils.PlotlyJSONEncoder)) + except FileNotFoundError as err: + message = f"Could not find the results pickle file: {str(err)}" + raise StochSSFileNotFoundError(message, traceback.format_exc()) from err + except KeyError as err: + message = f"The requested plot is not available: {str(err)}" + raise PlotNotAvailableError(message, traceback.format_exc()) from err + + + def update_fig_layout(self, fig=None, plt_data=None): + ''' + Update the figure layout. + + Attributes + ---------- + fig : dict + Plotly figure to be updated + plt_data : dict + Title and axes data for the plot + ''' + self.log("debug", f"Title and axis data for the plot: {plt_data}") + try: + if plt_data is None: + return fig + for key in plt_data.keys(): + if key == "title": + fig['layout']['title']['text'] = plt_data[key] + else: + fig['layout'][key]['title']['text'] = plt_data[key] + return fig + except KeyError as err: + message = f"The requested plot is not available: {str(err)}" + raise PlotNotAvailableError(message, traceback.format_exc()) from err + + +class ParameterSweep1D(): + ''' + ################################################################################################ + StochSS 1D parameter sweep job object + ################################################################################################ + ''' + + @classmethod + def plot(cls, results, species, param, mapper="final", reducer="avg"): + ''' + Plot the results with error bar from time series results. + + Attributes + ---------- + results : list + List of GillesPy2 results objects. + species : str + Species of interest name. + param : dict + StochSS sweep parameter dictionary. + mapper : str + Key indicating the feature extraction function to use. + reducer : str + Key indicating the ensemble aggragation function to use. + ''' + func_map = {"min": numpy.min, "max": numpy.max, "avg": numpy.mean, + "var": numpy.var, "final": lambda res: res[-1]} + map_results = [[func_map[mapper](traj[species]) for traj in result] for result in results] + if len(map_results[0]) > 1: + data = [[func_map[reducer](map_result), + numpy.std(map_result)] for map_result in map_results] + visible = True + else: + data = [[map_result[0], 0] for map_result in map_results] + visible = False + data = numpy.array(data) + + error_y = dict(type="data", array=data[:, 1], visible=visible) + trace_list = [plotly.graph_objs.Scatter(x=param['range'], + y=data[:, 0], error_y=error_y)] + + title = f"Parameter Sweep - Variable: {species}" + layout = plotly.graph_objs.Layout(title=dict(text=title, x=0.5), + xaxis=dict(title=f"{param['name']}"), + yaxis=dict(title="Population")) + + fig = dict(data=trace_list, layout=layout) + return json.loads(json.dumps(fig, cls=plotly.utils.PlotlyJSONEncoder)) + + +class ParameterSweep2D(): + ''' + ################################################################################################ + StochSS 2D parameter sweep job object + ################################################################################################ + ''' + + @classmethod + def plot(cls, results, species, params, mapper="final", reducer="avg"): + ''' + Plot the results with error bar from time series results. + + Attributes + ---------- + results : list + List of GillesPy2 results objects. + species : str + Species of interest name. + params : list + List of StochSS sweep parameter dictionaries. + mapper : str + Key indicating the feature extraction function to use. + reducer : str + Key indicating the ensemble aggragation function to use. + ''' + func_map = {"min": numpy.min, "max": numpy.max, "avg": numpy.mean, + "var": numpy.var, "final": lambda res: res[-1]} + data = [] + for p_results in results: + map_results = [[func_map[mapper](traj[species]) for traj in result] + for result in p_results] + if len(map_results[0]) > 1: + red_results = [func_map[reducer](map_result) for map_result in map_results] + else: + red_results = [map_result[0] for map_result in map_results] + data.append(red_results) + data = numpy.array(data) + + trace_list = [plotly.graph_objs.Heatmap(z=data, x=params[0]['range'], + y=params[1]['range'])] + + title = f"Parameter Sweep - Variable: {species}" + layout = plotly.graph_objs.Layout(title=dict(text=title, x=0.5), + xaxis=dict(title=f"{params[0]['name']}"), + yaxis=dict(title=f"{params[1]['name']}")) + fig = dict(data=trace_list, layout=layout) + return json.loads(json.dumps(fig, cls=plotly.utils.PlotlyJSONEncoder)) From feac34b53884180c550b8faa35447e73ab19f74b Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Fri, 13 Aug 2021 13:13:44 -0400 Subject: [PATCH 032/186] Added route for the plot results api handler. --- jupyterhub/jupyterhub_config.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/jupyterhub/jupyterhub_config.py b/jupyterhub/jupyterhub_config.py index 0dc6340d87..77b5b8e01e 100644 --- a/jupyterhub/jupyterhub_config.py +++ b/jupyterhub/jupyterhub_config.py @@ -28,7 +28,7 @@ # API Handlers from model_presentation import JsonFileAPIHandler, DownModelPresentationAPIHandler from notebook_presentation import NotebookAPIHandler, DownNotebookPresentationAPIHandler -from job_presentation import JobAPIHandler, DownJobPresentationAPIHandler +from job_presentation import JobAPIHandler, DownJobPresentationAPIHandler, PlotJobResultsAPIHandler # Page handlers from handlers import ( HomeHandler, JobPresentationHandler, ModelPresentationHandler, NotebookPresentationHandler @@ -95,7 +95,8 @@ (r"/stochss/notebook/download_presentation/(\w+)/(.+)\/?", DownNotebookPresentationAPIHandler), (r"/stochss/api/job/load\/?", JobAPIHandler), - (r"/stochss/job/download_presentation/(\w+)/(.+)\/?", DownJobPresentationAPIHandler) + (r"/stochss/job/download_presentation/(\w+)/(.+)\/?", DownJobPresentationAPIHandler), + (r"/stochss/api/workflow/plot-results\/?", PlotJobResultsAPIHandler) ] ## Paths to search for jinja templates, before using the default templates. From 4cba143bc630afa0d89afd7a9c5e6ca48e0c7454 Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Fri, 13 Aug 2021 13:29:57 -0400 Subject: [PATCH 033/186] Added api handler for downloading csv files. --- jupyterhub/job_presentation.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/jupyterhub/job_presentation.py b/jupyterhub/job_presentation.py index e02b86ade7..82545efd1c 100644 --- a/jupyterhub/job_presentation.py +++ b/jupyterhub/job_presentation.py @@ -132,6 +132,31 @@ async def get(self): self.finish() +class DownloadCSVAPIHandler(BaseHandler): + ''' + ################################################################################################ + Handler for getting result plots based on plot type. + ################################################################################################ + ''' + async def get(self): + ''' + Retrieve a plot figure of the job results based on the plot type in the request body. + + Attributes + ---------- + ''' + self.set_header('Content-Type', 'application/json') + path = self.get_query_argument(name="path") + log.debug("The path to the workflow: %s", path) + body = json.loads(self.get_query_argument(name='data')) + log.debug("Plot args passed to the plot: %s", body) + try: + job = StochSSJob(path=path) + except StochSSAPIError as err: + report_error(self, log, err) + self.finish() + + def process_job_presentation(path, file=None, for_download=False): ''' Get the job presentation data from the file. From c6b3193d7b4be646572a3d5a3ef1016e57a6324f Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Fri, 13 Aug 2021 13:31:07 -0400 Subject: [PATCH 034/186] Added route for downloading csv files. --- jupyterhub/jupyterhub_config.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/jupyterhub/jupyterhub_config.py b/jupyterhub/jupyterhub_config.py index 77b5b8e01e..5df8d5d76e 100644 --- a/jupyterhub/jupyterhub_config.py +++ b/jupyterhub/jupyterhub_config.py @@ -28,7 +28,9 @@ # API Handlers from model_presentation import JsonFileAPIHandler, DownModelPresentationAPIHandler from notebook_presentation import NotebookAPIHandler, DownNotebookPresentationAPIHandler -from job_presentation import JobAPIHandler, DownJobPresentationAPIHandler, PlotJobResultsAPIHandler +from job_presentation import ( + JobAPIHandler, DownJobPresentationAPIHandler, PlotJobResultsAPIHandler, DownloadCSVAPIHandler +) # Page handlers from handlers import ( HomeHandler, JobPresentationHandler, ModelPresentationHandler, NotebookPresentationHandler @@ -96,7 +98,8 @@ DownNotebookPresentationAPIHandler), (r"/stochss/api/job/load\/?", JobAPIHandler), (r"/stochss/job/download_presentation/(\w+)/(.+)\/?", DownJobPresentationAPIHandler), - (r"/stochss/api/workflow/plot-results\/?", PlotJobResultsAPIHandler) + (r"/stochss/api/workflow/plot-results\/?", PlotJobResultsAPIHandler), + (r"/stochss/api/job/download-csv\/?", DownloadCSVAPIHandler) ] ## Paths to search for jinja templates, before using the default templates. From 3eb22f6de65783e26da6f8b25fe1e61b00ba4ac8 Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Fri, 13 Aug 2021 14:15:50 -0400 Subject: [PATCH 035/186] Removed config from plot object. --- jupyterhub/job_presentation.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jupyterhub/job_presentation.py b/jupyterhub/job_presentation.py index 82545efd1c..8993f166f0 100644 --- a/jupyterhub/job_presentation.py +++ b/jupyterhub/job_presentation.py @@ -117,11 +117,11 @@ async def get(self): job = StochSSJob(path=path) if body['sim_type'] in ("GillesPy2", "GillesPy2_PS"): fig = job.get_plot_from_results(data_keys=body['data_keys'], - plt_key=body['plt_key'], add_config=True) + plt_key=body['plt_key']) job.print_logs(log) else: fig = job.get_psweep_plot_from_results(fixed=body['data_keys'], - kwargs=body['plt_key'], add_config=True) + kwargs=body['plt_key']) job.print_logs(log) if "plt_data" in body.keys(): fig = job.update_fig_layout(fig=fig, plt_data=body['plt_data']) From 16ed9c78f863fa2f1e2e46d63ae2f667cf9bc433 Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Fri, 13 Aug 2021 14:40:38 -0400 Subject: [PATCH 036/186] Attempting to fix plotly error. --- jupyterhub/job_presentation.py | 4 ++-- webpack.hub.config.js | 11 +++++++++++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/jupyterhub/job_presentation.py b/jupyterhub/job_presentation.py index 8993f166f0..82545efd1c 100644 --- a/jupyterhub/job_presentation.py +++ b/jupyterhub/job_presentation.py @@ -117,11 +117,11 @@ async def get(self): job = StochSSJob(path=path) if body['sim_type'] in ("GillesPy2", "GillesPy2_PS"): fig = job.get_plot_from_results(data_keys=body['data_keys'], - plt_key=body['plt_key']) + plt_key=body['plt_key'], add_config=True) job.print_logs(log) else: fig = job.get_psweep_plot_from_results(fixed=body['data_keys'], - kwargs=body['plt_key']) + kwargs=body['plt_key'], add_config=True) job.print_logs(log) if "plt_data" in body.keys(): fig = job.update_fig_layout(fig=fig, plt_data=body['plt_data']) diff --git a/webpack.hub.config.js b/webpack.hub.config.js index 62330fdbe3..52d4f9730c 100644 --- a/webpack.hub.config.js +++ b/webpack.hub.config.js @@ -44,6 +44,17 @@ module.exports = { inject: false }) ], + optimization: { + splitChunks: { + cacheGroups: { + commons: { + name: 'common', + chunks: 'initial', + minChunks: 2 + } + } + } + }, module: { rules: [ { From df94a9e89f4f72890803dc1371c449d1d55d9317 Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Fri, 13 Aug 2021 14:55:49 -0400 Subject: [PATCH 037/186] Added the head template in an attempt to fix plotly error. --- client/pages/job-presentation.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/client/pages/job-presentation.js b/client/pages/job-presentation.js index ac698ec8f0..863743eb53 100644 --- a/client/pages/job-presentation.js +++ b/client/pages/job-presentation.js @@ -28,6 +28,7 @@ let Job = require("../models/job"); let PageView = require('./base'); let JobView = require('../job-view/job-view'); //templates +let headTemplate = require('!pug-loader!../templates/head.pug'); let template = require('../templates/pages/jobPresentation.pug'); let loadingTemplate = require('../templates/pages/loadingPage.pug'); let errorTemplate = require('../templates/pages/errorTemplate.pug'); @@ -67,6 +68,7 @@ let JobPresentationPage = PageView.extend({ }, render: function (attrs, options) { PageView.prototype.render.apply(this, arguments); + document.head.appendChild(domify(headTemplate())); $(this.queryByHook("loading-header")).html(`Loading ${this.fileType}`); $(this.queryByHook("loading-target")).css("display", "none"); $(this.queryByHook("loading-spinner")).css("display", "block"); From b5248ef3cfab3083189b2670f9d10bcc903c9c7e Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Fri, 13 Aug 2021 14:59:25 -0400 Subject: [PATCH 038/186] Added missing requirement. --- client/pages/job-presentation.js | 1 + 1 file changed, 1 insertion(+) diff --git a/client/pages/job-presentation.js b/client/pages/job-presentation.js index 863743eb53..558d76a2d6 100644 --- a/client/pages/job-presentation.js +++ b/client/pages/job-presentation.js @@ -18,6 +18,7 @@ along with this program. If not, see . let $ = require('jquery'); let path = require('path'); +let domify = require('domify'); let domReady = require('domready'); let bootstrap = require('bootstrap'); //support files From 06b008ae100b37b208cb8e8cba5a90202cbea3ae Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Fri, 13 Aug 2021 15:28:16 -0400 Subject: [PATCH 039/186] Added auto render attribute to the job presentation. --- client/pages/job-presentation.js | 1 + 1 file changed, 1 insertion(+) diff --git a/client/pages/job-presentation.js b/client/pages/job-presentation.js index 558d76a2d6..ec7feb5f17 100644 --- a/client/pages/job-presentation.js +++ b/client/pages/job-presentation.js @@ -40,6 +40,7 @@ import fontawesomeStyles from '@fortawesome/fontawesome-free/css/svg-with-js.min let JobPresentationPage = PageView.extend({ template: loadingTemplate, + autoRender: true, initialize: function () { PageView.prototype.initialize.apply(this, arguments); let urlParams = new URLSearchParams(window.location.search); From 3207f18fd52997394abcfd7928c6edc6b9fc7dd3 Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Fri, 13 Aug 2021 16:07:55 -0400 Subject: [PATCH 040/186] Added header append to the render subviews functions. --- client/pages/job-presentation.js | 1 + 1 file changed, 1 insertion(+) diff --git a/client/pages/job-presentation.js b/client/pages/job-presentation.js index ec7feb5f17..2211e7913d 100644 --- a/client/pages/job-presentation.js +++ b/client/pages/job-presentation.js @@ -81,6 +81,7 @@ let JobPresentationPage = PageView.extend({ this.template = notFound ? errorTemplate : template PageView.prototype.render.apply(this, arguments); if(!notFound) { + document.head.appendChild(domify(headTemplate())); this.renderJobView(); } }, From 6af0677fd88551fb18a49266fff8c4ee8c67bce6 Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Fri, 13 Aug 2021 17:38:39 -0400 Subject: [PATCH 041/186] Added missing plotly style tag to head. --- client/pages/job-presentation.js | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/client/pages/job-presentation.js b/client/pages/job-presentation.js index 2211e7913d..831fb7145a 100644 --- a/client/pages/job-presentation.js +++ b/client/pages/job-presentation.js @@ -18,7 +18,6 @@ along with this program. If not, see . let $ = require('jquery'); let path = require('path'); -let domify = require('domify'); let domReady = require('domready'); let bootstrap = require('bootstrap'); //support files @@ -29,7 +28,6 @@ let Job = require("../models/job"); let PageView = require('./base'); let JobView = require('../job-view/job-view'); //templates -let headTemplate = require('!pug-loader!../templates/head.pug'); let template = require('../templates/pages/jobPresentation.pug'); let loadingTemplate = require('../templates/pages/loadingPage.pug'); let errorTemplate = require('../templates/pages/errorTemplate.pug'); @@ -40,7 +38,6 @@ import fontawesomeStyles from '@fortawesome/fontawesome-free/css/svg-with-js.min let JobPresentationPage = PageView.extend({ template: loadingTemplate, - autoRender: true, initialize: function () { PageView.prototype.initialize.apply(this, arguments); let urlParams = new URLSearchParams(window.location.search); @@ -70,7 +67,7 @@ let JobPresentationPage = PageView.extend({ }, render: function (attrs, options) { PageView.prototype.render.apply(this, arguments); - document.head.appendChild(domify(headTemplate())); + document.head.appendChild(''); $(this.queryByHook("loading-header")).html(`Loading ${this.fileType}`); $(this.queryByHook("loading-target")).css("display", "none"); $(this.queryByHook("loading-spinner")).css("display", "block"); @@ -81,7 +78,6 @@ let JobPresentationPage = PageView.extend({ this.template = notFound ? errorTemplate : template PageView.prototype.render.apply(this, arguments); if(!notFound) { - document.head.appendChild(domify(headTemplate())); this.renderJobView(); } }, From 0e970221a9cc14e07a54f4118bed85963f153961 Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Fri, 13 Aug 2021 17:43:36 -0400 Subject: [PATCH 042/186] Added body tag to job presentation template. --- client/templates/pages/jobPresentation.pug | 60 +++++++++++----------- 1 file changed, 31 insertions(+), 29 deletions(-) diff --git a/client/templates/pages/jobPresentation.pug b/client/templates/pages/jobPresentation.pug index 1dbe692e82..704fa749bb 100644 --- a/client/templates/pages/jobPresentation.pug +++ b/client/templates/pages/jobPresentation.pug @@ -1,43 +1,45 @@ -section.page +body - nav.navbar.navbar-expand-md.p-1.navbar-light.bg-light.fixed-top - a.navbar-brand(href="/hub/spawn") - img(style="height: 38px;margin-left: -1.2em;" src="/hub/static/stochss-logo.png") + section.page - button.navbar-toggler(type="button" data-toggle="collapse" data-target="#navbar" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation") - span.navbar-toggler-icon + nav.navbar.navbar-expand-md.p-1.navbar-light.bg-light.fixed-top + a.navbar-brand(href="/hub/spawn") + img(style="height: 38px;margin-left: -1.2em;" src="/hub/static/stochss-logo.png") - div#navbar.collapse.navbar-collapse.justify-content-end(role="navigation") - ul.navbar-nav.float-right - li.nav-item: a.nav-link(href="http://www.stochss.org", target="_blank") - | About + button.navbar-toggler(type="button" data-toggle="collapse" data-target="#navbar" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation") + span.navbar-toggler-icon - li.nav-item: a.nav-link(href="https://docs.google.com/forms/d/12tAH4f8CJ-3F-lK44Q9uQHFio_mGoK0oY829q5lD7i4/viewform?embedded=true", target="_blank") - | Register + div#navbar.collapse.navbar-collapse.justify-content-end(role="navigation") + ul.navbar-nav.float-right + li.nav-item: a.nav-link(href="http://www.stochss.org", target="_blank") + | About - li.nav-item: a.nav-link(href="http://www.stochss.org/documentation", target="_blank") - | Documentation + li.nav-item: a.nav-link(href="https://docs.google.com/forms/d/12tAH4f8CJ-3F-lK44Q9uQHFio_mGoK0oY829q5lD7i4/viewform?embedded=true", target="_blank") + | Register - li.nav-item: a.nav-link(href="https://forms.gle/hpGJ1ruxR7wTT43h8" target="_blank") - | Submit Feedback + li.nav-item: a.nav-link(href="http://www.stochss.org/documentation", target="_blank") + | Documentation - li.nav-item - div.dropdown - button.dropdown-toggle.nav-item.nav-link.dropbtn Contact - div.dropdown-content - a.nav-link(href="https://gitter.im/StochSS/community#", target="_blank") Community Chat - a.nav-link(href="https://github.com/StochSS/stochss/issues/new", target="_blank") Report Issue + li.nav-item: a.nav-link(href="https://forms.gle/hpGJ1ruxR7wTT43h8" target="_blank") + | Submit Feedback - div#job-presentation.container + li.nav-item + div.dropdown + button.dropdown-toggle.nav-item.nav-link.dropbtn Contact + div.dropdown-content + a.nav-link(href="https://gitter.im/StochSS/community#", target="_blank") Community Chat + a.nav-link(href="https://github.com/StochSS/stochss/issues/new", target="_blank") Report Issue - div.presentation-header + div#job-presentation.container - h1=this.title + div.presentation-header - div(data-hook="job-view") + h1=this.title - div.mt-3 + div(data-hook="job-view") - a.btn.btn-outline-secondary.box-shadow.text-break.mr-3(href=this.openLink role="button") Open in StochSS + div.mt-3 - a.btn.btn-outline-secondary.box-shadow.text-break(href=this.downloadLink role="button") Download \ No newline at end of file + a.btn.btn-outline-secondary.box-shadow.text-break.mr-3(href=this.openLink role="button") Open in StochSS + + a.btn.btn-outline-secondary.box-shadow.text-break(href=this.downloadLink role="button") Download \ No newline at end of file From 3853bc2ffdeacee591a1a014c06ea8c68af19802 Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Fri, 13 Aug 2021 18:14:51 -0400 Subject: [PATCH 043/186] Added body tags to presentation templates. Removed previously added style tag. --- client/pages/job-presentation.js | 1 - client/templates/pages/home.pug | 76 ++++++++++--------- client/templates/pages/modelPresentation.pug | 60 ++++++++------- .../templates/pages/notebookPresentation.pug | 62 +++++++-------- 4 files changed, 102 insertions(+), 97 deletions(-) diff --git a/client/pages/job-presentation.js b/client/pages/job-presentation.js index 831fb7145a..ac698ec8f0 100644 --- a/client/pages/job-presentation.js +++ b/client/pages/job-presentation.js @@ -67,7 +67,6 @@ let JobPresentationPage = PageView.extend({ }, render: function (attrs, options) { PageView.prototype.render.apply(this, arguments); - document.head.appendChild(''); $(this.queryByHook("loading-header")).html(`Loading ${this.fileType}`); $(this.queryByHook("loading-target")).css("display", "none"); $(this.queryByHook("loading-spinner")).css("display", "block"); diff --git a/client/templates/pages/home.pug b/client/templates/pages/home.pug index f65481f675..a23a82beb9 100644 --- a/client/templates/pages/home.pug +++ b/client/templates/pages/home.pug @@ -1,50 +1,52 @@ -section.page - div#home-wrapper.container.card.card-body - - div.logo(id="stochss-logo" data-hook="stochss-logo") - img(src="/hub/static/stochss-logo.png") +body - div.centered - h1.display-5 Welcome to StochSS Live! + section.page + div#home-wrapper.container.card.card-body + + div.logo(id="stochss-logo" data-hook="stochss-logo") + img(src="/hub/static/stochss-logo.png") - p.home-p - | An accessible platform for modeling mathematical, biological, and biochemical systems. + div.centered + h1.display-5 Welcome to StochSS Live! - div.row.home-highlights - div.col-md-2 - div.col-md-5 + p.home-p + | An accessible platform for modeling mathematical, biological, and biochemical systems. - h5 Features - ul.home-list - li Discrete Stochastic Modeling - li Continuous ODE Modeling - li Convert Between Stochastic and ODE Models - li Import SBML Models - li Parameter Sweep Workflows - li Spatial Simulations - li Publish Models, Jobs, and Notebooks - - div.col-md-5 + div.row.home-highlights + div.col-md-2 + div.col-md-5 - h5 Coming soon... - ul.home-list - li Sciope Integration - li Bring Your Own Cloud + h5 Features + ul.home-list + li Discrete Stochastic Modeling + li Continuous ODE Modeling + li Convert Between Stochastic and ODE Models + li Import SBML Models + li Parameter Sweep Workflows + li Spatial Simulations + li Publish Models, Jobs, and Notebooks + + div.col-md-5 - div.centered - a.try-stochss-btn.btn.btn-primary.box-shadow(href="/hub/spawn" role="button") Click here to login + h5 Coming soon... + ul.home-list + li Sciope Integration + li Bring Your Own Cloud - div#registration-home.container.card.card-body + div.centered + a.try-stochss-btn.btn.btn-primary.box-shadow(href="/hub/spawn" role="button") Click here to login - div.centered + div#registration-home.container.card.card-body - p.home-p - | StochSS is open-source software. To obtain continued funding for sustained development, we must show that StochSS is making an impact in the scientific community. For this, we need to know who is using the software, and a bit about what they are using it for. Please consider registering StochSS to show your support. + div.centered - div.collapse.show(data-hook="registration-link") + p.home-p + | StochSS is open-source software. To obtain continued funding for sustained development, we must show that StochSS is making an impact in the scientific community. For this, we need to know who is using the software, and a bit about what they are using it for. Please consider registering StochSS to show your support. - button.register-stochss-btn.btn.btn-primary.box-shadow(data-hook="registration-link-button" style="margin: 1.5rem;") Register Stochss + div.collapse.show(data-hook="registration-link") - div.collapse(data-hook="registration-form" id="registration-form") + button.register-stochss-btn.btn.btn-primary.box-shadow(data-hook="registration-link-button" style="margin: 1.5rem;") Register Stochss - iframe(src="https://docs.google.com/forms/d/12tAH4f8CJ-3F-lK44Q9uQHFio_mGoK0oY829q5lD7i4/viewform?embedded=true" width="100%" height="800" frameborder="0") + div.collapse(data-hook="registration-form" id="registration-form") + + iframe(src="https://docs.google.com/forms/d/12tAH4f8CJ-3F-lK44Q9uQHFio_mGoK0oY829q5lD7i4/viewform?embedded=true" width="100%" height="800" frameborder="0") diff --git a/client/templates/pages/modelPresentation.pug b/client/templates/pages/modelPresentation.pug index f5735e7810..9526fb2c1b 100644 --- a/client/templates/pages/modelPresentation.pug +++ b/client/templates/pages/modelPresentation.pug @@ -1,43 +1,45 @@ -section.page +body - nav.navbar.navbar-expand-md.p-1.navbar-light.bg-light.fixed-top - a.navbar-brand(href="/hub/spawn") - img(style="height: 38px;margin-left: -1.2em;" src="/hub/static/stochss-logo.png") + section.page - button.navbar-toggler(type="button" data-toggle="collapse" data-target="#navbar" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation") - span.navbar-toggler-icon + nav.navbar.navbar-expand-md.p-1.navbar-light.bg-light.fixed-top + a.navbar-brand(href="/hub/spawn") + img(style="height: 38px;margin-left: -1.2em;" src="/hub/static/stochss-logo.png") - div#navbar.collapse.navbar-collapse.justify-content-end(role="navigation") - ul.navbar-nav.float-right - li.nav-item: a.nav-link(href="http://www.stochss.org", target="_blank") - | About + button.navbar-toggler(type="button" data-toggle="collapse" data-target="#navbar" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation") + span.navbar-toggler-icon - li.nav-item: a.nav-link(href="https://docs.google.com/forms/d/12tAH4f8CJ-3F-lK44Q9uQHFio_mGoK0oY829q5lD7i4/viewform?embedded=true", target="_blank") - | Register + div#navbar.collapse.navbar-collapse.justify-content-end(role="navigation") + ul.navbar-nav.float-right + li.nav-item: a.nav-link(href="http://www.stochss.org", target="_blank") + | About - li.nav-item: a.nav-link(href="http://www.stochss.org/documentation", target="_blank") - | Documentation + li.nav-item: a.nav-link(href="https://docs.google.com/forms/d/12tAH4f8CJ-3F-lK44Q9uQHFio_mGoK0oY829q5lD7i4/viewform?embedded=true", target="_blank") + | Register - li.nav-item: a.nav-link(href="https://forms.gle/hpGJ1ruxR7wTT43h8" target="_blank") - | Submit Feedback + li.nav-item: a.nav-link(href="http://www.stochss.org/documentation", target="_blank") + | Documentation - li.nav-item - div.dropdown - button.dropdown-toggle.nav-item.nav-link.dropbtn Contact - div.dropdown-content - a.nav-link(href="https://gitter.im/StochSS/community#", target="_blank") Community Chat - a.nav-link(href="https://github.com/StochSS/stochss/issues/new", target="_blank") Report Issue + li.nav-item: a.nav-link(href="https://forms.gle/hpGJ1ruxR7wTT43h8" target="_blank") + | Submit Feedback - div#model-presentation.container + li.nav-item + div.dropdown + button.dropdown-toggle.nav-item.nav-link.dropbtn Contact + div.dropdown-content + a.nav-link(href="https://gitter.im/StochSS/community#", target="_blank") Community Chat + a.nav-link(href="https://github.com/StochSS/stochss/issues/new", target="_blank") Report Issue - div.presentation-header + div#model-presentation.container - h1=this.model.name + div.presentation-header - div(data-hook="model-view") + h1=this.model.name - div.mt-3 + div(data-hook="model-view") - a.btn.btn-outline-secondary.box-shadow.text-break.mr-3(href=this.openLink role="button") Open in StochSS + div.mt-3 - a.btn.btn-outline-secondary.box-shadow.text-break(href=this.downloadLink role="button") Download (.mdl) + a.btn.btn-outline-secondary.box-shadow.text-break.mr-3(href=this.openLink role="button") Open in StochSS + + a.btn.btn-outline-secondary.box-shadow.text-break(href=this.downloadLink role="button") Download (.mdl) diff --git a/client/templates/pages/notebookPresentation.pug b/client/templates/pages/notebookPresentation.pug index ba03a11038..679beb8515 100644 --- a/client/templates/pages/notebookPresentation.pug +++ b/client/templates/pages/notebookPresentation.pug @@ -1,45 +1,47 @@ -section.page +body - nav.navbar.navbar-expand-md.p-1.navbar-light.bg-light.fixed-top - a.navbar-brand(href="/hub/spawn") - img(style="height: 38px;margin-left: -1.2em;" src="/hub/static/stochss-logo.png") + section.page - button.navbar-toggler(type="button" data-toggle="collapse" data-target="#navbar" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation") - span.navbar-toggler-icon + nav.navbar.navbar-expand-md.p-1.navbar-light.bg-light.fixed-top + a.navbar-brand(href="/hub/spawn") + img(style="height: 38px;margin-left: -1.2em;" src="/hub/static/stochss-logo.png") - div#navbar.collapse.navbar-collapse.justify-content-end(role="navigation") - ul.navbar-nav.float-right - li.nav-item: a.nav-link(href="http://www.stochss.org", target="_blank") - | About + button.navbar-toggler(type="button" data-toggle="collapse" data-target="#navbar" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation") + span.navbar-toggler-icon - li.nav-item: a.nav-link(href="https://docs.google.com/forms/d/12tAH4f8CJ-3F-lK44Q9uQHFio_mGoK0oY829q5lD7i4/viewform?embedded=true", target="_blank") - | Register + div#navbar.collapse.navbar-collapse.justify-content-end(role="navigation") + ul.navbar-nav.float-right + li.nav-item: a.nav-link(href="http://www.stochss.org", target="_blank") + | About - li.nav-item: a.nav-link(href="http://www.stochss.org/documentation", target="_blank") - | Documentation + li.nav-item: a.nav-link(href="https://docs.google.com/forms/d/12tAH4f8CJ-3F-lK44Q9uQHFio_mGoK0oY829q5lD7i4/viewform?embedded=true", target="_blank") + | Register - li.nav-item: a.nav-link(href="https://forms.gle/hpGJ1ruxR7wTT43h8" target="_blank") - | Submit Feedback + li.nav-item: a.nav-link(href="http://www.stochss.org/documentation", target="_blank") + | Documentation - li.nav-item - div.dropdown - button.dropdown-toggle.nav-item.nav-link.dropbtn Contact - div.dropdown-content - a.nav-link(href="https://gitter.im/StochSS/community#", target="_blank") Community Chat - a.nav-link(href="https://github.com/StochSS/stochss/issues/new", target="_blank") Report Issue + li.nav-item: a.nav-link(href="https://forms.gle/hpGJ1ruxR7wTT43h8" target="_blank") + | Submit Feedback - div#notebook-presentation.container + li.nav-item + div.dropdown + button.dropdown-toggle.nav-item.nav-link.dropbtn Contact + div.dropdown-content + a.nav-link(href="https://gitter.im/StochSS/community#", target="_blank") Community Chat + a.nav-link(href="https://github.com/StochSS/stochss/issues/new", target="_blank") Report Issue - div.presentation-header + div#notebook-presentation.container - h1=this.name + div.presentation-header - div.card.card-body + h1=this.name - iframe(id="notebook" width="100%" height="100%" frameborder="0" onload="this.height=this.contentWindow.document.body.scrollHeight;") + div.card.card-body - div.mt-3 + iframe(id="notebook" width="100%" height="100%" frameborder="0" onload="this.height=this.contentWindow.document.body.scrollHeight;") - a.btn.btn-outline-secondary.box-shadow.text-break.mr-3(href=this.openLink role="button") Open in StochSS + div.mt-3 - a.btn.btn-outline-secondary.box-shadow.text-break(href=this.downloadLink role="button") Download (.ipynb) + a.btn.btn-outline-secondary.box-shadow.text-break.mr-3(href=this.openLink role="button") Open in StochSS + + a.btn.btn-outline-secondary.box-shadow.text-break(href=this.downloadLink role="button") Download (.ipynb) From 26eae371c503d87df9422ab3a207dc2a1a3416a6 Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Fri, 13 Aug 2021 18:15:34 -0400 Subject: [PATCH 044/186] Fixed issue with view presentation button. --- client/job-view/views/job-results-view.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/client/job-view/views/job-results-view.js b/client/job-view/views/job-results-view.js index 53c2687d4b..91002317d2 100644 --- a/client/job-view/views/job-results-view.js +++ b/client/job-view/views/job-results-view.js @@ -319,8 +319,7 @@ module.exports = View.extend({ let title = body.message; let linkHeaders = "Shareable Presentation Link"; let links = body.links; - let name = self.model.name - $(modals.presentationLinks(title, name, linkHeaders, links)).modal(); + $(modals.presentationLinks(title, linkHeaders, links)).modal(); let copyBtn = document.querySelector('#presentationLinksModal #copy-to-clipboard'); copyBtn.addEventListener('click', function (e) { app.copyToClipboard(links.presentation) From 21779aaf692682cb037a95e9db10fb4808c968a1 Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Mon, 16 Aug 2021 09:01:53 -0400 Subject: [PATCH 045/186] Added the multiple plots page to the presentation pages. --- jupyterhub/handlers.py | 17 +++++++++++++++++ webpack.hub.config.js | 10 +++++++++- 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/jupyterhub/handlers.py b/jupyterhub/handlers.py index d891ed670f..ca700b20e7 100644 --- a/jupyterhub/handlers.py +++ b/jupyterhub/handlers.py @@ -86,3 +86,20 @@ async def get(self): ''' html = self.render_template("stochss-notebook-presentation.html") self.finish(html) + + +class MultiplePlotsHandler(BaseHandler): + ''' + ################################################################################################ + Handler for rendering jupyterhub job presentations multiple plots page. + ################################################################################################ + ''' + async def get(self): + ''' + Render the jupyterhub job presentations multiple plots page. + + Attributes + ---------- + ''' + html = self.render_template("multiple-plots-page.html") + self.finish(html) diff --git a/webpack.hub.config.js b/webpack.hub.config.js index 52d4f9730c..06ca57e61a 100644 --- a/webpack.hub.config.js +++ b/webpack.hub.config.js @@ -7,7 +7,8 @@ module.exports = { home: './client/pages/home.js', jobPresentation: './client/pages/job-presentation.js', modelPresentation: './client/pages/model-presentation.js', - notebookPresentation: './client/pages/notebook-presentation.js' + notebookPresentation: './client/pages/notebook-presentation.js', + multiplePlots: './client/pages/multiple-plots.js' }, output: { filename: 'stochss-[name].bundle.js', @@ -42,6 +43,13 @@ module.exports = { template: 'jupyterhub/home_template.pug', name: 'notebookPresentation', inject: false + }), + new HtmlWebpackPlugin({ + title: "StochSS | Multiple Plots Presentation Page", + filename: '../templates/multiple-plots-page.html', + template: 'jupyterhub/home_template.pug', + name: 'multiplePlots', + inject: false }) ], optimization: { From eb92712e4c0fda41dd80aff63cfa053d5e81de2b Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Mon, 16 Aug 2021 09:02:47 -0400 Subject: [PATCH 046/186] Added a new template for multiple plots that will be used for presentations. --- client/pages/multiple-plots.js | 3 ++- client/templates/pages/multiplePlotsPresentation.pug | 11 +++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 client/templates/pages/multiplePlotsPresentation.pug diff --git a/client/pages/multiple-plots.js b/client/pages/multiple-plots.js index 108f9b60ce..84ea3cbd4a 100644 --- a/client/pages/multiple-plots.js +++ b/client/pages/multiple-plots.js @@ -24,11 +24,11 @@ let Plotly = require('../lib/plotly'); var PageView = require('../pages/base'); //templates let template = require("../templates/pages/multiplePlots.pug"); +let presTemplate = require("../templates/pages/multiplePlotsPresentation.pug"); import initPage from './page.js'; let ModelEditor = PageView.extend({ - template: template, initialize: function (attrs, options) { PageView.prototype.initialize.apply(this, arguments); let urlParams = new URLSearchParams(window.location.search); @@ -36,6 +36,7 @@ let ModelEditor = PageView.extend({ this.job = urlParams.get("job"); this.path = urlParams.get("path"); this.data = urlParams.get("data"); + this.template = this.path.includes("presentation_cache") ? presTemplate : template; }, render: function (attrs, options) { PageView.prototype.render.apply(this, arguments); diff --git a/client/templates/pages/multiplePlotsPresentation.pug b/client/templates/pages/multiplePlotsPresentation.pug new file mode 100644 index 0000000000..d53915d27f --- /dev/null +++ b/client/templates/pages/multiplePlotsPresentation.pug @@ -0,0 +1,11 @@ +body + + section.page + + h3="Workflow: "+this.workflow + + div.card.card-body + + h5 Plot Trajectories: Multiple Graphs + + div(data-hook="figures") \ No newline at end of file From 373efbc7e081828b585de2f0afad4133e1d00916 Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Mon, 16 Aug 2021 09:48:18 -0400 Subject: [PATCH 047/186] Added an info block to inform users that job can only be published if the workflow is in the new format. --- .../templates/gillespyResultsEnsembleView.pug | 25 +++++++++++-------- .../templates/gillespyResultsView.pug | 25 +++++++++++-------- .../templates/parameterScanResultsView.pug | 21 ++++++++++------ .../templates/parameterSweepResultsView.pug | 25 +++++++++++-------- client/job-view/views/job-results-view.js | 3 +++ 5 files changed, 61 insertions(+), 38 deletions(-) diff --git a/client/job-view/templates/gillespyResultsEnsembleView.pug b/client/job-view/templates/gillespyResultsEnsembleView.pug index 3bcdc2856e..3292ac0dd8 100644 --- a/client/job-view/templates/gillespyResultsEnsembleView.pug +++ b/client/job-view/templates/gillespyResultsEnsembleView.pug @@ -183,22 +183,27 @@ div#workflow-results.card disabled ) Download JSON - button.btn.btn-primary.box-shadow(id="convert-to-notebook" data-hook="convert-to-notebook") Convert to Notebook + div - button.btn.btn-primary.box-shadow(id="download-results-csv" data-hook="download-results-csv") Download Results as .csv + button.btn.btn-primary.box-shadow(id="convert-to-notebook" data-hook="convert-to-notebook") Convert to Notebook - button.btn.btn-primary.box-shadow(id="job-presentation" data-hook="job-presentation") Publish + button.btn.btn-primary.box-shadow(id="download-results-csv" data-hook="download-results-csv") Download Results as .csv - div.saving-status(data-hook="job-action-start") + button.btn.btn-primary.box-shadow(id="job-presentation" data-hook="job-presentation") Publish - div.spinner-grow + div.saving-status(data-hook="job-action-start") - span Publishing ... + div.spinner-grow - div.saved-status(data-hook="job-action-end") + span Publishing ... - span Published + div.saved-status(data-hook="job-action-end") - div.save-error-status(data-hook="job-action-err") + span Published - span Error + div.save-error-status(data-hook="job-action-err") + + span Error + + div.text-info(data-hook="update-format-message" style="display: none;") + | To publish you job the workflows format must be updated. diff --git a/client/job-view/templates/gillespyResultsView.pug b/client/job-view/templates/gillespyResultsView.pug index a7a9814814..6985ade660 100644 --- a/client/job-view/templates/gillespyResultsView.pug +++ b/client/job-view/templates/gillespyResultsView.pug @@ -64,22 +64,27 @@ div#workflow-results.card disabled ) Download JSON - button.btn.btn-primary.box-shadow(id="convert-to-notebook" data-hook="convert-to-notebook") Convert to Notebook + div - button.btn.btn-primary.box-shadow(id="download-results-csv" data-hook="download-results-csv") Download Results as .csv + button.btn.btn-primary.box-shadow(id="convert-to-notebook" data-hook="convert-to-notebook") Convert to Notebook - button.btn.btn-primary.box-shadow(id="job-presentation" data-hook="job-presentation") Publish + button.btn.btn-primary.box-shadow(id="download-results-csv" data-hook="download-results-csv") Download Results as .csv - div.saving-status(data-hook="job-action-start") + button.btn.btn-primary.box-shadow(id="job-presentation" data-hook="job-presentation") Publish - div.spinner-grow + div.saving-status(data-hook="job-action-start") - span Publishing ... + div.spinner-grow - div.saved-status(data-hook="job-action-end") + span Publishing ... - span Published + div.saved-status(data-hook="job-action-end") - div.save-error-status(data-hook="job-action-err") + span Published - span Error + div.save-error-status(data-hook="job-action-err") + + span Error + + div.text-info(data-hook="update-format-message" style="display: none;") + | To publish you job the workflows format must be updated. diff --git a/client/job-view/templates/parameterScanResultsView.pug b/client/job-view/templates/parameterScanResultsView.pug index 94add97d48..685ade51d5 100644 --- a/client/job-view/templates/parameterScanResultsView.pug +++ b/client/job-view/templates/parameterScanResultsView.pug @@ -185,18 +185,23 @@ div#workflow-results.card div.mt-3(data-hook="ps-parameter-ranges") - button.btn.btn-primary.box-shadow(id="job-presentation" data-hook="job-presentation") Publish Presentation + div - div.saving-status(data-hook="job-action-start") + button.btn.btn-primary.box-shadow(id="job-presentation" data-hook="job-presentation") Publish Presentation - div.spinner-grow + div.saving-status(data-hook="job-action-start") - span Publishing ... + div.spinner-grow - div.saved-status(data-hook="job-action-end") + span Publishing ... - span Published + div.saved-status(data-hook="job-action-end") - div.save-error-status(data-hook="job-action-err") + span Published - span Error + div.save-error-status(data-hook="job-action-err") + + span Error + + div.text-info(data-hook="update-format-message" style="display: none;") + | To publish you job the workflows format must be updated. diff --git a/client/job-view/templates/parameterSweepResultsView.pug b/client/job-view/templates/parameterSweepResultsView.pug index 10676fc3eb..a380284b83 100644 --- a/client/job-view/templates/parameterSweepResultsView.pug +++ b/client/job-view/templates/parameterSweepResultsView.pug @@ -163,22 +163,27 @@ div#workflow-results.card disabled ) Download JSON - button.btn.btn-primary.box-shadow(id="convert-to-notebook" data-hook="convert-to-notebook") Convert to Notebook + div - button.btn.btn-primary.box-shadow(id="download-results-csv" data-hook="download-results-csv") Download Results as .csv + button.btn.btn-primary.box-shadow(id="convert-to-notebook" data-hook="convert-to-notebook") Convert to Notebook - button.btn.btn-primary.box-shadow(id="job-presentation" data-hook="job-presentation") Publish + button.btn.btn-primary.box-shadow(id="download-results-csv" data-hook="download-results-csv") Download Results as .csv - div.saving-status(data-hook="job-action-start") + button.btn.btn-primary.box-shadow(id="job-presentation" data-hook="job-presentation") Publish - div.spinner-grow + div.saving-status(data-hook="job-action-start") - span Publishing ... + div.spinner-grow - div.saved-status(data-hook="job-action-end") + span Publishing ... - span Published + div.saved-status(data-hook="job-action-end") - div.save-error-status(data-hook="job-action-err") + span Published - span Error + div.save-error-status(data-hook="job-action-err") + + span Error + + div.text-info(data-hook="update-format-message" style="display: none;") + | To publish you job the workflows format must be updated. diff --git a/client/job-view/views/job-results-view.js b/client/job-view/views/job-results-view.js index 91002317d2..c0b721639b 100644 --- a/client/job-view/views/job-results-view.js +++ b/client/job-view/views/job-results-view.js @@ -79,6 +79,9 @@ module.exports = View.extend({ } }else if(app.getBasePath() === "/") { $(this.queryByHook("job-presentation")).css("display", "none"); + }else if(!this.parent.newFormat) { + $(this.queryByHook("job-presentation")).prop("disabled", true); + $(this.queryByHook("update-format-message")).css("display", "block"); } if(this.titleType === "Ensemble Simulation") { var type = isEnsemble ? "stddevran" : "trajectories"; From 502de86035c8d12f4ef14acb6f5e63dd127add57 Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Mon, 16 Aug 2021 09:53:49 -0400 Subject: [PATCH 048/186] Added route for the multiple plots presentation page. --- jupyterhub/jupyterhub_config.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/jupyterhub/jupyterhub_config.py b/jupyterhub/jupyterhub_config.py index 5df8d5d76e..7412f5e1c0 100644 --- a/jupyterhub/jupyterhub_config.py +++ b/jupyterhub/jupyterhub_config.py @@ -33,7 +33,8 @@ ) # Page handlers from handlers import ( - HomeHandler, JobPresentationHandler, ModelPresentationHandler, NotebookPresentationHandler + HomeHandler, JobPresentationHandler, ModelPresentationHandler, NotebookPresentationHandler, + MultiplePlotsHandler ) ## Class for authenticating users. @@ -91,6 +92,7 @@ (r"/stochss/present-job\/?", JobPresentationHandler), (r"/stochss/present-model\/?", ModelPresentationHandler), (r"/stochss/present-notebook\/?", NotebookPresentationHandler), + (r"/stochss/multiple-plots\/?", MultiplePlotsHandler), (r"/stochss/api/file/json-data\/?", JsonFileAPIHandler), (r"/stochss/download_presentation/(\w+)/(.+)\/?", DownModelPresentationAPIHandler), (r"/stochss/api/notebook/load\/?", NotebookAPIHandler), From 63dc3d6a0f35985482f0b90d7f8270a7aac87e33 Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Mon, 16 Aug 2021 11:59:34 -0400 Subject: [PATCH 049/186] Added function to get zipped csv data for ensemble result objects. --- jupyterhub/job_presentation.py | 56 +++++++++++++-- stochss/handlers/util/stochss_job.py | 102 ++++++++++++++------------- 2 files changed, 104 insertions(+), 54 deletions(-) diff --git a/jupyterhub/job_presentation.py b/jupyterhub/job_presentation.py index 82545efd1c..251ecce6b0 100644 --- a/jupyterhub/job_presentation.py +++ b/jupyterhub/job_presentation.py @@ -285,6 +285,15 @@ def __get_filtered_2d_results(self, f_keys, param): return f_results + def __get_filtered_ensemble_results(self, data_keys): + result = self.__get_pickled_results() + if data_keys: + key = [f"{name}:{value}" for name, value in data_keys.items()] + key = ','.join(key) + result = result[key] + return result + + @classmethod def __get_fixed_keys_and_dims(cls, settings, fixed): p_len = len(settings['parameterSweepSettings']['parameters']) @@ -326,6 +335,45 @@ def load(self): return json.load(job_file) + def get_csvzip_from_results(self, data_keys, proc_key, name): + ''' + Get the csv files of the plot data for download. + + Attributes + ---------- + data_keys : dict + Dictionary of param names and values used to identify the correct data. + proc_key : str + Type post processing to preform. + name : str + Name of the csv directory + ''' + try: + result = self.__get_filtered_ensemble_results(data_keys) + if plt_key == "stddev": + result = result.stddev_ensemble() + elif plt_key == "avg": + result = result.average_ensemble() + tmp_dir = tempfile.TemporaryDirectory() + result.to_csv(path=tmp_dir.name, nametag=name, stamp="") + if data_keys: + csv_path = os.path.join(tmp_dir.name, name, "parameters.csv") + with open(csv_path, "w") as csv_file: + csv_writer = csv.writer(csv_file) + csv_writer.writerow(list(data_keys.keys())) + csv_writer.writerow(list(data_keys.values())) + shutil.make_archive(os.path.join(tmp_dir.name, name), "zip", tmp_dir.name, name) + path = os.path.join(tmp_dir.name, f"{name}.zip") + with open(path, "rb") as zip_file: + return zip_file.read() + except FileNotFoundError as err: + message = f"Could not find the results pickle file: {str(err)}" + raise StochSSFileNotFoundError(message, traceback.format_exc()) from err + except KeyError as err: + message = f"The requested results are not available: {str(err)}" + raise PlotNotAvailableError(message, traceback.format_exc()) from err + + def get_plot_from_results(self, data_keys, plt_key, add_config=False): ''' Get the plotly figure for the results of a job @@ -336,15 +384,13 @@ def get_plot_from_results(self, data_keys, plt_key, add_config=False): Dictionary of param names and values used to identify the correct data. plt_key : str Type of plot to generate. + add_config : bool + Whether or not to add the config key to the plot fig ''' self.log("debug", f"Key identifying the plot to generate: {plt_key}") try: self.log("info", "Loading the results...") - result = self.__get_pickled_results() - if data_keys: - key = [f"{name}:{value}" for name, value in data_keys.items()] - key = ','.join(key) - result = result[key] + result = self.__get_filtered_ensemble_results(data_keys) self.log("info", "Generating the plot...") if plt_key == "mltplplt": fig = result.plotplotly(return_plotly_figure=True, multiple_graphs=True) diff --git a/stochss/handlers/util/stochss_job.py b/stochss/handlers/util/stochss_job.py index 6507be8330..bdd511acea 100644 --- a/stochss/handlers/util/stochss_job.py +++ b/stochss/handlers/util/stochss_job.py @@ -179,6 +179,15 @@ def __get_filtered_2d_results(self, f_keys, param): return f_results + def __get_filtered_ensemble_results(self, data_keys): + result = self.__get_pickled_results() + if data_keys: + key = [f"{name}:{value}" for name, value in data_keys.items()] + key = ','.join(key) + result = result[key] + return result + + @classmethod def __get_fixed_keys_and_dims(cls, settings, fixed): p_len = len(settings['parameterSweepSettings']['parameters']) @@ -303,50 +312,6 @@ def extract_model(self): return resp, kwargs - def generate_csv_zip(self): - ''' - Create a zip archive of the csv results for download - - Atrributes - ---------- - ''' - status = self.get_status() - if status == "error": - message = f"The job experienced an error during run: {status}" - raise StochSSJobError(message, traceback.format_exc()) - if status != "complete": - message = f"The job has not finished running: {status}." - raise StochSSJobNotCompleteError(message, traceback.format_exc()) - - csv_path = self.get_csv_path(full=True) - if os.path.exists(csv_path + ".zip"): - message = f"{csv_path}.zip already exists." - return {"Message":message, "Path":csv_path.replace(self.user_dir+"/", "") + ".zip"} - csv_folder = StochSSFolder(path=csv_path) - return csv_folder.generate_zip_file() - - - def get_csv_path(self, full=False): - ''' - Return the path to the csv directory - - Attributes - ---------- - ''' - res_path = self.__get_results_path(full=full) - if not os.path.exists(res_path): - message = f"Could not find the results directory: {res_path}" - raise StochSSFileNotFoundError(message, traceback.format_exc()) - - try: - res_files = os.listdir(res_path) - csv_path = list(filter(lambda file: self.__is_csv_dir(file=file), res_files))[0] - return os.path.join(res_path, csv_path) - except IndexError as err: - message = f"Could not find the job results csv directory: {str(err)}" - raise StochSSFileNotFoundError(message, traceback.format_exc()) from err - - def get_model_path(self, full=False, external=False): ''' Return the path to the model file @@ -405,6 +370,49 @@ def get_notebook_data(self): return {"kwargs":kwargs, "type":wkfl_type} + def get_csvzip_from_results(self, data_keys, proc_key, name): + ''' + Get the csv files of the plot data for download. + + Attributes + ---------- + data_keys : dict + Dictionary of param names and values used to identify the correct data. + proc_key : str + Type post processing to preform. + name : str + Name of the csv directory + ''' + try: + self.log("info", "Getting job results...") + result = self.__get_filtered_ensemble_results(data_keys) + self.log("info", "Processing results...") + if plt_key == "stddev": + result = result.stddev_ensemble() + elif plt_key == "avg": + result = result.average_ensemble() + self.log("info", "Generating CSV files...") + tmp_dir = tempfile.TemporaryDirectory() + result.to_csv(path=tmp_dir.name, nametag=name, stamp="") + if data_keys: + csv_path = os.path.join(tmp_dir.name, name, "parameters.csv") + with open(csv_path, "w") as csv_file: + csv_writer = csv.writer(csv_file) + csv_writer.writerow(list(data_keys.keys())) + csv_writer.writerow(list(data_keys.values())) + self.log("info", "Generating zip archive...") + shutil.make_archive(os.path.join(tmp_dir.name, name), "zip", tmp_dir.name, name) + path = os.path.join(tmp_dir.name, f"{name}.zip") + with open(path, "rb") as zip_file: + return zip_file.read() + except FileNotFoundError as err: + message = f"Could not find the results pickle file: {str(err)}" + raise StochSSFileNotFoundError(message, traceback.format_exc()) from err + except KeyError as err: + message = f"The requested results are not available: {str(err)}" + raise PlotNotAvailableError(message, traceback.format_exc()) from err + + def get_plot_from_results(self, data_keys, plt_key, add_config=False): ''' Get the plotly figure for the results of a job @@ -419,11 +427,7 @@ def get_plot_from_results(self, data_keys, plt_key, add_config=False): self.log("debug", f"Key identifying the plot to generate: {plt_key}") try: self.log("info", "Loading the results...") - result = self.__get_pickled_results() - if data_keys: - key = [f"{name}:{value}" for name, value in data_keys.items()] - key = ','.join(key) - result = result[key] + result = self.__get_filtered_ensemble_results(data_keys) self.log("info", "Generating the plot...") if plt_key == "mltplplt": fig = result.plotplotly(return_plotly_figure=True, multiple_graphs=True) From 9e43a2b57d8487bbc0f3666289dc6194aa2a3875 Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Mon, 16 Aug 2021 12:11:54 -0400 Subject: [PATCH 050/186] Fixed import and var issues. --- jupyterhub/job_presentation.py | 7 ++++--- stochss/handlers/util/stochss_job.py | 10 +++++----- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/jupyterhub/job_presentation.py b/jupyterhub/job_presentation.py index 251ecce6b0..0ad921fc9a 100644 --- a/jupyterhub/job_presentation.py +++ b/jupyterhub/job_presentation.py @@ -17,6 +17,7 @@ ''' import os +import csv import json import shutil import pickle @@ -350,9 +351,9 @@ def get_csvzip_from_results(self, data_keys, proc_key, name): ''' try: result = self.__get_filtered_ensemble_results(data_keys) - if plt_key == "stddev": + if proc_key == "stddev": result = result.stddev_ensemble() - elif plt_key == "avg": + elif proc_key == "avg": result = result.average_ensemble() tmp_dir = tempfile.TemporaryDirectory() result.to_csv(path=tmp_dir.name, nametag=name, stamp="") @@ -371,7 +372,7 @@ def get_csvzip_from_results(self, data_keys, proc_key, name): raise StochSSFileNotFoundError(message, traceback.format_exc()) from err except KeyError as err: message = f"The requested results are not available: {str(err)}" - raise PlotNotAvailableError(message, traceback.format_exc()) from err + raise PlotNotAvailableError(message, traceback.format_exc()) from err def get_plot_from_results(self, data_keys, plt_key, add_config=False): diff --git a/stochss/handlers/util/stochss_job.py b/stochss/handlers/util/stochss_job.py index bdd511acea..edb5862f9f 100644 --- a/stochss/handlers/util/stochss_job.py +++ b/stochss/handlers/util/stochss_job.py @@ -17,11 +17,13 @@ ''' import os +import csv import json import shutil import pickle import string import hashlib +import tempfile import datetime import traceback @@ -31,13 +33,11 @@ from escapism import escape from .stochss_base import StochSSBase -from .stochss_folder import StochSSFolder from .stochss_model import StochSSModel from .parameter_sweep_1d import ParameterSweep1D from .parameter_sweep_2d import ParameterSweep2D from .stochss_spatial_model import StochSSSpatialModel -from .stochss_errors import StochSSJobError, StochSSJobNotCompleteError, \ - StochSSFileNotFoundError, StochSSFileExistsError, \ +from .stochss_errors import StochSSFileNotFoundError, StochSSFileExistsError, \ FileNotJSONFormatError, PlotNotAvailableError, \ StochSSPermissionsError, StochSSJobResultsError @@ -387,9 +387,9 @@ def get_csvzip_from_results(self, data_keys, proc_key, name): self.log("info", "Getting job results...") result = self.__get_filtered_ensemble_results(data_keys) self.log("info", "Processing results...") - if plt_key == "stddev": + if proc_key == "stddev": result = result.stddev_ensemble() - elif plt_key == "avg": + elif proc_key == "avg": result = result.average_ensemble() self.log("info", "Generating CSV files...") tmp_dir = tempfile.TemporaryDirectory() From 6707240854838ceedbd285759d4bd5de9040ecce Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Mon, 16 Aug 2021 12:29:34 -0400 Subject: [PATCH 051/186] Moved the results process blocks out of the plot function and into a helper function. --- jupyterhub/job_presentation.py | 58 +++++++++++++-------- stochss/handlers/util/parameter_sweep_1d.py | 28 ++++++---- stochss/handlers/util/parameter_sweep_2d.py | 30 ++++++----- 3 files changed, 70 insertions(+), 46 deletions(-) diff --git a/jupyterhub/job_presentation.py b/jupyterhub/job_presentation.py index 0ad921fc9a..e2a8bcafef 100644 --- a/jupyterhub/job_presentation.py +++ b/jupyterhub/job_presentation.py @@ -488,6 +488,21 @@ class ParameterSweep1D(): ################################################################################################ ''' + @classmethod + def __process_results(cls, results, species, mapper="final", reducer="avg"): + func_map = {"min": numpy.min, "max": numpy.max, "avg": numpy.mean, + "var": numpy.var, "final": lambda res: res[-1]} + map_results = [[func_map[mapper](traj[species]) for traj in result] for result in results] + if len(map_results[0]) > 1: + data = [[func_map[reducer](map_result), + numpy.std(map_result)] for map_result in map_results] + visible = True + else: + data = [[map_result[0], 0] for map_result in map_results] + visible = False + return numpy.array(data), visible + + @classmethod def plot(cls, results, species, param, mapper="final", reducer="avg"): ''' @@ -506,17 +521,8 @@ def plot(cls, results, species, param, mapper="final", reducer="avg"): reducer : str Key indicating the ensemble aggragation function to use. ''' - func_map = {"min": numpy.min, "max": numpy.max, "avg": numpy.mean, - "var": numpy.var, "final": lambda res: res[-1]} - map_results = [[func_map[mapper](traj[species]) for traj in result] for result in results] - if len(map_results[0]) > 1: - data = [[func_map[reducer](map_result), - numpy.std(map_result)] for map_result in map_results] - visible = True - else: - data = [[map_result[0], 0] for map_result in map_results] - visible = False - data = numpy.array(data) + data, visible = cls.__process_results(results=results, species=species, + mapper=mapper, reducer=reducer) error_y = dict(type="data", array=data[:, 1], visible=visible) trace_list = [plotly.graph_objs.Scatter(x=param['range'], @@ -538,6 +544,22 @@ class ParameterSweep2D(): ################################################################################################ ''' + @classmethod + def __process_results(cls, results, species, mapper="final", reducer="avg"): + func_map = {"min": numpy.min, "max": numpy.max, "avg": numpy.mean, + "var": numpy.var, "final": lambda res: res[-1]} + data = [] + for p_results in results: + map_results = [[func_map[mapper](traj[species]) for traj in result] + for result in p_results] + if len(map_results[0]) > 1: + red_results = [func_map[reducer](map_result) for map_result in map_results] + else: + red_results = [map_result[0] for map_result in map_results] + data.append(red_results) + return numpy.array(data) + + @classmethod def plot(cls, results, species, params, mapper="final", reducer="avg"): ''' @@ -556,18 +578,8 @@ def plot(cls, results, species, params, mapper="final", reducer="avg"): reducer : str Key indicating the ensemble aggragation function to use. ''' - func_map = {"min": numpy.min, "max": numpy.max, "avg": numpy.mean, - "var": numpy.var, "final": lambda res: res[-1]} - data = [] - for p_results in results: - map_results = [[func_map[mapper](traj[species]) for traj in result] - for result in p_results] - if len(map_results[0]) > 1: - red_results = [func_map[reducer](map_result) for map_result in map_results] - else: - red_results = [map_result[0] for map_result in map_results] - data.append(red_results) - data = numpy.array(data) + data = cls.__process_results(results=results, species=species, + mapper=mapper, reducer=reducer) trace_list = [plotly.graph_objs.Heatmap(z=data, x=params[0]['range'], y=params[1]['range'])] diff --git a/stochss/handlers/util/parameter_sweep_1d.py b/stochss/handlers/util/parameter_sweep_1d.py index 011897c1c1..f4f259ff0b 100644 --- a/stochss/handlers/util/parameter_sweep_1d.py +++ b/stochss/handlers/util/parameter_sweep_1d.py @@ -92,6 +92,21 @@ def __feature_extraction(self, results, index, verbose=False): log.debug(' %s population %s=%s', key, species, data) + @classmethod + def __process_results(cls, results, species, mapper="final", reducer="avg"): + func_map = {"min": numpy.min, "max": numpy.max, "avg": numpy.mean, + "var": numpy.var, "final": lambda res: res[-1]} + map_results = [[func_map[mapper](traj[species]) for traj in result] for result in results] + if len(map_results[0]) > 1: + data = [[func_map[reducer](map_result), + numpy.std(map_result)] for map_result in map_results] + visible = True + else: + data = [[map_result[0], 0] for map_result in map_results] + visible = False + return numpy.array(data), visible + + def __setup_results(self, solver_name): for species in self.list_of_species: spec_res = {} @@ -184,17 +199,8 @@ def plot(cls, results, species, param, mapper="final", reducer="avg"): reducer : str Key indicating the ensemble aggragation function to use. ''' - func_map = {"min": numpy.min, "max": numpy.max, "avg": numpy.mean, - "var": numpy.var, "final": lambda res: res[-1]} - map_results = [[func_map[mapper](traj[species]) for traj in result] for result in results] - if len(map_results[0]) > 1: - data = [[func_map[reducer](map_result), - numpy.std(map_result)] for map_result in map_results] - visible = True - else: - data = [[map_result[0], 0] for map_result in map_results] - visible = False - data = numpy.array(data) + data, visible = cls.__process_results(results=results, species=species, + mapper=mapper, reducer=reducer) error_y = dict(type="data", array=data[:, 1], visible=visible) trace_list = [plotly.graph_objs.Scatter(x=param['range'], diff --git a/stochss/handlers/util/parameter_sweep_2d.py b/stochss/handlers/util/parameter_sweep_2d.py index 162e488230..aa8a4a950f 100644 --- a/stochss/handlers/util/parameter_sweep_2d.py +++ b/stochss/handlers/util/parameter_sweep_2d.py @@ -107,6 +107,22 @@ def __setup_results(self, solver_name): self.results[species] = spec_res + @classmethod + def __process_results(cls, results, species, mapper="final", reducer="avg"): + func_map = {"min": numpy.min, "max": numpy.max, "avg": numpy.mean, + "var": numpy.var, "final": lambda res: res[-1]} + data = [] + for p_results in results: + map_results = [[func_map[mapper](traj[species]) for traj in result] + for result in p_results] + if len(map_results[0]) > 1: + red_results = [func_map[reducer](map_result) for map_result in map_results] + else: + red_results = [map_result[0] for map_result in map_results] + data.append(red_results) + return numpy.array(data) + + def get_plotly_layout_data(self, plt_data): ''' Get plotly axes labels for layout @@ -187,18 +203,8 @@ def plot(cls, results, species, params, mapper="final", reducer="avg"): reducer : str Key indicating the ensemble aggragation function to use. ''' - func_map = {"min": numpy.min, "max": numpy.max, "avg": numpy.mean, - "var": numpy.var, "final": lambda res: res[-1]} - data = [] - for p_results in results: - map_results = [[func_map[mapper](traj[species]) for traj in result] - for result in p_results] - if len(map_results[0]) > 1: - red_results = [func_map[reducer](map_result) for map_result in map_results] - else: - red_results = [map_result[0] for map_result in map_results] - data.append(red_results) - data = numpy.array(data) + data = cls.__process_results(results=results, species=species, + mapper=mapper, reducer=reducer) trace_list = [plotly.graph_objs.Heatmap(z=data, x=params[0]['range'], y=params[1]['range'])] From 7603da06f24cf78aefd604ce7e6b56f2e6ae0ec5 Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Tue, 17 Aug 2021 16:31:10 -0400 Subject: [PATCH 052/186] Changed the version to v2.4. --- __version__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/__version__.py b/__version__.py index 294d16e551..124e6784c9 100644 --- a/__version__.py +++ b/__version__.py @@ -5,7 +5,7 @@ # @website https://github.com/stochss/stochss # ============================================================================= -__version__ = '2.3.12' +__version__ = '2.4' __title__ = 'StochSS' __description__ = 'StochSS is an integrated development environment (IDE) \ for simulation of biochemical networks.' From 6f69347c3253a848a4def57a8a43313510e8730c Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Tue, 17 Aug 2021 17:00:54 -0400 Subject: [PATCH 053/186] Changed the Ubuntu version for tests to v20.04. --- .github/workflows/main.yml | 2 +- .github/workflows/pylint_on_pull_request.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 7b2f46b5dc..551a37799b 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -2,7 +2,7 @@ on: [push, pull_request] jobs: run_tests: - runs-on: ubuntu-16.04 + runs-on: ubuntu-20.04 name: StochSS Continuous Testing steps: # Checkout diff --git a/.github/workflows/pylint_on_pull_request.yml b/.github/workflows/pylint_on_pull_request.yml index dfb9bf6f68..08e7db2806 100644 --- a/.github/workflows/pylint_on_pull_request.yml +++ b/.github/workflows/pylint_on_pull_request.yml @@ -2,7 +2,7 @@ name: PyLint On Pull Request on: [pull_request] jobs: build: - runs-on: ubuntu-18.04 + runs-on: ubuntu-20.04 steps: - name: Set Up Python uses: actions/setup-python@v2 From c5e4ea16f84e092b102ea1361a5909dbc51c2a2c Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Tue, 17 Aug 2021 17:01:40 -0400 Subject: [PATCH 054/186] Changed the Ubuntu version for jupyterhub to v20.04. --- jupyterhub/Dockerfile.jupyterhub | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jupyterhub/Dockerfile.jupyterhub b/jupyterhub/Dockerfile.jupyterhub index eed510b44b..d0823f67e6 100644 --- a/jupyterhub/Dockerfile.jupyterhub +++ b/jupyterhub/Dockerfile.jupyterhub @@ -1,6 +1,6 @@ ARG JUPYTERHUB_VERSION #FROM jupyterhub/jupyterhub-onbuild:$JUPYTERHUB_VERSION -FROM ubuntu:18.04 +FROM ubuntu:20.04 USER root From 71c4805bac7f3ad62d18769ce497e83b17a5ae05 Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Wed, 18 Aug 2021 08:45:07 -0400 Subject: [PATCH 055/186] Changed the Ubuntu version for jupyter hub back to 18.04 due to errors. --- jupyterhub/Dockerfile.jupyterhub | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jupyterhub/Dockerfile.jupyterhub b/jupyterhub/Dockerfile.jupyterhub index d0823f67e6..eed510b44b 100644 --- a/jupyterhub/Dockerfile.jupyterhub +++ b/jupyterhub/Dockerfile.jupyterhub @@ -1,6 +1,6 @@ ARG JUPYTERHUB_VERSION #FROM jupyterhub/jupyterhub-onbuild:$JUPYTERHUB_VERSION -FROM ubuntu:20.04 +FROM ubuntu:18.04 USER root From 9dc94e18bc9473e0ff2f37a313cf2c52af87f307 Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Wed, 18 Aug 2021 13:30:20 -0400 Subject: [PATCH 056/186] Refactored the to_csv functions to generate csv files from the pickled results file. --- stochss/handlers/util/parameter_sweep_1d.py | 189 ++++++------------- stochss/handlers/util/parameter_sweep_2d.py | 196 ++++++-------------- 2 files changed, 121 insertions(+), 264 deletions(-) diff --git a/stochss/handlers/util/parameter_sweep_1d.py b/stochss/handlers/util/parameter_sweep_1d.py index 7d9cd9941d..14e0d2b3df 100644 --- a/stochss/handlers/util/parameter_sweep_1d.py +++ b/stochss/handlers/util/parameter_sweep_1d.py @@ -16,6 +16,8 @@ along with this program. If not, see . ''' +import os +import csv import json import copy import logging @@ -59,37 +61,15 @@ def __init__(self, model, settings, param): self.ts_results = {} - def __ensemble_feature_extraction(self, results, index, verbose=False): - func_map = {"min":numpy.min, "max":numpy.max, "avg":numpy.mean, "var":numpy.var} - for species in self.list_of_species: - for m_key in self.MAPPER_KEYS: - if m_key == "final": - m_data = [x[species][-1] for x in results] - else: - m_data = [func_map[m_key](x[species]) for x in results] - if verbose: - log.debug(f' {m_key} population {species}={m_data}') - std = numpy.std(m_data) - for r_key in self.REDUCER_KEYS: - r_data = func_map[r_key](m_data) - self.results[species][m_key][r_key][index, 0] = r_data - self.results[species][m_key][r_key][index, 1] = std - if verbose: - log.debug(f' {r_key} std of ensemble m:{r_data} s:{std}') - - - def __feature_extraction(self, results, index, verbose=False): - func_map = {"min":numpy.min, "max":numpy.max, "avg":numpy.mean, "var":numpy.var} - for species in self.list_of_species: - spec_res = results[species] - for key in self.MAPPER_KEYS: - if key == "final": - data = spec_res[-1] - else: - data = func_map[key](spec_res) - self.results[species][key][index, 0] = data - if verbose: - log.debug(f' {key} population {species}={data}') + @classmethod + def __get_csv_data(cls, results=None, species=None, mapper=None, reducer=None): + data = [] + for specie in species: + p_data, _ = cls.__process_results( + results=results, species=specie, mapper=mapper, reducer=reducer + ) + data.extend([list(p_data[:,0]), list(p_data[:,1])]) + return data @classmethod @@ -107,78 +87,16 @@ def __process_results(cls, results, species, mapper="final", reducer="avg"): return numpy.array(data), visible - def __setup_results(self, solver_name): - for species in self.list_of_species: - spec_res = {} - if "ODE" not in solver_name and self.settings['number_of_trajectories'] > 1: - for m_key in self.MAPPER_KEYS: - spec_res[m_key] = {} - for r_key in self.REDUCER_KEYS: - spec_res[m_key][r_key] = numpy.zeros((len(self.param['range']), 2)) - else: - for key in self.MAPPER_KEYS: - spec_res[key] = numpy.zeros((len(self.param['range']), 2)) - self.results[species] = spec_res - - - def get_plotly_layout_data(self, plt_data): - ''' - Get plotly axes labels for layout - - Attributes - ---------- - plt_data : dict - Existing layout data - ''' - if "xaxis_label" not in plt_data: - plt_data['xaxis_label'] = f"{self.param['parameter']}" - if "yaxis_label" not in plt_data: - plt_data['yaxis_label'] = "Population" - - - def get_plotly_traces(self, keys): - ''' - Get the plotly trace list - - Attributes - ---------- - key : list - Identifiers for the results data - ''' - if len(keys) > 2: - results = self.results[keys[0]][keys[1]][keys[2]] - else: - results = self.results[keys[0]][keys[1]] - - visible = "number_of_trajectories" in self.settings.keys() and \ - self.settings['number_of_trajectories'] > 1 - error_y = dict(type="data", array=results[:, 1], visible=visible) - - trace_list = [plotly.graph_objs.Scatter(x=self.param['range'], - y=results[:, 0], error_y=error_y)] - return trace_list - - - # def plot(self, keys=None): - # ''' - # Plot the results based on the keys using matplotlib - - # Attributes - # ---------- - # key : list - # Identifiers for the results data - # ''' - # if len(keys) > 2: - # results = self.results[keys[0]][keys[1]][keys[2]] - # else: - # results = self.results[keys[0]][keys[1]] - - # matplotlib.pyplot.subplots(figsize=(8, 8)) - # matplotlib.pyplot.title(f"Parameter Sweep - Variable: {keys[0]}") - # matplotlib.pyplot.errorbar(self.param['range'], results[:, 0], results[:, 1]) - # matplotlib.pyplot.xlabel(self.param['parameter'], - # fontsize=16, fontweight='bold') - # matplotlib.pyplot.ylabel("Population", fontsize=16, fontweight='bold') + @classmethod + def __write_csv_file(cls, path, header, param, data): + with open(path, "w") as csv_file: + csv_writer = csv.writer(csv_file) + csv_writer.writerow(header) + for i, val in enumerate(param['range']): + line = [val] + for col in data: + line.append(col[i]) + csv_writer.writerow(line) @classmethod @@ -226,8 +144,7 @@ def run(self, job_id, verbose=False): solver_name = self.settings['solver'].name else: solver_name = self.model.get_best_solver().name - self.__setup_results(solver_name=solver_name) - for i, val in enumerate(self.param['range']): + for val in self.param['range']: if solver_name in ["SSACSolver", "TauLeapingCSolver", "ODECSolver"]: tmp_mdl = self.model self.settings['variables'] = {self.param['parameter']:val} @@ -243,34 +160,48 @@ def run(self, job_id, verbose=False): else: key = f"{self.param['parameter']}:{val}" self.ts_results[key] = tmp_res - if "ODE" not in solver_name and self.settings['number_of_trajectories'] > 1: - self.__ensemble_feature_extraction(results=tmp_res, index=i, verbose=verbose) - else: - self.__feature_extraction(results=tmp_res, index=i, verbose=verbose) - def to_csv(self, keys, csv_writer): + @classmethod + def to_csv(cls, param, kwargs, path=None, nametag="results_csv"): ''' - Convert self.results to a csv file + Output the post-process results as a series of CSV files. Attributes ---------- - key : list - Identifiers for the results data - csv_writer : obj - CSV file writer. + param : dict + Sweep parameter object + kwargs : dict + Filtered results and full list of Model species + path : str + Parent path to the csv directory + nametag : str + Name of the csv directory ''' - results = [] - names = [self.param['parameter']] - for species in self.list_of_species: - names.extend([species, f"{species}-stddev"]) - if len(keys) > 1: - results.append(self.results[species][keys[0]][keys[1]]) - else: - results.append(self.results[species][keys[0]]) - csv_writer.writerow(names) - for i, step in enumerate(self.param['range']): - line = [step] - for res in results: - line.extend([res[i, 0], res[i, 1]]) - csv_writer.writerow(line) + if path is None: + directory = os.path.join(".", str(nametag)) + else: + directory = os.path.join(path, str(nametag)) + os.mkdir(directory) + # Define header row for all files + header = [param['name']] + for specie in kwargs['species']: + header.extend([specie, f"{specie}-stddev"]) + # Get all CSV file data + mappers = ['min', 'max', 'avg', 'var', 'final'] + if len(kwargs['results'][0].data) == 1: + for mapper in mappers: + path = os.path.join(directory, f"{mapper}.csv") + # Get csv data for a mapper + data = cls.__get_csv_data(**kwargs, mapper=mapper) + # Write csv file + cls.__write_csv_file(path, header, param, data) + else: + reducers = mappers[:-1] + for mapper in mappers: + for reducer in reducers: + path = os.path.join(directory, f"{mapper}-{reducer}.csv") + # Get csv data for a mapper and a reducer + data = cls.__get_csv_data(**kwargs, mapper=mapper, reducer=reducer) + # Write csv file + cls.__write_csv_file(path, header, param, data) diff --git a/stochss/handlers/util/parameter_sweep_2d.py b/stochss/handlers/util/parameter_sweep_2d.py index 2b4f0bf2d4..f4cd173d57 100644 --- a/stochss/handlers/util/parameter_sweep_2d.py +++ b/stochss/handlers/util/parameter_sweep_2d.py @@ -16,6 +16,8 @@ along with this program. If not, see . ''' +import os +import csv import json import copy import logging @@ -60,51 +62,15 @@ def __init__(self, model, settings, params): self.results = {} - def __ensemble_feature_extraction(self, results, i_ndx, j_ndx, verbose=False): - func_map = {"min":numpy.min, "max":numpy.max, "avg":numpy.mean, "var":numpy.var} - for species in self.list_of_species: - for m_key in self.MAPPER_KEYS: - if m_key == "final": - m_data = [x[species][-1] for x in results] - else: - m_data = [func_map[m_key](x[species]) for x in results] - if verbose: - log.debug(f' {m_key} population {species}={m_data}') - for r_key in self.REDUCER_KEYS: - r_data = func_map[r_key](m_data) - self.results[species][m_key][r_key][i_ndx, j_ndx] = r_data - if verbose: - log.debug(f' {r_key} of ensemble = {r_data}') - - - def __feature_extraction(self, results, i_ndx, j_ndx, verbose=False): - func_map = {"min":numpy.min, "max":numpy.max, "avg":numpy.mean, "var":numpy.var} - for species in self.list_of_species: - spec_res = results[species] - for key in self.MAPPER_KEYS: - if key == "final": - data = spec_res[-1] - else: - data = func_map[key](spec_res) - self.results[species][key][i_ndx, j_ndx] = data - if verbose: - log.debug(f' {key} population {species}={data}') - - - def __setup_results(self, solver_name): - for species in self.list_of_species: - spec_res = {} - if "ODE" not in solver_name and self.settings['number_of_trajectories'] > 1: - for m_key in self.MAPPER_KEYS: - spec_res[m_key] = {} - for r_key in self.REDUCER_KEYS: - spec_res[m_key][r_key] = numpy.zeros((len(self.params[0]['range']), - len(self.params[1]['range']))) - else: - for key in self.MAPPER_KEYS: - spec_res[key] = numpy.zeros((len(self.params[0]['range']), - len(self.params[1]['range']))) - self.results[species] = spec_res + @classmethod + def __get_csv_data(cls, results=None, species=None, mapper=None, reducer=None): + data = [] + for specie in species: + p_data = cls.__process_results( + results=results, species=specie, mapper=mapper, reducer=reducer + ) + data.append([list(_data) for _data in p_data]) + return data @classmethod @@ -123,66 +89,17 @@ def __process_results(cls, results, species, mapper="final", reducer="avg"): return numpy.array(data) - def get_plotly_layout_data(self, plt_data): - ''' - Get plotly axes labels for layout - - Attributes - ---------- - plt_data : dict - Existing layout data - ''' - if "yaxis_label" not in plt_data: - plt_data['yaxis_label'] = f"{self.params[1]['parameter']}" - if "xaxis_label" not in plt_data: - plt_data['xaxis_label'] = f"{self.params[0]['parameter']}" - - - def get_plotly_traces(self, keys): - ''' - Get the plotly trace list - - Attributes - ---------- - key : list - Identifiers for the results data - ''' - if len(keys) <= 2: - results = self.results[keys[0]][keys[1]] - else: - results = self.results[keys[0]][keys[1]][keys[2]] - - trace_list = [plotly.graph_objs.Heatmap(z=results, x=self.params[0]['range'], - y=self.params[1]['range'])] - return trace_list - - - # def plot(self, keys=None): - # ''' - # Plot the results based on the keys using matplotlib - - # Attributes - # ---------- - # key : list - # Identifiers for the results data - # ''' - # if len(keys) <= 2: - # results = self.results[keys[0]][keys[1]] - # else: - # results = self.results[keys[0]][keys[1]][keys[2]] - - # _, axis = matplotlib.pyplot.subplots(figsize=(8, 8)) - # matplotlib.pyplot.imshow(results) - # axis.set_xticks(numpy.arange(results.shape[1])+0.5, minor=False) - # axis.set_yticks(numpy.arange(results.shape[0])+0.5, minor=False) - # matplotlib.pyplot.title(f"Parameter Sweep - Variable: {keys[0]}") - # axis.set_xticklabels(self.params[0]['range'], minor=False, rotation=90) - # axis.set_yticklabels(self.params[1]['range'], minor=False) - # axis.set_xlabel(self.params[0]['parameter'], fontsize=16, fontweight='bold') - # axis.set_ylabel(self.params[1]['parameter'], fontsize=16, fontweight='bold') - # divider = mpl_toolkits.axes_grid1.make_axes_locatable(axis) - # cax = divider.append_axes("right", size="5%", pad=0.2) - # _ = matplotlib.pyplot.colorbar(ax=axis, cax=cax) + @classmethod + def __write_csv_file(cls, path, header, params, data): + with open(path, "w") as csv_file: + csv_writer = csv.writer(csv_file) + csv_writer.writerow(header) + for i, val1 in enumerate(params[0]['range']): + for j, val2 in enumerate(params[1]['range']): + line = [val1, val2] + for col in data: + line.append(col[i][j]) + csv_writer.writerow(line) @classmethod @@ -228,9 +145,8 @@ def run(self, job_id, verbose=False): solver_name = self.settings['solver'].name else: solver_name = self.model.get_best_solver().name - self.__setup_results(solver_name=solver_name) - for i, val1 in enumerate(self.params[0]['range']): - for j, val2 in enumerate(self.params[1]['range']): + for val1 in self.params[0]['range']: + for val2 in self.params[1]['range']: if solver_name in ["SSACSolver", "TauLeapingCSolver", "ODECSolver"]: tmp_mdl = self.model variables = {self.params[0]['parameter']:val1, self.params[1]['parameter']:val2} @@ -251,37 +167,47 @@ def run(self, job_id, verbose=False): key = f"{self.params[0]['parameter']}:{val1}," key += f"{self.params[1]['parameter']}:{val2}" self.ts_results[key] = tmp_res - if "ODE" not in solver_name and self.settings['number_of_trajectories'] > 1: - self.__ensemble_feature_extraction(results=tmp_res, i_ndx=i, j_ndx=j, - verbose=verbose) - else: - self.__feature_extraction(results=tmp_res, i_ndx=i, j_ndx=j, - verbose=verbose) - def to_csv(self, keys, csv_writer): + @classmethod + def to_csv(cls, params, kwargs, path=None, nametag="results_csv"): ''' - Convert self.results to a csv file + Output the post-process results as a series of CSV files. Attributes ---------- - key : list - Identifiers for the results data - csv_writer : obj - CSV file writer. + params : list + List of sweep parameter objects + kwargs : dict + Filtered results and full list of Model species + path : str + Parent path to the csv directory + nametag : str + Name of the csv directory ''' - results = [] - names = [self.params[0]['parameter'], self.params[1]['parameter']] - names.extend(self.list_of_species) - for species in self.list_of_species: - if len(keys) <= 1: - results.append(self.results[species][keys[0]]) - else: - results.append(self.results[species][keys[0]][keys[1]]) - csv_writer.writerow(names) - for i, step1 in enumerate(self.params[0]['range']): - for j, step2 in enumerate(self.params[1]['range']): - line = [step1, step2] - for res in results: - line.append(res[i, j]) - csv_writer.writerow(line) + if path is None: + directory = os.path.join(".", str(nametag)) + else: + directory = os.path.join(path, str(nametag)) + os.mkdir(directory) + # Define header row for all files + header = [params[0]['name'], params[1]['name']] + header.extend(kwargs['species']) + # Get all CSV file data + mappers = ['min', 'max', 'avg', 'var', 'final'] + if len(kwargs['results'][0][0].data) == 1: + for mapper in mappers: + path = os.path.join(directory, f"{mapper}.csv") + # Get csv data for a mapper + data = cls.__get_csv_data(**kwargs, mapper=mapper) + # Write csv file + cls.__write_csv_file(path, header, params, data) + else: + reducers = mappers[:-1] + for mapper in mappers: + for reducer in reducers: + path = os.path.join(directory, f"{mapper}-{reducer}.csv") + # Get csv data for a mapper and a reducer + data = cls.__get_csv_data(**kwargs, mapper=mapper, reducer=reducer) + # Write csv file + cls.__write_csv_file(path, header, params, data) From 16725f24e609b2f56397a3f6ffde2db59b7b9241 Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Wed, 18 Aug 2021 13:35:19 -0400 Subject: [PATCH 057/186] Removed the post process result storage blocks. Removed all function that were dependent on the post process results. --- stochss/handlers/util/parameter_sweep.py | 47 +----------------------- 1 file changed, 1 insertion(+), 46 deletions(-) diff --git a/stochss/handlers/util/parameter_sweep.py b/stochss/handlers/util/parameter_sweep.py index 2ed6212db5..5f17e4770e 100644 --- a/stochss/handlers/util/parameter_sweep.py +++ b/stochss/handlers/util/parameter_sweep.py @@ -96,32 +96,6 @@ def __report_result_error(cls, trace): raise StochSSJobResultsError(message, trace) - def __store_csv_results(self, job): - try: - if "solver" in job.settings.keys(): - solver_name = job.settings['solver'].name - else: - solver_name = job.model.get_best_solver().name - if "ODE" not in solver_name and job.settings['number_of_trajectories'] > 1: - csv_keys = list(itertools.product(["min", "max", "avg", "var", "final"], - ["min", "max", "avg", "var"])) - else: - csv_keys = [["min"], ["max"], ["avg"], ["var"], ["final"]] - stamp = self.get_time_stamp() - dirname = f"results/results_csv{stamp}" - if not os.path.exists(dirname): - os.mkdir(dirname) - for key in csv_keys: - if not isinstance(key, list): - key = list(key) - path = os.path.join(dirname, f"{'-'.join(key)}.csv") - with open(path, "w", newline="") as csv_file: - csv_writer = csv.writer(csv_file) - job.to_csv(keys=key, csv_writer=csv_writer) - except Exception as err: - log.error(f"Error storing csv results: {err}\n{traceback.format_exc()}") - - @classmethod def __store_pickled_results(cls, job): try: @@ -134,18 +108,6 @@ def __store_pickled_results(cls, job): return False - @classmethod - def __store_results(cls, job): - try: - with open('results/results.json', 'w') as json_file: - json.dump(job.results, json_file, indent=4, sort_keys=True, cls=NumpyEncoder) - except Exception as err: - message = f"Error storing results dictionary: {err}\n{traceback.format_exc()}" - log.err(message) - return message - return False - - def configure(self): ''' Get the configuration arguments for 1D or 2D parameter sweep @@ -203,12 +165,5 @@ def run(self, verbose=True): if not 'results' in os.listdir(): os.mkdir('results') pkl_err = self.__store_pickled_results(job=job) - if job.name != "ParameterScan": - if verbose: - log.info("Storing the polts of the results") - res_err = self.__store_results(job=job) - self.__store_csv_results(job=job) - if pkl_err and res_err: - self.__report_result_error(trace=f"{res_err}\n{pkl_err}") - elif pkl_err: + if pkl_err: self.__report_result_error(trace=pkl_err) From be884de3fb0687f6edd1fb9ebfebe2d08b1b94b4 Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Wed, 18 Aug 2021 13:39:31 -0400 Subject: [PATCH 058/186] Removed the store csv results function from ensemble simulation jobs. Removed csv from results storage logs. --- stochss/handlers/util/ensemble_simulation.py | 10 +--------- stochss/handlers/util/parameter_sweep.py | 2 +- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/stochss/handlers/util/ensemble_simulation.py b/stochss/handlers/util/ensemble_simulation.py index 5dd7a7761a..949fe70b36 100644 --- a/stochss/handlers/util/ensemble_simulation.py +++ b/stochss/handlers/util/ensemble_simulation.py @@ -69,13 +69,6 @@ def __get_run_settings(self): return run_settings - def __store_csv_results(self, results): - try: - results.to_csv(path="results", nametag="results_csv", stamp=self.get_time_stamp()) - except Exception as err: - log.error(f"Error storing csv results: {err}\n{traceback.format_exc()}") - - @classmethod def __store_pickled_results(cls, results): try: @@ -131,10 +124,9 @@ def run(self, preview=False, verbose=True): results = self.g_model.run(**kwargs) if verbose: log.info("The ensemble simulation has completed") - log.info("Storing the results as pickle and csv") + log.info("Storing the results as pickle") if not 'results' in os.listdir(): os.mkdir('results') - self.__store_csv_results(results=results) pkl_err = self.__store_pickled_results(results=results) if verbose: log.info("Storing the polts of the results") diff --git a/stochss/handlers/util/parameter_sweep.py b/stochss/handlers/util/parameter_sweep.py index 5f17e4770e..62390f4e55 100644 --- a/stochss/handlers/util/parameter_sweep.py +++ b/stochss/handlers/util/parameter_sweep.py @@ -161,7 +161,7 @@ def run(self, verbose=True): raise StochSSJobError(message) if verbose: log.info(f"The {sim_type} has completed") - log.info("Storing the results as pickle and csv") + log.info("Storing the results as pickle.") if not 'results' in os.listdir(): os.mkdir('results') pkl_err = self.__store_pickled_results(job=job) From 23b74957f270b1da094c808084944ea13fcd0cbe Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Wed, 18 Aug 2021 13:50:42 -0400 Subject: [PATCH 059/186] Added functions to get the csv zip data for download. --- stochss/handlers/util/stochss_job.py | 51 ++++++++++++++++++++++++++-- 1 file changed, 49 insertions(+), 2 deletions(-) diff --git a/stochss/handlers/util/stochss_job.py b/stochss/handlers/util/stochss_job.py index edb5862f9f..9a90fcf171 100644 --- a/stochss/handlers/util/stochss_job.py +++ b/stochss/handlers/util/stochss_job.py @@ -410,7 +410,7 @@ def get_csvzip_from_results(self, data_keys, proc_key, name): raise StochSSFileNotFoundError(message, traceback.format_exc()) from err except KeyError as err: message = f"The requested results are not available: {str(err)}" - raise PlotNotAvailableError(message, traceback.format_exc()) from err + raise PlotNotAvailableError(message, traceback.format_exc()) from err def get_plot_from_results(self, data_keys, plt_key, add_config=False): @@ -451,6 +451,53 @@ def get_plot_from_results(self, data_keys, plt_key, add_config=False): raise PlotNotAvailableError(message, traceback.format_exc()) from err + def get_psweep_csvzip_from_results(self, fixed, name): + ''' + Get the csv file of the parameter sweep plot data for download + + Attributes + ---------- + fixed : dict + Dictionary for parameters that remain at a fixed value. + ''' + settings = self.load_settings() + try: + self.log("info", "Loading job results...") + dims, f_keys = self.__get_fixed_keys_and_dims(settings, fixed) + params = list(filter(lambda param: param['name'] not in fixed.keys(), + settings['parameterSweepSettings']['parameters'])) + tmp_dir = tempfile.TemporaryDirectory() + if dims == 1: + kwargs = {"results": self.__get_filtered_1d_results(f_keys)} + kwargs["species"] = list(kwargs['results'][0][0].model.listOfSpecies.keys()) + self.log("info", "Generating CSV files...") + ParameterSweep1D.to_csv( + param=params[0], kwargs=kwargs, path=tmp_dir.name, nametag=name + ) + else: + kwargs = {"results": self.__get_filtered_2d_results(f_keys, params[0])} + kwargs["species"] = list(kwargs['results'][0][0][0].model.listOfSpecies.keys()) + self.log("info", "Generating CSV files...") + ParameterSweep1D.to_csv( + param=params[0], kwargs=kwargs, path=tmp_dir.name, nametag=name + ) + if fixed: + csv_path = os.path.join(tmp_dir.name, name, "parameters.csv") + with open(csv_path, "w") as csv_file: + csv_writer = csv.writer(csv_file) + csv_writer.writerow(list(fixed.keys())) + csv_writer.writerow(list(fixed.values())) + shutil.make_archive(os.path.join(tmp_dir.name, name), "zip", tmp_dir.name, name) + with open(os.path.join(tmp_dir.name, f"{name}.zip"), "rb") as zip_file: + return zip_file.read() + except FileNotFoundError as err: + message = f"Could not find the results pickle file: {str(err)}" + raise StochSSFileNotFoundError(message, traceback.format_exc()) from err + except KeyError as err: + message = f"The requested results are not available: {str(err)}" + raise PlotNotAvailableError(message, traceback.format_exc()) from err + + def get_psweep_plot_from_results(self, fixed, kwargs, add_config=False): ''' Generate and return the parameter sweep plot form the time series results. @@ -462,7 +509,7 @@ def get_psweep_plot_from_results(self, fixed, kwargs, add_config=False): kwarps : dict Dictionary of keys used for post proccessing the results. ''' - self.log("debug", f"Key identifying the plot to generate: {kwargs}") + self.log("debug", f"Keys identifying the plot to generate: {kwargs}") settings = self.load_settings() try: self.log("info", "Loading the results...") From 1b2af42697b0a6b1e900e716382cd7ff8c45d28c Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Wed, 18 Aug 2021 14:27:08 -0400 Subject: [PATCH 060/186] Added buttons for downloading plot data as csv. --- .../templates/gillespyResultsEnsembleView.pug | 28 +++++++++++++++++++ .../templates/gillespyResultsView.pug | 7 +++++ .../templates/parameterScanResultsView.pug | 14 ++++++++++ .../templates/parameterSweepResultsView.pug | 14 ++++++++++ 4 files changed, 63 insertions(+) diff --git a/client/job-view/templates/gillespyResultsEnsembleView.pug b/client/job-view/templates/gillespyResultsEnsembleView.pug index 3292ac0dd8..b668cb4f38 100644 --- a/client/job-view/templates/gillespyResultsEnsembleView.pug +++ b/client/job-view/templates/gillespyResultsEnsembleView.pug @@ -64,6 +64,13 @@ div#workflow-results.card disabled ) Download JSON + button.btn.btn-primary.box-shadow( + data-hook="stddevran-plot-csv" + data-target="download-plot-csv" + data-type="stddevran" + disabled + ) Download as .csv + div.card div.card-header.pb-0 @@ -105,6 +112,13 @@ div#workflow-results.card disabled ) Download JSON + button.btn.btn-primary.box-shadow( + data-hook="trajectories-plot-csv" + data-target="download-plot-csv" + data-type="trajectories" + disabled + ) Download as .csv + div.card div.card-header.pb-0 @@ -144,6 +158,13 @@ div#workflow-results.card disabled ) Download JSON + button.btn.btn-primary.box-shadow( + data-hook="stddev-plot-csv" + data-target="download-plot-csv" + data-type="stddev" + disabled + ) Download as .csv + div.card div.card-header.pb-0 @@ -183,6 +204,13 @@ div#workflow-results.card disabled ) Download JSON + button.btn.btn-primary.box-shadow( + data-hook="avg-plot-csv" + data-target="download-plot-csv" + data-type="avg" + disabled + ) Download as .csv + div button.btn.btn-primary.box-shadow(id="convert-to-notebook" data-hook="convert-to-notebook") Convert to Notebook diff --git a/client/job-view/templates/gillespyResultsView.pug b/client/job-view/templates/gillespyResultsView.pug index 6985ade660..7022ba5373 100644 --- a/client/job-view/templates/gillespyResultsView.pug +++ b/client/job-view/templates/gillespyResultsView.pug @@ -64,6 +64,13 @@ div#workflow-results.card disabled ) Download JSON + button.btn.btn-primary.box-shadow( + data-hook="trajectories-plot-csv" + data-target="download-plot-csv" + data-type="trajectories" + disabled + ) Download as .csv + div button.btn.btn-primary.box-shadow(id="convert-to-notebook" data-hook="convert-to-notebook") Convert to Notebook diff --git a/client/job-view/templates/parameterScanResultsView.pug b/client/job-view/templates/parameterScanResultsView.pug index 685ade51d5..d8193963b2 100644 --- a/client/job-view/templates/parameterScanResultsView.pug +++ b/client/job-view/templates/parameterScanResultsView.pug @@ -84,6 +84,13 @@ div#workflow-results.card disabled ) Download JSON + button.btn.btn-primary.box-shadow( + data-hook="ts-psweep-plot-csv" + data-target="download-plot-csv" + data-type="ts-psweep" + disabled + ) Download as .csv + div.col-md-3 div.head(data-hook="plot-type-header") @@ -177,6 +184,13 @@ div#workflow-results.card disabled ) Download JSON + button.btn.btn-primary.box-shadow( + data-hook="psweep-plot-csv" + data-target="download-plot-csv" + data-type="psweep" + disabled + ) Download as .csv + div.col-md-3 div.head diff --git a/client/job-view/templates/parameterSweepResultsView.pug b/client/job-view/templates/parameterSweepResultsView.pug index a380284b83..66ac48c34a 100644 --- a/client/job-view/templates/parameterSweepResultsView.pug +++ b/client/job-view/templates/parameterSweepResultsView.pug @@ -84,6 +84,13 @@ div#workflow-results.card disabled ) Download JSON + button.btn.btn-primary.box-shadow( + data-hook="ts-psweep-plot-csv" + data-target="download-plot-csv" + data-type="ts-psweep" + disabled + ) Download as .csv + div.col-md-3 div.head(data-hook="plot-type-header") @@ -162,6 +169,13 @@ div#workflow-results.card data-type="psweep" disabled ) Download JSON + + button.btn.btn-primary.box-shadow( + data-hook="psweep-plot-csv" + data-target="download-plot-csv" + data-type="psweep" + disabled + ) Download as .csv div From a1892cb2a8b9a65b2dbdd666fa966567d40fd15c Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Wed, 18 Aug 2021 15:34:14 -0400 Subject: [PATCH 061/186] Added event handler function to get required data for csv download. Added function to launch csv download request. --- client/job-view/views/job-results-view.js | 25 +++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/client/job-view/views/job-results-view.js b/client/job-view/views/job-results-view.js index c0b721639b..bebf9ac8a5 100644 --- a/client/job-view/views/job-results-view.js +++ b/client/job-view/views/job-results-view.js @@ -49,6 +49,7 @@ module.exports = View.extend({ 'click [data-hook=multiple-plots]' : 'plotMultiplePlots', 'click [data-target=download-png-custom]' : 'handleDownloadPNGClick', 'click [data-target=download-json]' : 'handleDownloadJSONClick', + 'click [data-target=download-plot-csv]' : 'handlePlotCSVClick', 'click [data-hook=convert-to-notebook]' : 'handleConvertToNotebookClick', 'click [data-hook=download-results-csv]' : 'handleDownloadResultsCsvClick', 'click [data-hook=job-presentation]' : 'handlePresentationClick' @@ -132,10 +133,19 @@ module.exports = View.extend({ $(this.queryByHook(type + "-edit-plot")).prop("disabled", true); $(this.queryByHook(type + "-download-png-custom")).prop("disabled", true); $(this.queryByHook(type + "-download-json")).prop("disabled", true); + $(this.queryByHook(type + "-plot-csv")).prop("disabled", true); $(this.queryByHook("multiple-plots")).prop("disabled", true); } $(this.queryByHook(type + "-plot-spinner")).css("display", "block"); }, + downloadCSV: function (csvType, data) { + var queryStr = "?path=" + this.model.directory + "&type=" + csvType; + if(data) { + queryStr += "&data=" + JSON.stringify(data); + } + let endpoint = path.join(app.getApiPath(), "job/csv") + queryStr; + app.getXHR(endpoint); + }, getPlot: function (type) { let self = this; this.cleanupPlotContainer(type); @@ -310,6 +320,20 @@ module.exports = View.extend({ } }); }, + handlePlotCSVClick: function (e) { + let type = e.target.dataset.type; + if(type !== "psweep") { + var data = { + data_keys: type === "ts-psweep" ? this.getDataKeys(true) : {}, + proc_key: type === "ts-psweep" ? this.tsPlotData.type : type + } + var csvType = "time series" + }else{ + var data = this.getDataKeys(false) + var csvType = "psweep" + } + this.downloadCSV(csvType, data); + }, handlePresentationClick: function (e) { let self = this; this.startAction(); @@ -351,6 +375,7 @@ module.exports = View.extend({ $(this.queryByHook(type + "-edit-plot")).prop("disabled", false); $(this.queryByHook(type + "-download-png-custom")).prop("disabled", false); $(this.queryByHook(type + "-download-json")).prop("disabled", false); + $(this.queryByHook(type + "-plot-csv")).prop("disabled", false); if(type === "trajectories" || (this.tsPlotData && this.tsPlotData.type === "trajectories")) { $(this.queryByHook("multiple-plots")).prop("disabled", false); } From 8ef2c05efe5035a2cea8e01b875f305882b3d544 Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Wed, 18 Aug 2021 15:38:31 -0400 Subject: [PATCH 062/186] Added route and api handler for downloading csv files. --- stochss/handlers/__init__.py | 3 ++- stochss/handlers/workflows.py | 39 ++++++++++++++++++++++++++++++++--- 2 files changed, 38 insertions(+), 4 deletions(-) diff --git a/stochss/handlers/__init__.py b/stochss/handlers/__init__.py index de707d4ddb..92183e8e21 100644 --- a/stochss/handlers/__init__.py +++ b/stochss/handlers/__init__.py @@ -108,7 +108,8 @@ def get_page_handlers(route_start): (r"/stochss/api/workflow/save-plot\/?", SavePlotAPIHandler), (r"/stochss/api/workflow/save-annotation\/?", SaveAnnotationAPIHandler), (r"/stochss/api/workflow/update-format\/?", UpadteWorkflowAPIHandler), - (r"/stochss/api/job/presentation\/?", JobPresentationAPIHandler) + (r"/stochss/api/job/presentation\/?", JobPresentationAPIHandler), + (r"/stochss/api/job/csv\/?", DownloadCSVZipAPIHandler) ] full_handlers = list(map(lambda h: (url_path_join(route_start, h[0]), h[1]), handlers)) return full_handlers diff --git a/stochss/handlers/workflows.py b/stochss/handlers/workflows.py index ce57959779..ad6bd02bec 100644 --- a/stochss/handlers/workflows.py +++ b/stochss/handlers/workflows.py @@ -28,8 +28,7 @@ # Use finish() for json, write() for text from .util import StochSSJob, StochSSModel, StochSSSpatialModel, StochSSNotebook, StochSSWorkflow, \ - StochSSParamSweepNotebook, StochSSSciopeNotebook, StochSSAPIError, report_error, \ - StochSSFolder + StochSSParamSweepNotebook, StochSSSciopeNotebook, StochSSAPIError, report_error log = logging.getLogger('stochss') @@ -425,7 +424,7 @@ async def get(self): log.debug(f"Name of the job presentation: {name}") try: job = StochSSJob(path=path) - log.info("Publishing the %s presentation", job.get_name()) + log.info(f"Publishing the {job.get_name()} presentation") links, exists = job.publish_presentation(name=name) if exists: message = f"A presentation for {job.get_name()} already exists." @@ -438,3 +437,37 @@ async def get(self): except StochSSAPIError as err: report_error(self, log, err) self.finish() + + +class DownloadCSVZipAPIHandler(APIHandler): + ''' + ################################################################################################ + Handler for downloading job csv results files. + ################################################################################################ + ''' + @web.authenticated + async def get(self): + ''' + Download a jobs results as CSV. + + Attributes + ---------- + ''' + self.set_header('Content-Type', 'application/zip') + path = self.get_query_argument(name="path") + csv_type = self.get_query_argument(name="type") + data = self.get_query_argument(name="data") + try: + job = StochSSJob(path=path) + name = job.get_name() + self.set_header('Content-Disposition', f'attachment; filename="{name}.zip"') + if csv_type == "time series": + csv_data = job.get_csvzip_from_results(**data, name=name) + elif csv_type == "psweep": + csv_data = job.get_psweep_csvzip_from_results(fixed=data, name=name) + # else: + # csv_data = job.get_full_csvzip_from_results(name=name) + self.write(csv_data) + except StochSSAPIError as err: + report_error(self, log, err) + self.finish() From 7d60f348196636a7573ee4ffdb86a022909d27d7 Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Wed, 18 Aug 2021 16:14:35 -0400 Subject: [PATCH 063/186] Replaced the xhr request with window.open. --- client/job-view/views/job-results-view.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/job-view/views/job-results-view.js b/client/job-view/views/job-results-view.js index bebf9ac8a5..04c9db8c87 100644 --- a/client/job-view/views/job-results-view.js +++ b/client/job-view/views/job-results-view.js @@ -144,7 +144,7 @@ module.exports = View.extend({ queryStr += "&data=" + JSON.stringify(data); } let endpoint = path.join(app.getApiPath(), "job/csv") + queryStr; - app.getXHR(endpoint); + window.open(endpoint); }, getPlot: function (type) { let self = this; From 839f9e7d8d26d72565778e51a61b7a60de2bcf8b Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Wed, 18 Aug 2021 16:15:39 -0400 Subject: [PATCH 064/186] Fixed issue with the datas type. --- stochss/handlers/workflows.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stochss/handlers/workflows.py b/stochss/handlers/workflows.py index ad6bd02bec..c05e642008 100644 --- a/stochss/handlers/workflows.py +++ b/stochss/handlers/workflows.py @@ -456,7 +456,7 @@ async def get(self): self.set_header('Content-Type', 'application/zip') path = self.get_query_argument(name="path") csv_type = self.get_query_argument(name="type") - data = self.get_query_argument(name="data") + data = json.loads(self.get_query_argument(name="data", default=None)) try: job = StochSSJob(path=path) name = job.get_name() From 84d121910c62cdf33a106e049878549b539339eb Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Wed, 18 Aug 2021 16:59:20 -0400 Subject: [PATCH 065/186] Fixed function call issue for 2D parameter sweep jobs. --- stochss/handlers/util/stochss_job.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/stochss/handlers/util/stochss_job.py b/stochss/handlers/util/stochss_job.py index 9a90fcf171..0e6e1364b5 100644 --- a/stochss/handlers/util/stochss_job.py +++ b/stochss/handlers/util/stochss_job.py @@ -478,8 +478,8 @@ def get_psweep_csvzip_from_results(self, fixed, name): kwargs = {"results": self.__get_filtered_2d_results(f_keys, params[0])} kwargs["species"] = list(kwargs['results'][0][0][0].model.listOfSpecies.keys()) self.log("info", "Generating CSV files...") - ParameterSweep1D.to_csv( - param=params[0], kwargs=kwargs, path=tmp_dir.name, nametag=name + ParameterSweep2D.to_csv( + params=params, kwargs=kwargs, path=tmp_dir.name, nametag=name ) if fixed: csv_path = os.path.join(tmp_dir.name, name, "parameters.csv") From 332817ac287a9bdc2097569ec2b4271b7f77d281 Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Wed, 18 Aug 2021 17:19:31 -0400 Subject: [PATCH 066/186] Added csv download functions to the job presentations. --- jupyterhub/job_presentation.py | 221 +++++++++++++++++++++++++++++--- jupyterhub/jupyterhub_config.py | 29 ++--- 2 files changed, 214 insertions(+), 36 deletions(-) diff --git a/jupyterhub/job_presentation.py b/jupyterhub/job_presentation.py index e2a8bcafef..43c5a20613 100644 --- a/jupyterhub/job_presentation.py +++ b/jupyterhub/job_presentation.py @@ -54,9 +54,9 @@ async def get(self): ---------- ''' owner = self.get_query_argument(name="owner") - log.debug("Container id of the owner: %s", owner) + log.debug(f"Container id of the owner: {owner}") file = self.get_query_argument(name="file") - log.debug("Name to the file: %s", file) + log.debug(f"Name to the file: {file}") self.set_header('Content-Type', 'application/json') try: path = os.path.join("/tmp/presentation_cache", file) @@ -65,7 +65,7 @@ async def get(self): else: job = get_presentation_from_user(owner=owner, file=file, kwargs={"file": file}, process_func=process_job_presentation) - log.debug("Contents of the json file: %s", job) + log.debug(f"Contents of the json file: {job}") self.write(job) except StochSSAPIError as load_err: report_error(self, log, load_err) @@ -85,8 +85,8 @@ async def get(self, owner, file): Attributes ---------- ''' - log.debug("Container id of the owner: %s", owner) - log.debug("Name to the file: %s", file) + log.debug(f"Container id of the owner: {owner}") + log.debug(f"Name to the file: {file}") self.set_header('Content-Type', 'application/zip') job, name = get_presentation_from_user(owner=owner, file=file, kwargs={"for_download": True}, @@ -111,9 +111,9 @@ async def get(self): ''' self.set_header('Content-Type', 'application/json') path = self.get_query_argument(name="path") - log.debug("The path to the workflow: %s", path) + log.debug(f"The path to the workflow: {path}") body = json.loads(self.get_query_argument(name='data')) - log.debug("Plot args passed to the plot: %s", body) + log.debug(f"Plot args passed to the plot: {body}") try: job = StochSSJob(path=path) if body['sim_type'] in ("GillesPy2", "GillesPy2_PS"): @@ -126,7 +126,7 @@ async def get(self): job.print_logs(log) if "plt_data" in body.keys(): fig = job.update_fig_layout(fig=fig, plt_data=body['plt_data']) - log.debug("Plot figure: %s", fig) + log.debug(f"Plot figure: {fig}") self.write(fig) except StochSSAPIError as err: report_error(self, log, err) @@ -146,13 +146,21 @@ async def get(self): Attributes ---------- ''' - self.set_header('Content-Type', 'application/json') + self.set_header('Content-Type', 'application/zip') path = self.get_query_argument(name="path") - log.debug("The path to the workflow: %s", path) - body = json.loads(self.get_query_argument(name='data')) - log.debug("Plot args passed to the plot: %s", body) + csv_type = self.get_query_argument(name="type") + data = json.loads(self.get_query_argument(name="data", default=None)) try: job = StochSSJob(path=path) + name = job.load()['name'] + self.set_header('Content-Disposition', f'attachment; filename="{name}.zip"') + if csv_type == "time series": + csv_data = job.get_csvzip_from_results(**data, name=name) + elif csv_type == "psweep": + csv_data = job.get_psweep_csvzip_from_results(fixed=data, name=name) + # else: + # csv_data = job.get_full_csvzip_from_results(name=name) + self.write(csv_data) except StochSSAPIError as err: report_error(self, log, err) self.finish() @@ -388,11 +396,8 @@ def get_plot_from_results(self, data_keys, plt_key, add_config=False): add_config : bool Whether or not to add the config key to the plot fig ''' - self.log("debug", f"Key identifying the plot to generate: {plt_key}") try: - self.log("info", "Loading the results...") result = self.__get_filtered_ensemble_results(data_keys) - self.log("info", "Generating the plot...") if plt_key == "mltplplt": fig = result.plotplotly(return_plotly_figure=True, multiple_graphs=True) elif plt_key == "stddevran": @@ -405,7 +410,6 @@ def get_plot_from_results(self, data_keys, plt_key, add_config=False): fig = result.plotplotly(return_plotly_figure=True) if add_config and plt_key != "mltplplt": fig["config"] = {"responsive":True} - self.log("info", "Loading the plot...") return json.loads(json.dumps(fig, cls=plotly.utils.PlotlyJSONEncoder)) except FileNotFoundError as err: message = f"Could not find the results pickle file: {str(err)}" @@ -415,6 +419,50 @@ def get_plot_from_results(self, data_keys, plt_key, add_config=False): raise PlotNotAvailableError(message, traceback.format_exc()) from err + def get_psweep_csvzip_from_results(self, fixed, name): + ''' + Get the csv file of the parameter sweep plot data for download + + Attributes + ---------- + fixed : dict + Dictionary for parameters that remain at a fixed value. + ''' + settings = self.load()['settings'] + try: + dims, f_keys = self.__get_fixed_keys_and_dims(settings, fixed) + params = list(filter(lambda param: param['name'] not in fixed.keys(), + settings['parameterSweepSettings']['parameters'])) + tmp_dir = tempfile.TemporaryDirectory() + if dims == 1: + kwargs = {"results": self.__get_filtered_1d_results(f_keys)} + kwargs["species"] = list(kwargs['results'][0][0].model.listOfSpecies.keys()) + ParameterSweep1D.to_csv( + param=params[0], kwargs=kwargs, path=tmp_dir.name, nametag=name + ) + else: + kwargs = {"results": self.__get_filtered_2d_results(f_keys, params[0])} + kwargs["species"] = list(kwargs['results'][0][0][0].model.listOfSpecies.keys()) + ParameterSweep2D.to_csv( + params=params, kwargs=kwargs, path=tmp_dir.name, nametag=name + ) + if fixed: + csv_path = os.path.join(tmp_dir.name, name, "parameters.csv") + with open(csv_path, "w") as csv_file: + csv_writer = csv.writer(csv_file) + csv_writer.writerow(list(fixed.keys())) + csv_writer.writerow(list(fixed.values())) + shutil.make_archive(os.path.join(tmp_dir.name, name), "zip", tmp_dir.name, name) + with open(os.path.join(tmp_dir.name, f"{name}.zip"), "rb") as zip_file: + return zip_file.read() + except FileNotFoundError as err: + message = f"Could not find the results pickle file: {str(err)}" + raise StochSSFileNotFoundError(message, traceback.format_exc()) from err + except KeyError as err: + message = f"The requested results are not available: {str(err)}" + raise PlotNotAvailableError(message, traceback.format_exc()) from err + + def get_psweep_plot_from_results(self, fixed, kwargs, add_config=False): ''' Generate and return the parameter sweep plot form the time series results. @@ -426,26 +474,21 @@ def get_psweep_plot_from_results(self, fixed, kwargs, add_config=False): kwarps : dict Dictionary of keys used for post proccessing the results. ''' - self.log("debug", f"Key identifying the plot to generate: {kwargs}") settings = self.load()['settings'] try: - self.log("info", "Loading the results...") dims, f_keys = self.__get_fixed_keys_and_dims(settings, fixed) params = list(filter(lambda param: param['name'] not in fixed.keys(), settings['parameterSweepSettings']['parameters'])) if dims == 1: kwargs['param'] = params[0] kwargs['results'] = self.__get_filtered_1d_results(f_keys) - self.log("info", "Generating the plot...") fig = ParameterSweep1D.plot(**kwargs) else: kwargs['params'] = params kwargs['results'] = self.__get_filtered_2d_results(f_keys, params[0]) - self.log("info", "Generating the plot...") fig = ParameterSweep2D.plot(**kwargs) if add_config: fig['config'] = {"responsive": True} - self.log("info", "Loading the plot...") return json.loads(json.dumps(fig, cls=plotly.utils.PlotlyJSONEncoder)) except FileNotFoundError as err: message = f"Could not find the results pickle file: {str(err)}" @@ -488,6 +531,17 @@ class ParameterSweep1D(): ################################################################################################ ''' + @classmethod + def __get_csv_data(cls, results=None, species=None, mapper=None, reducer=None): + data = [] + for specie in species: + p_data, _ = cls.__process_results( + results=results, species=specie, mapper=mapper, reducer=reducer + ) + data.extend([list(p_data[:,0]), list(p_data[:,1])]) + return data + + @classmethod def __process_results(cls, results, species, mapper="final", reducer="avg"): func_map = {"min": numpy.min, "max": numpy.max, "avg": numpy.mean, @@ -503,6 +557,18 @@ def __process_results(cls, results, species, mapper="final", reducer="avg"): return numpy.array(data), visible + @classmethod + def __write_csv_file(cls, path, header, param, data): + with open(path, "w") as csv_file: + csv_writer = csv.writer(csv_file) + csv_writer.writerow(header) + for i, val in enumerate(param['range']): + line = [val] + for col in data: + line.append(col[i]) + csv_writer.writerow(line) + + @classmethod def plot(cls, results, species, param, mapper="final", reducer="avg"): ''' @@ -537,6 +603,51 @@ def plot(cls, results, species, param, mapper="final", reducer="avg"): return json.loads(json.dumps(fig, cls=plotly.utils.PlotlyJSONEncoder)) + @classmethod + def to_csv(cls, param, kwargs, path=None, nametag="results_csv"): + ''' + Output the post-process results as a series of CSV files. + + Attributes + ---------- + param : dict + Sweep parameter object + kwargs : dict + Filtered results and full list of Model species + path : str + Parent path to the csv directory + nametag : str + Name of the csv directory + ''' + if path is None: + directory = os.path.join(".", str(nametag)) + else: + directory = os.path.join(path, str(nametag)) + os.mkdir(directory) + # Define header row for all files + header = [param['name']] + for specie in kwargs['species']: + header.extend([specie, f"{specie}-stddev"]) + # Get all CSV file data + mappers = ['min', 'max', 'avg', 'var', 'final'] + if len(kwargs['results'][0].data) == 1: + for mapper in mappers: + path = os.path.join(directory, f"{mapper}.csv") + # Get csv data for a mapper + data = cls.__get_csv_data(**kwargs, mapper=mapper) + # Write csv file + cls.__write_csv_file(path, header, param, data) + else: + reducers = mappers[:-1] + for mapper in mappers: + for reducer in reducers: + path = os.path.join(directory, f"{mapper}-{reducer}.csv") + # Get csv data for a mapper and a reducer + data = cls.__get_csv_data(**kwargs, mapper=mapper, reducer=reducer) + # Write csv file + cls.__write_csv_file(path, header, param, data) + + class ParameterSweep2D(): ''' ################################################################################################ @@ -544,6 +655,17 @@ class ParameterSweep2D(): ################################################################################################ ''' + @classmethod + def __get_csv_data(cls, results=None, species=None, mapper=None, reducer=None): + data = [] + for specie in species: + p_data = cls.__process_results( + results=results, species=specie, mapper=mapper, reducer=reducer + ) + data.append([list(_data) for _data in p_data]) + return data + + @classmethod def __process_results(cls, results, species, mapper="final", reducer="avg"): func_map = {"min": numpy.min, "max": numpy.max, "avg": numpy.mean, @@ -560,6 +682,19 @@ def __process_results(cls, results, species, mapper="final", reducer="avg"): return numpy.array(data) + @classmethod + def __write_csv_file(cls, path, header, params, data): + with open(path, "w") as csv_file: + csv_writer = csv.writer(csv_file) + csv_writer.writerow(header) + for i, val1 in enumerate(params[0]['range']): + for j, val2 in enumerate(params[1]['range']): + line = [val1, val2] + for col in data: + line.append(col[i][j]) + csv_writer.writerow(line) + + @classmethod def plot(cls, results, species, params, mapper="final", reducer="avg"): ''' @@ -590,3 +725,47 @@ def plot(cls, results, species, params, mapper="final", reducer="avg"): yaxis=dict(title=f"{params[1]['name']}")) fig = dict(data=trace_list, layout=layout) return json.loads(json.dumps(fig, cls=plotly.utils.PlotlyJSONEncoder)) + + + @classmethod + def to_csv(cls, params, kwargs, path=None, nametag="results_csv"): + ''' + Output the post-process results as a series of CSV files. + + Attributes + ---------- + params : list + List of sweep parameter objects + kwargs : dict + Filtered results and full list of Model species + path : str + Parent path to the csv directory + nametag : str + Name of the csv directory + ''' + if path is None: + directory = os.path.join(".", str(nametag)) + else: + directory = os.path.join(path, str(nametag)) + os.mkdir(directory) + # Define header row for all files + header = [params[0]['name'], params[1]['name']] + header.extend(kwargs['species']) + # Get all CSV file data + mappers = ['min', 'max', 'avg', 'var', 'final'] + if len(kwargs['results'][0][0].data) == 1: + for mapper in mappers: + path = os.path.join(directory, f"{mapper}.csv") + # Get csv data for a mapper + data = cls.__get_csv_data(**kwargs, mapper=mapper) + # Write csv file + cls.__write_csv_file(path, header, params, data) + else: + reducers = mappers[:-1] + for mapper in mappers: + for reducer in reducers: + path = os.path.join(directory, f"{mapper}-{reducer}.csv") + # Get csv data for a mapper and a reducer + data = cls.__get_csv_data(**kwargs, mapper=mapper, reducer=reducer) + # Write csv file + cls.__write_csv_file(path, header, params, data) diff --git a/jupyterhub/jupyterhub_config.py b/jupyterhub/jupyterhub_config.py index 7412f5e1c0..d871a6280f 100644 --- a/jupyterhub/jupyterhub_config.py +++ b/jupyterhub/jupyterhub_config.py @@ -101,7 +101,7 @@ (r"/stochss/api/job/load\/?", JobAPIHandler), (r"/stochss/job/download_presentation/(\w+)/(.+)\/?", DownJobPresentationAPIHandler), (r"/stochss/api/workflow/plot-results\/?", PlotJobResultsAPIHandler), - (r"/stochss/api/job/download-csv\/?", DownloadCSVAPIHandler) + (r"/stochss/api/job/csv\/?", DownloadCSVAPIHandler) ] ## Paths to search for jinja templates, before using the default templates. @@ -203,7 +203,7 @@ def get_user_cpu_count_or_fail(): ''' log = logging.getLogger() reserve_count = int(os.environ['RESERVED_CPUS']) - log.info("RESERVED_CPUS environment variable is set to %s", reserve_count) + log.info(f"RESERVED_CPUS environment variable is set to {reserve_count}") # Round up to an even number of reserved cpus if reserve_count % 2 > 0: message = "Increasing reserved cpu count by one so it's an even number." @@ -211,7 +211,7 @@ def get_user_cpu_count_or_fail(): log.warning(message) reserve_count += 1 total_cpus = os.cpu_count() - log.info("Total cpu count as reported by os.count: %s", total_cpus) + log.info(f"Total cpu count as reported by os.count: {total_cpus}") if reserve_count >= total_cpus: e_message = "RESERVED_CPUS environment cannot be greater than or equal to the number of" e_message += " cpus returned by os.cpu_count()" @@ -223,8 +223,8 @@ def get_user_cpu_count_or_fail(): if user_cpu_count % 2 > 0 and user_cpu_count > 1: user_cpu_count -= 1 c.StochSS.reserved_cpu_count = reserve_count - log.info('Using %s logical cpus for user containers...', user_cpu_count) - log.info('Reserving %s logical cpus for hub container and underlying OS', reserve_count) + log.info(f'Using {user_cpu_count} logical cpus for user containers...') + log.info(f'Reserving {reserve_count} logical cpus for hub container and underlying OS') return user_cpu_count c.StochSS.user_cpu_count = get_user_cpu_count_or_fail() @@ -259,8 +259,8 @@ def pre_spawn_hook(spawner): palloc = c.StochSS.user_cpu_alloc div = len(palloc) // 2 reserved = c.StochSS.reserved_cpu_count - log.warning('Reserved CPUs: %s', reserved) - log.warning('Number of user containers using each logical core: %s', palloc) + log.warning(f'Reserved CPUs: {reserved}') + log.warning(f'Number of user containers using each logical core: {palloc}') # We want to allocate logical cores that are on the same physical core # whenever possible. # @@ -290,18 +290,17 @@ def pre_spawn_hook(spawner): avail_cpus = palloc least_used_cpu = min(avail_cpus) cpu1_index = avail_cpus.index(least_used_cpu) - log.info("User %s to use logical cpu %s", spawner.user.name, str(cpu1_index)) + log.info(f"User {spawner.user.name} to use logical cpu {cpu1_index}") palloc[cpu1_index] += 1 - spawner.extra_host_config['cpuset_cpus'] = '{}'.format(cpu1_index) + spawner.extra_host_config['cpuset_cpus'] = f'{cpu1_index}' else: least_used_cpu = min(avail_cpus) cpu1_index = avail_cpus.index(least_used_cpu) palloc[cpu1_index] += 1 cpu2_index = cpu1_index+div palloc[cpu2_index] += 1 - log.info("User %s to use logical cpus %s and %s", - spawner.user.name, str(cpu1_index), str(cpu2_index)) - spawner.extra_host_config['cpuset_cpus'] = '{},{}'.format(cpu1_index, cpu2_index) + log.info(f"User {spawner.user.name} to use logical cpus {cpu1_index} and {cpu2_index}") + spawner.extra_host_config['cpuset_cpus'] = f'{cpu1_index},{cpu2_index}' def post_stop_hook(spawner): @@ -315,11 +314,11 @@ def post_stop_hook(spawner): cpu1_index, cpu2_index = spawner.extra_host_config['cpuset_cpus'].split(',') palloc[int(cpu1_index)] -= 1 palloc[int(cpu2_index)] -= 1 - log.warning('Reserved CPUs: %s', reserved) - log.warning('Number of user containers using each logical core: %s', palloc) + log.warning(f'Reserved CPUs: {reserved}') + log.warning(f'Number of user containers using each logical core: {palloc}') except Exception as err: message = "Exception thrown due to cpuset_cpus not being set (power user)" - log.error("%s\n%s", message, err) + log.error(f"{message}\n{err}") # Exception thrown due to cpuset_cpus not being set (power user) pass From fe52f337414557f583a88c4abafcfaac5757375b Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Thu, 19 Aug 2021 12:55:02 -0400 Subject: [PATCH 067/186] Added function to download the full set of raw and processed result data. --- stochss/handlers/util/stochss_job.py | 352 +++++++++++++++++---------- 1 file changed, 219 insertions(+), 133 deletions(-) diff --git a/stochss/handlers/util/stochss_job.py b/stochss/handlers/util/stochss_job.py index 0e6e1364b5..80430b92cf 100644 --- a/stochss/handlers/util/stochss_job.py +++ b/stochss/handlers/util/stochss_job.py @@ -27,6 +27,8 @@ import datetime import traceback +from itertools import combinations + import numpy import plotly @@ -107,8 +109,49 @@ def __create_settings(self): self.__get_settings_path(full=True)) - def __get_settings_path(self, full=False): - return os.path.join(self.get_path(full=full), "settings.json") + @classmethod + def __get_1d_fixed_list(cls, results): + keys = [key.split(',') for key in results.keys()] + p_names = [param.split(':')[0] for param in keys[0]] + _f_keys = [] + for name in p_names: + for key in keys: + f_key = list(filter(lambda key_el, key=key, name=name: name not in key_el, key)) + _f_keys.append(",".join(f_key)) + f_keys = sorted(list(set(_f_keys))) + fixed_list = [] + for key in f_keys: + data = [_data.split(':') for _data in key.split(',')] + fixed = {_data[0]: _data[1] for _data in data} + fixed_list.append(fixed) + return fixed_list + + + @classmethod + def __get_2d_fixed_list(cls, results): + keys = [key.split(',') for key in results.keys()] + p_names = list(combinations([param.split(':')[0] for param in keys[0]], 2)) + _f_keys = [] + for names in p_names: + for key in keys: + test = lambda key_el, names=names: names[0] not in key_el and names[1] not in key_el + f_key = list(filter(lambda key_el, test=test: test(key_el), key)) + _f_keys.append(",".join(f_key)) + f_keys = sorted(list(set(_f_keys))) + fixed_list = [] + for key in f_keys: + data = [_data.split(':') for _data in key.split(',')] + fixed = {_data[0]: _data[1] for _data in data} + fixed_list.append(fixed) + return fixed_list + + + @classmethod + def __get_csvzip(cls, dirname, name): + shutil.make_archive(os.path.join(dirname, name), "zip", dirname, name) + path = os.path.join(dirname, f"{name}.zip") + with open(path, "rb") as zip_file: + return zip_file.read() def __get_extract_dst_path(self, mdl_file): @@ -132,31 +175,6 @@ def __get_extract_dst_path(self, mdl_file): return os.path.join(wkgp_path, mdl_file) - def __get_info_path(self, full=False): - ''' - Return the path to the info.json file - - Attributes - ---------- - full : bool - Indicates whether or not to get the full path or local path - ''' - return os.path.join(self.get_path(full=full), "info.json") - - - @classmethod - def __get_presentation_links(cls, file): - safe_chars = set(string.ascii_letters + string.digits) - hostname = escape(os.environ.get('JUPYTERHUB_USER'), safe=safe_chars) - query_str = f"?owner={hostname}&file={file}" - present_link = f"https://live.stochss.org/stochss/present-job{query_str}" - dl_base = "https://live.stochss.org/stochss/job/download_presentation" - downloadlink = os.path.join(dl_base, hostname, file) - open_link = f"https://open.stochss.org?open={downloadlink}" - links = {"presentation": present_link, "download": downloadlink, "open": open_link} - return links - - def __get_filtered_1d_results(self, f_keys): results = self.__get_pickled_results() f_results = [] @@ -188,6 +206,48 @@ def __get_filtered_ensemble_results(self, data_keys): return result + def __get_full_1dpsweep_csv(self, b_path, results, get_name, name): + settings = self.load_settings() + od_path = os.path.join(b_path, "1D_Resutls") + os.mkdir(od_path) + fixed_list = self.__get_1d_fixed_list(results) + for i, fixed in enumerate(fixed_list): + param = list(filter(lambda param, fixed=fixed: param['name'] not in fixed.keys(), + settings['parameterSweepSettings']['parameters']))[0] + kwargs = {'results': self.__get_filtered_1d_results(fixed)} + kwargs["species"] = list(kwargs['results'][0][0].model.listOfSpecies.keys()) + ParameterSweep1D.to_csv( + param=param, kwargs=kwargs, path=od_path, nametag=get_name(name, i) + ) + self.__write_parameters_csv(path=od_path, name=get_name(name, i), data_keys=fixed) + + + def __get_full_2dpsweep_csv(self, b_path, results, get_name, name): + settings = self.load_settings() + td_path = os.path.join(b_path, "2D_Resutls") + os.mkdir(td_path) + fixed_list = self.__get_2d_fixed_list(results) + for i, fixed in enumerate(fixed_list): + params = list(filter(lambda param, fixed=fixed: param['name'] not in fixed.keys(), + settings['parameterSweepSettings']['parameters'])) + kwargs = {"results": self.__get_filtered_2d_results(fixed, params[0])} + kwargs["species"] = list(kwargs['results'][0][0][0].model.listOfSpecies.keys()) + ParameterSweep2D.to_csv( + params=params, kwargs=kwargs, path=td_path, nametag=get_name(name, i) + ) + self.__write_parameters_csv(path=td_path, name=get_name(name, i), data_keys=fixed) + + + def __get_full_timeseries_csv(self, b_path, results, get_name, name): + ts_path = os.path.join(b_path, "Time_Series") + os.makedirs(ts_path) + for i, (key, result) in enumerate(results.items()): + data = [_data.split(':') for _data in key.split(',')] + data_keys = {_data[0]: _data[1] for _data in data} + result.to_csv(path=ts_path, nametag=get_name(name, i), stamp="") + self.__write_parameters_csv(path=ts_path, name=get_name(name, i), data_keys=data_keys) + + @classmethod def __get_fixed_keys_and_dims(cls, settings, fixed): p_len = len(settings['parameterSweepSettings']['parameters']) @@ -204,24 +264,51 @@ def __get_fixed_keys_and_dims(cls, settings, fixed): return dims, f_keys + def __get_info_path(self, full=False): + return os.path.join(self.get_path(full=full), "info.json") + + def __get_pickled_results(self): path = os.path.join(self.__get_results_path(full=True), "results.p") with open(path, "rb") as results_file: return pickle.load(results_file) - def __get_results_path(self, full=False): - ''' - Return the path to the results directory + @classmethod + def __get_presentation_links(cls, file): + safe_chars = set(string.ascii_letters + string.digits) + hostname = escape(os.environ.get('JUPYTERHUB_USER'), safe=safe_chars) + query_str = f"?owner={hostname}&file={file}" + present_link = f"https://live.stochss.org/stochss/present-job{query_str}" + dl_base = "https://live.stochss.org/stochss/job/download_presentation" + downloadlink = os.path.join(dl_base, hostname, file) + open_link = f"https://open.stochss.org?open={downloadlink}" + links = {"presentation": present_link, "download": downloadlink, "open": open_link} + return links - Attributes - ---------- - full : bool - Indicates whether or not to get the full path or local path - ''' + + def __get_results_path(self, full=False): return os.path.join(self.get_path(full=full), "results") + def __get_run_logs(self): + path = os.path.join(self.get_path(full=True), "logs.txt") + try: + with open(path, "r") as file: + logs = file.read() + self.log("debug", f"Contents of the log file: {logs}") + if logs: + return logs + return "No logs were recoded for this job." + except FileNotFoundError as err: + message = f"Could not find the log file: {str(err)}" + raise StochSSFileNotFoundError(message, traceback.format_exc()) from err + + + def __get_settings_path(self, full=False): + return os.path.join(self.get_path(full=full), "settings.json") + + def __is_csv_dir(self, file): if "results_csv" not in file: return False @@ -270,6 +357,15 @@ def __update_settings(self): "parameters": parameters} + @classmethod + def __write_parameters_csv(cls, path, name, data_keys): + csv_path = os.path.join(path, name, "parameters.csv") + with open(csv_path, "w") as csv_file: + csv_writer = csv.writer(csv_file) + csv_writer.writerow(list(data_keys.keys())) + csv_writer.writerow(list(data_keys.values())) + + def duplicate_as_new(self, stamp): ''' Get all data needed to create a new job that @@ -312,6 +408,65 @@ def extract_model(self): return resp, kwargs + def get_csvzip_from_results(self, data_keys, proc_key, name): + ''' + Get the csv files of the plot data for download. + + Attributes + ---------- + data_keys : dict + Dictionary of param names and values used to identify the correct data. + proc_key : str + Type post processing to preform. + name : str + Name of the csv directory + ''' + try: + self.log("info", "Getting job results...") + result = self.__get_filtered_ensemble_results(data_keys) + self.log("info", "Processing results...") + if proc_key == "stddev": + result = result.stddev_ensemble() + elif proc_key == "avg": + result = result.average_ensemble() + self.log("info", "Generating CSV files...") + tmp_dir = tempfile.TemporaryDirectory() + result.to_csv(path=tmp_dir.name, nametag=name, stamp="") + if data_keys: + self.__write_parameters_csv(path=tmp_dir.name, name=name, data_keys=data_keys) + self.log("info", "Generating zip archive...") + return self.__get_csvzip(dirname=tmp_dir.name, name=name) + except FileNotFoundError as err: + message = f"Could not find the results pickle file: {str(err)}" + raise StochSSFileNotFoundError(message, traceback.format_exc()) from err + except KeyError as err: + message = f"The requested results are not available: {str(err)}" + raise PlotNotAvailableError(message, traceback.format_exc()) from err + + + def get_full_csvzip_from_results(self, name): + ''' + Get the csv files for the full set of results data + + Attributes + ---------- + name : str + Name of the csv directory. + ''' + results = self.__get_pickled_results() + tmp_dir = tempfile.TemporaryDirectory() + if not isinstance(results, dict): + results.to_csv(path=tmp_dir.name, nametag=name, stamp="") + return self.__get_csvzip(dirname=tmp_dir.name, name=name) + def get_name(b_name, tag): + return f"{b_name}_{tag}" + b_path = os.path.join(tmp_dir.name, get_name(name, "full")) + self.__get_full_timeseries_csv(b_path, results, get_name, name) + self.__get_full_1dpsweep_csv(b_path, results, get_name, name) + self.__get_full_2dpsweep_csv(b_path, results, get_name, name) + return self.__get_csvzip(dirname=tmp_dir.name, name=get_name(name, "full")) + + def get_model_path(self, full=False, external=False): ''' Return the path to the model file @@ -370,49 +525,6 @@ def get_notebook_data(self): return {"kwargs":kwargs, "type":wkfl_type} - def get_csvzip_from_results(self, data_keys, proc_key, name): - ''' - Get the csv files of the plot data for download. - - Attributes - ---------- - data_keys : dict - Dictionary of param names and values used to identify the correct data. - proc_key : str - Type post processing to preform. - name : str - Name of the csv directory - ''' - try: - self.log("info", "Getting job results...") - result = self.__get_filtered_ensemble_results(data_keys) - self.log("info", "Processing results...") - if proc_key == "stddev": - result = result.stddev_ensemble() - elif proc_key == "avg": - result = result.average_ensemble() - self.log("info", "Generating CSV files...") - tmp_dir = tempfile.TemporaryDirectory() - result.to_csv(path=tmp_dir.name, nametag=name, stamp="") - if data_keys: - csv_path = os.path.join(tmp_dir.name, name, "parameters.csv") - with open(csv_path, "w") as csv_file: - csv_writer = csv.writer(csv_file) - csv_writer.writerow(list(data_keys.keys())) - csv_writer.writerow(list(data_keys.values())) - self.log("info", "Generating zip archive...") - shutil.make_archive(os.path.join(tmp_dir.name, name), "zip", tmp_dir.name, name) - path = os.path.join(tmp_dir.name, f"{name}.zip") - with open(path, "rb") as zip_file: - return zip_file.read() - except FileNotFoundError as err: - message = f"Could not find the results pickle file: {str(err)}" - raise StochSSFileNotFoundError(message, traceback.format_exc()) from err - except KeyError as err: - message = f"The requested results are not available: {str(err)}" - raise PlotNotAvailableError(message, traceback.format_exc()) from err - - def get_plot_from_results(self, data_keys, plt_key, add_config=False): ''' Get the plotly figure for the results of a job @@ -482,14 +594,8 @@ def get_psweep_csvzip_from_results(self, fixed, name): params=params, kwargs=kwargs, path=tmp_dir.name, nametag=name ) if fixed: - csv_path = os.path.join(tmp_dir.name, name, "parameters.csv") - with open(csv_path, "w") as csv_file: - csv_writer = csv.writer(csv_file) - csv_writer.writerow(list(fixed.keys())) - csv_writer.writerow(list(fixed.values())) - shutil.make_archive(os.path.join(tmp_dir.name, name), "zip", tmp_dir.name, name) - with open(os.path.join(tmp_dir.name, f"{name}.zip"), "rb") as zip_file: - return zip_file.read() + self.__write_parameters_csv(path=tmp_dir.name, name=name, data_keys=fixed) + return self.__get_csvzip(dirname=tmp_dir.name, name=name) except FileNotFoundError as err: message = f"Could not find the results pickle file: {str(err)}" raise StochSSFileNotFoundError(message, traceback.format_exc()) from err @@ -538,52 +644,6 @@ def get_psweep_plot_from_results(self, fixed, kwargs, add_config=False): raise PlotNotAvailableError(message, traceback.format_exc()) from err - def update_fig_layout(self, fig=None, plt_data=None): - ''' - Update the figure layout. - - Attributes - ---------- - fig : dict - Plotly figure to be updated - plt_data : dict - Title and axes data for the plot - ''' - self.log("debug", f"Title and axis data for the plot: {plt_data}") - try: - if plt_data is None: - return fig - for key in plt_data.keys(): - if key == "title": - fig['layout']['title']['text'] = plt_data[key] - else: - fig['layout'][key]['title']['text'] = plt_data[key] - return fig - except KeyError as err: - message = f"The requested plot is not available: {str(err)}" - raise PlotNotAvailableError(message, traceback.format_exc()) from err - - - def get_run_logs(self): - ''' - Return the contents of the log file' - - Attributes - ---------- - ''' - path = os.path.join(self.get_path(full=True), "logs.txt") - try: - with open(path, "r") as file: - logs = file.read() - self.log("debug", f"Contents of the log file: {logs}") - if logs: - return logs - return "No logs were recoded for this job." - except FileNotFoundError as err: - message = f"Could not find the log file: {str(err)}" - raise StochSSFileNotFoundError(message, traceback.format_exc()) from err - - @classmethod def get_run_settings(cls, settings, solver_map): ''' @@ -656,7 +716,7 @@ def load(self, new=False): finally: settings = self.load_settings(model=model) if os.path.exists(os.path.join(self.path, "logs.txt")): - logs = self.get_run_logs() + logs = self.__get_run_logs() else: logs = "" self.job = {"mdlPath":mdl_path, "model":model, "settings":settings, @@ -821,6 +881,32 @@ def save_plot(self, plot): return {"message":"The plot was successfully saved", "data":plot} + def update_fig_layout(self, fig=None, plt_data=None): + ''' + Update the figure layout. + + Attributes + ---------- + fig : dict + Plotly figure to be updated + plt_data : dict + Title and axes data for the plot + ''' + self.log("debug", f"Title and axis data for the plot: {plt_data}") + try: + if plt_data is None: + return fig + for key in plt_data.keys(): + if key == "title": + fig['layout']['title']['text'] = plt_data[key] + else: + fig['layout'][key]['title']['text'] = plt_data[key] + return fig + except KeyError as err: + message = f"The requested plot is not available: {str(err)}" + raise PlotNotAvailableError(message, traceback.format_exc()) from err + + def update_info(self, new_info, new=False): ''' Updates the contents of the info file. If source model is updated, From cc130ff22903ff1a9c171ef6ab1c939a7998d463 Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Thu, 19 Aug 2021 12:57:47 -0400 Subject: [PATCH 068/186] Enable full csv download option. --- stochss/handlers/workflows.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/stochss/handlers/workflows.py b/stochss/handlers/workflows.py index c05e642008..c138a2912d 100644 --- a/stochss/handlers/workflows.py +++ b/stochss/handlers/workflows.py @@ -465,8 +465,8 @@ async def get(self): csv_data = job.get_csvzip_from_results(**data, name=name) elif csv_type == "psweep": csv_data = job.get_psweep_csvzip_from_results(fixed=data, name=name) - # else: - # csv_data = job.get_full_csvzip_from_results(name=name) + else: + csv_data = job.get_full_csvzip_from_results(name=name) self.write(csv_data) except StochSSAPIError as err: report_error(self, log, err) From 766aecf19fd5bf1f4a99b01fcaba92518685a2e3 Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Thu, 19 Aug 2021 13:05:26 -0400 Subject: [PATCH 069/186] Refactored the download results as csv event handle and function to use the new api route for full results. --- client/job-view/views/job-results-view.js | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/client/job-view/views/job-results-view.js b/client/job-view/views/job-results-view.js index 04c9db8c87..6d5b4e621f 100644 --- a/client/job-view/views/job-results-view.js +++ b/client/job-view/views/job-results-view.js @@ -51,7 +51,7 @@ module.exports = View.extend({ 'click [data-target=download-json]' : 'handleDownloadJSONClick', 'click [data-target=download-plot-csv]' : 'handlePlotCSVClick', 'click [data-hook=convert-to-notebook]' : 'handleConvertToNotebookClick', - 'click [data-hook=download-results-csv]' : 'handleDownloadResultsCsvClick', + 'click [data-hook=download-results-csv]' : 'handleFullCSVClick', 'click [data-hook=job-presentation]' : 'handlePresentationClick' }, initialize: function (attrs, options) { @@ -310,15 +310,8 @@ module.exports = View.extend({ let pngButton = $('div[data-hook=' + type + '-plot] a[data-title*="Download plot as a png"]')[0]; pngButton.click(); }, - handleDownloadResultsCsvClick: function (e) { - let self = this; - let queryStr = "?path=" + this.model.directory + "&action=resultscsv"; - let endpoint = path.join(app.getApiPath(), "file/download-zip") + queryStr; - app.getXHR(endpoint, { - success: function (err, response, body) { - window.open(path.join("files", body.Path)); - } - }); + handleFullCSVClick: function (e) { + this.downloadCSV("full", null); }, handlePlotCSVClick: function (e) { let type = e.target.dataset.type; From 2f27f7b9149fe24bace181aa10bdd9f04b498283 Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Thu, 19 Aug 2021 13:06:34 -0400 Subject: [PATCH 070/186] Added the download full results as csv button. --- client/job-view/templates/parameterScanResultsView.pug | 2 ++ 1 file changed, 2 insertions(+) diff --git a/client/job-view/templates/parameterScanResultsView.pug b/client/job-view/templates/parameterScanResultsView.pug index d8193963b2..19cff01799 100644 --- a/client/job-view/templates/parameterScanResultsView.pug +++ b/client/job-view/templates/parameterScanResultsView.pug @@ -201,6 +201,8 @@ div#workflow-results.card div + button.btn.btn-primary.box-shadow(id="download-results-csv" data-hook="download-results-csv") Download Full Results as .csv + button.btn.btn-primary.box-shadow(id="job-presentation" data-hook="job-presentation") Publish Presentation div.saving-status(data-hook="job-action-start") From 4a804cd6720234509bc0074cabbdeffbc05e9542 Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Thu, 19 Aug 2021 13:07:38 -0400 Subject: [PATCH 071/186] Added 'Full' to the download results as csv text. --- client/job-view/templates/gillespyResultsEnsembleView.pug | 2 +- client/job-view/templates/gillespyResultsView.pug | 2 +- client/job-view/templates/parameterSweepResultsView.pug | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/client/job-view/templates/gillespyResultsEnsembleView.pug b/client/job-view/templates/gillespyResultsEnsembleView.pug index b668cb4f38..6d73ea86ea 100644 --- a/client/job-view/templates/gillespyResultsEnsembleView.pug +++ b/client/job-view/templates/gillespyResultsEnsembleView.pug @@ -215,7 +215,7 @@ div#workflow-results.card button.btn.btn-primary.box-shadow(id="convert-to-notebook" data-hook="convert-to-notebook") Convert to Notebook - button.btn.btn-primary.box-shadow(id="download-results-csv" data-hook="download-results-csv") Download Results as .csv + button.btn.btn-primary.box-shadow(id="download-results-csv" data-hook="download-results-csv") Download Full Results as .csv button.btn.btn-primary.box-shadow(id="job-presentation" data-hook="job-presentation") Publish diff --git a/client/job-view/templates/gillespyResultsView.pug b/client/job-view/templates/gillespyResultsView.pug index 7022ba5373..9ef834c1e8 100644 --- a/client/job-view/templates/gillespyResultsView.pug +++ b/client/job-view/templates/gillespyResultsView.pug @@ -75,7 +75,7 @@ div#workflow-results.card button.btn.btn-primary.box-shadow(id="convert-to-notebook" data-hook="convert-to-notebook") Convert to Notebook - button.btn.btn-primary.box-shadow(id="download-results-csv" data-hook="download-results-csv") Download Results as .csv + button.btn.btn-primary.box-shadow(id="download-results-csv" data-hook="download-results-csv") Download Full Results as .csv button.btn.btn-primary.box-shadow(id="job-presentation" data-hook="job-presentation") Publish diff --git a/client/job-view/templates/parameterSweepResultsView.pug b/client/job-view/templates/parameterSweepResultsView.pug index 66ac48c34a..6b6ee7c708 100644 --- a/client/job-view/templates/parameterSweepResultsView.pug +++ b/client/job-view/templates/parameterSweepResultsView.pug @@ -181,7 +181,7 @@ div#workflow-results.card button.btn.btn-primary.box-shadow(id="convert-to-notebook" data-hook="convert-to-notebook") Convert to Notebook - button.btn.btn-primary.box-shadow(id="download-results-csv" data-hook="download-results-csv") Download Results as .csv + button.btn.btn-primary.box-shadow(id="download-results-csv" data-hook="download-results-csv") Download Full Results as .csv button.btn.btn-primary.box-shadow(id="job-presentation" data-hook="job-presentation") Publish From 87de670099f5f316cd662115add5de0f42478d1a Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Thu, 19 Aug 2021 13:11:40 -0400 Subject: [PATCH 072/186] Added 'Plot Results' to download as csv function for plots. --- client/job-view/templates/gillespyResultsEnsembleView.pug | 8 ++++---- client/job-view/templates/gillespyResultsView.pug | 2 +- client/job-view/templates/parameterScanResultsView.pug | 4 ++-- client/job-view/templates/parameterSweepResultsView.pug | 4 ++-- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/client/job-view/templates/gillespyResultsEnsembleView.pug b/client/job-view/templates/gillespyResultsEnsembleView.pug index 6d73ea86ea..c16b762143 100644 --- a/client/job-view/templates/gillespyResultsEnsembleView.pug +++ b/client/job-view/templates/gillespyResultsEnsembleView.pug @@ -69,7 +69,7 @@ div#workflow-results.card data-target="download-plot-csv" data-type="stddevran" disabled - ) Download as .csv + ) Download Plot Results as .csv div.card @@ -117,7 +117,7 @@ div#workflow-results.card data-target="download-plot-csv" data-type="trajectories" disabled - ) Download as .csv + ) Download Plot Results as .csv div.card @@ -163,7 +163,7 @@ div#workflow-results.card data-target="download-plot-csv" data-type="stddev" disabled - ) Download as .csv + ) Download Plot Results as .csv div.card @@ -209,7 +209,7 @@ div#workflow-results.card data-target="download-plot-csv" data-type="avg" disabled - ) Download as .csv + ) Download Plot Results as .csv div diff --git a/client/job-view/templates/gillespyResultsView.pug b/client/job-view/templates/gillespyResultsView.pug index 9ef834c1e8..37e8c6a7c2 100644 --- a/client/job-view/templates/gillespyResultsView.pug +++ b/client/job-view/templates/gillespyResultsView.pug @@ -69,7 +69,7 @@ div#workflow-results.card data-target="download-plot-csv" data-type="trajectories" disabled - ) Download as .csv + ) Download Plot Results as .csv div diff --git a/client/job-view/templates/parameterScanResultsView.pug b/client/job-view/templates/parameterScanResultsView.pug index 19cff01799..a22b3ef745 100644 --- a/client/job-view/templates/parameterScanResultsView.pug +++ b/client/job-view/templates/parameterScanResultsView.pug @@ -89,7 +89,7 @@ div#workflow-results.card data-target="download-plot-csv" data-type="ts-psweep" disabled - ) Download as .csv + ) Download Plot Results as .csv div.col-md-3 @@ -189,7 +189,7 @@ div#workflow-results.card data-target="download-plot-csv" data-type="psweep" disabled - ) Download as .csv + ) Download Plot Results as .csv div.col-md-3 diff --git a/client/job-view/templates/parameterSweepResultsView.pug b/client/job-view/templates/parameterSweepResultsView.pug index 6b6ee7c708..a9d47efb6a 100644 --- a/client/job-view/templates/parameterSweepResultsView.pug +++ b/client/job-view/templates/parameterSweepResultsView.pug @@ -89,7 +89,7 @@ div#workflow-results.card data-target="download-plot-csv" data-type="ts-psweep" disabled - ) Download as .csv + ) Download Plot Results as .csv div.col-md-3 @@ -175,7 +175,7 @@ div#workflow-results.card data-target="download-plot-csv" data-type="psweep" disabled - ) Download as .csv + ) Download Plot Results as .csv div From 017c0eea52d63bbca449763f00fa4e71c24bb91e Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Thu, 19 Aug 2021 13:56:00 -0400 Subject: [PATCH 073/186] Fixed issue with data var in download csv zip handler. --- stochss/handlers/workflows.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/stochss/handlers/workflows.py b/stochss/handlers/workflows.py index c138a2912d..b5dc6d15d3 100644 --- a/stochss/handlers/workflows.py +++ b/stochss/handlers/workflows.py @@ -456,7 +456,9 @@ async def get(self): self.set_header('Content-Type', 'application/zip') path = self.get_query_argument(name="path") csv_type = self.get_query_argument(name="type") - data = json.loads(self.get_query_argument(name="data", default=None)) + data = self.get_query_argument(name="data", default=None) + if data is not None: + data = json.loads(data) try: job = StochSSJob(path=path) name = job.get_name() From c302a3207314214f5375bd3f7a0af102abe074f1 Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Thu, 19 Aug 2021 14:12:08 -0400 Subject: [PATCH 074/186] Added blocks to handle use case of no fixed parameters for full psweep csv. Added block to handle use case of 1D psweep for full 2D psweep csv. --- stochss/handlers/util/stochss_job.py | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/stochss/handlers/util/stochss_job.py b/stochss/handlers/util/stochss_job.py index 80430b92cf..e7ce7d68a0 100644 --- a/stochss/handlers/util/stochss_job.py +++ b/stochss/handlers/util/stochss_job.py @@ -113,16 +113,20 @@ def __create_settings(self): def __get_1d_fixed_list(cls, results): keys = [key.split(',') for key in results.keys()] p_names = [param.split(':')[0] for param in keys[0]] + if len(p_names) < 2: + return [{}] _f_keys = [] for name in p_names: for key in keys: f_key = list(filter(lambda key_el, key=key, name=name: name not in key_el, key)) _f_keys.append(",".join(f_key)) f_keys = sorted(list(set(_f_keys))) + print(f_keys) fixed_list = [] for key in f_keys: data = [_data.split(':') for _data in key.split(',')] - fixed = {_data[0]: _data[1] for _data in data} + print(data) + fixed = {_fixed[0]: _fixed[1] for _fixed in data} fixed_list.append(fixed) return fixed_list @@ -130,7 +134,12 @@ def __get_1d_fixed_list(cls, results): @classmethod def __get_2d_fixed_list(cls, results): keys = [key.split(',') for key in results.keys()] - p_names = list(combinations([param.split(':')[0] for param in keys[0]], 2)) + p_names = [param.split(':')[0] for param in keys[0]] + if len(p_names) < 2: + return None + elif len(p_names) < 3: + return [{}] + p_names = list(combinations(p_names, 2)) _f_keys = [] for names in p_names: for key in keys: @@ -223,10 +232,12 @@ def __get_full_1dpsweep_csv(self, b_path, results, get_name, name): def __get_full_2dpsweep_csv(self, b_path, results, get_name, name): + fixed_list = self.__get_2d_fixed_list(results) + if fixed_list is None: + return settings = self.load_settings() td_path = os.path.join(b_path, "2D_Resutls") os.mkdir(td_path) - fixed_list = self.__get_2d_fixed_list(results) for i, fixed in enumerate(fixed_list): params = list(filter(lambda param, fixed=fixed: param['name'] not in fixed.keys(), settings['parameterSweepSettings']['parameters'])) From d3b03a81aa4ce71cffc45c955ec655173ceeeea6 Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Thu, 19 Aug 2021 16:17:06 -0400 Subject: [PATCH 075/186] Added checks to only write the parameters csv files. --- stochss/handlers/util/stochss_job.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/stochss/handlers/util/stochss_job.py b/stochss/handlers/util/stochss_job.py index e7ce7d68a0..0009162ff9 100644 --- a/stochss/handlers/util/stochss_job.py +++ b/stochss/handlers/util/stochss_job.py @@ -137,7 +137,7 @@ def __get_2d_fixed_list(cls, results): p_names = [param.split(':')[0] for param in keys[0]] if len(p_names) < 2: return None - elif len(p_names) < 3: + if len(p_names) < 3: return [{}] p_names = list(combinations(p_names, 2)) _f_keys = [] @@ -228,7 +228,8 @@ def __get_full_1dpsweep_csv(self, b_path, results, get_name, name): ParameterSweep1D.to_csv( param=param, kwargs=kwargs, path=od_path, nametag=get_name(name, i) ) - self.__write_parameters_csv(path=od_path, name=get_name(name, i), data_keys=fixed) + if fixed: + self.__write_parameters_csv(path=od_path, name=get_name(name, i), data_keys=fixed) def __get_full_2dpsweep_csv(self, b_path, results, get_name, name): @@ -246,7 +247,8 @@ def __get_full_2dpsweep_csv(self, b_path, results, get_name, name): ParameterSweep2D.to_csv( params=params, kwargs=kwargs, path=td_path, nametag=get_name(name, i) ) - self.__write_parameters_csv(path=td_path, name=get_name(name, i), data_keys=fixed) + if fixed: + self.__write_parameters_csv(path=td_path, name=get_name(name, i), data_keys=fixed) def __get_full_timeseries_csv(self, b_path, results, get_name, name): From 67bcf7fec34f8b9b95136facc835608c55239593 Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Thu, 19 Aug 2021 17:52:17 -0400 Subject: [PATCH 076/186] Fixed issue with fixed dictionary. --- stochss/handlers/util/stochss_job.py | 33 +++++++++++----------------- 1 file changed, 13 insertions(+), 20 deletions(-) diff --git a/stochss/handlers/util/stochss_job.py b/stochss/handlers/util/stochss_job.py index 0009162ff9..4d1405689e 100644 --- a/stochss/handlers/util/stochss_job.py +++ b/stochss/handlers/util/stochss_job.py @@ -120,14 +120,7 @@ def __get_1d_fixed_list(cls, results): for key in keys: f_key = list(filter(lambda key_el, key=key, name=name: name not in key_el, key)) _f_keys.append(",".join(f_key)) - f_keys = sorted(list(set(_f_keys))) - print(f_keys) - fixed_list = [] - for key in f_keys: - data = [_data.split(':') for _data in key.split(',')] - print(data) - fixed = {_fixed[0]: _fixed[1] for _fixed in data} - fixed_list.append(fixed) + fixed_list = [key.split(',') for key in sorted(list(set(_f_keys)))] return fixed_list @@ -146,12 +139,7 @@ def __get_2d_fixed_list(cls, results): test = lambda key_el, names=names: names[0] not in key_el and names[1] not in key_el f_key = list(filter(lambda key_el, test=test: test(key_el), key)) _f_keys.append(",".join(f_key)) - f_keys = sorted(list(set(_f_keys))) - fixed_list = [] - for key in f_keys: - data = [_data.split(':') for _data in key.split(',')] - fixed = {_data[0]: _data[1] for _data in data} - fixed_list.append(fixed) + fixed_list = [key.split(',') for key in sorted(list(set(_f_keys)))] return fixed_list @@ -221,15 +209,19 @@ def __get_full_1dpsweep_csv(self, b_path, results, get_name, name): os.mkdir(od_path) fixed_list = self.__get_1d_fixed_list(results) for i, fixed in enumerate(fixed_list): - param = list(filter(lambda param, fixed=fixed: param['name'] not in fixed.keys(), + d_keys = dict([key.split(":") for key in fixed]) + param = list(filter(lambda param, d_keys=d_keys: param['name'] not in d_keys.keys(), settings['parameterSweepSettings']['parameters']))[0] - kwargs = {'results': self.__get_filtered_1d_results(fixed)} - kwargs["species"] = list(kwargs['results'][0][0].model.listOfSpecies.keys()) + try: + kwargs = {'results': self.__get_filtered_1d_results(fixed)} + kwargs["species"] = list(kwargs['results'][0][0].model.listOfSpecies.keys()) + except IndexError: + raise Exception(f"{len(kwargs['results'])}, {fixed}") ParameterSweep1D.to_csv( param=param, kwargs=kwargs, path=od_path, nametag=get_name(name, i) ) if fixed: - self.__write_parameters_csv(path=od_path, name=get_name(name, i), data_keys=fixed) + self.__write_parameters_csv(path=od_path, name=get_name(name, i), data_keys=d_keys) def __get_full_2dpsweep_csv(self, b_path, results, get_name, name): @@ -240,7 +232,8 @@ def __get_full_2dpsweep_csv(self, b_path, results, get_name, name): td_path = os.path.join(b_path, "2D_Resutls") os.mkdir(td_path) for i, fixed in enumerate(fixed_list): - params = list(filter(lambda param, fixed=fixed: param['name'] not in fixed.keys(), + d_keys = dict([key.split(":") for key in fixed]) + params = list(filter(lambda param, d_keys=d_keys: param['name'] not in d_keys.keys(), settings['parameterSweepSettings']['parameters'])) kwargs = {"results": self.__get_filtered_2d_results(fixed, params[0])} kwargs["species"] = list(kwargs['results'][0][0][0].model.listOfSpecies.keys()) @@ -248,7 +241,7 @@ def __get_full_2dpsweep_csv(self, b_path, results, get_name, name): params=params, kwargs=kwargs, path=td_path, nametag=get_name(name, i) ) if fixed: - self.__write_parameters_csv(path=td_path, name=get_name(name, i), data_keys=fixed) + self.__write_parameters_csv(path=td_path, name=get_name(name, i), data_keys=d_keys) def __get_full_timeseries_csv(self, b_path, results, get_name, name): From a7e37bd3dda0e6f632c3c9aa0611ec2eedff571c Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Fri, 20 Aug 2021 08:55:26 -0400 Subject: [PATCH 077/186] Removed unused function and debug code. --- stochss/handlers/util/stochss_job.py | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/stochss/handlers/util/stochss_job.py b/stochss/handlers/util/stochss_job.py index 4d1405689e..f6fbb00ba2 100644 --- a/stochss/handlers/util/stochss_job.py +++ b/stochss/handlers/util/stochss_job.py @@ -212,11 +212,8 @@ def __get_full_1dpsweep_csv(self, b_path, results, get_name, name): d_keys = dict([key.split(":") for key in fixed]) param = list(filter(lambda param, d_keys=d_keys: param['name'] not in d_keys.keys(), settings['parameterSweepSettings']['parameters']))[0] - try: - kwargs = {'results': self.__get_filtered_1d_results(fixed)} - kwargs["species"] = list(kwargs['results'][0][0].model.listOfSpecies.keys()) - except IndexError: - raise Exception(f"{len(kwargs['results'])}, {fixed}") + kwargs = {'results': self.__get_filtered_1d_results(fixed)} + kwargs["species"] = list(kwargs['results'][0][0].model.listOfSpecies.keys()) ParameterSweep1D.to_csv( param=param, kwargs=kwargs, path=od_path, nametag=get_name(name, i) ) @@ -315,15 +312,6 @@ def __get_settings_path(self, full=False): return os.path.join(self.get_path(full=full), "settings.json") - def __is_csv_dir(self, file): - if "results_csv" not in file: - return False - path = os.path.join(self.__get_results_path(), file) - if not os.path.isdir(path): - return False - return True - - @classmethod def __is_result_valid(cls, f_keys, key): for f_key in f_keys: From 0906ef21853bd19c72281b872ed37024b8d26c95 Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Fri, 20 Aug 2021 08:56:43 -0400 Subject: [PATCH 078/186] Added functions for full csv download to job presentations and updated the existing csv functions, --- jupyterhub/job_presentation.py | 169 ++++++++++++++++++++++++++++----- 1 file changed, 143 insertions(+), 26 deletions(-) diff --git a/jupyterhub/job_presentation.py b/jupyterhub/job_presentation.py index 43c5a20613..0fcaee064b 100644 --- a/jupyterhub/job_presentation.py +++ b/jupyterhub/job_presentation.py @@ -25,6 +25,7 @@ import tempfile import traceback +from itertools import combinations from pathlib import Path import numpy @@ -272,6 +273,48 @@ def __init__(self, path): super().__init__(path=path) + @classmethod + def __get_1d_fixed_list(cls, results): + keys = [key.split(',') for key in results.keys()] + p_names = [param.split(':')[0] for param in keys[0]] + if len(p_names) < 2: + return [{}] + _f_keys = [] + for name in p_names: + for key in keys: + f_key = list(filter(lambda key_el, key=key, name=name: name not in key_el, key)) + _f_keys.append(",".join(f_key)) + fixed_list = [key.split(',') for key in sorted(list(set(_f_keys)))] + return fixed_list + + + @classmethod + def __get_2d_fixed_list(cls, results): + keys = [key.split(',') for key in results.keys()] + p_names = [param.split(':')[0] for param in keys[0]] + if len(p_names) < 2: + return None + if len(p_names) < 3: + return [{}] + p_names = list(combinations(p_names, 2)) + _f_keys = [] + for names in p_names: + for key in keys: + test = lambda key_el, names=names: names[0] not in key_el and names[1] not in key_el + f_key = list(filter(lambda key_el, test=test: test(key_el), key)) + _f_keys.append(",".join(f_key)) + fixed_list = [key.split(',') for key in sorted(list(set(_f_keys)))] + return fixed_list + + + @classmethod + def __get_csvzip(cls, dirname, name): + shutil.make_archive(os.path.join(dirname, name), "zip", dirname, name) + path = os.path.join(dirname, f"{name}.zip") + with open(path, "rb") as zip_file: + return zip_file.read() + + def __get_filtered_1d_results(self, f_keys): results = self.__get_pickled_results() f_results = [] @@ -303,6 +346,54 @@ def __get_filtered_ensemble_results(self, data_keys): return result + def __get_full_1dpsweep_csv(self, b_path, results, get_name, name): + settings = self.load()['settings'] + od_path = os.path.join(b_path, "1D_Resutls") + os.mkdir(od_path) + fixed_list = self.__get_1d_fixed_list(results) + for i, fixed in enumerate(fixed_list): + d_keys = dict([key.split(":") for key in fixed]) + param = list(filter(lambda param, d_keys=d_keys: param['name'] not in d_keys.keys(), + settings['parameterSweepSettings']['parameters']))[0] + kwargs = {'results': self.__get_filtered_1d_results(fixed)} + kwargs["species"] = list(kwargs['results'][0][0].model.listOfSpecies.keys()) + ParameterSweep1D.to_csv( + param=param, kwargs=kwargs, path=od_path, nametag=get_name(name, i) + ) + if fixed: + self.__write_parameters_csv(path=od_path, name=get_name(name, i), data_keys=d_keys) + + + def __get_full_2dpsweep_csv(self, b_path, results, get_name, name): + fixed_list = self.__get_2d_fixed_list(results) + if fixed_list is None: + return + settings = self.load()['settings'] + td_path = os.path.join(b_path, "2D_Resutls") + os.mkdir(td_path) + for i, fixed in enumerate(fixed_list): + d_keys = dict([key.split(":") for key in fixed]) + params = list(filter(lambda param, d_keys=d_keys: param['name'] not in d_keys.keys(), + settings['parameterSweepSettings']['parameters'])) + kwargs = {"results": self.__get_filtered_2d_results(fixed, params[0])} + kwargs["species"] = list(kwargs['results'][0][0][0].model.listOfSpecies.keys()) + ParameterSweep2D.to_csv( + params=params, kwargs=kwargs, path=td_path, nametag=get_name(name, i) + ) + if fixed: + self.__write_parameters_csv(path=td_path, name=get_name(name, i), data_keys=d_keys) + + + def __get_full_timeseries_csv(self, b_path, results, get_name, name): + ts_path = os.path.join(b_path, "Time_Series") + os.makedirs(ts_path) + for i, (key, result) in enumerate(results.items()): + data = [_data.split(':') for _data in key.split(',')] + data_keys = {_data[0]: _data[1] for _data in data} + result.to_csv(path=ts_path, nametag=get_name(name, i), stamp="") + self.__write_parameters_csv(path=ts_path, name=get_name(name, i), data_keys=data_keys) + + @classmethod def __get_fixed_keys_and_dims(cls, settings, fixed): p_len = len(settings['parameterSweepSettings']['parameters']) @@ -333,15 +424,13 @@ def __is_result_valid(cls, f_keys, key): return True - def load(self): - ''' - Loads a job presentation from cache - - Attributes - ---------- - ''' - with open(os.path.join(self.path, "job.json"), "r") as job_file: - return json.load(job_file) + @classmethod + def __write_parameters_csv(cls, path, name, data_keys): + csv_path = os.path.join(path, name, "parameters.csv") + with open(csv_path, "w") as csv_file: + csv_writer = csv.writer(csv_file) + csv_writer.writerow(list(data_keys.keys())) + csv_writer.writerow(list(data_keys.values())) def get_csvzip_from_results(self, data_keys, proc_key, name): @@ -358,23 +447,20 @@ def get_csvzip_from_results(self, data_keys, proc_key, name): Name of the csv directory ''' try: + self.log("info", "Getting job results...") result = self.__get_filtered_ensemble_results(data_keys) + self.log("info", "Processing results...") if proc_key == "stddev": result = result.stddev_ensemble() elif proc_key == "avg": result = result.average_ensemble() + self.log("info", "Generating CSV files...") tmp_dir = tempfile.TemporaryDirectory() result.to_csv(path=tmp_dir.name, nametag=name, stamp="") if data_keys: - csv_path = os.path.join(tmp_dir.name, name, "parameters.csv") - with open(csv_path, "w") as csv_file: - csv_writer = csv.writer(csv_file) - csv_writer.writerow(list(data_keys.keys())) - csv_writer.writerow(list(data_keys.values())) - shutil.make_archive(os.path.join(tmp_dir.name, name), "zip", tmp_dir.name, name) - path = os.path.join(tmp_dir.name, f"{name}.zip") - with open(path, "rb") as zip_file: - return zip_file.read() + self.__write_parameters_csv(path=tmp_dir.name, name=name, data_keys=data_keys) + self.log("info", "Generating zip archive...") + return self.__get_csvzip(dirname=tmp_dir.name, name=name) except FileNotFoundError as err: message = f"Could not find the results pickle file: {str(err)}" raise StochSSFileNotFoundError(message, traceback.format_exc()) from err @@ -383,6 +469,29 @@ def get_csvzip_from_results(self, data_keys, proc_key, name): raise PlotNotAvailableError(message, traceback.format_exc()) from err + def get_full_csvzip_from_results(self, name): + ''' + Get the csv files for the full set of results data + + Attributes + ---------- + name : str + Name of the csv directory. + ''' + results = self.__get_pickled_results() + tmp_dir = tempfile.TemporaryDirectory() + if not isinstance(results, dict): + results.to_csv(path=tmp_dir.name, nametag=name, stamp="") + return self.__get_csvzip(dirname=tmp_dir.name, name=name) + def get_name(b_name, tag): + return f"{b_name}_{tag}" + b_path = os.path.join(tmp_dir.name, get_name(name, "full")) + self.__get_full_timeseries_csv(b_path, results, get_name, name) + self.__get_full_1dpsweep_csv(b_path, results, get_name, name) + self.__get_full_2dpsweep_csv(b_path, results, get_name, name) + return self.__get_csvzip(dirname=tmp_dir.name, name=get_name(name, "full")) + + def get_plot_from_results(self, data_keys, plt_key, add_config=False): ''' Get the plotly figure for the results of a job @@ -430,6 +539,7 @@ def get_psweep_csvzip_from_results(self, fixed, name): ''' settings = self.load()['settings'] try: + self.log("info", "Loading job results...") dims, f_keys = self.__get_fixed_keys_and_dims(settings, fixed) params = list(filter(lambda param: param['name'] not in fixed.keys(), settings['parameterSweepSettings']['parameters'])) @@ -437,24 +547,20 @@ def get_psweep_csvzip_from_results(self, fixed, name): if dims == 1: kwargs = {"results": self.__get_filtered_1d_results(f_keys)} kwargs["species"] = list(kwargs['results'][0][0].model.listOfSpecies.keys()) + self.log("info", "Generating CSV files...") ParameterSweep1D.to_csv( param=params[0], kwargs=kwargs, path=tmp_dir.name, nametag=name ) else: kwargs = {"results": self.__get_filtered_2d_results(f_keys, params[0])} kwargs["species"] = list(kwargs['results'][0][0][0].model.listOfSpecies.keys()) + self.log("info", "Generating CSV files...") ParameterSweep2D.to_csv( params=params, kwargs=kwargs, path=tmp_dir.name, nametag=name ) if fixed: - csv_path = os.path.join(tmp_dir.name, name, "parameters.csv") - with open(csv_path, "w") as csv_file: - csv_writer = csv.writer(csv_file) - csv_writer.writerow(list(fixed.keys())) - csv_writer.writerow(list(fixed.values())) - shutil.make_archive(os.path.join(tmp_dir.name, name), "zip", tmp_dir.name, name) - with open(os.path.join(tmp_dir.name, f"{name}.zip"), "rb") as zip_file: - return zip_file.read() + self.__write_parameters_csv(path=tmp_dir.name, name=name, data_keys=fixed) + return self.__get_csvzip(dirname=tmp_dir.name, name=name) except FileNotFoundError as err: message = f"Could not find the results pickle file: {str(err)}" raise StochSSFileNotFoundError(message, traceback.format_exc()) from err @@ -498,6 +604,17 @@ def get_psweep_plot_from_results(self, fixed, kwargs, add_config=False): raise PlotNotAvailableError(message, traceback.format_exc()) from err + def load(self): + ''' + Loads a job presentation from cache + + Attributes + ---------- + ''' + with open(os.path.join(self.path, "job.json"), "r") as job_file: + return json.load(job_file) + + def update_fig_layout(self, fig=None, plt_data=None): ''' Update the figure layout. From 8056dfb9ec707c78ce7c07c4b5f29ad200250fab Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Fri, 20 Aug 2021 09:08:47 -0400 Subject: [PATCH 079/186] Added info logs for user log support. --- stochss/handlers/util/stochss_job.py | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/stochss/handlers/util/stochss_job.py b/stochss/handlers/util/stochss_job.py index f6fbb00ba2..4564999776 100644 --- a/stochss/handlers/util/stochss_job.py +++ b/stochss/handlers/util/stochss_job.py @@ -209,16 +209,18 @@ def __get_full_1dpsweep_csv(self, b_path, results, get_name, name): os.mkdir(od_path) fixed_list = self.__get_1d_fixed_list(results) for i, fixed in enumerate(fixed_list): + nametag = get_name(name, i) + self.log("info", f"\tGenerating CSV files for {nametag}...") d_keys = dict([key.split(":") for key in fixed]) param = list(filter(lambda param, d_keys=d_keys: param['name'] not in d_keys.keys(), settings['parameterSweepSettings']['parameters']))[0] kwargs = {'results': self.__get_filtered_1d_results(fixed)} kwargs["species"] = list(kwargs['results'][0][0].model.listOfSpecies.keys()) ParameterSweep1D.to_csv( - param=param, kwargs=kwargs, path=od_path, nametag=get_name(name, i) + param=param, kwargs=kwargs, path=od_path, nametag=nametag ) if fixed: - self.__write_parameters_csv(path=od_path, name=get_name(name, i), data_keys=d_keys) + self.__write_parameters_csv(path=od_path, name=nametag, data_keys=d_keys) def __get_full_2dpsweep_csv(self, b_path, results, get_name, name): @@ -229,16 +231,18 @@ def __get_full_2dpsweep_csv(self, b_path, results, get_name, name): td_path = os.path.join(b_path, "2D_Resutls") os.mkdir(td_path) for i, fixed in enumerate(fixed_list): + nametag = get_name(name, i) + self.log("info", f"\tGenerating CSV files for {nametag}...") d_keys = dict([key.split(":") for key in fixed]) params = list(filter(lambda param, d_keys=d_keys: param['name'] not in d_keys.keys(), settings['parameterSweepSettings']['parameters'])) kwargs = {"results": self.__get_filtered_2d_results(fixed, params[0])} kwargs["species"] = list(kwargs['results'][0][0][0].model.listOfSpecies.keys()) ParameterSweep2D.to_csv( - params=params, kwargs=kwargs, path=td_path, nametag=get_name(name, i) + params=params, kwargs=kwargs, path=td_path, nametag=nametag ) if fixed: - self.__write_parameters_csv(path=td_path, name=get_name(name, i), data_keys=d_keys) + self.__write_parameters_csv(path=td_path, name=nametag, data_keys=d_keys) def __get_full_timeseries_csv(self, b_path, results, get_name, name): @@ -247,8 +251,10 @@ def __get_full_timeseries_csv(self, b_path, results, get_name, name): for i, (key, result) in enumerate(results.items()): data = [_data.split(':') for _data in key.split(',')] data_keys = {_data[0]: _data[1] for _data in data} - result.to_csv(path=ts_path, nametag=get_name(name, i), stamp="") - self.__write_parameters_csv(path=ts_path, name=get_name(name, i), data_keys=data_keys) + nametag = get_name(name, i) + self.log("info", f"\tGenerating CSV files for {nametag}...") + result.to_csv(path=ts_path, nametag=nametag, stamp="") + self.__write_parameters_csv(path=ts_path, name=nametag, data_keys=data_keys) @classmethod @@ -447,16 +453,22 @@ def get_full_csvzip_from_results(self, name): name : str Name of the csv directory. ''' + self.log("info", "Getting job results...") results = self.__get_pickled_results() tmp_dir = tempfile.TemporaryDirectory() if not isinstance(results, dict): + self.log("info", "Generating CSV files...") results.to_csv(path=tmp_dir.name, nametag=name, stamp="") + self.log("info", "Generating zip archive...") return self.__get_csvzip(dirname=tmp_dir.name, name=name) def get_name(b_name, tag): return f"{b_name}_{tag}" b_path = os.path.join(tmp_dir.name, get_name(name, "full")) + self.log("info", "Generating time series CSV files...") self.__get_full_timeseries_csv(b_path, results, get_name, name) + self.log("info", "Generating CSV files for 1D results...") self.__get_full_1dpsweep_csv(b_path, results, get_name, name) + self.log("info", "Generating CSV files for 2D results...") self.__get_full_2dpsweep_csv(b_path, results, get_name, name) return self.__get_csvzip(dirname=tmp_dir.name, name=get_name(name, "full")) From 1e542eebdce9bbd822dd0edae793c79f8e5b34b2 Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Fri, 20 Aug 2021 09:10:44 -0400 Subject: [PATCH 080/186] Enabled the full csv results api handler. --- jupyterhub/job_presentation.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jupyterhub/job_presentation.py b/jupyterhub/job_presentation.py index 0fcaee064b..b5ca5f6858 100644 --- a/jupyterhub/job_presentation.py +++ b/jupyterhub/job_presentation.py @@ -159,8 +159,8 @@ async def get(self): csv_data = job.get_csvzip_from_results(**data, name=name) elif csv_type == "psweep": csv_data = job.get_psweep_csvzip_from_results(fixed=data, name=name) - # else: - # csv_data = job.get_full_csvzip_from_results(name=name) + else: + csv_data = job.get_full_csvzip_from_results(name=name) self.write(csv_data) except StochSSAPIError as err: report_error(self, log, err) From 2ce379c46872bdaae0edd679a863ffd895b108fc Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Fri, 20 Aug 2021 13:09:09 -0400 Subject: [PATCH 081/186] Refactored the run preview script so that the model build process is covered by the try except block. --- stochss/handlers/util/scripts/run_preview.py | 79 +++++++++++++------- 1 file changed, 53 insertions(+), 26 deletions(-) diff --git a/stochss/handlers/util/scripts/run_preview.py b/stochss/handlers/util/scripts/run_preview.py index 97c07c6253..0c2de0c989 100755 --- a/stochss/handlers/util/scripts/run_preview.py +++ b/stochss/handlers/util/scripts/run_preview.py @@ -79,17 +79,60 @@ def get_parsed_args(): return parser.parse_args() -def run_preview(job): +def run_spatialpy_preview(args): ''' - Run the preview simulation + Run a spatialpy preview simulation. - wkfl : StochSSJob instance - The wkfl used for the preview simulation + Attributes + ---------- + args : argparse object + Command line args passed to the script + ''' + model = StochSSSpatialModel(path=args.path) + job = SpatialSimulation(path="", preview=True, target=args.target) + job.s_py_model = model.convert_to_spatialpy() + job.s_model = model.model + return job.run(preview=True) + + +def run_gillespy2_preview(args): + ''' + Run a gillespy2 preview simulation + + Attributes + ---------- + args : argparse object + Command line args passed to the script + ''' + log_stm, f_handler = setup_logger() + model = StochSSModel(path=args.path) + job = EnsembleSimulation(path="", preview=True) + job.g_model = model.convert_to_gillespy2() + plot = job.run(preview=True) + timeout = 'GillesPy2 simulation exceeded timeout.' in log_stm.getvalue() + log_stm.close() + f_handler.close() + return plot, timeout + + +def run_preview(args): + ''' + Run a preview simulation + + Attributes + ---------- + args : argparse object + Command line args passed to the script ''' + is_spatial = args.path.endswith(".smdl") response = {"timeout": False} try: - plot = job.run(preview=True) - response["results"] = plot + if is_spatial: + response['results'] = run_spatialpy_preview(args) + else: + fig, timeout = run_gillespy2_preview(args) + response['results'] = fig + response['timeout'] = timeout except ModelError as error: response['errors'] = f"{error}" except SimulationError as error: @@ -102,27 +145,11 @@ def run_preview(job): if __name__ == "__main__": + user_dir = StochSSModel.user_dir log.info("Initializing the preview simulation") - args = get_parsed_args() - is_spatial = args.path.endswith(".smdl") - if is_spatial: - model = StochSSSpatialModel(path=args.path) - wkfl = SpatialSimulation(path="", preview=True, target=args.target) - wkfl.s_py_model = model.convert_to_spatialpy() - wkfl.s_model = model.model - else: - log_stm, f_handler = setup_logger() - model = StochSSModel(path=args.path) - wkfl = EnsembleSimulation(path="", preview=True) - wkfl.g_model = model.convert_to_gillespy2() - resp = run_preview(wkfl) - if not is_spatial: - if 'GillesPy2 simulation exceeded timeout.' in log_stm.getvalue(): - resp['timeout'] = True - log_stm.close() - f_handler.close() - - outfile = os.path.join(model.user_dir, f".{args.outfile}.tmp") + cargs = get_parsed_args() + resp = run_preview(cargs) + outfile = os.path.join(user_dir, f".{cargs.outfile}.tmp") with open(outfile, "w") as file: json.dump(resp, file, cls=plotly.utils.PlotlyJSONEncoder, indent=4, sort_keys=True) From 927cc654cda6b82dcb7d7a38e3bbc624ae077f54 Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Fri, 20 Aug 2021 16:55:49 -0400 Subject: [PATCH 082/186] Combined the download buttons into a single download button with a dropdown. --- .../templates/gillespyResultsEnsembleView.pug | 176 ++++++++++-------- .../templates/gillespyResultsView.pug | 44 +++-- .../templates/parameterScanResultsView.pug | 86 +++++---- .../templates/parameterSweepResultsView.pug | 86 +++++---- client/job-view/views/job-results-view.js | 8 +- 5 files changed, 225 insertions(+), 175 deletions(-) diff --git a/client/job-view/templates/gillespyResultsEnsembleView.pug b/client/job-view/templates/gillespyResultsEnsembleView.pug index c16b762143..0c8d595de5 100644 --- a/client/job-view/templates/gillespyResultsEnsembleView.pug +++ b/client/job-view/templates/gillespyResultsEnsembleView.pug @@ -50,26 +50,32 @@ div#workflow-results.card button.btn.btn-primary.box-shadow(data-hook="stddevran-edit-plot" data-target="edit-plot" disabled) Edit Plot - button.btn.btn-primary.box-shadow( - data-hook="stddevran-download-png-custom" - data-target="download-png-custom" - data-type="stddevran" + button.btn.btn-primary.box-shadow.dropdown-toggle( + id="stddevran-download" + data-hook="stddevran-download" + data-toggle="dropdown", + aria-haspopup="true", + aria-expanded="false", + type="button" disabled - ) Download PNG - - button.btn.btn-primary.box-shadow( - data-hook="stddevran-download-json" - data-target="download-json" - data-type="stddevran" - disabled - ) Download JSON - - button.btn.btn-primary.box-shadow( - data-hook="stddevran-plot-csv" - data-target="download-plot-csv" - data-type="stddevran" - disabled - ) Download Plot Results as .csv + ) Download + + ul.dropdown-menu(aria-labelledby="#stddevran-download") + li.dropdown-item( + data-hook="stddevran-download-png-custom" + data-target="download-png-custom" + data-type="stddevran" + ) Plot as .png + li.dropdown-item( + data-hook="stddevran-download-json" + data-target="download-json" + data-type="stddevran" + ) Plot as .json + li.dropdown-item( + data-hook="stddevran-plot-csv" + data-target="download-plot-csv" + data-type="stddevran" + ) Plot Results as .csv div.card @@ -98,26 +104,32 @@ div#workflow-results.card button.btn.btn-primary.box-shadow(data-hook="multiple-plots", data-type="mltplplt" disabled) Multiple Plots - button.btn.btn-primary.box-shadow( - data-hook="trajectories-download-png-custom" - data-target="download-png-custom" - data-type="trajectories" - disabled - ) Download PNG - - button.btn.btn-primary.box-shadow( - data-hook="trajectories-download-json" - data-target="download-json" - data-type="trajectories" - disabled - ) Download JSON - - button.btn.btn-primary.box-shadow( - data-hook="trajectories-plot-csv" - data-target="download-plot-csv" - data-type="trajectories" + button.btn.btn-primary.box-shadow.dropdown-toggle( + id="trajectories-download" + data-hook="trajectories-download" + data-toggle="dropdown", + aria-haspopup="true", + aria-expanded="false", + type="button" disabled - ) Download Plot Results as .csv + ) Download + + ul.dropdown-menu(aria-labelledby="#trajectories-download") + li.dropdown-item( + data-hook="trajectories-download-png-custom" + data-target="download-png-custom" + data-type="trajectories" + ) Plot as .png + li.dropdown-item( + data-hook="trajectories-download-json" + data-target="download-json" + data-type="trajectories" + ) Plot as .json + li.dropdown-item( + data-hook="trajectories-plot-csv" + data-target="download-plot-csv" + data-type="trajectories" + ) Plot Results as .csv div.card @@ -144,26 +156,32 @@ div#workflow-results.card button.btn.btn-primary.box-shadow(data-hook="stddev-edit-plot" data-target="edit-plot" disabled) Edit Plot - button.btn.btn-primary.box-shadow( - data-hook="stddev-download-png-custom" - data-target="download-png-custom" - data-type="stddev" + button.btn.btn-primary.box-shadow.dropdown-toggle( + id="stddev-download" + data-hook="stddev-download" + data-toggle="dropdown", + aria-haspopup="true", + aria-expanded="false", + type="button" disabled - ) Download PNG - - button.btn.btn-primary.box-shadow( - data-hook="stddev-download-json" - data-target="download-json" - data-type="stddev" - disabled - ) Download JSON - - button.btn.btn-primary.box-shadow( - data-hook="stddev-plot-csv" - data-target="download-plot-csv" - data-type="stddev" - disabled - ) Download Plot Results as .csv + ) Download + + ul.dropdown-menu(aria-labelledby="#stddev-download") + li.dropdown-item( + data-hook="stddev-download-png-custom" + data-target="download-png-custom" + data-type="stddev" + ) Plot as .png + li.dropdown-item( + data-hook="stddev-download-json" + data-target="download-json" + data-type="stddev" + ) Plot as .json + li.dropdown-item( + data-hook="stddev-plot-csv" + data-target="download-plot-csv" + data-type="stddev" + ) Plot Results as .csv div.card @@ -190,26 +208,32 @@ div#workflow-results.card button.btn.btn-primary.box-shadow(data-hook="avg-edit-plot" data-target="edit-plot" disabled) Edit Plot - button.btn.btn-primary.box-shadow( - data-hook="avg-download-png-custom" - data-target="download-png-custom" - data-type="avg" - disabled - ) Download PNG - - button.btn.btn-primary.box-shadow( - data-hook="avg-download-json" - data-target="download-json" - data-type="avg" - disabled - ) Download JSON - - button.btn.btn-primary.box-shadow( - data-hook="avg-plot-csv" - data-target="download-plot-csv" - data-type="avg" + button.btn.btn-primary.box-shadow.dropdown-toggle( + id="avg-download" + data-hook="avg-download" + data-toggle="dropdown", + aria-haspopup="true", + aria-expanded="false", + type="button" disabled - ) Download Plot Results as .csv + ) Download + + ul.dropdown-menu(aria-labelledby="#avg-download") + li.dropdown-item( + data-hook="avg-download-png-custom" + data-target="download-png-custom" + data-type="avg" + ) Plot as .png + li.dropdown-item( + data-hook="avg-download-json" + data-target="download-json" + data-type="avg" + ) Plot as .json + li.dropdown-item( + data-hook="avg-plot-csv" + data-target="download-plot-csv" + data-type="avg" + ) Plot Results as .csv div diff --git a/client/job-view/templates/gillespyResultsView.pug b/client/job-view/templates/gillespyResultsView.pug index 37e8c6a7c2..3684e21ee1 100644 --- a/client/job-view/templates/gillespyResultsView.pug +++ b/client/job-view/templates/gillespyResultsView.pug @@ -50,26 +50,32 @@ div#workflow-results.card button.btn.btn-primary.box-shadow(data-hook="trajectories-edit-plot" data-target="edit-plot" disabled) Edit Plot - button.btn.btn-primary.box-shadow( - data-hook="trajectories-download-png-custom" - data-target="download-png-custom" - data-type="trajectories" + button.btn.btn-primary.box-shadow.dropdown-toggle( + id="trajectories-download" + data-hook="trajectories-download" + data-toggle="dropdown", + aria-haspopup="true", + aria-expanded="false", + type="button" disabled - ) Download PNG - - button.btn.btn-primary.box-shadow( - data-hook="trajectories-download-json" - data-target="download-json" - data-type="trajectories" - disabled - ) Download JSON - - button.btn.btn-primary.box-shadow( - data-hook="trajectories-plot-csv" - data-target="download-plot-csv" - data-type="trajectories" - disabled - ) Download Plot Results as .csv + ) Download + + ul.dropdown-menu(aria-labelledby="#trajectories-download") + li.dropdown-item( + data-hook="trajectories-download-png-custom" + data-target="download-png-custom" + data-type="trajectories" + ) Plot as .png + li.dropdown-item( + data-hook="trajectories-download-json" + data-target="download-json" + data-type="trajectories" + ) Plot as .json + li.dropdown-item( + data-hook="trajectories-plot-csv" + data-target="download-plot-csv" + data-type="trajectories" + ) Plot Results as .csv div diff --git a/client/job-view/templates/parameterScanResultsView.pug b/client/job-view/templates/parameterScanResultsView.pug index a22b3ef745..a3704a375f 100644 --- a/client/job-view/templates/parameterScanResultsView.pug +++ b/client/job-view/templates/parameterScanResultsView.pug @@ -70,26 +70,32 @@ div#workflow-results.card button.btn.btn-primary.box-shadow(data-hook="multiple-plots" data-type="ts-psweep-mp" style="display: none;" disabled) Multiple Plots - button.btn.btn-primary.box-shadow( - data-hook="ts-psweep-download-png-custom" - data-target="download-png-custom" - data-type="ts-psweep" + button.btn.btn-primary.box-shadow.dropdown-toggle( + id="ts-psweep-download" + data-hook="ts-psweep-download" + data-toggle="dropdown", + aria-haspopup="true", + aria-expanded="false", + type="button" disabled - ) Download PNG + ) Download - button.btn.btn-primary.box-shadow( - data-hook="ts-psweep-download-json" - data-target="download-json" - data-type="ts-psweep" - disabled - ) Download JSON - - button.btn.btn-primary.box-shadow( - data-hook="ts-psweep-plot-csv" - data-target="download-plot-csv" - data-type="ts-psweep" - disabled - ) Download Plot Results as .csv + ul.dropdown-menu(aria-labelledby="#ts-psweep-download") + li.dropdown-item( + data-hook="ts-psweep-download-png-custom" + data-target="download-png-custom" + data-type="ts-psweep" + ) Plot as .png + li.dropdown-item( + data-hook="ts-psweep-download-json" + data-target="download-json" + data-type="ts-psweep" + ) Plot as .json + li.dropdown-item( + data-hook="ts-psweep-plot-csv" + data-target="download-plot-csv" + data-type="ts-psweep" + ) Plot Results as .csv div.col-md-3 @@ -170,26 +176,32 @@ div#workflow-results.card button.btn.btn-primary.box-shadow(data-hook="psweep-edit-plot" data-target="edit-plot" disabled) Edit Plot - button.btn.btn-primary.box-shadow( - data-hook="psweep-download-png-custom" - data-target="download-png-custom" - data-type="psweep" - disabled - ) Download PNG - - button.btn.btn-primary.box-shadow( - data-hook="psweep-download-json" - data-target="download-json" - data-type="psweep" - disabled - ) Download JSON - - button.btn.btn-primary.box-shadow( - data-hook="psweep-plot-csv" - data-target="download-plot-csv" - data-type="psweep" + button.btn.btn-primary.box-shadow.dropdown-toggle( + id="psweep-download" + data-hook="psweep-download" + data-toggle="dropdown", + aria-haspopup="true", + aria-expanded="false", + type="button" disabled - ) Download Plot Results as .csv + ) Download + + ul.dropdown-menu(aria-labelledby="#psweep-download") + li.dropdown-item( + data-hook="psweep-download-png-custom" + data-target="download-png-custom" + data-type="psweep" + ) Plot as .png + li.dropdown-item( + data-hook="psweep-download-json" + data-target="download-json" + data-type="psweep" + ) Plot as .json + li.dropdown-item( + data-hook="psweep-plot-csv" + data-target="download-plot-csv" + data-type="psweep" + ) Plot Results as .csv div.col-md-3 diff --git a/client/job-view/templates/parameterSweepResultsView.pug b/client/job-view/templates/parameterSweepResultsView.pug index a9d47efb6a..b0a670bb09 100644 --- a/client/job-view/templates/parameterSweepResultsView.pug +++ b/client/job-view/templates/parameterSweepResultsView.pug @@ -70,26 +70,32 @@ div#workflow-results.card button.btn.btn-primary.box-shadow(data-hook="multiple-plots" data-type="ts-psweep-mp" style="display: none;" disabled) Multiple Plots - button.btn.btn-primary.box-shadow( - data-hook="ts-psweep-download-png-custom" - data-target="download-png-custom" - data-type="ts-psweep" - disabled - ) Download PNG - - button.btn.btn-primary.box-shadow( - data-hook="ts-psweep-download-json" - data-target="download-json" - data-type="ts-psweep" - disabled - ) Download JSON - - button.btn.btn-primary.box-shadow( - data-hook="ts-psweep-plot-csv" - data-target="download-plot-csv" - data-type="ts-psweep" + button.btn.btn-primary.box-shadow.dropdown-toggle( + id="ts-psweep-download" + data-hook="ts-psweep-download" + data-toggle="dropdown", + aria-haspopup="true", + aria-expanded="false", + type="button" disabled - ) Download Plot Results as .csv + ) Download + + ul.dropdown-menu(aria-labelledby="#ts-psweep-download") + li.dropdown-item( + data-hook="ts-psweep-download-png-custom" + data-target="download-png-custom" + data-type="ts-psweep" + ) Plot as .png + li.dropdown-item( + data-hook="ts-psweep-download-json" + data-target="download-json" + data-type="ts-psweep" + ) Plot as .json + li.dropdown-item( + data-hook="ts-psweep-plot-csv" + data-target="download-plot-csv" + data-type="ts-psweep" + ) Plot Results as .csv div.col-md-3 @@ -156,27 +162,33 @@ div#workflow-results.card button.btn.btn-primary.box-shadow(data-hook="psweep-edit-plot" data-target="edit-plot" disabled) Edit Plot - button.btn.btn-primary.box-shadow( - data-hook="psweep-download-png-custom" - data-target="download-png-custom" - data-type="psweep" + button.btn.btn-primary.box-shadow.dropdown-toggle( + id="psweep-download" + data-hook="psweep-download" + data-toggle="dropdown", + aria-haspopup="true", + aria-expanded="false", + type="button" disabled - ) Download PNG + ) Download - button.btn.btn-primary.box-shadow( - data-hook="psweep-download-json" - data-target="download-json" - data-type="psweep" - disabled - ) Download JSON + ul.dropdown-menu(aria-labelledby="#psweep-download") + li.dropdown-item( + data-hook="psweep-download-png-custom" + data-target="download-png-custom" + data-type="psweep" + ) Plot as .png + li.dropdown-item( + data-hook="psweep-download-json" + data-target="download-json" + data-type="psweep" + ) Plot as .json + li.dropdown-item( + data-hook="psweep-plot-csv" + data-target="download-plot-csv" + data-type="psweep" + ) Plot Results as .csv - button.btn.btn-primary.box-shadow( - data-hook="psweep-plot-csv" - data-target="download-plot-csv" - data-type="psweep" - disabled - ) Download Plot Results as .csv - div button.btn.btn-primary.box-shadow(id="convert-to-notebook" data-hook="convert-to-notebook") Convert to Notebook diff --git a/client/job-view/views/job-results-view.js b/client/job-view/views/job-results-view.js index 6d5b4e621f..631097c277 100644 --- a/client/job-view/views/job-results-view.js +++ b/client/job-view/views/job-results-view.js @@ -130,10 +130,8 @@ module.exports = View.extend({ Plotly.purge(el); $(this.queryByHook(type + "-plot")).empty(); if(type === "ts-psweep" || type === "psweep"){ + $(this.queryByHook(type + "-download")).prop("disabled", true); $(this.queryByHook(type + "-edit-plot")).prop("disabled", true); - $(this.queryByHook(type + "-download-png-custom")).prop("disabled", true); - $(this.queryByHook(type + "-download-json")).prop("disabled", true); - $(this.queryByHook(type + "-plot-csv")).prop("disabled", true); $(this.queryByHook("multiple-plots")).prop("disabled", true); } $(this.queryByHook(type + "-plot-spinner")).css("display", "block"); @@ -366,9 +364,7 @@ module.exports = View.extend({ Plotly.newPlot(el, figure); $(this.queryByHook(type + "-plot-spinner")).css("display", "none"); $(this.queryByHook(type + "-edit-plot")).prop("disabled", false); - $(this.queryByHook(type + "-download-png-custom")).prop("disabled", false); - $(this.queryByHook(type + "-download-json")).prop("disabled", false); - $(this.queryByHook(type + "-plot-csv")).prop("disabled", false); + $(this.queryByHook(type + "-download")).prop("disabled", false); if(type === "trajectories" || (this.tsPlotData && this.tsPlotData.type === "trajectories")) { $(this.queryByHook("multiple-plots")).prop("disabled", false); } From 0deebc7d706bbcc6782b6029704c08b872766a2e Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Mon, 23 Aug 2021 09:09:06 -0400 Subject: [PATCH 083/186] Added doc block to provide helpful information users for selecting the best algorithm for thier model. --- .../templates/simulationSettingsView.pug | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/client/settings-view/templates/simulationSettingsView.pug b/client/settings-view/templates/simulationSettingsView.pug index 8bfcc398d3..65825b1a89 100644 --- a/client/settings-view/templates/simulationSettingsView.pug +++ b/client/settings-view/templates/simulationSettingsView.pug @@ -18,12 +18,22 @@ div#simulation-settings.card button.btn.btn-outline-collapse(data-toggle="collapse" data-target="#" + this.model.elementID + "collapse-settings", data-hook="collapse-settings-view") - - div.collapse(class="show" id=this.model.elementID + "collapse-settings") + div.card-body - div.card-body.tab-content + p.mb-0 + | Consider the following when selecting an algorithm for your job. If you would like StochSS to select the algorithm for you select Choose for me. + + ul.mb-0 + li Algorithms ODE and Hybrid ODE/SSA work best to simulate models with a mode of Concentration. + li Algorithns SSA, Tau Leaping, and Hybrid ODE/SSA work best to simulate models with a mode of Population. + li Algorithm Hybrid ODE/SSA is required if the model contains advanced components. + + div.collapse.tab-content(class="show" id=this.model.elementID + "collapse-settings") div.tab-pane.active(id=this.model.elementID + "-edit-sim-settings" data-hook=this.model.elementID + "-edit-sim-settings") + hr + div.mx-1.head.align-items-baseline h4.inline Simulation Algorithm @@ -138,6 +148,8 @@ div#simulation-settings.card div.tab-pane(id=this.model.elementID + "-view-sim-settings" data-hook=this.model.elementID + "-view-sim-settings") + hr + div h6.inline.pr-2 Simulation Algorithm: From 4e9300a188de6fb3202772c36fe92c4a4bd1a80a Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Mon, 23 Aug 2021 10:58:49 -0400 Subject: [PATCH 084/186] Added function to get the list of presentations from the users presentation directory. --- stochss/handlers/util/stochss_folder.py | 27 +++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/stochss/handlers/util/stochss_folder.py b/stochss/handlers/util/stochss_folder.py index 948d795886..ae87f995a0 100644 --- a/stochss/handlers/util/stochss_folder.py +++ b/stochss/handlers/util/stochss_folder.py @@ -19,9 +19,11 @@ import os import json import shutil +import string import traceback import requests +from escapism import escape from .stochss_base import StochSSBase from .stochss_file import StochSSFile @@ -334,6 +336,31 @@ def get_jstree_node(self, is_root=False): raise StochSSFileNotFoundError(message, traceback.format_exc()) from err + @classmethod + def get_presentations(cls): + ''' + Get the list of presentations from the users presentation directory. + + Attributes + ---------- + ''' + path = os.path.join(cls.user_dir, ".presentations") + presentations = [] + if not os.path.isdir(path): + return presentations + for file in os.listdir(path): + file_path = os.path.join(path, file) + safe_chars = set(string.ascii_letters + string.digits) + hostname = escape(os.environ.get('JUPYTERHUB_USER'), safe=safe_chars) + query_str = f"?owner={hostname}&file={file}" + link = f"https://staging.stochss.org/stochss/present-model{query_str}" + presentation = { + "file": file, "link": link, "size": os.path.getsize(file_path) + } + presentations.append(presentation) + return presentations + + def get_project_list(self): ''' Get the list of project on the users disk From 2c170b52594baba1c8ec42872872baab45776b67 Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Mon, 23 Aug 2021 11:00:11 -0400 Subject: [PATCH 085/186] Added api route and handler function for getting the list of presentations for the user. --- stochss/handlers/__init__.py | 1 + stochss/handlers/file_browser.py | 26 ++++++++++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/stochss/handlers/__init__.py b/stochss/handlers/__init__.py index de707d4ddb..aaeee8fa2e 100644 --- a/stochss/handlers/__init__.py +++ b/stochss/handlers/__init__.py @@ -66,6 +66,7 @@ def get_page_handlers(route_start): (r"/stochss/api/file/json-data\/?", JsonFileAPIHandler), (r"/stochss/api/file/duplicate\/?", DuplicateModelHandler), (r"/stochss/api/file/unzip\/?", UnzipFileAPIHandler), + (r"/stochss/api/file/presentations\/?", PresentationListAPIHandler), (r"/stochss/api/notebook/presentation\/?", NotebookPresentationAPIHandler), (r"/stochss/api/directory/duplicate\/?", DuplicateDirectoryHandler), (r"/stochss/api/directory/create\/?", CreateDirectoryHandler), diff --git a/stochss/handlers/file_browser.py b/stochss/handlers/file_browser.py index 7f16724892..deed8d1927 100644 --- a/stochss/handlers/file_browser.py +++ b/stochss/handlers/file_browser.py @@ -764,3 +764,29 @@ async def get(self): except StochSSAPIError as err: report_error(self, log, err) self.finish() + + +class PresentationListAPIHandler(APIHandler): + ''' + ################################################################################################ + Handler for getting the users full list of presentations. + ################################################################################################ + ''' + @web.authenticated + async def get(self): + ''' + Get the list of presentations. + + Attributes + ---------- + ''' + self.set_header('Content-Type', 'application/json') + try: + log.info("Loading presentations...") + presentations = StochSSFolder.get_presentations() + log.debug(f"List of presentations: {presentations}") + log.info("Loading complete.") + self.write({"presentations": presentations}) + except StochSSAPIError as err: + report_error(self, log, err) + self.finish() From 8443fe6701f6b4be4db0668534537427416ffdde Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Mon, 23 Aug 2021 11:07:25 -0400 Subject: [PATCH 086/186] Added a presentation model for rendering the list of presentations. --- client/models/presentation.js | 43 +++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 client/models/presentation.js diff --git a/client/models/presentation.js b/client/models/presentation.js new file mode 100644 index 0000000000..d87cf911fc --- /dev/null +++ b/client/models/presentation.js @@ -0,0 +1,43 @@ +/* +StochSS is a platform for simulating biochemical systems +Copyright (C) 2019-2021 StochSS developers. + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +let State = require('ampersand-state'); + +module.exports = State.extend({ + session: { + file: 'string', + link: 'string', + size: 'number' + tag: 'string' + }, + initialize: function (attrs, options) { + State.prototype.initialize.apply(this, arguments); + this.formatSize(); + }, + formatSize: function () { + let tags = ["B", "KB", "MB", "GB", "TB"]; + var size = this.size; + var tag_index = 0; + while(size >= 10e3) { + size = size / 10e3; + tag_index += 1; + } + this.size = Number.parseFloat(size).toFixed(2); + this.tag = tags[tag_index] + } +}); \ No newline at end of file From d4da1d4daeea4ec203208c7af3d7f8b52968561e Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Mon, 23 Aug 2021 13:26:36 -0400 Subject: [PATCH 087/186] Moved the hostname block out of the for loop. --- stochss/handlers/util/stochss_folder.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/stochss/handlers/util/stochss_folder.py b/stochss/handlers/util/stochss_folder.py index ae87f995a0..3408198aa4 100644 --- a/stochss/handlers/util/stochss_folder.py +++ b/stochss/handlers/util/stochss_folder.py @@ -348,10 +348,10 @@ def get_presentations(cls): presentations = [] if not os.path.isdir(path): return presentations + safe_chars = set(string.ascii_letters + string.digits) + hostname = escape(os.environ.get('JUPYTERHUB_USER'), safe=safe_chars) for file in os.listdir(path): file_path = os.path.join(path, file) - safe_chars = set(string.ascii_letters + string.digits) - hostname = escape(os.environ.get('JUPYTERHUB_USER'), safe=safe_chars) query_str = f"?owner={hostname}&file={file}" link = f"https://staging.stochss.org/stochss/present-model{query_str}" presentation = { From 28cf326df18494498cda35a55150dd0f67bdb452 Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Mon, 23 Aug 2021 13:30:03 -0400 Subject: [PATCH 088/186] Fixed issue with model properties. Corrected the format error for file size. --- client/models/presentation.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/client/models/presentation.js b/client/models/presentation.js index d87cf911fc..17a07f2295 100644 --- a/client/models/presentation.js +++ b/client/models/presentation.js @@ -22,7 +22,7 @@ module.exports = State.extend({ session: { file: 'string', link: 'string', - size: 'number' + size: 'number', tag: 'string' }, initialize: function (attrs, options) { @@ -33,11 +33,11 @@ module.exports = State.extend({ let tags = ["B", "KB", "MB", "GB", "TB"]; var size = this.size; var tag_index = 0; - while(size >= 10e3) { - size = size / 10e3; + while(size >= 1e3) { + size = size / 1e3; tag_index += 1; } - this.size = Number.parseFloat(size).toFixed(2); + this.size = Number(Number.parseFloat(size).toFixed(2)); this.tag = tags[tag_index] } }); \ No newline at end of file From cbba7c7397e7e4a36ed600b17a2762920a0848da Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Mon, 23 Aug 2021 13:31:30 -0400 Subject: [PATCH 089/186] Added a view to visualize the presentation list. --- .../templates/includes/presentationView.pug | 26 ++++++++ client/views/presentation-view.js | 64 +++++++++++++++++++ 2 files changed, 90 insertions(+) create mode 100644 client/templates/includes/presentationView.pug create mode 100644 client/views/presentation-view.js diff --git a/client/templates/includes/presentationView.pug b/client/templates/includes/presentationView.pug new file mode 100644 index 0000000000..113ec43540 --- /dev/null +++ b/client/templates/includes/presentationView.pug @@ -0,0 +1,26 @@ +div.mx-1 + + if(this.model.collection.indexOf(this.model) !== 0) + hr + + div.row + + div.col-sm-6 + + a.pl-2(href=this.model.link)=this.model.file + + div.col-sm-2 + + button.btn.btn-outline-secondary.box-shadow(data-hook="copy-link") Copy Link + + div.col-sm-2 + + div=this.model.size + " " + this.model.tag + + div.col-sm-2 + + button.btn.btn-outline-secondary.box-shadow(data-hook="remove") X + + div.text-success(data-hook="copy-link-success" style="display: none;") Link copied to clipboard + + div.text-danger(data-hook="copy-link-failed" style="display: none;") diff --git a/client/views/presentation-view.js b/client/views/presentation-view.js new file mode 100644 index 0000000000..df5fb3dfe5 --- /dev/null +++ b/client/views/presentation-view.js @@ -0,0 +1,64 @@ +/* +StochSS is a platform for simulating biochemical systems +Copyright (C) 2019-2021 StochSS developers. + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +let $ = require('jquery'); +let path = require('path'); +//support files +let app = require('../app'); +//views +let View = require('ampersand-view'); +//templates +let template = require('../templates/includes/presentationView.pug'); + +module.exports = View.extend({ + template: template, + events: { + 'click [data-hook=copy-link]' : 'copyLink', + 'click [data-hook=remove]' : 'removePresentation' + }, + initialize: function (attrs, options) { + View.prototype.initialize.apply(this, arguments); + }, + render: function ( attrs, options) { + View.prototype.render.apply(this, arguments); + }, + copyLink: function (e) { + let onFulfilled = (value) => { + $(this.queryByHook("copy-link-success")).css("display", "inline-block"); + setTimeout(() => { + $(this.queryByHook("copy-link-success")).css("display", "none"); + }, 5000); + }; + let onReject = (reason) => { + let msg = $(this.queryByHook("copy-link-failed")); + msg.html(reason); + msg.css("display", "inline-block"); + }; + app.copyToClipboard(this.model.link, onFulfilled, onReject); + }, + removePresentation: function (e) { + let self = this; + let filePath = path.join('.presentations', this.model.file); + let endpoint = path.join(app.getApiPath(), "file/delete") + "?path=" + filePath; + app.getXHR(endpoint, { + success: function (err, response, body) { + self.model.collection.remove(self.model); + } + }); + } +}); \ No newline at end of file From 183a85f6d2d23c4c94c3e357eace7099b01a16d1 Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Mon, 23 Aug 2021 13:34:44 -0400 Subject: [PATCH 090/186] Added a presentation listing section to the users home page. --- client/pages/users-home.js | 22 ++++++++++++++++++++++ client/templates/pages/usersHome.pug | 20 +++++++++++++++++++- 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/client/pages/users-home.js b/client/pages/users-home.js index 2eb2e8ee93..06f4443c41 100644 --- a/client/pages/users-home.js +++ b/client/pages/users-home.js @@ -21,8 +21,13 @@ let path = require('path'); //support files let app = require('../app'); let modals = require('../modals'); +//collections +let Collection = require('ampersand-collection'); +//model +let Presentation = require('../models/presentation') //views let PageView = require('./base'); +let PresentationView = require('../views/presentation-view'); //templates let template = require('../templates/pages/usersHome.pug'); @@ -44,6 +49,14 @@ let usersHomePage = PageView.extend({ let queryString = "?path=" + urlParams.get("open") + "&action=open"; let endpoint = path.join(app.getBasePath(), 'stochss/loading-page') + queryString; window.location.href = endpoint; + }else{ + let self = this; + let endpoint = path.join(app.getApiPath(), "file/presentations") + app.getXHR(endpoint, { + success: function (err, response, body) { + self.renderPresentationView(body.presentations); + } + }); } }, render: function (attrs, options) { @@ -165,6 +178,15 @@ let usersHomePage = PageView.extend({ }, navToPage: function (endpoint) { window.location.href = endpoint + }, + renderPresentationView: function (presentations) { + let options = {model: Presentation}; + let presentCollection = new Collection(presentations, options); + this.renderCollection( + presentCollection, + PresentationView, + this.queryByHook("presentation-list") + ); } }); diff --git a/client/templates/pages/usersHome.pug b/client/templates/pages/usersHome.pug index 83046269d8..d325d9cada 100644 --- a/client/templates/pages/usersHome.pug +++ b/client/templates/pages/usersHome.pug @@ -52,4 +52,22 @@ selction.page button.btn.btn-primary.box-shadow.user-home-btn(id="quickstart-btn" data-hook="quickstart-btn") | Click here for a tutorial - div.col-md-2.col-lg-3.col-xl-4 \ No newline at end of file + div.col-md-2.col-lg-3.col-xl-4 + + div#presentations.card + + div.card-header.pb-0 + + h3 Presentations + + div.card-body + + div.mx-1.row.head.align-items-baseline + + div.col-sm-8: h6 File + + div.col-sm-2: h6 Size + + div.col-sm-2: h6 Remove + + div.mt-3(data-hook="presentation-list") \ No newline at end of file From f9671699a93bc47c0f2b408d9238c0fc0b8be678 Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Mon, 23 Aug 2021 13:45:56 -0400 Subject: [PATCH 091/186] Added function to hide the presentation manager when running StochSS locally. --- client/pages/users-home.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/client/pages/users-home.js b/client/pages/users-home.js index 06f4443c41..b416cb70e0 100644 --- a/client/pages/users-home.js +++ b/client/pages/users-home.js @@ -64,6 +64,9 @@ let usersHomePage = PageView.extend({ $(document).on('hide.bs.modal', '.modal', function (e) { e.target.remove() }); + if(app.getBasePath() === "/") { + $("#presentations").css("display", "none"); + } }, validateName(input) { var error = "" From 3a5bcc1fd33f1932103c1009fdf096d7ce8358b4 Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Mon, 23 Aug 2021 14:46:31 -0400 Subject: [PATCH 092/186] Added a nav link for presentations to the left nav bar. --- client/templates/body.pug | 3 +++ client/views/main.js | 4 +++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/client/templates/body.pug b/client/templates/body.pug index 1137b23683..7e6d7cd5da 100644 --- a/client/templates/body.pug +++ b/client/templates/body.pug @@ -42,6 +42,9 @@ body li.nav-item div: a.nav-link(href="stochss/project/browser", title="Explore your StochSS projects") Projects + li.nav-item(id="presentation-nav-link") + div: a.nav-link(href="stochss/home#presentations", title="Explore your StochSS presentations") Presentations + li.nav-item div: a.nav-link(href="stochss/files", title="Explore your StochSS files") Files diff --git a/client/views/main.js b/client/views/main.js index 7a90ddadfc..c77d0ce9ce 100644 --- a/client/views/main.js +++ b/client/views/main.js @@ -129,7 +129,9 @@ module.exports = View.extend({ App.currentPage = newView; } }); - + if(app.getBasePath() === "/") { + $("#presentation-nav-link").css("display", "none"); + } let self = this; let message = app.getBasePath() === "/" ? "Welcome to StochSS!" : "Welcome to StochSS Live!"; $("#user-logs").html(message) From 4def040b0ae2e83380925777f777f632ae04ed6a Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Mon, 23 Aug 2021 14:55:25 -0400 Subject: [PATCH 093/186] Set links for testing on staging. --- stochss/handlers/util/stochss_job.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/stochss/handlers/util/stochss_job.py b/stochss/handlers/util/stochss_job.py index 4564999776..7e612c1810 100644 --- a/stochss/handlers/util/stochss_job.py +++ b/stochss/handlers/util/stochss_job.py @@ -288,8 +288,8 @@ def __get_presentation_links(cls, file): safe_chars = set(string.ascii_letters + string.digits) hostname = escape(os.environ.get('JUPYTERHUB_USER'), safe=safe_chars) query_str = f"?owner={hostname}&file={file}" - present_link = f"https://live.stochss.org/stochss/present-job{query_str}" - dl_base = "https://live.stochss.org/stochss/job/download_presentation" + present_link = f"https://staging.stochss.org/stochss/present-job{query_str}" + dl_base = "https://staging.stochss.org/stochss/job/download_presentation" downloadlink = os.path.join(dl_base, hostname, file) open_link = f"https://open.stochss.org?open={downloadlink}" links = {"presentation": present_link, "download": downloadlink, "open": open_link} From 84ece5ba2e6b5e1863b62498a74410083d08111f Mon Sep 17 00:00:00 2001 From: seanebum Date: Mon, 23 Aug 2021 15:02:14 -0400 Subject: [PATCH 094/186] added custom favicon for stochss pages --- Dockerfile | 4 ++++ client/templates/head.pug | 1 + custom.js | 8 ++++++++ stochss/dist/favicon.ico | Bin 0 -> 803 bytes 4 files changed, 13 insertions(+) create mode 100644 custom.js create mode 100644 stochss/dist/favicon.ico diff --git a/Dockerfile b/Dockerfile index 2dbc9102d1..27d57c6e4d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -21,6 +21,10 @@ COPY --chown=jovyan:users public_models/ /home/jovyan/Examples COPY --chown=jovyan:users . /stochss +COPY --chown=jovyan:users /stochss/dist/favicon.ico $JUPYTER_CONFIG_DIR/custom/favicon.ico + +COPY --chown=jovyan:users custom.js $JUPYTER_CONFIG_DIR/custom/custom.js + COPY --chown=jovyan:users stochss-logo.png $JUPYTER_CONFIG_DIR/custom/logo.png COPY --chown=jovyan:users custom.css $JUPYTER_CONFIG_DIR/custom/custom.css diff --git a/client/templates/head.pug b/client/templates/head.pug index 1c592483bd..c86352c5c3 100644 --- a/client/templates/head.pug +++ b/client/templates/head.pug @@ -1,6 +1,7 @@ head meta(charset="utf-8") title StochSS: Stochastic Simulation Service + link(rel="shortcut icon" href="static/favicon.ico") meta(name="viewport", content="width=device-width, initial-scale=1.0") meta(name="description", content="") meta(name="author", content="") diff --git a/custom.js b/custom.js new file mode 100644 index 0000000000..afd7589cfc --- /dev/null +++ b/custom.js @@ -0,0 +1,8 @@ +requirejs([ + 'jquery', + 'base/js/utils', +], function($, utils + ){ + + utils.change_favicon("static/favicon.ico") +}); diff --git a/stochss/dist/favicon.ico b/stochss/dist/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..df7bf93e65c4a7ec037ed4e54df6077de9e6e97e GIT binary patch literal 803 zcmV+;1Kj+HP))2 zL^)9MfZbj?{r7ajgcIun-3%Z#cD?6$p8)JClj<{&cqN5}k;t}}Z{7@-6AFv32`vfn zJkR@dEPT9M#b0ki=hD9>&%CNjWik&{aZ6HJQM9ad!f}QYPBd+N9ai`O^aHRLKyvIkM92gGq4L-&<}X!#RmHN4kE@f2>zb~v+tmfxzWI7^S_aay z{7Pe`>S;OKcr Date: Mon, 23 Aug 2021 17:16:43 -0400 Subject: [PATCH 095/186] Added a validate command to check if the file being uploaded already exists. Refactored the upload from link function to remove the existing files if the overwrite flag was provided. --- stochss/handlers/file_browser.py | 10 +++- .../util/scripts/upload_remote_file.py | 3 +- stochss/handlers/util/stochss_folder.py | 54 +++++++++++++++++-- 3 files changed, 62 insertions(+), 5 deletions(-) diff --git a/stochss/handlers/file_browser.py b/stochss/handlers/file_browser.py index 7f16724892..9c53c6ddaf 100644 --- a/stochss/handlers/file_browser.py +++ b/stochss/handlers/file_browser.py @@ -684,10 +684,18 @@ async def get(self): cmd = self.get_query_argument(name="cmd", default=None) log.debug(f"The command for the upload script: {cmd}") script = '/stochss/stochss/handlers/util/scripts/upload_remote_file.py' - if cmd is None: + if cmd == "validate": + folder = StochSSFolder(path="") + resp = {'exists': folder.validate_upload_link(remote_path=path)} + log.debug(f"Response: {resp}") + self.write(resp) + elif cmd is None: + overwrite = self.get_query_argument(name='overwrite', default=False) outfile = f"{str(uuid.uuid4()).replace('-', '_')}.tmp" log.debug(f"Response file name: {outfile}") exec_cmd = [script, f'{path}', f'{outfile}'] # Script commands for read run_cmd + if overwrite: + exec_cmd.append('-o') log.debug(f"Exec command: {exec_cmd}") pipe = subprocess.Popen(exec_cmd) resp = {"responsePath": outfile} diff --git a/stochss/handlers/util/scripts/upload_remote_file.py b/stochss/handlers/util/scripts/upload_remote_file.py index e92073cdd7..12db185ba5 100755 --- a/stochss/handlers/util/scripts/upload_remote_file.py +++ b/stochss/handlers/util/scripts/upload_remote_file.py @@ -37,6 +37,7 @@ def get_parsed_args(): parser = argparse.ArgumentParser(description="Upload a file from an external link") parser.add_argument("file_path", help="The path to the external file.") parser.add_argument("outfile", help="The path to the response file") + parser.add_argument("-o", "--overwrite", action="store_true", help="Overwrite existing files.") return parser.parse_args() @@ -44,7 +45,7 @@ def get_parsed_args(): args = get_parsed_args() if args.file_path != 'None': folder = StochSSFolder(path="/") - resp = folder.upload_from_link(remote_path=args.file_path) + resp = folder.upload_from_link(remote_path=args.file_path, overwrite=args.overwrite) with open(args.outfile, "w") as fd: json.dump(resp, fd) open(args.outfile + ".done", "w").close() diff --git a/stochss/handlers/util/stochss_folder.py b/stochss/handlers/util/stochss_folder.py index 948d795886..5b91feff8c 100644 --- a/stochss/handlers/util/stochss_folder.py +++ b/stochss/handlers/util/stochss_folder.py @@ -19,6 +19,7 @@ import os import json import shutil +import zipfile import traceback import requests @@ -95,6 +96,20 @@ def __build_jstree_node(self, path, file): return node + @classmethod + def __overwrite(cls, path, ext): + if ext == "zip": + with zipfile.ZipFile(path, "r") as zip_file: + members = zip_file.namelist() + for name in members: + if os.path.isdir(name): + shutil.rmtree(name) + elif os.path.exists(name): + os.remove(name) + elif os.path.exists(path): + os.remove(path) + + def __upload_file(self, file, body, new_name=None): if new_name is not None: file = f"{new_name}.{file.split('.').pop()}" @@ -434,7 +449,7 @@ def upload(self, file_type, file, body, new_name=None): return resp - def upload_from_link(self, remote_path): + def upload_from_link(self, remote_path, overwrite=False): ''' Uploads a file from a remote link to the users root directory @@ -442,6 +457,8 @@ def upload_from_link(self, remote_path): ---------- remote_path : str Path to the remote file + overwrite : bool + Overwrite the existing files. ''' ext = remote_path.split('.').pop() body = requests.get(remote_path, allow_redirects=True).content @@ -455,8 +472,10 @@ def upload_from_link(self, remote_path): file = self.get_file(path=remote_path) path = self.get_new_path(dst_path=file) if os.path.exists(path): - message = f"Could not upload this file as {file} already exists" - return {"message":message, "reason":"File Already Exists"} + if not overwrite: + message = f"Could not upload this file as {file} already exists" + return {"message":message, "reason":"File Already Exists"} + self.__overwrite(path=path, ext=ext) try: file_types = {"mdl":"model", "smdl":"model", "sbml":"sbml"} file_type = file_types[ext] if ext in file_types.keys() else "file" @@ -466,3 +485,32 @@ def upload_from_link(self, remote_path): return {"message":message, "file_path":new_path} except StochSSFileExistsError as err: return {"message":err.message, "reason":err.reason} + + + def validate_upload_link(self, remote_path): + ''' + Check if the target of upload from link already exists. + + Attributes + ---------- + remote_path : str + Path to the remote file + ''' + ext = remote_path.split('.').pop() + body = requests.get(remote_path, allow_redirects=True).content + if "download_presentation" in remote_path: + if ext in ("mdl", "smdl"): + file = f"{json.loads(body)['name']}.{ext}" + elif ext == "ipynb": + file = json.loads(body)['file'] + body = json.dumps(json.loads(body)['notebook']) + else: + file = self.get_file(path=remote_path) + path = self.get_new_path(dst_path=file) + if ext == "zip": + with zipfile.ZipFile(path, "r") as zip_file: + members = zip_file.namelist() + for name in members: + if os.path.exists(name): + return True + return os.path.exists(path) From 165ecf6c8f3eb448b04d7796c4174675c35c1b06 Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Mon, 23 Aug 2021 17:17:43 -0400 Subject: [PATCH 096/186] Added modal to request confirmation from the user if they wish to overwrite an existing file. --- client/modals.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/client/modals.js b/client/modals.js index ac499bb04a..807dda8e9f 100644 --- a/client/modals.js +++ b/client/modals.js @@ -527,6 +527,11 @@ module.exports = { return templates.confirmation_with_message(modalID, title, message); }, + uploadFileExistsHtml: (title, message) => { + let modalID = 'uploadFileExistsModal'; + + return templates.confirmation_with_message(modalID, title, message); + }, renderDefaultModeModalHtml : () => { let concentrationDesciption = `Variables will only be represented using continuous (floating point) values.`; let populationDescription = `Population - Variables will only be represented using discrete (integer count) values.`; From d389d675ce5b6a7c671cf416cf240bb3eb1a948c Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Mon, 23 Aug 2021 17:19:51 -0400 Subject: [PATCH 097/186] Refactored the upload from link functionality to check for existing files, prompt the user if they wish to overwrite the files if existing, then send the upload request eith the appropriate flags. --- client/pages/loading-page.js | 47 ++++++++++++++++++++++++++++-------- 1 file changed, 37 insertions(+), 10 deletions(-) diff --git a/client/pages/loading-page.js b/client/pages/loading-page.js index 8a83f98360..173f2d389f 100644 --- a/client/pages/loading-page.js +++ b/client/pages/loading-page.js @@ -44,16 +44,41 @@ let LoadingPage = PageView.extend({ $(document.querySelector("main[data-hook=page-main]")).removeClass().addClass("col-md-12 body"); $(this.queryByHook("loading-spinner")).css("display", "block"); if(this.action === "open") { - this.uploadFileFromLink(this.filePath); - setTimeout(function () { - $(self.queryByHook("loading-problem").css("display", "block")); - }, 30000); + this.checkForDuplicateFile(this.filePath); }else if(this.action === "update-workflow") { this.updateWorkflowFormat(this.filePath); }else if(this.action === "update-project") { this.updateProjectFormat(this.filePath); } }, + checkForDuplicateFile: function (filePath) { + $(this.queryByHook("loading-header")).html("Uploading file"); + $(this.queryByHook("loading-target")).css("display", "none") + let message = `If the file is a Project, Workflow, Model, Domain, or Notebook it will be opened when the upload has completed.`; + $(this.queryByHook("loading-message")).html(message); + let self = this; + let queryStr = "?path=" + filePath + "&cmd=validate"; + let endpoint = path.join(app.getApiPath(), 'file/upload-from-link') + queryStr; + app.getXHR(endpoint, { + success: function (err, response, body) { + if(!body.exists) { + self.uploadFileFromLink(filePath, false); + }else{ + let title = "File Already Exists"; + let message = "A file with that name already exists, do you wish to overwrite this file?"; + let modal = $(modals.uploadFileExistsHtml(title, message)).modal(); + let yesBtn = document.querySelector("#uploadFileExistsModal .yes-modal-btn"); + let noBtn = document.querySelector("#uploadFileExistsModal .btn-secondary") + yesBtn.addEventListener('click', function (e) { + self.uploadFileFromLink(filePath, true); + }); + noBtn.addEventListener('click', function (e) { + window.location.href = self.homeLink; + }); + } + } + }); + }, getUploadResponse: function () { let self = this; setTimeout(function () { @@ -128,13 +153,15 @@ let LoadingPage = PageView.extend({ let identifier = "stochss/workflow/edit" this.updateFormat(filePath, message, "workflow", identifier); }, - uploadFileFromLink: function (filePath) { - $(this.queryByHook("loading-header")).html("Uploading file"); - $(this.queryByHook("loading-target")).css("display", "none") - let message = `If the file is a Project, Workflow, Model, Domain, or Notebook it will be opened when the upload has completed.`; - $(this.queryByHook("loading-message")).html(message); + uploadFileFromLink: function (filePath, overwrite) { + setTimeout(function () { + $(self.queryByHook("loading-problem").css("display", "block")); + }, 30000); let self = this; - let queryStr = "?path=" + filePath; + var queryStr = "?path=" + filePath; + if(overwrite) { + queryStr += "&overwrite=" + overwrite; + } let endpoint = path.join(app.getApiPath(), 'file/upload-from-link') + queryStr; app.getXHR(endpoint, { success: function (err, response, body) { From 688f935b9a7d5bf0594386fa19b4a698eb930b3a Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Tue, 24 Aug 2021 09:31:32 -0400 Subject: [PATCH 098/186] Concept for new file browser with projects and presentations. --- client/pages/file-browser.js | 52 ++++++++++- client/styles/styles.css | 8 +- client/templates/body.pug | 6 +- client/templates/pages/fileBrowser.pug | 112 ++++++++++++++++++------ client/views/main.js | 6 +- stochss/handlers/util/stochss_folder.py | 3 +- 6 files changed, 148 insertions(+), 39 deletions(-) diff --git a/client/pages/file-browser.js b/client/pages/file-browser.js index cfe2aa7ced..bf2a8d226d 100644 --- a/client/pages/file-browser.js +++ b/client/pages/file-browser.js @@ -23,10 +23,16 @@ let _ = require('underscore'); //support files let app = require('../app'); let modals = require('../modals'); -//models +//collections +let Collection = require('ampersand-collection'); // form presentation browser +//model +let Project = require('../models/project'); // from project browser +let Presentation = require('../models/presentation'); // form presentation browser let Model = require('../models/model'); //views let PageView = require('./base'); +let EditProjectView = require('../views/edit-project'); // from project browser +let PresentationView = require('../views/presentation-view'); // form presentation browser //templates let template = require('../templates/pages/fileBrowser.pug'); @@ -46,11 +52,30 @@ let FileBrowser = PageView.extend({ 'click [data-hook=file-browser-help]' : function () { let modal = $(modals.operationInfoModalHtml('file-browser')).modal(); }, - 'click [data-hook=empty-trash]' : 'emptyTrash' + 'click [data-hook=empty-trash]' : 'emptyTrash', + 'click [data-hook=collapse-projects]' : 'changeCollapseButtonText', + 'click [data-hook=collapse-presentations]' : 'changeCollapseButtonText', + 'click [data-hook=collapse-files]' : 'changeCollapseButtonText' }, initialize: function (attrs, options) { PageView.prototype.initialize.apply(this, arguments) var self = this + // start block from project browser + var endpoint = path.join(app.getApiPath(), "project/load-browser"); + app.getXHR(endpoint, { + success: function (err, response, body) { + self.renderProjectsView(body.projects); + } + }); + // End block from project browser + // Start block from presentation bowser + var endpoint = path.join(app.getApiPath(), "file/presentations") + app.getXHR(endpoint, { + success: function (err, response, body) { + self.renderPresentationView(body.presentations); + } + }); + // End block from presentation browser this.root = "none" this.ajaxData = { "url" : function (node) { @@ -169,6 +194,29 @@ let FileBrowser = PageView.extend({ e.target.remove() }); }, + // Function from project browser + renderProjectsView: function (projects) { + let options = {model: Project, comparator: 'parentDir'}; + let projectCollection = new Collection(projects, options); + this.renderCollection( + projectCollection, + EditProjectView, + this.queryByHook("projects-view-container") + ); + }, + // Function from presentation browser + renderPresentationView: function (presentations) { + let options = {model: Presentation}; + let presentCollection = new Collection(presentations, options); + this.renderCollection( + presentCollection, + PresentationView, + this.queryByHook("presentation-list") + ); + }, + changeCollapseButtonText: function (e) { + app.changeCollapseButtonText(this, e); + }, refreshJSTree: function () { this.jstreeIsLoaded = false $('#models-jstree').jstree().deselect_all(true) diff --git a/client/styles/styles.css b/client/styles/styles.css index f9d28c9eb0..450dfca499 100644 --- a/client/styles/styles.css +++ b/client/styles/styles.css @@ -163,6 +163,13 @@ a.disabled { cursor: default; } +a.anchor { + display: block; + position: relative; + top: -80px; + visibility: hidden; +} + .nav-item { width: fit-content; } @@ -304,7 +311,6 @@ input[type="file"]::-ms-browse { .information-btn { color: #007bff; - border-color: #ffffff; padding: 0.5rem 1rem; text-align: left; } diff --git a/client/templates/body.pug b/client/templates/body.pug index 7e6d7cd5da..cc4c4e4425 100644 --- a/client/templates/body.pug +++ b/client/templates/body.pug @@ -40,13 +40,13 @@ body | Home li.nav-item - div: a.nav-link(href="stochss/project/browser", title="Explore your StochSS projects") Projects + div: a.nav-link(href="stochss/files#project-browser-section", title="Explore your StochSS projects") Projects li.nav-item(id="presentation-nav-link") - div: a.nav-link(href="stochss/home#presentations", title="Explore your StochSS presentations") Presentations + div: a.nav-link(href="stochss/files#presentation-browser-section", title="Explore your StochSS presentations") Presentations li.nav-item - div: a.nav-link(href="stochss/files", title="Explore your StochSS files") Files + div: a.nav-link(href="stochss/files#file-browser-section", title="Explore your StochSS files") Files li.nav-item div: a.nav-link(target="_blank", href="tree", title="Browse your files in a Jupyter interface") Jupyter diff --git a/client/templates/pages/fileBrowser.pug b/client/templates/pages/fileBrowser.pug index ea87593238..479dac6f58 100644 --- a/client/templates/pages/fileBrowser.pug +++ b/client/templates/pages/fileBrowser.pug @@ -1,40 +1,94 @@ section.page - div - h2.inline File Browser + div.card.mt-3 - button.big-tip.btn.information-btn.help(id="file-browser-help" data-hook="file-browser-help") + a.anchor#project-browser-section - div.alert-warning(class="collapse", id="extension-warning" data-hook="extension-warning") You should avoid changing the file extension unless you know what you are doing! + div.card-header.pb-0 - div.alert-warning(class="collapse", id="rename-warning" data-hook="rename-warning") MESSAGE + h2.inline Project Browser - div.alert-danger(id="renameSpecialCharError" style="display: none;") Names can only include the following characters: (0-9), (a-z), (A-Z) and (., -, _, (, or )) + button.btn.btn-outline-collapse(data-toggle="collapse" data-target="#collapse-projects" data-hook="collapse-projects") - - div#models-jstree + div.collapse.show(id="collapse-projects") - button.btn.btn-primary.inline.box-shadow( - id="new-file-directory", - data-hook="new-file-directory", - data-toggle="dropdown", - aria-haspopup="true", - aria-expanded="false", - type="button" - ) + + div.card-body - ul.dropdown-menu(aria-labelledby="new-file-directory") - li.dropdown-item(id="new-directory" data-hook="new-directory") Create Directory - li.dropdown-item(id="new-project" data-hook="new-project") Create Project - li.dropdown-item(id="new-model" data-hook="new-model" data-type="model") Create Model - li.dropdown-item(id="new-model" data-hook="new-model" data-type="spatial") Create Spatial Model (beta) - li.dropdown-item(id="new-model" data-hook="new-domain") Create Domain (beta) - li.dropdown-divider - li.dropdown-item(id="upload-file-btn" data-hook="upload-file-btn", data-type="model") Upload StochSS Model - li.dropdown-item(id="upload-file-btn" data-hook="upload-file-btn", data-type="sbml") Upload SBML Model - li.dropdown-item(id="upload-file-btn" data-hook="upload-file-btn", data-type="file") Upload File + table.table + + tbody(id="projects-view-container" data-hook="projects-view-container") - button.btn.btn-primary.box-shadow(id="options-for-node" data-hook="options-for-node" disabled) Actions - - button.btn.btn-primary.inline.box-shadow(id="refresh-jstree" data-hook="refresh-jstree") Refresh + button.btn.btn-outline-primary.box-shadow(id="new-project-btn" data-hook="new-project-btn") New - button.btn.btn-primary.inline.box-shadow(id="empty-trash" data-hook="empty-trash") Empty Trash \ No newline at end of file + div.card.my-4 + + a.anchor#presentation-browser-section + + div.card-header.pb-0 + + h2.inline Presentations + + button.btn.btn-outline-collapse(data-toggle="collapse" data-target="#collapse-presentations" data-hook="collapse-presentations") - + + div.collapse.show(id="collapse-presentations") + + div.card-body + + div.mx-1.row.head.align-items-baseline + + div.col-sm-8: h6 File + + div.col-sm-2: h6 Size + + div.col-sm-2: h6 Remove + + div.mt-3(data-hook="presentation-list") + + div.card + + a.anchor#file-browser-section + + div.card-header.pb-0 + h2.inline File Browser + + button.big-tip.btn.information-btn.help(id="file-browser-help" data-hook="file-browser-help") + + button.btn.btn-outline-collapse(data-toggle="collapse" data-target="#collapse-files" data-hook="collapse-files") - + + div.collapse.show(id="collapse-files") + + div.card-body + + div.alert-warning(class="collapse", id="extension-warning" data-hook="extension-warning") You should avoid changing the file extension unless you know what you are doing! + + div.alert-warning(class="collapse", id="rename-warning" data-hook="rename-warning") MESSAGE + + div.alert-danger(id="renameSpecialCharError" style="display: none;") Names can only include the following characters: (0-9), (a-z), (A-Z) and (., -, _, (, or )) + + div#models-jstree + + button.btn.btn-primary.inline.box-shadow( + id="new-file-directory", + data-hook="new-file-directory", + data-toggle="dropdown", + aria-haspopup="true", + aria-expanded="false", + type="button" + ) + + + ul.dropdown-menu(aria-labelledby="new-file-directory") + li.dropdown-item(id="new-directory" data-hook="new-directory") Create Directory + li.dropdown-item(id="new-project" data-hook="new-project") Create Project + li.dropdown-item(id="new-model" data-hook="new-model" data-type="model") Create Model + li.dropdown-item(id="new-model" data-hook="new-model" data-type="spatial") Create Spatial Model (beta) + li.dropdown-item(id="new-model" data-hook="new-domain") Create Domain (beta) + li.dropdown-divider + li.dropdown-item(id="upload-file-btn" data-hook="upload-file-btn", data-type="model") Upload StochSS Model + li.dropdown-item(id="upload-file-btn" data-hook="upload-file-btn", data-type="sbml") Upload SBML Model + li.dropdown-item(id="upload-file-btn" data-hook="upload-file-btn", data-type="file") Upload File + + button.btn.btn-primary.box-shadow(id="options-for-node" data-hook="options-for-node" disabled) Actions + + button.btn.btn-primary.inline.box-shadow(id="refresh-jstree" data-hook="refresh-jstree") Refresh + + button.btn.btn-primary.inline.box-shadow(id="empty-trash" data-hook="empty-trash") Empty Trash \ No newline at end of file diff --git a/client/views/main.js b/client/views/main.js index c77d0ce9ce..37fa7343b7 100644 --- a/client/views/main.js +++ b/client/views/main.js @@ -129,9 +129,9 @@ module.exports = View.extend({ App.currentPage = newView; } }); - if(app.getBasePath() === "/") { - $("#presentation-nav-link").css("display", "none"); - } + // if(app.getBasePath() === "/") { + // $("#presentation-nav-link").css("display", "none"); + // } let self = this; let message = app.getBasePath() === "/" ? "Welcome to StochSS!" : "Welcome to StochSS Live!"; $("#user-logs").html(message) diff --git a/stochss/handlers/util/stochss_folder.py b/stochss/handlers/util/stochss_folder.py index 3408198aa4..4a6ccfb69a 100644 --- a/stochss/handlers/util/stochss_folder.py +++ b/stochss/handlers/util/stochss_folder.py @@ -349,7 +349,8 @@ def get_presentations(cls): if not os.path.isdir(path): return presentations safe_chars = set(string.ascii_letters + string.digits) - hostname = escape(os.environ.get('JUPYTERHUB_USER'), safe=safe_chars) + # hostname = escape(os.environ.get('JUPYTERHUB_USER'), safe=safe_chars) + hostname = "brumsey@unca.edu" for file in os.listdir(path): file_path = os.path.join(path, file) query_str = f"?owner={hostname}&file={file}" From fd6b54f6fff45ed623044c509b7e79b8a2980ea8 Mon Sep 17 00:00:00 2001 From: BryanRumsey <44621966+BryanRumsey@users.noreply.github.com> Date: Tue, 24 Aug 2021 15:39:46 -0400 Subject: [PATCH 099/186] Updated info syntax --- client/settings-view/templates/simulationSettingsView.pug | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/settings-view/templates/simulationSettingsView.pug b/client/settings-view/templates/simulationSettingsView.pug index 65825b1a89..d06f917689 100644 --- a/client/settings-view/templates/simulationSettingsView.pug +++ b/client/settings-view/templates/simulationSettingsView.pug @@ -21,7 +21,7 @@ div#simulation-settings.card div.card-body p.mb-0 - | Consider the following when selecting an algorithm for your job. If you would like StochSS to select the algorithm for you select Choose for me. + | Consider the following when selecting an algorithm for your job. If you would like StochSS to choose the algorithm, select Choose for me. ul.mb-0 li Algorithms ODE and Hybrid ODE/SSA work best to simulate models with a mode of Concentration. From 84f7592e14ee64cac20954fdddd527cee6f12591 Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Tue, 24 Aug 2021 17:28:07 -0400 Subject: [PATCH 100/186] Added new JSTree view file with template. --- client/templates/includes/jstreeView.pug | 9 ++ client/views/jstree-view.js | 175 +++++++++++++++++++++++ 2 files changed, 184 insertions(+) create mode 100644 client/templates/includes/jstreeView.pug create mode 100644 client/views/jstree-view.js diff --git a/client/templates/includes/jstreeView.pug b/client/templates/includes/jstreeView.pug new file mode 100644 index 0000000000..83cc1cf796 --- /dev/null +++ b/client/templates/includes/jstreeView.pug @@ -0,0 +1,9 @@ +div + + div.alert-warning(class="collapse", id="extension-warning" data-hook="extension-warning") You should avoid changing the file extension unless you know what you are doing! + + div.alert-warning(class="collapse", id="rename-warning" data-hook="rename-warning") MESSAGE + + div.alert-danger(id="renameSpecialCharError" style="display: none;") Names can only include the following characters: (0-9), (a-z), (A-Z) and (., -, _, (, or )) + + div#files-jstree \ No newline at end of file diff --git a/client/views/jstree-view.js b/client/views/jstree-view.js new file mode 100644 index 0000000000..312740aa7a --- /dev/null +++ b/client/views/jstree-view.js @@ -0,0 +1,175 @@ +/* +StochSS is a platform for simulating biochemical systems +Copyright (C) 2019-2021 StochSS developers. + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +let $ = require('jquery'); +let path = require('path'); +let jstree = require('jstree'); +//support files +let app = require('../app'); +//config +let FileConfig = require('../file-config') +let ProjectConfig = require('../project-config'); +//views +let View = require('ampersand-view'); +//templates +let template = require('../templates/includes/jstreeView.pug'); + +module.exports = View.extend({ + template: template, + initialize: function (attrs, options) { + View.prototype.initialize.apply(this, arguments); + this.config = attrs.configKey === "file" ? FileConfig : ProjectConfig; + this.root = Boolean(attrs.root) ? attrs.root : "none"; + this.nodeForContextMenu = null; + this.jstreeIsLoaded = false; + this.ajaxData = { + "url" : (node) => { + if(node.parent === null){ + var queryStr = "?path=" + this.root; + if(this.root !== "none") { + queryStr += "&isRoot=True"; + } + return path.join(app.getApiPath(), "file/browser-list") + queryStr; + } + var queryStr = "?path=" + node.original._path; + return path.join(app.getApiPath(), "file/browser-list") + queryStr; + }, + "dataType" : "json", + "data" : (node) => { + return { 'id' : node.id} + } + } + this.treeSettings = { + 'plugins': ['types', 'wholerow', 'changed', 'contextmenu', 'dnd'], + 'core': { + 'multiple': false, + 'animation': 0, + 'check_callback': (op, node, par, pos, more) => { + if(op === "rename_node" && this.validateName(pos, true) !== ""){ + let err = $("#renameSpecialCharError"); + err.css("display", "block"); + setTimeout(() => { + err.css("display", "none"); + }, 5000) + return false + } + if(op === 'move_node' && more && more.core) { + this.config.move(this, par, node); + }else if(op === "move_node") { + return this.config.validateMove(this, node, more); + } + }, + 'themes': {'stripes': true, 'variant': 'large'}, + 'data': this.ajaxData + }, + 'types': this.config.types + } + }, + render: function (attrs, options) { + View.prototype.render.apply(this, arguments); + window.addEventListener('pageshow', function (e) { + let navType = window.performance.navigation.type; + if(navType === 2){ + window.location.reload(); + } + }); + this.setupJstree(() => { + setTimeout(() => { + this.refreshInitialJSTree(); + }, 3000); + }); + }, + refreshInitialJSTree: function () { + let count = $('#files-jstree').jstree()._model.data['#'].children.length; + if(count == 0) { + this.refreshJSTree(null); + setTimeout(() => { + this.refreshInitialJSTree(); + }, 3000); + } + }, + refreshJSTree: function (par) { + if(par === null || par.type === 'root'){ + this.jstreeIsLoaded = false + $('#files-jstree').jstree().deselect_all(true) + $('#files-jstree').jstree().refresh(); + }else{ + $('#files-jstree').jstree().refresh_node(par); + } + }, + setupJstree: function (cb) { + $.jstree.defaults.contextmenu.items = (o, cb) => { + let nodeType = o.original.type; + let zipTypes = this.config.contextZipTypes; + let asZip = zipTypes.includes(nodeType); + let optionsButton = $(this.queryByHook("options-for-node")); + if(!this.nodeForContextMenu) { + optionsButton.prop('disabled', false); + } + optionsButton.text("Actions for " + o.original.text); + this.nodeForContextMenu = o; + } + $(document).ready(() => { + $(document).on('shown.bs.modal', (e) => { + $('[autofocus]', e.target).focus(); + }); + $(document).on('dnd_start.vakata', (data, element, helper, event) => { + $('#files-jstree').jstree().load_all(); + }); + $('#files-jstree').jstree(this.treeSettings).bind("loaded.jstree", (event, data) => { + this.jstreeIsLoaded = true; + }).bind("refresh.jstree", (event, data) => { + this.jstreeIsLoaded = true; + }); + $('#files-jstree').on('click.jstree', (e) => { + let parent = e.target.parentElement; + let _node = parent.children[parent.children.length - 1]; + let node = $('#files-jstree').jstree().get_node(_node); + if(_node.nodeName === "A" && $('#files-jstree').jstree().is_loaded(node) && node.type === "folder"){ + this.refreshJSTree(node); + }else{ + let optionsButton = $(this.queryByHook("options-for-node")); + if(this.nodeForContextMenu === null){ + optionsButton.prop('disabled', false); + } + optionsButton.text("Actions for " + node.original.text); + this.nodeForContextMenu = node; + } + }); + $('#files-jstree').on('dblclick.jstree', (e) => { + this.config.doubleClick(this, e); + }); + }); + }, + validateName: function (input, rename = false) { + var error = "" + if(input.endsWith('/')) { + error = 'forward' + } + var invalidChars = "`~!@#$%^&*=+[{]}\"|:;'<,>?\\" + if(rename) { + invalidChars += "/" + } + for(var i = 0; i < input.length; i++) { + if(invalidChars.includes(input.charAt(i))) { + error = error === "" || error === "special" ? "special" : "both" + } + } + return error + } +}); \ No newline at end of file From 5be443711539d2ae7557a21179b63d3b124348c4 Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Tue, 24 Aug 2021 17:28:53 -0400 Subject: [PATCH 101/186] Added new JSTree config files. --- client/file-config.js | 128 ++++++++++++++++++++++++++++++ client/project-config.js | 167 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 295 insertions(+) create mode 100644 client/file-config.js create mode 100644 client/project-config.js diff --git a/client/file-config.js b/client/file-config.js new file mode 100644 index 0000000000..13c0ac6ddf --- /dev/null +++ b/client/file-config.js @@ -0,0 +1,128 @@ +/* +StochSS is a platform for simulating biochemical systems +Copyright (C) 2019-2021 StochSS developers. + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +let $ = require('jquery'); +let path = require('path'); +//support files +let app = require('./app'); + +let contextZipTypes = ["workflow", "folder", "other", "project", "root"]; + +let doubleClick = (view, e) => { + let node = $('#files-jstree').jstree().get_node(e.target); + if(!(node.original._path.split("/")[0] === "trash")) { + if(node.type === "folder" && $('#files-jstree').jstree().is_open(node) && $('#files-jstree').jstree().is_loaded(node)){ + view.refreshJSTree(node); + }else if(node.type === "nonspatial" || node.type === "spatial"){ + let queryStr = "?path=" + node.original._path; + window.location.href = path.join(app.getBasePath(), "stochss/models/edit") + queryStr; + }else if(node.type === "notebook"){ + window.open(path.join(app.getBasePath(), "notebooks", node.original._path), '_blank'); + }else if(node.type === "sbml-model"){ + window.open(path.join(app.getBasePath(), "edit", node.original._path), '_blank'); + }else if(node.type === "project"){ + let queryStr = "?path=" + node.original._path + window.location.href = path.join(app.getBasePath(), "stochss/project/manager") + queryStr; + }else if(node.type === "workflow"){ + let queryStr = "?path=" + node.original._path + "&type=none"; + window.location.href = path.join(app.getBasePath(), "stochss/workflow/edit") + queryStr; + }else if(node.type === "domain") { + let queryStr = "?domainPath=" + node.original._path; + window.location.href = path.join(app.getBasePath(), "stochss/domain/edit") + queryStr + }else if(node.type === "other"){ + var openPath = path.join(app.getBasePath(), "view", node.original._path); + window.open(openPath, "_blank"); + } + } +} + +let move = (view, par, node) => { + let newDir = par.original._path !== "/" ? par.original._path : ""; + let file = node.original._path.split('/').pop(); + let oldPath = node.original._path; + let queryStr = "?srcPath=" + oldPath + "&dstPath=" + path.join(newDir, file); + let endpoint = path.join(app.getApiPath(), "file/move") + queryStr; + app.getXHR(endpoint, { + success: (err, response, body) => { + node.original._path = path.join(newDir, file); + if(node.type === "folder") { + view.refreshJSTree(node); + }else if(newDir.endsWith("trash")) { + $(view.queryByHook('empty-trash')).prop('disabled', false); + view.refreshJSTree(par); + }else if(oldPath.split("/").includes("trash")) { + view.refreshJSTree(par); + } + }, + error: (err, response, body) => { + body = JSON.parse(body); + view.refreshJSTree(par); + } + }); +} + +types = { + 'root' : {"icon": "jstree-icon jstree-folder"}, + 'folder' : {"icon": "jstree-icon jstree-folder"}, + 'spatial' : {"icon": "jstree-icon jstree-file"}, + 'nonspatial' : {"icon": "jstree-icon jstree-file"}, + 'project' : {"icon": "jstree-icon jstree-file"}, + 'workflow' : {"icon": "jstree-icon jstree-file"}, + 'notebook' : {"icon": "jstree-icon jstree-file"}, + 'domain' : {"icon": "jstree-icon jstree-file"}, + 'sbml-model' : {"icon": "jstree-icon jstree-file"}, + 'other' : {"icon": "jstree-icon jstree-file"}, +} + +validateMove = (view, node, more) => { + // Check if workflow is running + let validSrc = Boolean(node && node.type && node.original && node.original.text !== "trash"); + let isWorkflow = Boolean(validSrc && node.type === "workflow"); + if(isWorkflow && node.original._status && node.original._status === "running") { return false }; + + // Check if file is moving to a valid location + let validDsts = ["root", "folder"]; + let validDst = Boolean(more && more.ref && more.ref.type && more.ref.original); + if(validDst && !validDsts.includes(more.ref.type)) { return false }; + if(validDst && path.dirname(more.ref.original._path).split("/").includes("trash")) { return false }; + + // Check if file already exists with that name in folder + if(validDst && more.ref.type === 'folder' && more.ref.text !== "trash"){ + if(!more.ref.state.loaded) { return false }; + try{ + let BreakException = {}; + more.ref.children.forEach(function (child) { + let child_node = $('#files-jstree').jstree().get_node(child); + let exists = child_node.text === node.text; + if(exists) { throw BreakException; }; + }); + }catch{ return false; }; + } + + // Check if curser is over the correct location + if(more && (pos != 0 || more.pos !== "i") && !more.core) { return false }; + return true; +} + +module.exports = { + contextZipTypes: contextZipTypes, + doubleClick: doubleClick, + move: move, + types: types, + validateMove: validateMove +} diff --git a/client/project-config.js b/client/project-config.js new file mode 100644 index 0000000000..3e7c95c00c --- /dev/null +++ b/client/project-config.js @@ -0,0 +1,167 @@ +/* +StochSS is a platform for simulating biochemical systems +Copyright (C) 2019-2021 StochSS developers. + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +let path = require('path'); +//support files +let app = require('./app'); + +let contextZipTypes = ["workflow", "folder", "other", "root", "workflow-group"]; + +let doubleClick = (view, e) => { + let node = $('#files-jstree').jstree().get_node(e.target); + if(!node.original._path.includes(".proj/trash/")){ + if((node.type === "folder" || node.type === "workflow-group") && $('#files-jstree').jstree().is_open(node) && $('#files-jstree').jstree().is_loaded(node)){ + view.refreshJSTree(node); + }else if(node.type === "nonspatial" || node.type === "spatial"){ + let queryStr = "?path=" + node.original._path; + window.location.href = path.join(app.getBasePath(), "stochss/models/edit") + queryStr; + }else if(node.type === "notebook"){ + window.open(path.join(app.getBasePath(), "notebooks", node.original._path), '_blank'); + }else if(node.type === "sbml-model"){ + window.open(path.join(app.getBasePath(), "edit", node.original._path), '_blank'); + }else if(node.type === "workflow"){ + let queryStr = "?path=" + node.original._path + "&type=none"; + window.location.href = path.join(app.getBasePath(), "stochss/workflow/edit") + queryStr; + }else if(node.type === "domain") { + let queryStr = "?domainPath=" + node.original._path; + window.location.href = path.join(app.getBasePath(), "stochss/domain/edit") + queryStr + }else if(node.type === "other"){ + var openPath = path.join(app.getBasePath(), "view", node.original._path); + window.open(openPath, "_blank"); + } + } +} + +let move = (view, par, node) => { + let newDir = par.original._path !== "/" ? par.original._path : ""; + let file = node.original._path.split('/').pop(); + let oldPath = node.original._path; + let queryStr = "?srcPath=" + oldPath + "&dstPath=" + path.join(newDir, file); + let endpoint = path.join(app.getApiPath(), "file/move") + queryStr; + app.getXHR(endpoint, { + success: (err, response, body) => { + node.original._path = path.join(newDir, file); + if((node.type === "nonspatial" || node.type === "spatial") && (oldPath.includes("trash") || newDir.includes("trash"))) { + updateParent(view, "Archive"); + }else if(node.type !== "notebook" || node.original._path.includes(".wkgp") || newDir.includes(".wkgp")) { + updateParent(view, node.type); + } + }, + error: (err, response, body) => { + body = JSON.parse(body); + view.refreshJSTree(par); + } + }); +} + +types = { + 'root' : {"icon": "jstree-icon jstree-folder"}, + 'folder' : {"icon": "jstree-icon jstree-folder"}, + 'spatial' : {"icon": "jstree-icon jstree-file"}, + 'nonspatial' : {"icon": "jstree-icon jstree-file"}, + 'workflow-group' : {"icon": "jstree-icon jstree-folder"}, + 'workflow' : {"icon": "jstree-icon jstree-file"}, + 'notebook' : {"icon": "jstree-icon jstree-file"}, + 'domain' : {"icon": "jstree-icon jstree-file"}, + 'sbml-model' : {"icon": "jstree-icon jstree-file"}, + 'other' : {"icon": "jstree-icon jstree-file"}, +} + +updateParent = (view, type) => { + let models = ["nonspatial", "spatial", "sbml", "model"]; + let workflows = ["workflow", "notebook"]; + if(models.includes(type)) { + view.parent.update("Model"); + }else if(workflows.includes(type)) { + view.parent.update("Workflow"); + }else if(type === "workflow-group") { + view.parent.update("WorkflowGroup"); + }else if(type === "Archive") { + view.parent.update(type); + } +} + +validateMove = (view, node, more) => { + // Check if files are being move directly into the trash and remain static with respect to the trash + let validDst = Boolean(more && more.ref && more.ref.type && more.ref.original); + if(validDst && path.dirname(more.ref.original._path).includes("trash")) { return false }; + let validSrc = Boolean(node && node.type && node.original && node.original.text !== "trash"); + if(validSrc && validDst && node.original._path.includes("trash") && more.ref.original.text === 'trash') { return false }; + + // Check if workflow is running + let isWorkflow = Boolean(validSrc && node.type === "workflow"); + if(isWorkflow && node.original._status && node.original._status === "running") { return false }; + + // Check if model, workflow, or workflow group is moving to or from trash + let isWkgp = Boolean(validSrc && node.type === "workflow-group"); + let trashAction = Boolean((validSrc && node.original._path.includes("trash")) || (validDst && more.ref.original.text === "trash")); + if(isWkgp && !(view.parent.model.newFormat && trashAction)) { return false }; + let isModel = Boolean(validSrc && (node.type === "nonspatial" || node.type === "spatial")); + if((isModel || isWorkflow) && !trashAction) { return false }; + + // Check if model, workflow, or workflow group is moving from trash to the correct location + if(validSrc && node.original._path.includes("trash")) { + if(isWkgp && (!view.parent.model.newFormat || (validDst && more.ref.type !== "root"))) { return false }; + if(isWorkflow && validDst && more.ref.type !== "workflow-group") { return false }; + if(isModel && validDst) { + if(!view.parent.model.newFormat && more.ref.type !== "root") { return false }; + let length = node.original.text.split(".").length; + let modelName = node.original.text.split(".").slice(0, length - 1).join("."); + if(view.parent.model.newFormat && (more.ref.type !== "workflow-group" || !more.ref.original.text.startsWith(modelName))) { return false }; + } + } + + // Check if notebook or other file is moving to a valid location. + let validDsts = ["root", "folder"]; + let isNotebook = Boolean(validSrc && node.type === "notebook"); + let isOther = Boolean(validSrc && !isModel && !isWorkflow && !isWkgp && !isNotebook); + if(isOther && validDst && !validDsts.includes(more.ref.type)) { return false }; + validDsts.push("workflow-group"); + if(isNotebook && validDst && !validDsts.includes(more.ref.type)) { return false }; + + // Check if file already exists with that name in folder + if(validDst && validDsts.includes(more.ref.type)){ + if(!more.ref.state.loaded) { return false }; + var text = node.text; + if(!isNaN(text.split(' ').pop().split('.').join(""))){ + text = text.replace(text.split(' ').pop(), '').trim(); + } + if(more.ref.text !== "trash"){ + try{ + let BreakException = {}; + more.ref.children.forEach((child) => { + let child_node = $('#files-jstree').jstree().get_node(child); + let exists = child_node.text === text; + if(exists) { throw BreakException; }; + }) + }catch { return false; }; + } + } + + // Check if curser is over the correct location + if(more && (pos != 0 || more.pos !== "i") && !more.core) { return false }; + return true; +} + +module.exports = { + contextZipTypes: contextZipTypes, + doubleClick: doubleClick, + move: move, + types: types, + validateMove: validateMove +} From affcc18b45939a235a0790e4546a13cc4cd56275 Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Tue, 24 Aug 2021 17:31:05 -0400 Subject: [PATCH 102/186] Setup conceptual layout for new browse page. Moved common code from existing file browser views to the new JSTree view. Moved unique code to the new config files. --- client/pages/file-browser.js | 362 ++++++++--------- client/templates/pages/fileBrowser.pug | 8 +- client/views/file-browser-view.js | 514 ++++++++++++------------- 3 files changed, 442 insertions(+), 442 deletions(-) diff --git a/client/pages/file-browser.js b/client/pages/file-browser.js index bf2a8d226d..e6eb836b56 100644 --- a/client/pages/file-browser.js +++ b/client/pages/file-browser.js @@ -33,6 +33,7 @@ let Model = require('../models/model'); let PageView = require('./base'); let EditProjectView = require('../views/edit-project'); // from project browser let PresentationView = require('../views/presentation-view'); // form presentation browser +let JSTreeView = require('../views/jstree-view'); //templates let template = require('../templates/pages/fileBrowser.pug'); @@ -76,120 +77,125 @@ let FileBrowser = PageView.extend({ } }); // End block from presentation browser - this.root = "none" - this.ajaxData = { - "url" : function (node) { - if(node.parent === null){ - return path.join(app.getApiPath(), "file/browser-list")+"?path="+self.root - } - return path.join(app.getApiPath(), "file/browser-list")+"?path="+ node.original._path - }, - "dataType" : "json", - "data" : function (node) { - return { 'id' : node.id} - }, - } - this.treeSettings = { - 'plugins': ['types', 'wholerow', 'changed', 'contextmenu', 'dnd'], - 'core': {'multiple' : false, 'animation': 0, - 'check_callback': function (op, node, par, pos, more) { - if(op === "rename_node" && self.validateName(pos, true) !== ""){ - document.querySelector("#renameSpecialCharError").style.display = "block" - setTimeout(function () { - document.querySelector("#renameSpecialCharError").style.display = "none" - }, 5000) - return false - } - if(op === 'move_node' && node && node.type && node.type === "workflow" && node.original && node.original._status && node.original._status === "running"){ - return false - } - if(op === 'move_node' && more && more.ref && more.ref.type && !(more.ref.type == 'folder' || more.ref.type == 'root')){ - return false - } - if(op === 'move_node' && more && more.ref && more.ref.original && path.dirname(more.ref.original._path).split("/").includes("trash")){ - return false - } - if(op === 'move_node' && more && more.ref && more.ref.type && more.ref.type === 'folder' && more.ref.text !== "trash"){ - if(!more.ref.state.loaded){ - return false - } - var exists = false - var BreakException = {} - try{ - more.ref.children.forEach(function (child) { - var child_node = $('#models-jstree').jstree().get_node(child) - exists = child_node.text === node.text - if(exists){ - throw BreakException; - } - }) - }catch{ - return false; - } - } - if(op === 'move_node' && more && (pos != 0 || more.pos !== "i") && !more.core){ - return false - } - if(op === 'move_node' && more && more.core) { - var newDir = par.type !== "root" ? par.original._path : "" - var file = node.original._path.split('/').pop() - var oldPath = node.original._path - let queryStr = "?srcPath="+oldPath+"&dstPath="+path.join(newDir, file) - var endpoint = path.join(app.getApiPath(), "file/move")+queryStr - app.getXHR(endpoint, { - success: function (err, response, body) { - node.original._path = path.join(newDir, file); - if(node.type === "folder") { - $('#models-jstree').jstree().refresh_node(node); - }else if(newDir.endsWith("trash")) { - $(self.queryByHook('empty-trash')).prop('disabled', false); - $('#models-jstree').jstree().refresh_node(par); - }else if(oldPath.split("/").includes("trash")) { - $('#models-jstree').jstree().refresh_node(par); - } - }, - error: function (err, response, body) { - body = JSON.parse(body); - $('#models-jstree').jstree().refresh(); - } - }); - } - return true - }, - 'themes': {'stripes': true, 'variant': 'large'}, - 'data': this.ajaxData, - }, - 'types' : { - 'root' : {"icon": "jstree-icon jstree-folder"}, - 'folder' : {"icon": "jstree-icon jstree-folder"}, - 'spatial' : {"icon": "jstree-icon jstree-file"}, - 'nonspatial' : {"icon": "jstree-icon jstree-file"}, - 'project' : {"icon": "jstree-icon jstree-file"}, - 'workflow-group' : {"icon": "jstree-icon jstree-file"}, - 'workflow' : {"icon": "jstree-icon jstree-file"}, - 'notebook' : {"icon": "jstree-icon jstree-file"}, - 'domain' : {"icon": "jstree-icon jstree-file"}, - 'sbml-model' : {"icon": "jstree-icon jstree-file"}, - 'other' : {"icon": "jstree-icon jstree-file"}, - }, - } + // this.root = "none" + // this.ajaxData = { + // "url" : function (node) { + // if(node.parent === null){ + // return path.join(app.getApiPath(), "file/browser-list")+"?path="+self.root + // } + // return path.join(app.getApiPath(), "file/browser-list")+"?path="+ node.original._path + // }, + // "dataType" : "json", + // "data" : function (node) { + // return { 'id' : node.id} + // }, + // } + // this.treeSettings = { + // 'plugins': ['types', 'wholerow', 'changed', 'contextmenu', 'dnd'], + // 'core': {'multiple' : false, 'animation': 0, + // 'check_callback': function (op, node, par, pos, more) { + // if(op === "rename_node" && self.validateName(pos, true) !== ""){ + // document.querySelector("#renameSpecialCharError").style.display = "block" + // setTimeout(function () { + // document.querySelector("#renameSpecialCharError").style.display = "none" + // }, 5000) + // return false + // } + // if(op === 'move_node' && node && node.type && node.type === "workflow" && node.original && node.original._status && node.original._status === "running"){ + // return false + // } + // if(op === 'move_node' && more && more.ref && more.ref.type && !(more.ref.type == 'folder' || more.ref.type == 'root')){ + // return false + // } + // if(op === 'move_node' && more && more.ref && more.ref.original && path.dirname(more.ref.original._path).split("/").includes("trash")){ + // return false + // } + // if(op === 'move_node' && more && more.ref && more.ref.type && more.ref.type === 'folder' && more.ref.text !== "trash"){ + // if(!more.ref.state.loaded){ + // return false + // } + // var exists = false + // var BreakException = {} + // try{ + // more.ref.children.forEach(function (child) { + // var child_node = $('#models-jstree').jstree().get_node(child) + // exists = child_node.text === node.text + // if(exists){ + // throw BreakException; + // } + // }) + // }catch{ + // return false; + // } + // } + // if(op === 'move_node' && more && (pos != 0 || more.pos !== "i") && !more.core){ + // return false + // } + // if(op === 'move_node' && more && more.core) { + // var newDir = par.type !== "root" ? par.original._path : "" + // var file = node.original._path.split('/').pop() + // var oldPath = node.original._path + // let queryStr = "?srcPath="+oldPath+"&dstPath="+path.join(newDir, file) + // var endpoint = path.join(app.getApiPath(), "file/move")+queryStr + // app.getXHR(endpoint, { + // success: function (err, response, body) { + // node.original._path = path.join(newDir, file); + // if(node.type === "folder") { + // $('#models-jstree').jstree().refresh_node(node); + // }else if(newDir.endsWith("trash")) { + // $(self.queryByHook('empty-trash')).prop('disabled', false); + // $('#models-jstree').jstree().refresh_node(par); + // }else if(oldPath.split("/").includes("trash")) { + // $('#models-jstree').jstree().refresh_node(par); + // } + // }, + // error: function (err, response, body) { + // body = JSON.parse(body); + // $('#models-jstree').jstree().refresh(); + // } + // }); + // } + // return true + // }, + // 'themes': {'stripes': true, 'variant': 'large'}, + // 'data': this.ajaxData, + // }, + // 'types' : { + // 'root' : {"icon": "jstree-icon jstree-folder"}, + // 'folder' : {"icon": "jstree-icon jstree-folder"}, + // 'spatial' : {"icon": "jstree-icon jstree-file"}, + // 'nonspatial' : {"icon": "jstree-icon jstree-file"}, + // 'project' : {"icon": "jstree-icon jstree-file"}, + // 'workflow-group' : {"icon": "jstree-icon jstree-file"}, + // 'workflow' : {"icon": "jstree-icon jstree-file"}, + // 'notebook' : {"icon": "jstree-icon jstree-file"}, + // 'domain' : {"icon": "jstree-icon jstree-file"}, + // 'sbml-model' : {"icon": "jstree-icon jstree-file"}, + // 'other' : {"icon": "jstree-icon jstree-file"}, + // }, + // } }, - render: function () { - var self = this; - this.nodeForContextMenu = ""; - this.renderWithTemplate(); - this.jstreeIsLoaded = false - window.addEventListener('pageshow', function (e) { - var navType = window.performance.navigation.type - if(navType === 2){ - window.location.reload() - } - }); - this.setupJstree(function () { - setTimeout(function () { - self.refreshInitialJSTree(); - }, 3000); + render: function (attrs, options) { + PageView.prototype.render.apply(this, arguments) + let jstreeView = new JSTreeView({ + configKey: "file" }); + app.registerRenderSubview(this, jstreeView, "jstree-view-container"); + // var self = this; + // this.nodeForContextMenu = ""; + // this.renderWithTemplate(); + // this.jstreeIsLoaded = false + // window.addEventListener('pageshow', function (e) { + // var navType = window.performance.navigation.type + // if(navType === 2){ + // window.location.reload() + // } + // }); + // this.setupJstree(function () { + // setTimeout(function () { + // self.refreshInitialJSTree(); + // }, 3000); + // }); $(document).on('hide.bs.modal', '.modal', function (e) { e.target.remove() }); @@ -217,21 +223,21 @@ let FileBrowser = PageView.extend({ changeCollapseButtonText: function (e) { app.changeCollapseButtonText(this, e); }, - refreshJSTree: function () { - this.jstreeIsLoaded = false - $('#models-jstree').jstree().deselect_all(true) - $('#models-jstree').jstree().refresh() - }, - refreshInitialJSTree: function () { - var self = this; - var count = $('#models-jstree').jstree()._model.data['#'].children.length; - if(count == 0) { - self.refreshJSTree(); - setTimeout(function () { - self.refreshInitialJSTree(); - }, 3000); - } - }, + // refreshJSTree: function () { + // this.jstreeIsLoaded = false + // $('#models-jstree').jstree().deselect_all(true) + // $('#models-jstree').jstree().refresh() + // }, + // refreshInitialJSTree: function () { + // var self = this; + // var count = $('#models-jstree').jstree()._model.data['#'].children.length; + // if(count == 0) { + // self.refreshJSTree(); + // setTimeout(function () { + // self.refreshInitialJSTree(); + // }, 3000); + // } + // }, selectNode: function (node, fileName) { let self = this if(!this.jstreeIsLoaded || !$('#models-jstree').jstree().is_loaded(node) && $('#models-jstree').jstree().is_loading(node)) { @@ -648,22 +654,22 @@ let FileBrowser = PageView.extend({ var endpoint = path.join(app.getBasePath(), "/files", targetPath); window.open(endpoint) }, - validateName: function (input, rename = false) { - var error = "" - if(input.endsWith('/')) { - error = 'forward' - } - var invalidChars = "`~!@#$%^&*=+[{]}\"|:;'<,>?\\" - if(rename) { - invalidChars += "/" - } - for(var i = 0; i < input.length; i++) { - if(invalidChars.includes(input.charAt(i))) { - error = error === "" || error === "special" ? "special" : "both" - } - } - return error - }, + // validateName: function (input, rename = false) { + // var error = "" + // if(input.endsWith('/')) { + // error = 'forward' + // } + // var invalidChars = "`~!@#$%^&*=+[{]}\"|:;'<,>?\\" + // if(rename) { + // invalidChars += "/" + // } + // for(var i = 0; i < input.length; i++) { + // if(invalidChars.includes(input.charAt(i))) { + // error = error === "" || error === "special" ? "special" : "both" + // } + // } + // return error + // }, newProjectOrWorkflowGroup: function (o, isProject) { var self = this if(document.querySelector("#newProjectModal")) { @@ -995,15 +1001,15 @@ let FileBrowser = PageView.extend({ setupJstree: function () { var self = this; $.jstree.defaults.contextmenu.items = (o, cb) => { - let optionsButton = $(self.queryByHook("options-for-node")) - if(!self.nodeForContextMenu){ - optionsButton.prop('disabled', false) - } - optionsButton.text("Actions for " + o.original.text) - self.nodeForContextMenu = o; - let nodeType = o.original.type - let zipTypes = ["workflow", "folder", "other", "project", "workflow-group"] - let asZip = zipTypes.includes(nodeType) + // let optionsButton = $(self.queryByHook("options-for-node")) + // if(!self.nodeForContextMenu){ + // optionsButton.prop('disabled', false) + // } + // optionsButton.text("Actions for " + o.original.text) + // self.nodeForContextMenu = o; + // let nodeType = o.original.type + // let zipTypes = ["workflow", "folder", "other", "project", "workflow-group"] + // let asZip = zipTypes.includes(nodeType) // common to all type except root let common = { "Download" : { @@ -1462,32 +1468,32 @@ let FileBrowser = PageView.extend({ return $.extend(open, common) } } - $(document).on('shown.bs.modal', function (e) { - $('[autofocus]', e.target).focus(); - }); - $(document).on('dnd_start.vakata', function (data, element, helper, event) { - $('#models-jstree').jstree().load_all() - }); - $('#models-jstree').jstree(this.treeSettings).bind("loaded.jstree", function (event, data) { - self.jstreeIsLoaded = true - }).bind("refresh.jstree", function (event, data) { - self.jstreeIsLoaded = true - }); - $('#models-jstree').on('click.jstree', function(e) { - var parent = e.target.parentElement - var _node = parent.children[parent.children.length - 1] - var node = $('#models-jstree').jstree().get_node(_node) - if(_node.nodeName === "A" && $('#models-jstree').jstree().is_loaded(node) && node.type === "folder"){ - $('#models-jstree').jstree().refresh_node(node) - }else{ - let optionsButton = $(self.queryByHook("options-for-node")) - if(!self.nodeForContextMenu){ - optionsButton.prop('disabled', false) - } - optionsButton.text("Actions for " + node.original.text) - self.nodeForContextMenu = node; - } - }); + // $(document).on('shown.bs.modal', function (e) { + // $('[autofocus]', e.target).focus(); + // }); + // $(document).on('dnd_start.vakata', function (data, element, helper, event) { + // $('#models-jstree').jstree().load_all() + // }); + // $('#models-jstree').jstree(this.treeSettings).bind("loaded.jstree", function (event, data) { + // self.jstreeIsLoaded = true + // }).bind("refresh.jstree", function (event, data) { + // self.jstreeIsLoaded = true + // }); + // $('#models-jstree').on('click.jstree', function(e) { + // var parent = e.target.parentElement + // var _node = parent.children[parent.children.length - 1] + // var node = $('#models-jstree').jstree().get_node(_node) + // if(_node.nodeName === "A" && $('#models-jstree').jstree().is_loaded(node) && node.type === "folder"){ + // $('#models-jstree').jstree().refresh_node(node) + // }else{ + // let optionsButton = $(self.queryByHook("options-for-node")) + // if(!self.nodeForContextMenu){ + // optionsButton.prop('disabled', false) + // } + // optionsButton.text("Actions for " + node.original.text) + // self.nodeForContextMenu = node; + // } + // }); $('#models-jstree').on('dblclick.jstree', function(e) { var file = e.target.text var node = $('#models-jstree').jstree().get_node(e.target) diff --git a/client/templates/pages/fileBrowser.pug b/client/templates/pages/fileBrowser.pug index 479dac6f58..cf71352bc2 100644 --- a/client/templates/pages/fileBrowser.pug +++ b/client/templates/pages/fileBrowser.pug @@ -59,13 +59,7 @@ section.page div.card-body - div.alert-warning(class="collapse", id="extension-warning" data-hook="extension-warning") You should avoid changing the file extension unless you know what you are doing! - - div.alert-warning(class="collapse", id="rename-warning" data-hook="rename-warning") MESSAGE - - div.alert-danger(id="renameSpecialCharError" style="display: none;") Names can only include the following characters: (0-9), (a-z), (A-Z) and (., -, _, (, or )) - - div#models-jstree + div(data-hook="jstree-view-container") button.btn.btn-primary.inline.box-shadow( id="new-file-directory", diff --git a/client/views/file-browser-view.js b/client/views/file-browser-view.js index 78df88eb52..95ad5a18fa 100644 --- a/client/views/file-browser-view.js +++ b/client/views/file-browser-view.js @@ -16,22 +16,22 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . */ -let jstree = require('jstree'); -let path = require('path'); +// let jstree = require('jstree'); +// let path = require('path'); let $ = require('jquery'); let _ = require('underscore'); //support files -let app = require('../app'); +// let app = require('../app'); let modals = require('../modals'); //models let Model = require('../models/model'); //views -let View = require('ampersand-view'); +// let View = require('ampersand-view'); //templates -let template = require('../templates/includes/fileBrowserView.pug'); +// let template = require('../templates/includes/fileBrowserView.pug'); -module.exports = View.extend({ - template: template, +// module.exports = View.extend({ + // template: template, events: { 'click [data-hook=collapse-browse-files]' : 'changeCollapseButtonText', 'click [data-hook=refresh-jstree]' : 'refreshJSTree', @@ -46,179 +46,179 @@ module.exports = View.extend({ let modal = $(modals.operationInfoModalHtml('file-browser')).modal(); }, }, - initialize: function (attrs, options) { - View.prototype.initialize.apply(this, arguments) - var self = this - this.root = "none" - if(attrs && attrs.root){ - this.root = attrs.root - } - this.ajaxData = { - "url" : function (node) { - if(node.parent === null){ - var endpoint = path.join(app.getApiPath(), "file/browser-list")+"?path="+self.root - if(self.root !== "none") { - endpoint += "&isRoot=True" - } - return endpoint - } - return path.join(app.getApiPath(), "file/browser-list")+"?path="+ node.original._path - }, - "dataType" : "json", - "data" : function (node) { - return { 'id' : node.id} - }, - } - this.treeSettings = { - 'plugins': ['types', 'wholerow', 'changed', 'contextmenu', 'dnd'], - 'core': {'multiple' : false, 'animation': 0, - 'check_callback': function (op, node, par, pos, more) { - if(op === "rename_node" && self.validateName(pos, true) !== ""){ - document.querySelector("#renameSpecialCharError").style.display = "block" - setTimeout(function () { - document.querySelector("#renameSpecialCharError").style.display = "none" - }, 5000) - return false - } - if(op === 'move_node' && more && more.core) { - var newDir = par.original._path !== "/" ? par.original._path : "" - var file = node.original._path.split('/').pop() - var oldPath = node.original._path - let queryStr = "?srcPath="+oldPath+"&dstPath="+path.join(newDir, file) - var endpoint = path.join(app.getApiPath(), "file/move")+queryStr - app.getXHR(endpoint, { - success: function (err, response, body) { - node.original._path = path.join(newDir, file) - if((node.type === "nonspatial" || node.type === "spatial") && (oldPath.includes("trash") || newDir.includes("trash"))) { - self.updateParent("Archive"); - }else if(node.type !== "notebook" || node.original._path.includes(".wkgp") || newDir.includes(".wkgp")) { - self.updateParent(node.type) - } - }, - error: function (err, response, body) { - body = JSON.parse(body) - if(par.type === 'root'){ - $('#models-jstree-view').jstree().refresh() - }else{ - $('#models-jstree-view').jstree().refresh_node(par); - } - } - }); - }else{ - let isMove = op === 'move_node' - let validSrc = Boolean(node && node.type && node.original && node.original.text !== "trash") - let validDst = Boolean(more && more.ref && more.ref.type && more.ref.original) - let validDsts = ["root", "folder"] - let isModel = Boolean(validSrc && (node.type === "nonspatial" || node.type === "spatial")) - let isWorkflow = Boolean(validSrc && node.type === "workflow") - let isWkgp = Boolean(validSrc && node.type === "workflow-group") - let isNotebook = Boolean(validSrc && node.type === "notebook") - let isOther = Boolean(validSrc && !isModel && !isWorkflow && !isWkgp && !isNotebook) - let trashAction = Boolean((validSrc && node.original._path.includes("trash")) || (validDst && more.ref.original.text === "trash")) - // Check if files are being move directly into the trash and remain static with respect to the trash - if(isMove && validDst && path.dirname(more.ref.original._path).includes("trash")) { return false } - if(isMove && validSrc && validDst && node.original._path.includes("trash") && more.ref.original.text === 'trash') { return false } - // Check if workflow is running - if(isMove && isWorkflow && node.original._status && node.original._status === "running") { return false }; - // Check if model, workflow, or workflow group is moving to or from trash - if(isMove && (isModel || isWorkflow) && !trashAction) { return false }; - if(isMove && isWkgp && !(self.parent.model.newFormat && trashAction)) { return false }; - // Check if model, workflow, or workflow group is moving from trash to the correct location - if(isMove && validSrc && node.original._path.includes("trash")) { - if(isWkgp && (!self.parent.model.newFormat || (validDst && more.ref.type !== "root"))) { return false }; - if(isWorkflow && validDst && more.ref.type !== "workflow-group") { return false }; - if(isModel && validDst) { - if(!self.parent.model.newFormat && more.ref.type !== "root") { return false }; - let length = node.original.text.split(".").length; - let modelName = node.original.text.split(".").slice(0, length - 1).join(".") - if(self.parent.model.newFormat && (more.ref.type !== "workflow-group" || !more.ref.original.text.startsWith(modelName))) { return false }; - } - } - // Check if notebook or other file is moving to a valid location. - if(isOther && validDst && !validDsts.includes(more.ref.type)) { return false }; - validDsts.push("workflow-group") - if(isNotebook && validDst && !validDsts.includes(more.ref.type)) { return false }; - if(isMove && validDst && validDsts.includes(more.ref.type)){ - if(!more.ref.state.loaded) { return false }; - var exists = false - var BreakException = {} - var text = node.text - if(!isNaN(text.split(' ').pop().split('.').join(""))){ - text = text.replace(text.split(' ').pop(), '').trim() - } - if(more.ref.text !== "trash"){ - try{ - more.ref.children.forEach(function (child) { - var child_node = $('#models-jstree-view').jstree().get_node(child) - exists = child_node.text === text - if(exists) { throw BreakException; }; - }) - }catch { return false; }; - } - } - if(isMove && more && (pos != 0 || more.pos !== "i") && !more.core) { return false } - return true - } - }, - 'themes': {'stripes': true, 'variant': 'large'}, - 'data': self.ajaxData, - }, - 'types' : { - 'root' : {"icon": "jstree-icon jstree-folder"}, - 'folder' : {"icon": "jstree-icon jstree-folder"}, - 'spatial' : {"icon": "jstree-icon jstree-file"}, - 'nonspatial' : {"icon": "jstree-icon jstree-file"}, - 'project' : {"icon": "jstree-icon jstree-file"}, - 'workflow-group' : {"icon": "jstree-icon jstree-folder"}, - 'workflow' : {"icon": "jstree-icon jstree-file"}, - 'notebook' : {"icon": "jstree-icon jstree-file"}, - 'domain' : {"icon": "jstree-icon jstree-file"}, - 'sbml-model' : {"icon": "jstree-icon jstree-file"}, - 'other' : {"icon": "jstree-icon jstree-file"}, - }, - } - this.setupJstree() - }, - render: function () { - View.prototype.render.apply(this, arguments) + // initialize: function (attrs, options) { + // View.prototype.initialize.apply(this, arguments) + // var self = this + // this.root = "none" + // if(attrs && attrs.root){ + // this.root = attrs.root + // } + // this.ajaxData = { + // "url" : function (node) { + // if(node.parent === null){ + // var endpoint = path.join(app.getApiPath(), "file/browser-list")+"?path="+self.root + // if(self.root !== "none") { + // endpoint += "&isRoot=True" + // } + // return endpoint + // } + // return path.join(app.getApiPath(), "file/browser-list")+"?path="+ node.original._path + // }, + // "dataType" : "json", + // "data" : function (node) { + // return { 'id' : node.id} + // }, + // } + // this.treeSettings = { + // 'plugins': ['types', 'wholerow', 'changed', 'contextmenu', 'dnd'], + // 'core': {'multiple' : false, 'animation': 0, + // 'check_callback': function (op, node, par, pos, more) { + // if(op === "rename_node" && self.validateName(pos, true) !== ""){ + // document.querySelector("#renameSpecialCharError").style.display = "block" + // setTimeout(function () { + // document.querySelector("#renameSpecialCharError").style.display = "none" + // }, 5000) + // return false + // } + // if(op === 'move_node' && more && more.core) { + // var newDir = par.original._path !== "/" ? par.original._path : "" + // var file = node.original._path.split('/').pop() + // var oldPath = node.original._path + // let queryStr = "?srcPath="+oldPath+"&dstPath="+path.join(newDir, file) + // var endpoint = path.join(app.getApiPath(), "file/move")+queryStr + // app.getXHR(endpoint, { + // success: function (err, response, body) { + // node.original._path = path.join(newDir, file) + // if((node.type === "nonspatial" || node.type === "spatial") && (oldPath.includes("trash") || newDir.includes("trash"))) { + // self.updateParent("Archive"); + // }else if(node.type !== "notebook" || node.original._path.includes(".wkgp") || newDir.includes(".wkgp")) { + // self.updateParent(node.type) + // } + // }, + // error: function (err, response, body) { + // body = JSON.parse(body) + // if(par.type === 'root'){ + // $('#models-jstree-view').jstree().refresh() + // }else{ + // $('#models-jstree-view').jstree().refresh_node(par); + // } + // } + // }); + // }else{ + // let isMove = op === 'move_node' + // let validSrc = Boolean(node && node.type && node.original && node.original.text !== "trash") + // let validDst = Boolean(more && more.ref && more.ref.type && more.ref.original) + // let validDsts = ["root", "folder"] + // let isModel = Boolean(validSrc && (node.type === "nonspatial" || node.type === "spatial")) + // let isWorkflow = Boolean(validSrc && node.type === "workflow") + // let isWkgp = Boolean(validSrc && node.type === "workflow-group") + // let isNotebook = Boolean(validSrc && node.type === "notebook") + // let isOther = Boolean(validSrc && !isModel && !isWorkflow && !isWkgp && !isNotebook) + // let trashAction = Boolean((validSrc && node.original._path.includes("trash")) || (validDst && more.ref.original.text === "trash")) + // // Check if files are being move directly into the trash and remain static with respect to the trash + // if(isMove && validDst && path.dirname(more.ref.original._path).includes("trash")) { return false } + // if(isMove && validSrc && validDst && node.original._path.includes("trash") && more.ref.original.text === 'trash') { return false } + // // Check if workflow is running + // if(isMove && isWorkflow && node.original._status && node.original._status === "running") { return false }; + // // Check if model, workflow, or workflow group is moving to or from trash + // if(isMove && (isModel || isWorkflow) && !trashAction) { return false }; + // if(isMove && isWkgp && !(self.parent.model.newFormat && trashAction)) { return false }; + // // Check if model, workflow, or workflow group is moving from trash to the correct location + // if(isMove && validSrc && node.original._path.includes("trash")) { + // if(isWkgp && (!self.parent.model.newFormat || (validDst && more.ref.type !== "root"))) { return false }; + // if(isWorkflow && validDst && more.ref.type !== "workflow-group") { return false }; + // if(isModel && validDst) { + // if(!self.parent.model.newFormat && more.ref.type !== "root") { return false }; + // let length = node.original.text.split(".").length; + // let modelName = node.original.text.split(".").slice(0, length - 1).join(".") + // if(self.parent.model.newFormat && (more.ref.type !== "workflow-group" || !more.ref.original.text.startsWith(modelName))) { return false }; + // } + // } + // // Check if notebook or other file is moving to a valid location. + // if(isOther && validDst && !validDsts.includes(more.ref.type)) { return false }; + // validDsts.push("workflow-group") + // if(isNotebook && validDst && !validDsts.includes(more.ref.type)) { return false }; + // if(isMove && validDst && validDsts.includes(more.ref.type)){ + // if(!more.ref.state.loaded) { return false }; + // var exists = false + // var BreakException = {} + // var text = node.text + // if(!isNaN(text.split(' ').pop().split('.').join(""))){ + // text = text.replace(text.split(' ').pop(), '').trim() + // } + // if(more.ref.text !== "trash"){ + // try{ + // more.ref.children.forEach(function (child) { + // var child_node = $('#models-jstree-view').jstree().get_node(child) + // exists = child_node.text === text + // if(exists) { throw BreakException; }; + // }) + // }catch { return false; }; + // } + // } + // if(isMove && more && (pos != 0 || more.pos !== "i") && !more.core) { return false } + // return true + // } + // }, + // 'themes': {'stripes': true, 'variant': 'large'}, + // 'data': self.ajaxData, + // }, + // 'types' : { + // 'root' : {"icon": "jstree-icon jstree-folder"}, + // 'folder' : {"icon": "jstree-icon jstree-folder"}, + // 'spatial' : {"icon": "jstree-icon jstree-file"}, + // 'nonspatial' : {"icon": "jstree-icon jstree-file"}, + // 'project' : {"icon": "jstree-icon jstree-file"}, + // 'workflow-group' : {"icon": "jstree-icon jstree-folder"}, + // 'workflow' : {"icon": "jstree-icon jstree-file"}, + // 'notebook' : {"icon": "jstree-icon jstree-file"}, + // 'domain' : {"icon": "jstree-icon jstree-file"}, + // 'sbml-model' : {"icon": "jstree-icon jstree-file"}, + // 'other' : {"icon": "jstree-icon jstree-file"}, + // }, + // } + // this.setupJstree() + // }, + // render: function () { + // View.prototype.render.apply(this, arguments) var self = this; - this.nodeForContextMenu = ""; - this.jstreeIsLoaded = false - window.addEventListener('pageshow', function (e) { - var navType = window.performance.navigation.type - if(navType === 2){ - window.location.reload() - } - }); - }, - updateParent: function (type) { - let models = ["nonspatial", "spatial", "sbml", "model"] - let workflows = ["workflow", "notebook"] - if(models.includes(type)) { - this.parent.update("Model") - }else if(workflows.includes(type)) { - this.parent.update("Workflow") - }else if(type === "workflow-group") { - this.parent.update("WorkflowGroup") - }else if(type === "Archive") { - this.parent.update(type); - } - }, - refreshJSTree: function () { - this.jstreeIsLoaded = false - $('#models-jstree-view').jstree().deselect_all(true) - $('#models-jstree-view').jstree().refresh() - }, - refreshInitialJSTree: function () { - var self = this; - var count = $('#models-jstree-view').jstree()._model.data['#'].children.length; - if(count == 0) { - self.refreshJSTree(); - setTimeout(function () { - self.refreshInitialJSTree(); - }, 3000); - } - }, + // this.nodeForContextMenu = ""; + // this.jstreeIsLoaded = false + // window.addEventListener('pageshow', function (e) { + // var navType = window.performance.navigation.type + // if(navType === 2){ + // window.location.reload() + // } + // }); + // }, + // updateParent: function (type) { + // let models = ["nonspatial", "spatial", "sbml", "model"] + // let workflows = ["workflow", "notebook"] + // if(models.includes(type)) { + // this.parent.update("Model") + // }else if(workflows.includes(type)) { + // this.parent.update("Workflow") + // }else if(type === "workflow-group") { + // this.parent.update("WorkflowGroup") + // }else if(type === "Archive") { + // this.parent.update(type); + // } + // }, + // refreshJSTree: function () { + // this.jstreeIsLoaded = false + // $('#models-jstree-view').jstree().deselect_all(true) + // $('#models-jstree-view').jstree().refresh() + // }, + // refreshInitialJSTree: function () { + // var self = this; + // var count = $('#models-jstree-view').jstree()._model.data['#'].children.length; + // if(count == 0) { + // self.refreshJSTree(); + // setTimeout(function () { + // self.refreshInitialJSTree(); + // }, 3000); + // } + // }, selectNode: function (node, fileName) { let self = this if(!this.jstreeIsLoaded || !$('#models-jstree-view').jstree().is_loaded(node) && $('#models-jstree-view').jstree().is_loading(node)) { @@ -634,22 +634,22 @@ module.exports = View.extend({ var endpoint = path.join(app.getBasePath(), "/files", targetPath); window.open(endpoint) }, - validateName(input, rename = false) { - var error = "" - if(input.endsWith('/')) { - error = 'forward' - } - var invalidChars = "`~!@#$%^&*=+[{]}\"|:;'<,>?\\" - if(rename) { - invalidChars += "/" - } - for(var i = 0; i < input.length; i++) { - if(invalidChars.includes(input.charAt(i))) { - error = error === "" || error === "special" ? "special" : "both" - } - } - return error - }, + // validateName(input, rename = false) { + // var error = "" + // if(input.endsWith('/')) { + // error = 'forward' + // } + // var invalidChars = "`~!@#$%^&*=+[{]}\"|:;'<,>?\\" + // if(rename) { + // invalidChars += "/" + // } + // for(var i = 0; i < input.length; i++) { + // if(invalidChars.includes(input.charAt(i))) { + // error = error === "" || error === "special" ? "special" : "both" + // } + // } + // return error + // }, newWorkflowGroup: function (o) { var self = this if(document.querySelector("#newWorkflowGroupModal")) { @@ -988,12 +988,12 @@ module.exports = View.extend({ } }); }, - setupJstree: function () { + // setupJstree: function () { var self = this; - $.jstree.defaults.contextmenu.items = (o, cb) => { - let nodeType = o.original.type - let zipTypes = ["workflow", "folder", "other", "root", "workflow-group"] - let asZip = zipTypes.includes(nodeType) + // $.jstree.defaults.contextmenu.items = (o, cb) => { + // let nodeType = o.original.type + // let zipTypes = ["workflow", "folder", "other", "root", "workflow-group"] + // let asZip = zipTypes.includes(nodeType) // refresh context menu option let refresh = { "Refresh" : { @@ -1473,63 +1473,63 @@ module.exports = View.extend({ return $.extend(open, common) } } - $(document).ready(function () { - $(document).on('shown.bs.modal', function (e) { - $('[autofocus]', e.target).focus(); - }); - $(document).on('dnd_start.vakata', function (data, element, helper, event) { - $('#models-jstree-view').jstree().load_all() - }); - $('#models-jstree-view').jstree(self.treeSettings).bind("loaded.jstree", function (event, data) { - self.jstreeIsLoaded = true - }).bind("refresh.jstree", function (event, data) { - self.jstreeIsLoaded = true - }); - $('#models-jstree-view').on('click.jstree', function(e) { - var parent = e.target.parentElement - var _node = parent.children[parent.children.length - 1] - var node = $('#models-jstree-view').jstree().get_node(_node) - if(_node.nodeName === "A" && $('#models-jstree-view').jstree().is_loaded(node) && node.type === "folder"){ - $('#models-jstree-view').jstree().refresh_node(node) - }else{ - let optionsButton = $(self.queryByHook("options-for-node")) - if(!self.nodeForContextMenu){ - optionsButton.prop('disabled', false) - } - optionsButton.text("Actions for " + node.original.text) - self.nodeForContextMenu = node; - } - }); - $('#models-jstree-view').on('dblclick.jstree', function(e) { - var file = e.target.text - var node = $('#models-jstree-view').jstree().get_node(e.target) - var _path = node.original._path; - if(!_path.includes(".proj/trash/")){ - if(file.endsWith('.mdl') || file.endsWith('.smdl')){ - window.location.href = path.join(app.getBasePath(), "stochss/models/edit")+"?path="+_path; - }else if(file.endsWith('.ipynb')){ - var notebookPath = path.join(app.getBasePath(), "notebooks", _path) - window.open(notebookPath, '_blank') - }else if(file.endsWith('.sbml')){ - var openPath = path.join(app.getBasePath(), "edit", _path) - window.open(openPath, '_blank') - }else if(file.endsWith('.proj')){ - window.location.href = path.join(app.getBasePath(), "stochss/project/manager")+"?path="+_path; - }else if(file.endsWith('.wkfl')){ - window.location.href = path.join(app.getBasePath(), "stochss/workflow/edit")+"?path="+_path+"&type=none"; - }else if(file.endsWith('.domn')) { - let queryStr = "?domainPath=" + _path - window.location.href = path.join(app.getBasePath(), "stochss/domain/edit") + queryStr - }else if(node.type === "folder" && $('#models-jstree-view').jstree().is_open(node) && $('#models-jstree-view').jstree().is_loaded(node)){ - $('#models-jstree-view').jstree().refresh_node(node) - }else if(node.type === "other"){ - var openPath = path.join(app.getBasePath(), "view", _path); - window.open(openPath, "_blank"); - } - } - }); - }) - }, + // $(document).ready(function () { + // $(document).on('shown.bs.modal', function (e) { + // $('[autofocus]', e.target).focus(); + // }); + // $(document).on('dnd_start.vakata', function (data, element, helper, event) { + // $('#models-jstree-view').jstree().load_all() + // }); + // $('#models-jstree-view').jstree(self.treeSettings).bind("loaded.jstree", function (event, data) { + // self.jstreeIsLoaded = true + // }).bind("refresh.jstree", function (event, data) { + // self.jstreeIsLoaded = true + // }); + // $('#models-jstree-view').on('click.jstree', function(e) { + // var parent = e.target.parentElement + // var _node = parent.children[parent.children.length - 1] + // var node = $('#models-jstree-view').jstree().get_node(_node) + // if(_node.nodeName === "A" && $('#models-jstree-view').jstree().is_loaded(node) && node.type === "folder"){ + // $('#models-jstree-view').jstree().refresh_node(node) + // }else{ + // let optionsButton = $(self.queryByHook("options-for-node")) + // if(!self.nodeForContextMenu){ + // optionsButton.prop('disabled', false) + // } + // optionsButton.text("Actions for " + node.original.text) + // self.nodeForContextMenu = node; + // } + // }); + // $('#models-jstree-view').on('dblclick.jstree', function(e) { + // var file = e.target.text + // var node = $('#models-jstree-view').jstree().get_node(e.target) + // var _path = node.original._path; + // if(!_path.includes(".proj/trash/")){ + // if(file.endsWith('.mdl') || file.endsWith('.smdl')){ + // window.location.href = path.join(app.getBasePath(), "stochss/models/edit")+"?path="+_path; + // }else if(file.endsWith('.ipynb')){ + // var notebookPath = path.join(app.getBasePath(), "notebooks", _path) + // window.open(notebookPath, '_blank') + // }else if(file.endsWith('.sbml')){ + // var openPath = path.join(app.getBasePath(), "edit", _path) + // window.open(openPath, '_blank') + // }else if(file.endsWith('.proj')){ + // window.location.href = path.join(app.getBasePath(), "stochss/project/manager")+"?path="+_path; + // }else if(file.endsWith('.wkfl')){ + // window.location.href = path.join(app.getBasePath(), "stochss/workflow/edit")+"?path="+_path+"&type=none"; + // }else if(file.endsWith('.domn')) { + // let queryStr = "?domainPath=" + _path + // window.location.href = path.join(app.getBasePath(), "stochss/domain/edit") + queryStr + // }else if(node.type === "folder" && $('#models-jstree-view').jstree().is_open(node) && $('#models-jstree-view').jstree().is_loaded(node)){ + // $('#models-jstree-view').jstree().refresh_node(node) + // }else if(node.type === "other"){ + // var openPath = path.join(app.getBasePath(), "view", _path); + // window.open(openPath, "_blank"); + // } + // } + // }); + // }) + // }, changeCollapseButtonText: function (e) { app.changeCollapseButtonText(this, e); } From 01daa8027f699edafb2d28dea6505d7e334f2605 Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Wed, 25 Aug 2021 08:19:36 -0400 Subject: [PATCH 103/186] Removed the post process csv files from the get full results as csv functionality. --- jupyterhub/job_presentation.py | 75 -------------------------- stochss/handlers/util/stochss_job.py | 81 ---------------------------- 2 files changed, 156 deletions(-) diff --git a/jupyterhub/job_presentation.py b/jupyterhub/job_presentation.py index b5ca5f6858..75b22f5470 100644 --- a/jupyterhub/job_presentation.py +++ b/jupyterhub/job_presentation.py @@ -25,7 +25,6 @@ import tempfile import traceback -from itertools import combinations from pathlib import Path import numpy @@ -273,40 +272,6 @@ def __init__(self, path): super().__init__(path=path) - @classmethod - def __get_1d_fixed_list(cls, results): - keys = [key.split(',') for key in results.keys()] - p_names = [param.split(':')[0] for param in keys[0]] - if len(p_names) < 2: - return [{}] - _f_keys = [] - for name in p_names: - for key in keys: - f_key = list(filter(lambda key_el, key=key, name=name: name not in key_el, key)) - _f_keys.append(",".join(f_key)) - fixed_list = [key.split(',') for key in sorted(list(set(_f_keys)))] - return fixed_list - - - @classmethod - def __get_2d_fixed_list(cls, results): - keys = [key.split(',') for key in results.keys()] - p_names = [param.split(':')[0] for param in keys[0]] - if len(p_names) < 2: - return None - if len(p_names) < 3: - return [{}] - p_names = list(combinations(p_names, 2)) - _f_keys = [] - for names in p_names: - for key in keys: - test = lambda key_el, names=names: names[0] not in key_el and names[1] not in key_el - f_key = list(filter(lambda key_el, test=test: test(key_el), key)) - _f_keys.append(",".join(f_key)) - fixed_list = [key.split(',') for key in sorted(list(set(_f_keys)))] - return fixed_list - - @classmethod def __get_csvzip(cls, dirname, name): shutil.make_archive(os.path.join(dirname, name), "zip", dirname, name) @@ -346,44 +311,6 @@ def __get_filtered_ensemble_results(self, data_keys): return result - def __get_full_1dpsweep_csv(self, b_path, results, get_name, name): - settings = self.load()['settings'] - od_path = os.path.join(b_path, "1D_Resutls") - os.mkdir(od_path) - fixed_list = self.__get_1d_fixed_list(results) - for i, fixed in enumerate(fixed_list): - d_keys = dict([key.split(":") for key in fixed]) - param = list(filter(lambda param, d_keys=d_keys: param['name'] not in d_keys.keys(), - settings['parameterSweepSettings']['parameters']))[0] - kwargs = {'results': self.__get_filtered_1d_results(fixed)} - kwargs["species"] = list(kwargs['results'][0][0].model.listOfSpecies.keys()) - ParameterSweep1D.to_csv( - param=param, kwargs=kwargs, path=od_path, nametag=get_name(name, i) - ) - if fixed: - self.__write_parameters_csv(path=od_path, name=get_name(name, i), data_keys=d_keys) - - - def __get_full_2dpsweep_csv(self, b_path, results, get_name, name): - fixed_list = self.__get_2d_fixed_list(results) - if fixed_list is None: - return - settings = self.load()['settings'] - td_path = os.path.join(b_path, "2D_Resutls") - os.mkdir(td_path) - for i, fixed in enumerate(fixed_list): - d_keys = dict([key.split(":") for key in fixed]) - params = list(filter(lambda param, d_keys=d_keys: param['name'] not in d_keys.keys(), - settings['parameterSweepSettings']['parameters'])) - kwargs = {"results": self.__get_filtered_2d_results(fixed, params[0])} - kwargs["species"] = list(kwargs['results'][0][0][0].model.listOfSpecies.keys()) - ParameterSweep2D.to_csv( - params=params, kwargs=kwargs, path=td_path, nametag=get_name(name, i) - ) - if fixed: - self.__write_parameters_csv(path=td_path, name=get_name(name, i), data_keys=d_keys) - - def __get_full_timeseries_csv(self, b_path, results, get_name, name): ts_path = os.path.join(b_path, "Time_Series") os.makedirs(ts_path) @@ -487,8 +414,6 @@ def get_name(b_name, tag): return f"{b_name}_{tag}" b_path = os.path.join(tmp_dir.name, get_name(name, "full")) self.__get_full_timeseries_csv(b_path, results, get_name, name) - self.__get_full_1dpsweep_csv(b_path, results, get_name, name) - self.__get_full_2dpsweep_csv(b_path, results, get_name, name) return self.__get_csvzip(dirname=tmp_dir.name, name=get_name(name, "full")) diff --git a/stochss/handlers/util/stochss_job.py b/stochss/handlers/util/stochss_job.py index 7e612c1810..6675e34a29 100644 --- a/stochss/handlers/util/stochss_job.py +++ b/stochss/handlers/util/stochss_job.py @@ -27,7 +27,6 @@ import datetime import traceback -from itertools import combinations import numpy import plotly @@ -109,40 +108,6 @@ def __create_settings(self): self.__get_settings_path(full=True)) - @classmethod - def __get_1d_fixed_list(cls, results): - keys = [key.split(',') for key in results.keys()] - p_names = [param.split(':')[0] for param in keys[0]] - if len(p_names) < 2: - return [{}] - _f_keys = [] - for name in p_names: - for key in keys: - f_key = list(filter(lambda key_el, key=key, name=name: name not in key_el, key)) - _f_keys.append(",".join(f_key)) - fixed_list = [key.split(',') for key in sorted(list(set(_f_keys)))] - return fixed_list - - - @classmethod - def __get_2d_fixed_list(cls, results): - keys = [key.split(',') for key in results.keys()] - p_names = [param.split(':')[0] for param in keys[0]] - if len(p_names) < 2: - return None - if len(p_names) < 3: - return [{}] - p_names = list(combinations(p_names, 2)) - _f_keys = [] - for names in p_names: - for key in keys: - test = lambda key_el, names=names: names[0] not in key_el and names[1] not in key_el - f_key = list(filter(lambda key_el, test=test: test(key_el), key)) - _f_keys.append(",".join(f_key)) - fixed_list = [key.split(',') for key in sorted(list(set(_f_keys)))] - return fixed_list - - @classmethod def __get_csvzip(cls, dirname, name): shutil.make_archive(os.path.join(dirname, name), "zip", dirname, name) @@ -203,48 +168,6 @@ def __get_filtered_ensemble_results(self, data_keys): return result - def __get_full_1dpsweep_csv(self, b_path, results, get_name, name): - settings = self.load_settings() - od_path = os.path.join(b_path, "1D_Resutls") - os.mkdir(od_path) - fixed_list = self.__get_1d_fixed_list(results) - for i, fixed in enumerate(fixed_list): - nametag = get_name(name, i) - self.log("info", f"\tGenerating CSV files for {nametag}...") - d_keys = dict([key.split(":") for key in fixed]) - param = list(filter(lambda param, d_keys=d_keys: param['name'] not in d_keys.keys(), - settings['parameterSweepSettings']['parameters']))[0] - kwargs = {'results': self.__get_filtered_1d_results(fixed)} - kwargs["species"] = list(kwargs['results'][0][0].model.listOfSpecies.keys()) - ParameterSweep1D.to_csv( - param=param, kwargs=kwargs, path=od_path, nametag=nametag - ) - if fixed: - self.__write_parameters_csv(path=od_path, name=nametag, data_keys=d_keys) - - - def __get_full_2dpsweep_csv(self, b_path, results, get_name, name): - fixed_list = self.__get_2d_fixed_list(results) - if fixed_list is None: - return - settings = self.load_settings() - td_path = os.path.join(b_path, "2D_Resutls") - os.mkdir(td_path) - for i, fixed in enumerate(fixed_list): - nametag = get_name(name, i) - self.log("info", f"\tGenerating CSV files for {nametag}...") - d_keys = dict([key.split(":") for key in fixed]) - params = list(filter(lambda param, d_keys=d_keys: param['name'] not in d_keys.keys(), - settings['parameterSweepSettings']['parameters'])) - kwargs = {"results": self.__get_filtered_2d_results(fixed, params[0])} - kwargs["species"] = list(kwargs['results'][0][0][0].model.listOfSpecies.keys()) - ParameterSweep2D.to_csv( - params=params, kwargs=kwargs, path=td_path, nametag=nametag - ) - if fixed: - self.__write_parameters_csv(path=td_path, name=nametag, data_keys=d_keys) - - def __get_full_timeseries_csv(self, b_path, results, get_name, name): ts_path = os.path.join(b_path, "Time_Series") os.makedirs(ts_path) @@ -466,10 +389,6 @@ def get_name(b_name, tag): b_path = os.path.join(tmp_dir.name, get_name(name, "full")) self.log("info", "Generating time series CSV files...") self.__get_full_timeseries_csv(b_path, results, get_name, name) - self.log("info", "Generating CSV files for 1D results...") - self.__get_full_1dpsweep_csv(b_path, results, get_name, name) - self.log("info", "Generating CSV files for 2D results...") - self.__get_full_2dpsweep_csv(b_path, results, get_name, name) return self.__get_csvzip(dirname=tmp_dir.name, name=get_name(name, "full")) From 6fe2e04b37a01b036ddb5351223686f14117ff32 Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Wed, 25 Aug 2021 13:29:29 -0400 Subject: [PATCH 104/186] Added missing arg for validate move. --- client/file-config.js | 2 +- client/project-config.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/client/file-config.js b/client/file-config.js index 13c0ac6ddf..bf3c082bcd 100644 --- a/client/file-config.js +++ b/client/file-config.js @@ -89,7 +89,7 @@ types = { 'other' : {"icon": "jstree-icon jstree-file"}, } -validateMove = (view, node, more) => { +validateMove = (view, node, more, pos) => { // Check if workflow is running let validSrc = Boolean(node && node.type && node.original && node.original.text !== "trash"); let isWorkflow = Boolean(validSrc && node.type === "workflow"); diff --git a/client/project-config.js b/client/project-config.js index 3e7c95c00c..b880d876db 100644 --- a/client/project-config.js +++ b/client/project-config.js @@ -96,7 +96,7 @@ updateParent = (view, type) => { } } -validateMove = (view, node, more) => { +validateMove = (view, node, more, pos) => { // Check if files are being move directly into the trash and remain static with respect to the trash let validDst = Boolean(more && more.ref && more.ref.type && more.ref.original); if(validDst && path.dirname(more.ref.original._path).includes("trash")) { return false }; From 66e02dda0be53177826e53d2ae8f4025acf7220f Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Wed, 25 Aug 2021 13:31:05 -0400 Subject: [PATCH 105/186] Added the file browser buttons. --- client/templates/includes/jstreeView.pug | 32 ++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/client/templates/includes/jstreeView.pug b/client/templates/includes/jstreeView.pug index 83cc1cf796..5b33ca6133 100644 --- a/client/templates/includes/jstreeView.pug +++ b/client/templates/includes/jstreeView.pug @@ -1,9 +1,37 @@ div - div.alert-warning(class="collapse", id="extension-warning" data-hook="extension-warning") You should avoid changing the file extension unless you know what you are doing! + div.alert-warning(class="collapse" id="extension-warning" data-hook="extension-warning") You should avoid changing the file extension unless you know what you are doing! div.alert-warning(class="collapse", id="rename-warning" data-hook="rename-warning") MESSAGE div.alert-danger(id="renameSpecialCharError" style="display: none;") Names can only include the following characters: (0-9), (a-z), (A-Z) and (., -, _, (, or )) - div#files-jstree \ No newline at end of file + div#files-jstree + + div + + button.btn.btn-primary.inline.box-shadow( + id="files-add-btn", + data-hook="files-add-btn", + data-toggle="dropdown", + aria-haspopup="true", + aria-expanded="false", + type="button" + ) + + + ul.dropdown-menu(aria-labelledby="files-add-btn") + li.dropdown-item(id="new-directory" data-hook="new-directory") Create Directory + li.dropdown-item(id="new-project" data-hook="new-project") Create Project + li.dropdown-item(id="new-model" data-hook="new-model" data-type="model") Create Model + li.dropdown-item(id="new-model" data-hook="new-model" data-type="spatial") Create Spatial Model (beta) + li.dropdown-item(id="new-model" data-hook="new-domain") Create Domain (beta) + li.dropdown-divider + li.dropdown-item(id="upload-file-btn" data-hook="upload-file-btn" data-type="model") Upload StochSS Model + li.dropdown-item(id="upload-file-btn" data-hook="upload-file-btn" data-type="sbml") Upload SBML Model + li.dropdown-item(id="upload-file-btn" data-hook="upload-file-btn" data-type="file") Upload File + + button.btn.btn-primary.inline.box-shadow(id="options-for-node" data-hook="options-for-node" disabled) Actions + + button.btn.btn-primary.inline.box-shadow(id="refresh-jstree" data-hook="refresh-jstree") Refresh + + button.btn.btn-primary.inline.box-shadow(id="empty-trash" data-hook="empty-trash") Empty Trash From a21f6f0dd8d0535de7dfc3af5a0575e5d37e22cf Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Wed, 25 Aug 2021 13:32:07 -0400 Subject: [PATCH 106/186] Added functions for creating directories and models. --- client/views/jstree-view.js | 128 ++++++++++++++++++++++++++++++++++-- 1 file changed, 124 insertions(+), 4 deletions(-) diff --git a/client/views/jstree-view.js b/client/views/jstree-view.js index 312740aa7a..cd60c2874d 100644 --- a/client/views/jstree-view.js +++ b/client/views/jstree-view.js @@ -21,6 +21,7 @@ let path = require('path'); let jstree = require('jstree'); //support files let app = require('../app'); +let modals = require('../modals'); //config let FileConfig = require('../file-config') let ProjectConfig = require('../project-config'); @@ -31,6 +32,12 @@ let template = require('../templates/includes/jstreeView.pug'); module.exports = View.extend({ template: template, + events: { + 'click [data-hook=new-directory]' : 'handleCreateDirectoryClick', + 'click [data-hook=new-project]' : 'handleCreateProjectClick', + 'click [data-hook=new-model]' : 'handleCreateModelClick', + 'click [data-hook=new-domain]' : 'handleCreateDomain', + }, initialize: function (attrs, options) { View.prototype.initialize.apply(this, arguments); this.config = attrs.configKey === "file" ? FileConfig : ProjectConfig; @@ -60,7 +67,7 @@ module.exports = View.extend({ 'multiple': false, 'animation': 0, 'check_callback': (op, node, par, pos, more) => { - if(op === "rename_node" && this.validateName(pos, true) !== ""){ + if(op === "rename_node" && this.validateName(pos, {rename: true}) !== ""){ let err = $("#renameSpecialCharError"); err.css("display", "block"); setTimeout(() => { @@ -71,7 +78,7 @@ module.exports = View.extend({ if(op === 'move_node' && more && more.core) { this.config.move(this, par, node); }else if(op === "move_node") { - return this.config.validateMove(this, node, more); + return this.config.validateMove(this, node, more, pos); } }, 'themes': {'stripes': true, 'variant': 'large'}, @@ -94,6 +101,118 @@ module.exports = View.extend({ }, 3000); }); }, + addInputKeyupEvent: function (input, okBtn) { + input.addEventListener("keyup", (event) => { + if(event.keyCode === 13){ + event.preventDefault(); + okBtn.click(); + } + }); + }, + addInputValidateEvent: function (input, okBtn, modalID, inputID, {inProject = false}={}) { + input.addEventListener("input", (e) => { + let endErrMsg = document.querySelector(`${modalID} ${inputID}EndCharError`); + let charErrMsg = document.querySelector(`${modalID} ${inputID}SpecCharError`); + let error = this.validateName(input.value, {saveAs: !inProject}); + okBtn.disabled = error !== "" || input.value.trim() === ""; + charErrMsg.style.display = error === "both" || error === "special" ? "block" : "none"; + endErrMsg.style.display = error === "both" || error === "forward" ? "block" : "none"; + }); + }, + addModel: function (dirname, file) { + let queryStr = "?path=" + path.join(dirname, file); + let existEP = path.join(app.getApiPath(), "model/exists") + queryStr; + app.getXHR(existEP, { + always: (err, response, body) => { + if(body.exists) { + if(document.querySelector("#errorModal")) { + document.querySelector("#errorModal").remove(); + } + let title = "Model Already Exists"; + let message = "A model already exists with that name"; + let errorModel = $(modals.errorHtml(title, message)).modal(); + }else{ + let endpoint = path.join(app.getBasePath(), "stochss/models/edit") + queryStr; + window.location.href = endpoint; + } + } + }); + }, + addProjectModel: function (dirname, file) { + let queryStr = "?path=" + dirname + "&mdlFile=" + file; + let newMdlEP = path.join(app.getApiPath(), "project/new-model") + queryStr; + app.getXHR(newMdlEP, { + success: (err, response, body) => { + let endpoint = path.join(app.getBasePath(), "stochss/models/edit") + "?path=" + body.path; + window.location.href = endpoint; + }, + error: (err, response, body) => { + if(document.querySelector("#errorModal")) { + document.querySelector("#errorModal").remove(); + } + let title = "Model Already Exists"; + let message = "A model already exists with that name"; + let errorModel = $(modals.errorHtml(title, message)).modal(); + } + }); + }, + createDirectory: function (node, dirname) { + if(document.querySelector('#newDirectoryModal')) { + document.querySelector('#newDirectoryModal').remove(); + } + let modal = $(modals.createDirectoryHtml()).modal(); + let okBtn = document.querySelector('#newDirectoryModal .ok-model-btn'); + let input = document.querySelector('#newDirectoryModal #directoryNameInput'); + this.addInputKeyupEvent(input, okBtn); + this.addInputValidateEvent(input, okBtn, "#newDirectoryModal", "#directoryNameInput"); + okBtn.addEventListener('click', (e) => { + modal.modal('hide'); + let queryStr = "?path=" + path.join(dirname, input.value.trim()); + let endpoint = path.join(app.getApiPath(), "directory/create") + queryStr; + app.getXHR(endpoint, { + success: (err, response, body) => { + this.refreshJSTree(node) + }, + error: (err, response, body) => { + if(document.querySelector("#errorModal")) { + document.querySelector("#errorModal").remove(); + } + body = JSON.parse(body); + let errorModal = $(modals.errorHtml(body.Reason, body.Message)).modal(); + } + }); + }); + }, + createModel: function (node, dirname, isSpatial, inProject) { + if(document.querySelector('#newModelModal')) { + document.querySelector('#newModelModal').remove(); + } + let modal = $(modals.createModelHtml(isSpatial)).modal(); + let okBtn = document.querySelector('#newModelModal .ok-model-btn'); + let input = document.querySelector('#newModelModal #modelNameInput'); + this.addInputKeyupEvent(input, okBtn); + this.addInputValidateEvent(input, okBtn, "#newModelModal", "#modelNameInput", {inProject: inProject}); + okBtn.addEventListener('click', (e) => { + modal.modal('hide'); + let ext = isSpatial ? "smdl" : "mdl"; + let file = `${input.value.trim()}.${ext}`; + if(inProject) { + this.addProjectModel(dirname, file); + }else{ + this.addModel(dirname, file); + } + }); + }, + handleCreateDirectoryClick: function (e) { + let dirname = this.root === "none" ? "" : this.root; + this.createDirectory(null, dirname); + }, + handleCreateModelClick: function (e) { + let dirname = this.root === "none" ? "" : this.root; + let inProject = this.root !== "none"; + let isSpatial = e.target.dataset.type === "spatial"; + this.createModel(null, dirname, isSpatial, inProject); + }, refreshInitialJSTree: function () { let count = $('#files-jstree').jstree()._model.data['#'].children.length; if(count == 0) { @@ -124,6 +243,7 @@ module.exports = View.extend({ optionsButton.text("Actions for " + o.original.text); this.nodeForContextMenu = o; } + $(document).ready(() => { $(document).on('shown.bs.modal', (e) => { $('[autofocus]', e.target).focus(); @@ -156,13 +276,13 @@ module.exports = View.extend({ }); }); }, - validateName: function (input, rename = false) { + validateName: function (input, {rename = false, saveAs = true}={}) { var error = "" if(input.endsWith('/')) { error = 'forward' } var invalidChars = "`~!@#$%^&*=+[{]}\"|:;'<,>?\\" - if(rename) { + if(rename || !saveAs) { invalidChars += "/" } for(var i = 0; i < input.length; i++) { From ce1ae5b2587c403d4380635f996addfe462f3440 Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Wed, 25 Aug 2021 15:19:05 -0400 Subject: [PATCH 107/186] Added setup function to the configs to hide unique template elements. --- client/file-config.js | 10 ++++++++-- client/project-config.js | 13 ++++++++++--- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/client/file-config.js b/client/file-config.js index bf3c082bcd..09d2a96ce3 100644 --- a/client/file-config.js +++ b/client/file-config.js @@ -76,7 +76,12 @@ let move = (view, par, node) => { }); } -types = { +let setup = (view) => { + $(view.queryByHook("fb-proj-seperator")).css("display", "none"); + $(view.queryByHook("fb-add-existing-model")).css("display", "none"); +} + +let types = { 'root' : {"icon": "jstree-icon jstree-folder"}, 'folder' : {"icon": "jstree-icon jstree-folder"}, 'spatial' : {"icon": "jstree-icon jstree-file"}, @@ -89,7 +94,7 @@ types = { 'other' : {"icon": "jstree-icon jstree-file"}, } -validateMove = (view, node, more, pos) => { +let validateMove = (view, node, more, pos) => { // Check if workflow is running let validSrc = Boolean(node && node.type && node.original && node.original.text !== "trash"); let isWorkflow = Boolean(validSrc && node.type === "workflow"); @@ -123,6 +128,7 @@ module.exports = { contextZipTypes: contextZipTypes, doubleClick: doubleClick, move: move, + setup: setup, types: types, validateMove: validateMove } diff --git a/client/project-config.js b/client/project-config.js index b880d876db..cfe488674a 100644 --- a/client/project-config.js +++ b/client/project-config.js @@ -69,7 +69,12 @@ let move = (view, par, node) => { }); } -types = { +let setup = (view) => { + $(view.queryByHook("fb-new-project")).css("display", "none"); + $(view.queryByHook("fb-empty-trash")).css("display", "none"); +} + +let types = { 'root' : {"icon": "jstree-icon jstree-folder"}, 'folder' : {"icon": "jstree-icon jstree-folder"}, 'spatial' : {"icon": "jstree-icon jstree-file"}, @@ -82,7 +87,7 @@ types = { 'other' : {"icon": "jstree-icon jstree-file"}, } -updateParent = (view, type) => { +let updateParent = (view, type) => { let models = ["nonspatial", "spatial", "sbml", "model"]; let workflows = ["workflow", "notebook"]; if(models.includes(type)) { @@ -96,7 +101,7 @@ updateParent = (view, type) => { } } -validateMove = (view, node, more, pos) => { +let validateMove = (view, node, more, pos) => { // Check if files are being move directly into the trash and remain static with respect to the trash let validDst = Boolean(more && more.ref && more.ref.type && more.ref.original); if(validDst && path.dirname(more.ref.original._path).includes("trash")) { return false }; @@ -162,6 +167,8 @@ module.exports = { contextZipTypes: contextZipTypes, doubleClick: doubleClick, move: move, + setup: setup, types: types, + updateParent: updateParent, validateMove: validateMove } From 3ee3f942f380aa5334d532ae7643586c91cda187 Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Wed, 25 Aug 2021 15:26:16 -0400 Subject: [PATCH 108/186] Added functions for creating projects and domains. Added function to import a model into a project. --- client/views/jstree-view.js | 122 +++++++++++++++++++++++++++++++----- 1 file changed, 108 insertions(+), 14 deletions(-) diff --git a/client/views/jstree-view.js b/client/views/jstree-view.js index cd60c2874d..79f1abd90d 100644 --- a/client/views/jstree-view.js +++ b/client/views/jstree-view.js @@ -33,10 +33,11 @@ let template = require('../templates/includes/jstreeView.pug'); module.exports = View.extend({ template: template, events: { - 'click [data-hook=new-directory]' : 'handleCreateDirectoryClick', - 'click [data-hook=new-project]' : 'handleCreateProjectClick', - 'click [data-hook=new-model]' : 'handleCreateModelClick', - 'click [data-hook=new-domain]' : 'handleCreateDomain', + 'click [data-hook=fb-new-directory]' : 'handleCreateDirectoryClick', + 'click [data-hook=fb-new-project]' : 'handleCreateProjectClick', + 'click [data-hook=fb-new-model]' : 'handleCreateModelClick', + 'click [data-hook=fb-new-domain]' : 'handleCreateDomain', + 'click [data-hook=fb-import-model]' : 'handleImportModelClick', }, initialize: function (attrs, options) { View.prototype.initialize.apply(this, arguments); @@ -89,6 +90,7 @@ module.exports = View.extend({ }, render: function (attrs, options) { View.prototype.render.apply(this, arguments); + this.config.setup(this); window.addEventListener('pageshow', function (e) { let navType = window.performance.navigation.type; if(navType === 2){ @@ -160,7 +162,7 @@ module.exports = View.extend({ if(document.querySelector('#newDirectoryModal')) { document.querySelector('#newDirectoryModal').remove(); } - let modal = $(modals.createDirectoryHtml()).modal(); + let modal = $(modals.createDirectoryHtml()).modal('show'); let okBtn = document.querySelector('#newDirectoryModal .ok-model-btn'); let input = document.querySelector('#newDirectoryModal #directoryNameInput'); this.addInputKeyupEvent(input, okBtn); @@ -194,8 +196,7 @@ module.exports = View.extend({ this.addInputValidateEvent(input, okBtn, "#newModelModal", "#modelNameInput", {inProject: inProject}); okBtn.addEventListener('click', (e) => { modal.modal('hide'); - let ext = isSpatial ? "smdl" : "mdl"; - let file = `${input.value.trim()}.${ext}`; + let file = `${input.value.trim()}.${isSpatial ? "smdl" : "mdl"}`; if(inProject) { this.addProjectModel(dirname, file); }else{ @@ -203,16 +204,110 @@ module.exports = View.extend({ } }); }, + createProject: function (node, dirname) { + if(document.querySelector("#newProjectModal")) { + document.querySelector("#newProjectModal").remove(); + } + let modal = $(modals.createProjectHtml()).modal(); + let okBtn = document.querySelector('#newProjectModal .ok-model-btn'); + let input = document.querySelector('#newProjectModal #projectNameInput'); + this.addInputKeyupEvent(input, okBtn); + this.addInputValidateEvent(input, okBtn, "#newProjectModal", "#projectNameInput"); + okBtn.addEventListener("click", function (e) { + modal.modal('hide'); + let queryStr = "?path=" + path.join(dirname, `${input.value.trim()}.proj`); + let endpoint = path.join(app.getApiPath(), "project/new-project") + queryStr; + app.getXHR(endpoint, { + success: (err, response, body) => { + let queryStr = "?path=" + body.path; + let endpoint = path.join(app.getBasePath(), 'stochss/project/manager') + queryStr; + window.location.href = endpoint; + }, + error: (err, response, body) => { + if(document.querySelector("#errorModal")) { + document.querySelector("#errorModal").remove(); + } + let errorModel = $(modals.errorHtml(body.Reason, body.Message)).modal(); + } + }); + }); + }, handleCreateDirectoryClick: function (e) { let dirname = this.root === "none" ? "" : this.root; this.createDirectory(null, dirname); }, + handleImportModelClick: function () { + this.importModel(null); + }, handleCreateModelClick: function (e) { let dirname = this.root === "none" ? "" : this.root; let inProject = this.root !== "none"; let isSpatial = e.target.dataset.type === "spatial"; this.createModel(null, dirname, isSpatial, inProject); }, + handleCreateProjectClick: function (e) { + let dirname = this.root === "none" ? "" : this.root; + this.createProject(null, dirname); + }, + handleCreateDomain: function (e) { + let dirname = this.root === "none" ? "/" : this.root; + let queryStr = "?domainPath=" + dirname + "&new"; + window.location.href = path.join(app.getBasePath(), "stochss/domain/edit") + queryStr; + }, + importModel: function (node) { + if(document.querySelector('#importModelModal')){ + document.querySelector('#importModelModal').remove(); + } + let mdlListEP = path.join(app.getApiPath(), 'project/add-existing-model') + "?path=" + this.root; + app.getXHR(mdlListEP, { + always: (err, response, body) => { + let modal = $(modals.importModelHtml(body.files)).modal(); + let okBtn = document.querySelector('#importModelModal .ok-model-btn'); + let select = document.querySelector('#importModelModal #modelFileSelect'); + let location = document.querySelector('#importModelModal #modelPathSelect'); + select.addEventListener("change", (e) => { + okBtn.disabled = e.target.value && body.paths[e.target.value].length >= 2; + if(body.paths[e.target.value].length >= 2) { + var locations = body.paths[e.target.value].map((path) => { + return ``; + }); + locations.unshift(``); + locations = locations.join(" "); + $("#modelPathSelect").find('option').remove().end().append(locations); + $("#location-container").css("display", "block"); + }else{ + $("#location-container").css("display", "none"); + $("#modelPathSelect").find('option').remove().end(); + } + }); + location.addEventListener("change", (e) => { + okBtn.disabled = !Boolean(e.target.value); + }); + okBtn.addEventListener("click", (e) => { + modal.modal('hide'); + let mdlPath = body.paths[select.value].length < 2 ? body.paths[select.value][0] : location.value; + let queryStr = "?path=" + this.root + "&mdlPath=" + mdlPath; + let endpoint = path.join(app.getApiPath(), 'project/add-existing-model') + queryStr; + app.postXHR(endpoint, null, { + success: (err, response, body) => { + if(document.querySelector("#successModal")) { + document.querySelector("#successModal").remove(); + } + let successModal = $(modals.successHtml(body.message)).modal(); + this.config.updateParent(this, "model"); + this.refreshJSTree(null); + }, + error: function (err, response, body) { + if(document.querySelector("#errorModal")) { + document.querySelector("#errorModal").remove(); + } + let errorModal = $(modals.errorHtml(body.Reason, body.Message)).modal(); + } + }); + }); + } + }); + }, refreshInitialJSTree: function () { let count = $('#files-jstree').jstree()._model.data['#'].children.length; if(count == 0) { @@ -262,14 +357,13 @@ module.exports = View.extend({ let node = $('#files-jstree').jstree().get_node(_node); if(_node.nodeName === "A" && $('#files-jstree').jstree().is_loaded(node) && node.type === "folder"){ this.refreshJSTree(node); - }else{ - let optionsButton = $(this.queryByHook("options-for-node")); - if(this.nodeForContextMenu === null){ - optionsButton.prop('disabled', false); - } - optionsButton.text("Actions for " + node.original.text); - this.nodeForContextMenu = node; } + let optionsButton = $(this.queryByHook("options-for-node")); + if(this.nodeForContextMenu === null){ + optionsButton.prop('disabled', false); + } + optionsButton.text("Actions for " + node.original.text); + this.nodeForContextMenu = node; }); $('#files-jstree').on('dblclick.jstree', (e) => { this.config.doubleClick(this, e); From 0a8b3bf38745dcad8829f397bfbf64ea84c49d5d Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Wed, 25 Aug 2021 15:28:03 -0400 Subject: [PATCH 109/186] Added unique elements to the add button dropdown item ids and data hooks. Added dropdown item for importing models into projects. --- client/templates/includes/jstreeView.pug | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/client/templates/includes/jstreeView.pug b/client/templates/includes/jstreeView.pug index 5b33ca6133..bedda923ab 100644 --- a/client/templates/includes/jstreeView.pug +++ b/client/templates/includes/jstreeView.pug @@ -20,11 +20,13 @@ div ) + ul.dropdown-menu(aria-labelledby="files-add-btn") - li.dropdown-item(id="new-directory" data-hook="new-directory") Create Directory - li.dropdown-item(id="new-project" data-hook="new-project") Create Project - li.dropdown-item(id="new-model" data-hook="new-model" data-type="model") Create Model - li.dropdown-item(id="new-model" data-hook="new-model" data-type="spatial") Create Spatial Model (beta) - li.dropdown-item(id="new-model" data-hook="new-domain") Create Domain (beta) + li.dropdown-item(id="fb-new-directory" data-hook="fb-new-directory") Create Directory + li.dropdown-item(id="fb-new-project" data-hook="fb-new-project") Create Project + li.dropdown-item(id="fb-new-model" data-hook="fb-new-model" data-type="model") Create Model + li.dropdown-item(id="fb-new-model" data-hook="fb-new-model" data-type="spatial") Create Spatial Model (beta) + li.dropdown-item(id="fb-new-model" data-hook="fb-new-domain") Create Domain (beta) + li.dropdown-divider(data-hook="fb-proj-seperator") + li.dropdown-item(id="fb-add-existing-model" data-hook="fb-add-existing-model") Add Existing Model li.dropdown-divider li.dropdown-item(id="upload-file-btn" data-hook="upload-file-btn" data-type="model") Upload StochSS Model li.dropdown-item(id="upload-file-btn" data-hook="upload-file-btn" data-type="sbml") Upload SBML Model @@ -34,4 +36,4 @@ div button.btn.btn-primary.inline.box-shadow(id="refresh-jstree" data-hook="refresh-jstree") Refresh - button.btn.btn-primary.inline.box-shadow(id="empty-trash" data-hook="empty-trash") Empty Trash + button.btn.btn-primary.inline.box-shadow(id="fb-empty-trash" data-hook="fb-empty-trash") Empty Trash From 53354bf77e8d686a44a6c4c2c7e37e9db1c06bc1 Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Wed, 25 Aug 2021 16:57:46 -0400 Subject: [PATCH 110/186] Added functions for uploading files. --- client/templates/includes/jstreeView.pug | 2 +- client/views/jstree-view.js | 103 +++++++++++++++++++++-- 2 files changed, 99 insertions(+), 6 deletions(-) diff --git a/client/templates/includes/jstreeView.pug b/client/templates/includes/jstreeView.pug index bedda923ab..ae2a20c57a 100644 --- a/client/templates/includes/jstreeView.pug +++ b/client/templates/includes/jstreeView.pug @@ -26,7 +26,7 @@ div li.dropdown-item(id="fb-new-model" data-hook="fb-new-model" data-type="spatial") Create Spatial Model (beta) li.dropdown-item(id="fb-new-model" data-hook="fb-new-domain") Create Domain (beta) li.dropdown-divider(data-hook="fb-proj-seperator") - li.dropdown-item(id="fb-add-existing-model" data-hook="fb-add-existing-model") Add Existing Model + li.dropdown-item(id="fb-add-existing-model" data-hook="fb-import-model") Add Existing Model li.dropdown-divider li.dropdown-item(id="upload-file-btn" data-hook="upload-file-btn" data-type="model") Upload StochSS Model li.dropdown-item(id="upload-file-btn" data-hook="upload-file-btn" data-type="sbml") Upload SBML Model diff --git a/client/views/jstree-view.js b/client/views/jstree-view.js index 79f1abd90d..f1c839e250 100644 --- a/client/views/jstree-view.js +++ b/client/views/jstree-view.js @@ -38,6 +38,7 @@ module.exports = View.extend({ 'click [data-hook=fb-new-model]' : 'handleCreateModelClick', 'click [data-hook=fb-new-domain]' : 'handleCreateDomain', 'click [data-hook=fb-import-model]' : 'handleImportModelClick', + 'click [data-hook=upload-file-btn]' : 'handleUploadFileClick', }, initialize: function (attrs, options) { View.prototype.initialize.apply(this, arguments); @@ -254,6 +255,12 @@ module.exports = View.extend({ let queryStr = "?domainPath=" + dirname + "&new"; window.location.href = path.join(app.getBasePath(), "stochss/domain/edit") + queryStr; }, + handleUploadFileClick: function (e) { + let dirname = this.root === "none" ? "/" : this.root; + let inProject = this.root !== "none"; + let type = e.target.dataset.type; + this.uploadFile(null, dirname, type, inProject); + }, importModel: function (node) { if(document.querySelector('#importModelModal')){ document.querySelector('#importModelModal').remove(); @@ -317,13 +324,13 @@ module.exports = View.extend({ }, 3000); } }, - refreshJSTree: function (par) { - if(par === null || par.type === 'root'){ + refreshJSTree: function (node) { + if(node === null || node.type === 'root'){ this.jstreeIsLoaded = false $('#files-jstree').jstree().deselect_all(true) $('#files-jstree').jstree().refresh(); }else{ - $('#files-jstree').jstree().refresh_node(par); + $('#files-jstree').jstree().refresh_node(node); } }, setupJstree: function (cb) { @@ -338,8 +345,7 @@ module.exports = View.extend({ optionsButton.text("Actions for " + o.original.text); this.nodeForContextMenu = o; } - - $(document).ready(() => { + $(() => { $(document).on('shown.bs.modal', (e) => { $('[autofocus]', e.target).focus(); }); @@ -385,5 +391,92 @@ module.exports = View.extend({ } } return error + }, + uploadFile: function (node, dirname, type, inProject) { + if(document.querySelector('#uploadFileModal')) { + document.querySelector('#uploadFileModal').remove() + } + if(this.isSafariV14Plus == undefined){ + let browser = app.getBrowser(); + this.isSafariV14Plus = (browser.name === "Safari" && browser.version >= 14) + } + let modal = $(modals.uploadFileHtml(type, this.isSafariV14Plus)).modal(); + let uploadBtn = document.querySelector('#uploadFileModal .upload-modal-btn'); + let fileInput = document.querySelector('#uploadFileModal #fileForUpload'); + let input = document.querySelector('#uploadFileModal #fileNameInput'); + let fileCharErrMsg = document.querySelector('#uploadFileModal #fileSpecCharError'); + let nameEndErrMsg = document.querySelector('#uploadFileModal #fileNameInputEndCharError'); + let nameCharErrMsg = document.querySelector('#uploadFileModal #fileNameInputSpecCharError'); + let nameUsageMsg = document.querySelector('#uploadFileModal #fileNameUsageMessage'); + let getOptions = (file) => { + if(!inProject) { return {saveAs: true}; }; + if(file.name.endsWith(".mdl") || (type === "model" && file.name.endsWith(".json"))) { + return {saveAs: false}; + } + if(file.name.endsWith(".sbml") || (type === "sbml" && file.name.endsWith(".xml"))) { + return {saveAs: false}; + } + return {saveAs: true}; + } + let validateFile = () => { + let options = getOptions(fileInput.files[0]) + let fileErr = !fileInput.files.length ? "" : this.validateName(fileInput.files[0].name, options); + let nameErr = this.validateName(input.value, options); + if(!fileInput.files.length) { + uploadBtn.disabled = true; + fileCharErrMsg.style.display = 'none'; + }else if(fileErr === "" || (Boolean(input.value) && nameErr === "")){ + uploadBtn.disabled = false; + fileCharErrMsg.style.display = 'none'; + }else{ + uploadBtn.disabled = true; + fileCharErrMsg.style.display = 'block'; + } + return nameErr; + } + fileInput.addEventListener('change', (e) => { + validateFile(); + }); + input.addEventListener("input", (e) => { + let nameErr = validateFile(); + nameCharErrMsg.style.display = nameErr === "both" || nameErr === "special" ? "block" : "none"; + nameEndErrMsg.style.display = nameErr === "both" || nameErr === "forward" ? "block" : "none"; + nameUsageMsg.style.display = nameErr !== "" ? "block" : "none"; + }); + uploadBtn.addEventListener('click', (e) => { + modal.modal('hide'); + let file = fileInput.files[0]; + let options = getOptions(file) + let fileinfo = {type: type, name: "", path: dirname}; + if(Boolean(input.value) && this.validateName(input.value.trim(), options) === ""){ + fileinfo.name = input.value.trim(); + } + let formData = new FormData(); + formData.append("datafile", file); + formData.append("fileinfo", JSON.stringify(fileinfo)); + let endpoint = path.join(app.getApiPath(), 'file/upload'); + app.postXHR(endpoint, formData, { + success: (err, response, body) => { + body = JSON.parse(body); + this.refreshJSTree(node); + if(inProject && ['mdl', 'smdl', 'sbml'].includes(body.file.split('.').pop())) { + this.config.updateParent("model") + } + if(body.errors.length > 0){ + if(document.querySelector("#uploadFileErrorsModal")) { + document.querySelector("#uploadFileErrorsModal").remove(); + } + let errorModal = $(modals.uploadFileErrorsHtml(file.name, type, body.message, body.errors)).modal(); + } + }, + error: (err, response, body) => { + if(document.querySelector("#errorModal")) { + document.querySelector("#errorModal").remove(); + } + body = JSON.parse(body); + let zipErrorModal = $(modals.errorHtml(body.Reason, body.Message)).modal(); + } + }, false); + }); } }); \ No newline at end of file From e9671c5d401cd82f01207718a37d0fea8eea4a94 Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Wed, 25 Aug 2021 17:18:10 -0400 Subject: [PATCH 111/186] Added functions for actions, refresh jstree, and empty trash buttons. --- client/views/jstree-view.js | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/client/views/jstree-view.js b/client/views/jstree-view.js index f1c839e250..33baaafd50 100644 --- a/client/views/jstree-view.js +++ b/client/views/jstree-view.js @@ -39,6 +39,9 @@ module.exports = View.extend({ 'click [data-hook=fb-new-domain]' : 'handleCreateDomain', 'click [data-hook=fb-import-model]' : 'handleImportModelClick', 'click [data-hook=upload-file-btn]' : 'handleUploadFileClick', + 'click [data-hook=options-for-node]' : 'showContextMenuForNode', + 'click [data-hook=refresh-jstree]' : 'handleRefreshJSTreeClick', + 'click [data-hook=empty-trash]' : 'emptyTrash', }, initialize: function (attrs, options) { View.prototype.initialize.apply(this, arguments); @@ -233,6 +236,23 @@ module.exports = View.extend({ }); }); }, + emptyTrash: function (e) { + if(document.querySelector("#emptyTrashConfirmModal")) { + document.querySelector("#emptyTrashConfirmModal").remove(); + } + let modal = $(modals.emptyTrashConfirmHtml()).modal(); + let yesBtn = document.querySelector('#emptyTrashConfirmModal .yes-modal-btn'); + yesBtn.addEventListener('click', (e) => { + modal.modal('hide'); + let endpoint = path.join(app.getApiPath(), "file/empty-trash") + "?path=trash"; + app.getXHR(endpoint, { + success: (err, response, body) => { + this.refreshJSTree(null); + $(this.queryByHook('empty-trash')).prop('disabled', true); + } + }); + }); + }, handleCreateDirectoryClick: function (e) { let dirname = this.root === "none" ? "" : this.root; this.createDirectory(null, dirname); @@ -255,6 +275,9 @@ module.exports = View.extend({ let queryStr = "?domainPath=" + dirname + "&new"; window.location.href = path.join(app.getBasePath(), "stochss/domain/edit") + queryStr; }, + handleRefreshJSTreeClick: function (e) { + this.refreshJSTree(null); + }, handleUploadFileClick: function (e) { let dirname = this.root === "none" ? "/" : this.root; let inProject = this.root !== "none"; @@ -376,6 +399,9 @@ module.exports = View.extend({ }); }); }, + showContextMenuForNode: function (e) { + $('#files-jstree').jstree().show_contextmenu(this.nodeForContextMenu); + }, validateName: function (input, {rename = false, saveAs = true}={}) { var error = "" if(input.endsWith('/')) { From d3d265d2a5c6473ee6b0592a4c9d3b2b190cf5a9 Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Wed, 25 Aug 2021 17:18:49 -0400 Subject: [PATCH 112/186] Started modal cleanup . --- client/modals.js | 128 +++++++++++++++++++++++++++++++++-------------- 1 file changed, 91 insertions(+), 37 deletions(-) diff --git a/client/modals.js b/client/modals.js index ac499bb04a..41b93d8bd3 100644 --- a/client/modals.js +++ b/client/modals.js @@ -254,6 +254,97 @@ let templates = { } module.exports = { + createDirectoryHtml: () => { + let title = "New Directory"; + let modalID = "newDirectoryModal"; + let inputID = "directoryNameInput"; + let label = "Name:"; + let value = ""; + + return templates.input(modalID, inputID, title, label, value); + }, + createModelHtml: (isSpatial) => { + let title = `New ${isSpatial ? 'Spatial Model' : 'Model'}`; + let modalID = "newModelModal"; + let inputID = "modelNameInput"; + let label = "Name:"; + let value = ""; + + return templates.input(modalID, inputID, title, label, value); + }, + createProjectHtml : () => { + let modalID = "newProjectModal"; + let inputID = "projectNameInput"; + let title = "New Project"; + let label = "Project Name"; + let value = ""; + + return templates.input(modalID, inputID, title, label, value); + }, + emptyTrashConfirmHtml : () => { + let modalID = "emptyTrashConfirmModal"; + let title = "Are you sure you want to permanently erase the items in the Trash?"; + + return templates.confirmation(modalID, title); + }, + errorHtml: (title, error) => { + let modalID = "errorModal"; + + return templates.message(modalID, title, error); + }, + importModelHtml : (files) => { + let modalID = "importModelModal"; + let fileID = "modelFileSelect"; + let locationID = "modelPathSelect"; + let title = "Add Existing Model to Project"; + let label = "Models: "; + files = files.map(function (file) { + return ``; + }); + files.unshift(``); + files = files.join(" "); + + return templates.fileSelect(modalID, fileID, locationID, title, label, files); + }, + successHtml : (message) => { + let modalID = "successModal"; + let title = "Success!"; + + return templates.message(modalID, title, message); + }, + uploadFileHtml : (type, isSafariV14Plus) => { + let modalID = "uploadFileModal"; + let title = `Upload a ${type}`; + var accept = ""; + if(type === "model") { + accept = 'accept=".json, .mdl, .smdl"'; + }else if(type === "sbml") { + accept = 'accept=".xml, .sbml"'; + }else if(type === "file" && isSafariV14Plus){ + // only used if using Safari v14+ and only needed to fix upload issue + accept = 'accept=".json, .mdl, .smdl, .xml, .sbml, .ipynb, .zip, .md, .csv, .p, .omex, .domn, .txt, .pdf, audio/*, video/*, image/*"'; + } + + return templates.upload(modalID, title, accept); + }, + uploadFileErrorsHtml : (file, type, statusMessage, errors) => { + let modalID = "uploadFileErrorsModal"; + let types = {mdl: "model file", sbml: "sbml file", smdl: "spatial model file", zip: "zip archive"}; + if(type === "file") { + type = types[file.split('.').pop()]; + }else{ + type += " file"; + } + let title = `Errors uploading ${file} as a ${type}`; + for(var i = 0; i < errors.length; i++) { + errors[i] = `Error: ${errors[i]}`; + } + let message = `

${errors.join("
")}

Upload status: ${statusMessage}

`; + + return templates.message(modalID, title, message); + }, + + deleteFileHtml : (fileType) => { let modalID = "deleteFileModal" let title = `Permanently delete this ${fileType}?` @@ -270,12 +361,6 @@ module.exports = { } return templates.confirmation(modalID, title) }, - emptyTrashConfirmHtml : () => { - let modalID = "emptyTrashConfirmModal" - let title = "Are you sure you want to permanently erase the items in the Trash?" - - return templates.confirmation(modalID, title) - }, operationInfoModalHtml : (page) => { let modalID = "operationInfoModal" let title = "Help" @@ -369,37 +454,6 @@ module.exports = { return templates.message(modalID, title, message) }, - uploadFileErrorsHtml : (file, type, statusMessage, errors) => { - let modalID = "sbmlToModelModal" - let types = {"mdl":"model file", "sbml":"sbml file", "smdl":"spatial model file", "zip":"zip archive"} - if(type === "file") { - type = types[file.split('.').pop()] - }else{ - type += " file" - } - let title = `Errors uploading ${file} as a ${type}` - for(var i = 0; i < errors.length; i++) { - errors[i] = "Error: " + errors[i] - } - let message = `

${errors.join("
")}

Upload status: ${statusMessage}

` - - return templates.message(modalID, title, message) - }, - uploadFileHtml : (type, isSafariV14Plus) => { - let modalID = "uploadFileModal" - let title = `Upload a ${type}` - var accept = ""; - if(type === "model") { - accept = 'accept=".json, .mdl, .smdl"' - }else if(type === "sbml") { - accept = 'accept=".xml, .sbml"' - }else if(type === "file" && isSafariV14Plus){ - // only used if using Safari v14+ and only needed to fix upload issue - accept = 'accept=".json, .mdl, .smdl, .xml, .sbml, .ipynb, .zip, .md, .csv, .p, .omex, .domn, .txt, .pdf, audio/*, video/*, image/*"' - } - - return templates.upload(modalID, title, accept) - }, duplicateWorkflowHtml : (wkflFile, body) => { let modalID = "duplicateWorkflowModal" let title = `Model for ${wkflFile}` From 5b0dd95b06702b75fe7bd523aefbcc5d1eebc293 Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Wed, 25 Aug 2021 17:20:08 -0400 Subject: [PATCH 113/186] Moved file browser buttons to jstree view template. --- client/templates/pages/fileBrowser.pug | 26 -------------------------- 1 file changed, 26 deletions(-) diff --git a/client/templates/pages/fileBrowser.pug b/client/templates/pages/fileBrowser.pug index cf71352bc2..21b1231601 100644 --- a/client/templates/pages/fileBrowser.pug +++ b/client/templates/pages/fileBrowser.pug @@ -60,29 +60,3 @@ section.page div.card-body div(data-hook="jstree-view-container") - - button.btn.btn-primary.inline.box-shadow( - id="new-file-directory", - data-hook="new-file-directory", - data-toggle="dropdown", - aria-haspopup="true", - aria-expanded="false", - type="button" - ) + - - ul.dropdown-menu(aria-labelledby="new-file-directory") - li.dropdown-item(id="new-directory" data-hook="new-directory") Create Directory - li.dropdown-item(id="new-project" data-hook="new-project") Create Project - li.dropdown-item(id="new-model" data-hook="new-model" data-type="model") Create Model - li.dropdown-item(id="new-model" data-hook="new-model" data-type="spatial") Create Spatial Model (beta) - li.dropdown-item(id="new-model" data-hook="new-domain") Create Domain (beta) - li.dropdown-divider - li.dropdown-item(id="upload-file-btn" data-hook="upload-file-btn", data-type="model") Upload StochSS Model - li.dropdown-item(id="upload-file-btn" data-hook="upload-file-btn", data-type="sbml") Upload SBML Model - li.dropdown-item(id="upload-file-btn" data-hook="upload-file-btn", data-type="file") Upload File - - button.btn.btn-primary.box-shadow(id="options-for-node" data-hook="options-for-node" disabled) Actions - - button.btn.btn-primary.inline.box-shadow(id="refresh-jstree" data-hook="refresh-jstree") Refresh - - button.btn.btn-primary.inline.box-shadow(id="empty-trash" data-hook="empty-trash") Empty Trash \ No newline at end of file From 7a8cbc12bc6be4cbfb1d2289fec21514d0545dbb Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Wed, 25 Aug 2021 17:20:50 -0400 Subject: [PATCH 114/186] Moved button function to jstree view file. --- client/pages/file-browser.js | 622 +++++++++++++++--------------- client/views/file-browser-view.js | 588 ++++++++++++++-------------- 2 files changed, 605 insertions(+), 605 deletions(-) diff --git a/client/pages/file-browser.js b/client/pages/file-browser.js index e6eb836b56..1640d8821d 100644 --- a/client/pages/file-browser.js +++ b/client/pages/file-browser.js @@ -43,17 +43,17 @@ let FileBrowser = PageView.extend({ pageTitle: 'StochSS | File Browser', template: template, events: { - 'click [data-hook=refresh-jstree]' : 'refreshJSTree', - 'click [data-hook=options-for-node]' : 'showContextMenuForNode', - 'click [data-hook=new-directory]' : 'handleCreateDirectoryClick', - 'click [data-hook=new-project]' : 'handleCreateProjectClick', - 'click [data-hook=new-model]' : 'handleCreateModelClick', - 'click [data-hook=new-domain]' : 'handleCreateDomain', - 'click [data-hook=upload-file-btn]' : 'handleUploadFileClick', + // 'click [data-hook=refresh-jstree]' : 'refreshJSTree', + // 'click [data-hook=options-for-node]' : 'showContextMenuForNode', + // 'click [data-hook=new-directory]' : 'handleCreateDirectoryClick', + // 'click [data-hook=new-project]' : 'handleCreateProjectClick', + // 'click [data-hook=new-model]' : 'handleCreateModelClick', + // 'click [data-hook=new-domain]' : 'handleCreateDomain', + // 'click [data-hook=upload-file-btn]' : 'handleUploadFileClick', 'click [data-hook=file-browser-help]' : function () { let modal = $(modals.operationInfoModalHtml('file-browser')).modal(); }, - 'click [data-hook=empty-trash]' : 'emptyTrash', + // 'click [data-hook=empty-trash]' : 'emptyTrash', 'click [data-hook=collapse-projects]' : 'changeCollapseButtonText', 'click [data-hook=collapse-presentations]' : 'changeCollapseButtonText', 'click [data-hook=collapse-files]' : 'changeCollapseButtonText' @@ -260,98 +260,98 @@ let FileBrowser = PageView.extend({ } } }, - handleUploadFileClick: function (e) { - let type = e.target.dataset.type - this.uploadFile(undefined, type) - }, - uploadFile: function (o, type) { - var self = this - if(document.querySelector('#uploadFileModal')) { - document.querySelector('#uploadFileModal').remove() - } - if(this.browser == undefined) { - this.browser = app.getBrowser(); - } - if(this.isSafariV14Plus == undefined){ - this.isSafariV14Plus = (this.browser.name === "Safari" && this.browser.version >= 14) - } - let modal = $(modals.uploadFileHtml(type, this.isSafariV14Plus)).modal(); - let uploadBtn = document.querySelector('#uploadFileModal .upload-modal-btn'); - let fileInput = document.querySelector('#uploadFileModal #fileForUpload'); - let input = document.querySelector('#uploadFileModal #fileNameInput'); - let fileCharErrMsg = document.querySelector('#uploadFileModal #fileSpecCharError') - let nameEndErrMsg = document.querySelector('#uploadFileModal #fileNameInputEndCharError') - let nameCharErrMsg = document.querySelector('#uploadFileModal #fileNameInputSpecCharError') - let nameUsageMsg = document.querySelector('#uploadFileModal #fileNameUsageMessage') - fileInput.addEventListener('change', function (e) { - let fileErr = !fileInput.files.length ? "" : self.validateName(fileInput.files[0].name) - let nameErr = self.validateName(input.value) - if(!fileInput.files.length) { - uploadBtn.disabled = true - fileCharErrMsg.style.display = 'none' - }else if(fileErr === "" || (Boolean(input.value) && nameErr === "")){ - uploadBtn.disabled = false - fileCharErrMsg.style.display = 'none' - }else{ - uploadBtn.disabled = true - fileCharErrMsg.style.display = 'block' - } - }) - input.addEventListener("input", function (e) { - let fileErr = !fileInput.files.length ? "" : self.validateName(fileInput.files[0].name) - let nameErr = self.validateName(input.value) - if(!fileInput.files.length) { - uploadBtn.disabled = true - fileCharErrMsg.style.display = 'none' - }else if(fileErr === "" || (Boolean(input.value) && nameErr === "")){ - uploadBtn.disabled = false - fileCharErrMsg.style.display = 'none' - }else{ - uploadBtn.disabled = true - fileCharErrMsg.style.display = 'block' - } - nameCharErrMsg.style.display = nameErr === "both" || nameErr === "special" ? "block" : "none" - nameEndErrMsg.style.display = nameErr === "both" || nameErr === "forward" ? "block" : "none" - nameUsageMsg.style.display = nameErr !== "" ? "block" : "none" - }); - uploadBtn.addEventListener('click', function (e) { - let file = fileInput.files[0] - var fileinfo = {"type":type,"name":"","path":"/"} - if(o && o.original){ - fileinfo.path = o.original._path - } - if(Boolean(input.value) && self.validateName(input.value) === ""){ - fileinfo.name = input.value.trim() - } - let formData = new FormData() - formData.append("datafile", file) - formData.append("fileinfo", JSON.stringify(fileinfo)) - let endpoint = path.join(app.getApiPath(), 'file/upload'); - app.postXHR(endpoint, formData, { - success: function (err, response, body) { - body = JSON.parse(body); - if(o){ - var node = $('#models-jstree').jstree().get_node(o.parent); - if(node.type === "root" || node.type === "#"){ - self.refreshJSTree(); - }else{ - $('#models-jstree').jstree().refresh_node(node); - } - }else{ - self.refreshJSTree(); - } - if(body.errors.length > 0){ - let errorModal = $(modals.uploadFileErrorsHtml(file.name, type, body.message, body.errors)).modal(); - } - }, - error: function (err, response, body) { - body = JSON.parse(body); - let zipErrorModal = $(modals.projectExportErrorHtml(resp.Reason, resp.Message)).modal(); - } - }, false); - modal.modal('hide') - }); - }, + // handleUploadFileClick: function (e) { + // let type = e.target.dataset.type + // this.uploadFile(undefined, type) + // }, + // uploadFile: function (o, type) { + // var self = this + // if(document.querySelector('#uploadFileModal')) { + // document.querySelector('#uploadFileModal').remove() + // } + // if(this.browser == undefined) { + // this.browser = app.getBrowser(); + // } + // if(this.isSafariV14Plus == undefined){ + // this.isSafariV14Plus = (this.browser.name === "Safari" && this.browser.version >= 14) + // } + // let modal = $(modals.uploadFileHtml(type, this.isSafariV14Plus)).modal(); + // let uploadBtn = document.querySelector('#uploadFileModal .upload-modal-btn'); + // let fileInput = document.querySelector('#uploadFileModal #fileForUpload'); + // let input = document.querySelector('#uploadFileModal #fileNameInput'); + // let fileCharErrMsg = document.querySelector('#uploadFileModal #fileSpecCharError') + // let nameEndErrMsg = document.querySelector('#uploadFileModal #fileNameInputEndCharError') + // let nameCharErrMsg = document.querySelector('#uploadFileModal #fileNameInputSpecCharError') + // let nameUsageMsg = document.querySelector('#uploadFileModal #fileNameUsageMessage') + // fileInput.addEventListener('change', function (e) { + // let fileErr = !fileInput.files.length ? "" : self.validateName(fileInput.files[0].name) + // let nameErr = self.validateName(input.value) + // if(!fileInput.files.length) { + // uploadBtn.disabled = true + // fileCharErrMsg.style.display = 'none' + // }else if(fileErr === "" || (Boolean(input.value) && nameErr === "")){ + // uploadBtn.disabled = false + // fileCharErrMsg.style.display = 'none' + // }else{ + // uploadBtn.disabled = true + // fileCharErrMsg.style.display = 'block' + // } + // }) + // input.addEventListener("input", function (e) { + // let fileErr = !fileInput.files.length ? "" : self.validateName(fileInput.files[0].name) + // let nameErr = self.validateName(input.value) + // if(!fileInput.files.length) { + // uploadBtn.disabled = true + // fileCharErrMsg.style.display = 'none' + // }else if(fileErr === "" || (Boolean(input.value) && nameErr === "")){ + // uploadBtn.disabled = false + // fileCharErrMsg.style.display = 'none' + // }else{ + // uploadBtn.disabled = true + // fileCharErrMsg.style.display = 'block' + // } + // nameCharErrMsg.style.display = nameErr === "both" || nameErr === "special" ? "block" : "none" + // nameEndErrMsg.style.display = nameErr === "both" || nameErr === "forward" ? "block" : "none" + // nameUsageMsg.style.display = nameErr !== "" ? "block" : "none" + // }); + // uploadBtn.addEventListener('click', function (e) { + // let file = fileInput.files[0] + // var fileinfo = {"type":type,"name":"","path":"/"} + // if(o && o.original){ + // fileinfo.path = o.original._path + // } + // if(Boolean(input.value) && self.validateName(input.value) === ""){ + // fileinfo.name = input.value.trim() + // } + // let formData = new FormData() + // formData.append("datafile", file) + // formData.append("fileinfo", JSON.stringify(fileinfo)) + // let endpoint = path.join(app.getApiPath(), 'file/upload'); + // app.postXHR(endpoint, formData, { + // success: function (err, response, body) { + // body = JSON.parse(body); + // if(o){ + // var node = $('#models-jstree').jstree().get_node(o.parent); + // if(node.type === "root" || node.type === "#"){ + // self.refreshJSTree(); + // }else{ + // $('#models-jstree').jstree().refresh_node(node); + // } + // }else{ + // self.refreshJSTree(); + // } + // if(body.errors.length > 0){ + // let errorModal = $(modals.uploadFileErrorsHtml(file.name, type, body.message, body.errors)).modal(); + // } + // }, + // error: function (err, response, body) { + // body = JSON.parse(body); + // let zipErrorModal = $(modals.projectExportErrorHtml(resp.Reason, resp.Message)).modal(); + // } + // }, false); + // modal.modal('hide') + // }); + // }, deleteFile: function (o) { var fileType = o.type if(fileType === "nonspatial") @@ -670,51 +670,51 @@ let FileBrowser = PageView.extend({ // } // return error // }, - newProjectOrWorkflowGroup: function (o, isProject) { - var self = this - if(document.querySelector("#newProjectModal")) { - document.querySelector("#newProjectModal").remove() - } - var modal = $(modals.newProjectModalHtml()).modal(); - var okBtn = document.querySelector('#newProjectModal .ok-model-btn'); - var input = document.querySelector('#newProjectModal #projectNameInput'); - input.addEventListener("keyup", function (event) { - if(event.keyCode === 13){ - event.preventDefault(); - okBtn.click(); - } - }); - input.addEventListener("input", function (e) { - var endErrMsg = document.querySelector('#newProjectModal #projectNameInputEndCharError') - var charErrMsg = document.querySelector('#newProjectModal #projectNameInputSpecCharError') - let error = self.validateName(input.value) - okBtn.disabled = error !== "" || input.value.trim() === "" - charErrMsg.style.display = error === "both" || error === "special" ? "block" : "none" - endErrMsg.style.display = error === "both" || error === "forward" ? "block" : "none" - }); - okBtn.addEventListener("click", function (e) { - if(Boolean(input.value)) { - modal.modal('hide') - var parentPath = "" - if(o && o.original && o.original.type !== "root") { - parentPath = o.original._path - } - var projectName = input.value.trim() + ".proj" - var projectPath = path.join(parentPath, projectName) - var endpoint = path.join(app.getApiPath(), "project/new-project")+"?path="+projectPath - app.getXHR(endpoint, { - success: function (err, response, body) { - let queryStr = "?path=" + body.path; - let endpoint = path.join(app.getBasePath(), 'stochss/project/manager') + queryStr; - window.location.href = endpoint; - }, - error: function (err, response, body) { - let errorModel = $(modals.newProjectOrWorkflowGroupErrorHtml(body.Reason, body.Message)).modal(); - } - }); - } - }); - }, + // newProjectOrWorkflowGroup: function (o, isProject) { + // var self = this + // if(document.querySelector("#newProjectModal")) { + // document.querySelector("#newProjectModal").remove() + // } + // var modal = $(modals.newProjectModalHtml()).modal(); + // var okBtn = document.querySelector('#newProjectModal .ok-model-btn'); + // var input = document.querySelector('#newProjectModal #projectNameInput'); + // input.addEventListener("keyup", function (event) { + // if(event.keyCode === 13){ + // event.preventDefault(); + // okBtn.click(); + // } + // }); + // input.addEventListener("input", function (e) { + // var endErrMsg = document.querySelector('#newProjectModal #projectNameInputEndCharError') + // var charErrMsg = document.querySelector('#newProjectModal #projectNameInputSpecCharError') + // let error = self.validateName(input.value) + // okBtn.disabled = error !== "" || input.value.trim() === "" + // charErrMsg.style.display = error === "both" || error === "special" ? "block" : "none" + // endErrMsg.style.display = error === "both" || error === "forward" ? "block" : "none" + // }); + // okBtn.addEventListener("click", function (e) { + // if(Boolean(input.value)) { + // modal.modal('hide') + // var parentPath = "" + // if(o && o.original && o.original.type !== "root") { + // parentPath = o.original._path + // } + // var projectName = input.value.trim() + ".proj" + // var projectPath = path.join(parentPath, projectName) + // var endpoint = path.join(app.getApiPath(), "project/new-project")+"?path="+projectPath + // app.getXHR(endpoint, { + // success: function (err, response, body) { + // let queryStr = "?path=" + body.path; + // let endpoint = path.join(app.getBasePath(), 'stochss/project/manager') + queryStr; + // window.location.href = endpoint; + // }, + // error: function (err, response, body) { + // let errorModel = $(modals.newProjectOrWorkflowGroupErrorHtml(body.Reason, body.Message)).modal(); + // } + // }); + // } + // }); + // }, newWorkflow: function (o, type) { let self = this; let model = new Model({ @@ -782,126 +782,126 @@ let FileBrowser = PageView.extend({ } }); }, - addModel: function (parentPath, modelName, message) { - var endpoint = path.join(app.getBasePath(), "stochss/models/edit") - if(parentPath.endsWith(".proj")) { - let queryString = "?path=" + parentPath + "&mdlFile=" + modelName - let newMdlEP = path.join(app.getApiPath(), "project/new-model") + queryString - app.getXHR(newMdlEP, { - success: function (err, response, body) { - endpoint += "?path="+body.path; - window.location.href = endpoint; - }, - error: function (err, response, body) { - let title = "Model Already Exists"; - let message = "A model already exists with that name"; - let errorModel = $(modals.newProjectOrWorkflowGroupErrorHtml(title, message)).modal(); - } - }); - }else{ - let modelPath = path.join(parentPath, modelName) - let queryString = "?path="+modelPath+"&message="+message; - endpoint += queryString - let existEP = path.join(app.getApiPath(), "model/exists")+queryString - app.getXHR(existEP, { - always: function (err, response, body) { - if(body.exists) { - let title = "Model Already Exists"; - let message = "A model already exists with that name"; - let errorModel = $(modals.newProjectOrWorkflowGroupErrorHtml(title, message)).modal(); - }else{ - window.location.href = endpoint; - } - } - }); - } - }, - newModelOrDirectory: function (o, isModel, isSpatial) { - var self = this - if(document.querySelector('#newModalModel')) { - document.querySelector('#newModalModel').remove() - } - let modal = $(modals.renderCreateModalHtml(isModel, isSpatial)).modal(); - let okBtn = document.querySelector('#newModalModel .ok-model-btn'); - let input = document.querySelector('#newModalModel #modelNameInput'); - input.addEventListener("keyup", function (event) { - if(event.keyCode === 13){ - event.preventDefault(); - okBtn.click(); - } - }); - input.addEventListener("input", function (e) { - var endErrMsg = document.querySelector('#newModalModel #modelNameInputEndCharError') - var charErrMsg = document.querySelector('#newModalModel #modelNameInputSpecCharError') - let error = self.validateName(input.value) - okBtn.disabled = error !== "" || input.value.trim() === "" - charErrMsg.style.display = error === "both" || error === "special" ? "block" : "none" - endErrMsg.style.display = error === "both" || error === "forward" ? "block" : "none" - }); - okBtn.addEventListener('click', function (e) { - if (Boolean(input.value)) { - modal.modal('hide') - var parentPath = "" - if(o && o.original && o.original.type !== "root"){ - parentPath = o.original._path - } - if(isModel) { - let ext = isSpatial ? ".smdl" : ".mdl" - let modelName = o && o.type === "project" ? input.value.trim().split("/").pop() + ext : input.value.trim() + ext; - let message = modelName !== input.value.trim() + ext? - "Warning: Models are saved directly in StochSS Projects and cannot be saved to the "+input.value.trim().split("/")[0]+" directory in the project.

Do you wish to save your model directly in your project?

" : "" - if(message){ - let warningModal = $(modals.newProjectModelWarningHtml(message)).modal() - let yesBtn = document.querySelector('#newProjectModelWarningModal .yes-modal-btn'); - yesBtn.addEventListener('click', function (e) { - warningModal.modal('hide') - self.addModel(parentPath, modelName, message); - }); - }else{ - self.addModel(parentPath, modelName, message); - } - }else{ - let dirName = input.value.trim(); - let endpoint = path.join(app.getApiPath(), "directory/create")+"?path="+path.join(parentPath, dirName); - app.getXHR(endpoint, { - success: function (err, response, body) { - if(o){//directory was created with context menu option - var node = $('#models-jstree').jstree().get_node(o); - if(node.type === "root"){ - self.refreshJSTree(); - }else{ - $('#models-jstree').jstree().refresh_node(node); - } - }else{//directory was created with create directory button - self.refreshJSTree(); - } - }, - error: function (err, response, body) { - body = JSON.parse(body); - let errorModal = $(modals.newDirectoryErrorHtml(body.Reason, body.Message)).modal(); - } - }); - } - } - }); - }, - handleCreateDirectoryClick: function (e) { - this.newModelOrDirectory(undefined, false, false); - }, - handleCreateProjectClick: function (e) { - this.newProjectOrWorkflowGroup(undefined, true) - }, - handleCreateModelClick: function (e) { - let isSpatial = e.target.dataset.type === "spatial" - this.newModelOrDirectory(undefined, true, isSpatial); - }, - handleCreateDomain: function (e) { - let queryStr = "?domainPath=/&new" - window.location.href = path.join(app.getBasePath(), "stochss/domain/edit") + queryStr - }, - showContextMenuForNode: function (e) { - $('#models-jstree').jstree().show_contextmenu(this.nodeForContextMenu) - }, + // addModel: function (parentPath, modelName, message) { + // var endpoint = path.join(app.getBasePath(), "stochss/models/edit") + // if(parentPath.endsWith(".proj")) { + // let queryString = "?path=" + parentPath + "&mdlFile=" + modelName + // let newMdlEP = path.join(app.getApiPath(), "project/new-model") + queryString + // app.getXHR(newMdlEP, { + // success: function (err, response, body) { + // endpoint += "?path="+body.path; + // window.location.href = endpoint; + // }, + // error: function (err, response, body) { + // let title = "Model Already Exists"; + // let message = "A model already exists with that name"; + // let errorModel = $(modals.newProjectOrWorkflowGroupErrorHtml(title, message)).modal(); + // } + // }); + // }else{ + // let modelPath = path.join(parentPath, modelName) + // let queryString = "?path="+modelPath+"&message="+message; + // endpoint += queryString + // let existEP = path.join(app.getApiPath(), "model/exists")+queryString + // app.getXHR(existEP, { + // always: function (err, response, body) { + // if(body.exists) { + // let title = "Model Already Exists"; + // let message = "A model already exists with that name"; + // let errorModel = $(modals.newProjectOrWorkflowGroupErrorHtml(title, message)).modal(); + // }else{ + // window.location.href = endpoint; + // } + // } + // }); + // } + // }, + // newModelOrDirectory: function (o, isModel, isSpatial) { + // var self = this + // if(document.querySelector('#newModalModel')) { + // document.querySelector('#newModalModel').remove() + // } + // let modal = $(modals.renderCreateModalHtml(isModel, isSpatial)).modal(); + // let okBtn = document.querySelector('#newModalModel .ok-model-btn'); + // let input = document.querySelector('#newModalModel #modelNameInput'); + // input.addEventListener("keyup", function (event) { + // if(event.keyCode === 13){ + // event.preventDefault(); + // okBtn.click(); + // } + // }); + // input.addEventListener("input", function (e) { + // var endErrMsg = document.querySelector('#newModalModel #modelNameInputEndCharError') + // var charErrMsg = document.querySelector('#newModalModel #modelNameInputSpecCharError') + // let error = self.validateName(input.value) + // okBtn.disabled = error !== "" || input.value.trim() === "" + // charErrMsg.style.display = error === "both" || error === "special" ? "block" : "none" + // endErrMsg.style.display = error === "both" || error === "forward" ? "block" : "none" + // }); + // okBtn.addEventListener('click', function (e) { + // if (Boolean(input.value)) { + // modal.modal('hide') + // var parentPath = "" + // if(o && o.original && o.original.type !== "root"){ + // parentPath = o.original._path + // } + // if(isModel) { + // let ext = isSpatial ? ".smdl" : ".mdl" + // let modelName = o && o.type === "project" ? input.value.trim().split("/").pop() + ext : input.value.trim() + ext; + // let message = modelName !== input.value.trim() + ext? + // "Warning: Models are saved directly in StochSS Projects and cannot be saved to the "+input.value.trim().split("/")[0]+" directory in the project.

Do you wish to save your model directly in your project?

" : "" + // if(message){ + // let warningModal = $(modals.newProjectModelWarningHtml(message)).modal() + // let yesBtn = document.querySelector('#newProjectModelWarningModal .yes-modal-btn'); + // yesBtn.addEventListener('click', function (e) { + // warningModal.modal('hide') + // self.addModel(parentPath, modelName, message); + // }); + // }else{ + // self.addModel(parentPath, modelName, message); + // } + // }else{ + // let dirName = input.value.trim(); + // let endpoint = path.join(app.getApiPath(), "directory/create")+"?path="+path.join(parentPath, dirName); + // app.getXHR(endpoint, { + // success: function (err, response, body) { + // if(o){//directory was created with context menu option + // var node = $('#models-jstree').jstree().get_node(o); + // if(node.type === "root"){ + // self.refreshJSTree(); + // }else{ + // $('#models-jstree').jstree().refresh_node(node); + // } + // }else{//directory was created with create directory button + // self.refreshJSTree(); + // } + // }, + // error: function (err, response, body) { + // body = JSON.parse(body); + // let errorModal = $(modals.newDirectoryErrorHtml(body.Reason, body.Message)).modal(); + // } + // }); + // } + // } + // }); + // }, + // handleCreateDirectoryClick: function (e) { + // this.newModelOrDirectory(undefined, false, false); + // }, + // handleCreateProjectClick: function (e) { + // this.newProjectOrWorkflowGroup(undefined, true) + // }, + // handleCreateModelClick: function (e) { + // let isSpatial = e.target.dataset.type === "spatial" + // this.newModelOrDirectory(undefined, true, isSpatial); + // }, + // handleCreateDomain: function (e) { + // let queryStr = "?domainPath=/&new" + // window.location.href = path.join(app.getBasePath(), "stochss/domain/edit") + queryStr + // }, + // showContextMenuForNode: function (e) { + // $('#models-jstree').jstree().show_contextmenu(this.nodeForContextMenu) + // }, editWorkflowModel: function (o) { let endpoint = path.join(app.getApiPath(), "workflow/edit-model")+"?path="+o.original._path app.getXHR(endpoint, { @@ -953,24 +953,24 @@ let FileBrowser = PageView.extend({ }); }); }, - emptyTrash: function (e) { - if(document.querySelector("#emptyTrashConfirmModal")) { - document.querySelector("#emptyTrashConfirmModal").remove() - } - let self = this; - let modal = $(modals.emptyTrashConfirmHtml()).modal(); - let yesBtn = document.querySelector('#emptyTrashConfirmModal .yes-modal-btn'); - yesBtn.addEventListener('click', function (e) { - modal.modal('hide'); - let endpoint = path.join(app.getApiPath(), "file/empty-trash") + "?path=trash"; - app.getXHR(endpoint, { - success: function (err, response, body) { - self.refreshJSTree(); - $(self.queryByHook('empty-trash')).prop('disabled', true); - } - }); - }); - }, + // emptyTrash: function (e) { + // if(document.querySelector("#emptyTrashConfirmModal")) { + // document.querySelector("#emptyTrashConfirmModal").remove() + // } + // let self = this; + // let modal = $(modals.emptyTrashConfirmHtml()).modal(); + // let yesBtn = document.querySelector('#emptyTrashConfirmModal .yes-modal-btn'); + // yesBtn.addEventListener('click', function (e) { + // modal.modal('hide'); + // let endpoint = path.join(app.getApiPath(), "file/empty-trash") + "?path=trash"; + // app.getXHR(endpoint, { + // success: function (err, response, body) { + // self.refreshJSTree(); + // $(self.queryByHook('empty-trash')).prop('disabled', true); + // } + // }); + // }); + // }, publishNotebookPresentation: function (o) { let queryStr = "?path=" + o.original._path; let endpoint = path.join(app.getApiPath(), "notebook/presentation") + queryStr; @@ -1494,34 +1494,34 @@ let FileBrowser = PageView.extend({ // self.nodeForContextMenu = node; // } // }); - $('#models-jstree').on('dblclick.jstree', function(e) { - var file = e.target.text - var node = $('#models-jstree').jstree().get_node(e.target) - var _path = node.original._path; - if(!(_path.split("/")[0] === "trash")) { - if(file.endsWith('.mdl') || file.endsWith('.smdl')){ - window.location.href = path.join(app.getBasePath(), "stochss/models/edit")+"?path="+_path; - }else if(file.endsWith('.ipynb')){ - var notebookPath = path.join(app.getBasePath(), "notebooks", _path) - window.open(notebookPath, '_blank') - }else if(file.endsWith('.sbml')){ - var openPath = path.join(app.getBasePath(), "edit", _path) - window.open(openPath, '_blank') - }else if(file.endsWith('.proj')){ - window.location.href = path.join(app.getBasePath(), "stochss/project/manager")+"?path="+_path; - }else if(file.endsWith('.wkfl')){ - window.location.href = path.join(app.getBasePath(), "stochss/workflow/edit")+"?path="+_path+"&type=none"; - }else if(file.endsWith('.domn')) { - let queryStr = "?domainPath=" + _path - window.location.href = path.join(app.getBasePath(), "stochss/domain/edit") + queryStr - }else if(node.type === "folder" && $('#models-jstree').jstree().is_open(node) && $('#models-jstree').jstree().is_loaded(node)){ - $('#models-jstree').jstree().refresh_node(node) - }else if(node.type === "other"){ - var openPath = path.join(app.getBasePath(), "view", _path); - window.open(openPath, "_blank"); - } - } - }); + // $('#models-jstree').on('dblclick.jstree', function(e) { + // var file = e.target.text + // var node = $('#models-jstree').jstree().get_node(e.target) + // var _path = node.original._path; + // if(!(_path.split("/")[0] === "trash")) { + // if(file.endsWith('.mdl') || file.endsWith('.smdl')){ + // window.location.href = path.join(app.getBasePath(), "stochss/models/edit")+"?path="+_path; + // }else if(file.endsWith('.ipynb')){ + // var notebookPath = path.join(app.getBasePath(), "notebooks", _path) + // window.open(notebookPath, '_blank') + // }else if(file.endsWith('.sbml')){ + // var openPath = path.join(app.getBasePath(), "edit", _path) + // window.open(openPath, '_blank') + // }else if(file.endsWith('.proj')){ + // window.location.href = path.join(app.getBasePath(), "stochss/project/manager")+"?path="+_path; + // }else if(file.endsWith('.wkfl')){ + // window.location.href = path.join(app.getBasePath(), "stochss/workflow/edit")+"?path="+_path+"&type=none"; + // }else if(file.endsWith('.domn')) { + // let queryStr = "?domainPath=" + _path + // window.location.href = path.join(app.getBasePath(), "stochss/domain/edit") + queryStr + // }else if(node.type === "folder" && $('#models-jstree').jstree().is_open(node) && $('#models-jstree').jstree().is_loaded(node)){ + // $('#models-jstree').jstree().refresh_node(node) + // }else if(node.type === "other"){ + // var openPath = path.join(app.getBasePath(), "view", _path); + // window.open(openPath, "_blank"); + // } + // } + // }); } }); diff --git a/client/views/file-browser-view.js b/client/views/file-browser-view.js index 95ad5a18fa..32490b342e 100644 --- a/client/views/file-browser-view.js +++ b/client/views/file-browser-view.js @@ -34,14 +34,14 @@ let Model = require('../models/model'); // template: template, events: { 'click [data-hook=collapse-browse-files]' : 'changeCollapseButtonText', - 'click [data-hook=refresh-jstree]' : 'refreshJSTree', - 'click [data-hook=options-for-node]' : 'showContextMenuForNode', - 'click [data-hook=new-directory]' : 'handleCreateDirectoryClick', + // 'click [data-hook=refresh-jstree]' : 'refreshJSTree', + // 'click [data-hook=options-for-node]' : 'showContextMenuForNode', + // 'click [data-hook=new-directory]' : 'handleCreateDirectoryClick', 'click [data-hook=browser-new-workflow-group]' : 'handleCreateWorkflowGroupClick', - 'click [data-hook=browser-new-model]' : 'handleCreateModelClick', - 'click [data-hook=browser-new-domain]' : 'handelCreateDomainClick', - 'click [data-hook=browser-existing-model]' : 'handleAddExistingModelClick', - 'click [data-hook=upload-file-btn-bf]' : 'handleUploadFileClick', + // 'click [data-hook=browser-new-model]' : 'handleCreateModelClick', + // 'click [data-hook=browser-new-domain]' : 'handelCreateDomainClick', + // 'click [data-hook=browser-existing-model]' : 'handleAddExistingModelClick', + // 'click [data-hook=upload-file-btn-bf]' : 'handleUploadFileClick', 'click [data-hook=file-browser-help]' : function () { let modal = $(modals.operationInfoModalHtml('file-browser')).modal(); }, @@ -241,122 +241,122 @@ let Model = require('../models/model'); } } }, - handleUploadFileClick: function (e) { - let type = e.target.dataset.type - this.uploadFile(undefined, type) - }, - uploadFile: function (o, type) { - var self = this - if(document.querySelector('#uploadFileModal')) { - document.querySelector('#uploadFileModal').remove() - } - if(this.browser == undefined) { - this.browser = app.getBrowser(); - } - if(this.isSafariV14Plus == undefined){ - this.isSafariV14Plus = (this.browser.name === "Safari" && this.browser.version >= 14) - } - let modal = $(modals.uploadFileHtml(type, this.isSafariV14Plus)).modal(); - let uploadBtn = document.querySelector('#uploadFileModal .upload-modal-btn'); - let fileInput = document.querySelector('#uploadFileModal #fileForUpload'); - let input = document.querySelector('#uploadFileModal #fileNameInput'); - let fileCharErrMsg = document.querySelector('#uploadFileModal #fileSpecCharError') - let nameEndErrMsg = document.querySelector('#uploadFileModal #fileNameInputEndCharError') - let nameCharErrMsg = document.querySelector('#uploadFileModal #fileNameInputSpecCharError') - let nameUsageMsg = document.querySelector('#uploadFileModal #fileNameUsageMessage') - fileInput.addEventListener('change', function (e) { - let fileErr = !fileInput.files.length ? "" : self.validateName(fileInput.files[0].name) - let nameErr = self.validateName(input.value) - if(!fileInput.files.length) { - uploadBtn.disabled = true - fileCharErrMsg.style.display = 'none' - }else if(fileErr === "" || (Boolean(input.value) && nameErr === "")){ - uploadBtn.disabled = false - fileCharErrMsg.style.display = 'none' - }else{ - uploadBtn.disabled = true - fileCharErrMsg.style.display = 'block' - } - }) - input.addEventListener("input", function (e) { - let fileErr = !fileInput.files.length ? "" : self.validateName(fileInput.files[0].name) - let nameErr = self.validateName(input.value) - if(!fileInput.files.length) { - uploadBtn.disabled = true - fileCharErrMsg.style.display = 'none' - }else if(fileErr === "" || (Boolean(input.value) && nameErr === "")){ - uploadBtn.disabled = false - fileCharErrMsg.style.display = 'none' - }else{ - uploadBtn.disabled = true - fileCharErrMsg.style.display = 'block' - } - nameCharErrMsg.style.display = nameErr === "both" || nameErr === "special" ? "block" : "none" - nameEndErrMsg.style.display = nameErr === "both" || nameErr === "forward" ? "block" : "none" - nameUsageMsg.style.display = nameErr !== "" ? "block" : "none" - }); - uploadBtn.addEventListener('click', function (e) { - let file = fileInput.files[0] - var fileinfo = {"type":type,"name":"","path":self.parent.model.directory} - if(o && o.original){ - fileinfo.path = o.original._path - } - if(Boolean(input.value) && self.validateName(input.value) === ""){ - let name = input.value.trim() - if(file.name.endsWith(".mdl") || (type === "model" && file.name.endsWith(".json"))){ - fileinfo.name = name.split('/').pop() - }else if(file.name.endsWith(".sbml") || (type === "sbml" && file.name.endsWith(".xml"))){ - fileinfo.name = name.split('/').pop() - }else{ - fileinfo.name = name - } - } - let formData = new FormData() - formData.append("datafile", file) - formData.append("fileinfo", JSON.stringify(fileinfo)) - let endpoint = path.join(app.getApiPath(), 'file/upload'); - if(Boolean(input.value) && self.validateName(input.value) === "" && fileinfo.name !== input.value.trim()){ - let message = "Warning: Models are saved directly in StochSS Projects and cannot be saved to the "+input.value.trim().split("/")[0]+" directory in the project.

Do you wish to save your model directly in your project?

" - let warningModal = $(modals.newProjectModelWarningHtml(message)).modal() - let yesBtn = document.querySelector('#newProjectModelWarningModal .yes-modal-btn'); - yesBtn.addEventListener('click', function (e) { - warningModal.modal('hide') - self.openUploadRequest(endpoint, formData, file, type, o) - }) - }else{ - self.openUploadRequest(endpoint, formData, file, type, o) - } - modal.modal('hide') - }) - }, - openUploadRequest: function (endpoint, formData, file, type, o) { - let self = this - app.postXHR(endpoint, formData, { - success: function (err, response, body) { - body = JSON.parse(body); - if(o){ - var node = $('#models-jstree-view').jstree().get_node(o.parent); - if(node.type === "root" || node.type === "#"){ - self.refreshJSTree(); - }else{ - $('#models-jstree-view').jstree().refresh_node(node); - } - }else{ - self.refreshJSTree(); - } - if(body.file.endsWith(".mdl") || body.file.endsWith(".smdl") ||body.file.endsWith(".sbml")) { - self.updateParent("model") - } - if(body.errors.length > 0){ - let errorModal = $(modals.uploadFileErrorsHtml(file.name, type, body.message, body.errors)).modal(); - } - }, - error: function (err, response, body) { - body = JSON.parse(body); - let zipErrorModal = $(modals.projectExportErrorHtml(body.Reason, body.Message)).modal() - } - }, false); - }, + // handleUploadFileClick: function (e) { + // let type = e.target.dataset.type + // this.uploadFile(undefined, type) + // }, + // uploadFile: function (o, type) { + // var self = this + // if(document.querySelector('#uploadFileModal')) { + // document.querySelector('#uploadFileModal').remove() + // } + // if(this.browser == undefined) { + // this.browser = app.getBrowser(); + // } + // if(this.isSafariV14Plus == undefined){ + // this.isSafariV14Plus = (this.browser.name === "Safari" && this.browser.version >= 14) + // } + // let modal = $(modals.uploadFileHtml(type, this.isSafariV14Plus)).modal(); + // let uploadBtn = document.querySelector('#uploadFileModal .upload-modal-btn'); + // let fileInput = document.querySelector('#uploadFileModal #fileForUpload'); + // let input = document.querySelector('#uploadFileModal #fileNameInput'); + // let fileCharErrMsg = document.querySelector('#uploadFileModal #fileSpecCharError') + // let nameEndErrMsg = document.querySelector('#uploadFileModal #fileNameInputEndCharError') + // let nameCharErrMsg = document.querySelector('#uploadFileModal #fileNameInputSpecCharError') + // let nameUsageMsg = document.querySelector('#uploadFileModal #fileNameUsageMessage') + // fileInput.addEventListener('change', function (e) { + // let fileErr = !fileInput.files.length ? "" : self.validateName(fileInput.files[0].name) + // let nameErr = self.validateName(input.value) + // if(!fileInput.files.length) { + // uploadBtn.disabled = true + // fileCharErrMsg.style.display = 'none' + // }else if(fileErr === "" || (Boolean(input.value) && nameErr === "")){ + // uploadBtn.disabled = false + // fileCharErrMsg.style.display = 'none' + // }else{ + // uploadBtn.disabled = true + // fileCharErrMsg.style.display = 'block' + // } + // }) + // input.addEventListener("input", function (e) { + // let fileErr = !fileInput.files.length ? "" : self.validateName(fileInput.files[0].name) + // let nameErr = self.validateName(input.value) + // if(!fileInput.files.length) { + // uploadBtn.disabled = true + // fileCharErrMsg.style.display = 'none' + // }else if(fileErr === "" || (Boolean(input.value) && nameErr === "")){ + // uploadBtn.disabled = false + // fileCharErrMsg.style.display = 'none' + // }else{ + // uploadBtn.disabled = true + // fileCharErrMsg.style.display = 'block' + // } + // nameCharErrMsg.style.display = nameErr === "both" || nameErr === "special" ? "block" : "none" + // nameEndErrMsg.style.display = nameErr === "both" || nameErr === "forward" ? "block" : "none" + // nameUsageMsg.style.display = nameErr !== "" ? "block" : "none" + // }); + // uploadBtn.addEventListener('click', function (e) { + // let file = fileInput.files[0] + // var fileinfo = {"type":type,"name":"","path":self.parent.model.directory} + // if(o && o.original){ + // fileinfo.path = o.original._path + // } + // if(Boolean(input.value) && self.validateName(input.value) === ""){ + // let name = input.value.trim() + // if(file.name.endsWith(".mdl") || (type === "model" && file.name.endsWith(".json"))){ + // fileinfo.name = name.split('/').pop() + // }else if(file.name.endsWith(".sbml") || (type === "sbml" && file.name.endsWith(".xml"))){ + // fileinfo.name = name.split('/').pop() + // }else{ + // fileinfo.name = name + // } + // } + // let formData = new FormData() + // formData.append("datafile", file) + // formData.append("fileinfo", JSON.stringify(fileinfo)) + // let endpoint = path.join(app.getApiPath(), 'file/upload'); + // if(Boolean(input.value) && self.validateName(input.value) === "" && fileinfo.name !== input.value.trim()){ + // let message = "Warning: Models are saved directly in StochSS Projects and cannot be saved to the "+input.value.trim().split("/")[0]+" directory in the project.

Do you wish to save your model directly in your project?

" + // let warningModal = $(modals.newProjectModelWarningHtml(message)).modal() + // let yesBtn = document.querySelector('#newProjectModelWarningModal .yes-modal-btn'); + // yesBtn.addEventListener('click', function (e) { + // warningModal.modal('hide') + // self.openUploadRequest(endpoint, formData, file, type, o) + // }) + // }else{ + // self.openUploadRequest(endpoint, formData, file, type, o) + // } + // modal.modal('hide') + // }) + // }, + // openUploadRequest: function (endpoint, formData, file, type, o) { + // let self = this + // app.postXHR(endpoint, formData, { + // success: function (err, response, body) { + // body = JSON.parse(body); + // if(o){ + // var node = $('#models-jstree-view').jstree().get_node(o.parent); + // if(node.type === "root" || node.type === "#"){ + // self.refreshJSTree(); + // }else{ + // $('#models-jstree-view').jstree().refresh_node(node); + // } + // }else{ + // self.refreshJSTree(); + // } + // if(body.file.endsWith(".mdl") || body.file.endsWith(".smdl") ||body.file.endsWith(".sbml")) { + // self.updateParent("model") + // } + // if(body.errors.length > 0){ + // let errorModal = $(modals.uploadFileErrorsHtml(file.name, type, body.message, body.errors)).modal(); + // } + // }, + // error: function (err, response, body) { + // body = JSON.parse(body); + // let zipErrorModal = $(modals.projectExportErrorHtml(body.Reason, body.Message)).modal() + // } + // }, false); + // }, deleteFile: function (o) { var fileType = o.type if(fileType === "nonspatial") @@ -703,58 +703,58 @@ let Model = require('../models/model'); } }) }, - handleAddExistingModelClick: function () { - this.addExistingModel(undefined) - }, - addExistingModel: function (o) { - var self = this - if(document.querySelector('#newProjectModelModal')){ - document.querySelector('#newProjectModelModal').remove() - } - let mdlListEP = path.join(app.getApiPath(), 'project/add-existing-model') + "?path="+self.parent.model.directory - app.getXHR(mdlListEP, { - always: function (err, response, body) { - let modal = $(modals.newProjectModelHtml(body.files)).modal(); - let okBtn = document.querySelector('#newProjectModelModal .ok-model-btn'); - let select = document.querySelector('#newProjectModelModal #modelFileInput'); - let location = document.querySelector('#newProjectModelModal #modelPathInput'); - select.addEventListener("change", function (e) { - okBtn.disabled = e.target.value && body.paths[e.target.value].length >= 2; - if(body.paths[e.target.value].length >= 2) { - var locations = body.paths[e.target.value].map(function (path) { - return ``; - }); - locations.unshift(``); - locations = locations.join(" "); - $("#modelPathInput").find('option').remove().end().append(locations); - $("#location-container").css("display", "block"); - }else{ - $("#location-container").css("display", "none"); - $("#modelPathInput").find('option').remove().end(); - } - }); - location.addEventListener("change", function (e) { - okBtn.disabled = !Boolean(e.target.value); - }); - okBtn.addEventListener("click", function (e) { - modal.modal('hide'); - let mdlPath = body.paths[select.value].length < 2 ? body.paths[select.value][0] : location.value; - let queryString = "?path="+self.parent.model.directory+"&mdlPath="+mdlPath; - let endpoint = path.join(app.getApiPath(), 'project/add-existing-model') + queryString; - app.postXHR(endpoint, null, { - success: function (err, response, body) { - let successModal = $(modals.newProjectModelSuccessHtml(body.message)).modal(); - self.updateParent("model"); - self.refreshJSTree(); - }, - error: function (err, response, body) { - let errorModal = $(modals.newProjectModelErrorHtml(body.Reason, body.Message)).modal(); - } - }); - }); - } - }); - }, + // handleAddExistingModelClick: function () { + // this.addExistingModel(undefined) + // }, + // addExistingModel: function (o) { + // var self = this + // if(document.querySelector('#newProjectModelModal')){ + // document.querySelector('#newProjectModelModal').remove() + // } + // let mdlListEP = path.join(app.getApiPath(), 'project/add-existing-model') + "?path="+self.parent.model.directory + // app.getXHR(mdlListEP, { + // always: function (err, response, body) { + // let modal = $(modals.newProjectModelHtml(body.files)).modal(); + // let okBtn = document.querySelector('#newProjectModelModal .ok-model-btn'); + // let select = document.querySelector('#newProjectModelModal #modelFileInput'); + // let location = document.querySelector('#newProjectModelModal #modelPathInput'); + // select.addEventListener("change", function (e) { + // okBtn.disabled = e.target.value && body.paths[e.target.value].length >= 2; + // if(body.paths[e.target.value].length >= 2) { + // var locations = body.paths[e.target.value].map(function (path) { + // return ``; + // }); + // locations.unshift(``); + // locations = locations.join(" "); + // $("#modelPathInput").find('option').remove().end().append(locations); + // $("#location-container").css("display", "block"); + // }else{ + // $("#location-container").css("display", "none"); + // $("#modelPathInput").find('option').remove().end(); + // } + // }); + // location.addEventListener("change", function (e) { + // okBtn.disabled = !Boolean(e.target.value); + // }); + // okBtn.addEventListener("click", function (e) { + // modal.modal('hide'); + // let mdlPath = body.paths[select.value].length < 2 ? body.paths[select.value][0] : location.value; + // let queryString = "?path="+self.parent.model.directory+"&mdlPath="+mdlPath; + // let endpoint = path.join(app.getApiPath(), 'project/add-existing-model') + queryString; + // app.postXHR(endpoint, null, { + // success: function (err, response, body) { + // let successModal = $(modals.newProjectModelSuccessHtml(body.message)).modal(); + // self.updateParent("model"); + // self.refreshJSTree(); + // }, + // error: function (err, response, body) { + // let errorModal = $(modals.newProjectModelErrorHtml(body.Reason, body.Message)).modal(); + // } + // }); + // }); + // } + // }); + // }, newWorkflow: function (o, type) { let self = this; let model = new Model({ @@ -775,123 +775,123 @@ let Model = require('../models/model'); } }); }, - addModel: function (parentPath, modelName, message) { - var endpoint = path.join(app.getBasePath(), "stochss/models/edit") - if(parentPath.endsWith(".proj")) { - let queryString = "?path=" + parentPath + "&mdlFile=" + modelName - let newMdlEP = path.join(app.getApiPath(), "project/new-model") + queryString - app.getXHR(newMdlEP, { - success: function (err, response, body) { - endpoint += "?path="+body.path; - window.location.href = endpoint; - }, - error: function (err, response, body) { - let title = "Model Already Exists"; - let message = "A model already exists with that name"; - let errorModel = $(modals.newProjectOrWorkflowGroupErrorHtml(title, message)).modal(); - } - }); - }else{ - let modelPath = path.join(parentPath, modelName) - let queryString = "?path="+modelPath+"&message="+message; - endpoint += queryString - let existEP = path.join(app.getApiPath(), "model/exists")+queryString - app.getXHR(existEP, { - always: function (err, response, body) { - if(body.exists) { - let title = "Model Already Exists"; - let message = "A model already exists with that name"; - let errorModel = $(modals.newProjectOrWorkflowGroupErrorHtml(title, message)).modal(); - }else{ - window.location.href = endpoint; - } - } - }); - } - }, - newModelOrDirectory: function (o, isModel, isSpatial) { - var self = this - if(document.querySelector('#newModalModel')) { - document.querySelector('#newModalModel').remove() - } - let modal = $(modals.renderCreateModalHtml(isModel, isSpatial)).modal(); - let okBtn = document.querySelector('#newModalModel .ok-model-btn'); - let input = document.querySelector('#newModalModel #modelNameInput'); - input.addEventListener("keyup", function (event) { - if(event.keyCode === 13){ - event.preventDefault(); - okBtn.click(); - } - }); - input.addEventListener("input", function (e) { - var endErrMsg = document.querySelector('#newModalModel #modelNameInputEndCharError') - var charErrMsg = document.querySelector('#newModalModel #modelNameInputSpecCharError') - let error = self.validateName(input.value) - okBtn.disabled = error !== "" || input.value.trim() === "" - charErrMsg.style.display = error === "both" || error === "special" ? "block" : "none" - endErrMsg.style.display = error === "both" || error === "forward" ? "block" : "none" - }); - okBtn.addEventListener('click', function (e) { - if (Boolean(input.value)) { - modal.modal('hide') - var parentPath = self.parent.model.directory - if(o && o.original && o.original._path !== "/"){ - parentPath = o.original._path - } - if(isModel) { - let ext = isSpatial ? ".smdl" : ".mdl"; - let modelName = !o || (o && o.type === "root") ? input.value.trim().split("/").pop() + ext : input.value.trim() + ext; - let message = modelName !== input.value.trim() + ext? - "Warning: Models are saved directly in StochSS Projects and cannot be saved to the "+input.value.trim().split("/")[0]+" directory in the project.

Your model will be saved directly in your project.

" : "" - if(message){ - let warningModal = $(modals.newProjectModelWarningHtml(message)).modal() - let yesBtn = document.querySelector('#newProjectModelWarningModal .yes-modal-btn'); - yesBtn.addEventListener('click', function (e) { - warningModal.modal('hide'); - self.addModel(parentPath, modelName, message); - }); - }else{ - self.addModel(parentPath, modelName, message); - } - }else{ - let dirName = input.value.trim(); - let endpoint = path.join(app.getApiPath(), "directory/create")+"?path="+path.join(parentPath, dirName); - app.getXHR(endpoint, { - success: function (err, response, body) { - if(o){//directory was created with context menu option - var node = $('#models-jstree-view').jstree().get_node(o); - if(node.type === "root"){ - self.refreshJSTree(); - }else{ - $('#models-jstree-view').jstree().refresh_node(node); - } - }else{//directory was created with create directory button - self.refreshJSTree(); - } - }, - error: function (err, response, body) { - body = JSON.parse(body); - let errorModal = $(modals.newDirectoryErrorHtml(body.Reason, body.Message)).modal(); - } - }); - } - } - }); - }, - handleCreateDirectoryClick: function (e) { - this.newModelOrDirectory(undefined, false, false); - }, + // addModel: function (parentPath, modelName, message) { + // var endpoint = path.join(app.getBasePath(), "stochss/models/edit") + // if(parentPath.endsWith(".proj")) { + // let queryString = "?path=" + parentPath + "&mdlFile=" + modelName + // let newMdlEP = path.join(app.getApiPath(), "project/new-model") + queryString + // app.getXHR(newMdlEP, { + // success: function (err, response, body) { + // endpoint += "?path="+body.path; + // window.location.href = endpoint; + // }, + // error: function (err, response, body) { + // let title = "Model Already Exists"; + // let message = "A model already exists with that name"; + // let errorModel = $(modals.newProjectOrWorkflowGroupErrorHtml(title, message)).modal(); + // } + // }); + // }else{ + // let modelPath = path.join(parentPath, modelName) + // let queryString = "?path="+modelPath+"&message="+message; + // endpoint += queryString + // let existEP = path.join(app.getApiPath(), "model/exists")+queryString + // app.getXHR(existEP, { + // always: function (err, response, body) { + // if(body.exists) { + // let title = "Model Already Exists"; + // let message = "A model already exists with that name"; + // let errorModel = $(modals.newProjectOrWorkflowGroupErrorHtml(title, message)).modal(); + // }else{ + // window.location.href = endpoint; + // } + // } + // }); + // } + // }, + // newModelOrDirectory: function (o, isModel, isSpatial) { + // var self = this + // if(document.querySelector('#newModalModel')) { + // document.querySelector('#newModalModel').remove() + // } + // let modal = $(modals.renderCreateModalHtml(isModel, isSpatial)).modal(); + // let okBtn = document.querySelector('#newModalModel .ok-model-btn'); + // let input = document.querySelector('#newModalModel #modelNameInput'); + // input.addEventListener("keyup", function (event) { + // if(event.keyCode === 13){ + // event.preventDefault(); + // okBtn.click(); + // } + // }); + // input.addEventListener("input", function (e) { + // var endErrMsg = document.querySelector('#newModalModel #modelNameInputEndCharError') + // var charErrMsg = document.querySelector('#newModalModel #modelNameInputSpecCharError') + // let error = self.validateName(input.value) + // okBtn.disabled = error !== "" || input.value.trim() === "" + // charErrMsg.style.display = error === "both" || error === "special" ? "block" : "none" + // endErrMsg.style.display = error === "both" || error === "forward" ? "block" : "none" + // }); + // okBtn.addEventListener('click', function (e) { + // if (Boolean(input.value)) { + // modal.modal('hide') + // var parentPath = self.parent.model.directory + // if(o && o.original && o.original._path !== "/"){ + // parentPath = o.original._path + // } + // if(isModel) { + // let ext = isSpatial ? ".smdl" : ".mdl"; + // let modelName = !o || (o && o.type === "root") ? input.value.trim().split("/").pop() + ext : input.value.trim() + ext; + // let message = modelName !== input.value.trim() + ext? + // "Warning: Models are saved directly in StochSS Projects and cannot be saved to the "+input.value.trim().split("/")[0]+" directory in the project.

Your model will be saved directly in your project.

" : "" + // if(message){ + // let warningModal = $(modals.newProjectModelWarningHtml(message)).modal() + // let yesBtn = document.querySelector('#newProjectModelWarningModal .yes-modal-btn'); + // yesBtn.addEventListener('click', function (e) { + // warningModal.modal('hide'); + // self.addModel(parentPath, modelName, message); + // }); + // }else{ + // self.addModel(parentPath, modelName, message); + // } + // }else{ + // let dirName = input.value.trim(); + // let endpoint = path.join(app.getApiPath(), "directory/create")+"?path="+path.join(parentPath, dirName); + // app.getXHR(endpoint, { + // success: function (err, response, body) { + // if(o){//directory was created with context menu option + // var node = $('#models-jstree-view').jstree().get_node(o); + // if(node.type === "root"){ + // self.refreshJSTree(); + // }else{ + // $('#models-jstree-view').jstree().refresh_node(node); + // } + // }else{//directory was created with create directory button + // self.refreshJSTree(); + // } + // }, + // error: function (err, response, body) { + // body = JSON.parse(body); + // let errorModal = $(modals.newDirectoryErrorHtml(body.Reason, body.Message)).modal(); + // } + // }); + // } + // } + // }); + // }, + // handleCreateDirectoryClick: function (e) { + // this.newModelOrDirectory(undefined, false, false); + // }, handleCreateWorkflowGroupClick: function (e) { this.newWorkflowGroup(undefined) }, - handleCreateModelClick: function (e) { - let isSpatial = e.target.dataset.type === "spatial" - this.newModelOrDirectory(undefined, true, isSpatial); - }, - handelCreateDomainClick: function (e) { - let queryStr = "?domainPath=" + this.parent.model.directory + "&new" - window.location.href = path.join(app.getBasePath(), "stochss/domain/edit") + queryStr - }, + // handleCreateModelClick: function (e) { + // let isSpatial = e.target.dataset.type === "spatial" + // this.newModelOrDirectory(undefined, true, isSpatial); + // }, + // handelCreateDomainClick: function (e) { + // let queryStr = "?domainPath=" + this.parent.model.directory + "&new" + // window.location.href = path.join(app.getBasePath(), "stochss/domain/edit") + queryStr + // }, handleExtractModelClick: function (o) { let self = this let projectParent = path.dirname(this.parent.model.directory) === '.' ? "" : path.dirname(this.parent.model.directory) @@ -926,9 +926,9 @@ let Model = require('../models/model'); let target = o.original._path this.parent.exportAsCombine() }, - showContextMenuForNode: function (e) { - $('#models-jstree-view').jstree().show_contextmenu(this.nodeForContextMenu) - }, + // showContextMenuForNode: function (e) { + // $('#models-jstree-view').jstree().show_contextmenu(this.nodeForContextMenu) + // }, editWorkflowModel: function (o) { let endpoint = path.join(app.getApiPath(), "workflow/edit-model")+"?path="+o.original._path app.getXHR(endpoint, { @@ -1532,5 +1532,5 @@ let Model = require('../models/model'); // }, changeCollapseButtonText: function (e) { app.changeCollapseButtonText(this, e); - } -}); \ No newline at end of file +// } +// }); \ No newline at end of file From b106923727297258f24256cdf1c786d0870afd55 Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Thu, 26 Aug 2021 07:59:38 -0400 Subject: [PATCH 115/186] Fixed issue with copy to clipboard confirmation text not displaying. --- client/job-view/views/job-results-view.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/client/job-view/views/job-results-view.js b/client/job-view/views/job-results-view.js index 631097c277..6181565cd5 100644 --- a/client/job-view/views/job-results-view.js +++ b/client/job-view/views/job-results-view.js @@ -340,7 +340,15 @@ module.exports = View.extend({ $(modals.presentationLinks(title, linkHeaders, links)).modal(); let copyBtn = document.querySelector('#presentationLinksModal #copy-to-clipboard'); copyBtn.addEventListener('click', function (e) { - app.copyToClipboard(links.presentation) + let onFulfilled = (value) => { + $("#copy-link-success").css("display", "inline-block"); + } + let onReject = (reason) => { + let msg = $("#copy-link-failed"); + msg.html(reason); + msg.css("display", "inline-block"); + } + app.copyToClipboard(links.presentation, onFulfilled, onReject); }); }, error: function (err, response, body) { From f6c408e7b91838084496d2938ac250443c58001a Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Thu, 26 Aug 2021 09:43:07 -0400 Subject: [PATCH 116/186] Attempmted to overwrite jupyterhub favican for jupyterhub pages. --- jupyterhub/Dockerfile.jupyterhub | 4 ++++ jupyterhub/custom/custom.js | 8 ++++++++ jupyterhub/custom/favicon.ico | Bin 0 -> 803 bytes jupyterhub/home_template.pug | 1 + 4 files changed, 13 insertions(+) create mode 100644 jupyterhub/custom/custom.js create mode 100644 jupyterhub/custom/favicon.ico diff --git a/jupyterhub/Dockerfile.jupyterhub b/jupyterhub/Dockerfile.jupyterhub index eed510b44b..f3d3156e3e 100644 --- a/jupyterhub/Dockerfile.jupyterhub +++ b/jupyterhub/Dockerfile.jupyterhub @@ -45,6 +45,10 @@ RUN python3 -m pip install --no-cache-dir \ COPY static/* /usr/local/share/jupyterhub/static/ +COPY custom/favicon.ico /usr/local/share/jupyterhub/static/favicon.ico + +COPY custom/custom.js $JUPYTER_CONFIG_DIR/custom/custom.js + COPY cull_idle_servers.py /srv/jupyterhub/cull_idle_servers.py CMD ["jupyterhub" "-f" "/srv/jupyterhub/jupyterhub_config.py"] diff --git a/jupyterhub/custom/custom.js b/jupyterhub/custom/custom.js new file mode 100644 index 0000000000..9be2875002 --- /dev/null +++ b/jupyterhub/custom/custom.js @@ -0,0 +1,8 @@ +requirejs([ + 'jquery', + 'base/js/utils', +], function($, utils + ){ + + utils.change_favicon("/hub/static/favicon.ico") +}); diff --git a/jupyterhub/custom/favicon.ico b/jupyterhub/custom/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..df7bf93e65c4a7ec037ed4e54df6077de9e6e97e GIT binary patch literal 803 zcmV+;1Kj+HP))2 zL^)9MfZbj?{r7ajgcIun-3%Z#cD?6$p8)JClj<{&cqN5}k;t}}Z{7@-6AFv32`vfn zJkR@dEPT9M#b0ki=hD9>&%CNjWik&{aZ6HJQM9ad!f}QYPBd+N9ai`O^aHRLKyvIkM92gGq4L-&<}X!#RmHN4kE@f2>zb~v+tmfxzWI7^S_aay z{7Pe`>S;OKcr Date: Thu, 26 Aug 2021 11:25:42 -0400 Subject: [PATCH 117/186] Refactored the file browser section to use the new jstree view. --- client/file-config.js | 3 +++ client/pages/project-manager.js | 20 ++++++++++++-------- client/project-config.js | 9 +++++---- client/templates/pages/projectManager.pug | 14 ++++++++++++-- client/views/jstree-view.js | 4 +--- 5 files changed, 33 insertions(+), 17 deletions(-) diff --git a/client/file-config.js b/client/file-config.js index 09d2a96ce3..c71caad96f 100644 --- a/client/file-config.js +++ b/client/file-config.js @@ -94,6 +94,8 @@ let types = { 'other' : {"icon": "jstree-icon jstree-file"}, } +let updateParent = (view, type) => {} + let validateMove = (view, node, more, pos) => { // Check if workflow is running let validSrc = Boolean(node && node.type && node.original && node.original.text !== "trash"); @@ -130,5 +132,6 @@ module.exports = { move: move, setup: setup, types: types, + updateParent: updateParent, validateMove: validateMove } diff --git a/client/pages/project-manager.js b/client/pages/project-manager.js index 0ac554d7ce..6b76eaeb5e 100644 --- a/client/pages/project-manager.js +++ b/client/pages/project-manager.js @@ -30,10 +30,11 @@ let Project = require('../models/project'); let PageView = require('./base'); let MetaDataView = require('../views/meta-data'); let ModelListing = require('../views/model-listing'); -let FileBrowser = require('../views/file-browser-view'); +// let FileBrowser = require('../views/file-browser-view'); let ArchiveListing = require('../views/archive-listing'); let WorkflowListing = require('../views/workflow-listing'); let WorkflowGroupListing = require('../views/workflow-group-listing'); +let JSTreeView = require('../views/jstree-view'); //templates let template = require('../templates/pages/projectManager.pug'); @@ -47,12 +48,13 @@ let ProjectManager = PageView.extend({ 'click [data-hook=collapse-annotation-text]' : 'changeCollapseButtonText', 'click [data-hook=new-model]' : 'handleNewModelClick', 'click [data-hook=existing-model]' : 'handleExistingModelClick', - 'click [data-hook=upload-file-btn]' : 'handleUploadModelClick', + 'click [data-hook=upload-model-btn]' : 'handleUploadModelClick', 'click [data-hook=new-ensemble-simulation]' : 'handleNewWorkflowClick', 'click [data-hook=new-parameter-sweep]' : 'handleNewWorkflowClick', 'click [data-hook=new-jupyter-notebook]' : 'handleNewWorkflowClick', 'click [data-hook=project-manager-advanced-btn]' : 'changeCollapseButtonText', 'click [data-hook=archive-btn]' : 'changeCollapseButtonText', + 'click [data-hook=collapse-browse-files]' : 'changeCollapseButtonText', 'click [data-hook=export-project-as-zip]' : 'handleExportZipClick', 'click [data-hook=export-project-as-combine]' : 'handleExportCombineClick', 'click [data-hook=empty-project-trash]' : 'handleEmptyTrashClick' @@ -298,8 +300,7 @@ let ProjectManager = PageView.extend({ } }, handleUploadModelClick: function (e) { - let type = e.target.dataset.type - this.projectFileBrowser.uploadFile(undefined, type) + this.projectFileBrowser.uploadFile(undefined, "model") }, renderArchiveCollection: function () { if(this.archiveCollectionView) { @@ -335,8 +336,9 @@ let ProjectManager = PageView.extend({ this.projectFileBrowser.remove(); } let self = this; - this.projectFileBrowser = new FileBrowser({ - root: self.model.directory + this.projectFileBrowser = new JSTreeView({ + root: self.model.directory, + configKey: "project" }); app.registerRenderSubview(this, this.projectFileBrowser, "file-browser"); }, @@ -401,8 +403,10 @@ let ProjectManager = PageView.extend({ this.queryByHook("model-listing") ); }, - update: function (target) { - this.projectFileBrowser.refreshJSTree(); + update: function (target, from) { + if(from !== "file-browser") { + this.projectFileBrowser.refreshJSTree(null); + } let fetchTypes = ["Model", "Workflow", "WorkflowGroup", "Archive"]; if(fetchTypes.includes(target)) { let self = this; diff --git a/client/project-config.js b/client/project-config.js index cfe488674a..be0be6f011 100644 --- a/client/project-config.js +++ b/client/project-config.js @@ -16,6 +16,7 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . */ +let $ = require('jquery'); let path = require('path'); //support files let app = require('./app'); @@ -91,13 +92,13 @@ let updateParent = (view, type) => { let models = ["nonspatial", "spatial", "sbml", "model"]; let workflows = ["workflow", "notebook"]; if(models.includes(type)) { - view.parent.update("Model"); + view.parent.update("Model", "file-browser"); }else if(workflows.includes(type)) { - view.parent.update("Workflow"); + view.parent.update("Workflow", "file-browser"); }else if(type === "workflow-group") { - view.parent.update("WorkflowGroup"); + view.parent.update("WorkflowGroup", "file-browser"); }else if(type === "Archive") { - view.parent.update(type); + view.parent.update(type, "file-browser"); } } diff --git a/client/templates/pages/projectManager.pug b/client/templates/pages/projectManager.pug index 6481baa211..59bd4742bb 100644 --- a/client/templates/pages/projectManager.pug +++ b/client/templates/pages/projectManager.pug @@ -55,7 +55,7 @@ section.page li.dropdown-divider li.dropdown-item(id="existing-model" data-hook="existing-model") Existing Model li.dropdown-divider - li.dropdown-item(id="upload-file-btn" data-hook="upload-file-btn", data-type="model") Upload StochSS Model (.mdl or .smdl) + li.dropdown-item(id="upload-model-btn" data-hook="upload-model-btn") Upload StochSS Model (.mdl or .smdl) div.verticle-space-3(id=this.model.elementID + "-workflows-section" style="display: none;") @@ -109,7 +109,17 @@ section.page div(id="project-meta-data-container" data-hook="project-meta-data-container") - div(id="project-manager-file-browser" data-hook="file-browser") + div#browse-files.card.card-body + + div + + h3.inline Browse Files + div.inline + button.btn.btn-outline-collapse(data-toggle="collapse", data-target="#collapse-browse-files", id="collapse-browse-files-btn" data-hook="collapse-browse-files") + + + div.collapse(id="collapse-browse-files") + + div(id="project-manager-file-browser" data-hook="file-browser") div diff --git a/client/views/jstree-view.js b/client/views/jstree-view.js index 33baaafd50..2b267a408f 100644 --- a/client/views/jstree-view.js +++ b/client/views/jstree-view.js @@ -485,9 +485,7 @@ module.exports = View.extend({ success: (err, response, body) => { body = JSON.parse(body); this.refreshJSTree(node); - if(inProject && ['mdl', 'smdl', 'sbml'].includes(body.file.split('.').pop())) { - this.config.updateParent("model") - } + this.config.updateParent(this, "model") if(body.errors.length > 0){ if(document.querySelector("#uploadFileErrorsModal")) { document.querySelector("#uploadFileErrorsModal").remove(); From 0f5db4a6536667d42e133ebae3bcf245f4a7dc9f Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Thu, 26 Aug 2021 17:24:19 -0400 Subject: [PATCH 118/186] Moved the root context menu options to the new files. Moved the download and rename functions to the jstree view file. --- client/file-config.js | 21 ++ client/pages/file-browser.js | 578 +++++++++++++++--------------- client/project-config.js | 16 + client/views/file-browser-view.js | 490 ++++++++++++------------- client/views/jstree-view.js | 323 ++++++++++++++++- 5 files changed, 875 insertions(+), 553 deletions(-) diff --git a/client/file-config.js b/client/file-config.js index c71caad96f..b38a0a56fc 100644 --- a/client/file-config.js +++ b/client/file-config.js @@ -51,6 +51,26 @@ let doubleClick = (view, e) => { } } +let getRootContext = (view, node) => { + let dirname = node.original._path === "/" ? "" : node.original._path; + return { + refresh: view.getRefreshContext(node), + newDirectory: view.getNewDirectoryContext(node), + newProject: { + label: "New Project", + _disabled: false, + separator_before: false, + separator_after: false, + action: (data) => { + view.createProject(node, dirname); + } + }, + newModel: view.getNewModelContext(node, false), + newDomain: view.getNewDomainContext(node), + upload: view.getFullUploadContext(node, false) + } +} + let move = (view, par, node) => { let newDir = par.original._path !== "/" ? par.original._path : ""; let file = node.original._path.split('/').pop(); @@ -129,6 +149,7 @@ let validateMove = (view, node, more, pos) => { module.exports = { contextZipTypes: contextZipTypes, doubleClick: doubleClick, + getRootContext: getRootContext, move: move, setup: setup, types: types, diff --git a/client/pages/file-browser.js b/client/pages/file-browser.js index 1640d8821d..0a4ce6e97a 100644 --- a/client/pages/file-browser.js +++ b/client/pages/file-browser.js @@ -540,120 +540,120 @@ let FileBrowser = PageView.extend({ } }); }, - renameNode: function (o) { - var self = this - var text = o.text; - var parent = $('#models-jstree').jstree().get_node(o.parent) - var extensionWarning = $(this.queryByHook('extension-warning')); - var nameWarning = $(this.queryByHook('rename-warning')); - extensionWarning.collapse('show') - $('#models-jstree').jstree().edit(o, null, function(node, status) { - if(text != node.text){ - var endpoint = path.join(app.getApiPath(), "file/rename")+"?path="+ o.original._path+"&name="+node.text - app.getXHR(endpoint, { - always: function (err, response, body) { - if(parent.type === "root"){ - self.refreshJSTree(); - }else{ - $('#models-jstree').jstree().refresh_node(parent); - } - }, - success: function (err, response, body) { - if(body.changed) { - nameWarning.text(body.message); - nameWarning.collapse('show'); - window.scrollTo(0,0); - setTimeout(_.bind(self.hideNameWarning, self), 10000); - } - node.original._path = body._path; - } - }); - } - extensionWarning.collapse('hide'); - nameWarning.collapse('hide'); - }); - }, - hideNameWarning: function () { - $(this.queryByHook('rename-warning')).collapse('hide') - }, - getExportData: function (o, asZip) { - var self = this; - let nodeType = o.original.type - let isJSON = nodeType === "sbml-model" ? false : true - if(nodeType === "sbml-model"){ - var dataType = "plain-text" - var identifier = "file/download" - }else if(nodeType === "domain") { - var dataType = "json" - var identifier = "spatial-model/load-domain" - }else if(asZip) { - var dataType = "zip" - var identifier = "file/download-zip" - }else{ - var dataType = "json" - var identifier = "file/json-data" - } - if(nodeType === "domain") { - var queryStr = "?domain_path=" + o.original._path - }else{ - var queryStr = "?path="+o.original._path - if(dataType === "json"){ - queryStr = queryStr.concat("&for=None") - }else if(dataType === "zip"){ - queryStr = queryStr.concat("&action=generate") - } - } - var endpoint = path.join(app.getApiPath(), identifier)+queryStr - app.getXHR(endpoint, { - success: function (err, response, body) { - if(dataType === "json") { - let data = nodeType === "domain" ? body.domain : body; - self.exportToJsonFile(data, o.original.text); - }else if(dataType === "zip") { - var node = $('#models-jstree').jstree().get_node(o.parent); - if(node.type === "root"){ - self.refreshJSTree(); - }else{ - $('#models-jstree').jstree().refresh_node(node); - } - self.exportToZipFile(body.Path); - }else{ - self.exportToFile(body, o.original.text); - } - }, - error: function (err, response, body) { - if(dataType === "plain-text") { - body = JSON.parse(body); - } - } - }); - }, - exportToJsonFile: function (fileData, fileName) { - let dataStr = JSON.stringify(fileData); - let dataURI = 'data:application/json;charset=utf-8,' + encodeURIComponent(dataStr); - let exportFileDefaultName = fileName + // renameNode: function (o) { + // var self = this + // var text = o.text; + // var parent = $('#models-jstree').jstree().get_node(o.parent) + // var extensionWarning = $(this.queryByHook('extension-warning')); + // var nameWarning = $(this.queryByHook('rename-warning')); + // extensionWarning.collapse('show') + // $('#models-jstree').jstree().edit(o, null, function(node, status) { + // if(text != node.text){ + // var endpoint = path.join(app.getApiPath(), "file/rename")+"?path="+ o.original._path+"&name="+node.text + // app.getXHR(endpoint, { + // always: function (err, response, body) { + // if(parent.type === "root"){ + // self.refreshJSTree(); + // }else{ + // $('#models-jstree').jstree().refresh_node(parent); + // } + // }, + // success: function (err, response, body) { + // if(body.changed) { + // nameWarning.text(body.message); + // nameWarning.collapse('show'); + // window.scrollTo(0,0); + // setTimeout(_.bind(self.hideNameWarning, self), 10000); + // } + // node.original._path = body._path; + // } + // }); + // } + // extensionWarning.collapse('hide'); + // nameWarning.collapse('hide'); + // }); + // }, + // hideNameWarning: function () { + // $(this.queryByHook('rename-warning')).collapse('hide') + // }, + // getExportData: function (o, asZip) { + // var self = this; + // let nodeType = o.original.type + // let isJSON = nodeType === "sbml-model" ? false : true + // if(nodeType === "sbml-model"){ + // var dataType = "plain-text" + // var identifier = "file/download" + // }else if(nodeType === "domain") { + // var dataType = "json" + // var identifier = "spatial-model/load-domain" + // }else if(asZip) { + // var dataType = "zip" + // var identifier = "file/download-zip" + // }else{ + // var dataType = "json" + // var identifier = "file/json-data" + // } + // if(nodeType === "domain") { + // var queryStr = "?domain_path=" + o.original._path + // }else{ + // var queryStr = "?path="+o.original._path + // if(dataType === "json"){ + // queryStr = queryStr.concat("&for=None") + // }else if(dataType === "zip"){ + // queryStr = queryStr.concat("&action=generate") + // } + // } + // var endpoint = path.join(app.getApiPath(), identifier)+queryStr + // app.getXHR(endpoint, { + // success: function (err, response, body) { + // if(dataType === "json") { + // let data = nodeType === "domain" ? body.domain : body; + // self.exportToJsonFile(data, o.original.text); + // }else if(dataType === "zip") { + // var node = $('#models-jstree').jstree().get_node(o.parent); + // if(node.type === "root"){ + // self.refreshJSTree(); + // }else{ + // $('#models-jstree').jstree().refresh_node(node); + // } + // self.exportToZipFile(body.Path); + // }else{ + // self.exportToFile(body, o.original.text); + // } + // }, + // error: function (err, response, body) { + // if(dataType === "plain-text") { + // body = JSON.parse(body); + // } + // } + // }); + // }, + // exportToJsonFile: function (fileData, fileName) { + // let dataStr = JSON.stringify(fileData); + // let dataURI = 'data:application/json;charset=utf-8,' + encodeURIComponent(dataStr); + // let exportFileDefaultName = fileName - let linkElement = document.createElement('a'); - linkElement.setAttribute('href', dataURI); - linkElement.setAttribute('download', exportFileDefaultName); - linkElement.click(); - }, - exportToFile: function (fileData, fileName) { - let dataURI = 'data:text/plain;charset=utf-8,' + encodeURIComponent(fileData); + // let linkElement = document.createElement('a'); + // linkElement.setAttribute('href', dataURI); + // linkElement.setAttribute('download', exportFileDefaultName); + // linkElement.click(); + // }, + // exportToFile: function (fileData, fileName) { + // let dataURI = 'data:text/plain;charset=utf-8,' + encodeURIComponent(fileData); - let linkElement = document.createElement('a'); - linkElement.setAttribute('href', dataURI); - linkElement.setAttribute('download', fileName); - linkElement.click(); - }, - exportToZipFile: function (o) { - var targetPath = o - if(o.original){ - targetPath = o.original._path - } - var endpoint = path.join(app.getBasePath(), "/files", targetPath); - window.open(endpoint) - }, + // let linkElement = document.createElement('a'); + // linkElement.setAttribute('href', dataURI); + // linkElement.setAttribute('download', fileName); + // linkElement.click(); + // }, + // exportToZipFile: function (o) { + // var targetPath = o + // if(o.original){ + // targetPath = o.original._path + // } + // var endpoint = path.join(app.getBasePath(), "/files", targetPath); + // window.open(endpoint) + // }, // validateName: function (input, rename = false) { // var error = "" // if(input.endsWith('/')) { @@ -735,53 +735,53 @@ let FileBrowser = PageView.extend({ } }); }, - addExistingModel: function (o) { - var self = this - if(document.querySelector('#newProjectModelModal')){ - document.querySelector('#newProjectModelModal').remove() - } - let mdlListEP = path.join(app.getApiPath(), 'project/add-existing-model') + "?path="+o.original._path - app.getXHR(mdlListEP, { - always: function (err, response, body) { - let modal = $(modals.newProjectModelHtml(body.files)).modal(); - let okBtn = document.querySelector('#newProjectModelModal .ok-model-btn'); - let select = document.querySelector('#newProjectModelModal #modelFileInput'); - let location = document.querySelector('#newProjectModelModal #modelPathInput'); - select.addEventListener("change", function (e) { - okBtn.disabled = e.target.value && body.paths[e.target.value].length >= 2; - if(body.paths[e.target.value].length >= 2) { - var locations = body.paths[e.target.value].map(function (path) { - return ``; - }); - locations.unshift(``); - locations = locations.join(" "); - $("#modelPathInput").find('option').remove().end().append(locations); - $("#location-container").css("display", "block"); - }else{ - $("#location-container").css("display", "none"); - $("#modelPathInput").find('option').remove().end(); - } - }); - location.addEventListener("change", function (e) { - okBtn.disabled = !Boolean(e.target.value); - }); - okBtn.addEventListener("click", function (e) { - let mdlPath = body.paths[select.value].length < 2 ? body.paths[select.value][0] : location.value; - let queryString = "?path="+o.original._path+"&mdlPath="+mdlPath; - let endpoint = path.join(app.getApiPath(), 'project/add-existing-model') + queryString; - app.postXHR(endpoint, null, { - success: function (err, response, body) { - let successModal = $(modals.newProjectModelSuccessHtml(body.message)).modal(); - }, - error: function (err, response, body) { - let errorModal = $(modals.newProjectModelErrorHtml(body.Reason, body.Message)).modal(); - } - }); - modal.modal('hide'); - }); - } - }); - }, + // addExistingModel: function (o) { + // var self = this + // if(document.querySelector('#newProjectModelModal')){ + // document.querySelector('#newProjectModelModal').remove() + // } + // let mdlListEP = path.join(app.getApiPath(), 'project/add-existing-model') + "?path="+o.original._path + // app.getXHR(mdlListEP, { + // always: function (err, response, body) { + // let modal = $(modals.newProjectModelHtml(body.files)).modal(); + // let okBtn = document.querySelector('#newProjectModelModal .ok-model-btn'); + // let select = document.querySelector('#newProjectModelModal #modelFileInput'); + // let location = document.querySelector('#newProjectModelModal #modelPathInput'); + // select.addEventListener("change", function (e) { + // okBtn.disabled = e.target.value && body.paths[e.target.value].length >= 2; + // if(body.paths[e.target.value].length >= 2) { + // var locations = body.paths[e.target.value].map(function (path) { + // return ``; + // }); + // locations.unshift(``); + // locations = locations.join(" "); + // $("#modelPathInput").find('option').remove().end().append(locations); + // $("#location-container").css("display", "block"); + // }else{ + // $("#location-container").css("display", "none"); + // $("#modelPathInput").find('option').remove().end(); + // } + // }); + // location.addEventListener("change", function (e) { + // okBtn.disabled = !Boolean(e.target.value); + // }); + // okBtn.addEventListener("click", function (e) { + // let mdlPath = body.paths[select.value].length < 2 ? body.paths[select.value][0] : location.value; + // let queryString = "?path="+o.original._path+"&mdlPath="+mdlPath; + // let endpoint = path.join(app.getApiPath(), 'project/add-existing-model') + queryString; + // app.postXHR(endpoint, null, { + // success: function (err, response, body) { + // let successModal = $(modals.newProjectModelSuccessHtml(body.message)).modal(); + // }, + // error: function (err, response, body) { + // let errorModal = $(modals.newProjectModelErrorHtml(body.Reason, body.Message)).modal(); + // } + // }); + // modal.modal('hide'); + // }); + // } + // }); + // }, // addModel: function (parentPath, modelName, message) { // var endpoint = path.join(app.getBasePath(), "stochss/models/edit") // if(parentPath.endsWith(".proj")) { @@ -1012,28 +1012,28 @@ let FileBrowser = PageView.extend({ // let asZip = zipTypes.includes(nodeType) // common to all type except root let common = { - "Download" : { - "label" : asZip ? "Download as .zip" : "Download", - "_disabled" : false, - "separator_before" : true, - "separator_after" : false, - "action" : function (data) { - if(o.original.text.endsWith('.zip')){ - self.exportToZipFile(o); - }else{ - self.getExportData(o, asZip) - } - } - }, - "Rename" : { - "label" : "Rename", - "_disabled" : (o.type === "workflow" && o.original._status === "running"), - "separator_before" : false, - "separator_after" : false, - "action" : function (data) { - self.renameNode(o); - } - }, + // "Download" : { + // "label" : asZip ? "Download as .zip" : "Download", + // "_disabled" : false, + // "separator_before" : true, + // "separator_after" : false, + // "action" : function (data) { + // if(o.original.text.endsWith('.zip')){ + // self.exportToZipFile(o); + // }else{ + // self.getExportData(o, asZip) + // } + // } + // }, + // "Rename" : { + // "label" : "Rename", + // "_disabled" : (o.type === "workflow" && o.original._status === "running"), + // "separator_before" : false, + // "separator_after" : false, + // "action" : function (data) { + // self.renameNode(o); + // } + // }, "Duplicate" : { "label" : (nodeType === "workflow") ? "Duplicate as new" : "Duplicate", "_disabled" : (nodeType === "project"), @@ -1065,111 +1065,111 @@ let FileBrowser = PageView.extend({ } } // common to root and folders - let folder = { - "Refresh" : { - "label" : "Refresh", - "_disabled" : false, - "_class" : "font-weight-bold", - "separator_before" : false, - "separator_after" : true, - "action" : function (data) { - if(nodeType === "root"){ - self.refreshJSTree(); - }else{ - $('#models-jstree').jstree().refresh_node(o); - } - } - }, - "New_Directory" : { - "label" : "New Directory", - "_disabled" : false, - "separator_before" : false, - "separator_after" : false, - "action" : function (data) { - self.newModelOrDirectory(o, false, false); - } - }, - "New Project" : { - "label" : "New Project", - "_disabled" : false, - "separator_before" : false, - "separator_after" : false, - "action" : function (data) { - self.newProjectOrWorkflowGroup(o, true) - } - }, - "New_model" : { - "label" : "New Model", - "_disabled" : false, - "separator_before" : false, - "separator_after" : false, - "submenu" : { - "spatial" : { - "label" : "Spatial (beta)", - "_disabled" : false, - "separator_before" : false, - "separator_after" : false, - "action" : function (data) { - self.newModelOrDirectory(o, true, true); - } - }, - "nonspatial" : { - "label" : "Non-Spatial", - "_disabled" : false, - "separator_before" : false, - "separator_after" : false, - "action" : function (data) { - self.newModelOrDirectory(o, true, false); - } - } - } - }, - "New Domain" : { - "label" : "New Domain (beta)", - "_disabled" : false, - "separator_before" : false, - "separator_after" : false, - "action" : function (data) { - let queryStr = "?domainPath=" + o.original._path + "&new" - window.location.href = path.join(app.getBasePath(), "stochss/domain/edit") + queryStr - } - }, - "Upload" : { - "label" : "Upload File", - "_disabled" : false, - "separator_before" : false, - "separator_after" : false, - "submenu" : { - "Model" : { - "label" : "StochSS Model", - "_disabled" : false, - "separator_before" : false, - "separator_after" : false, - "action" : function (data) { - self.uploadFile(o, "model") - } - }, - "SBML" : { - "label" : "SBML Model", - "_disabled" : false, - "separator_before" : false, - "separator_after" : false, - "action" : function (data) { - self.uploadFile(o, "sbml") - } - }, - "File" : { - "label" : "File", - "_disabled" : false, - "separator_before" : false, - "separator_after" : false, - "action" : function (data) { - self.uploadFile(o, "file") - } - } - } - } - } + // let folder = { + // "Refresh" : { + // "label" : "Refresh", + // "_disabled" : false, + // "_class" : "font-weight-bold", + // "separator_before" : false, + // "separator_after" : true, + // "action" : function (data) { + // if(nodeType === "root"){ + // self.refreshJSTree(); + // }else{ + // $('#models-jstree').jstree().refresh_node(o); + // } + // } + // }, + // "New_Directory" : { + // "label" : "New Directory", + // "_disabled" : false, + // "separator_before" : false, + // "separator_after" : false, + // "action" : function (data) { + // self.newModelOrDirectory(o, false, false); + // } + // }, + // "New Project" : { + // "label" : "New Project", + // "_disabled" : false, + // "separator_before" : false, + // "separator_after" : false, + // "action" : function (data) { + // self.newProjectOrWorkflowGroup(o, true) + // } + // }, + // "New_model" : { + // "label" : "New Model", + // "_disabled" : false, + // "separator_before" : false, + // "separator_after" : false, + // "submenu" : { + // "spatial" : { + // "label" : "Spatial (beta)", + // "_disabled" : false, + // "separator_before" : false, + // "separator_after" : false, + // "action" : function (data) { + // self.newModelOrDirectory(o, true, true); + // } + // }, + // "nonspatial" : { + // "label" : "Non-Spatial", + // "_disabled" : false, + // "separator_before" : false, + // "separator_after" : false, + // "action" : function (data) { + // self.newModelOrDirectory(o, true, false); + // } + // } + // } + // }, + // "New Domain" : { + // "label" : "New Domain (beta)", + // "_disabled" : false, + // "separator_before" : false, + // "separator_after" : false, + // "action" : function (data) { + // let queryStr = "?domainPath=" + o.original._path + "&new" + // window.location.href = path.join(app.getBasePath(), "stochss/domain/edit") + queryStr + // } + // }, + // "Upload" : { + // "label" : "Upload File", + // "_disabled" : false, + // "separator_before" : false, + // "separator_after" : false, + // "submenu" : { + // "Model" : { + // "label" : "StochSS Model", + // "_disabled" : false, + // "separator_before" : false, + // "separator_after" : false, + // "action" : function (data) { + // self.uploadFile(o, "model") + // } + // }, + // "SBML" : { + // "label" : "SBML Model", + // "_disabled" : false, + // "separator_before" : false, + // "separator_after" : false, + // "action" : function (data) { + // self.uploadFile(o, "sbml") + // } + // }, + // "File" : { + // "label" : "File", + // "_disabled" : false, + // "separator_before" : false, + // "separator_after" : false, + // "action" : function (data) { + // self.uploadFile(o, "file") + // } + // } + // } + // } + // } // common to both spatial and non-spatial models let newWorkflow = { "ensembleSimulation" : { @@ -1425,9 +1425,9 @@ let FileBrowser = PageView.extend({ } } } - if (o.type === 'root'){ - return folder - } + // if (o.type === 'root'){ + // return folder + // } if (o.text === "trash") { return {"Refresh": folder.Refresh} } diff --git a/client/project-config.js b/client/project-config.js index be0be6f011..b8e9b3f3bd 100644 --- a/client/project-config.js +++ b/client/project-config.js @@ -48,6 +48,21 @@ let doubleClick = (view, e) => { } } +let getRootContext = (view, node) => { + let upload = node.type === "root" ? + view.getFullUploadContext(node, true) : + view.getFileUploadContext(node, true); + return { + refresh: view.getRefreshContext(node), + addModel: view.getAddModelContext(node), + newDirectory: view.getNewDirectoryContext(node), + newDomain: view.getNewDomainContext(node), + upload: upload, + download: view.getDownloadWCombineContext(node), + rename: view.getRenameContext(node) + } +} + let move = (view, par, node) => { let newDir = par.original._path !== "/" ? par.original._path : ""; let file = node.original._path.split('/').pop(); @@ -167,6 +182,7 @@ let validateMove = (view, node, more, pos) => { module.exports = { contextZipTypes: contextZipTypes, doubleClick: doubleClick, + getRootContext: getRootContext, move: move, setup: setup, types: types, diff --git a/client/views/file-browser-view.js b/client/views/file-browser-view.js index 32490b342e..65b7f036bd 100644 --- a/client/views/file-browser-view.js +++ b/client/views/file-browser-view.js @@ -33,7 +33,7 @@ let Model = require('../models/model'); // module.exports = View.extend({ // template: template, events: { - 'click [data-hook=collapse-browse-files]' : 'changeCollapseButtonText', + // 'click [data-hook=collapse-browse-files]' : 'changeCollapseButtonText', // 'click [data-hook=refresh-jstree]' : 'refreshJSTree', // 'click [data-hook=options-for-node]' : 'showContextMenuForNode', // 'click [data-hook=new-directory]' : 'handleCreateDirectoryClick', @@ -524,116 +524,116 @@ let Model = require('../models/model'); } }); }, - renameNode: function (o) { - var self = this - var text = o.text; - var parent = $('#models-jstree-view').jstree().get_node(o.parent) - var extensionWarning = $(this.queryByHook('extension-warning')); - var nameWarning = $(this.queryByHook('rename-warning')); - extensionWarning.collapse('show') - $('#models-jstree-view').jstree().edit(o, null, function(node, status) { - if(text != node.text){ - let name = node.type === "root" ? node.text + ".proj" : node.text - var endpoint = path.join(app.getApiPath(), "file/rename")+"?path="+ o.original._path+"&name="+name - app.getXHR(endpoint, { - always: function (err, response, body) { - if(parent.type === "root"){ - self.refreshJSTree(); - }else{ - $('#models-jstree-view').jstree().refresh_node(parent); - } - }, - success: function (err, response, body) { - if(body.changed) { - nameWarning.text(body.message); - nameWarning.collapse('show'); - window.scrollTo(0,0); - setTimeout(_.bind(self.hideNameWarning, self), 10000); - } - node.original._path = body._path; - } - }); - } - extensionWarning.collapse('hide'); - nameWarning.collapse('hide'); - }); - }, - hideNameWarning: function () { - $(this.queryByHook('rename-warning')).collapse('hide') - }, - getExportData: function (o, asZip) { - var self = this; - let nodeType = o.original.type - let isJSON = nodeType === "sbml-model" ? false : true - if(nodeType === "sbml-model"){ - var dataType = "plain-text" - var identifier = "file/download" - }else if(nodeType === "domain") { - var dataType = "json" - var identifier = "spatial-model/load-domain" - }else if(asZip) { - var dataType = "zip" - var identifier = "file/download-zip" - }else{ - var dataType = "json" - var identifier = "file/json-data" - } - if(nodeType === "domain") { - var queryStr = "?domain_path=" + o.original._path - }else{ - var queryStr = "?path="+o.original._path - if(dataType === "json"){ - queryStr = queryStr.concat("&for=None") - }else if(dataType === "zip"){ - queryStr = queryStr.concat("&action=generate") - } - } - var endpoint = path.join(app.getApiPath(), identifier)+queryStr - app.getXHR(endpoint, { - success: function (err, response, body) { - if(dataType === "json") { - let data = nodeType === "domain" ? body.domain : body; - self.exportToJsonFile(data, o.original.text); - }else if(dataType === "zip") { - var node = $('#models-jstree-view').jstree().get_node(o.parent); - if(node.type === "root"){ - self.refreshJSTree(); - }else{ - $('#models-jstree-view').jstree().refresh_node(node); - } - self.exportToZipFile(body.Path); - }else{ - self.exportToFile(body, o.original.text); - } - } - }); - }, - exportToJsonFile: function (fileData, fileName) { - let dataStr = JSON.stringify(fileData); - let dataURI = 'data:application/json;charset=utf-8,' + encodeURIComponent(dataStr); - let exportFileDefaultName = fileName + // renameNode: function (o) { + // var self = this + // var text = o.text; + // var parent = $('#models-jstree-view').jstree().get_node(o.parent) + // var extensionWarning = $(this.queryByHook('extension-warning')); + // var nameWarning = $(this.queryByHook('rename-warning')); + // extensionWarning.collapse('show') + // $('#models-jstree-view').jstree().edit(o, null, function(node, status) { + // if(text != node.text){ + // let name = node.type === "root" ? node.text + ".proj" : node.text + // var endpoint = path.join(app.getApiPath(), "file/rename")+"?path="+ o.original._path+"&name="+name + // app.getXHR(endpoint, { + // always: function (err, response, body) { + // if(parent.type === "root"){ + // self.refreshJSTree(); + // }else{ + // $('#models-jstree-view').jstree().refresh_node(parent); + // } + // }, + // success: function (err, response, body) { + // if(body.changed) { + // nameWarning.text(body.message); + // nameWarning.collapse('show'); + // window.scrollTo(0,0); + // setTimeout(_.bind(self.hideNameWarning, self), 10000); + // } + // node.original._path = body._path; + // } + // }); + // } + // extensionWarning.collapse('hide'); + // nameWarning.collapse('hide'); + // }); + // }, + // hideNameWarning: function () { + // $(this.queryByHook('rename-warning')).collapse('hide') + // }, + // getExportData: function (o, asZip) { + // var self = this; + // let nodeType = o.original.type + // let isJSON = nodeType === "sbml-model" ? false : true + // if(nodeType === "sbml-model"){ + // var dataType = "plain-text" + // var identifier = "file/download" + // }else if(nodeType === "domain") { + // var dataType = "json" + // var identifier = "spatial-model/load-domain" + // }else if(asZip) { + // var dataType = "zip" + // var identifier = "file/download-zip" + // }else{ + // var dataType = "json" + // var identifier = "file/json-data" + // } + // if(nodeType === "domain") { + // var queryStr = "?domain_path=" + o.original._path + // }else{ + // var queryStr = "?path="+o.original._path + // if(dataType === "json"){ + // queryStr = queryStr.concat("&for=None") + // }else if(dataType === "zip"){ + // queryStr = queryStr.concat("&action=generate") + // } + // } + // var endpoint = path.join(app.getApiPath(), identifier)+queryStr + // app.getXHR(endpoint, { + // success: function (err, response, body) { + // if(dataType === "json") { + // let data = nodeType === "domain" ? body.domain : body; + // self.exportToJsonFile(data, o.original.text); + // }else if(dataType === "zip") { + // var node = $('#models-jstree-view').jstree().get_node(o.parent); + // if(node.type === "root"){ + // self.refreshJSTree(); + // }else{ + // $('#models-jstree-view').jstree().refresh_node(node); + // } + // self.exportToZipFile(body.Path); + // }else{ + // self.exportToFile(body, o.original.text); + // } + // } + // }); + // }, + // exportToJsonFile: function (fileData, fileName) { + // let dataStr = JSON.stringify(fileData); + // let dataURI = 'data:application/json;charset=utf-8,' + encodeURIComponent(dataStr); + // let exportFileDefaultName = fileName - let linkElement = document.createElement('a'); - linkElement.setAttribute('href', dataURI); - linkElement.setAttribute('download', exportFileDefaultName); - linkElement.click(); - }, - exportToFile: function (fileData, fileName) { - let dataURI = 'data:text/plain;charset=utf-8,' + encodeURIComponent(fileData); + // let linkElement = document.createElement('a'); + // linkElement.setAttribute('href', dataURI); + // linkElement.setAttribute('download', exportFileDefaultName); + // linkElement.click(); + // }, + // exportToFile: function (fileData, fileName) { + // let dataURI = 'data:text/plain;charset=utf-8,' + encodeURIComponent(fileData); - let linkElement = document.createElement('a'); - linkElement.setAttribute('href', dataURI); - linkElement.setAttribute('download', fileName); - linkElement.click(); - }, - exportToZipFile: function (o) { - var targetPath = o - if(o.original){ - targetPath = o.original._path - } - var endpoint = path.join(app.getBasePath(), "/files", targetPath); - window.open(endpoint) - }, + // let linkElement = document.createElement('a'); + // linkElement.setAttribute('href', dataURI); + // linkElement.setAttribute('download', fileName); + // linkElement.click(); + // }, + // exportToZipFile: function (o) { + // var targetPath = o + // if(o.original){ + // targetPath = o.original._path + // } + // var endpoint = path.join(app.getBasePath(), "/files", targetPath); + // window.open(endpoint) + // }, // validateName(input, rename = false) { // var error = "" // if(input.endsWith('/')) { @@ -995,22 +995,22 @@ let Model = require('../models/model'); // let zipTypes = ["workflow", "folder", "other", "root", "workflow-group"] // let asZip = zipTypes.includes(nodeType) // refresh context menu option - let refresh = { - "Refresh" : { - "label" : "Refresh", - "_disabled" : false, - "_class" : "font-weight-bold", - "separator_before" : false, - "separator_after" : o.text !== "trash", - "action" : function (data) { - if(nodeType === "root"){ - self.refreshJSTree(); - }else{ - $('#models-jstree-view').jstree().refresh_node(o); - } - } - } - } + // let refresh = { + // "Refresh" : { + // "label" : "Refresh", + // "_disabled" : false, + // "_class" : "font-weight-bold", + // "separator_before" : false, + // "separator_after" : o.text !== "trash", + // "action" : function (data) { + // if(nodeType === "root"){ + // self.refreshJSTree(); + // }else{ + // $('#models-jstree-view').jstree().refresh_node(o); + // } + // } + // } + // } // For notebooks, workflows, sbml models, and other files let open = { "Open" : { @@ -1041,51 +1041,51 @@ let Model = require('../models/model'); } } // project contect menu option - let project = { - "Add_Model" : { - "label" : "Add Model", - "_disabled" : false, - "separator_before" : false, - "separator_after" : false, - "submenu" : { - "New_model" : { - "label" : "New Model", - "_disabled" : false, - "separator_before" : false, - "separator_after" : false, - "submenu" : { - "spatial" : { - "label" : "Spatial (beta)", - "_disabled" : false, - "separator_before" : false, - "separator_after" : false, - "action" : function (data) { - self.newModelOrDirectory(o, true, true); - } - }, - "nonspatial" : { - "label" : "Non-Spatial", - "_disabled" : false, - "separator_before" : false, - "separator_after" : false, - "action" : function (data) { - self.newModelOrDirectory(o, true, false); - } - } - } - }, - "Existing Model" : { - "label" : "Existing Model", - "_disabled" : false, - "separator_before" : false, - "separator_after" : false, - "action" : function (data) { - self.addExistingModel(o) - } - } - } - } - } + // let project = { + // "Add_Model" : { + // "label" : "Add Model", + // "_disabled" : false, + // "separator_before" : false, + // "separator_after" : false, + // "submenu" : { + // "New_model" : { + // "label" : "New Model", + // "_disabled" : false, + // "separator_before" : false, + // "separator_after" : false, + // "submenu" : { + // "spatial" : { + // "label" : "Spatial (beta)", + // "_disabled" : false, + // "separator_before" : false, + // "separator_after" : false, + // "action" : function (data) { + // self.newModelOrDirectory(o, true, true); + // } + // }, + // "nonspatial" : { + // "label" : "Non-Spatial", + // "_disabled" : false, + // "separator_before" : false, + // "separator_after" : false, + // "action" : function (data) { + // self.newModelOrDirectory(o, true, false); + // } + // } + // } + // }, + // "Existing Model" : { + // "label" : "Existing Model", + // "_disabled" : false, + // "separator_before" : false, + // "separator_after" : false, + // "action" : function (data) { + // self.addExistingModel(o) + // } + // } + // } + // } + // } // option for uploading files let uploadFile = { "Upload": { @@ -1129,70 +1129,70 @@ let Model = require('../models/model'); } } // common to folder and root - let commonFolder = { - "New_Directory" : { - "label" : "New Directory", - "_disabled" : false, - "separator_before" : false, - "separator_after" : false, - "action" : function (data) { - self.newModelOrDirectory(o, false, false); - } - }, - "New Domain" : { - "label" : "New Domain (beta)", - "_disabled" : false, - "separator_before" : false, - "separator_after" : false, - "action" : function (data) { - let queryStr = "?domainPath=" + o.original._path + "&new" - window.location.href = path.join(app.getBasePath(), "stochss/domain/edit") + queryStr - } - }, - "Upload": o.type === "root" ? uploadAll.Upload : uploadFile.Upload - } - if(o.type === "root" || o.type === "workflow-group" || o.type === "workflow") - var downloadLabel = "as .zip" - else if(asZip) - var downloadLabel = "Download as .zip" - else - var downloadLabel = "Download" - let download = { - "Download" : { - "label" : downloadLabel, - "_disabled" : false, - "separator_before" : false, - "separator_after" : !(o.type === "root" || o.type === "workflow-group" || o.type === "workflow"), - "action" : function (data) { - if(o.original.text.endsWith('.zip')){ - self.exportToZipFile(o); - }else{ - self.getExportData(o, asZip) - } - } - } - } + // let commonFolder = { + // "New_Directory" : { + // "label" : "New Directory", + // "_disabled" : false, + // "separator_before" : false, + // "separator_after" : false, + // "action" : function (data) { + // self.newModelOrDirectory(o, false, false); + // } + // }, + // "New Domain" : { + // "label" : "New Domain (beta)", + // "_disabled" : false, + // "separator_before" : false, + // "separator_after" : false, + // "action" : function (data) { + // let queryStr = "?domainPath=" + o.original._path + "&new" + // window.location.href = path.join(app.getBasePath(), "stochss/domain/edit") + queryStr + // } + // }, + // "Upload": o.type === "root" ? uploadAll.Upload : uploadFile.Upload + // } + // if(o.type === "root" || o.type === "workflow-group" || o.type === "workflow") + // var downloadLabel = "as .zip" + // else if(asZip) + // var downloadLabel = "Download as .zip" + // else + // var downloadLabel = "Download" + // let download = { + // "Download" : { + // "label" : downloadLabel, + // "_disabled" : false, + // "separator_before" : false, + // "separator_after" : !(o.type === "root" || o.type === "workflow-group" || o.type === "workflow"), + // "action" : function (data) { + // if(o.original.text.endsWith('.zip')){ + // self.exportToZipFile(o); + // }else{ + // self.getExportData(o, asZip) + // } + // } + // } + // } // download options for .zip and COMBINE - let downloadWCombine = { - "Download" : { - "label" : "Download", - "_disabled" : false, - "separator_before" : false, - "separator_after" : true, - "submenu" : { - "DownloadAsZip": download.Download, - "downloadAsCombine" : { - "label" : "as COMBINE", - "_disabled" : true, - "separator_before" : false, - "separator_after" : false, - "action" : function (data) { - self.handleExportCombineClick(o, true) - } - } - } - } - } + // let downloadWCombine = { + // "Download" : { + // "label" : "Download", + // "_disabled" : false, + // "separator_before" : false, + // "separator_after" : true, + // "submenu" : { + // "DownloadAsZip": download.Download, + // "downloadAsCombine" : { + // "label" : "as COMBINE", + // "_disabled" : true, + // "separator_before" : false, + // "separator_after" : false, + // "action" : function (data) { + // self.handleExportCombineClick(o, true) + // } + // } + // } + // } + // } // menu option for creating new workflows let newWorkflow = { "ensembleSimulation" : { @@ -1379,15 +1379,15 @@ let Model = require('../models/model'); } // common to all type except root and trash let common = { - "Rename" : { - "label" : "Rename", - "_disabled" : (o.type === "workflow" && o.original._status === "running"), - "separator_before" : false, - "separator_after" : false, - "action" : function (data) { - self.renameNode(o); - } - }, + // "Rename" : { + // "label" : "Rename", + // "_disabled" : (o.type === "workflow" && o.original._status === "running"), + // "separator_before" : false, + // "separator_after" : false, + // "action" : function (data) { + // self.renameNode(o); + // } + // }, "Duplicate" : { "label" : (nodeType === "workflow") ? "Duplicate as new" : "Duplicate", "_disabled" : (nodeType === "project" || nodeType === "workflow-group"), @@ -1430,9 +1430,9 @@ let Model = require('../models/model'); } } } - if (o.type === 'root'){ - return $.extend(refresh, project, commonFolder, downloadWCombine, {"Rename": common.Rename}) - } + // if (o.type === 'root'){ + // return $.extend(refresh, project, commonFolder, downloadWCombine, {"Rename": common.Rename}) + // } if (o.text === "trash"){ return refresh } diff --git a/client/views/jstree-view.js b/client/views/jstree-view.js index 2b267a408f..dc49332725 100644 --- a/client/views/jstree-view.js +++ b/client/views/jstree-view.js @@ -95,7 +95,7 @@ module.exports = View.extend({ render: function (attrs, options) { View.prototype.render.apply(this, arguments); this.config.setup(this); - window.addEventListener('pageshow', function (e) { + window.addEventListener('pageshow', (e) => { let navType = window.performance.navigation.type; if(navType === 2){ window.location.reload(); @@ -217,12 +217,13 @@ module.exports = View.extend({ let input = document.querySelector('#newProjectModal #projectNameInput'); this.addInputKeyupEvent(input, okBtn); this.addInputValidateEvent(input, okBtn, "#newProjectModal", "#projectNameInput"); - okBtn.addEventListener("click", function (e) { + okBtn.addEventListener("click", (e) => { modal.modal('hide'); let queryStr = "?path=" + path.join(dirname, `${input.value.trim()}.proj`); let endpoint = path.join(app.getApiPath(), "project/new-project") + queryStr; app.getXHR(endpoint, { success: (err, response, body) => { + this.config.updateParent(this, "project"); let queryStr = "?path=" + body.path; let endpoint = path.join(app.getBasePath(), 'stochss/project/manager') + queryStr; window.location.href = endpoint; @@ -253,13 +254,250 @@ module.exports = View.extend({ }); }); }, + exportToFile: function (fileData, fileName) { + let dataURI = 'data:text/plain;charset=utf-8,' + encodeURIComponent(fileData); + + let linkElement = document.createElement('a'); + linkElement.setAttribute('href', dataURI); + linkElement.setAttribute('download', fileName); + linkElement.click(); + }, + exportToJsonFile: function (fileData, fileName) { + let dataStr = JSON.stringify(fileData); + let dataURI = 'data:application/json;charset=utf-8,' + encodeURIComponent(dataStr); + let exportFileDefaultName = fileName; + + let linkElement = document.createElement('a'); + linkElement.setAttribute('href', dataURI); + linkElement.setAttribute('download', exportFileDefaultName); + linkElement.click(); + }, + exportToZipFile: function (targetPath) { + let endpoint = path.join(app.getBasePath(), "/files", targetPath); + window.open(endpoint, "_blank"); + }, + getAddModelContext: function (node) { + let newModel = this.getNewModelContext(node, true); + return { + label: "Add Model", + _disabled: false, + separator_before: false, + separator_after: false, + submenu: { + newModel: newModel, + existingModel: { + label: "Existing Model", + _disabled: false, + separator_before: false, + separator_after: false, + action: (data) => { + this.importModel(node, node.original._path); + } + } + } + } + }, + getDownloadContext: function (node, options, {asZip=false, withCombine=false}={}) { + if(withCombine) { + var label = "as .zip"; + }else if(asZip) { + var label = "Download as .zip"; + }else { + var label = "Download"; + } + return { + label: label, + _disabled: false, + separator_before: !withCombine, + separator_after: false, + action: (data) => { + this.getExportData(node, options); + } + } + }, + getDownloadWCombineContext: function (node) { + let options = {dataType: "zip", identifier: "file/download-zip"}; + let download = this.getDownloadContext(node, options, {withCombine: true}); + return { + label: "Download", + _disabled: false, + separator_before: true, + separator_after: false, + submenu: { + downloadAsZip: download, + downloadAsCombine: { + label: "as COMBINE", + _disabled: true, + separator_before: false, + separator_after: false, + action: (data) => { + // handleExportCombineClick(o, true) + } + } + } + } + }, + getExportData: function (node, {dataType="", identifier=""}={}) { + if(node.original.text.endsWith('.zip')) { + return this.exportToZipFile(node.original._path); + } + let isJSON = node.original.type === "sbml-model" ? false : true; + if(node.original.type === "domain") { + var queryStr = "?domain_path=" + node.original._path; + }else{ + var queryStr = "?path=" + node.original._path; + if(dataType === "json"){ + queryStr += "&for=None"; + }else if(dataType === "zip"){ + queryStr += "&action=generate"; + } + } + let endpoint = path.join(app.getApiPath(), identifier) + queryStr; + app.getXHR(endpoint, { + success: (err, response, body) => { + if(dataType === "json") { + let data = node.original.type === "domain" ? body.domain : body; + this.exportToJsonFile(data, node.original.text); + }else if(dataType === "zip") { + let par = $('#files-jstree').jstree().get_node(node.parent); + this.refreshJSTree(par); + this.exportToZipFile(body.Path); + }else{ + this.exportToFile(body, node.original.text); + } + }, + error: (err, response, body) => { + if(dataType === "plain-text") { + body = JSON.parse(body); + } + console.log(body) + } + }); + }, + getFileUploadContext: function (node, inProject) { + let dirname = node.original._path === "/" ? "" : node.original._path; + return { + label: "File", + _disabled: false, + separator_before: false, + separator_after: false, + action: (data) => { + this.uploadFile(node, dirname, "file", inProject); + } + } + }, + getFullUploadContext: function (node, inProject) { + let dirname = node.original._path === "/" ? "" : node.original._path; + let file = this.getFileUploadContext(node, inProject); + return { + label: "Upload File", + _disabled: false, + separator_before: false, + separator_after: false, + submenu: { + model: { + label: "StochSS Model", + _disabled: false, + separator_before: false, + separator_after: false, + action: (data) => { + this.uploadFile(node, dirname, "model", inProject); + } + }, + sbml: { + label: "SBML Model", + _disabled: false, + separator_before: false, + separator_after: false, + action: (data) => { + this.uploadFile(node, dirname, "sbml", inProject); + } + }, + file: file + } + } + }, + getNewDirectoryContext: function (node) { + let dirname = node.original._path === "/" ? "" : node.original._path; + return { + label: "New Directory", + _disabled: false, + separator_before: false, + separator_after: false, + action: (data) => { + this.createDirectory(node, dirname); + } + } + }, + getNewDomainContext: function (node) { + return { + label: "New Domain (beta)", + _disabled: false, + separator_before: false, + separator_after: false, + action: (data) => { + let queryStr = "?domainPath=" + node.original._path + "&new"; + window.location.href = path.join(app.getBasePath(), "stochss/domain/edit") + queryStr; + } + } + }, + getNewModelContext: function (node, inProject) { + let dirname = node.original._path === "/" ? "" : node.original._path; + return { + label: "New Model", + _disabled: false, + separator_before: false, + separator_after: false, + submenu: { + spatial: { + label: "Spatial (beta)", + _disabled: false, + separator_before: false, + separator_after: false, + action: (data) => { + this.createModel(node, dirname, true, inProject); + } + }, + nonspatial: { + label: "Non-Spatial", + _disabled: false, + separator_before: false, + separator_after: false, + action: (data) => { + this.createModel(node, dirname, false, inProject); + } + } + } + } + }, + getRefreshContext: function (node) { + return { + label: "Refresh", + _disabled: false, + _class: "font-weight-bold", + separator_before: false, + separator_after: true, + action: (data) => { + this.refreshJSTree(node); + } + } + }, + getRenameContext: function (node) { + let disabled = node.type === "workflow" && node.original._status === "running"; + return { + label: "Rename", + _disabled: disabled, + separator_before: false, + separator_after: false, + action: (data) => { + this.renameNode(node); + } + } + }, handleCreateDirectoryClick: function (e) { let dirname = this.root === "none" ? "" : this.root; this.createDirectory(null, dirname); }, - handleImportModelClick: function () { - this.importModel(null); - }, handleCreateModelClick: function (e) { let dirname = this.root === "none" ? "" : this.root; let inProject = this.root !== "none"; @@ -275,6 +513,9 @@ module.exports = View.extend({ let queryStr = "?domainPath=" + dirname + "&new"; window.location.href = path.join(app.getBasePath(), "stochss/domain/edit") + queryStr; }, + handleImportModelClick: function () { + this.importModel(null, this.root); + }, handleRefreshJSTreeClick: function (e) { this.refreshJSTree(null); }, @@ -284,11 +525,14 @@ module.exports = View.extend({ let type = e.target.dataset.type; this.uploadFile(null, dirname, type, inProject); }, - importModel: function (node) { + hideNameWarning: function () { + $(this.queryByHook('rename-warning')).collapse('hide'); + }, + importModel: function (node, projectPath) { if(document.querySelector('#importModelModal')){ document.querySelector('#importModelModal').remove(); } - let mdlListEP = path.join(app.getApiPath(), 'project/add-existing-model') + "?path=" + this.root; + let mdlListEP = path.join(app.getApiPath(), 'project/add-existing-model') + "?path=" + projectPath; app.getXHR(mdlListEP, { always: (err, response, body) => { let modal = $(modals.importModelHtml(body.files)).modal(); @@ -316,7 +560,7 @@ module.exports = View.extend({ okBtn.addEventListener("click", (e) => { modal.modal('hide'); let mdlPath = body.paths[select.value].length < 2 ? body.paths[select.value][0] : location.value; - let queryStr = "?path=" + this.root + "&mdlPath=" + mdlPath; + let queryStr = "?path=" + projectPath + "&mdlPath=" + mdlPath; let endpoint = path.join(app.getApiPath(), 'project/add-existing-model') + queryStr; app.postXHR(endpoint, null, { success: (err, response, body) => { @@ -325,9 +569,11 @@ module.exports = View.extend({ } let successModal = $(modals.successHtml(body.message)).modal(); this.config.updateParent(this, "model"); - this.refreshJSTree(null); + if(this.root !== "none") { + this.refreshJSTree(null); + } }, - error: function (err, response, body) { + error: (err, response, body) => { if(document.querySelector("#errorModal")) { document.querySelector("#errorModal").remove(); } @@ -356,17 +602,53 @@ module.exports = View.extend({ $('#files-jstree').jstree().refresh_node(node); } }, + renameNode: function (node) { + let currentName = node.text; + let par = $('#files-jstree').jstree().get_node(node.parent); + let extensionWarning = $(this.queryByHook('extension-warning')); + let nameWarning = $(this.queryByHook('rename-warning')); + extensionWarning.collapse('show'); + $('#files-jstree').jstree().edit(node, null, (node, status) => { + if(currentName != node.text){ + let name = node.type === "root" ? node.text + ".proj" : node.text; + let queryStr = "?path=" + node.original._path + "&name=" + name; + let endpoint = path.join(app.getApiPath(), "file/rename") + queryStr; + app.getXHR(endpoint, { + always: (err, response, body) => { + this.refreshJSTree(par); + }, + success: (err, response, body) => { + if(this.root !== "none") { + let queryStr = "?path=" + body._path; + let endpoint = path.join(app.getBasePath(), 'stochss/project/manager') + queryStr; + window.location.href = endpoint; + }else if(body.changed) { + nameWarning.text(body.message); + nameWarning.collapse('show'); + window.scrollTo(0,0); + setTimeout(_.bind(this.hideNameWarning, this), 10000); + } + node.original._path = body._path; + } + }); + } + extensionWarning.collapse('hide'); + nameWarning.collapse('hide'); + }); + }, setupJstree: function (cb) { - $.jstree.defaults.contextmenu.items = (o, cb) => { - let nodeType = o.original.type; + $.jstree.defaults.contextmenu.items = (node, cb) => { let zipTypes = this.config.contextZipTypes; - let asZip = zipTypes.includes(nodeType); + let asZip = zipTypes.includes(node.original.type); let optionsButton = $(this.queryByHook("options-for-node")); if(!this.nodeForContextMenu) { optionsButton.prop('disabled', false); } - optionsButton.text("Actions for " + o.original.text); - this.nodeForContextMenu = o; + optionsButton.text("Actions for " + node.original.text); + this.nodeForContextMenu = node; + if (node.type === 'root'){ + return this.config.getRootContext(this, node); + } } $(() => { $(document).on('shown.bs.modal', (e) => { @@ -436,11 +718,14 @@ module.exports = View.extend({ let nameUsageMsg = document.querySelector('#uploadFileModal #fileNameUsageMessage'); let getOptions = (file) => { if(!inProject) { return {saveAs: true}; }; - if(file.name.endsWith(".mdl") || (type === "model" && file.name.endsWith(".json"))) { - return {saveAs: false}; + if(file) { + if(file.name.endsWith(".mdl")) { return {saveAs: false}; } + if(file.name.endsWith(".sbml")) { return {saveAs: false}; } } - if(file.name.endsWith(".sbml") || (type === "sbml" && file.name.endsWith(".xml"))) { - return {saveAs: false}; + if(type === "model" || type === "sbml") { + if(!file) { return {saveAs: false}; } + if(type === "model" && file.name.endsWith(".json")) { return {saveAs: false}; } + if(type === "sbml" && file.name.endsWith(".xml")) { return {saveAs: false}; } } return {saveAs: true}; } From 95e6ab94f466ce966d19ce8c6a4c34c70f5c92c9 Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Fri, 27 Aug 2021 13:09:40 -0400 Subject: [PATCH 119/186] Added template file to gitignore. Fixed path issue with custom.js in dockerfile. --- .gitignore | 2 ++ jupyterhub/Dockerfile.jupyterhub | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 73262f68be..e96d3327a9 100644 --- a/.gitignore +++ b/.gitignore @@ -19,6 +19,8 @@ jupyterhub/templates/stochss-home.html jupyterhub/templates/stochss-job-presentation.html jupyterhub/templates/stochss-model-presentation.html jupyterhub/templates/stochss-notebook-presentation.html +jupyterhub/templates/multiple-plots-page.html + *.swp *.swo package-lock.json diff --git a/jupyterhub/Dockerfile.jupyterhub b/jupyterhub/Dockerfile.jupyterhub index 10802830d8..8d8d1dc221 100644 --- a/jupyterhub/Dockerfile.jupyterhub +++ b/jupyterhub/Dockerfile.jupyterhub @@ -48,7 +48,7 @@ COPY static/* /usr/local/share/jupyterhub/static/ COPY custom/favicon.ico /usr/local/share/jupyterhub/static/favicon.ico -COPY custom/custom.js $JUPYTER_CONFIG_DIR/custom/custom.js +COPY custom/custom.js /opt/stochss-config/.jupyter/custom/custom.js COPY cull_idle_servers.py /srv/jupyterhub/cull_idle_servers.py From ee173f298aac2797900a86c4717785db654fd1c1 Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Mon, 30 Aug 2021 11:22:23 -0400 Subject: [PATCH 120/186] Moved context menu for projects and required function to the jstree view and config files. --- client/file-config.js | 31 ++- client/modals.js | 20 +- client/pages/file-browser.js | 256 +++++++++++------------ client/templates/includes/jstreeView.pug | 2 +- client/views/file-browser-view.js | 148 ++++++------- client/views/jstree-view.js | 149 ++++++++++--- 6 files changed, 362 insertions(+), 244 deletions(-) diff --git a/client/file-config.js b/client/file-config.js index b38a0a56fc..75d41bcbdb 100644 --- a/client/file-config.js +++ b/client/file-config.js @@ -36,8 +36,7 @@ let doubleClick = (view, e) => { }else if(node.type === "sbml-model"){ window.open(path.join(app.getBasePath(), "edit", node.original._path), '_blank'); }else if(node.type === "project"){ - let queryStr = "?path=" + node.original._path - window.location.href = path.join(app.getBasePath(), "stochss/project/manager") + queryStr; + view.openProject(node.original._path); }else if(node.type === "workflow"){ let queryStr = "?path=" + node.original._path + "&type=none"; window.location.href = path.join(app.getBasePath(), "stochss/workflow/edit") + queryStr; @@ -51,6 +50,31 @@ let doubleClick = (view, e) => { } } +let getOpenProjectContext = (view, node) => { + return { + label: "Open", + _disabled: false, + _class: "font-weight-bolder", + separator_before: false, + separator_after: true, + action: (data) => { + view.openProject(node.original._path); + } + } +} + +let getProjectContext = (view, node) => { + let dirname = node.original._path === "/" ? "" : node.original._path; + return { + open: getOpenProjectContext(view, node), + addModel: view.getAddModelContext(node), + download: view.getDownloadWCombineContext(node), + rename: view.getRenameContext(node), + duplicate: view.getDuplicateContext(node, "directory/duplicate"), + moveToTrash: view.getMoveToTrashContext(node) + } +} + let getRootContext = (view, node) => { let dirname = node.original._path === "/" ? "" : node.original._path; return { @@ -98,7 +122,7 @@ let move = (view, par, node) => { let setup = (view) => { $(view.queryByHook("fb-proj-seperator")).css("display", "none"); - $(view.queryByHook("fb-add-existing-model")).css("display", "none"); + $(view.queryByHook("fb-import-model")).css("display", "none"); } let types = { @@ -149,6 +173,7 @@ let validateMove = (view, node, more, pos) => { module.exports = { contextZipTypes: contextZipTypes, doubleClick: doubleClick, + getProjectContext: getProjectContext, getRootContext: getRootContext, move: move, setup: setup, diff --git a/client/modals.js b/client/modals.js index 41b93d8bd3..a2c6e1d0a0 100644 --- a/client/modals.js +++ b/client/modals.js @@ -306,6 +306,16 @@ module.exports = { return templates.fileSelect(modalID, fileID, locationID, title, label, files); }, + moveToTrashConfirmHtml : (fileType, {newFormat=false}={}) => { + let modalID = "moveToTrashConfirmModal"; + let title = `Move this ${fileType} to trash?`; + + if(newFormat) { + let message = "The workflows for this model will be archived"; + return templates.confirmation_with_message(modalID, title, message); + } + return templates.confirmation(modalID, title); + }, successHtml : (message) => { let modalID = "successModal"; let title = "Success!"; @@ -351,16 +361,6 @@ module.exports = { return templates.confirmation(modalID, title) }, - moveToTrashConfirmHtml : (fileType, newProjectFormat=false) => { - let modalID = "moveToTrashConfirmModal" - let title = `Move this ${fileType} to trash?` - - if(newProjectFormat) { - let message = "The workflows for this model will be archived" - return templates.confirmation_with_message(modalID, title, message); - } - return templates.confirmation(modalID, title) - }, operationInfoModalHtml : (page) => { let modalID = "operationInfoModal" let title = "Help" diff --git a/client/pages/file-browser.js b/client/pages/file-browser.js index 0a4ce6e97a..e2669bd6bd 100644 --- a/client/pages/file-browser.js +++ b/client/pages/file-browser.js @@ -238,28 +238,28 @@ let FileBrowser = PageView.extend({ // }, 3000); // } // }, - selectNode: function (node, fileName) { - let self = this - if(!this.jstreeIsLoaded || !$('#models-jstree').jstree().is_loaded(node) && $('#models-jstree').jstree().is_loading(node)) { - setTimeout(_.bind(self.selectNode, self, node, fileName), 1000); - }else{ - node = $('#models-jstree').jstree().get_node(node) - var child = "" - for(var i = 0; i < node.children.length; i++) { - var child = $('#models-jstree').jstree().get_node(node.children[i]) - if(child.original.text === fileName) { - $('#models-jstree').jstree().select_node(child) - let optionsButton = $(self.queryByHook("options-for-node")) - if(!self.nodeForContextMenu){ - optionsButton.prop('disabled', false) - } - optionsButton.text("Actions for " + child.original.text) - self.nodeForContextMenu = child; - break - } - } - } - }, + // selectNode: function (node, fileName) { + // let self = this + // if(!this.jstreeIsLoaded || !$('#models-jstree').jstree().is_loaded(node) && $('#models-jstree').jstree().is_loading(node)) { + // setTimeout(_.bind(self.selectNode, self, node, fileName), 1000); + // }else{ + // node = $('#models-jstree').jstree().get_node(node) + // var child = "" + // for(var i = 0; i < node.children.length; i++) { + // var child = $('#models-jstree').jstree().get_node(node.children[i]) + // if(child.original.text === fileName) { + // $('#models-jstree').jstree().select_node(child) + // let optionsButton = $(self.queryByHook("options-for-node")) + // if(!self.nodeForContextMenu){ + // optionsButton.prop('disabled', false) + // } + // optionsButton.text("Actions for " + child.original.text) + // self.nodeForContextMenu = child; + // break + // } + // } + // } + // }, // handleUploadFileClick: function (e) { // let type = e.target.dataset.type // this.uploadFile(undefined, type) @@ -392,48 +392,48 @@ let FileBrowser = PageView.extend({ modal.modal('hide') }); }, - duplicateFileOrDirectory: function(o, type) { - var self = this; - var parentID = o.parent; - var queryStr = "?path="+o.original._path - if(!type && o.original.type === 'folder'){ - type = "directory" - }else if(!type && o.original.type === 'workflow'){ - type = "workflow" - }else if(!type){ - type = "file" - } - if(type === "directory"){ - var identifier = "directory/duplicate" - }else if(type === "workflow" || type === "wkfl_model"){ - var timeStamp = type === "workflow" ? this.getTimeStamp() : "None" - var identifier = "workflow/duplicate" - queryStr = queryStr.concat("&target="+type+"&stamp="+timeStamp) - }else{ - var identifier = "file/duplicate" - } - var endpoint = path.join(app.getApiPath(), identifier)+queryStr - app.getXHR(endpoint, { - success: function (err, response, body) { - var node = $('#models-jstree').jstree().get_node(parentID); - if(node.type === "root"){ - self.refreshJSTree(); - }else{ - $('#models-jstree').jstree().refresh_node(node); - } - if(type === "workflow"){ - var message = "" - if(body.error){ - message = body.error; - }else{ - message = "The model for "+body.File+" is located here: "+body.mdlPath+""; - } - let modal = $(modals.duplicateWorkflowHtml(body.File, message)).modal(); - } - self.selectNode(node, body.File); - } - }); - }, + // duplicateFileOrDirectory: function(o, type) { + // var self = this; + // var parentID = o.parent; + // var queryStr = "?path="+o.original._path + // if(!type && o.original.type === 'folder'){ + // type = "directory" + // }else if(!type && o.original.type === 'workflow'){ + // type = "workflow" + // }else if(!type){ + // type = "file" + // } + // if(type === "directory"){ + // var identifier = "directory/duplicate" + // }else if(type === "workflow" || type === "wkfl_model"){ + // var timeStamp = type === "workflow" ? this.getTimeStamp() : "None" + // var identifier = "workflow/duplicate" + // queryStr = queryStr.concat("&target="+type+"&stamp="+timeStamp) + // }else{ + // var identifier = "file/duplicate" + // } + // var endpoint = path.join(app.getApiPath(), identifier)+queryStr + // app.getXHR(endpoint, { + // success: function (err, response, body) { + // var node = $('#models-jstree').jstree().get_node(parentID); + // if(node.type === "root"){ + // self.refreshJSTree(); + // }else{ + // $('#models-jstree').jstree().refresh_node(node); + // } + // if(type === "workflow"){ + // var message = "" + // if(body.error){ + // message = body.error; + // }else{ + // message = "The model for "+body.File+" is located here: "+body.mdlPath+""; + // } + // let modal = $(modals.duplicateWorkflowHtml(body.File, message)).modal(); + // } + // self.selectNode(node, body.File); + // } + // }); + // }, getTimeStamp: function () { var date = new Date(); var year = date.getFullYear(); @@ -934,25 +934,25 @@ let FileBrowser = PageView.extend({ } }); }, - moveToTrash: function (o) { - if(document.querySelector('#moveToTrashConfirmModal')) { - document.querySelector('#moveToTrashConfirmModal').remove(); - } - let self = this; - let modal = $(modals.moveToTrashConfirmHtml("model")).modal(); - let yesBtn = document.querySelector('#moveToTrashConfirmModal .yes-modal-btn'); - yesBtn.addEventListener('click', function (e) { - modal.modal('hide'); - let queryStr = "?srcPath=" + o.original._path + "&dstPath=" + path.join("trash", o.text) - let endpoint = path.join(app.getApiPath(), "file/move") + queryStr - app.getXHR(endpoint, { - always: function (err, response, body) { - $(self.queryByHook('empty-trash')).prop('disabled', false); - $('#models-jstree').jstree().refresh(); - } - }); - }); - }, + // moveToTrash: function (o) { + // if(document.querySelector('#moveToTrashConfirmModal')) { + // document.querySelector('#moveToTrashConfirmModal').remove(); + // } + // let self = this; + // let modal = $(modals.moveToTrashConfirmHtml("model")).modal(); + // let yesBtn = document.querySelector('#moveToTrashConfirmModal .yes-modal-btn'); + // yesBtn.addEventListener('click', function (e) { + // modal.modal('hide'); + // let queryStr = "?srcPath=" + o.original._path + "&dstPath=" + path.join("trash", o.text) + // let endpoint = path.join(app.getApiPath(), "file/move") + queryStr + // app.getXHR(endpoint, { + // always: function (err, response, body) { + // $(self.queryByHook('empty-trash')).prop('disabled', false); + // $('#models-jstree').jstree().refresh(); + // } + // }); + // }); + // }, // emptyTrash: function (e) { // if(document.querySelector("#emptyTrashConfirmModal")) { // document.querySelector("#emptyTrashConfirmModal").remove() @@ -1011,7 +1011,7 @@ let FileBrowser = PageView.extend({ // let zipTypes = ["workflow", "folder", "other", "project", "workflow-group"] // let asZip = zipTypes.includes(nodeType) // common to all type except root - let common = { + // let common = { // "Download" : { // "label" : asZip ? "Download as .zip" : "Download", // "_disabled" : false, @@ -1034,25 +1034,25 @@ let FileBrowser = PageView.extend({ // self.renameNode(o); // } // }, - "Duplicate" : { - "label" : (nodeType === "workflow") ? "Duplicate as new" : "Duplicate", - "_disabled" : (nodeType === "project"), - "separator_before" : false, - "separator_after" : false, - "action" : function (data) { - self.duplicateFileOrDirectory(o, null) - } - }, - "MoveToTrash" : { - "label" : "Move To Trash", - "_disabled" : false, - "separator_before" : false, - "separator_after" : false, - "action" : function (data) { - self.moveToTrash(o); - } - } - } + // "Duplicate" : { + // "label" : (nodeType === "workflow") ? "Duplicate as new" : "Duplicate", + // "_disabled" : (nodeType === "project"), + // "separator_before" : false, + // "separator_after" : false, + // "action" : function (data) { + // self.duplicateFileOrDirectory(o, null) + // } + // }, + // "MoveToTrash" : { + // "label" : "Move To Trash", + // "_disabled" : false, + // "separator_before" : false, + // "separator_after" : false, + // "action" : function (data) { + // self.moveToTrash(o); + // } + // } + // } let delete_node = { "Delete" : { "label" : "Delete", @@ -1297,8 +1297,8 @@ let FileBrowser = PageView.extend({ "action" : function (data) { if(nodeType === "workflow"){ window.location.href = path.join(app.getBasePath(), "stochss/workflow/edit")+"?path="+o.original._path+"&type=none"; - }else if(nodeType === "project"){ - window.location.href = path.join(app.getBasePath(), "stochss/project/manager")+"?path="+o.original._path + // }else if(nodeType === "project"){ + // window.location.href = path.join(app.getBasePath(), "stochss/project/manager")+"?path="+o.original._path }else if(nodeType === "domain") { let queryStr = "?domainPath=" + o.original._path window.location.href = path.join(app.getBasePath(), "stochss/domain/edit") + queryStr @@ -1315,26 +1315,26 @@ let FileBrowser = PageView.extend({ } } } - let project = { - "Add Model" : { - "label" : "Add Model", - "_disabled" : false, - "separator_before" : false, - "separator_after" : false, - "submenu" : { - "New Model" : folder.New_model, - "Existing Model" : { - "label" : "Existing Model", - "_disabled" : false, - "separator_before" : false, - "separator_after" : false, - "action" : function (data) { - self.addExistingModel(o) - } - } - } - } - } + // let project = { + // "Add Model" : { + // "label" : "Add Model", + // "_disabled" : false, + // "separator_before" : false, + // "separator_after" : false, + // "submenu" : { + // "New Model" : folder.New_model, + // "Existing Model" : { + // "label" : "Existing Model", + // "_disabled" : false, + // "separator_before" : false, + // "separator_after" : false, + // "action" : function (data) { + // self.addExistingModel(o) + // } + // } + // } + // } + // } // specific to workflows let workflow = { "Start/Restart Workflow" : { @@ -1443,9 +1443,9 @@ let FileBrowser = PageView.extend({ if (o.type === 'nonspatial') { return $.extend(model, modelConvert, common) } - if (o.type === 'project'){ - return $.extend(open, project, common) - } + // if (o.type === 'project'){ + // return $.extend(open, project, common) + // } if (o.type === 'workflow') { return $.extend(open, workflow, common) } diff --git a/client/templates/includes/jstreeView.pug b/client/templates/includes/jstreeView.pug index ae2a20c57a..64c3109a8f 100644 --- a/client/templates/includes/jstreeView.pug +++ b/client/templates/includes/jstreeView.pug @@ -26,7 +26,7 @@ div li.dropdown-item(id="fb-new-model" data-hook="fb-new-model" data-type="spatial") Create Spatial Model (beta) li.dropdown-item(id="fb-new-model" data-hook="fb-new-domain") Create Domain (beta) li.dropdown-divider(data-hook="fb-proj-seperator") - li.dropdown-item(id="fb-add-existing-model" data-hook="fb-import-model") Add Existing Model + li.dropdown-item(id="fb-import-model" data-hook="fb-import-model") Add Existing Model li.dropdown-divider li.dropdown-item(id="upload-file-btn" data-hook="upload-file-btn" data-type="model") Upload StochSS Model li.dropdown-item(id="upload-file-btn" data-hook="upload-file-btn" data-type="sbml") Upload SBML Model diff --git a/client/views/file-browser-view.js b/client/views/file-browser-view.js index 65b7f036bd..495c89567a 100644 --- a/client/views/file-browser-view.js +++ b/client/views/file-browser-view.js @@ -219,28 +219,28 @@ let Model = require('../models/model'); // }, 3000); // } // }, - selectNode: function (node, fileName) { - let self = this - if(!this.jstreeIsLoaded || !$('#models-jstree-view').jstree().is_loaded(node) && $('#models-jstree-view').jstree().is_loading(node)) { - setTimeout(_.bind(self.selectNode, self, node, fileName), 1000); - }else{ - node = $('#models-jstree-view').jstree().get_node(node) - var child = "" - for(var i = 0; i < node.children.length; i++) { - var child = $('#models-jstree-view').jstree().get_node(node.children[i]) - if(child.original.text === fileName) { - $('#models-jstree-view').jstree().select_node(child) - let optionsButton = $(self.queryByHook("options-for-node")) - if(!self.nodeForContextMenu){ - optionsButton.prop('disabled', false) - } - optionsButton.text("Actions for " + child.original.text) - self.nodeForContextMenu = child; - break - } - } - } - }, + // selectNode: function (node, fileName) { + // let self = this + // if(!this.jstreeIsLoaded || !$('#models-jstree-view').jstree().is_loaded(node) && $('#models-jstree-view').jstree().is_loading(node)) { + // setTimeout(_.bind(self.selectNode, self, node, fileName), 1000); + // }else{ + // node = $('#models-jstree-view').jstree().get_node(node) + // var child = "" + // for(var i = 0; i < node.children.length; i++) { + // var child = $('#models-jstree-view').jstree().get_node(node.children[i]) + // if(child.original.text === fileName) { + // $('#models-jstree-view').jstree().select_node(child) + // let optionsButton = $(self.queryByHook("options-for-node")) + // if(!self.nodeForContextMenu){ + // optionsButton.prop('disabled', false) + // } + // optionsButton.text("Actions for " + child.original.text) + // self.nodeForContextMenu = child; + // break + // } + // } + // } + // }, // handleUploadFileClick: function (e) { // let type = e.target.dataset.type // this.uploadFile(undefined, type) @@ -391,47 +391,47 @@ let Model = require('../models/model'); } }); }, - duplicateFileOrDirectory: function(o, type) { - var self = this; - var parentID = o.parent; - var queryStr = "?path="+o.original._path - if(!type && o.original.type === 'folder'){ - type = "directory" - }else if(!type && o.original.type === 'workflow'){ - type = "workflow" - }else if(!type){ - type = "file" - } - if(type === "directory"){ - var identifier = "directory/duplicate" - }else if(type === "workflow" || type === "wkfl_model"){ - var timeStamp = type === "workflow" ? this.getTimeStamp() : "None" - var identifier = "workflow/duplicate" - queryStr = queryStr.concat("&target="+type+"&stamp="+timeStamp) - }else{ - var identifier = "file/duplicate" - } - var endpoint = path.join(app.getApiPath(), identifier)+queryStr - app.getXHR(endpoint, { - success: function (err, response, body) { - var node = $('#models-jstree-view').jstree().get_node(parentID); - self.refreshJSTree() - if(type === "workflow"){ - var message = "" - if(body.error){ - message = body.error - }else{ - message = "The model for "+body.File+" is located here: "+body.mdlPath+"" - } - let modal = $(modals.duplicateWorkflowHtml(body.File, message)).modal() - } - self.selectNode(node, body.File) - if(o.type !== "notebook" || o.original._path.includes(".wkgp")) { - self.updateParent(o.type) - } - } - }); - }, + // duplicateFileOrDirectory: function(o, type) { + // var self = this; + // var parentID = o.parent; + // var queryStr = "?path="+o.original._path + // if(!type && o.original.type === 'folder'){ + // type = "directory" + // }else if(!type && o.original.type === 'workflow'){ + // type = "workflow" + // }else if(!type){ + // type = "file" + // } + // if(type === "directory"){ + // var identifier = "directory/duplicate" + // }else if(type === "workflow" || type === "wkfl_model"){ + // var timeStamp = type === "workflow" ? this.getTimeStamp() : "None" + // var identifier = "workflow/duplicate" + // queryStr = queryStr.concat("&target="+type+"&stamp="+timeStamp) + // }else{ + // var identifier = "file/duplicate" + // } + // var endpoint = path.join(app.getApiPath(), identifier)+queryStr + // app.getXHR(endpoint, { + // success: function (err, response, body) { + // var node = $('#models-jstree-view').jstree().get_node(parentID); + // self.refreshJSTree() + // if(type === "workflow"){ + // var message = "" + // if(body.error){ + // message = body.error + // }else{ + // message = "The model for "+body.File+" is located here: "+body.mdlPath+"" + // } + // let modal = $(modals.duplicateWorkflowHtml(body.File, message)).modal() + // } + // self.selectNode(node, body.File) + // if(o.type !== "notebook" || o.original._path.includes(".wkgp")) { + // self.updateParent(o.type) + // } + // } + // }); + // }, getTimeStamp: function () { var date = new Date(); var year = date.getFullYear(); @@ -1022,8 +1022,8 @@ let Model = require('../models/model'); "action" : function (data) { if(nodeType === "workflow"){ window.location.href = path.join(app.getBasePath(), "stochss/workflow/edit")+"?path="+o.original._path+"&type=none"; - }else if(nodeType === "project"){ - window.location.href = path.join(app.getBasePath(), "stochss/project/manager")+"?path="+o.original._path + // }else if(nodeType === "project"){ + // window.location.href = path.join(app.getBasePath(), "stochss/project/manager")+"?path="+o.original._path }else if(nodeType === "domain") { let queryStr = "?domainPath=" + o.original._path window.location.href = path.join(app.getBasePath(), "stochss/domain/edit") + queryStr @@ -1388,15 +1388,15 @@ let Model = require('../models/model'); // self.renameNode(o); // } // }, - "Duplicate" : { - "label" : (nodeType === "workflow") ? "Duplicate as new" : "Duplicate", - "_disabled" : (nodeType === "project" || nodeType === "workflow-group"), - "separator_before" : false, - "separator_after" : false, - "action" : function (data) { - self.duplicateFileOrDirectory(o, null) - } - }, + // "Duplicate" : { + // "label" : (nodeType === "workflow") ? "Duplicate as new" : "Duplicate", + // "_disabled" : (nodeType === "project" || nodeType === "workflow-group"), + // "separator_before" : false, + // "separator_after" : false, + // "action" : function (data) { + // self.duplicateFileOrDirectory(o, null) + // } + // }, "Delete" : { "label" : "Delete", "_disabled" : false, diff --git a/client/views/jstree-view.js b/client/views/jstree-view.js index dc49332725..8207912937 100644 --- a/client/views/jstree-view.js +++ b/client/views/jstree-view.js @@ -18,6 +18,7 @@ along with this program. If not, see . let $ = require('jquery'); let path = require('path'); +let _ = require('underscore'); let jstree = require('jstree'); //support files let app = require('../app'); @@ -41,7 +42,7 @@ module.exports = View.extend({ 'click [data-hook=upload-file-btn]' : 'handleUploadFileClick', 'click [data-hook=options-for-node]' : 'showContextMenuForNode', 'click [data-hook=refresh-jstree]' : 'handleRefreshJSTreeClick', - 'click [data-hook=empty-trash]' : 'emptyTrash', + 'click [data-hook=fb-empty-trash]' : 'emptyTrash', }, initialize: function (attrs, options) { View.prototype.initialize.apply(this, arguments); @@ -52,13 +53,13 @@ module.exports = View.extend({ this.ajaxData = { "url" : (node) => { if(node.parent === null){ - var queryStr = "?path=" + this.root; + var queryStr = `?path=${this.root}`; if(this.root !== "none") { queryStr += "&isRoot=True"; } return path.join(app.getApiPath(), "file/browser-list") + queryStr; } - var queryStr = "?path=" + node.original._path; + var queryStr = `?path=${node.original._path}`; return path.join(app.getApiPath(), "file/browser-list") + queryStr; }, "dataType" : "json", @@ -126,7 +127,7 @@ module.exports = View.extend({ }); }, addModel: function (dirname, file) { - let queryStr = "?path=" + path.join(dirname, file); + let queryStr = `?path=${path.join(dirname, file)}`; let existEP = path.join(app.getApiPath(), "model/exists") + queryStr; app.getXHR(existEP, { always: (err, response, body) => { @@ -145,11 +146,11 @@ module.exports = View.extend({ }); }, addProjectModel: function (dirname, file) { - let queryStr = "?path=" + dirname + "&mdlFile=" + file; + let queryStr = `?path=${dirname}&mdlFile=${file}`; let newMdlEP = path.join(app.getApiPath(), "project/new-model") + queryStr; app.getXHR(newMdlEP, { success: (err, response, body) => { - let endpoint = path.join(app.getBasePath(), "stochss/models/edit") + "?path=" + body.path; + let endpoint = path.join(app.getBasePath(), "stochss/models/edit") + `?path=${body.path}`; window.location.href = endpoint; }, error: (err, response, body) => { @@ -173,7 +174,7 @@ module.exports = View.extend({ this.addInputValidateEvent(input, okBtn, "#newDirectoryModal", "#directoryNameInput"); okBtn.addEventListener('click', (e) => { modal.modal('hide'); - let queryStr = "?path=" + path.join(dirname, input.value.trim()); + let queryStr = `?path=${path.join(dirname, input.value.trim())}`; let endpoint = path.join(app.getApiPath(), "directory/create") + queryStr; app.getXHR(endpoint, { success: (err, response, body) => { @@ -189,6 +190,10 @@ module.exports = View.extend({ }); }); }, + createDomain: function (dirname) { + let queryStr = `?domainPath=${dirname}&new`; + window.location.href = path.join(app.getBasePath(), "stochss/domain/edit") + queryStr; + }, createModel: function (node, dirname, isSpatial, inProject) { if(document.querySelector('#newModelModal')) { document.querySelector('#newModelModal').remove(); @@ -219,14 +224,12 @@ module.exports = View.extend({ this.addInputValidateEvent(input, okBtn, "#newProjectModal", "#projectNameInput"); okBtn.addEventListener("click", (e) => { modal.modal('hide'); - let queryStr = "?path=" + path.join(dirname, `${input.value.trim()}.proj`); + let queryStr = `?path=${path.join(dirname, `${input.value.trim()}.proj`)}`; let endpoint = path.join(app.getApiPath(), "project/new-project") + queryStr; app.getXHR(endpoint, { success: (err, response, body) => { this.config.updateParent(this, "project"); - let queryStr = "?path=" + body.path; - let endpoint = path.join(app.getBasePath(), 'stochss/project/manager') + queryStr; - window.location.href = endpoint; + this.openProject(body.path); }, error: (err, response, body) => { if(document.querySelector("#errorModal")) { @@ -237,6 +240,27 @@ module.exports = View.extend({ }); }); }, + duplicate: function (node, identifier, {cb=null, target=null, timeStamp=null}={}) { + var queryStr = `?path=${node.original._path}`; + if(target) { + queryStr += `&target=${target}`; + } + if(timeStamp) { + queryStr += `&stamp=${timeStamp}`; + } + let endpoint = path.join(app.getApiPath(), identifier) + queryStr; + app.getXHR(endpoint, { + success: (err, response, body) => { + let par = $('#files-jstree').jstree().get_node(node.parent); + this.refreshJSTree(par); + if(cb) { + cb(body.File, body.mdlPath); + } + this.selectNode(par, body.File); + this.config.updateParent(this, node.type); + } + }); + }, emptyTrash: function (e) { if(document.querySelector("#emptyTrashConfirmModal")) { document.querySelector("#emptyTrashConfirmModal").remove(); @@ -255,7 +279,7 @@ module.exports = View.extend({ }); }, exportToFile: function (fileData, fileName) { - let dataURI = 'data:text/plain;charset=utf-8,' + encodeURIComponent(fileData); + let dataURI = `data:text/plain;charset=utf-8,${encodeURIComponent(fileData)}`; let linkElement = document.createElement('a'); linkElement.setAttribute('href', dataURI); @@ -264,7 +288,7 @@ module.exports = View.extend({ }, exportToJsonFile: function (fileData, fileName) { let dataStr = JSON.stringify(fileData); - let dataURI = 'data:application/json;charset=utf-8,' + encodeURIComponent(dataStr); + let dataURI = `data:application/json;charset=utf-8,${encodeURIComponent(dataStr)}`; let exportFileDefaultName = fileName; let linkElement = document.createElement('a'); @@ -337,15 +361,30 @@ module.exports = View.extend({ } } }, + getDuplicateContext: function (node, identifier, options) { + if(!options) { + options = {}; + } + let label = Boolean(options.cb) ? "Duplicate as new" : "Duplicate" + return { + label: label, + _disabled: false, + separator_before: false, + separator_after: false, + action: (data) => { + this.duplicate(node, identifier, options) + } + } + }, getExportData: function (node, {dataType="", identifier=""}={}) { if(node.original.text.endsWith('.zip')) { return this.exportToZipFile(node.original._path); } let isJSON = node.original.type === "sbml-model" ? false : true; if(node.original.type === "domain") { - var queryStr = "?domain_path=" + node.original._path; + var queryStr = `?domain_path=${node.original._path}`; }else{ - var queryStr = "?path=" + node.original._path; + var queryStr = `?path=${node.original._path}`; if(dataType === "json"){ queryStr += "&for=None"; }else if(dataType === "zip"){ @@ -417,6 +456,17 @@ module.exports = View.extend({ } } }, + getMoveToTrashContext: function (node, type) { + return { + label: "Move To Trash", + _disabled: false, + separator_before: false, + separator_after: false, + action: (data) => { + this.moveToTrash(node, type); + } + } + }, getNewDirectoryContext: function (node) { let dirname = node.original._path === "/" ? "" : node.original._path; return { @@ -436,8 +486,7 @@ module.exports = View.extend({ separator_before: false, separator_after: false, action: (data) => { - let queryStr = "?domainPath=" + node.original._path + "&new"; - window.location.href = path.join(app.getBasePath(), "stochss/domain/edit") + queryStr; + this.createDomain(node.original._path); } } }, @@ -510,8 +559,7 @@ module.exports = View.extend({ }, handleCreateDomain: function (e) { let dirname = this.root === "none" ? "/" : this.root; - let queryStr = "?domainPath=" + dirname + "&new"; - window.location.href = path.join(app.getBasePath(), "stochss/domain/edit") + queryStr; + this.createDomain(dirname); }, handleImportModelClick: function () { this.importModel(null, this.root); @@ -532,7 +580,7 @@ module.exports = View.extend({ if(document.querySelector('#importModelModal')){ document.querySelector('#importModelModal').remove(); } - let mdlListEP = path.join(app.getApiPath(), 'project/add-existing-model') + "?path=" + projectPath; + let mdlListEP = path.join(app.getApiPath(), 'project/add-existing-model') + `?path=${projectPath}`; app.getXHR(mdlListEP, { always: (err, response, body) => { let modal = $(modals.importModelHtml(body.files)).modal(); @@ -560,7 +608,7 @@ module.exports = View.extend({ okBtn.addEventListener("click", (e) => { modal.modal('hide'); let mdlPath = body.paths[select.value].length < 2 ? body.paths[select.value][0] : location.value; - let queryStr = "?path=" + projectPath + "&mdlPath=" + mdlPath; + let queryStr = `?path=${projectPath}&mdlPath=${mdlPath}`; let endpoint = path.join(app.getApiPath(), 'project/add-existing-model') + queryStr; app.postXHR(endpoint, null, { success: (err, response, body) => { @@ -584,6 +632,28 @@ module.exports = View.extend({ } }); }, + moveToTrash: function (node, type) { + if(document.querySelector('#moveToTrashConfirmModal')) { + document.querySelector('#moveToTrashConfirmModal').remove(); + } + let modal = $(modals.moveToTrashConfirmHtml(type)).modal(); + let yesBtn = document.querySelector('#moveToTrashConfirmModal .yes-modal-btn'); + yesBtn.addEventListener('click', (e) => { + modal.modal('hide'); + let queryStr = `?srcPath=${node.original._path}&dstPath=${path.join("trash", node.text)}`; + let endpoint = path.join(app.getApiPath(), "file/move") + queryStr; + app.getXHR(endpoint, { + always: (err, response, body) => { + $(this.queryByHook('empty-trash')).prop('disabled', false); + this.refreshJSTree(null); + } + }); + }); + }, + openProject: function (projectPath) { + let queryStr = `?path=${projectPath}`; + window.location.href = path.join(app.getBasePath(), "stochss/project/manager") + queryStr; + }, refreshInitialJSTree: function () { let count = $('#files-jstree').jstree()._model.data['#'].children.length; if(count == 0) { @@ -610,8 +680,8 @@ module.exports = View.extend({ extensionWarning.collapse('show'); $('#files-jstree').jstree().edit(node, null, (node, status) => { if(currentName != node.text){ - let name = node.type === "root" ? node.text + ".proj" : node.text; - let queryStr = "?path=" + node.original._path + "&name=" + name; + let name = node.type === "root" ? `${node.text}.proj` : node.text; + let queryStr = `?path=${node.original._path}&name=${name}`; let endpoint = path.join(app.getApiPath(), "file/rename") + queryStr; app.getXHR(endpoint, { always: (err, response, body) => { @@ -619,9 +689,7 @@ module.exports = View.extend({ }, success: (err, response, body) => { if(this.root !== "none") { - let queryStr = "?path=" + body._path; - let endpoint = path.join(app.getBasePath(), 'stochss/project/manager') + queryStr; - window.location.href = endpoint; + this.openProject(body._path); }else if(body.changed) { nameWarning.text(body.message); nameWarning.collapse('show'); @@ -636,6 +704,27 @@ module.exports = View.extend({ nameWarning.collapse('hide'); }); }, + selectNode: function (node, fileName) { + if(!this.jstreeIsLoaded || !$('#files-jstree').jstree().is_loaded(node) && $('#files-jstree').jstree().is_loading(node)) { + setTimeout(_.bind(this.selectNode, this, node, fileName), 1000); + }else{ + node = $('#files-jstree').jstree().get_node(node); + var child = ""; + for(var i = 0; i < node.children.length; i++) { + var child = $('#files-jstree').jstree().get_node(node.children[i]); + if(child.original.text === fileName) { + $('#files-jstree').jstree().select_node(child); + let optionsButton = $(this.queryByHook("options-for-node")); + if(!this.nodeForContextMenu){ + optionsButton.prop('disabled', false); + } + optionsButton.text(`Actions for ${child.original.text}`); + this.nodeForContextMenu = child; + break; + } + } + } + }, setupJstree: function (cb) { $.jstree.defaults.contextmenu.items = (node, cb) => { let zipTypes = this.config.contextZipTypes; @@ -644,11 +733,15 @@ module.exports = View.extend({ if(!this.nodeForContextMenu) { optionsButton.prop('disabled', false); } - optionsButton.text("Actions for " + node.original.text); + optionsButton.text(`Actions for ${node.original.text}`); this.nodeForContextMenu = node; if (node.type === 'root'){ return this.config.getRootContext(this, node); } + if (node.type === 'project'){ + return this.config.getProjectContext(this, node); + } + } $(() => { $(document).on('shown.bs.modal', (e) => { @@ -673,7 +766,7 @@ module.exports = View.extend({ if(this.nodeForContextMenu === null){ optionsButton.prop('disabled', false); } - optionsButton.text("Actions for " + node.original.text); + optionsButton.text(`Actions for ${node.original.text}`); this.nodeForContextMenu = node; }); $('#files-jstree').on('dblclick.jstree', (e) => { From 9f7081cbf789fbb7086da5e54058cb84f2afd6b2 Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Mon, 30 Aug 2021 12:31:57 -0400 Subject: [PATCH 121/186] Moved context menu for workflow groups. Moved the delete function. --- client/file-config.js | 5 ++- client/modals.js | 13 ++++--- client/pages/file-browser.js | 44 +++++++++++----------- client/project-config.js | 9 +++++ client/views/file-browser-view.js | 50 ++++++++++++------------- client/views/jstree-view.js | 61 +++++++++++++++++++++++++++---- 6 files changed, 120 insertions(+), 62 deletions(-) diff --git a/client/file-config.js b/client/file-config.js index 75d41bcbdb..e1f93d49bd 100644 --- a/client/file-config.js +++ b/client/file-config.js @@ -64,7 +64,9 @@ let getOpenProjectContext = (view, node) => { } let getProjectContext = (view, node) => { - let dirname = node.original._path === "/" ? "" : node.original._path; + if(node.original._path.split("/")[0] === "trash") { // project in trash + return view.getDeleteContext(node, "project"); + } return { open: getOpenProjectContext(view, node), addModel: view.getAddModelContext(node), @@ -175,6 +177,7 @@ module.exports = { doubleClick: doubleClick, getProjectContext: getProjectContext, getRootContext: getRootContext, + // getWorflowGroupContext: getOtherContext, move: move, setup: setup, types: types, diff --git a/client/modals.js b/client/modals.js index a2c6e1d0a0..bef1cf743a 100644 --- a/client/modals.js +++ b/client/modals.js @@ -281,6 +281,12 @@ module.exports = { return templates.input(modalID, inputID, title, label, value); }, + deleteFileHtml : (fileType) => { + let modalID = "deleteFileModal"; + let title = `Permanently delete this ${fileType}?`; + + return templates.confirmation(modalID, title); + }, emptyTrashConfirmHtml : () => { let modalID = "emptyTrashConfirmModal"; let title = "Are you sure you want to permanently erase the items in the Trash?"; @@ -355,12 +361,7 @@ module.exports = { }, - deleteFileHtml : (fileType) => { - let modalID = "deleteFileModal" - let title = `Permanently delete this ${fileType}?` - - return templates.confirmation(modalID, title) - }, + operationInfoModalHtml : (page) => { let modalID = "operationInfoModal" let title = "Help" diff --git a/client/pages/file-browser.js b/client/pages/file-browser.js index e2669bd6bd..d24f1a1a5d 100644 --- a/client/pages/file-browser.js +++ b/client/pages/file-browser.js @@ -1053,17 +1053,17 @@ let FileBrowser = PageView.extend({ // } // } // } - let delete_node = { - "Delete" : { - "label" : "Delete", - "_disabled" : false, - "separator_before" : false, - "separator_after" : false, - "action" : function (data) { - self.deleteFile(o); - } - } - } + // let delete_node = { + // "Delete" : { + // "label" : "Delete", + // "_disabled" : false, + // "separator_before" : false, + // "separator_after" : false, + // "action" : function (data) { + // self.deleteFile(o); + // } + // } + // } // common to root and folders // let folder = { // "Refresh" : { @@ -1428,43 +1428,43 @@ let FileBrowser = PageView.extend({ // if (o.type === 'root'){ // return folder // } - if (o.text === "trash") { + if (o.text === "trash") {//Trash node return {"Refresh": folder.Refresh} } - if (o.original._path.split("/")[0] === "trash") { + if (o.original._path.split("/")[0] === "trash") { // item in trash return delete_node } - if (o.type === 'folder') { + if (o.type === 'folder') { // folder node return $.extend(folder, common) } - if (o.type === 'spatial') { + if (o.type === 'spatial') { // spatial model node return $.extend(model, spatialConvert, common) } - if (o.type === 'nonspatial') { + if (o.type === 'nonspatial') { // model node return $.extend(model, modelConvert, common) } // if (o.type === 'project'){ // return $.extend(open, project, common) // } - if (o.type === 'workflow') { + if (o.type === 'workflow') { // workflow node return $.extend(open, workflow, common) } - if (o.text.endsWith(".zip")) { + if (o.text.endsWith(".zip")) { // zip archive node return $.extend(open, extractAll, common) } - if (o.type === 'notebook') { + if (o.type === 'notebook') { // notebook node if(app.getBasePath() === "/") { return $.extend(open, common) } return $.extend(open, notebook, common) } - if (o.type === 'other') { + if (o.type === 'other') { // other nodes return $.extend(open, common) } - if (o.type === 'sbml-model') { + if (o.type === 'sbml-model') { // sbml model node return $.extend(open, sbml, common) } - if (o.type === "domain") { + if (o.type === "domain") { // domain node return $.extend(open, common) } } diff --git a/client/project-config.js b/client/project-config.js index b8e9b3f3bd..4ec2f89e75 100644 --- a/client/project-config.js +++ b/client/project-config.js @@ -63,6 +63,13 @@ let getRootContext = (view, node) => { } } +let getWorkflowGroupContext = (view, node) => { + return { + refresh: view.getRefreshContext(node), + download: view.getDownloadWCombineContext(node) + } +} + let move = (view, par, node) => { let newDir = par.original._path !== "/" ? par.original._path : ""; let file = node.original._path.split('/').pop(); @@ -182,7 +189,9 @@ let validateMove = (view, node, more, pos) => { module.exports = { contextZipTypes: contextZipTypes, doubleClick: doubleClick, + // getProjectContext: getOtherContext, getRootContext: getRootContext, + getWorkflowGroupContext: getWorkflowGroupContext, move: move, setup: setup, types: types, diff --git a/client/views/file-browser-view.js b/client/views/file-browser-view.js index 495c89567a..0c4f23de6c 100644 --- a/client/views/file-browser-view.js +++ b/client/views/file-browser-view.js @@ -1378,7 +1378,7 @@ let Model = require('../models/model'); } } // common to all type except root and trash - let common = { + // let common = { // "Rename" : { // "label" : "Rename", // "_disabled" : (o.type === "workflow" && o.original._status === "running"), @@ -1397,16 +1397,16 @@ let Model = require('../models/model'); // self.duplicateFileOrDirectory(o, null) // } // }, - "Delete" : { - "label" : "Delete", - "_disabled" : false, - "separator_before" : false, - "separator_after" : false, - "action" : function (data) { - self.deleteFile(o); - } - } - } + // "Delete" : { + // "label" : "Delete", + // "_disabled" : false, + // "separator_before" : false, + // "separator_after" : false, + // "action" : function (data) { + // self.deleteFile(o); + // } + // } + // } //Specific to zip archives let extractAll = { "extractAll" : { @@ -1433,43 +1433,43 @@ let Model = require('../models/model'); // if (o.type === 'root'){ // return $.extend(refresh, project, commonFolder, downloadWCombine, {"Rename": common.Rename}) // } - if (o.text === "trash"){ + if (o.text === "trash"){ // Trash node return refresh } - if (o.original._path.includes(".proj/trash/")) { + if (o.original._path.includes(".proj/trash/")) { //item in trash return {"Delete": common.Delete} } - if (o.type === 'folder') { + if (o.type === 'folder') { // folder node return $.extend(refresh, commonFolder, download, common) } - if (o.type === 'spatial') { + if (o.type === 'spatial') { // spatial model node return $.extend(commonModel, spatialConvert, download, common) } - if (o.type === 'nonspatial') { + if (o.type === 'nonspatial') { // model node return $.extend(commonModel, modelConvert, download, common) } - if (o.type === 'workflow-group') { - return $.extend(refresh, downloadWCombine) - } - if (o.type === 'workflow') { + // if (o.type === 'workflow-group') { + // return $.extend(refresh, downloadWCombine) + // } + if (o.type === 'workflow') { // workflow node return $.extend(open, workflow, downloadWCombine, common) } - if (o.text.endsWith(".zip")) { + if (o.text.endsWith(".zip")) { // zip archove node return $.extend(open, extractAll, download, common) } - if (o.type === 'notebook') { + if (o.type === 'notebook') { // notebook node if(app.getBasePath() === "/") { return $.extend(open, download, common) } return $.extend(open, notebook, download, common) } - if (o.type === 'other') { + if (o.type === 'other') { // other nodes return $.extend(open, download, common) } - if (o.type === 'sbml-model') { + if (o.type === 'sbml-model') { // sbml model node return $.extend(open, sbml, common) } - if (o.type === "domain") { + if (o.type === "domain") { // domain node return $.extend(open, common) } } diff --git a/client/views/jstree-view.js b/client/views/jstree-view.js index 8207912937..f9d387c197 100644 --- a/client/views/jstree-view.js +++ b/client/views/jstree-view.js @@ -240,6 +240,39 @@ module.exports = View.extend({ }); }); }, + delete: function (node, type) { + if(document.querySelector('#deleteFileModal')) { + document.querySelector('#deleteFileModal').remove(); + } + let modal = $(modals.deleteFileHtml(type)).modal(); + let yesBtn = document.querySelector('#deleteFileModal .yes-modal-btn'); + yesBtn.addEventListener('click', (e) => { + modal.modal('hide'); + let queryStr = `?path=${node.original._path}`; + let endpoint = path.join(app.getApiPath(), "file/delete") + queryStr; + app.getXHR(endpoint, { + success: (err, response, body) => { + let par = $('#files-jstree').jstree().get_node(node.parent); + this.refreshJSTree(par); + if(par.type === "root"){ + let actionsBtn = $(this.queryByHook("options-for-node")); + if(actionsBtn.text().endsWith(node.text)) { + actionsBtn.text("Actions"); + actionsBtn.prop("disabled", true); + this.nodeForContextMenu = ""; + } + } + }, + error: (err, response, body) => { + if(document.querySelector("#errorModal")) { + document.querySelector("#errorModal").remove(); + } + body = JSON.parse(body); + let errorModel = $(modals.errorHtml(body.Reason, body.Message)).modal(); + } + }); + }); + }, duplicate: function (node, identifier, {cb=null, target=null, timeStamp=null}={}) { var queryStr = `?path=${node.original._path}`; if(target) { @@ -321,6 +354,19 @@ module.exports = View.extend({ } } }, + getDeleteContext: function (node, type) { + return { + Delete: { + label: "Delete", + _disabled: false, + separator_before: false, + separator_after: false, + action: (data) => { + this.delete(node, type); + } + } + } + }, getDownloadContext: function (node, options, {asZip=false, withCombine=false}={}) { if(withCombine) { var label = "as .zip"; @@ -429,9 +475,9 @@ module.exports = View.extend({ let dirname = node.original._path === "/" ? "" : node.original._path; let file = this.getFileUploadContext(node, inProject); return { - label: "Upload File", + label: "Upload", _disabled: false, - separator_before: false, + separator_before: true, separator_after: false, submenu: { model: { @@ -735,13 +781,12 @@ module.exports = View.extend({ } optionsButton.text(`Actions for ${node.original.text}`); this.nodeForContextMenu = node; - if (node.type === 'root'){ - return this.config.getRootContext(this, node); - } - if (node.type === 'project'){ - return this.config.getProjectContext(this, node); + let contextMenus = { + root: this.config.getRootContext, + project: this.config.getProjectContext, + "workflow-group": this.config.getWorkflowGroupContext } - + return contextMenus[node.type](this, node); } $(() => { $(document).on('shown.bs.modal', (e) => { From 892f7a6ba8136bd5a256f09901c43475a9061e08 Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Mon, 30 Aug 2021 13:13:40 -0400 Subject: [PATCH 122/186] Added special case for workflow groups in the trash. --- client/file-config.js | 2 +- client/pages/file-browser.js | 80 +++++++++++++------------- client/project-config.js | 3 + client/views/file-browser-view.js | 68 +++++++++++----------- client/views/jstree-view.js | 94 +++++++++++++------------------ 5 files changed, 116 insertions(+), 131 deletions(-) diff --git a/client/file-config.js b/client/file-config.js index e1f93d49bd..2388c886a5 100644 --- a/client/file-config.js +++ b/client/file-config.js @@ -65,7 +65,7 @@ let getOpenProjectContext = (view, node) => { let getProjectContext = (view, node) => { if(node.original._path.split("/")[0] === "trash") { // project in trash - return view.getDeleteContext(node, "project"); + return {delete: view.getDeleteContext(node, "project")}; } return { open: getOpenProjectContext(view, node), diff --git a/client/pages/file-browser.js b/client/pages/file-browser.js index d24f1a1a5d..99a1a08c6b 100644 --- a/client/pages/file-browser.js +++ b/client/pages/file-browser.js @@ -352,46 +352,46 @@ let FileBrowser = PageView.extend({ // modal.modal('hide') // }); // }, - deleteFile: function (o) { - var fileType = o.type - if(fileType === "nonspatial") - fileType = "model"; - else if(fileType === "spatial") - fileType = "spatial model" - else if(fileType === "sbml-model") - fileType = "sbml model" - else if(fileType === "other") - fileType = "file" - var self = this - if(document.querySelector('#deleteFileModal')) { - document.querySelector('#deleteFileModal').remove() - } - let modal = $(modals.deleteFileHtml(fileType)).modal(); - let yesBtn = document.querySelector('#deleteFileModal .yes-modal-btn'); - yesBtn.addEventListener('click', function (e) { - var endpoint = path.join(app.getApiPath(), "file/delete")+"?path="+o.original._path - app.getXHR(endpoint, { - success: function (err, response, body) { - var node = $('#models-jstree').jstree().get_node(o.parent); - if(node.type === "root"){ - self.refreshJSTree(); - let actionsBtn = $(self.queryByHook("options-for-node")); - if(actionsBtn.text().endsWith(o.text)) { - actionsBtn.text("Actions"); - actionsBtn.prop("disabled", true); - self.nodeForContextMenu = ""; - } - }else{ - $('#models-jstree').jstree().refresh_node(node); - } - }, - error: function (err, response, body) { - body = JSON.parse(body); - } - }); - modal.modal('hide') - }); - }, + // deleteFile: function (o) { + // var fileType = o.type + // if(fileType === "nonspatial") + // fileType = "model"; + // else if(fileType === "spatial") + // fileType = "spatial model" + // else if(fileType === "sbml-model") + // fileType = "sbml model" + // else if(fileType === "other") + // fileType = "file" + // var self = this + // if(document.querySelector('#deleteFileModal')) { + // document.querySelector('#deleteFileModal').remove() + // } + // let modal = $(modals.deleteFileHtml(fileType)).modal(); + // let yesBtn = document.querySelector('#deleteFileModal .yes-modal-btn'); + // yesBtn.addEventListener('click', function (e) { + // var endpoint = path.join(app.getApiPath(), "file/delete")+"?path="+o.original._path + // app.getXHR(endpoint, { + // success: function (err, response, body) { + // var node = $('#models-jstree').jstree().get_node(o.parent); + // if(node.type === "root"){ + // self.refreshJSTree(); + // let actionsBtn = $(self.queryByHook("options-for-node")); + // if(actionsBtn.text().endsWith(o.text)) { + // actionsBtn.text("Actions"); + // actionsBtn.prop("disabled", true); + // self.nodeForContextMenu = ""; + // } + // }else{ + // $('#models-jstree').jstree().refresh_node(node); + // } + // }, + // error: function (err, response, body) { + // body = JSON.parse(body); + // } + // }); + // modal.modal('hide') + // }); + // }, // duplicateFileOrDirectory: function(o, type) { // var self = this; // var parentID = o.parent; diff --git a/client/project-config.js b/client/project-config.js index 4ec2f89e75..28801fcae8 100644 --- a/client/project-config.js +++ b/client/project-config.js @@ -64,6 +64,9 @@ let getRootContext = (view, node) => { } let getWorkflowGroupContext = (view, node) => { + if(node.original._path.includes(".proj/trash/")) { //item in trash + return {delete: view.getDeleteContext(node, "workflow group")}; + } return { refresh: view.getRefreshContext(node), download: view.getDownloadWCombineContext(node) diff --git a/client/views/file-browser-view.js b/client/views/file-browser-view.js index 0c4f23de6c..1129a2994f 100644 --- a/client/views/file-browser-view.js +++ b/client/views/file-browser-view.js @@ -357,40 +357,40 @@ let Model = require('../models/model'); // } // }, false); // }, - deleteFile: function (o) { - var fileType = o.type - if(fileType === "nonspatial") - fileType = "model"; - else if(fileType === "spatial") - fileType = "spatial model" - else if(fileType === "sbml-model") - fileType = "sbml model" - else if(fileType === "other") - fileType = "file" - var self = this - if(document.querySelector('#deleteFileModal')) { - document.querySelector('#deleteFileModal').remove() - } - let modal = $(modals.deleteFileHtml(fileType)).modal(); - let yesBtn = document.querySelector('#deleteFileModal .yes-modal-btn'); - yesBtn.addEventListener('click', function (e) { - var endpoint = path.join(app.getApiPath(), "file/delete")+"?path="+o.original._path - app.getXHR(endpoint, { - success: function (err, response, body) { - var node = $('#models-jstree-view').jstree().get_node(o.parent); - if(node.type === "root"){ - self.refreshJSTree(); - }else{ - $('#models-jstree-view').jstree().refresh_node(node); - } - } - }); - modal.modal('hide') - if(o.type !== "notebook" || o.original._path.includes(".wkgp")) { - self.updateParent(o.type) - } - }); - }, + // deleteFile: function (o) { + // var fileType = o.type + // if(fileType === "nonspatial") + // fileType = "model"; + // else if(fileType === "spatial") + // fileType = "spatial model" + // else if(fileType === "sbml-model") + // fileType = "sbml model" + // else if(fileType === "other") + // fileType = "file" + // var self = this + // if(document.querySelector('#deleteFileModal')) { + // document.querySelector('#deleteFileModal').remove() + // } + // let modal = $(modals.deleteFileHtml(fileType)).modal(); + // let yesBtn = document.querySelector('#deleteFileModal .yes-modal-btn'); + // yesBtn.addEventListener('click', function (e) { + // var endpoint = path.join(app.getApiPath(), "file/delete")+"?path="+o.original._path + // app.getXHR(endpoint, { + // success: function (err, response, body) { + // var node = $('#models-jstree-view').jstree().get_node(o.parent); + // if(node.type === "root"){ + // self.refreshJSTree(); + // }else{ + // $('#models-jstree-view').jstree().refresh_node(node); + // } + // } + // }); + // modal.modal('hide') + // if(o.type !== "notebook" || o.original._path.includes(".wkgp")) { + // self.updateParent(o.type) + // } + // }); + // }, // duplicateFileOrDirectory: function(o, type) { // var self = this; // var parentID = o.parent; diff --git a/client/views/jstree-view.js b/client/views/jstree-view.js index f9d387c197..e46a0a0474 100644 --- a/client/views/jstree-view.js +++ b/client/views/jstree-view.js @@ -64,7 +64,7 @@ module.exports = View.extend({ }, "dataType" : "json", "data" : (node) => { - return { 'id' : node.id} + return { 'id' : node.id}; } } this.treeSettings = { @@ -132,12 +132,9 @@ module.exports = View.extend({ app.getXHR(existEP, { always: (err, response, body) => { if(body.exists) { - if(document.querySelector("#errorModal")) { - document.querySelector("#errorModal").remove(); - } let title = "Model Already Exists"; let message = "A model already exists with that name"; - let errorModel = $(modals.errorHtml(title, message)).modal(); + this.reportError({Reason: title, Measage: message}); }else{ let endpoint = path.join(app.getBasePath(), "stochss/models/edit") + queryStr; window.location.href = endpoint; @@ -154,12 +151,9 @@ module.exports = View.extend({ window.location.href = endpoint; }, error: (err, response, body) => { - if(document.querySelector("#errorModal")) { - document.querySelector("#errorModal").remove(); - } let title = "Model Already Exists"; let message = "A model already exists with that name"; - let errorModel = $(modals.errorHtml(title, message)).modal(); + this.reportError({Reason: title, Measage: message}); } }); }, @@ -178,14 +172,10 @@ module.exports = View.extend({ let endpoint = path.join(app.getApiPath(), "directory/create") + queryStr; app.getXHR(endpoint, { success: (err, response, body) => { - this.refreshJSTree(node) + this.refreshJSTree(node); }, error: (err, response, body) => { - if(document.querySelector("#errorModal")) { - document.querySelector("#errorModal").remove(); - } - body = JSON.parse(body); - let errorModal = $(modals.errorHtml(body.Reason, body.Message)).modal(); + this.reportError(JSON.parse(body)); } }); }); @@ -232,10 +222,7 @@ module.exports = View.extend({ this.openProject(body.path); }, error: (err, response, body) => { - if(document.querySelector("#errorModal")) { - document.querySelector("#errorModal").remove(); - } - let errorModel = $(modals.errorHtml(body.Reason, body.Message)).modal(); + this.reportError(body); } }); }); @@ -254,6 +241,7 @@ module.exports = View.extend({ success: (err, response, body) => { let par = $('#files-jstree').jstree().get_node(node.parent); this.refreshJSTree(par); + this.config.updateParent(node.type); if(par.type === "root"){ let actionsBtn = $(this.queryByHook("options-for-node")); if(actionsBtn.text().endsWith(node.text)) { @@ -264,11 +252,7 @@ module.exports = View.extend({ } }, error: (err, response, body) => { - if(document.querySelector("#errorModal")) { - document.querySelector("#errorModal").remove(); - } - body = JSON.parse(body); - let errorModel = $(modals.errorHtml(body.Reason, body.Message)).modal(); + this.reportError(JSON.parse(body)); } }); }); @@ -356,14 +340,12 @@ module.exports = View.extend({ }, getDeleteContext: function (node, type) { return { - Delete: { - label: "Delete", - _disabled: false, - separator_before: false, - separator_after: false, - action: (data) => { - this.delete(node, type); - } + label: "Delete", + _disabled: false, + separator_before: false, + separator_after: false, + action: (data) => { + this.delete(node, type); } } }, @@ -411,14 +393,14 @@ module.exports = View.extend({ if(!options) { options = {}; } - let label = Boolean(options.cb) ? "Duplicate as new" : "Duplicate" + let label = Boolean(options.cb) ? "Duplicate as new" : "Duplicate"; return { label: label, _disabled: false, separator_before: false, separator_after: false, action: (data) => { - this.duplicate(node, identifier, options) + this.duplicate(node, identifier, options); } } }, @@ -455,7 +437,7 @@ module.exports = View.extend({ if(dataType === "plain-text") { body = JSON.parse(body); } - console.log(body) + this.reportError(body); } }); }, @@ -668,10 +650,7 @@ module.exports = View.extend({ } }, error: (err, response, body) => { - if(document.querySelector("#errorModal")) { - document.querySelector("#errorModal").remove(); - } - let errorModal = $(modals.errorHtml(body.Reason, body.Message)).modal(); + this.reportError(body); } }); }); @@ -711,8 +690,8 @@ module.exports = View.extend({ }, refreshJSTree: function (node) { if(node === null || node.type === 'root'){ - this.jstreeIsLoaded = false - $('#files-jstree').jstree().deselect_all(true) + this.jstreeIsLoaded = false; + $('#files-jstree').jstree().deselect_all(true); $('#files-jstree').jstree().refresh(); }else{ $('#files-jstree').jstree().refresh_node(node); @@ -750,6 +729,12 @@ module.exports = View.extend({ nameWarning.collapse('hide'); }); }, + reportError: function (body) { + if(document.querySelector("#errorModal")) { + document.querySelector("#errorModal").remove(); + } + let errorModal = $(modals.errorHtml(body.Reason, body.Message)).modal(); + }, selectNode: function (node, fileName) { if(!this.jstreeIsLoaded || !$('#files-jstree').jstree().is_loaded(node) && $('#files-jstree').jstree().is_loading(node)) { setTimeout(_.bind(this.selectNode, this, node, fileName), 1000); @@ -823,28 +808,28 @@ module.exports = View.extend({ $('#files-jstree').jstree().show_contextmenu(this.nodeForContextMenu); }, validateName: function (input, {rename = false, saveAs = true}={}) { - var error = "" + var error = ""; if(input.endsWith('/')) { - error = 'forward' + error = 'forward'; } - var invalidChars = "`~!@#$%^&*=+[{]}\"|:;'<,>?\\" + var invalidChars = "`~!@#$%^&*=+[{]}\"|:;'<,>?\\"; if(rename || !saveAs) { - invalidChars += "/" + invalidChars += "/"; } for(var i = 0; i < input.length; i++) { if(invalidChars.includes(input.charAt(i))) { - error = error === "" || error === "special" ? "special" : "both" + error = error === "" || error === "special" ? "special" : "both"; } } - return error + return error; }, uploadFile: function (node, dirname, type, inProject) { if(document.querySelector('#uploadFileModal')) { - document.querySelector('#uploadFileModal').remove() + document.querySelector('#uploadFileModal').remove(); } if(this.isSafariV14Plus == undefined){ let browser = app.getBrowser(); - this.isSafariV14Plus = (browser.name === "Safari" && browser.version >= 14) + this.isSafariV14Plus = (browser.name === "Safari" && browser.version >= 14); } let modal = $(modals.uploadFileHtml(type, this.isSafariV14Plus)).modal(); let uploadBtn = document.querySelector('#uploadFileModal .upload-modal-btn'); @@ -868,7 +853,7 @@ module.exports = View.extend({ return {saveAs: true}; } let validateFile = () => { - let options = getOptions(fileInput.files[0]) + let options = getOptions(fileInput.files[0]); let fileErr = !fileInput.files.length ? "" : this.validateName(fileInput.files[0].name, options); let nameErr = this.validateName(input.value, options); if(!fileInput.files.length) { @@ -895,7 +880,7 @@ module.exports = View.extend({ uploadBtn.addEventListener('click', (e) => { modal.modal('hide'); let file = fileInput.files[0]; - let options = getOptions(file) + let options = getOptions(file); let fileinfo = {type: type, name: "", path: dirname}; if(Boolean(input.value) && this.validateName(input.value.trim(), options) === ""){ fileinfo.name = input.value.trim(); @@ -908,7 +893,7 @@ module.exports = View.extend({ success: (err, response, body) => { body = JSON.parse(body); this.refreshJSTree(node); - this.config.updateParent(this, "model") + this.config.updateParent(this, "model"); if(body.errors.length > 0){ if(document.querySelector("#uploadFileErrorsModal")) { document.querySelector("#uploadFileErrorsModal").remove(); @@ -917,11 +902,8 @@ module.exports = View.extend({ } }, error: (err, response, body) => { - if(document.querySelector("#errorModal")) { - document.querySelector("#errorModal").remove(); - } body = JSON.parse(body); - let zipErrorModal = $(modals.errorHtml(body.Reason, body.Message)).modal(); + this.reportError(body); } }, false); }); From 4997f0e867849b5e0d916224d0072ec5847a975c Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Mon, 30 Aug 2021 17:10:32 -0400 Subject: [PATCH 123/186] Moved context menus for folders and file browser models and the required function. --- client/file-config.js | 79 ++++++++- client/pages/file-browser.js | 286 +++++++++++++++--------------- client/project-config.js | 30 +++- client/views/file-browser-view.js | 248 +++++++++++++------------- client/views/jstree-view.js | 122 ++++++++++++- 5 files changed, 482 insertions(+), 283 deletions(-) diff --git a/client/file-config.js b/client/file-config.js index 2388c886a5..eaf5b783fa 100644 --- a/client/file-config.js +++ b/client/file-config.js @@ -29,8 +29,7 @@ let doubleClick = (view, e) => { if(node.type === "folder" && $('#files-jstree').jstree().is_open(node) && $('#files-jstree').jstree().is_loaded(node)){ view.refreshJSTree(node); }else if(node.type === "nonspatial" || node.type === "spatial"){ - let queryStr = "?path=" + node.original._path; - window.location.href = path.join(app.getBasePath(), "stochss/models/edit") + queryStr; + view.openModel(node.original._path); }else if(node.type === "notebook"){ window.open(path.join(app.getBasePath(), "notebooks", node.original._path), '_blank'); }else if(node.type === "sbml-model"){ @@ -50,6 +49,54 @@ let doubleClick = (view, e) => { } } +let getFolderContext = (view, node) => { + if(node.text === "trash") {//Trash node + return {refresh: view.getRefreshContext(node)}; + } + if(node.original._path.split("/")[0] === "trash") { // item in trash + return {delete: view.getDeleteContext(node, "directory")}; + } + let dirname = node.original._path; + let downloadOptions = {dataType: "zip", identifier: "file/download-zip"}; + let options = {asZip: true}; + return { + refresh: view.getRefreshContext(node), + newDirectory: view.getNewDirectoryContext(node), + newProject: { + label: "New Project", + _disabled: false, + separator_before: false, + separator_after: false, + action: (data) => { + view.createProject(node, dirname); + } + }, + newModel: view.getNewModelContext(node, false), + newDomain: view.getNewDomainContext(node), + upload: view.getFullUploadContext(node, false), + download: view.getDownloadContext(node, downloadOptions, options), + rename: view.getRenameContext(node), + duplicate: view.getDuplicateContext(node, "directory/duplicate"), + moveToTrash: view.getMoveToTrashContext(node) + } +} + +let getModelContext = (view, node) => { + if(node.original._path.split("/")[0] === "trash") { // item in trash + return {delete: view.getDeleteContext(node, "model")}; + } + let downloadOptions = {dataType: "json", identifier: "file/json-data"} + return { + edit: view.getEditModelContext(node), + newWorkflow: view.getFullNewWorkflowContext(node), + convert: view.getMdlConvertContext(node), + download: view.getDownloadContext(node, downloadOptions), + rename: view.getRenameContext(node), + duplicate: view.getDuplicateContext(node, "file/duplicate"), + moveToTrash: view.getMoveToTrashContext(node) + } +} + let getOpenProjectContext = (view, node) => { return { label: "Open", @@ -127,6 +174,30 @@ let setup = (view) => { $(view.queryByHook("fb-import-model")).css("display", "none"); } +let toSBML = (view, node) => { + let queryStr = `?path=${node.original._path}`; + let endpoint = path.join(app.getApiPath(), "model/to-sbml") + queryStr; + app.getXHR(endpoint, { + success: (err, response, body) => { + let par = $('#files-jstree').jstree().get_node(node.parent); + view.refreshJSTree(par); + view.selectNode(node, body.File); + } + }); +} + +let toSpatial = (view, node) => { + let queryStr = `?path=${node.original._path}`; + let endpoint = path.join(app.getApiPath(), "model/to-spatial") + queryStr; + app.getXHR(endpoint, { + success: (err, response, body) => { + let par = $('#files-jstree').jstree().get_node(node.parent); + view.refreshJSTree(par); + view.selectNode(node, body.File); + } + }); +} + let types = { 'root' : {"icon": "jstree-icon jstree-folder"}, 'folder' : {"icon": "jstree-icon jstree-folder"}, @@ -175,11 +246,15 @@ let validateMove = (view, node, more, pos) => { module.exports = { contextZipTypes: contextZipTypes, doubleClick: doubleClick, + getFolderContext: getFolderContext, + getModelContext: getModelContext, getProjectContext: getProjectContext, getRootContext: getRootContext, // getWorflowGroupContext: getOtherContext, move: move, setup: setup, + toSBML: toSBML, + toSpatial: toSpatial, types: types, updateParent: updateParent, validateMove: validateMove diff --git a/client/pages/file-browser.js b/client/pages/file-browser.js index 99a1a08c6b..529fbc548e 100644 --- a/client/pages/file-browser.js +++ b/client/pages/file-browser.js @@ -459,22 +459,22 @@ let FileBrowser = PageView.extend({ } return "_" + month + day + year + "_" + hours + minutes + seconds; }, - toSpatial: function (o) { - var self = this; - var parentID = o.parent; - var endpoint = path.join(app.getApiPath(), "model/to-spatial")+"?path="+o.original._path; - app.getXHR(endpoint, { - success: function (err, response, body) { - var node = $('#models-jstree').jstree().get_node(parentID); - if(node.type === "root"){ - self.refreshJSTree(); - }else{ - $('#models-jstree').jstree().refresh_node(node); - } - self.selectNode(node, body.File); - } - }); - }, + // toSpatial: function (o) { + // var self = this; + // var parentID = o.parent; + // var endpoint = path.join(app.getApiPath(), "model/to-spatial")+"?path="+o.original._path; + // app.getXHR(endpoint, { + // success: function (err, response, body) { + // var node = $('#models-jstree').jstree().get_node(parentID); + // if(node.type === "root"){ + // self.refreshJSTree(); + // }else{ + // $('#models-jstree').jstree().refresh_node(node); + // } + // self.selectNode(node, body.File); + // } + // }); + // }, toModel: function (o, from) { var self = this; var parentID = o.parent; @@ -524,22 +524,22 @@ let FileBrowser = PageView.extend({ } }); }, - toSBML: function (o) { - var self = this; - var parentID = o.parent; - var endpoint = path.join(app.getApiPath(), "model/to-sbml")+"?path="+o.original._path; - app.getXHR(endpoint, { - success: function (err, response, body) { - var node = $('#models-jstree').jstree().get_node(parentID); - if(node.type === "root"){ - self.refreshJSTree(); - }else{ - $('#models-jstree').jstree().refresh_node(node); - } - self.selectNode(node, body.File); - } - }); - }, + // toSBML: function (o) { + // var self = this; + // var parentID = o.parent; + // var endpoint = path.join(app.getApiPath(), "model/to-sbml")+"?path="+o.original._path; + // app.getXHR(endpoint, { + // success: function (err, response, body) { + // var node = $('#models-jstree').jstree().get_node(parentID); + // if(node.type === "root"){ + // self.refreshJSTree(); + // }else{ + // $('#models-jstree').jstree().refresh_node(node); + // } + // self.selectNode(node, body.File); + // } + // }); + // }, // renameNode: function (o) { // var self = this // var text = o.text; @@ -715,26 +715,26 @@ let FileBrowser = PageView.extend({ // } // }); // }, - newWorkflow: function (o, type) { - let self = this; - let model = new Model({ - directory: o.original._path - }); - app.getXHR(model.url(), { - success: function (err, response, body) { - model.set(body); - model.updateValid(); - if(model.valid){ - app.newWorkflow(self, o.original._path, o.type === "spatial", type); - }else{ - let title = "Model Errors Detected"; - let endpoint = path.join(app.getBasePath(), "stochss/models/edit") + '?path=' + model.directory + '&validate'; - let message = 'Errors were detected in you model click here to fix your model'; - $(modals.modelErrorHtml(title, message)).modal(); - } - } - }); - }, + // newWorkflow: function (o, type) { + // let self = this; + // let model = new Model({ + // directory: o.original._path + // }); + // app.getXHR(model.url(), { + // success: function (err, response, body) { + // model.set(body); + // model.updateValid(); + // if(model.valid){ + // app.newWorkflow(self, o.original._path, o.type === "spatial", type); + // }else{ + // let title = "Model Errors Detected"; + // let endpoint = path.join(app.getBasePath(), "stochss/models/edit") + '?path=' + model.directory + '&validate'; + // let message = 'Errors were detected in you model click here to fix your model'; + // $(modals.modelErrorHtml(title, message)).modal(); + // } + // } + // }); + // }, // addExistingModel: function (o) { // var self = this // if(document.querySelector('#newProjectModelModal')){ @@ -1171,54 +1171,54 @@ let FileBrowser = PageView.extend({ // } // } // common to both spatial and non-spatial models - let newWorkflow = { - "ensembleSimulation" : { - "label" : "Ensemble Simulation", - "_disabled" : false, - "separator_before" : false, - "separator_after" : false, - "action" : function (data) { - self.newWorkflow(o, "Ensemble Simulation") - } - }, - "parameterSweep" : { - "label" : "Parameter Sweep", - "_disabled" : false, - "separator_before" : false, - "separator_after" : false, - "action" : function (data) { - self.newWorkflow(o, "Parameter Sweep") - } - }, - "jupyterNotebook" : { - "label" : "Jupyter Notebook", - "_disabled" : false, - "separator_before" : false, - "separator_after" : false, - "action" : function (data) { - window.location.href = path.join(app.getBasePath(), "stochss/workflow/selection")+"?path="+o.original._path; - } - } - } - let model = { - "Edit" : { - "label" : "Edit", - "_disabled" : false, - "_class" : "font-weight-bolder", - "separator_before" : false, - "separator_after" : true, - "action" : function (data) { - window.location.href = path.join(app.getBasePath(), "stochss/models/edit")+"?path="+o.original._path; - } - }, - "New Workflow" : { - "label" : "New Workflow", - "_disabled" : false, - "separator_before" : false, - "separator_after" : false, - "submenu" : o.type === "nonspatial" ? newWorkflow : {"jupyterNotebook":newWorkflow.jupyterNotebook} - } - } + // let newWorkflow = { + // "ensembleSimulation" : { + // "label" : "Ensemble Simulation", + // "_disabled" : false, + // "separator_before" : false, + // "separator_after" : false, + // "action" : function (data) { + // self.newWorkflow(o, "Ensemble Simulation") + // } + // }, + // "parameterSweep" : { + // "label" : "Parameter Sweep", + // "_disabled" : false, + // "separator_before" : false, + // "separator_after" : false, + // "action" : function (data) { + // self.newWorkflow(o, "Parameter Sweep") + // } + // }, + // "jupyterNotebook" : { + // "label" : "Jupyter Notebook", + // "_disabled" : false, + // "separator_before" : false, + // "separator_after" : false, + // "action" : function (data) { + // window.location.href = path.join(app.getBasePath(), "stochss/workflow/selection")+"?path="+o.original._path; + // } + // } + // } + // let model = { + // "Edit" : { + // "label" : "Edit", + // "_disabled" : false, + // "_class" : "font-weight-bolder", + // "separator_before" : false, + // "separator_after" : true, + // "action" : function (data) { + // window.location.href = path.join(app.getBasePath(), "stochss/models/edit")+"?path="+o.original._path; + // } + // }, + // "New Workflow" : { + // "label" : "New Workflow", + // "_disabled" : false, + // "separator_before" : false, + // "separator_after" : false, + // "submenu" : o.type === "nonspatial" ? newWorkflow : {"jupyterNotebook":newWorkflow.jupyterNotebook} + // } + // } // convert options for spatial models let spatialConvert = { "Convert" : { @@ -1249,43 +1249,43 @@ let FileBrowser = PageView.extend({ } } // convert options for non-spatial models - let modelConvert = { - "Convert" : { - "label" : "Convert", - "_disabled" : false, - "separator_before" : false, - "separator_after" : false, - "submenu" : { - "Convert to Spatial" : { - "label" : "To Spatial Model", - "_disabled" : false, - "separator_before" : false, - "separator_after" : false, - "action" : function (data) { - self.toSpatial(o) - } - }, - "Convert to Notebook" : { - "label" : "To Notebook", - "_disabled" : false, - "separator_before" : false, - "separator_after" : false, - "action" : function (data) { - self.toNotebook(o, "model") - } - }, - "Convert to SBML" : { - "label" : "To SBML Model", - "_disabled" : false, - "separator_before" : false, - "separator_after" : false, - "action" : function (data) { - self.toSBML(o) - } - } - } - } - } + // let modelConvert = { + // "Convert" : { + // "label" : "Convert", + // "_disabled" : false, + // "separator_before" : false, + // "separator_after" : false, + // "submenu" : { + // "Convert to Spatial" : { + // "label" : "To Spatial Model", + // "_disabled" : false, + // "separator_before" : false, + // "separator_after" : false, + // "action" : function (data) { + // self.toSpatial(o) + // } + // }, + // "Convert to Notebook" : { + // "label" : "To Notebook", + // "_disabled" : false, + // "separator_before" : false, + // "separator_after" : false, + // "action" : function (data) { + // self.toNotebook(o, "model") + // } + // }, + // "Convert to SBML" : { + // "label" : "To SBML Model", + // "_disabled" : false, + // "separator_before" : false, + // "separator_after" : false, + // "action" : function (data) { + // self.toSBML(o) + // } + // } + // } + // } + // } // For notebooks, workflows, sbml models, and other files let open = { "Open" : { @@ -1428,15 +1428,15 @@ let FileBrowser = PageView.extend({ // if (o.type === 'root'){ // return folder // } - if (o.text === "trash") {//Trash node - return {"Refresh": folder.Refresh} - } + // if (o.text === "trash") {//Trash node + // return {"Refresh": folder.Refresh} + // } if (o.original._path.split("/")[0] === "trash") { // item in trash return delete_node } - if (o.type === 'folder') { // folder node - return $.extend(folder, common) - } + // if (o.type === 'folder') { // folder node + // return $.extend(folder, common) + // } if (o.type === 'spatial') { // spatial model node return $.extend(model, spatialConvert, common) } diff --git a/client/project-config.js b/client/project-config.js index 28801fcae8..88840a2d0b 100644 --- a/client/project-config.js +++ b/client/project-config.js @@ -29,8 +29,7 @@ let doubleClick = (view, e) => { if((node.type === "folder" || node.type === "workflow-group") && $('#files-jstree').jstree().is_open(node) && $('#files-jstree').jstree().is_loaded(node)){ view.refreshJSTree(node); }else if(node.type === "nonspatial" || node.type === "spatial"){ - let queryStr = "?path=" + node.original._path; - window.location.href = path.join(app.getBasePath(), "stochss/models/edit") + queryStr; + view.openModel(node.original._path); }else if(node.type === "notebook"){ window.open(path.join(app.getBasePath(), "notebooks", node.original._path), '_blank'); }else if(node.type === "sbml-model"){ @@ -48,16 +47,34 @@ let doubleClick = (view, e) => { } } +let getFolderContext = (view, node) => { + if(node.text === "trash"){ // Trash node + return {refresh: view.getRefreshContext(node)}; + } + if(node.original._path.includes(".proj/trash/")) { //item in trash + return {delete: view.getDeleteContext(node, "directory")}; + } + let downloadOptions = {dataType: "zip", identifier: "file/download-zip"}; + let options = {asZip: true}; + return { + refresh: view.getRefreshContext(node), + newDirectory: view.getNewDirectoryContext(node), + newDomain: view.getNewDomainContext(node), + upload: view.getFileUploadContext(node, true, {label: "Uplaod File"}), + download: view.getDownloadContext(node, downloadOptions, options), + rename: view.getRenameContext(node), + duplicate: view.getDuplicateContext(node, "directory/duplicate"), + delete: view.getDeleteContext(node, "directory") + } +} + let getRootContext = (view, node) => { - let upload = node.type === "root" ? - view.getFullUploadContext(node, true) : - view.getFileUploadContext(node, true); return { refresh: view.getRefreshContext(node), addModel: view.getAddModelContext(node), newDirectory: view.getNewDirectoryContext(node), newDomain: view.getNewDomainContext(node), - upload: upload, + upload: view.getFullUploadContext(node, true), download: view.getDownloadWCombineContext(node), rename: view.getRenameContext(node) } @@ -192,6 +209,7 @@ let validateMove = (view, node, more, pos) => { module.exports = { contextZipTypes: contextZipTypes, doubleClick: doubleClick, + getFolderContext: getFolderContext, // getProjectContext: getOtherContext, getRootContext: getRootContext, getWorkflowGroupContext: getWorkflowGroupContext, diff --git a/client/views/file-browser-view.js b/client/views/file-browser-view.js index 1129a2994f..50f4d3009c 100644 --- a/client/views/file-browser-view.js +++ b/client/views/file-browser-view.js @@ -755,26 +755,26 @@ let Model = require('../models/model'); // } // }); // }, - newWorkflow: function (o, type) { - let self = this; - let model = new Model({ - directory: o.original._path - }); - app.getXHR(model.url(), { - success: function (err, response, body) { - model.set(body); - model.updateValid(); - if(model.valid){ - app.newWorkflow(self, o.original._path, o.type === "spatial", type); - }else{ - let title = "Model Errors Detected"; - let endpoint = path.join(app.getBasePath(), "stochss/models/edit") + '?path=' + model.directory + '&validate'; - let message = 'Errors were detected in you model click here to fix your model'; - $(modals.modelErrorHtml(title, message)).modal(); - } - } - }); - }, + // newWorkflow: function (o, type) { + // let self = this; + // let model = new Model({ + // directory: o.original._path + // }); + // app.getXHR(model.url(), { + // success: function (err, response, body) { + // model.set(body); + // model.updateValid(); + // if(model.valid){ + // app.newWorkflow(self, o.original._path, o.type === "spatial", type); + // }else{ + // let title = "Model Errors Detected"; + // let endpoint = path.join(app.getBasePath(), "stochss/models/edit") + '?path=' + model.directory + '&validate'; + // let message = 'Errors were detected in you model click here to fix your model'; + // $(modals.modelErrorHtml(title, message)).modal(); + // } + // } + // }); + // }, // addModel: function (parentPath, modelName, message) { // var endpoint = path.join(app.getBasePath(), "stochss/models/edit") // if(parentPath.endsWith(".proj")) { @@ -1087,47 +1087,47 @@ let Model = require('../models/model'); // } // } // option for uploading files - let uploadFile = { - "Upload": { - "label" : o.type === "root" ? "File" : "Upload File", - "_disabled" : false, - "separator_before" : false, - "separator_after" : o.type !== "root", - "action" : function (data) { - self.uploadFile(o, "file") - } - } - } - // all upload options - let uploadAll = { - "Upload" : { - "label" : "Upload File", - "_disabled" : false, - "separator_before" : false, - "separator_after" : true, - "submenu" : { - "Model" : { - "label" : "StochSS Model", - "_disabled" : false, - "separator_before" : false, - "separator_after" : false, - "action" : function (data) { - self.uploadFile(o, "model") - } - }, - "SBML" : { - "label" : "SBML Model", - "_disabled" : false, - "separator_before" : false, - "separator_after" : false, - "action" : function (data) { - self.uploadFile(o, "sbml") - } - }, - "File" : uploadFile.Upload - } - } - } + // let uploadFile = { + // "Upload": { + // "label" : o.type === "root" ? "File" : "Upload File", + // "_disabled" : false, + // "separator_before" : false, + // "separator_after" : o.type !== "root", + // "action" : function (data) { + // self.uploadFile(o, "file") + // } + // } + // } + // // all upload options + // let uploadAll = { + // "Upload" : { + // "label" : "Upload File", + // "_disabled" : false, + // "separator_before" : false, + // "separator_after" : true, + // "submenu" : { + // "Model" : { + // "label" : "StochSS Model", + // "_disabled" : false, + // "separator_before" : false, + // "separator_after" : false, + // "action" : function (data) { + // self.uploadFile(o, "model") + // } + // }, + // "SBML" : { + // "label" : "SBML Model", + // "_disabled" : false, + // "separator_before" : false, + // "separator_after" : false, + // "action" : function (data) { + // self.uploadFile(o, "sbml") + // } + // }, + // "File" : uploadFile.Upload + // } + // } + // } // common to folder and root // let commonFolder = { // "New_Directory" : { @@ -1194,35 +1194,35 @@ let Model = require('../models/model'); // } // } // menu option for creating new workflows - let newWorkflow = { - "ensembleSimulation" : { - "label" : "Ensemble Simulation", - "_disabled" : false, - "separator_before" : false, - "separator_after" : false, - "action" : function (data) { - self.newWorkflow(o, "Ensemble Simulation") - } - }, - "parameterSweep" : { - "label" : "Parameter Sweep", - "_disabled" : false, - "separator_before" : false, - "separator_after" : false, - "action" : function (data) { - self.newWorkflow(o, "Parameter Sweep") - } - }, - "jupyterNotebook" : { - "label" : "Jupyter Notebook", - "_disabled" : false, - "separator_before" : false, - "separator_after" : false, - "action" : function (data) { - window.location.href = path.join(app.getBasePath(), "stochss/workflow/selection")+"?path="+o.original._path; - } - } - } + // let newWorkflow = { + // "ensembleSimulation" : { + // "label" : "Ensemble Simulation", + // "_disabled" : false, + // "separator_before" : false, + // "separator_after" : false, + // "action" : function (data) { + // self.newWorkflow(o, "Ensemble Simulation") + // } + // }, + // "parameterSweep" : { + // "label" : "Parameter Sweep", + // "_disabled" : false, + // "separator_before" : false, + // "separator_after" : false, + // "action" : function (data) { + // self.newWorkflow(o, "Parameter Sweep") + // } + // }, + // "jupyterNotebook" : { + // "label" : "Jupyter Notebook", + // "_disabled" : false, + // "separator_before" : false, + // "separator_after" : false, + // "action" : function (data) { + // window.location.href = path.join(app.getBasePath(), "stochss/workflow/selection")+"?path="+o.original._path; + // } + // } + // } // common to all models let commonModel = { "Edit" : { @@ -1253,34 +1253,34 @@ let Model = require('../models/model'); } } // convert options for non-spatial models - let modelConvert = { - "Convert" : { - "label" : "Convert", - "_disabled" : false, - "separator_before" : false, - "separator_after" : true, - "submenu" : { - "Convert to Spatial" : { - "label" : "To Spatial Model", - "_disabled" : false, - "separator_before" : false, - "separator_after" : false, - "action" : function (data) { - self.toSpatial(o) - } - }, - "Convert to SBML" : { - "label" : "To SBML Model", - "_disabled" : false, - "separator_before" : false, - "separator_after" : false, - "action" : function (data) { - self.toSBML(o) - } - } - } - } - } + // let modelConvert = { + // "Convert" : { + // "label" : "Convert", + // "_disabled" : false, + // "separator_before" : false, + // "separator_after" : true, + // "submenu" : { + // "Convert to Spatial" : { + // "label" : "To Spatial Model", + // "_disabled" : false, + // "separator_before" : false, + // "separator_after" : false, + // "action" : function (data) { + // self.toSpatial(o) + // } + // }, + // "Convert to SBML" : { + // "label" : "To SBML Model", + // "_disabled" : false, + // "separator_before" : false, + // "separator_after" : false, + // "action" : function (data) { + // self.toSBML(o) + // } + // } + // } + // } + // } // convert options for spatial models let spatialConvert = { "Convert" : { @@ -1433,15 +1433,15 @@ let Model = require('../models/model'); // if (o.type === 'root'){ // return $.extend(refresh, project, commonFolder, downloadWCombine, {"Rename": common.Rename}) // } - if (o.text === "trash"){ // Trash node - return refresh - } + // if (o.text === "trash"){ // Trash node + // return refresh + // } if (o.original._path.includes(".proj/trash/")) { //item in trash return {"Delete": common.Delete} } - if (o.type === 'folder') { // folder node - return $.extend(refresh, commonFolder, download, common) - } + // if (o.type === 'folder') { // folder node + // return $.extend(refresh, commonFolder, download, common) + // } if (o.type === 'spatial') { // spatial model node return $.extend(commonModel, spatialConvert, download, common) } diff --git a/client/views/jstree-view.js b/client/views/jstree-view.js index e46a0a0474..8361ef24e4 100644 --- a/client/views/jstree-view.js +++ b/client/views/jstree-view.js @@ -23,6 +23,8 @@ let jstree = require('jstree'); //support files let app = require('../app'); let modals = require('../modals'); +//models +let Model = require('../models/model'); //config let FileConfig = require('../file-config') let ProjectConfig = require('../project-config'); @@ -136,8 +138,7 @@ module.exports = View.extend({ let message = "A model already exists with that name"; this.reportError({Reason: title, Measage: message}); }else{ - let endpoint = path.join(app.getBasePath(), "stochss/models/edit") + queryStr; - window.location.href = endpoint; + this.openModel(path.join(dirname, file)); } } }); @@ -147,8 +148,7 @@ module.exports = View.extend({ let newMdlEP = path.join(app.getApiPath(), "project/new-model") + queryStr; app.getXHR(newMdlEP, { success: (err, response, body) => { - let endpoint = path.join(app.getBasePath(), "stochss/models/edit") + `?path=${body.path}`; - window.location.href = endpoint; + this.openModel(body.path); }, error: (err, response, body) => { let title = "Model Already Exists"; @@ -404,6 +404,18 @@ module.exports = View.extend({ } } }, + getEditModelContext: function (node) { + return { + label: "Edit", + _disabled: false, + _class: "font-weight-bolder", + separator_before: false, + separator_after: true, + action: function (data) { + this.openModel(node.original._path); + } + } + }, getExportData: function (node, {dataType="", identifier=""}={}) { if(node.original.text.endsWith('.zip')) { return this.exportToZipFile(node.original._path); @@ -441,18 +453,47 @@ module.exports = View.extend({ } }); }, - getFileUploadContext: function (node, inProject) { + getFileUploadContext: function (node, inProject, {label="File"}={}) { let dirname = node.original._path === "/" ? "" : node.original._path; return { - label: "File", + label: label, _disabled: false, - separator_before: false, + separator_before: label !== "File", separator_after: false, action: (data) => { this.uploadFile(node, dirname, "file", inProject); } } }, + getFullNewWorkflowContext: function (node) { + return { + label: "New Workflow", + _disabled: false, + separator_before: false, + separator_after: false, + submenu: { + ensembleSimulation: { + label: "Ensemble Simulation", + _disabled: false, + separator_before: false, + separator_after: false, + action: (data) => { + this.newWorkflow(node, "Ensemble Simulation"); + } + }, + parameterSweep: { + label: "Parameter Sweep", + _disabled: false, + separator_before: false, + separator_after: false, + action: (data) => { + this.newWorkflow(node, "Parameter Sweep"); + } + }, + jupyterNotebook: this.getNotebookNewWorkflowContext(node) + } + } + }, getFullUploadContext: function (node, inProject) { let dirname = node.original._path === "/" ? "" : node.original._path; let file = this.getFileUploadContext(node, inProject); @@ -484,6 +525,34 @@ module.exports = View.extend({ } } }, + getMdlConvertContext: function (node) { + return { + label: "Convert", + _disabled: false, + separator_before: false, + separator_after: false, + submenu: { + toSpatial: { + label: "To Spatial Model", + _disabled: false, + separator_before: false, + separator_after: false, + action: (data) => { + this.config.toSpatial(this, node); + } + }, + toSBML: { + label: "To SBML Model", + _disabled: false, + separator_before: false, + separator_after: false, + action: (data) => { + this.config.toSBML(this, node); + } + } + } + } + }, getMoveToTrashContext: function (node, type) { return { label: "Move To Trash", @@ -547,6 +616,18 @@ module.exports = View.extend({ } } }, + getNotebookNewWorkflowContext: function (node) { + return { + label: "Jupyter Notebook", + _disabled: false, + separator_before: false, + separator_after: false, + action: (data) => { + let queryStr = `?path=${node.original._path}`; + window.location.href = path.join(app.getBasePath(), "stochss/workflow/selection") + queryStr; + } + } + }, getRefreshContext: function (node) { return { label: "Refresh", @@ -675,6 +756,29 @@ module.exports = View.extend({ }); }); }, + newWorkflow: function (node, type) { + let model = new Model({ + directory: node.original._path + }); + app.getXHR(model.url(), { + success: (err, response, body) => { + model.set(body); + model.updateValid(); + if(model.valid){ + app.newWorkflow(self, node.original._path, node.type === "spatial", type); + }else{ + let title = "Model Errors Detected"; + let endpoint = path.join(app.getBasePath(), "stochss/models/edit") + '?path=' + model.directory + '&validate'; + let message = 'Errors were detected in you model click here to fix your model'; + $(modals.modelErrorHtml(title, message)).modal(); + } + } + }); + }, + openModel: function (modelPath) { + let queryStr = `?path=${modelPath}`; + window.location.href = path.join(app.getBasePath(), "stochss/models/edit") + queryStr; + }, openProject: function (projectPath) { let queryStr = `?path=${projectPath}`; window.location.href = path.join(app.getBasePath(), "stochss/project/manager") + queryStr; @@ -769,7 +873,9 @@ module.exports = View.extend({ let contextMenus = { root: this.config.getRootContext, project: this.config.getProjectContext, - "workflow-group": this.config.getWorkflowGroupContext + "workflow-group": this.config.getWorkflowGroupContext, + folder: this.config.getFolderContext, + nonspatial: this.config.getModelContext } return contextMenus[node.type](this, node); } From 605b0fe8a5d1be93204363b295055ad2483c3577 Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Tue, 31 Aug 2021 10:11:28 -0400 Subject: [PATCH 124/186] Moved the model context menu from the file browser view. --- client/file-config.js | 4 +- client/modals.js | 6 ++- client/pages/file-browser.js | 6 +-- client/project-config.js | 65 ++++++++++++++++++++++++++----- client/views/file-browser-view.js | 62 ++++++++++++++--------------- client/views/jstree-view.js | 2 +- 6 files changed, 97 insertions(+), 48 deletions(-) diff --git a/client/file-config.js b/client/file-config.js index eaf5b783fa..39c27c90f0 100644 --- a/client/file-config.js +++ b/client/file-config.js @@ -85,7 +85,7 @@ let getModelContext = (view, node) => { if(node.original._path.split("/")[0] === "trash") { // item in trash return {delete: view.getDeleteContext(node, "model")}; } - let downloadOptions = {dataType: "json", identifier: "file/json-data"} + let downloadOptions = {dataType: "json", identifier: "file/json-data"}; return { edit: view.getEditModelContext(node), newWorkflow: view.getFullNewWorkflowContext(node), @@ -230,7 +230,7 @@ let validateMove = (view, node, more, pos) => { if(!more.ref.state.loaded) { return false }; try{ let BreakException = {}; - more.ref.children.forEach(function (child) { + more.ref.children.forEach((child) => { let child_node = $('#files-jstree').jstree().get_node(child); let exists = child_node.text === node.text; if(exists) { throw BreakException; }; diff --git a/client/modals.js b/client/modals.js index bef1cf743a..edc131e7b6 100644 --- a/client/modals.js +++ b/client/modals.js @@ -322,9 +322,11 @@ module.exports = { } return templates.confirmation(modalID, title); }, - successHtml : (message) => { + successHtml : (message, {title=null}={}) => { let modalID = "successModal"; - let title = "Success!"; + if(!title) { + title = "Success!"; + } return templates.message(modalID, title, message); }, diff --git a/client/pages/file-browser.js b/client/pages/file-browser.js index 529fbc548e..e313c8558c 100644 --- a/client/pages/file-browser.js +++ b/client/pages/file-browser.js @@ -1440,9 +1440,9 @@ let FileBrowser = PageView.extend({ if (o.type === 'spatial') { // spatial model node return $.extend(model, spatialConvert, common) } - if (o.type === 'nonspatial') { // model node - return $.extend(model, modelConvert, common) - } + // if (o.type === 'nonspatial') { // model node + // return $.extend(model, modelConvert, common) + // } // if (o.type === 'project'){ // return $.extend(open, project, common) // } diff --git a/client/project-config.js b/client/project-config.js index 88840a2d0b..d2e113b9f5 100644 --- a/client/project-config.js +++ b/client/project-config.js @@ -20,13 +20,14 @@ let $ = require('jquery'); let path = require('path'); //support files let app = require('./app'); +let modals = require('./modals'); -let contextZipTypes = ["workflow", "folder", "other", "root", "workflow-group"]; +let contextZipTypes = ["workflow", "folder", "other", "root", "workflowGroup"]; let doubleClick = (view, e) => { let node = $('#files-jstree').jstree().get_node(e.target); if(!node.original._path.includes(".proj/trash/")){ - if((node.type === "folder" || node.type === "workflow-group") && $('#files-jstree').jstree().is_open(node) && $('#files-jstree').jstree().is_loaded(node)){ + if((node.type === "folder" || node.type === "workflowGroup") && $('#files-jstree').jstree().is_open(node) && $('#files-jstree').jstree().is_loaded(node)){ view.refreshJSTree(node); }else if(node.type === "nonspatial" || node.type === "spatial"){ view.openModel(node.original._path); @@ -47,6 +48,34 @@ let doubleClick = (view, e) => { } } +let extractModel = (view, node) => { + let projectPar = path.dirname(view.root) === '.' ? "" : path.dirname(view.root); + let dstPath = path.join(projectPar, node.original._path.split('/').pop()); + let queryStr = `?srcPath=${node.original._path}&dstPath=${dstPath}`; + let endpoint = path.join(app.getApiPath(), "project/extract-model") + queryStr; + app.getXHR(endpoint, { + success: (err, response, body) => { + let title = "Successfully Exported the Model"; + let successModel = $(modals.successHtml(body, {title: title})).modal(); + }, + error: (err, response, body) => { + view.reportError(JSON.parse(body)); + } + }); +} + +let getExtractContext = (view, node) => { + return { + label: "Extract", + _disabled: false, + separator_before: false, + separator_after: false, + action: (data) => { + extractModel(view, node); + } + } +} + let getFolderContext = (view, node) => { if(node.text === "trash"){ // Trash node return {refresh: view.getRefreshContext(node)}; @@ -68,6 +97,23 @@ let getFolderContext = (view, node) => { } } +let getModelContext = (view, node) => { + if(node.original._path.includes(".proj/trash/")) { //item in trash + return {delete: view.getDeleteContext(node, "model")}; + } + let downloadOptions = {dataType: "json", identifier: "file/json-data"}; + return { + edit: view.getEditModelContext(node), + extract: getExtractContext(view, node), + newWorkflow: view.getFullNewWorkflowContext(node), + convert: view.getMdlConvertContext(node), + download: view.getDownloadContext(node, downloadOptions), + rename: view.getRenameContext(node), + duplicate: view.getDuplicateContext(node, "file/duplicate"), + moveToTrash: view.getMoveToTrashContext(node) + } +} + let getRootContext = (view, node) => { return { refresh: view.getRefreshContext(node), @@ -122,7 +168,7 @@ let types = { 'folder' : {"icon": "jstree-icon jstree-folder"}, 'spatial' : {"icon": "jstree-icon jstree-file"}, 'nonspatial' : {"icon": "jstree-icon jstree-file"}, - 'workflow-group' : {"icon": "jstree-icon jstree-folder"}, + 'workflowGroup' : {"icon": "jstree-icon jstree-folder"}, 'workflow' : {"icon": "jstree-icon jstree-file"}, 'notebook' : {"icon": "jstree-icon jstree-file"}, 'domain' : {"icon": "jstree-icon jstree-file"}, @@ -137,8 +183,8 @@ let updateParent = (view, type) => { view.parent.update("Model", "file-browser"); }else if(workflows.includes(type)) { view.parent.update("Workflow", "file-browser"); - }else if(type === "workflow-group") { - view.parent.update("WorkflowGroup", "file-browser"); + }else if(type === "workflowGroup") { + view.parent.update("Workflow-group", "file-browser"); }else if(type === "Archive") { view.parent.update(type, "file-browser"); } @@ -156,7 +202,7 @@ let validateMove = (view, node, more, pos) => { if(isWorkflow && node.original._status && node.original._status === "running") { return false }; // Check if model, workflow, or workflow group is moving to or from trash - let isWkgp = Boolean(validSrc && node.type === "workflow-group"); + let isWkgp = Boolean(validSrc && node.type === "workflowGroup"); let trashAction = Boolean((validSrc && node.original._path.includes("trash")) || (validDst && more.ref.original.text === "trash")); if(isWkgp && !(view.parent.model.newFormat && trashAction)) { return false }; let isModel = Boolean(validSrc && (node.type === "nonspatial" || node.type === "spatial")); @@ -165,12 +211,12 @@ let validateMove = (view, node, more, pos) => { // Check if model, workflow, or workflow group is moving from trash to the correct location if(validSrc && node.original._path.includes("trash")) { if(isWkgp && (!view.parent.model.newFormat || (validDst && more.ref.type !== "root"))) { return false }; - if(isWorkflow && validDst && more.ref.type !== "workflow-group") { return false }; + if(isWorkflow && validDst && more.ref.type !== "workflowGroup") { return false }; if(isModel && validDst) { if(!view.parent.model.newFormat && more.ref.type !== "root") { return false }; let length = node.original.text.split(".").length; let modelName = node.original.text.split(".").slice(0, length - 1).join("."); - if(view.parent.model.newFormat && (more.ref.type !== "workflow-group" || !more.ref.original.text.startsWith(modelName))) { return false }; + if(view.parent.model.newFormat && (more.ref.type !== "workflowGroup" || !more.ref.original.text.startsWith(modelName))) { return false }; } } @@ -179,7 +225,7 @@ let validateMove = (view, node, more, pos) => { let isNotebook = Boolean(validSrc && node.type === "notebook"); let isOther = Boolean(validSrc && !isModel && !isWorkflow && !isWkgp && !isNotebook); if(isOther && validDst && !validDsts.includes(more.ref.type)) { return false }; - validDsts.push("workflow-group"); + validDsts.push("workflowGroup"); if(isNotebook && validDst && !validDsts.includes(more.ref.type)) { return false }; // Check if file already exists with that name in folder @@ -210,6 +256,7 @@ module.exports = { contextZipTypes: contextZipTypes, doubleClick: doubleClick, getFolderContext: getFolderContext, + getModelContext: getModelContext, // getProjectContext: getOtherContext, getRootContext: getRootContext, getWorkflowGroupContext: getWorkflowGroupContext, diff --git a/client/views/file-browser-view.js b/client/views/file-browser-view.js index 50f4d3009c..803f657839 100644 --- a/client/views/file-browser-view.js +++ b/client/views/file-browser-view.js @@ -1224,34 +1224,34 @@ let Model = require('../models/model'); // } // } // common to all models - let commonModel = { - "Edit" : { - "label" : "Edit", - "_disabled" : false, - "_class" : "font-weight-bolder", - "separator_before" : false, - "separator_after" : true, - "action" : function (data) { - window.location.href = path.join(app.getBasePath(), "stochss/models/edit")+"?path="+o.original._path; - } - }, - "Extract" : { - "label" : "Extract", - "_disabled" : false, - "separator_before" : false, - "separator_after" : false, - "action" : function (data) { - self.handleExtractModelClick(o); - } - }, - "New Workflow" : { - "label" : "New Workflow", - "_disabled" : false, - "separator_before" : false, - "separator_after" : false, - "submenu" : o.type === "nonspatial" ? newWorkflow : {"jupyterNotebook":newWorkflow.jupyterNotebook} - } - } + // let commonModel = { + // "Edit" : { + // "label" : "Edit", + // "_disabled" : false, + // "_class" : "font-weight-bolder", + // "separator_before" : false, + // "separator_after" : true, + // "action" : function (data) { + // window.location.href = path.join(app.getBasePath(), "stochss/models/edit")+"?path="+o.original._path; + // } + // }, + // "Extract" : { + // "label" : "Extract", + // "_disabled" : false, + // "separator_before" : false, + // "separator_after" : false, + // "action" : function (data) { + // self.handleExtractModelClick(o); + // } + // }, + // "New Workflow" : { + // "label" : "New Workflow", + // "_disabled" : false, + // "separator_before" : false, + // "separator_after" : false, + // "submenu" : o.type === "nonspatial" ? newWorkflow : {"jupyterNotebook":newWorkflow.jupyterNotebook} + // } + // } // convert options for non-spatial models // let modelConvert = { // "Convert" : { @@ -1445,9 +1445,9 @@ let Model = require('../models/model'); if (o.type === 'spatial') { // spatial model node return $.extend(commonModel, spatialConvert, download, common) } - if (o.type === 'nonspatial') { // model node - return $.extend(commonModel, modelConvert, download, common) - } + // if (o.type === 'nonspatial') { // model node + // return $.extend(commonModel, modelConvert, download, common) + // } // if (o.type === 'workflow-group') { // return $.extend(refresh, downloadWCombine) // } diff --git a/client/views/jstree-view.js b/client/views/jstree-view.js index 8361ef24e4..d0516dd57e 100644 --- a/client/views/jstree-view.js +++ b/client/views/jstree-view.js @@ -873,7 +873,7 @@ module.exports = View.extend({ let contextMenus = { root: this.config.getRootContext, project: this.config.getProjectContext, - "workflow-group": this.config.getWorkflowGroupContext, + workflowGroup: this.config.getWorkflowGroupContext, folder: this.config.getFolderContext, nonspatial: this.config.getModelContext } From 5d1933f835e10cea3f0b559a48bcc656484ff799 Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Tue, 31 Aug 2021 12:00:39 -0400 Subject: [PATCH 125/186] Moved spatial model context menu. --- client/file-config.js | 37 ++++++- client/modals.js | 26 ++--- client/pages/file-browser.js | 116 ++++++++++----------- client/project-config.js | 72 ++++++++++++- client/views/file-browser-view.js | 168 +++++++++++++++--------------- client/views/jstree-view.js | 34 ++++-- 6 files changed, 287 insertions(+), 166 deletions(-) diff --git a/client/file-config.js b/client/file-config.js index 39c27c90f0..ee9063cc2b 100644 --- a/client/file-config.js +++ b/client/file-config.js @@ -144,6 +144,22 @@ let getRootContext = (view, node) => { } } +let getSpatialModelContext = (view, node) => { + if(node.original._path.split("/")[0] === "trash") { // project in trash + return {delete: view.getDeleteContext(node, "spatial model")}; + } + let downloadOptions = {dataType: "json", identifier: "file/json-data"}; + return { + edit: view.getEditModelContext(node), + newWorkflow: view.getFullNewWorkflowContext(node), + convert: view.getSmdlConvertContext(node, "spatial/to-model"), + download: view.getDownloadContext(node, downloadOptions), + rename: view.getRenameContext(node), + duplicate: view.getDuplicateContext(node, "file/duplicate"), + moveToTrash: view.getMoveToTrashContext(node) + } +} + let move = (view, par, node) => { let newDir = par.original._path !== "/" ? par.original._path : ""; let file = node.original._path.split('/').pop(); @@ -174,6 +190,24 @@ let setup = (view) => { $(view.queryByHook("fb-import-model")).css("display", "none"); } +let toModel = (view, node, identifier) => { + let queryStr = `?path=${node.original._path}`; + let endpoint = path.join(app.getApiPath(), identifier) + queryStr; + app.getXHR(endpoint, { + success: function (err, response, body) { + let par = $('#models-jstree').jstree().get_node(node.parent); + view.refreshJSTree(par); + view.selectNode(par, body.File); + if(identifier.startsWith("sbml") && body.errors.length > 0){ + if(document.querySelector('#sbmlToModelModal')) { + document.querySelector('#sbmlToModelModal').remove(); + } + let modal = $(modals.sbmlToModelHtml(body.message, body.errors)).modal(); + } + } + }); +} + let toSBML = (view, node) => { let queryStr = `?path=${node.original._path}`; let endpoint = path.join(app.getApiPath(), "model/to-sbml") + queryStr; @@ -208,7 +242,7 @@ let types = { 'notebook' : {"icon": "jstree-icon jstree-file"}, 'domain' : {"icon": "jstree-icon jstree-file"}, 'sbml-model' : {"icon": "jstree-icon jstree-file"}, - 'other' : {"icon": "jstree-icon jstree-file"}, + 'other' : {"icon": "jstree-icon jstree-file"} } let updateParent = (view, type) => {} @@ -250,6 +284,7 @@ module.exports = { getModelContext: getModelContext, getProjectContext: getProjectContext, getRootContext: getRootContext, + getSpatialModelContext: getSpatialModelContext, // getWorflowGroupContext: getOtherContext, move: move, setup: setup, diff --git a/client/modals.js b/client/modals.js index edc131e7b6..00ecbc642a 100644 --- a/client/modals.js +++ b/client/modals.js @@ -322,6 +322,19 @@ module.exports = { } return templates.confirmation(modalID, title); }, + sbmlToModelHtml : (title, errors) => { + let modalID = "sbmlToModelModal"; + for(var i = 0; i < errors.length; i++) { + if(errors[i].startsWith("SBML Error") || errors[i].startsWith("Error")){ + errors[i] = "Error: " + errors[i]; + }else{ + errors[i] = "Warning: " + errors[i]; + } + } + let message = errors.join("
"); + + return templates.message(modalID, title, message); + }, successHtml : (message, {title=null}={}) => { let modalID = "successModal"; if(!title) { @@ -444,19 +457,6 @@ module.exports = { return templates.input(modalID, inputID, title, label, value) }, - sbmlToModelHtml : (title, errors) => { - let modalID = "sbmlToModelModal" - for(var i = 0; i < errors.length; i++) { - if(errors[i].startsWith("SBML Error") || errors[i].startsWith("Error")){ - errors[i] = "Error: " + errors[i] - }else{ - errors[i] = "Warning: " + errors[i] - } - } - let message = errors.join("
") - - return templates.message(modalID, title, message) - }, duplicateWorkflowHtml : (wkflFile, body) => { let modalID = "duplicateWorkflowModal" let title = `Model for ${wkflFile}` diff --git a/client/pages/file-browser.js b/client/pages/file-browser.js index e313c8558c..3c491e0cfe 100644 --- a/client/pages/file-browser.js +++ b/client/pages/file-browser.js @@ -475,33 +475,33 @@ let FileBrowser = PageView.extend({ // } // }); // }, - toModel: function (o, from) { - var self = this; - var parentID = o.parent; - if(from === "Spatial"){ - var identifier = "spatial/to-model" - }else{ - var identifier = "sbml/to-model" - } - let endpoint = path.join(app.getApiPath(), identifier)+"?path="+o.original._path; - app.getXHR(endpoint, { - success: function (err, response, body) { - var node = $('#models-jstree').jstree().get_node(parentID); - if(node.type === "root"){ - self.refreshJSTree(); - }else{ - $('#models-jstree').jstree().refresh_node(node); - } - self.selectNode(node, body.File); - if(from === "SBML" && body.errors.length > 0){ - var title = ""; - var msg = body.message; - var errors = body.errors; - let modal = $(modals.sbmlToModelHtml(msg, errors)).modal(); - } - } - }); - }, + // toModel: function (o, from) { + // var self = this; + // var parentID = o.parent; + // if(from === "Spatial"){ + // var identifier = "spatial/to-model" + // }else{ + // var identifier = "sbml/to-model" + // } + // let endpoint = path.join(app.getApiPath(), identifier)+"?path="+o.original._path; + // app.getXHR(endpoint, { + // success: function (err, response, body) { + // var node = $('#models-jstree').jstree().get_node(parentID); + // if(node.type === "root"){ + // self.refreshJSTree(); + // }else{ + // $('#models-jstree').jstree().refresh_node(node); + // } + // self.selectNode(node, body.File); + // if(from === "SBML" && body.errors.length > 0){ + // var title = ""; + // var msg = body.message; + // var errors = body.errors; + // let modal = $(modals.sbmlToModelHtml(msg, errors)).modal(); + // } + // } + // }); + // }, toNotebook: function (o, type) { let self = this var endpoint = "" @@ -1220,34 +1220,34 @@ let FileBrowser = PageView.extend({ // } // } // convert options for spatial models - let spatialConvert = { - "Convert" : { - "label" : "Convert", - "_disabled" : false, - "separator_before" : false, - "separator_after" : false, - "submenu" : { - "Convert to Model" : { - "label" : "Convert to Model", - "_disabled" : false, - "separator_before" : false, - "separator_after" : false, - "action" : function (data) { - self.toModel(o, "Spatial"); - } - }, - "Convert to Notebook" : { - "label" : "Convert to Notebook", - "_disabled" : false, - "separator_before" : false, - "separator_after" : false, - "action" : function (data) { - self.toNotebook(o, "model") - } - } - } - } - } + // let spatialConvert = { + // "Convert" : { + // "label" : "Convert", + // "_disabled" : false, + // "separator_before" : false, + // "separator_after" : false, + // "submenu" : { + // "Convert to Model" : { + // "label" : "Convert to Model", + // "_disabled" : false, + // "separator_before" : false, + // "separator_after" : false, + // "action" : function (data) { + // self.toModel(o, "Spatial"); + // } + // }, + // "Convert to Notebook" : { + // "label" : "Convert to Notebook", + // "_disabled" : false, + // "separator_before" : false, + // "separator_after" : false, + // "action" : function (data) { + // self.toNotebook(o, "model") + // } + // } + // } + // } + // } // convert options for non-spatial models // let modelConvert = { // "Convert" : { @@ -1431,9 +1431,9 @@ let FileBrowser = PageView.extend({ // if (o.text === "trash") {//Trash node // return {"Refresh": folder.Refresh} // } - if (o.original._path.split("/")[0] === "trash") { // item in trash - return delete_node - } + // if (o.original._path.split("/")[0] === "trash") { // item in trash + // return delete_node + // } // if (o.type === 'folder') { // folder node // return $.extend(folder, common) // } diff --git a/client/project-config.js b/client/project-config.js index d2e113b9f5..2ddf2d1cc1 100644 --- a/client/project-config.js +++ b/client/project-config.js @@ -110,7 +110,7 @@ let getModelContext = (view, node) => { download: view.getDownloadContext(node, downloadOptions), rename: view.getRenameContext(node), duplicate: view.getDuplicateContext(node, "file/duplicate"), - moveToTrash: view.getMoveToTrashContext(node) + delete: view.getDeleteContext(node, "model") } } @@ -136,6 +136,23 @@ let getWorkflowGroupContext = (view, node) => { } } +let getSpatialModelContext = (view, node) => { + if(node.original._path.includes(".proj/trash/")) { //item in trash + return {delete: view.getDeleteContext(node, "spatial model")}; + } + let downloadOptions = {dataType: "json", identifier: "file/json-data"}; + return { + edit: view.getEditModelContext(node), + extract: getExtractContext(view, node), + newWorkflow: view.getFullNewWorkflowContext(node), + convert: view.getSmdlConvertContext(node, "spatial/to-model"), + download: view.getDownloadContext(node, downloadOptions), + rename: view.getRenameContext(node), + duplicate: view.getDuplicateContext(node, "file/duplicate"), + delete: view.getDeleteContext(node, "model") + } +} + let move = (view, par, node) => { let newDir = par.original._path !== "/" ? par.original._path : ""; let file = node.original._path.split('/').pop(); @@ -163,6 +180,56 @@ let setup = (view) => { $(view.queryByHook("fb-empty-trash")).css("display", "none"); } +let toModel = (view, node, identifier) => { + let queryStr = `?path=${node.original._path}`; + let endpoint = path.join(app.getApiPath(), identifier) + queryStr; + app.getXHR(endpoint, { + success: (err, response, body) => { + var root = $('#files-jstree').jstree().get_node(node.parent); + while(root.type !== "root") { + root = $('#files-jstree').jstree().get_node(root.parent); + } + view.refreshJSTree(root); + view.selectNode(root, body.File.replace(".mdl", ".wkgp")); + if(identifier.startsWith("sbml") && body.errors.length > 0){ + if(document.querySelector('#sbmlToModelModal')) { + document.querySelector('#sbmlToModelModal').remove(); + } + let modal = $(modals.sbmlToModelHtml(body.message, body.errors)).modal(); + }else{ + view.updateParent("model"); + } + } + }); +} + +let toSBML = (view, node) => { + let queryStr = `?path=${node.original._path}`; + let endpoint = path.join(app.getApiPath(), "model/to-sbml") + queryStr; + app.getXHR(endpoint, { + success: (err, response, body) => { + let par = $('#files-jstree').jstree().get_node(node.parent); + let grandPar = $('#files-jstree').jstree().get_node(par.parent); + view.refreshJSTree(grandPar); + view.selectNode(grandPar, body.File); + } + }); +} + +let toSpatial = (view, node) => { + let queryStr = `?path=${node.original._path}`; + let endpoint = path.join(app.getApiPath(), "model/to-spatial") + queryStr; + app.getXHR(endpoint, { + success: (err, response, body) => { + let par = $('#files-jstree').jstree().get_node(node.parent); + let grandPar = $('#files-jstree').jstree().get_node(par.parent); + view.refreshJSTree(grandPar); + updateParent("spatial"); + view.selectNode(grandPar, body.File.replace(".smdl", ".wkgp")); + } + }); +} + let types = { 'root' : {"icon": "jstree-icon jstree-folder"}, 'folder' : {"icon": "jstree-icon jstree-folder"}, @@ -173,7 +240,7 @@ let types = { 'notebook' : {"icon": "jstree-icon jstree-file"}, 'domain' : {"icon": "jstree-icon jstree-file"}, 'sbml-model' : {"icon": "jstree-icon jstree-file"}, - 'other' : {"icon": "jstree-icon jstree-file"}, + 'other' : {"icon": "jstree-icon jstree-file"} } let updateParent = (view, type) => { @@ -259,6 +326,7 @@ module.exports = { getModelContext: getModelContext, // getProjectContext: getOtherContext, getRootContext: getRootContext, + getSpatialModelContext: getSpatialModelContext, getWorkflowGroupContext: getWorkflowGroupContext, move: move, setup: setup, diff --git a/client/views/file-browser-view.js b/client/views/file-browser-view.js index 803f657839..5b07bb4a4b 100644 --- a/client/views/file-browser-view.js +++ b/client/views/file-browser-view.js @@ -457,44 +457,44 @@ let Model = require('../models/model'); } return "_" + month + day + year + "_" + hours + minutes + seconds; }, - toSpatial: function (o) { - var self = this; - var parentID = o.parent; - var endpoint = path.join(app.getApiPath(), "model/to-spatial")+"?path="+o.original._path; - app.getXHR(endpoint, { - success: function (err, response, body) { - var node = $('#models-jstree-view').jstree().get_node(parentID); - self.refreshJSTree() - self.updateParent("spatial") - self.selectNode(node, body.File) - } - }); - }, - toModel: function (o, from) { - var self = this; - var parentID = o.parent; - if(from === "Spatial"){ - var identifier = "spatial/to-model" - }else{ - var identifier = "sbml/to-model" - } - let endpoint = path.join(app.getApiPath(), identifier)+"?path="+o.original._path; - app.getXHR(endpoint, { - success: function (err, response, body) { - var node = $('#models-jstree-view').jstree().get_node(parentID); - self.refreshJSTree() - self.selectNode(node, body.File) - if(from === "SBML" && body.errors.length > 0){ - var title = "" - var msg = body.message - var errors = body.errors - let modal = $(modals.sbmlToModelHtml(msg, errors)).modal(); - }else{ - self.updateParent("nonspatial") - } - } - }); - }, + // toSpatial: function (o) { + // var self = this; + // var parentID = o.parent; + // var endpoint = path.join(app.getApiPath(), "model/to-spatial")+"?path="+o.original._path; + // app.getXHR(endpoint, { + // success: function (err, response, body) { + // var node = $('#models-jstree-view').jstree().get_node(parentID); + // self.refreshJSTree() + // self.updateParent("spatial") + // self.selectNode(node, body.File) + // } + // }); + // }, + // toModel: function (o, from) { + // var self = this; + // var parentID = o.parent; + // if(from === "Spatial"){ + // var identifier = "spatial/to-model" + // }else{ + // var identifier = "sbml/to-model" + // } + // let endpoint = path.join(app.getApiPath(), identifier)+"?path="+o.original._path; + // app.getXHR(endpoint, { + // success: function (err, response, body) { + // var node = $('#models-jstree-view').jstree().get_node(parentID); + // self.refreshJSTree() + // self.selectNode(node, body.File) + // if(from === "SBML" && body.errors.length > 0){ + // var title = "" + // var msg = body.message + // var errors = body.errors + // let modal = $(modals.sbmlToModelHtml(msg, errors)).modal(); + // }else{ + // self.updateParent("nonspatial") + // } + // } + // }); + // }, toNotebook: function (o, type) { let self = this var endpoint = path.join(app.getApiPath(), "workflow/notebook")+"?type=none&path="+o.original._path @@ -512,18 +512,18 @@ let Model = require('../models/model'); } }); }, - toSBML: function (o) { - var self = this; - var parentID = o.parent; - var endpoint = path.join(app.getApiPath(), "model/to-sbml")+"?path="+o.original._path; - app.getXHR(endpoint, { - success: function (err, response, body) { - var node = $('#models-jstree-view').jstree().get_node(parentID); - self.refreshJSTree() - self.selectNode(node, body.File) - } - }); - }, + // toSBML: function (o) { + // var self = this; + // var parentID = o.parent; + // var endpoint = path.join(app.getApiPath(), "model/to-sbml")+"?path="+o.original._path; + // app.getXHR(endpoint, { + // success: function (err, response, body) { + // var node = $('#models-jstree-view').jstree().get_node(parentID); + // self.refreshJSTree() + // self.selectNode(node, body.File) + // } + // }); + // }, // renameNode: function (o) { // var self = this // var text = o.text; @@ -892,21 +892,21 @@ let Model = require('../models/model'); // let queryStr = "?domainPath=" + this.parent.model.directory + "&new" // window.location.href = path.join(app.getBasePath(), "stochss/domain/edit") + queryStr // }, - handleExtractModelClick: function (o) { - let self = this - let projectParent = path.dirname(this.parent.model.directory) === '.' ? "" : path.dirname(this.parent.model.directory) - let queryString = "?srcPath="+o.original._path+"&dstPath="+path.join(projectParent, o.original._path.split('/').pop()) - let endpoint = path.join(app.getApiPath(), "project/extract-model")+queryString - app.getXHR(endpoint, { - success: function (err, response, body) { - let successModel = $(modals.projectExportSuccessHtml("Model", body)).modal(); - }, - error: function (err, response, body) { - body = JSON.parse(body); - let successModel = $(modals.projectExportErrorHtml(body.Reason, body.message)).modal(); - } - }); - }, + // handleExtractModelClick: function (o) { + // let self = this + // let projectParent = path.dirname(this.parent.model.directory) === '.' ? "" : path.dirname(this.parent.model.directory) + // let queryString = "?srcPath="+o.original._path+"&dstPath="+path.join(projectParent, o.original._path.split('/').pop()) + // let endpoint = path.join(app.getApiPath(), "project/extract-model")+queryString + // app.getXHR(endpoint, { + // success: function (err, response, body) { + // let successModel = $(modals.projectExportSuccessHtml("Model", body)).modal(); + // }, + // error: function (err, response, body) { + // body = JSON.parse(body); + // let successModel = $(modals.projectExportErrorHtml(body.Reason, body.message)).modal(); + // } + // }); + // }, handleExportWorkflowClick: function (o) { let self = this let projectParent = path.dirname(this.parent.model.directory) === '.' ? "" : path.dirname(this.parent.model.directory) @@ -1282,25 +1282,25 @@ let Model = require('../models/model'); // } // } // convert options for spatial models - let spatialConvert = { - "Convert" : { - "label" : "Convert", - "_disabled" : false, - "separator_before" : false, - "separator_after" : true, - "submenu" : { - "Convert to Model" : { - "label" : "Convert to Model", - "_disabled" : false, - "separator_before" : false, - "separator_after" : false, - "action" : function (data) { - self.toModel(o, "Spatial"); - } - } - } - } - } + // let spatialConvert = { + // "Convert" : { + // "label" : "Convert", + // "_disabled" : false, + // "separator_before" : false, + // "separator_after" : true, + // "submenu" : { + // "Convert to Model" : { + // "label" : "Convert to Model", + // "_disabled" : false, + // "separator_before" : false, + // "separator_after" : false, + // "action" : function (data) { + // self.toModel(o, "Spatial"); + // } + // } + // } + // } + // } // specific to workflows let workflow = { "Start/Restart Workflow" : { diff --git a/client/views/jstree-view.js b/client/views/jstree-view.js index d0516dd57e..66f04cd1c3 100644 --- a/client/views/jstree-view.js +++ b/client/views/jstree-view.js @@ -242,13 +242,11 @@ module.exports = View.extend({ let par = $('#files-jstree').jstree().get_node(node.parent); this.refreshJSTree(par); this.config.updateParent(node.type); - if(par.type === "root"){ - let actionsBtn = $(this.queryByHook("options-for-node")); - if(actionsBtn.text().endsWith(node.text)) { - actionsBtn.text("Actions"); - actionsBtn.prop("disabled", true); - this.nodeForContextMenu = ""; - } + let actionsBtn = $(this.queryByHook("options-for-node")); + if(actionsBtn.text().endsWith(node.text)) { + actionsBtn.text("Actions"); + actionsBtn.prop("disabled", true); + this.nodeForContextMenu = ""; } }, error: (err, response, body) => { @@ -652,6 +650,25 @@ module.exports = View.extend({ } } }, + getSmdlConvertContext: function (node, identifier) { + return { + label: "Convert", + _disabled: false, + separator_before: false, + separator_after: true, + submenu: { + convertToModel: { + label: "To Model", + _disabled: false, + separator_before: false, + separator_after: false, + action: function (data) { + this.config.toModel(this, node, identifier); + } + } + } + } + }, handleCreateDirectoryClick: function (e) { let dirname = this.root === "none" ? "" : this.root; this.createDirectory(null, dirname); @@ -875,7 +892,8 @@ module.exports = View.extend({ project: this.config.getProjectContext, workflowGroup: this.config.getWorkflowGroupContext, folder: this.config.getFolderContext, - nonspatial: this.config.getModelContext + nonspatial: this.config.getModelContext, + spatial: this.config.getSpatialModelContext } return contextMenus[node.type](this, node); } From 127a7b011ef14cb470055129320864fe99051c5a Mon Sep 17 00:00:00 2001 From: Matthew Geiger Date: Tue, 31 Aug 2021 15:21:00 -0400 Subject: [PATCH 126/186] Fix cpu limitation logic so a small-core cpu can run a hub without failing; remove reliance on .power_users file and move logic for power user determination to the userlist file --- Makefile | 24 +++++++------ jupyterhub/jupyterhub_config.py | 60 +++++++++++++++------------------ 2 files changed, 42 insertions(+), 42 deletions(-) diff --git a/Makefile b/Makefile index 5fff122244..91dbb792a7 100644 --- a/Makefile +++ b/Makefile @@ -65,15 +65,19 @@ jupyterhub/secrets/postgres.env: jupyterhub/userlist: @echo "You're missing a userlist file. We'll make a blank one for you." @echo "If you'd like to set admins to jupyterhub, add entries to the userlist file" - @echo "in the jupyterhub/ directory like this (one user per line):" - @echo "myuser@uni.edu admin" + @echo " in the jupyterhub/ directory like this (one user per line):" + @echo " myuser@uni.edu admin" + @echo "If you'd like to set power users who are exempt from resource constraints" + @echo " use entries like this:" + @echo " myuser@uni.edu power" + @echo "" @touch $@ check-files: jupyterhub/userlist jupyterhub/secrets/.oauth.dummy.env jupyterhub/secrets/postgres.env check_files_staging: check-files jupyterhub/secrets/.oauth.staging.env -check_files_prod: check-files jupyterhub/secrets/.oauth.prod.env +check_files_prod: check-files jupyterhub/secrets/.oauth.prod.env jupyterhub/.power_users cert: @echo "Generating certificate..." @@ -126,17 +130,17 @@ hub: build_hub build run_hub_dev build_clean: docker build \ --build-arg JUPYTER_CONFIG_DIR=$(JUPYTER_CONFIG_DIR) \ - --no-cache -t $(DOCKER_STOCHSS_IMAGE):latest . + --no-cache -t $(DOCKER_STOCHSS_IMAGE):latest . create_working_dir: $(DOCKER_SETUP_COMMAND) -build: +build: docker build \ --build-arg JUPYTER_CONFIG_DIR=$(JUPYTER_CONFIG_DIR) \ - -t $(DOCKER_STOCHSS_IMAGE):latest . + -t $(DOCKER_STOCHSS_IMAGE):latest . -test: create_working_dir +test: create_working_dir docker run --rm \ --name $(DOCKER_STOCHSS_IMAGE) \ --env-file .env \ @@ -144,11 +148,11 @@ test: create_working_dir -v $(DOCKER_WORKING_DIR):/home/jovyan/ \ -p 8888:8888 \ $(DOCKER_STOCHSS_IMAGE):latest \ - /stochss/stochss/tests/run_tests.py + /stochss/stochss/tests/run_tests.py build_and_test: build test -run: create_working_dir +run: create_working_dir $(PYTHON_EXE) launch_webbrowser.py & docker run --rm \ --name $(DOCKER_STOCHSS_IMAGE) \ @@ -156,7 +160,7 @@ run: create_working_dir -v $(DOCKER_WORKING_DIR):/home/jovyan/ \ -p 8888:8888 \ $(DOCKER_STOCHSS_IMAGE):latest \ - bash -c "cd /home/jovyan; start-notebook.sh " + bash -c "cd /home/jovyan; start-notebook.sh " build_and_run: build run diff --git a/jupyterhub/jupyterhub_config.py b/jupyterhub/jupyterhub_config.py index d871a6280f..d73a7c3ead 100644 --- a/jupyterhub/jupyterhub_config.py +++ b/jupyterhub/jupyterhub_config.py @@ -199,23 +199,21 @@ def get_user_cpu_count_or_fail(): ''' - Get the user cpu count or raise error + Get the user cpu count or raise error. ''' - log = logging.getLogger() reserve_count = int(os.environ['RESERVED_CPUS']) - log.info(f"RESERVED_CPUS environment variable is set to {reserve_count}") # Round up to an even number of reserved cpus + total_cpus = os.cpu_count() + if total_cpus <= 2: + c.StochSS.reserved_cpu_count = 0 + return 0 if reserve_count % 2 > 0: message = "Increasing reserved cpu count by one so it's an even number." message += " This helps allocate logical cpus to users more easily." - log.warning(message) reserve_count += 1 - total_cpus = os.cpu_count() - log.info(f"Total cpu count as reported by os.count: {total_cpus}") if reserve_count >= total_cpus: e_message = "RESERVED_CPUS environment cannot be greater than or equal to the number of" e_message += " cpus returned by os.cpu_count()" - log.error(e_message) raise ValueError(e_message) user_cpu_count = total_cpus - reserve_count # If (num logical cpus) - (num reserved cpus) is odd, @@ -223,44 +221,36 @@ def get_user_cpu_count_or_fail(): if user_cpu_count % 2 > 0 and user_cpu_count > 1: user_cpu_count -= 1 c.StochSS.reserved_cpu_count = reserve_count - log.info(f'Using {user_cpu_count} logical cpus for user containers...') - log.info(f'Reserving {reserve_count} logical cpus for hub container and underlying OS') return user_cpu_count c.StochSS.user_cpu_count = get_user_cpu_count_or_fail() c.StochSS.user_cpu_alloc = [0] * c.StochSS.user_cpu_count -def get_power_users(): - ''' - Get the list of power users - ''' - power_users_file = os.environ.get('POWER_USERS_FILE') - log = logging.getLogger() - if not os.path.exists(power_users_file): - log.warning('No power users defined!') - return [] - with open(power_users_file) as file: - power_users = [ x.rstrip() for x in file.readlines() ] - return power_users - - -c.StochSS.power_users = get_power_users() - def pre_spawn_hook(spawner): ''' Function that runs before DockerSpawner spawns a user container. Limits the resources available to user containers, excluding a list of power users. ''' - log = logging.getLogger() + log = spawner.log # Remove the memory limit for power users + if c.StochSS.user_cpu_count == 0: + spawner.mem_limit = None + log.info(f'Skipping resource limitations since the host machine has a limited number of cpus.') + return + user_type = None if spawner.user.name in c.StochSS.power_users: + user_type = 'power' + if spawner.user.name in c.Authenticator.admin_users: + user_type = 'admin' + if user_type: spawner.mem_limit = None + log.info(f'Skipping resource limitation for {user_type} user: {spawner.user.name}') return palloc = c.StochSS.user_cpu_alloc div = len(palloc) // 2 reserved = c.StochSS.reserved_cpu_count - log.warning(f'Reserved CPUs: {reserved}') - log.warning(f'Number of user containers using each logical core: {palloc}') + log.debug(f'Reserved CPUs: {reserved}') + log.debug(f'Number of user containers using each logical core: {palloc}') # We want to allocate logical cores that are on the same physical core # whenever possible. # @@ -307,8 +297,10 @@ def post_stop_hook(spawner): ''' Post stop hook ''' - log = logging.getLogger() + log = spawner.log reserved = c.StochSS.reserved_cpu_count + if reserved == 0: + return palloc = c.StochSS.user_cpu_alloc try: cpu1_index, cpu2_index = spawner.extra_host_config['cpuset_cpus'].split(',') @@ -319,7 +311,6 @@ def post_stop_hook(spawner): except Exception as err: message = "Exception thrown due to cpuset_cpus not being set (power user)" log.error(f"{message}\n{err}") - # Exception thrown due to cpuset_cpus not being set (power user) pass c.Spawner.pre_spawn_hook = pre_spawn_hook @@ -439,6 +430,7 @@ def post_stop_hook(spawner): # # Defaults to an empty set, in which case no user has admin access. c.Authenticator.admin_users = admin = set([]) +c.StochSS.power_users = power_users = set([]) pwd = os.path.dirname(__file__) with open(os.path.join(pwd, 'userlist')) as f: @@ -450,5 +442,9 @@ def post_stop_hook(spawner): if len(parts) >= 1: name = parts[0] #whitelist.add(name) - if len(parts) > 1 and parts[1] == 'admin': - admin.add(name) + if len(parts) > 1: + if parts[1] == 'admin': + admin.add(name) + power_users.add(name) + if parts[1] == 'power': + power_users.add(name) From 09599f583dbc07ac764d9c557bb4d43f623f21cb Mon Sep 17 00:00:00 2001 From: Matthew Geiger Date: Tue, 31 Aug 2021 15:29:18 -0400 Subject: [PATCH 127/186] Update README on how to make power users --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8914e285de..758a0fdaf2 100644 --- a/README.md +++ b/README.md @@ -59,7 +59,7 @@ StochSS uses [JupyterHub](https://jupyterhub.readthedocs.io/en/stable/#) as the - [Optional] To set admins for JupyterHub, make a file called `userlist` in the `jupyterhub/` directory. On each line of this file place a username followed by the word 'admin'. For example: `myuser admin`. If using Google OAuth, the uesername will be a Gmail address. Navigate to `/hub/admin` to use the JupyterHub admin interface. -- [Optional] By default multi-user StochSS is set up to allocate 2 logical cpus per user, reserving 2 logical cpus for the hub container and underlying OS. You can define a list of "power users" that are excluded from resource limitations by adding a text file called `.power_users` (note the leading period) to the `jupyterhub/` directory with one username/email address on each line of the file. +- [Optional] By default multi-user StochSS is set up to allocate 2 logical cpus per user, reserving 2 logical cpus for the hub container and underlying OS. You can define "power users" that are excluded from resource limitations using the same method as above for adding an admin, but instead of following the username with 'admin', use the keyword 'power' instead. ### Run Locally From 4cf8c333867de5de671d4aab37505306fb40a6d5 Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Tue, 31 Aug 2021 16:43:49 -0400 Subject: [PATCH 128/186] Moved context menus for workflows and domains. --- client/file-config.js | 38 +++++- client/pages/file-browser.js | 186 +++++++++++++++--------------- client/project-config.js | 69 +++++++---- client/views/file-browser-view.js | 132 ++++++++++----------- client/views/jstree-view.js | 95 ++++++++++++++- 5 files changed, 336 insertions(+), 184 deletions(-) diff --git a/client/file-config.js b/client/file-config.js index ee9063cc2b..78f39d1639 100644 --- a/client/file-config.js +++ b/client/file-config.js @@ -37,11 +37,9 @@ let doubleClick = (view, e) => { }else if(node.type === "project"){ view.openProject(node.original._path); }else if(node.type === "workflow"){ - let queryStr = "?path=" + node.original._path + "&type=none"; - window.location.href = path.join(app.getBasePath(), "stochss/workflow/edit") + queryStr; + view.openWorkflow(node.original._path); }else if(node.type === "domain") { - let queryStr = "?domainPath=" + node.original._path; - window.location.href = path.join(app.getBasePath(), "stochss/domain/edit") + queryStr + view.openDomain(node.original._path); }else if(node.type === "other"){ var openPath = path.join(app.getBasePath(), "view", node.original._path); window.open(openPath, "_blank"); @@ -160,6 +158,37 @@ let getSpatialModelContext = (view, node) => { } } +let getWorkflowContext = (view, node) => { + if(node.original._path.split("/")[0] === "trash") { // project in trash + return {delete: view.getDeleteContext(node, "workflow")}; + } + let duplicateOptions = {target: "workflow", cb: (body) => { + let title = `Model for ${body.File}`; + if(body.error){ + view.reportError({Reason: title, Message: body.error}); + }else{ + if(document.querySelector("#successModal")) { + document.querySelector("#successModal").remove(); + } + let message = `The model for ${body.File} is located here: ${body.mdlPath}`; + let modal = $(modals.successHtml(title, message)).modal(); + } + }} + if(!node.original._newFormat) { + options['timeStamp'] = view.getTimeStamp(); + } + let downloadOptions = {dataType: "zip", identifier: "file/download-zip"}; + let options = {asZip: true}; + return { + open: view.getOpenWorkflowContext(node), + model: view.getWorkflowMdlContext(node), + download: view.getDownloadContext(node, downloadOptions, options), + rename: view.getRenameContext(node), + duplicate: view.getDuplicateContext(node, "workflow/duplicate", duplicateOptions), + moveToTrash: view.getMoveToTrashContext(node) + } +} + let move = (view, par, node) => { let newDir = par.original._path !== "/" ? par.original._path : ""; let file = node.original._path.split('/').pop(); @@ -285,6 +314,7 @@ module.exports = { getProjectContext: getProjectContext, getRootContext: getRootContext, getSpatialModelContext: getSpatialModelContext, + getWorkflowContext: getWorkflowContext, // getWorflowGroupContext: getOtherContext, move: move, setup: setup, diff --git a/client/pages/file-browser.js b/client/pages/file-browser.js index 3c491e0cfe..f551af6c34 100644 --- a/client/pages/file-browser.js +++ b/client/pages/file-browser.js @@ -434,31 +434,31 @@ let FileBrowser = PageView.extend({ // } // }); // }, - getTimeStamp: function () { - var date = new Date(); - var year = date.getFullYear(); - var month = date.getMonth() + 1; - if(month < 10){ - month = "0" + month - } - var day = date.getDate(); - if(day < 10){ - day = "0" + day - } - var hours = date.getHours(); - if(hours < 10){ - hours = "0" + hours - } - var minutes = date.getMinutes(); - if(minutes < 10){ - minutes = "0" + minutes - } - var seconds = date.getSeconds(); - if(seconds < 10){ - seconds = "0" + seconds - } - return "_" + month + day + year + "_" + hours + minutes + seconds; - }, + // getTimeStamp: function () { + // var date = new Date(); + // var year = date.getFullYear(); + // var month = date.getMonth() + 1; + // if(month < 10){ + // month = "0" + month + // } + // var day = date.getDate(); + // if(day < 10){ + // day = "0" + day + // } + // var hours = date.getHours(); + // if(hours < 10){ + // hours = "0" + hours + // } + // var minutes = date.getMinutes(); + // if(minutes < 10){ + // minutes = "0" + minutes + // } + // var seconds = date.getSeconds(); + // if(seconds < 10){ + // seconds = "0" + seconds + // } + // return "_" + month + day + year + "_" + hours + minutes + seconds; + // }, // toSpatial: function (o) { // var self = this; // var parentID = o.parent; @@ -902,20 +902,20 @@ let FileBrowser = PageView.extend({ // showContextMenuForNode: function (e) { // $('#models-jstree').jstree().show_contextmenu(this.nodeForContextMenu) // }, - editWorkflowModel: function (o) { - let endpoint = path.join(app.getApiPath(), "workflow/edit-model")+"?path="+o.original._path - app.getXHR(endpoint, { - success: function (err, response, body) { - if(body.error){ - let title = o.text + " Not Found"; - let message = body.error; - let modal = $(modals.duplicateWorkflowHtml(title, message)).modal(); - }else{ - window.location.href = path.join(app.routePrefix, "models/edit")+"?path="+body.file; - } - } - }); - }, + // editWorkflowModel: function (o) { + // let endpoint = path.join(app.getApiPath(), "workflow/edit-model")+"?path="+o.original._path + // app.getXHR(endpoint, { + // success: function (err, response, body) { + // if(body.error){ + // let title = o.text + " Not Found"; + // let message = body.error; + // let modal = $(modals.duplicateWorkflowHtml(title, message)).modal(); + // }else{ + // window.location.href = path.join(app.routePrefix, "models/edit")+"?path="+body.file; + // } + // } + // }); + // }, extractAll: function (o) { let self = this; let queryStr = "?path=" + o.original._path; @@ -1296,7 +1296,7 @@ let FileBrowser = PageView.extend({ "separator_after" : true, "action" : function (data) { if(nodeType === "workflow"){ - window.location.href = path.join(app.getBasePath(), "stochss/workflow/edit")+"?path="+o.original._path+"&type=none"; + // window.location.href = path.join(app.getBasePath(), "stochss/workflow/edit")+"?path="+o.original._path+"&type=none"; // }else if(nodeType === "project"){ // window.location.href = path.join(app.getBasePath(), "stochss/project/manager")+"?path="+o.original._path }else if(nodeType === "domain") { @@ -1336,52 +1336,52 @@ let FileBrowser = PageView.extend({ // } // } // specific to workflows - let workflow = { - "Start/Restart Workflow" : { - "label" : (o.original._status === "ready") ? "Start Workflow" : "Restart Workflow", - "_disabled" : true, - "separator_before" : false, - "separator_after" : false, - "action" : function (data) { + // let workflow = { + // "Start/Restart Workflow" : { + // "label" : (o.original._status === "ready") ? "Start Workflow" : "Restart Workflow", + // "_disabled" : true, + // "separator_before" : false, + // "separator_after" : false, + // "action" : function (data) { - } - }, - "Stop Workflow" : { - "label" : "Stop Workflow", - "_disabled" : true, - "separator_before" : false, - "separator_after" : false, - "action" : function (data) { + // } + // }, + // "Stop Workflow" : { + // "label" : "Stop Workflow", + // "_disabled" : true, + // "separator_before" : false, + // "separator_after" : false, + // "action" : function (data) { - } - }, - "Model" : { - "label" : "Model", - "_disabled" : false, - "separator_before" : false, - "separator_after" : false, - "submenu" : { - "Edit" : { - "label" : " Edit", - "_disabled" : (!o.original._newFormat && o.original._status !== "ready"), - "separator_before" : false, - "separator_after" : false, - "action" : function (data) { - self.editWorkflowModel(o) - } - }, - "Extract" : { - "label" : "Extract", - "_disabled" : (o.original._newFormat && !o.original._hasJobs), - "separator_before" : false, - "separator_after" : false, - "action" : function (data) { - self.duplicateFileOrDirectory(o, "wkfl_model") - } - } - } - } - } + // } + // }, + // "Model" : { + // "label" : "Model", + // "_disabled" : false, + // "separator_before" : false, + // "separator_after" : false, + // "submenu" : { + // "Edit" : { + // "label" : " Edit", + // "_disabled" : (!o.original._newFormat && o.original._status !== "ready"), + // "separator_before" : false, + // "separator_after" : false, + // "action" : function (data) { + // self.editWorkflowModel(o) + // } + // }, + // "Extract" : { + // "label" : "Extract", + // "_disabled" : (o.original._newFormat && !o.original._hasJobs), + // "separator_before" : false, + // "separator_after" : false, + // "action" : function (data) { + // self.duplicateFileOrDirectory(o, "wkfl_model") + // } + // } + // } + // } + // } // Specific to sbml files let sbml = { "Convert" : { @@ -1437,18 +1437,18 @@ let FileBrowser = PageView.extend({ // if (o.type === 'folder') { // folder node // return $.extend(folder, common) // } - if (o.type === 'spatial') { // spatial model node - return $.extend(model, spatialConvert, common) - } + // if (o.type === 'spatial') { // spatial model node + // return $.extend(model, spatialConvert, common) + // } // if (o.type === 'nonspatial') { // model node // return $.extend(model, modelConvert, common) // } // if (o.type === 'project'){ // return $.extend(open, project, common) // } - if (o.type === 'workflow') { // workflow node - return $.extend(open, workflow, common) - } + // if (o.type === 'workflow') { // workflow node + // return $.extend(open, workflow, common) + // } if (o.text.endsWith(".zip")) { // zip archive node return $.extend(open, extractAll, common) } @@ -1464,9 +1464,9 @@ let FileBrowser = PageView.extend({ if (o.type === 'sbml-model') { // sbml model node return $.extend(open, sbml, common) } - if (o.type === "domain") { // domain node - return $.extend(open, common) - } + // if (o.type === "domain") { // domain node + // return $.extend(open, common) + // } } // $(document).on('shown.bs.modal', function (e) { // $('[autofocus]', e.target).focus(); diff --git a/client/project-config.js b/client/project-config.js index 2ddf2d1cc1..899f6e55c9 100644 --- a/client/project-config.js +++ b/client/project-config.js @@ -36,11 +36,9 @@ let doubleClick = (view, e) => { }else if(node.type === "sbml-model"){ window.open(path.join(app.getBasePath(), "edit", node.original._path), '_blank'); }else if(node.type === "workflow"){ - let queryStr = "?path=" + node.original._path + "&type=none"; - window.location.href = path.join(app.getBasePath(), "stochss/workflow/edit") + queryStr; + view.openWorkflow(node.original._path); }else if(node.type === "domain") { - let queryStr = "?domainPath=" + node.original._path; - window.location.href = path.join(app.getBasePath(), "stochss/domain/edit") + queryStr + view.openDomain(node.original._path); }else if(node.type === "other"){ var openPath = path.join(app.getBasePath(), "view", node.original._path); window.open(openPath, "_blank"); @@ -48,14 +46,14 @@ let doubleClick = (view, e) => { } } -let extractModel = (view, node) => { +let extract = (view, node, type) => { let projectPar = path.dirname(view.root) === '.' ? "" : path.dirname(view.root); let dstPath = path.join(projectPar, node.original._path.split('/').pop()); let queryStr = `?srcPath=${node.original._path}&dstPath=${dstPath}`; - let endpoint = path.join(app.getApiPath(), "project/extract-model") + queryStr; + let endpoint = path.join(app.getApiPath(), `project/extract-${type}`) + queryStr; app.getXHR(endpoint, { success: (err, response, body) => { - let title = "Successfully Exported the Model"; + let title = `Successfully Exported the ${type.replace(type.charAt(0), type.charAt(0).toUpperCase())}`; let successModel = $(modals.successHtml(body, {title: title})).modal(); }, error: (err, response, body) => { @@ -64,14 +62,14 @@ let extractModel = (view, node) => { }); } -let getExtractContext = (view, node) => { +let getExtractContext = (view, node, type) => { return { label: "Extract", _disabled: false, separator_before: false, separator_after: false, action: (data) => { - extractModel(view, node); + extract(view, node, type); } } } @@ -104,7 +102,7 @@ let getModelContext = (view, node) => { let downloadOptions = {dataType: "json", identifier: "file/json-data"}; return { edit: view.getEditModelContext(node), - extract: getExtractContext(view, node), + extract: getExtractModelContext(view, node, "model"), newWorkflow: view.getFullNewWorkflowContext(node), convert: view.getMdlConvertContext(node), download: view.getDownloadContext(node, downloadOptions), @@ -126,16 +124,6 @@ let getRootContext = (view, node) => { } } -let getWorkflowGroupContext = (view, node) => { - if(node.original._path.includes(".proj/trash/")) { //item in trash - return {delete: view.getDeleteContext(node, "workflow group")}; - } - return { - refresh: view.getRefreshContext(node), - download: view.getDownloadWCombineContext(node) - } -} - let getSpatialModelContext = (view, node) => { if(node.original._path.includes(".proj/trash/")) { //item in trash return {delete: view.getDeleteContext(node, "spatial model")}; @@ -153,6 +141,46 @@ let getSpatialModelContext = (view, node) => { } } +let getWorkflowContext = (view, node) => { + if(node.original._path.includes(".proj/trash/")) { //item in trash + return {delete: view.getDeleteContext(node, "workflow")}; + } + let options = {target: "workflow", cb: (body) => { + let title = `Model for ${body.File}`; + if(body.error){ + view.reportError({Reason: title, Message: body.error}); + }else{ + if(document.querySelector("#successModal")) { + document.querySelector("#successModal").remove(); + } + let message = `The model for ${body.File} is located here: ${body.mdlPath}`; + let modal = $(modals.successHtml(title, message)).modal(); + } + }} + if(!node.original._newFormat) { + options['timeStamp'] = view.getTimeStamp(); + } + return { + open: view.getOpenWorkflowContext(node), + model: view.getWorkflowMdlContext(node), + extract: getExtractContext(view, node, "workflow"), + download: view.getDownloadWCombineContext(node), + rename: view.getRenameContext(node), + duplicate: view.getDuplicateContext(node, "workflow/duplicate", options), + delete: view.getDeleteContext(node, "workflow") + } +} + +let getWorkflowGroupContext = (view, node) => { + if(node.original._path.includes(".proj/trash/")) { //item in trash + return {delete: view.getDeleteContext(node, "workflow group")}; + } + return { + refresh: view.getRefreshContext(node), + download: view.getDownloadWCombineContext(node) + } +} + let move = (view, par, node) => { let newDir = par.original._path !== "/" ? par.original._path : ""; let file = node.original._path.split('/').pop(); @@ -327,6 +355,7 @@ module.exports = { // getProjectContext: getOtherContext, getRootContext: getRootContext, getSpatialModelContext: getSpatialModelContext, + getWorkflowContext: getWorkflowContext, getWorkflowGroupContext: getWorkflowGroupContext, move: move, setup: setup, diff --git a/client/views/file-browser-view.js b/client/views/file-browser-view.js index 5b07bb4a4b..bd29e1b7e4 100644 --- a/client/views/file-browser-view.js +++ b/client/views/file-browser-view.js @@ -432,31 +432,31 @@ let Model = require('../models/model'); // } // }); // }, - getTimeStamp: function () { - var date = new Date(); - var year = date.getFullYear(); - var month = date.getMonth() + 1; - if(month < 10){ - month = "0" + month - } - var day = date.getDate(); - if(day < 10){ - day = "0" + day - } - var hours = date.getHours(); - if(hours < 10){ - hours = "0" + hours - } - var minutes = date.getMinutes(); - if(minutes < 10){ - minutes = "0" + minutes - } - var seconds = date.getSeconds(); - if(seconds < 10){ - seconds = "0" + seconds - } - return "_" + month + day + year + "_" + hours + minutes + seconds; - }, + // getTimeStamp: function () { + // var date = new Date(); + // var year = date.getFullYear(); + // var month = date.getMonth() + 1; + // if(month < 10){ + // month = "0" + month + // } + // var day = date.getDate(); + // if(day < 10){ + // day = "0" + day + // } + // var hours = date.getHours(); + // if(hours < 10){ + // hours = "0" + hours + // } + // var minutes = date.getMinutes(); + // if(minutes < 10){ + // minutes = "0" + minutes + // } + // var seconds = date.getSeconds(); + // if(seconds < 10){ + // seconds = "0" + seconds + // } + // return "_" + month + day + year + "_" + hours + minutes + seconds; + // }, // toSpatial: function (o) { // var self = this; // var parentID = o.parent; @@ -907,21 +907,21 @@ let Model = require('../models/model'); // } // }); // }, - handleExportWorkflowClick: function (o) { - let self = this - let projectParent = path.dirname(this.parent.model.directory) === '.' ? "" : path.dirname(this.parent.model.directory) - let queryString = "?srcPath="+o.original._path+"&dstPath="+path.join(projectParent, o.original._path.split('/').pop()) - let endpoint = path.join(app.getApiPath(), "project/extract-workflow")+queryString - app.getXHR(endpoint, { - success: function (err, response, body) { - let successModel = $(modals.projectExportSuccessHtml("Workflow", body)).modal(); - }, - error: function (err, response, body) { - body = JSON.parse(body); - let successModel = $(modals.projectExportErrorHtml(body.Reason, body.message)).modal(); - } - }); - }, + // handleExportWorkflowClick: function (o) { + // let self = this + // let projectParent = path.dirname(this.parent.model.directory) === '.' ? "" : path.dirname(this.parent.model.directory) + // let queryString = "?srcPath="+o.original._path+"&dstPath="+path.join(projectParent, o.original._path.split('/').pop()) + // let endpoint = path.join(app.getApiPath(), "project/extract-workflow")+queryString + // app.getXHR(endpoint, { + // success: function (err, response, body) { + // let successModel = $(modals.projectExportSuccessHtml("Workflow", body)).modal(); + // }, + // error: function (err, response, body) { + // body = JSON.parse(body); + // let successModel = $(modals.projectExportErrorHtml(body.Reason, body.message)).modal(); + // } + // }); + // }, handleExportCombineClick: function (o, download) { let target = o.original._path this.parent.exportAsCombine() @@ -929,20 +929,20 @@ let Model = require('../models/model'); // showContextMenuForNode: function (e) { // $('#models-jstree-view').jstree().show_contextmenu(this.nodeForContextMenu) // }, - editWorkflowModel: function (o) { - let endpoint = path.join(app.getApiPath(), "workflow/edit-model")+"?path="+o.original._path - app.getXHR(endpoint, { - success: function (err, response, body) { - if(body.error){ - let title = o.text + " Not Found"; - let message = body.error; - let modal = $(modals.duplicateWorkflowHtml(title, message)).modal(); - }else{ - window.location.href = path.join(app.routePrefix, "models/edit")+"?path="+body.file; - } - } - }); - }, + // editWorkflowModel: function (o) { + // let endpoint = path.join(app.getApiPath(), "workflow/edit-model")+"?path="+o.original._path + // app.getXHR(endpoint, { + // success: function (err, response, body) { + // if(body.error){ + // let title = o.text + " Not Found"; + // let message = body.error; + // let modal = $(modals.duplicateWorkflowHtml(title, message)).modal(); + // }else{ + // window.location.href = path.join(app.routePrefix, "models/edit")+"?path="+body.file; + // } + // } + // }); + // }, extractAll: function (o) { let self = this; let queryStr = "?path=" + o.original._path; @@ -1436,24 +1436,24 @@ let Model = require('../models/model'); // if (o.text === "trash"){ // Trash node // return refresh // } - if (o.original._path.includes(".proj/trash/")) { //item in trash - return {"Delete": common.Delete} - } + // if (o.original._path.includes(".proj/trash/")) { //item in trash + // return {"Delete": common.Delete} + // } // if (o.type === 'folder') { // folder node // return $.extend(refresh, commonFolder, download, common) // } - if (o.type === 'spatial') { // spatial model node - return $.extend(commonModel, spatialConvert, download, common) - } + // if (o.type === 'spatial') { // spatial model node + // return $.extend(commonModel, spatialConvert, download, common) + // } // if (o.type === 'nonspatial') { // model node // return $.extend(commonModel, modelConvert, download, common) // } // if (o.type === 'workflow-group') { // return $.extend(refresh, downloadWCombine) // } - if (o.type === 'workflow') { // workflow node - return $.extend(open, workflow, downloadWCombine, common) - } + // if (o.type === 'workflow') { // workflow node + // return $.extend(open, workflow, downloadWCombine, common) + // } if (o.text.endsWith(".zip")) { // zip archove node return $.extend(open, extractAll, download, common) } @@ -1469,9 +1469,9 @@ let Model = require('../models/model'); if (o.type === 'sbml-model') { // sbml model node return $.extend(open, sbml, common) } - if (o.type === "domain") { // domain node - return $.extend(open, common) - } + // if (o.type === "domain") { // domain node + // return $.extend(open, common) + // } } // $(document).ready(function () { // $(document).on('shown.bs.modal', function (e) { diff --git a/client/views/jstree-view.js b/client/views/jstree-view.js index 66f04cd1c3..4b8ff90879 100644 --- a/client/views/jstree-view.js +++ b/client/views/jstree-view.js @@ -347,6 +347,25 @@ module.exports = View.extend({ } } }, + getDomainContext: function (view, node) { + let downloadOptions = {dataType: "json", identifier: "spatial-model/load-domain"}; + return { + open: { + label: "Open", + _disabled: false, + _class: "font-weight-bolder", + separator_before: false, + separator_after: true, + action: (data) => { + view.openDomain(node.original._path); + } + }, + download: view.getDownloadContext(node, downloadOptions), + rename: view.getRenameContext(node), + duplicate: view.getDuplicateContext(node, "file/duplicate"), + moveToTrash: view.getMoveToTrashContext(node) + } + }, getDownloadContext: function (node, options, {asZip=false, withCombine=false}={}) { if(withCombine) { var label = "as .zip"; @@ -562,6 +581,18 @@ module.exports = View.extend({ } } }, + getOpenWorkflowContext: function (node) { + return { + label: "Open", + _disabled: false, + _class: "font-weight-bolder", + separator_before: false, + separator_after: true, + action : (data) => { + this.openWorkflow(node.original._path) + } + } + }, getNewDirectoryContext: function (node) { let dirname = node.original._path === "/" ? "" : node.original._path; return { @@ -669,6 +700,44 @@ module.exports = View.extend({ } } }, + getTimeStamp: function () { + let date = new Date(); + let year = date.getFullYear(); + let month = (date.getMonth() + 1).toString().padStart(2, "0"); + let day = date.getDate().toString().padStart(2, "0"); + let hours = date.getHours().toString().padStart(2, "0"); + let minutes = date.getMinutes().toString().padStart(2, "0"); + let seconds = date.getSeconds().toString().padStart(2, "0"); + return `_${month}${day}${year}_${hours}${minutes}${seconds}`; + }, + getWorkflowMdlContext: function (node) { + return { + label: "Model", + _disabled: false, + separator_before: false, + separator_after: false, + submenu: { + edit: { + label: "Edit", + _disabled: (!node.original._newFormat && node.original._status !== "ready"), + separator_before: false, + separator_after: false, + action: function (data) { + this.openWorkflowModel(node); + } + }, + extract: { + label: "Extract", + _disabled: (node.original._newFormat && !node.original._hasJobs), + separator_before: false, + separator_after: false, + action: function (data) { + this.duplicate(node, "workflow/duplicate", {target: "wkfl_model"}); + } + } + } + } + }, handleCreateDirectoryClick: function (e) { let dirname = this.root === "none" ? "" : this.root; this.createDirectory(null, dirname); @@ -792,6 +861,10 @@ module.exports = View.extend({ } }); }, + openDomain: function (domainPath) { + let queryStr = `?domainPath=${domainPath}`; + window.location.href = path.join(app.getBasePath(), "stochss/domain/edit") + queryStr + }, openModel: function (modelPath) { let queryStr = `?path=${modelPath}`; window.location.href = path.join(app.getBasePath(), "stochss/models/edit") + queryStr; @@ -800,6 +873,24 @@ module.exports = View.extend({ let queryStr = `?path=${projectPath}`; window.location.href = path.join(app.getBasePath(), "stochss/project/manager") + queryStr; }, + openWorkflow: function (workflowPath) { + let queryStr = `?path=${workflowPath}&type=none`; + window.location.href = path.join(app.getBasePath(), "stochss/workflow/edit") + queryStr; + }, + openWorkflowModel: function (node) { + let queryStr = `?path=${node.original._path}`; + let endpoint = path.join(app.getApiPath(), "workflow/edit-model") + queryStr; + app.getXHR(endpoint, { + success: (err, response, body) => { + if(body.error){ + let title = `Model for ${node.text} Not Found`; + this.reportError({Reason: title, Message: body.error}); + }else{ + this.openModel(body.file); + } + } + }); + }, refreshInitialJSTree: function () { let count = $('#files-jstree').jstree()._model.data['#'].children.length; if(count == 0) { @@ -893,7 +984,9 @@ module.exports = View.extend({ workflowGroup: this.config.getWorkflowGroupContext, folder: this.config.getFolderContext, nonspatial: this.config.getModelContext, - spatial: this.config.getSpatialModelContext + spatial: this.config.getSpatialModelContext, + domain: this.getDomainContext, + workflow: this.config.getWorkflowContext } return contextMenus[node.type](this, node); } From 13808550b2b6469274d23a7b9602650ace0f3562 Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Wed, 1 Sep 2021 10:16:10 -0400 Subject: [PATCH 129/186] Update workflow group node type. --- stochss/handlers/util/stochss_folder.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/stochss/handlers/util/stochss_folder.py b/stochss/handlers/util/stochss_folder.py index e1e829bb49..a479dc0191 100644 --- a/stochss/handlers/util/stochss_folder.py +++ b/stochss/handlers/util/stochss_folder.py @@ -74,7 +74,7 @@ def __get_rmt_upld_path(self, file): def __build_jstree_node(self, path, file): types = {"mdl":"nonspatial", "smdl":"spatial", "sbml":"sbml-model", "ipynb":"notebook", - "wkfl":"workflow", "proj":"project", "wkgp":"workflow-group", "domn":"domain"} + "wkfl":"workflow", "proj":"project", "wkgp":"workflowGroup", "domn":"domain"} _path = file if self.path == "none" else os.path.join(self.path, file) ext = file.split('.').pop() if "." in file else None node = {"text":file, "type":"other", "_path":_path, "children":False} @@ -88,7 +88,7 @@ def __build_jstree_node(self, path, file): os.listdir(_path)))) > 0 else: node['_status'] = self.get_status(path=_path) - elif file_type == "workflow-group": + elif file_type == "workflowGroup": node['children'] = True elif os.path.isdir(os.path.join(path, file)): node['type'] = "folder" From 9965c0248d19737a5b6845f57a0a8cb0be8e8285 Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Wed, 1 Sep 2021 10:19:59 -0400 Subject: [PATCH 130/186] Context menu build enhancements. --- client/file-config.js | 26 +--- client/project-config.js | 13 +- client/views/jstree-view.js | 284 ++++++++++++++---------------------- 3 files changed, 119 insertions(+), 204 deletions(-) diff --git a/client/file-config.js b/client/file-config.js index 78f39d1639..c8e5b25940 100644 --- a/client/file-config.js +++ b/client/file-config.js @@ -41,7 +41,7 @@ let doubleClick = (view, e) => { }else if(node.type === "domain") { view.openDomain(node.original._path); }else if(node.type === "other"){ - var openPath = path.join(app.getBasePath(), "view", node.original._path); + let openPath = path.join(app.getBasePath(), "view", node.original._path); window.open(openPath, "_blank"); } } @@ -60,15 +60,12 @@ let getFolderContext = (view, node) => { return { refresh: view.getRefreshContext(node), newDirectory: view.getNewDirectoryContext(node), - newProject: { + newProject: view.buildContextBase({ label: "New Project", - _disabled: false, - separator_before: false, - separator_after: false, action: (data) => { view.createProject(node, dirname); } - }, + }), newModel: view.getNewModelContext(node, false), newDomain: view.getNewDomainContext(node), upload: view.getFullUploadContext(node, false), @@ -96,16 +93,12 @@ let getModelContext = (view, node) => { } let getOpenProjectContext = (view, node) => { - return { + return view.buildContextBaseWithClass({ label: "Open", - _disabled: false, - _class: "font-weight-bolder", - separator_before: false, - separator_after: true, action: (data) => { view.openProject(node.original._path); } - } + }); } let getProjectContext = (view, node) => { @@ -127,15 +120,12 @@ let getRootContext = (view, node) => { return { refresh: view.getRefreshContext(node), newDirectory: view.getNewDirectoryContext(node), - newProject: { + newProject: view.buildContextBase({ label: "New Project", - _disabled: false, - separator_before: false, - separator_after: false, action: (data) => { view.createProject(node, dirname); } - }, + }), newModel: view.getNewModelContext(node, false), newDomain: view.getNewDomainContext(node), upload: view.getFullUploadContext(node, false) @@ -193,7 +183,7 @@ let move = (view, par, node) => { let newDir = par.original._path !== "/" ? par.original._path : ""; let file = node.original._path.split('/').pop(); let oldPath = node.original._path; - let queryStr = "?srcPath=" + oldPath + "&dstPath=" + path.join(newDir, file); + let queryStr = `?srcPath=${oldPath}&dstPath=${path.join(newDir, file)}`; let endpoint = path.join(app.getApiPath(), "file/move") + queryStr; app.getXHR(endpoint, { success: (err, response, body) => { diff --git a/client/project-config.js b/client/project-config.js index 899f6e55c9..db3f0af833 100644 --- a/client/project-config.js +++ b/client/project-config.js @@ -40,7 +40,7 @@ let doubleClick = (view, e) => { }else if(node.type === "domain") { view.openDomain(node.original._path); }else if(node.type === "other"){ - var openPath = path.join(app.getBasePath(), "view", node.original._path); + let openPath = path.join(app.getBasePath(), "view", node.original._path); window.open(openPath, "_blank"); } } @@ -63,15 +63,12 @@ let extract = (view, node, type) => { } let getExtractContext = (view, node, type) => { - return { + return view.buildContextBase({ label: "Extract", - _disabled: false, - separator_before: false, - separator_after: false, action: (data) => { extract(view, node, type); } - } + }); } let getFolderContext = (view, node) => { @@ -102,7 +99,7 @@ let getModelContext = (view, node) => { let downloadOptions = {dataType: "json", identifier: "file/json-data"}; return { edit: view.getEditModelContext(node), - extract: getExtractModelContext(view, node, "model"), + extract: getExtractContext(view, node, "model"), newWorkflow: view.getFullNewWorkflowContext(node), convert: view.getMdlConvertContext(node), download: view.getDownloadContext(node, downloadOptions), @@ -185,7 +182,7 @@ let move = (view, par, node) => { let newDir = par.original._path !== "/" ? par.original._path : ""; let file = node.original._path.split('/').pop(); let oldPath = node.original._path; - let queryStr = "?srcPath=" + oldPath + "&dstPath=" + path.join(newDir, file); + let queryStr = `?srcPath=${oldPath}&dstPath=${path.join(newDir, file)}`; let endpoint = path.join(app.getApiPath(), "file/move") + queryStr; app.getXHR(endpoint, { success: (err, response, body) => { diff --git a/client/views/jstree-view.js b/client/views/jstree-view.js index 4b8ff90879..35570d6cdc 100644 --- a/client/views/jstree-view.js +++ b/client/views/jstree-view.js @@ -157,6 +157,34 @@ module.exports = View.extend({ } }); }, + buildContextBase: function ({label="", disabled=false, bSep=false, aSep=false, action=(data)=>{}}={}) { + return { + label: label, + _disabled: disabled, + separator_before: bSep, + separator_after: aSep, + action: action + } + }, + buildContextBaseWithClass: function ({label="", disabled=false, bSep=false, aSep=true, action=(data)=>{}}={}) { + return { + label: label, + _disabled: disabled, + _class: "font-weight-bolder", + separator_before: bSep, + separator_after: aSep, + action: action + } + }, + buildContextWithSubmenus: function ({label="", disabled=false, bSep=false, aSep=false, submenu={}}={}) { + return { + label: label, + _disabled: disabled, + separator_before: bSep, + separator_after: aSep, + submenu: submenu + } + }, createDirectory: function (node, dirname) { if(document.querySelector('#newDirectoryModal')) { document.querySelector('#newDirectoryModal').remove(); @@ -317,49 +345,36 @@ module.exports = View.extend({ }, getAddModelContext: function (node) { let newModel = this.getNewModelContext(node, true); - return { + return this.buildContextWithSubmenus({ label: "Add Model", - _disabled: false, - separator_before: false, - separator_after: false, submenu: { newModel: newModel, - existingModel: { + existingModel: this.buildContextBase({ label: "Existing Model", - _disabled: false, - separator_before: false, - separator_after: false, action: (data) => { this.importModel(node, node.original._path); } - } + }) } - } + }); }, getDeleteContext: function (node, type) { - return { + return this.buildContextBase({ label: "Delete", - _disabled: false, - separator_before: false, - separator_after: false, action: (data) => { this.delete(node, type); } - } + }); }, getDomainContext: function (view, node) { let downloadOptions = {dataType: "json", identifier: "spatial-model/load-domain"}; return { - open: { + open: view.buildContextBaseWithClass({ label: "Open", - _disabled: false, - _class: "font-weight-bolder", - separator_before: false, - separator_after: true, action: (data) => { view.openDomain(node.original._path); } - }, + }), download: view.getDownloadContext(node, downloadOptions), rename: view.getRenameContext(node), duplicate: view.getDuplicateContext(node, "file/duplicate"), @@ -374,64 +389,48 @@ module.exports = View.extend({ }else { var label = "Download"; } - return { + return this.buildContextBase({ label: label, - _disabled: false, - separator_before: !withCombine, - separator_after: false, + bSep: !withCombine, action: (data) => { this.getExportData(node, options); } - } + }); }, getDownloadWCombineContext: function (node) { let options = {dataType: "zip", identifier: "file/download-zip"}; let download = this.getDownloadContext(node, options, {withCombine: true}); - return { + return this.buildContextWithSubmenus({ label: "Download", - _disabled: false, - separator_before: true, - separator_after: false, + bSep: true, submenu: { downloadAsZip: download, - downloadAsCombine: { + downloadAsCombine: this.buildContextBase({ label: "as COMBINE", - _disabled: true, - separator_before: false, - separator_after: false, - action: (data) => { - // handleExportCombineClick(o, true) - } - } + disabled: true, + }) } - } + }); }, getDuplicateContext: function (node, identifier, options) { if(!options) { options = {}; } let label = Boolean(options.cb) ? "Duplicate as new" : "Duplicate"; - return { + return this.buildContextBase({ label: label, - _disabled: false, - separator_before: false, - separator_after: false, action: (data) => { this.duplicate(node, identifier, options); } - } + }); }, getEditModelContext: function (node) { - return { + return this.buildContextBaseWithClass({ label: "Edit", - _disabled: false, - _class: "font-weight-bolder", - separator_before: false, - separator_after: true, action: function (data) { this.openModel(node.original._path); } - } + }); }, getExportData: function (node, {dataType="", identifier=""}={}) { if(node.original.text.endsWith('.zip')) { @@ -472,233 +471,169 @@ module.exports = View.extend({ }, getFileUploadContext: function (node, inProject, {label="File"}={}) { let dirname = node.original._path === "/" ? "" : node.original._path; - return { + return this.buildContextBase({ label: label, - _disabled: false, - separator_before: label !== "File", - separator_after: false, + bSep: label !== "File", action: (data) => { this.uploadFile(node, dirname, "file", inProject); } - } + }); }, getFullNewWorkflowContext: function (node) { - return { + return this.buildContextWithSubmenus({ label: "New Workflow", - _disabled: false, - separator_before: false, - separator_after: false, submenu: { - ensembleSimulation: { + ensembleSimulation: this.buildContextBase({ label: "Ensemble Simulation", - _disabled: false, - separator_before: false, - separator_after: false, action: (data) => { this.newWorkflow(node, "Ensemble Simulation"); } - }, - parameterSweep: { + }), + parameterSweep: this.buildContextBase({ label: "Parameter Sweep", - _disabled: false, - separator_before: false, - separator_after: false, action: (data) => { this.newWorkflow(node, "Parameter Sweep"); } - }, + }), jupyterNotebook: this.getNotebookNewWorkflowContext(node) } - } + }); }, getFullUploadContext: function (node, inProject) { let dirname = node.original._path === "/" ? "" : node.original._path; let file = this.getFileUploadContext(node, inProject); - return { + return this.buildContextWithSubmenus({ label: "Upload", - _disabled: false, - separator_before: true, - separator_after: false, + bSep: true, submenu: { - model: { + model: this.buildContextBase({ label: "StochSS Model", - _disabled: false, - separator_before: false, - separator_after: false, action: (data) => { this.uploadFile(node, dirname, "model", inProject); } - }, - sbml: { + }), + sbml: this.buildContextBase({ label: "SBML Model", - _disabled: false, - separator_before: false, - separator_after: false, action: (data) => { this.uploadFile(node, dirname, "sbml", inProject); } - }, + }), file: file } - } + }); }, getMdlConvertContext: function (node) { - return { + return this.buildContextWithSubmenus({ label: "Convert", - _disabled: false, - separator_before: false, - separator_after: false, submenu: { - toSpatial: { + toSpatial: this.buildContextBase({ label: "To Spatial Model", - _disabled: false, - separator_before: false, - separator_after: false, action: (data) => { this.config.toSpatial(this, node); } - }, - toSBML: { + }), + toSBML: this.buildContextBase({ label: "To SBML Model", - _disabled: false, - separator_before: false, - separator_after: false, action: (data) => { this.config.toSBML(this, node); } - } + }) } - } + }); }, getMoveToTrashContext: function (node, type) { - return { + return this.buildContextBase({ label: "Move To Trash", - _disabled: false, - separator_before: false, - separator_after: false, action: (data) => { this.moveToTrash(node, type); } - } + }); }, getOpenWorkflowContext: function (node) { - return { + return this.buildContextBaseWithClass({ label: "Open", - _disabled: false, - _class: "font-weight-bolder", - separator_before: false, - separator_after: true, action : (data) => { - this.openWorkflow(node.original._path) + this.openWorkflow(node.original._path); } - } + }); }, getNewDirectoryContext: function (node) { let dirname = node.original._path === "/" ? "" : node.original._path; - return { + return this.buildContextBase({ label: "New Directory", - _disabled: false, - separator_before: false, - separator_after: false, action: (data) => { this.createDirectory(node, dirname); } - } + }); }, getNewDomainContext: function (node) { - return { + return this.buildContextBase({ label: "New Domain (beta)", - _disabled: false, - separator_before: false, - separator_after: false, action: (data) => { this.createDomain(node.original._path); } - } + }); }, getNewModelContext: function (node, inProject) { let dirname = node.original._path === "/" ? "" : node.original._path; - return { + return this.buildContextWithSubmenus({ label: "New Model", - _disabled: false, - separator_before: false, - separator_after: false, submenu: { - spatial: { + spatial: this.buildContextBase({ label: "Spatial (beta)", - _disabled: false, - separator_before: false, - separator_after: false, action: (data) => { this.createModel(node, dirname, true, inProject); } - }, - nonspatial: { + }), + nonspatial: this.buildContextBase({ label: "Non-Spatial", - _disabled: false, - separator_before: false, - separator_after: false, action: (data) => { this.createModel(node, dirname, false, inProject); } - } + }) } - } + }); }, getNotebookNewWorkflowContext: function (node) { - return { + return this.buildContextBase({ label: "Jupyter Notebook", - _disabled: false, - separator_before: false, - separator_after: false, action: (data) => { let queryStr = `?path=${node.original._path}`; window.location.href = path.join(app.getBasePath(), "stochss/workflow/selection") + queryStr; } - } + }); }, getRefreshContext: function (node) { - return { + return this.buildContextBaseWithClass({ label: "Refresh", - _disabled: false, - _class: "font-weight-bold", - separator_before: false, - separator_after: true, action: (data) => { this.refreshJSTree(node); } - } + }); }, getRenameContext: function (node) { let disabled = node.type === "workflow" && node.original._status === "running"; - return { + return this.buildContextBase({ label: "Rename", - _disabled: disabled, - separator_before: false, - separator_after: false, + disabled: disabled, action: (data) => { this.renameNode(node); } - } + }); }, getSmdlConvertContext: function (node, identifier) { - return { + return this.buildContextWithSubmenus({ label: "Convert", - _disabled: false, - separator_before: false, - separator_after: true, + aSep: true, submenu: { - convertToModel: { + convertToModel: this.buildContextBase({ label: "To Model", - _disabled: false, - separator_before: false, - separator_after: false, action: function (data) { this.config.toModel(this, node, identifier); } - } + }) } - } + }); }, getTimeStamp: function () { let date = new Date(); @@ -711,32 +646,25 @@ module.exports = View.extend({ return `_${month}${day}${year}_${hours}${minutes}${seconds}`; }, getWorkflowMdlContext: function (node) { - return { + return this.buildContextWithSubmenus({ label: "Model", - _disabled: false, - separator_before: false, - separator_after: false, submenu: { - edit: { + edit: this.buildContextBase({ label: "Edit", - _disabled: (!node.original._newFormat && node.original._status !== "ready"), - separator_before: false, - separator_after: false, + disabled: (!node.original._newFormat && node.original._status !== "ready"), action: function (data) { this.openWorkflowModel(node); } - }, - extract: { + }), + extract: this.buildContextBase({ label: "Extract", - _disabled: (node.original._newFormat && !node.original._hasJobs), - separator_before: false, - separator_after: false, + disabled: (node.original._newFormat && !node.original._hasJobs), action: function (data) { this.duplicate(node, "workflow/duplicate", {target: "wkfl_model"}); } - } + }) } - } + }); }, handleCreateDirectoryClick: function (e) { let dirname = this.root === "none" ? "" : this.root; From cf2ff5ae89dadc8a20c1f17ef9da982dd9896662 Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Wed, 1 Sep 2021 12:11:24 -0400 Subject: [PATCH 131/186] Moved the notebook context menu and required functions. --- client/file-config.js | 51 +++++++- client/pages/file-browser.js | 88 +++++++------- client/project-config.js | 49 +++++++- client/views/file-browser-view.js | 194 +++++++++++++++--------------- client/views/jstree-view.js | 66 +++++++--- 5 files changed, 278 insertions(+), 170 deletions(-) diff --git a/client/file-config.js b/client/file-config.js index c8e5b25940..577ab1989d 100644 --- a/client/file-config.js +++ b/client/file-config.js @@ -31,7 +31,7 @@ let doubleClick = (view, e) => { }else if(node.type === "nonspatial" || node.type === "spatial"){ view.openModel(node.original._path); }else if(node.type === "notebook"){ - window.open(path.join(app.getBasePath(), "notebooks", node.original._path), '_blank'); + view.openNotebook(node.original._path); }else if(node.type === "sbml-model"){ window.open(path.join(app.getBasePath(), "edit", node.original._path), '_blank'); }else if(node.type === "project"){ @@ -47,6 +47,22 @@ let doubleClick = (view, e) => { } } +let getDomainContext = (view, node) => { + let downloadOptions = {dataType: "json", identifier: "spatial-model/load-domain"}; + return { + open: view.buildContextBaseWithClass({ + label: "Open", + action: (data) => { + view.openDomain(node.original._path); + } + }), + download: view.getDownloadContext(node, downloadOptions), + rename: view.getRenameContext(node), + duplicate: view.getDuplicateContext(node, "file/duplicate"), + moveToTrash: view.getMoveToTrashContext(node, "domain") + } +} + let getFolderContext = (view, node) => { if(node.text === "trash") {//Trash node return {refresh: view.getRefreshContext(node)}; @@ -72,7 +88,7 @@ let getFolderContext = (view, node) => { download: view.getDownloadContext(node, downloadOptions, options), rename: view.getRenameContext(node), duplicate: view.getDuplicateContext(node, "directory/duplicate"), - moveToTrash: view.getMoveToTrashContext(node) + moveToTrash: view.getMoveToTrashContext(node, "directory") } } @@ -88,7 +104,28 @@ let getModelContext = (view, node) => { download: view.getDownloadContext(node, downloadOptions), rename: view.getRenameContext(node), duplicate: view.getDuplicateContext(node, "file/duplicate"), - moveToTrash: view.getMoveToTrashContext(node) + moveToTrash: view.getMoveToTrashContext(node, "model") + } +} + +let getNotebookContext = (view, node) => { + let open = view.getOpenNotebookContext(node); + let downloadOptions = {dataType: "json", identifier: "file/json-data"}; + let download = view.getDownloadContext(node, downloadOptions); + let rename = view.getRenameContext(node); + let duplicate = view.getDuplicateContext(node, "file/duplicate"); + let moveToTrash = view.getMoveToTrashContext(node, "notebook"); + if(app.getBasePath() === "/") { + return { + open: open, download: download, rename: rename, + duplicate: duplicate, moveToTrash: moveToTrash + } + } + return { + open: open, + publish: view.getPublishNotebookContext(node), + download: download, rename: rename, + duplicate: duplicate, moveToTrash: moveToTrash } } @@ -111,7 +148,7 @@ let getProjectContext = (view, node) => { download: view.getDownloadWCombineContext(node), rename: view.getRenameContext(node), duplicate: view.getDuplicateContext(node, "directory/duplicate"), - moveToTrash: view.getMoveToTrashContext(node) + moveToTrash: view.getMoveToTrashContext(node, "project") } } @@ -144,7 +181,7 @@ let getSpatialModelContext = (view, node) => { download: view.getDownloadContext(node, downloadOptions), rename: view.getRenameContext(node), duplicate: view.getDuplicateContext(node, "file/duplicate"), - moveToTrash: view.getMoveToTrashContext(node) + moveToTrash: view.getMoveToTrashContext(node, "spatial model") } } @@ -175,7 +212,7 @@ let getWorkflowContext = (view, node) => { download: view.getDownloadContext(node, downloadOptions, options), rename: view.getRenameContext(node), duplicate: view.getDuplicateContext(node, "workflow/duplicate", duplicateOptions), - moveToTrash: view.getMoveToTrashContext(node) + moveToTrash: view.getMoveToTrashContext(node, "workflow") } } @@ -299,8 +336,10 @@ let validateMove = (view, node, more, pos) => { module.exports = { contextZipTypes: contextZipTypes, doubleClick: doubleClick, + getDomainContext: getDomainContext, getFolderContext: getFolderContext, getModelContext: getModelContext, + getNotebookContext: getNotebookContext, getProjectContext: getProjectContext, getRootContext: getRootContext, getSpatialModelContext: getSpatialModelContext, diff --git a/client/pages/file-browser.js b/client/pages/file-browser.js index f551af6c34..277410dc88 100644 --- a/client/pages/file-browser.js +++ b/client/pages/file-browser.js @@ -971,33 +971,33 @@ let FileBrowser = PageView.extend({ // }); // }); // }, - publishNotebookPresentation: function (o) { - let queryStr = "?path=" + o.original._path; - let endpoint = path.join(app.getApiPath(), "notebook/presentation") + queryStr; - app.getXHR(endpoint, { - success: function (err, response, body) { - let title = body.message; - let linkHeaders = "Shareable Presentation"; - let links = body.links; - $(modals.presentationLinks(title, linkHeaders, links)).modal(); - let copyBtn = document.querySelector('#presentationLinksModal #copy-to-clipboard'); - copyBtn.addEventListener('click', function (e) { - let onFulfilled = (value) => { - $("#copy-link-success").css("display", "inline-block"); - } - let onReject = (reason) => { - let msg = $("#copy-link-failed"); - msg.html(reason); - msg.css("display", "inline-block"); - } - app.copyToClipboard(links.presentation, onFulfilled, onReject); - }); - }, - error: function (err, response, body) { - $(modals.newProjectModelErrorHtml(body.Reason, body.Message)).modal(); - } - }); - }, + // publishNotebookPresentation: function (o) { + // let queryStr = "?path=" + o.original._path; + // let endpoint = path.join(app.getApiPath(), "notebook/presentation") + queryStr; + // app.getXHR(endpoint, { + // success: function (err, response, body) { + // let title = body.message; + // let linkHeaders = "Shareable Presentation"; + // let links = body.links; + // $(modals.presentationLinks(title, linkHeaders, links)).modal(); + // let copyBtn = document.querySelector('#presentationLinksModal #copy-to-clipboard'); + // copyBtn.addEventListener('click', function (e) { + // let onFulfilled = (value) => { + // $("#copy-link-success").css("display", "inline-block"); + // } + // let onReject = (reason) => { + // let msg = $("#copy-link-failed"); + // msg.html(reason); + // msg.css("display", "inline-block"); + // } + // app.copyToClipboard(links.presentation, onFulfilled, onReject); + // }); + // }, + // error: function (err, response, body) { + // $(modals.newProjectModelErrorHtml(body.Reason, body.Message)).modal(); + // } + // }); + // }, setupJstree: function () { var self = this; $.jstree.defaults.contextmenu.items = (o, cb) => { @@ -1414,17 +1414,17 @@ let FileBrowser = PageView.extend({ } } } - let notebook = { - "publish" : { - "label" : "Publish", - "_disabled" : false, - "separator_before" : false, - "separator_after" : false, - "action" : function (data) { - self.publishNotebookPresentation(o); - } - } - } + // let notebook = { + // "publish" : { + // "label" : "Publish", + // "_disabled" : false, + // "separator_before" : false, + // "separator_after" : false, + // "action" : function (data) { + // self.publishNotebookPresentation(o); + // } + // } + // } // if (o.type === 'root'){ // return folder // } @@ -1452,12 +1452,12 @@ let FileBrowser = PageView.extend({ if (o.text.endsWith(".zip")) { // zip archive node return $.extend(open, extractAll, common) } - if (o.type === 'notebook') { // notebook node - if(app.getBasePath() === "/") { - return $.extend(open, common) - } - return $.extend(open, notebook, common) - } + // if (o.type === 'notebook') { // notebook node + // if(app.getBasePath() === "/") { + // return $.extend(open, common) + // } + // return $.extend(open, notebook, common) + // } if (o.type === 'other') { // other nodes return $.extend(open, common) } diff --git a/client/project-config.js b/client/project-config.js index db3f0af833..67e96363aa 100644 --- a/client/project-config.js +++ b/client/project-config.js @@ -32,7 +32,7 @@ let doubleClick = (view, e) => { }else if(node.type === "nonspatial" || node.type === "spatial"){ view.openModel(node.original._path); }else if(node.type === "notebook"){ - window.open(path.join(app.getBasePath(), "notebooks", node.original._path), '_blank'); + view.openNotebook(node.original._path); }else if(node.type === "sbml-model"){ window.open(path.join(app.getBasePath(), "edit", node.original._path), '_blank'); }else if(node.type === "workflow"){ @@ -62,6 +62,22 @@ let extract = (view, node, type) => { }); } +let getDomainContext = (view, node) => { + let downloadOptions = {dataType: "json", identifier: "spatial-model/load-domain"}; + return { + open: view.buildContextBaseWithClass({ + label: "Open", + action: (data) => { + view.openDomain(node.original._path); + } + }), + download: view.getDownloadContext(node, downloadOptions), + rename: view.getRenameContext(node), + duplicate: view.getDuplicateContext(node, "file/duplicate"), + moveToTrash: view.getMoveToTrashContext(node, "domain") + } +} + let getExtractContext = (view, node, type) => { return view.buildContextBase({ label: "Extract", @@ -88,7 +104,7 @@ let getFolderContext = (view, node) => { download: view.getDownloadContext(node, downloadOptions, options), rename: view.getRenameContext(node), duplicate: view.getDuplicateContext(node, "directory/duplicate"), - delete: view.getDeleteContext(node, "directory") + moveToTrash: view.getMoveToTrashContext(node, "directory") } } @@ -105,7 +121,28 @@ let getModelContext = (view, node) => { download: view.getDownloadContext(node, downloadOptions), rename: view.getRenameContext(node), duplicate: view.getDuplicateContext(node, "file/duplicate"), - delete: view.getDeleteContext(node, "model") + moveToTrash: view.getMoveToTrashContext(node, "model") + } +} + +let getNotebookContext = (view, node) => { + let open = view.getOpenNotebookContext(node); + let downloadOptions = {dataType: "json", identifier: "file/json-data"}; + let download = view.getDownloadContext(node, downloadOptions); + let rename = view.getRenameContext(node); + let duplicate = view.getDuplicateContext(node, "file/duplicate"); + let moveToTrash = view.getMoveToTrashContext(node, "notebook"); + if(app.getBasePath() === "/") { + return { + open: open, download: download, rename: rename, + duplicate: duplicate, moveToTrash: moveToTrash + } + } + return { + open: open, + publish: view.getPublishNotebookContext(node), + download: download, rename: rename, + duplicate: duplicate, delete: deleteFile } } @@ -134,7 +171,7 @@ let getSpatialModelContext = (view, node) => { download: view.getDownloadContext(node, downloadOptions), rename: view.getRenameContext(node), duplicate: view.getDuplicateContext(node, "file/duplicate"), - delete: view.getDeleteContext(node, "model") + moveToTrash: view.getMoveToTrashContext(node, "model") } } @@ -164,7 +201,7 @@ let getWorkflowContext = (view, node) => { download: view.getDownloadWCombineContext(node), rename: view.getRenameContext(node), duplicate: view.getDuplicateContext(node, "workflow/duplicate", options), - delete: view.getDeleteContext(node, "workflow") + moveToTrash: view.getMoveToTrashContext(node, "workflow") } } @@ -347,8 +384,10 @@ let validateMove = (view, node, more, pos) => { module.exports = { contextZipTypes: contextZipTypes, doubleClick: doubleClick, + getDomainContext: getDomainContext, getFolderContext: getFolderContext, getModelContext: getModelContext, + getNotebookContext: getNotebookContext, // getProjectContext: getOtherContext, getRootContext: getRootContext, getSpatialModelContext: getSpatialModelContext, diff --git a/client/views/file-browser-view.js b/client/views/file-browser-view.js index bd29e1b7e4..1e85eb0089 100644 --- a/client/views/file-browser-view.js +++ b/client/views/file-browser-view.js @@ -961,33 +961,33 @@ let Model = require('../models/model'); } }); }, - publishNotebookPresentation: function (o) { - let queryStr = "?path=" + o.original._path; - let endpoint = path.join(app.getApiPath(), "notebook/presentation") + queryStr; - app.getXHR(endpoint, { - success: function (err, response, body) { - let title = body.message; - let linkHeaders = "Shareable Presentation"; - let links = body.links; - $(modals.presentationLinks(title, linkHeaders, links)).modal(); - let copyBtn = document.querySelector('#presentationLinksModal #copy-to-clipboard'); - copyBtn.addEventListener('click', function (e) { - let onFulfilled = (value) => { - $("#copy-link-success").css("display", "inline-block"); - } - let onReject = (reason) => { - let msg = $("#copy-link-failed"); - msg.html(reason); - msg.css("display", "inline-block"); - } - app.copyToClipboard(links.presentation, onFulfilled, onReject); - }); - }, - error: function (err, response, body) { - $(modals.newProjectModelErrorHtml(body.Reason, body.Message)).modal(); - } - }); - }, + // publishNotebookPresentation: function (o) { + // let queryStr = "?path=" + o.original._path; + // let endpoint = path.join(app.getApiPath(), "notebook/presentation") + queryStr; + // app.getXHR(endpoint, { + // success: function (err, response, body) { + // let title = body.message; + // let linkHeaders = "Shareable Presentation"; + // let links = body.links; + // $(modals.presentationLinks(title, linkHeaders, links)).modal(); + // let copyBtn = document.querySelector('#presentationLinksModal #copy-to-clipboard'); + // copyBtn.addEventListener('click', function (e) { + // let onFulfilled = (value) => { + // $("#copy-link-success").css("display", "inline-block"); + // } + // let onReject = (reason) => { + // let msg = $("#copy-link-failed"); + // msg.html(reason); + // msg.css("display", "inline-block"); + // } + // app.copyToClipboard(links.presentation, onFulfilled, onReject); + // }); + // }, + // error: function (err, response, body) { + // $(modals.newProjectModelErrorHtml(body.Reason, body.Message)).modal(); + // } + // }); + // }, // setupJstree: function () { var self = this; // $.jstree.defaults.contextmenu.items = (o, cb) => { @@ -1302,61 +1302,61 @@ let Model = require('../models/model'); // } // } // specific to workflows - let workflow = { - "Start/Restart Workflow" : { - "label" : (o.original._status === "ready") ? "Start Workflow" : "Restart Workflow", - "_disabled" : true, - "separator_before" : false, - "separator_after" : false, - "action" : function (data) { + // let workflow = { + // "Start/Restart Workflow" : { + // "label" : (o.original._status === "ready") ? "Start Workflow" : "Restart Workflow", + // "_disabled" : true, + // "separator_before" : false, + // "separator_after" : false, + // "action" : function (data) { - } - }, - "Stop Workflow" : { - "label" : "Stop Workflow", - "_disabled" : true, - "separator_before" : false, - "separator_after" : false, - "action" : function (data) { + // } + // }, + // "Stop Workflow" : { + // "label" : "Stop Workflow", + // "_disabled" : true, + // "separator_before" : false, + // "separator_after" : false, + // "action" : function (data) { - } - }, - "Model" : { - "label" : "Model", - "_disabled" : false, - "separator_before" : false, - "separator_after" : false, - "submenu" : { - "Edit" : { - "label" : " Edit", - "_disabled" : (!o.original._newFormat && o.original._status !== "ready"), - "separator_before" : false, - "separator_after" : false, - "action" : function (data) { - self.editWorkflowModel(o) - } - }, - "Extract" : { - "label" : "Extract", - "_disabled" : (o.original._newFormat && !o.original._hasJobs), - "separator_before" : false, - "separator_after" : false, - "action" : function (data) { - self.duplicateFileOrDirectory(o, "wkfl_model") - } - } - } - }, - "Extract" : { - "label" : "Extract", - "_disabled" : false, - "separator_before" : false, - "separator_after" : true, - "action" : function (data) { - self.handleExportWorkflowClick(o) - } - }, - } + // } + // }, + // "Model" : { + // "label" : "Model", + // "_disabled" : false, + // "separator_before" : false, + // "separator_after" : false, + // "submenu" : { + // "Edit" : { + // "label" : " Edit", + // "_disabled" : (!o.original._newFormat && o.original._status !== "ready"), + // "separator_before" : false, + // "separator_after" : false, + // "action" : function (data) { + // self.editWorkflowModel(o) + // } + // }, + // "Extract" : { + // "label" : "Extract", + // "_disabled" : (o.original._newFormat && !o.original._hasJobs), + // "separator_before" : false, + // "separator_after" : false, + // "action" : function (data) { + // self.duplicateFileOrDirectory(o, "wkfl_model") + // } + // } + // } + // }, + // "Extract" : { + // "label" : "Extract", + // "_disabled" : false, + // "separator_before" : false, + // "separator_after" : true, + // "action" : function (data) { + // self.handleExportWorkflowClick(o) + // } + // }, + // } // Specific to sbml files let sbml = { "Convert" : { @@ -1419,17 +1419,17 @@ let Model = require('../models/model'); } } } - let notebook = { - "publish" : { - "label" : "Publish", - "_disabled" : false, - "separator_before" : false, - "separator_after" : false, - "action" : function (data) { - self.publishNotebookPresentation(o); - } - } - } + // let notebook = { + // "publish" : { + // "label" : "Publish", + // "_disabled" : false, + // "separator_before" : false, + // "separator_after" : false, + // "action" : function (data) { + // self.publishNotebookPresentation(o); + // } + // } + // } // if (o.type === 'root'){ // return $.extend(refresh, project, commonFolder, downloadWCombine, {"Rename": common.Rename}) // } @@ -1457,12 +1457,12 @@ let Model = require('../models/model'); if (o.text.endsWith(".zip")) { // zip archove node return $.extend(open, extractAll, download, common) } - if (o.type === 'notebook') { // notebook node - if(app.getBasePath() === "/") { - return $.extend(open, download, common) - } - return $.extend(open, notebook, download, common) - } + // if (o.type === 'notebook') { // notebook node + // if(app.getBasePath() === "/") { + // return $.extend(open, download, common) + // } + // return $.extend(open, notebook, download, common) + // } if (o.type === 'other') { // other nodes return $.extend(open, download, common) } diff --git a/client/views/jstree-view.js b/client/views/jstree-view.js index 35570d6cdc..e608aabe0b 100644 --- a/client/views/jstree-view.js +++ b/client/views/jstree-view.js @@ -366,21 +366,6 @@ module.exports = View.extend({ } }); }, - getDomainContext: function (view, node) { - let downloadOptions = {dataType: "json", identifier: "spatial-model/load-domain"}; - return { - open: view.buildContextBaseWithClass({ - label: "Open", - action: (data) => { - view.openDomain(node.original._path); - } - }), - download: view.getDownloadContext(node, downloadOptions), - rename: view.getRenameContext(node), - duplicate: view.getDuplicateContext(node, "file/duplicate"), - moveToTrash: view.getMoveToTrashContext(node) - } - }, getDownloadContext: function (node, options, {asZip=false, withCombine=false}={}) { if(withCombine) { var label = "as .zip"; @@ -549,6 +534,14 @@ module.exports = View.extend({ } }); }, + getOpenNotebookContext: function (node) { + return this.buildContextBaseWithClass({ + label: "Open", + action: (data) => { + this.openNotebook(node.original._path); + } + }); + }, getOpenWorkflowContext: function (node) { return this.buildContextBaseWithClass({ label: "Open", @@ -603,6 +596,14 @@ module.exports = View.extend({ } }); }, + getPublishNotebookContext: function (node) { + return this.buildContextBase({ + label: "Publish", + action: function (data) { + this.publishNotebookPresentation(node); + } + }); + }, getRefreshContext: function (node) { return this.buildContextBaseWithClass({ label: "Refresh", @@ -760,7 +761,8 @@ module.exports = View.extend({ let yesBtn = document.querySelector('#moveToTrashConfirmModal .yes-modal-btn'); yesBtn.addEventListener('click', (e) => { modal.modal('hide'); - let queryStr = `?srcPath=${node.original._path}&dstPath=${path.join("trash", node.text)}`; + let dirname = this.root === "none" ? "" : this.root; + let queryStr = `?srcPath=${node.original._path}&dstPath=${path.join(dirname, "trash", node.text)}`; let endpoint = path.join(app.getApiPath(), "file/move") + queryStr; app.getXHR(endpoint, { always: (err, response, body) => { @@ -797,6 +799,9 @@ module.exports = View.extend({ let queryStr = `?path=${modelPath}`; window.location.href = path.join(app.getBasePath(), "stochss/models/edit") + queryStr; }, + openNotebook: function (notebookPath) { + window.open(path.join(app.getBasePath(), "notebooks", node.original._path), '_blank'); + }, openProject: function (projectPath) { let queryStr = `?path=${projectPath}`; window.location.href = path.join(app.getBasePath(), "stochss/project/manager") + queryStr; @@ -819,6 +824,30 @@ module.exports = View.extend({ } }); }, + publishNotebookPresentation: function (node) { + let queryStr = `?path=${o.original._path}`; + let endpoint = path.join(app.getApiPath(), "notebook/presentation") + queryStr; + app.getXHR(endpoint, { + success: (err, response, body) => { + $(modals.presentationLinks(body.message, "Shareable Presentation", body.links)).modal(); + let copyBtn = document.querySelector('#presentationLinksModal #copy-to-clipboard'); + copyBtn.addEventListener('click', (e) => { + let onFulfilled = (value) => { + $("#copy-link-success").css("display", "inline-block"); + } + let onReject = (reason) => { + let msg = $("#copy-link-failed"); + msg.html(reason); + msg.css("display", "inline-block"); + } + app.copyToClipboard(links.presentation, onFulfilled, onReject); + }); + }, + error: (err, response, body) => { + this.reportError(body); + } + }); + }, refreshInitialJSTree: function () { let count = $('#files-jstree').jstree()._model.data['#'].children.length; if(count == 0) { @@ -913,8 +942,9 @@ module.exports = View.extend({ folder: this.config.getFolderContext, nonspatial: this.config.getModelContext, spatial: this.config.getSpatialModelContext, - domain: this.getDomainContext, - workflow: this.config.getWorkflowContext + domain: this.config.getDomainContext, + workflow: this.config.getWorkflowContext, + notebook: this.config.getNotebookContext } return contextMenus[node.type](this, node); } From d60d9c5aca0e515c818d26220510757745bc52c2 Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Wed, 1 Sep 2021 12:19:55 -0400 Subject: [PATCH 132/186] Updated the sbml model node type. --- client/file-config.js | 4 ++-- client/project-config.js | 4 ++-- client/views/jstree-view.js | 2 +- stochss/handlers/util/stochss_folder.py | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/client/file-config.js b/client/file-config.js index 577ab1989d..8f1e3443c7 100644 --- a/client/file-config.js +++ b/client/file-config.js @@ -32,7 +32,7 @@ let doubleClick = (view, e) => { view.openModel(node.original._path); }else if(node.type === "notebook"){ view.openNotebook(node.original._path); - }else if(node.type === "sbml-model"){ + }else if(node.type === "sbmlModel"){ window.open(path.join(app.getBasePath(), "edit", node.original._path), '_blank'); }else if(node.type === "project"){ view.openProject(node.original._path); @@ -297,7 +297,7 @@ let types = { 'workflow' : {"icon": "jstree-icon jstree-file"}, 'notebook' : {"icon": "jstree-icon jstree-file"}, 'domain' : {"icon": "jstree-icon jstree-file"}, - 'sbml-model' : {"icon": "jstree-icon jstree-file"}, + 'sbmlModel' : {"icon": "jstree-icon jstree-file"}, 'other' : {"icon": "jstree-icon jstree-file"} } diff --git a/client/project-config.js b/client/project-config.js index 67e96363aa..e13fdd55b7 100644 --- a/client/project-config.js +++ b/client/project-config.js @@ -33,7 +33,7 @@ let doubleClick = (view, e) => { view.openModel(node.original._path); }else if(node.type === "notebook"){ view.openNotebook(node.original._path); - }else if(node.type === "sbml-model"){ + }else if(node.type === "sbmlModel"){ window.open(path.join(app.getBasePath(), "edit", node.original._path), '_blank'); }else if(node.type === "workflow"){ view.openWorkflow(node.original._path); @@ -301,7 +301,7 @@ let types = { 'workflow' : {"icon": "jstree-icon jstree-file"}, 'notebook' : {"icon": "jstree-icon jstree-file"}, 'domain' : {"icon": "jstree-icon jstree-file"}, - 'sbml-model' : {"icon": "jstree-icon jstree-file"}, + 'sbmlModel' : {"icon": "jstree-icon jstree-file"}, 'other' : {"icon": "jstree-icon jstree-file"} } diff --git a/client/views/jstree-view.js b/client/views/jstree-view.js index e608aabe0b..27283c8dad 100644 --- a/client/views/jstree-view.js +++ b/client/views/jstree-view.js @@ -421,7 +421,6 @@ module.exports = View.extend({ if(node.original.text.endsWith('.zip')) { return this.exportToZipFile(node.original._path); } - let isJSON = node.original.type === "sbml-model" ? false : true; if(node.original.type === "domain") { var queryStr = `?domain_path=${node.original._path}`; }else{ @@ -945,6 +944,7 @@ module.exports = View.extend({ domain: this.config.getDomainContext, workflow: this.config.getWorkflowContext, notebook: this.config.getNotebookContext + sbmlModel: } return contextMenus[node.type](this, node); } diff --git a/stochss/handlers/util/stochss_folder.py b/stochss/handlers/util/stochss_folder.py index a479dc0191..36ce554756 100644 --- a/stochss/handlers/util/stochss_folder.py +++ b/stochss/handlers/util/stochss_folder.py @@ -73,7 +73,7 @@ def __get_rmt_upld_path(self, file): def __build_jstree_node(self, path, file): - types = {"mdl":"nonspatial", "smdl":"spatial", "sbml":"sbml-model", "ipynb":"notebook", + types = {"mdl":"nonspatial", "smdl":"spatial", "sbml":"sbmlModel", "ipynb":"notebook", "wkfl":"workflow", "proj":"project", "wkgp":"workflowGroup", "domn":"domain"} _path = file if self.path == "none" else os.path.join(self.path, file) ext = file.split('.').pop() if "." in file else None From adcb1d423e4aab247a1e7b64afd94424894e96d4 Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Wed, 1 Sep 2021 13:08:46 -0400 Subject: [PATCH 133/186] Fixed spatial model context menu. Moved context menu for sbml models. --- client/file-config.js | 32 ++++++++++++++++++++-- client/pages/file-browser.js | 44 +++++++++++++++---------------- client/project-config.js | 31 ++++++++++++++++++++-- client/views/file-browser-view.js | 44 +++++++++++++++---------------- client/views/jstree-view.js | 30 ++++++++++++++++++--- 5 files changed, 130 insertions(+), 51 deletions(-) diff --git a/client/file-config.js b/client/file-config.js index 8f1e3443c7..403a027dd6 100644 --- a/client/file-config.js +++ b/client/file-config.js @@ -33,7 +33,7 @@ let doubleClick = (view, e) => { }else if(node.type === "notebook"){ view.openNotebook(node.original._path); }else if(node.type === "sbmlModel"){ - window.open(path.join(app.getBasePath(), "edit", node.original._path), '_blank'); + view.openSBML(node.original._path); }else if(node.type === "project"){ view.openProject(node.original._path); }else if(node.type === "workflow"){ @@ -48,6 +48,9 @@ let doubleClick = (view, e) => { } let getDomainContext = (view, node) => { + if(node.original._path.split("/")[0] === "trash") { // item in trash + return {delete: view.getDeleteContext(node, "directory")}; + } let downloadOptions = {dataType: "json", identifier: "spatial-model/load-domain"}; return { open: view.buildContextBaseWithClass({ @@ -109,6 +112,9 @@ let getModelContext = (view, node) => { } let getNotebookContext = (view, node) => { + if(node.original._path.split("/")[0] === "trash") { // item in trash + return {delete: view.getDeleteContext(node, "directory")}; + } let open = view.getOpenNotebookContext(node); let downloadOptions = {dataType: "json", identifier: "file/json-data"}; let download = view.getDownloadContext(node, downloadOptions); @@ -169,6 +175,21 @@ let getRootContext = (view, node) => { } } +let getSBMLContext = (view, node) => { + if(node.original._path.split("/")[0] === "trash") { // item in trash + return {delete: view.getDeleteContext(node, "directory")}; + } + let downloadOptions = {dataType: "plain-text", identifier: "file/download"}; + return { + open: view.getOpenSBMLContext(node), + convert: view.getSBMLConvertContext(node, "sbml/to-model"), + download: view.getDownloadContext(node, downloadOptions), + rename: view.getRenameContext(node), + duplicate: view.getDuplicateContext(node, "file/duplicate"), + moveToTrash: view.getMoveToTrashContext(node, "sbml model") + } +} + let getSpatialModelContext = (view, node) => { if(node.original._path.split("/")[0] === "trash") { // project in trash return {delete: view.getDeleteContext(node, "spatial model")}; @@ -176,7 +197,12 @@ let getSpatialModelContext = (view, node) => { let downloadOptions = {dataType: "json", identifier: "file/json-data"}; return { edit: view.getEditModelContext(node), - newWorkflow: view.getFullNewWorkflowContext(node), + newWorkflow: view.buildContextWithSubmenus({ + label: "New Workflow", + submenu: { + jupyterNotebook: view.getNotebookNewWorkflowContext(node) + } + }), convert: view.getSmdlConvertContext(node, "spatial/to-model"), download: view.getDownloadContext(node, downloadOptions), rename: view.getRenameContext(node), @@ -342,11 +368,13 @@ module.exports = { getNotebookContext: getNotebookContext, getProjectContext: getProjectContext, getRootContext: getRootContext, + getSBMLContext: getSBMLContext, getSpatialModelContext: getSpatialModelContext, getWorkflowContext: getWorkflowContext, // getWorflowGroupContext: getOtherContext, move: move, setup: setup, + toModel: toModel, toSBML: toSBML, toSpatial: toSpatial, types: types, diff --git a/client/pages/file-browser.js b/client/pages/file-browser.js index 277410dc88..0654b0e78c 100644 --- a/client/pages/file-browser.js +++ b/client/pages/file-browser.js @@ -1383,25 +1383,25 @@ let FileBrowser = PageView.extend({ // } // } // Specific to sbml files - let sbml = { - "Convert" : { - "label" : "Convert", - "_disabled" : false, - "separator_before" : false, - "separator_after" : false, - "submenu" : { - "Convert to Model" : { - "label" : "To Model", - "_disabled" : false, - "separator_before" : false, - "separator_after" : false, - "action" : function (data) { - self.toModel(o, "SBML"); - } - } - } - } - } + // let sbml = { + // "Convert" : { + // "label" : "Convert", + // "_disabled" : false, + // "separator_before" : false, + // "separator_after" : false, + // "submenu" : { + // "Convert to Model" : { + // "label" : "To Model", + // "_disabled" : false, + // "separator_before" : false, + // "separator_after" : false, + // "action" : function (data) { + // self.toModel(o, "SBML"); + // } + // } + // } + // } + // } //Specific to zip archives let extractAll = { "extractAll" : { @@ -1461,9 +1461,9 @@ let FileBrowser = PageView.extend({ if (o.type === 'other') { // other nodes return $.extend(open, common) } - if (o.type === 'sbml-model') { // sbml model node - return $.extend(open, sbml, common) - } + // if (o.type === 'sbml-model') { // sbml model node + // return $.extend(open, sbml, common) + // } // if (o.type === "domain") { // domain node // return $.extend(open, common) // } diff --git a/client/project-config.js b/client/project-config.js index e13fdd55b7..675decfd36 100644 --- a/client/project-config.js +++ b/client/project-config.js @@ -34,7 +34,7 @@ let doubleClick = (view, e) => { }else if(node.type === "notebook"){ view.openNotebook(node.original._path); }else if(node.type === "sbmlModel"){ - window.open(path.join(app.getBasePath(), "edit", node.original._path), '_blank'); + view.openSBML(node.original._path); }else if(node.type === "workflow"){ view.openWorkflow(node.original._path); }else if(node.type === "domain") { @@ -63,6 +63,9 @@ let extract = (view, node, type) => { } let getDomainContext = (view, node) => { + if(node.original._path.includes(".proj/trash/")) { //item in trash + return {delete: view.getDeleteContext(node, "directory")}; + } let downloadOptions = {dataType: "json", identifier: "spatial-model/load-domain"}; return { open: view.buildContextBaseWithClass({ @@ -126,6 +129,9 @@ let getModelContext = (view, node) => { } let getNotebookContext = (view, node) => { + if(node.original._path.includes(".proj/trash/")) { //item in trash + return {delete: view.getDeleteContext(node, "directory")}; + } let open = view.getOpenNotebookContext(node); let downloadOptions = {dataType: "json", identifier: "file/json-data"}; let download = view.getDownloadContext(node, downloadOptions); @@ -158,6 +164,21 @@ let getRootContext = (view, node) => { } } +let getSBMLContext = (view, node) => { + if(node.original._path.includes(".proj/trash/")) { //item in trash + return {delete: view.getDeleteContext(node, "sbml model")}; + } + let downloadOptions = {dataType: "plain-text", identifier: "file/download"}; + return { + open: view.getOpenSBMLContext(node), + convert: view.getSBMLConvertContext(node, "sbml/to-model"), + download: view.getDownloadContext(node, downloadOptions), + rename: view.getRenameContext(node), + duplicate: view.getDuplicateContext(node, "file/duplicate"), + moveToTrash: view.getMoveToTrashContext(node, "sbml model") + } +} + let getSpatialModelContext = (view, node) => { if(node.original._path.includes(".proj/trash/")) { //item in trash return {delete: view.getDeleteContext(node, "spatial model")}; @@ -166,7 +187,12 @@ let getSpatialModelContext = (view, node) => { return { edit: view.getEditModelContext(node), extract: getExtractContext(view, node), - newWorkflow: view.getFullNewWorkflowContext(node), + newWorkflow: view.buildContextWithSubmenus({ + label: "New Workflow", + submenu: { + jupyterNotebook: view.getNotebookNewWorkflowContext(node) + } + }), convert: view.getSmdlConvertContext(node, "spatial/to-model"), download: view.getDownloadContext(node, downloadOptions), rename: view.getRenameContext(node), @@ -390,6 +416,7 @@ module.exports = { getNotebookContext: getNotebookContext, // getProjectContext: getOtherContext, getRootContext: getRootContext, + getSBMLContext: getSBMLContext, getSpatialModelContext: getSpatialModelContext, getWorkflowContext: getWorkflowContext, getWorkflowGroupContext: getWorkflowGroupContext, diff --git a/client/views/file-browser-view.js b/client/views/file-browser-view.js index 1e85eb0089..148f2e20eb 100644 --- a/client/views/file-browser-view.js +++ b/client/views/file-browser-view.js @@ -1358,25 +1358,25 @@ let Model = require('../models/model'); // }, // } // Specific to sbml files - let sbml = { - "Convert" : { - "label" : "Convert", - "_disabled" : false, - "separator_before" : false, - "separator_after" : false, - "submenu" : { - "Convert to Model" : { - "label" : "To Model", - "_disabled" : false, - "separator_before" : false, - "separator_after" : false, - "action" : function (data) { - self.toModel(o, "SBML"); - } - } - } - } - } + // let sbml = { + // "Convert" : { + // "label" : "Convert", + // "_disabled" : false, + // "separator_before" : false, + // "separator_after" : false, + // "submenu" : { + // "Convert to Model" : { + // "label" : "To Model", + // "_disabled" : false, + // "separator_before" : false, + // "separator_after" : false, + // "action" : function (data) { + // self.toModel(o, "SBML"); + // } + // } + // } + // } + // } // common to all type except root and trash // let common = { // "Rename" : { @@ -1466,9 +1466,9 @@ let Model = require('../models/model'); if (o.type === 'other') { // other nodes return $.extend(open, download, common) } - if (o.type === 'sbml-model') { // sbml model node - return $.extend(open, sbml, common) - } + // if (o.type === 'sbml-model') { // sbml model node + // return $.extend(open, sbml, common) + // } // if (o.type === "domain") { // domain node // return $.extend(open, common) // } diff --git a/client/views/jstree-view.js b/client/views/jstree-view.js index 27283c8dad..283dafc7d4 100644 --- a/client/views/jstree-view.js +++ b/client/views/jstree-view.js @@ -541,6 +541,14 @@ module.exports = View.extend({ } }); }, + getOpenSBMLContext: function (node) { + return this.buildContextBaseWithClass({ + label: "Open", + action: (data) => { + this.openSBML(node.original._path); + } + }); + }, getOpenWorkflowContext: function (node) { return this.buildContextBaseWithClass({ label: "Open", @@ -621,6 +629,19 @@ module.exports = View.extend({ } }); }, + getSBMLConvertContext: function (node, identifier) { + return this.buildContextWithSubmenus({ + label: "Convert", + submenu: { + convertToModel: this.buildContextBase({ + label: "To Model", + action: (data) => { + this.config.toModel(this, node, identifier); + } + }) + } + }); + }, getSmdlConvertContext: function (node, identifier) { return this.buildContextWithSubmenus({ label: "Convert", @@ -799,7 +820,10 @@ module.exports = View.extend({ window.location.href = path.join(app.getBasePath(), "stochss/models/edit") + queryStr; }, openNotebook: function (notebookPath) { - window.open(path.join(app.getBasePath(), "notebooks", node.original._path), '_blank'); + window.open(path.join(app.getBasePath(), "notebooks", notebookPath), '_blank'); + }, + openSBML: function (sbmlPath) { + window.open(path.join(app.getBasePath(), "edit", sbmlPath), '_blank'); }, openProject: function (projectPath) { let queryStr = `?path=${projectPath}`; @@ -943,8 +967,8 @@ module.exports = View.extend({ spatial: this.config.getSpatialModelContext, domain: this.config.getDomainContext, workflow: this.config.getWorkflowContext, - notebook: this.config.getNotebookContext - sbmlModel: + notebook: this.config.getNotebookContext, + sbmlModel: this.config.getSBMLContext } return contextMenus[node.type](this, node); } From cfd274f08c4b3df0a28ddd7050562592557d2ef9 Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Wed, 1 Sep 2021 14:51:10 -0400 Subject: [PATCH 134/186] Moved context menu for other files. --- client/file-config.js | 31 ++++++- client/pages/file-browser.js | 136 ++++++++++++++--------------- client/project-config.js | 31 ++++++- client/views/file-browser-view.js | 140 +++++++++++++++--------------- client/views/jstree-view.js | 41 ++++++++- 5 files changed, 232 insertions(+), 147 deletions(-) diff --git a/client/file-config.js b/client/file-config.js index 403a027dd6..fc2bca5c22 100644 --- a/client/file-config.js +++ b/client/file-config.js @@ -41,8 +41,7 @@ let doubleClick = (view, e) => { }else if(node.type === "domain") { view.openDomain(node.original._path); }else if(node.type === "other"){ - let openPath = path.join(app.getBasePath(), "view", node.original._path); - window.open(openPath, "_blank"); + view.openFile(node.original._path); } } } @@ -135,6 +134,31 @@ let getNotebookContext = (view, node) => { } } +let getOtherContext = (view, node) => { + if(node.original._path.split("/")[0] === "trash") { // project in trash + return {delete: view.getDeleteContext(node, "file")}; + } + let open = view.getOpenFileContext(node); + let downloadOptions = {dataType: "zip", identifier: "file/download-zip"}; + let options = {asZip: true}; + let download = view.getDownloadContext(node, downloadOptions, options); + let rename = view.getRenameContext(node); + let duplicate = view.getDuplicateContext(node, "file/duplicate"); + let moveToTrash = view.getMoveToTrashContext(node, "file"); + if(node.text.endsWith(".zip")) { + return { + open: open, + extractAll: view.getExtractAllContext(node), + download: download, rename: rename, + duplicate: duplicate, moveToTrash: moveToTrash + } + } + return { + open: open, download: download, rename: rename, + duplicate: duplicate, moveToTrash: moveToTrash + } +} + let getOpenProjectContext = (view, node) => { return view.buildContextBaseWithClass({ label: "Open", @@ -366,12 +390,13 @@ module.exports = { getFolderContext: getFolderContext, getModelContext: getModelContext, getNotebookContext: getNotebookContext, + getOtherContext: getOtherContext, getProjectContext: getProjectContext, getRootContext: getRootContext, getSBMLContext: getSBMLContext, getSpatialModelContext: getSpatialModelContext, getWorkflowContext: getWorkflowContext, - // getWorflowGroupContext: getOtherContext, + getWorflowGroupContext: getOtherContext, move: move, setup: setup, toModel: toModel, diff --git a/client/pages/file-browser.js b/client/pages/file-browser.js index 0654b0e78c..ff9a028002 100644 --- a/client/pages/file-browser.js +++ b/client/pages/file-browser.js @@ -916,24 +916,24 @@ let FileBrowser = PageView.extend({ // } // }); // }, - extractAll: function (o) { - let self = this; - let queryStr = "?path=" + o.original._path; - let endpoint = path.join(app.getApiPath(), "file/unzip") + queryStr; - app.getXHR(endpoint, { - success: function (err, response, body) { - let node = $('#models-jstree').jstree().get_node(o.parent); - if(node.type === "root"){ - self.refreshJSTree(); - }else{ - $('#models-jstree').jstree().refresh_node(node); - } - }, - error: function (err, response, body) { - let modal = $(modals.newProjectModelErrorHtml(body.Reason, body.message)).modal(); - } - }); - }, + // extractAll: function (o) { + // let self = this; + // let queryStr = "?path=" + o.original._path; + // let endpoint = path.join(app.getApiPath(), "file/unzip") + queryStr; + // app.getXHR(endpoint, { + // success: function (err, response, body) { + // let node = $('#models-jstree').jstree().get_node(o.parent); + // if(node.type === "root"){ + // self.refreshJSTree(); + // }else{ + // $('#models-jstree').jstree().refresh_node(node); + // } + // }, + // error: function (err, response, body) { + // let modal = $(modals.newProjectModelErrorHtml(body.Reason, body.message)).modal(); + // } + // }); + // }, // moveToTrash: function (o) { // if(document.querySelector('#moveToTrashConfirmModal')) { // document.querySelector('#moveToTrashConfirmModal').remove(); @@ -998,9 +998,9 @@ let FileBrowser = PageView.extend({ // } // }); // }, - setupJstree: function () { - var self = this; - $.jstree.defaults.contextmenu.items = (o, cb) => { + // setupJstree: function () { + // var self = this; + // $.jstree.defaults.contextmenu.items = (o, cb) => { // let optionsButton = $(self.queryByHook("options-for-node")) // if(!self.nodeForContextMenu){ // optionsButton.prop('disabled', false) @@ -1287,34 +1287,34 @@ let FileBrowser = PageView.extend({ // } // } // For notebooks, workflows, sbml models, and other files - let open = { - "Open" : { - "label" : "Open", - "_disabled" : false, - "_class" : "font-weight-bolder", - "separator_before" : false, - "separator_after" : true, - "action" : function (data) { - if(nodeType === "workflow"){ - // window.location.href = path.join(app.getBasePath(), "stochss/workflow/edit")+"?path="+o.original._path+"&type=none"; - // }else if(nodeType === "project"){ - // window.location.href = path.join(app.getBasePath(), "stochss/project/manager")+"?path="+o.original._path - }else if(nodeType === "domain") { - let queryStr = "?domainPath=" + o.original._path - window.location.href = path.join(app.getBasePath(), "stochss/domain/edit") + queryStr - }else{ - if(nodeType === "notebook") { - var identifier = "notebooks" - }else if(nodeType === "sbml-model") { - var identifier = "edit" - }else{ - var identifier = "view" - } - window.open(path.join(app.getBasePath(), identifier, o.original._path)); - } - } - } - } + // let open = { + // "Open" : { + // "label" : "Open", + // "_disabled" : false, + // "_class" : "font-weight-bolder", + // "separator_before" : false, + // "separator_after" : true, + // "action" : function (data) { + // if(nodeType === "workflow"){ + // // window.location.href = path.join(app.getBasePath(), "stochss/workflow/edit")+"?path="+o.original._path+"&type=none"; + // // }else if(nodeType === "project"){ + // // window.location.href = path.join(app.getBasePath(), "stochss/project/manager")+"?path="+o.original._path + // }else if(nodeType === "domain") { + // let queryStr = "?domainPath=" + o.original._path + // window.location.href = path.join(app.getBasePath(), "stochss/domain/edit") + queryStr + // }else{ + // if(nodeType === "notebook") { + // var identifier = "notebooks" + // }else if(nodeType === "sbml-model") { + // var identifier = "edit" + // }else{ + // var identifier = "view" + // } + // window.open(path.join(app.getBasePath(), identifier, o.original._path)); + // } + // } + // } + // } // let project = { // "Add Model" : { // "label" : "Add Model", @@ -1403,17 +1403,17 @@ let FileBrowser = PageView.extend({ // } // } //Specific to zip archives - let extractAll = { - "extractAll" : { - "label" : "Extract All", - "_disabled" : false, - "separator_before" : false, - "separator_after" : false, - "action" : function (data) { - self.extractAll(o); - } - } - } + // let extractAll = { + // "extractAll" : { + // "label" : "Extract All", + // "_disabled" : false, + // "separator_before" : false, + // "separator_after" : false, + // "action" : function (data) { + // self.extractAll(o); + // } + // } + // } // let notebook = { // "publish" : { // "label" : "Publish", @@ -1449,25 +1449,25 @@ let FileBrowser = PageView.extend({ // if (o.type === 'workflow') { // workflow node // return $.extend(open, workflow, common) // } - if (o.text.endsWith(".zip")) { // zip archive node - return $.extend(open, extractAll, common) - } + // if (o.text.endsWith(".zip")) { // zip archive node + // return $.extend(open, extractAll, common) + // } // if (o.type === 'notebook') { // notebook node // if(app.getBasePath() === "/") { // return $.extend(open, common) // } // return $.extend(open, notebook, common) // } - if (o.type === 'other') { // other nodes - return $.extend(open, common) - } + // if (o.type === 'other') { // other nodes + // return $.extend(open, common) + // } // if (o.type === 'sbml-model') { // sbml model node // return $.extend(open, sbml, common) // } // if (o.type === "domain") { // domain node // return $.extend(open, common) // } - } + // } // $(document).on('shown.bs.modal', function (e) { // $('[autofocus]', e.target).focus(); // }); @@ -1522,7 +1522,7 @@ let FileBrowser = PageView.extend({ // } // } // }); - } + // } }); initPage(FileBrowser); diff --git a/client/project-config.js b/client/project-config.js index 675decfd36..12e17b3d52 100644 --- a/client/project-config.js +++ b/client/project-config.js @@ -40,8 +40,7 @@ let doubleClick = (view, e) => { }else if(node.type === "domain") { view.openDomain(node.original._path); }else if(node.type === "other"){ - let openPath = path.join(app.getBasePath(), "view", node.original._path); - window.open(openPath, "_blank"); + view.openFile(node.original._path); } } } @@ -152,6 +151,31 @@ let getNotebookContext = (view, node) => { } } +let getOtherContext = (view, node) => { + if(node.original._path.includes(".proj/trash/")) { //item in trash + return {delete: view.getDeleteContext(node, "file")}; + } + let open = view.getOpenFileContext(node); + let downloadOptions = {dataType: "zip", identifier: "file/download-zip"}; + let options = {asZip: true}; + let download = view.getDownloadContext(node, downloadOptions, options); + let rename = view.getRenameContext(node); + let duplicate = view.getDuplicateContext(node, "file/duplicate"); + let moveToTrash = view.getMoveToTrashContext(node, "file"); + if(node.text.endsWith(".zip")) { + return { + open: open, + extractAll: view.getExtractAllContext(node), + download: download, rename: rename, + duplicate: duplicate, moveToTrash: moveToTrash + } + } + return { + open: open, download: download, rename: rename, + duplicate: duplicate, moveToTrash: moveToTrash + } +} + let getRootContext = (view, node) => { return { refresh: view.getRefreshContext(node), @@ -414,7 +438,8 @@ module.exports = { getFolderContext: getFolderContext, getModelContext: getModelContext, getNotebookContext: getNotebookContext, - // getProjectContext: getOtherContext, + getOtherContext: getOtherContext, + getProjectContext: getOtherContext, getRootContext: getRootContext, getSBMLContext: getSBMLContext, getSpatialModelContext: getSpatialModelContext, diff --git a/client/views/file-browser-view.js b/client/views/file-browser-view.js index 148f2e20eb..988ca32f79 100644 --- a/client/views/file-browser-view.js +++ b/client/views/file-browser-view.js @@ -18,13 +18,13 @@ along with this program. If not, see . // let jstree = require('jstree'); // let path = require('path'); -let $ = require('jquery'); -let _ = require('underscore'); +// let $ = require('jquery'); +// let _ = require('underscore'); //support files // let app = require('../app'); -let modals = require('../modals'); +// let modals = require('../modals'); //models -let Model = require('../models/model'); +// let Model = require('../models/model'); //views // let View = require('ampersand-view'); //templates @@ -181,7 +181,7 @@ let Model = require('../models/model'); // }, // render: function () { // View.prototype.render.apply(this, arguments) - var self = this; + // var self = this; // this.nodeForContextMenu = ""; // this.jstreeIsLoaded = false // window.addEventListener('pageshow', function (e) { @@ -943,24 +943,24 @@ let Model = require('../models/model'); // } // }); // }, - extractAll: function (o) { - let self = this; - let queryStr = "?path=" + o.original._path; - let endpoint = path.join(app.getApiPath(), "file/unzip") + queryStr; - app.getXHR(endpoint, { - success: function (err, response, body) { - let node = $('#models-jstree-view').jstree().get_node(o.parent); - if(node.type === "root"){ - self.refreshJSTree(); - }else{ - $('#models-jstree-view').jstree().refresh_node(node); - } - }, - error: function (err, response, body) { - let modal = $(modals.newProjectModelErrorHtml(body.Reason, body.message)).modal(); - } - }); - }, + // extractAll: function (o) { + // let self = this; + // let queryStr = "?path=" + o.original._path; + // let endpoint = path.join(app.getApiPath(), "file/unzip") + queryStr; + // app.getXHR(endpoint, { + // success: function (err, response, body) { + // let node = $('#models-jstree-view').jstree().get_node(o.parent); + // if(node.type === "root"){ + // self.refreshJSTree(); + // }else{ + // $('#models-jstree-view').jstree().refresh_node(node); + // } + // }, + // error: function (err, response, body) { + // let modal = $(modals.newProjectModelErrorHtml(body.Reason, body.message)).modal(); + // } + // }); + // }, // publishNotebookPresentation: function (o) { // let queryStr = "?path=" + o.original._path; // let endpoint = path.join(app.getApiPath(), "notebook/presentation") + queryStr; @@ -989,7 +989,7 @@ let Model = require('../models/model'); // }); // }, // setupJstree: function () { - var self = this; + // var self = this; // $.jstree.defaults.contextmenu.items = (o, cb) => { // let nodeType = o.original.type // let zipTypes = ["workflow", "folder", "other", "root", "workflow-group"] @@ -1012,34 +1012,34 @@ let Model = require('../models/model'); // } // } // For notebooks, workflows, sbml models, and other files - let open = { - "Open" : { - "label" : "Open", - "_disabled" : false, - "_class" : "font-weight-bolder", - "separator_before" : false, - "separator_after" : true, - "action" : function (data) { - if(nodeType === "workflow"){ - window.location.href = path.join(app.getBasePath(), "stochss/workflow/edit")+"?path="+o.original._path+"&type=none"; - // }else if(nodeType === "project"){ - // window.location.href = path.join(app.getBasePath(), "stochss/project/manager")+"?path="+o.original._path - }else if(nodeType === "domain") { - let queryStr = "?domainPath=" + o.original._path - window.location.href = path.join(app.getBasePath(), "stochss/domain/edit") + queryStr - }else{ - if(nodeType === "notebook") { - var identifier = "notebooks" - }else if(nodeType === "sbml-model") { - var identifier = "edit" - }else{ - var identifier = "view" - } - window.open(path.join(app.getBasePath(), identifier, o.original._path)); - } - } - } - } + // let open = { + // "Open" : { + // "label" : "Open", + // "_disabled" : false, + // "_class" : "font-weight-bolder", + // "separator_before" : false, + // "separator_after" : true, + // "action" : function (data) { + // if(nodeType === "workflow"){ + // window.location.href = path.join(app.getBasePath(), "stochss/workflow/edit")+"?path="+o.original._path+"&type=none"; + // // }else if(nodeType === "project"){ + // // window.location.href = path.join(app.getBasePath(), "stochss/project/manager")+"?path="+o.original._path + // }else if(nodeType === "domain") { + // let queryStr = "?domainPath=" + o.original._path + // window.location.href = path.join(app.getBasePath(), "stochss/domain/edit") + queryStr + // }else{ + // if(nodeType === "notebook") { + // var identifier = "notebooks" + // }else if(nodeType === "sbml-model") { + // var identifier = "edit" + // }else{ + // var identifier = "view" + // } + // window.open(path.join(app.getBasePath(), identifier, o.original._path)); + // } + // } + // } + // } // project contect menu option // let project = { // "Add_Model" : { @@ -1408,17 +1408,17 @@ let Model = require('../models/model'); // } // } //Specific to zip archives - let extractAll = { - "extractAll" : { - "label" : "Extract All", - "_disabled" : false, - "separator_before" : false, - "separator_after" : false, - "action" : function (data) { - self.extractAll(o); - } - } - } + // let extractAll = { + // "extractAll" : { + // "label" : "Extract All", + // "_disabled" : false, + // "separator_before" : false, + // "separator_after" : false, + // "action" : function (data) { + // self.extractAll(o); + // } + // } + // } // let notebook = { // "publish" : { // "label" : "Publish", @@ -1454,25 +1454,25 @@ let Model = require('../models/model'); // if (o.type === 'workflow') { // workflow node // return $.extend(open, workflow, downloadWCombine, common) // } - if (o.text.endsWith(".zip")) { // zip archove node - return $.extend(open, extractAll, download, common) - } + // if (o.text.endsWith(".zip")) { // zip archove node + // return $.extend(open, extractAll, download, common) + // } // if (o.type === 'notebook') { // notebook node // if(app.getBasePath() === "/") { // return $.extend(open, download, common) // } // return $.extend(open, notebook, download, common) // } - if (o.type === 'other') { // other nodes - return $.extend(open, download, common) - } + // if (o.type === 'other') { // other nodes + // return $.extend(open, download, common) + // } // if (o.type === 'sbml-model') { // sbml model node // return $.extend(open, sbml, common) // } // if (o.type === "domain") { // domain node // return $.extend(open, common) // } - } + // } // $(document).ready(function () { // $(document).on('shown.bs.modal', function (e) { // $('[autofocus]', e.target).focus(); diff --git a/client/views/jstree-view.js b/client/views/jstree-view.js index 283dafc7d4..88af4ca913 100644 --- a/client/views/jstree-view.js +++ b/client/views/jstree-view.js @@ -343,6 +343,19 @@ module.exports = View.extend({ let endpoint = path.join(app.getBasePath(), "/files", targetPath); window.open(endpoint, "_blank"); }, + extractAll: function (node) { + let queryStr = `?path=${o.original._path}`; + let endpoint = path.join(app.getApiPath(), "file/unzip") + queryStr; + app.getXHR(endpoint, { + success: (err, response, body) => { + let par = $('#files-jstree').jstree().get_node(node.parent); + this.refreshJSTree(par); + }, + error: (err, response, body) => { + this.reportError(body); + } + }); + }, getAddModelContext: function (node) { let newModel = this.getNewModelContext(node, true); return this.buildContextWithSubmenus({ @@ -412,7 +425,7 @@ module.exports = View.extend({ getEditModelContext: function (node) { return this.buildContextBaseWithClass({ label: "Edit", - action: function (data) { + action: (data) => { this.openModel(node.original._path); } }); @@ -453,6 +466,14 @@ module.exports = View.extend({ } }); }, + getExtractAllContext: function (node) { + return this.buildContextBase({ + label: "Extract All", + action: (data) => { + this.extractAll(node); + } + }); + }, getFileUploadContext: function (node, inProject, {label="File"}={}) { let dirname = node.original._path === "/" ? "" : node.original._path; return this.buildContextBase({ @@ -533,6 +554,14 @@ module.exports = View.extend({ } }); }, + getOpenFileContext: function (node) { + return this.buildContextBaseWithClass({ + label: "Open", + action: (data) => { + this.openFile(node); + } + }); + }, getOpenNotebookContext: function (node) { return this.buildContextBaseWithClass({ label: "Open", @@ -815,6 +844,9 @@ module.exports = View.extend({ let queryStr = `?domainPath=${domainPath}`; window.location.href = path.join(app.getBasePath(), "stochss/domain/edit") + queryStr }, + openFile: function (filePath) { + window.open(path.join(app.getBasePath(), "view", filePath), "_blank"); + }, openModel: function (modelPath) { let queryStr = `?path=${modelPath}`; window.location.href = path.join(app.getBasePath(), "stochss/models/edit") + queryStr; @@ -968,9 +1000,12 @@ module.exports = View.extend({ domain: this.config.getDomainContext, workflow: this.config.getWorkflowContext, notebook: this.config.getNotebookContext, - sbmlModel: this.config.getSBMLContext + sbmlModel: this.config.getSBMLContext, + } + if(Object.keys(contextMenus).includes(node.type)){ + return contextMenus[node.type](this, node); } - return contextMenus[node.type](this, node); + return this.config.getOtherContext(this, node); } $(() => { $(document).on('shown.bs.modal', (e) => { From bdb2fbd29d6f5ef5397539b53007f7bbfa39f09d Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Thu, 2 Sep 2021 09:34:03 -0400 Subject: [PATCH 135/186] Fixed issues caused by refactor. --- client/file-config.js | 28 ++++++++++++++-------------- client/project-config.js | 24 ++++++++++++++++-------- client/views/jstree-view.js | 36 ++++++++++++++++++++++++------------ 3 files changed, 54 insertions(+), 34 deletions(-) diff --git a/client/file-config.js b/client/file-config.js index fc2bca5c22..24c8c9b30f 100644 --- a/client/file-config.js +++ b/client/file-config.js @@ -20,6 +20,7 @@ let $ = require('jquery'); let path = require('path'); //support files let app = require('./app'); +let modals = require('./modals'); let contextZipTypes = ["workflow", "folder", "other", "project", "root"]; @@ -41,7 +42,11 @@ let doubleClick = (view, e) => { }else if(node.type === "domain") { view.openDomain(node.original._path); }else if(node.type === "other"){ - view.openFile(node.original._path); + if(node.text.endsWith(".zip")) { + view.extractAll(node); + }else{ + view.openFile(node.original._path); + } } } } @@ -147,7 +152,6 @@ let getOtherContext = (view, node) => { let moveToTrash = view.getMoveToTrashContext(node, "file"); if(node.text.endsWith(".zip")) { return { - open: open, extractAll: view.getExtractAllContext(node), download: download, rename: rename, duplicate: duplicate, moveToTrash: moveToTrash @@ -248,11 +252,11 @@ let getWorkflowContext = (view, node) => { document.querySelector("#successModal").remove(); } let message = `The model for ${body.File} is located here: ${body.mdlPath}`; - let modal = $(modals.successHtml(title, message)).modal(); + let modal = $(modals.successHtml(message, {title: title})).modal(); } }} if(!node.original._newFormat) { - options['timeStamp'] = view.getTimeStamp(); + duplicateOptions['timeStamp'] = view.getTimeStamp(); } let downloadOptions = {dataType: "zip", identifier: "file/download-zip"}; let options = {asZip: true}; @@ -275,14 +279,10 @@ let move = (view, par, node) => { app.getXHR(endpoint, { success: (err, response, body) => { node.original._path = path.join(newDir, file); - if(node.type === "folder") { - view.refreshJSTree(node); - }else if(newDir.endsWith("trash")) { + if(newDir.endsWith("trash")) { $(view.queryByHook('empty-trash')).prop('disabled', false); - view.refreshJSTree(par); - }else if(oldPath.split("/").includes("trash")) { - view.refreshJSTree(par); } + view.refreshJSTree(par); }, error: (err, response, body) => { body = JSON.parse(body); @@ -300,8 +300,8 @@ let toModel = (view, node, identifier) => { let queryStr = `?path=${node.original._path}`; let endpoint = path.join(app.getApiPath(), identifier) + queryStr; app.getXHR(endpoint, { - success: function (err, response, body) { - let par = $('#models-jstree').jstree().get_node(node.parent); + success: (err, response, body) => { + let par = $('#files-jstree').jstree().get_node(node.parent); view.refreshJSTree(par); view.selectNode(par, body.File); if(identifier.startsWith("sbml") && body.errors.length > 0){ @@ -321,7 +321,7 @@ let toSBML = (view, node) => { success: (err, response, body) => { let par = $('#files-jstree').jstree().get_node(node.parent); view.refreshJSTree(par); - view.selectNode(node, body.File); + view.selectNode(par, body.File); } }); } @@ -333,7 +333,7 @@ let toSpatial = (view, node) => { success: (err, response, body) => { let par = $('#files-jstree').jstree().get_node(node.parent); view.refreshJSTree(par); - view.selectNode(node, body.File); + view.selectNode(par, body.File); } }); } diff --git a/client/project-config.js b/client/project-config.js index 12e17b3d52..edf1cef1b0 100644 --- a/client/project-config.js +++ b/client/project-config.js @@ -40,7 +40,11 @@ let doubleClick = (view, e) => { }else if(node.type === "domain") { view.openDomain(node.original._path); }else if(node.type === "other"){ - view.openFile(node.original._path); + if(node.text.endsWith(".zip")) { + view.extractAll(node); + }else{ + view.openFile(node.original._path); + } } } } @@ -164,7 +168,6 @@ let getOtherContext = (view, node) => { let moveToTrash = view.getMoveToTrashContext(node, "file"); if(node.text.endsWith(".zip")) { return { - open: open, extractAll: view.getExtractAllContext(node), download: download, rename: rename, duplicate: duplicate, moveToTrash: moveToTrash @@ -210,7 +213,7 @@ let getSpatialModelContext = (view, node) => { let downloadOptions = {dataType: "json", identifier: "file/json-data"}; return { edit: view.getEditModelContext(node), - extract: getExtractContext(view, node), + extract: getExtractContext(view, node, "model"), newWorkflow: view.buildContextWithSubmenus({ label: "New Workflow", submenu: { @@ -238,7 +241,7 @@ let getWorkflowContext = (view, node) => { document.querySelector("#successModal").remove(); } let message = `The model for ${body.File} is located here: ${body.mdlPath}`; - let modal = $(modals.successHtml(title, message)).modal(); + let modal = $(modals.successHtml(message, {title: title})).modal(); } }} if(!node.original._newFormat) { @@ -261,7 +264,8 @@ let getWorkflowGroupContext = (view, node) => { } return { refresh: view.getRefreshContext(node), - download: view.getDownloadWCombineContext(node) + download: view.getDownloadWCombineContext(node), + moveToTrash: view.getMoveToTrashContext(node, "workflow group") } } @@ -279,6 +283,7 @@ let move = (view, par, node) => { }else if(node.type !== "notebook" || node.original._path.includes(".wkgp") || newDir.includes(".wkgp")) { updateParent(view, node.type); } + view.refreshJSTree(par); }, error: (err, response, body) => { body = JSON.parse(body); @@ -309,7 +314,7 @@ let toModel = (view, node, identifier) => { } let modal = $(modals.sbmlToModelHtml(body.message, body.errors)).modal(); }else{ - view.updateParent("model"); + updateParent(view, "model"); } } }); @@ -336,7 +341,7 @@ let toSpatial = (view, node) => { let par = $('#files-jstree').jstree().get_node(node.parent); let grandPar = $('#files-jstree').jstree().get_node(par.parent); view.refreshJSTree(grandPar); - updateParent("spatial"); + updateParent(view, "spatial"); view.selectNode(grandPar, body.File.replace(".smdl", ".wkgp")); } }); @@ -363,7 +368,7 @@ let updateParent = (view, type) => { }else if(workflows.includes(type)) { view.parent.update("Workflow", "file-browser"); }else if(type === "workflowGroup") { - view.parent.update("Workflow-group", "file-browser"); + view.parent.update("WorkflowGroup", "file-browser"); }else if(type === "Archive") { view.parent.update(type, "file-browser"); } @@ -447,6 +452,9 @@ module.exports = { getWorkflowGroupContext: getWorkflowGroupContext, move: move, setup: setup, + toModel: toModel, + toSBML: toSBML, + toSpatial: toSpatial, types: types, updateParent: updateParent, validateMove: validateMove diff --git a/client/views/jstree-view.js b/client/views/jstree-view.js index 88af4ca913..fb7d2d1924 100644 --- a/client/views/jstree-view.js +++ b/client/views/jstree-view.js @@ -294,12 +294,18 @@ module.exports = View.extend({ let endpoint = path.join(app.getApiPath(), identifier) + queryStr; app.getXHR(endpoint, { success: (err, response, body) => { - let par = $('#files-jstree').jstree().get_node(node.parent); + var par = $('#files-jstree').jstree().get_node(node.parent); + if(this.root !== "none" && (["nonspatial", "spatial"].includes(node.type) || target === "wkfl_model")){ + par = $('#files-jstree').jstree().get_node(par.parent); + var file = body.File.replace(node.type === "spatial" ? ".smdl" : ".mdl", ".wkgp"); + }else{ + var file = body.File; + } this.refreshJSTree(par); if(cb) { - cb(body.File, body.mdlPath); + cb(body); } - this.selectNode(par, body.File); + this.selectNode(par, file); this.config.updateParent(this, node.type); } }); @@ -344,7 +350,7 @@ module.exports = View.extend({ window.open(endpoint, "_blank"); }, extractAll: function (node) { - let queryStr = `?path=${o.original._path}`; + let queryStr = `?path=${node.original._path}`; let endpoint = path.join(app.getApiPath(), "file/unzip") + queryStr; app.getXHR(endpoint, { success: (err, response, body) => { @@ -467,7 +473,7 @@ module.exports = View.extend({ }); }, getExtractAllContext: function (node) { - return this.buildContextBase({ + return this.buildContextBaseWithClass({ label: "Extract All", action: (data) => { this.extractAll(node); @@ -558,7 +564,7 @@ module.exports = View.extend({ return this.buildContextBaseWithClass({ label: "Open", action: (data) => { - this.openFile(node); + this.openFile(node.original._path); } }); }, @@ -635,7 +641,7 @@ module.exports = View.extend({ getPublishNotebookContext: function (node) { return this.buildContextBase({ label: "Publish", - action: function (data) { + action: (data) => { this.publishNotebookPresentation(node); } }); @@ -678,7 +684,7 @@ module.exports = View.extend({ submenu: { convertToModel: this.buildContextBase({ label: "To Model", - action: function (data) { + action: (data) => { this.config.toModel(this, node, identifier); } }) @@ -702,14 +708,14 @@ module.exports = View.extend({ edit: this.buildContextBase({ label: "Edit", disabled: (!node.original._newFormat && node.original._status !== "ready"), - action: function (data) { + action: (data) => { this.openWorkflowModel(node); } }), extract: this.buildContextBase({ label: "Extract", disabled: (node.original._newFormat && !node.original._hasJobs), - action: function (data) { + action: (data) => { this.duplicate(node, "workflow/duplicate", {target: "wkfl_model"}); } }) @@ -817,6 +823,7 @@ module.exports = View.extend({ always: (err, response, body) => { $(this.queryByHook('empty-trash')).prop('disabled', false); this.refreshJSTree(null); + this.config.updateParent(this, node.type); } }); }); @@ -934,10 +941,14 @@ module.exports = View.extend({ let endpoint = path.join(app.getApiPath(), "file/rename") + queryStr; app.getXHR(endpoint, { always: (err, response, body) => { - this.refreshJSTree(par); + if(this.root !== "none" && ["nonspatial", "spatial"].includes(node.type)){ + this.refreshJSTree(null); + }else{ + this.refreshJSTree(par); + } }, success: (err, response, body) => { - if(this.root !== "none") { + if(this.root !== "none" && node.type === "root") { this.openProject(body._path); }else if(body.changed) { nameWarning.text(body.message); @@ -946,6 +957,7 @@ module.exports = View.extend({ setTimeout(_.bind(this.hideNameWarning, this), 10000); } node.original._path = body._path; + this.config.updateParent(this, node.type); } }); } From 0a4ca708594f2c2b680ef5f97497995403fb3162 Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Thu, 2 Sep 2021 09:36:16 -0400 Subject: [PATCH 136/186] Removed the file-browser-view.js file. --- client/pages/project-manager.js | 3 +- client/views/file-browser-view.js | 1536 ----------------------------- 2 files changed, 1 insertion(+), 1538 deletions(-) delete mode 100644 client/views/file-browser-view.js diff --git a/client/pages/project-manager.js b/client/pages/project-manager.js index 6b76eaeb5e..fc791a9145 100644 --- a/client/pages/project-manager.js +++ b/client/pages/project-manager.js @@ -28,13 +28,12 @@ let Model = require('../models/model'); let Project = require('../models/project'); //views let PageView = require('./base'); +let JSTreeView = require('../views/jstree-view'); let MetaDataView = require('../views/meta-data'); let ModelListing = require('../views/model-listing'); -// let FileBrowser = require('../views/file-browser-view'); let ArchiveListing = require('../views/archive-listing'); let WorkflowListing = require('../views/workflow-listing'); let WorkflowGroupListing = require('../views/workflow-group-listing'); -let JSTreeView = require('../views/jstree-view'); //templates let template = require('../templates/pages/projectManager.pug'); diff --git a/client/views/file-browser-view.js b/client/views/file-browser-view.js deleted file mode 100644 index 988ca32f79..0000000000 --- a/client/views/file-browser-view.js +++ /dev/null @@ -1,1536 +0,0 @@ -/* -StochSS is a platform for simulating biochemical systems -Copyright (C) 2019-2021 StochSS developers. - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program. If not, see . -*/ - -// let jstree = require('jstree'); -// let path = require('path'); -// let $ = require('jquery'); -// let _ = require('underscore'); -//support files -// let app = require('../app'); -// let modals = require('../modals'); -//models -// let Model = require('../models/model'); -//views -// let View = require('ampersand-view'); -//templates -// let template = require('../templates/includes/fileBrowserView.pug'); - -// module.exports = View.extend({ - // template: template, - events: { - // 'click [data-hook=collapse-browse-files]' : 'changeCollapseButtonText', - // 'click [data-hook=refresh-jstree]' : 'refreshJSTree', - // 'click [data-hook=options-for-node]' : 'showContextMenuForNode', - // 'click [data-hook=new-directory]' : 'handleCreateDirectoryClick', - 'click [data-hook=browser-new-workflow-group]' : 'handleCreateWorkflowGroupClick', - // 'click [data-hook=browser-new-model]' : 'handleCreateModelClick', - // 'click [data-hook=browser-new-domain]' : 'handelCreateDomainClick', - // 'click [data-hook=browser-existing-model]' : 'handleAddExistingModelClick', - // 'click [data-hook=upload-file-btn-bf]' : 'handleUploadFileClick', - 'click [data-hook=file-browser-help]' : function () { - let modal = $(modals.operationInfoModalHtml('file-browser')).modal(); - }, - }, - // initialize: function (attrs, options) { - // View.prototype.initialize.apply(this, arguments) - // var self = this - // this.root = "none" - // if(attrs && attrs.root){ - // this.root = attrs.root - // } - // this.ajaxData = { - // "url" : function (node) { - // if(node.parent === null){ - // var endpoint = path.join(app.getApiPath(), "file/browser-list")+"?path="+self.root - // if(self.root !== "none") { - // endpoint += "&isRoot=True" - // } - // return endpoint - // } - // return path.join(app.getApiPath(), "file/browser-list")+"?path="+ node.original._path - // }, - // "dataType" : "json", - // "data" : function (node) { - // return { 'id' : node.id} - // }, - // } - // this.treeSettings = { - // 'plugins': ['types', 'wholerow', 'changed', 'contextmenu', 'dnd'], - // 'core': {'multiple' : false, 'animation': 0, - // 'check_callback': function (op, node, par, pos, more) { - // if(op === "rename_node" && self.validateName(pos, true) !== ""){ - // document.querySelector("#renameSpecialCharError").style.display = "block" - // setTimeout(function () { - // document.querySelector("#renameSpecialCharError").style.display = "none" - // }, 5000) - // return false - // } - // if(op === 'move_node' && more && more.core) { - // var newDir = par.original._path !== "/" ? par.original._path : "" - // var file = node.original._path.split('/').pop() - // var oldPath = node.original._path - // let queryStr = "?srcPath="+oldPath+"&dstPath="+path.join(newDir, file) - // var endpoint = path.join(app.getApiPath(), "file/move")+queryStr - // app.getXHR(endpoint, { - // success: function (err, response, body) { - // node.original._path = path.join(newDir, file) - // if((node.type === "nonspatial" || node.type === "spatial") && (oldPath.includes("trash") || newDir.includes("trash"))) { - // self.updateParent("Archive"); - // }else if(node.type !== "notebook" || node.original._path.includes(".wkgp") || newDir.includes(".wkgp")) { - // self.updateParent(node.type) - // } - // }, - // error: function (err, response, body) { - // body = JSON.parse(body) - // if(par.type === 'root'){ - // $('#models-jstree-view').jstree().refresh() - // }else{ - // $('#models-jstree-view').jstree().refresh_node(par); - // } - // } - // }); - // }else{ - // let isMove = op === 'move_node' - // let validSrc = Boolean(node && node.type && node.original && node.original.text !== "trash") - // let validDst = Boolean(more && more.ref && more.ref.type && more.ref.original) - // let validDsts = ["root", "folder"] - // let isModel = Boolean(validSrc && (node.type === "nonspatial" || node.type === "spatial")) - // let isWorkflow = Boolean(validSrc && node.type === "workflow") - // let isWkgp = Boolean(validSrc && node.type === "workflow-group") - // let isNotebook = Boolean(validSrc && node.type === "notebook") - // let isOther = Boolean(validSrc && !isModel && !isWorkflow && !isWkgp && !isNotebook) - // let trashAction = Boolean((validSrc && node.original._path.includes("trash")) || (validDst && more.ref.original.text === "trash")) - // // Check if files are being move directly into the trash and remain static with respect to the trash - // if(isMove && validDst && path.dirname(more.ref.original._path).includes("trash")) { return false } - // if(isMove && validSrc && validDst && node.original._path.includes("trash") && more.ref.original.text === 'trash') { return false } - // // Check if workflow is running - // if(isMove && isWorkflow && node.original._status && node.original._status === "running") { return false }; - // // Check if model, workflow, or workflow group is moving to or from trash - // if(isMove && (isModel || isWorkflow) && !trashAction) { return false }; - // if(isMove && isWkgp && !(self.parent.model.newFormat && trashAction)) { return false }; - // // Check if model, workflow, or workflow group is moving from trash to the correct location - // if(isMove && validSrc && node.original._path.includes("trash")) { - // if(isWkgp && (!self.parent.model.newFormat || (validDst && more.ref.type !== "root"))) { return false }; - // if(isWorkflow && validDst && more.ref.type !== "workflow-group") { return false }; - // if(isModel && validDst) { - // if(!self.parent.model.newFormat && more.ref.type !== "root") { return false }; - // let length = node.original.text.split(".").length; - // let modelName = node.original.text.split(".").slice(0, length - 1).join(".") - // if(self.parent.model.newFormat && (more.ref.type !== "workflow-group" || !more.ref.original.text.startsWith(modelName))) { return false }; - // } - // } - // // Check if notebook or other file is moving to a valid location. - // if(isOther && validDst && !validDsts.includes(more.ref.type)) { return false }; - // validDsts.push("workflow-group") - // if(isNotebook && validDst && !validDsts.includes(more.ref.type)) { return false }; - // if(isMove && validDst && validDsts.includes(more.ref.type)){ - // if(!more.ref.state.loaded) { return false }; - // var exists = false - // var BreakException = {} - // var text = node.text - // if(!isNaN(text.split(' ').pop().split('.').join(""))){ - // text = text.replace(text.split(' ').pop(), '').trim() - // } - // if(more.ref.text !== "trash"){ - // try{ - // more.ref.children.forEach(function (child) { - // var child_node = $('#models-jstree-view').jstree().get_node(child) - // exists = child_node.text === text - // if(exists) { throw BreakException; }; - // }) - // }catch { return false; }; - // } - // } - // if(isMove && more && (pos != 0 || more.pos !== "i") && !more.core) { return false } - // return true - // } - // }, - // 'themes': {'stripes': true, 'variant': 'large'}, - // 'data': self.ajaxData, - // }, - // 'types' : { - // 'root' : {"icon": "jstree-icon jstree-folder"}, - // 'folder' : {"icon": "jstree-icon jstree-folder"}, - // 'spatial' : {"icon": "jstree-icon jstree-file"}, - // 'nonspatial' : {"icon": "jstree-icon jstree-file"}, - // 'project' : {"icon": "jstree-icon jstree-file"}, - // 'workflow-group' : {"icon": "jstree-icon jstree-folder"}, - // 'workflow' : {"icon": "jstree-icon jstree-file"}, - // 'notebook' : {"icon": "jstree-icon jstree-file"}, - // 'domain' : {"icon": "jstree-icon jstree-file"}, - // 'sbml-model' : {"icon": "jstree-icon jstree-file"}, - // 'other' : {"icon": "jstree-icon jstree-file"}, - // }, - // } - // this.setupJstree() - // }, - // render: function () { - // View.prototype.render.apply(this, arguments) - // var self = this; - // this.nodeForContextMenu = ""; - // this.jstreeIsLoaded = false - // window.addEventListener('pageshow', function (e) { - // var navType = window.performance.navigation.type - // if(navType === 2){ - // window.location.reload() - // } - // }); - // }, - // updateParent: function (type) { - // let models = ["nonspatial", "spatial", "sbml", "model"] - // let workflows = ["workflow", "notebook"] - // if(models.includes(type)) { - // this.parent.update("Model") - // }else if(workflows.includes(type)) { - // this.parent.update("Workflow") - // }else if(type === "workflow-group") { - // this.parent.update("WorkflowGroup") - // }else if(type === "Archive") { - // this.parent.update(type); - // } - // }, - // refreshJSTree: function () { - // this.jstreeIsLoaded = false - // $('#models-jstree-view').jstree().deselect_all(true) - // $('#models-jstree-view').jstree().refresh() - // }, - // refreshInitialJSTree: function () { - // var self = this; - // var count = $('#models-jstree-view').jstree()._model.data['#'].children.length; - // if(count == 0) { - // self.refreshJSTree(); - // setTimeout(function () { - // self.refreshInitialJSTree(); - // }, 3000); - // } - // }, - // selectNode: function (node, fileName) { - // let self = this - // if(!this.jstreeIsLoaded || !$('#models-jstree-view').jstree().is_loaded(node) && $('#models-jstree-view').jstree().is_loading(node)) { - // setTimeout(_.bind(self.selectNode, self, node, fileName), 1000); - // }else{ - // node = $('#models-jstree-view').jstree().get_node(node) - // var child = "" - // for(var i = 0; i < node.children.length; i++) { - // var child = $('#models-jstree-view').jstree().get_node(node.children[i]) - // if(child.original.text === fileName) { - // $('#models-jstree-view').jstree().select_node(child) - // let optionsButton = $(self.queryByHook("options-for-node")) - // if(!self.nodeForContextMenu){ - // optionsButton.prop('disabled', false) - // } - // optionsButton.text("Actions for " + child.original.text) - // self.nodeForContextMenu = child; - // break - // } - // } - // } - // }, - // handleUploadFileClick: function (e) { - // let type = e.target.dataset.type - // this.uploadFile(undefined, type) - // }, - // uploadFile: function (o, type) { - // var self = this - // if(document.querySelector('#uploadFileModal')) { - // document.querySelector('#uploadFileModal').remove() - // } - // if(this.browser == undefined) { - // this.browser = app.getBrowser(); - // } - // if(this.isSafariV14Plus == undefined){ - // this.isSafariV14Plus = (this.browser.name === "Safari" && this.browser.version >= 14) - // } - // let modal = $(modals.uploadFileHtml(type, this.isSafariV14Plus)).modal(); - // let uploadBtn = document.querySelector('#uploadFileModal .upload-modal-btn'); - // let fileInput = document.querySelector('#uploadFileModal #fileForUpload'); - // let input = document.querySelector('#uploadFileModal #fileNameInput'); - // let fileCharErrMsg = document.querySelector('#uploadFileModal #fileSpecCharError') - // let nameEndErrMsg = document.querySelector('#uploadFileModal #fileNameInputEndCharError') - // let nameCharErrMsg = document.querySelector('#uploadFileModal #fileNameInputSpecCharError') - // let nameUsageMsg = document.querySelector('#uploadFileModal #fileNameUsageMessage') - // fileInput.addEventListener('change', function (e) { - // let fileErr = !fileInput.files.length ? "" : self.validateName(fileInput.files[0].name) - // let nameErr = self.validateName(input.value) - // if(!fileInput.files.length) { - // uploadBtn.disabled = true - // fileCharErrMsg.style.display = 'none' - // }else if(fileErr === "" || (Boolean(input.value) && nameErr === "")){ - // uploadBtn.disabled = false - // fileCharErrMsg.style.display = 'none' - // }else{ - // uploadBtn.disabled = true - // fileCharErrMsg.style.display = 'block' - // } - // }) - // input.addEventListener("input", function (e) { - // let fileErr = !fileInput.files.length ? "" : self.validateName(fileInput.files[0].name) - // let nameErr = self.validateName(input.value) - // if(!fileInput.files.length) { - // uploadBtn.disabled = true - // fileCharErrMsg.style.display = 'none' - // }else if(fileErr === "" || (Boolean(input.value) && nameErr === "")){ - // uploadBtn.disabled = false - // fileCharErrMsg.style.display = 'none' - // }else{ - // uploadBtn.disabled = true - // fileCharErrMsg.style.display = 'block' - // } - // nameCharErrMsg.style.display = nameErr === "both" || nameErr === "special" ? "block" : "none" - // nameEndErrMsg.style.display = nameErr === "both" || nameErr === "forward" ? "block" : "none" - // nameUsageMsg.style.display = nameErr !== "" ? "block" : "none" - // }); - // uploadBtn.addEventListener('click', function (e) { - // let file = fileInput.files[0] - // var fileinfo = {"type":type,"name":"","path":self.parent.model.directory} - // if(o && o.original){ - // fileinfo.path = o.original._path - // } - // if(Boolean(input.value) && self.validateName(input.value) === ""){ - // let name = input.value.trim() - // if(file.name.endsWith(".mdl") || (type === "model" && file.name.endsWith(".json"))){ - // fileinfo.name = name.split('/').pop() - // }else if(file.name.endsWith(".sbml") || (type === "sbml" && file.name.endsWith(".xml"))){ - // fileinfo.name = name.split('/').pop() - // }else{ - // fileinfo.name = name - // } - // } - // let formData = new FormData() - // formData.append("datafile", file) - // formData.append("fileinfo", JSON.stringify(fileinfo)) - // let endpoint = path.join(app.getApiPath(), 'file/upload'); - // if(Boolean(input.value) && self.validateName(input.value) === "" && fileinfo.name !== input.value.trim()){ - // let message = "Warning: Models are saved directly in StochSS Projects and cannot be saved to the "+input.value.trim().split("/")[0]+" directory in the project.

Do you wish to save your model directly in your project?

" - // let warningModal = $(modals.newProjectModelWarningHtml(message)).modal() - // let yesBtn = document.querySelector('#newProjectModelWarningModal .yes-modal-btn'); - // yesBtn.addEventListener('click', function (e) { - // warningModal.modal('hide') - // self.openUploadRequest(endpoint, formData, file, type, o) - // }) - // }else{ - // self.openUploadRequest(endpoint, formData, file, type, o) - // } - // modal.modal('hide') - // }) - // }, - // openUploadRequest: function (endpoint, formData, file, type, o) { - // let self = this - // app.postXHR(endpoint, formData, { - // success: function (err, response, body) { - // body = JSON.parse(body); - // if(o){ - // var node = $('#models-jstree-view').jstree().get_node(o.parent); - // if(node.type === "root" || node.type === "#"){ - // self.refreshJSTree(); - // }else{ - // $('#models-jstree-view').jstree().refresh_node(node); - // } - // }else{ - // self.refreshJSTree(); - // } - // if(body.file.endsWith(".mdl") || body.file.endsWith(".smdl") ||body.file.endsWith(".sbml")) { - // self.updateParent("model") - // } - // if(body.errors.length > 0){ - // let errorModal = $(modals.uploadFileErrorsHtml(file.name, type, body.message, body.errors)).modal(); - // } - // }, - // error: function (err, response, body) { - // body = JSON.parse(body); - // let zipErrorModal = $(modals.projectExportErrorHtml(body.Reason, body.Message)).modal() - // } - // }, false); - // }, - // deleteFile: function (o) { - // var fileType = o.type - // if(fileType === "nonspatial") - // fileType = "model"; - // else if(fileType === "spatial") - // fileType = "spatial model" - // else if(fileType === "sbml-model") - // fileType = "sbml model" - // else if(fileType === "other") - // fileType = "file" - // var self = this - // if(document.querySelector('#deleteFileModal')) { - // document.querySelector('#deleteFileModal').remove() - // } - // let modal = $(modals.deleteFileHtml(fileType)).modal(); - // let yesBtn = document.querySelector('#deleteFileModal .yes-modal-btn'); - // yesBtn.addEventListener('click', function (e) { - // var endpoint = path.join(app.getApiPath(), "file/delete")+"?path="+o.original._path - // app.getXHR(endpoint, { - // success: function (err, response, body) { - // var node = $('#models-jstree-view').jstree().get_node(o.parent); - // if(node.type === "root"){ - // self.refreshJSTree(); - // }else{ - // $('#models-jstree-view').jstree().refresh_node(node); - // } - // } - // }); - // modal.modal('hide') - // if(o.type !== "notebook" || o.original._path.includes(".wkgp")) { - // self.updateParent(o.type) - // } - // }); - // }, - // duplicateFileOrDirectory: function(o, type) { - // var self = this; - // var parentID = o.parent; - // var queryStr = "?path="+o.original._path - // if(!type && o.original.type === 'folder'){ - // type = "directory" - // }else if(!type && o.original.type === 'workflow'){ - // type = "workflow" - // }else if(!type){ - // type = "file" - // } - // if(type === "directory"){ - // var identifier = "directory/duplicate" - // }else if(type === "workflow" || type === "wkfl_model"){ - // var timeStamp = type === "workflow" ? this.getTimeStamp() : "None" - // var identifier = "workflow/duplicate" - // queryStr = queryStr.concat("&target="+type+"&stamp="+timeStamp) - // }else{ - // var identifier = "file/duplicate" - // } - // var endpoint = path.join(app.getApiPath(), identifier)+queryStr - // app.getXHR(endpoint, { - // success: function (err, response, body) { - // var node = $('#models-jstree-view').jstree().get_node(parentID); - // self.refreshJSTree() - // if(type === "workflow"){ - // var message = "" - // if(body.error){ - // message = body.error - // }else{ - // message = "The model for "+body.File+" is located here: "+body.mdlPath+"" - // } - // let modal = $(modals.duplicateWorkflowHtml(body.File, message)).modal() - // } - // self.selectNode(node, body.File) - // if(o.type !== "notebook" || o.original._path.includes(".wkgp")) { - // self.updateParent(o.type) - // } - // } - // }); - // }, - // getTimeStamp: function () { - // var date = new Date(); - // var year = date.getFullYear(); - // var month = date.getMonth() + 1; - // if(month < 10){ - // month = "0" + month - // } - // var day = date.getDate(); - // if(day < 10){ - // day = "0" + day - // } - // var hours = date.getHours(); - // if(hours < 10){ - // hours = "0" + hours - // } - // var minutes = date.getMinutes(); - // if(minutes < 10){ - // minutes = "0" + minutes - // } - // var seconds = date.getSeconds(); - // if(seconds < 10){ - // seconds = "0" + seconds - // } - // return "_" + month + day + year + "_" + hours + minutes + seconds; - // }, - // toSpatial: function (o) { - // var self = this; - // var parentID = o.parent; - // var endpoint = path.join(app.getApiPath(), "model/to-spatial")+"?path="+o.original._path; - // app.getXHR(endpoint, { - // success: function (err, response, body) { - // var node = $('#models-jstree-view').jstree().get_node(parentID); - // self.refreshJSTree() - // self.updateParent("spatial") - // self.selectNode(node, body.File) - // } - // }); - // }, - // toModel: function (o, from) { - // var self = this; - // var parentID = o.parent; - // if(from === "Spatial"){ - // var identifier = "spatial/to-model" - // }else{ - // var identifier = "sbml/to-model" - // } - // let endpoint = path.join(app.getApiPath(), identifier)+"?path="+o.original._path; - // app.getXHR(endpoint, { - // success: function (err, response, body) { - // var node = $('#models-jstree-view').jstree().get_node(parentID); - // self.refreshJSTree() - // self.selectNode(node, body.File) - // if(from === "SBML" && body.errors.length > 0){ - // var title = "" - // var msg = body.message - // var errors = body.errors - // let modal = $(modals.sbmlToModelHtml(msg, errors)).modal(); - // }else{ - // self.updateParent("nonspatial") - // } - // } - // }); - // }, - toNotebook: function (o, type) { - let self = this - var endpoint = path.join(app.getApiPath(), "workflow/notebook")+"?type=none&path="+o.original._path - app.getXHR(endpoint, { - success: function (err, response, body) { - var node = $('#models-jstree-view').jstree().get_node(o.parent) - if(node.type === 'root'){ - self.refreshJSTree(); - }else{ - $('#models-jstree-view').jstree().refresh_node(node); - } - var notebookPath = path.join(app.getBasePath(), "notebooks", body.FilePath) - self.selectNode(node, body.File) - window.open(notebookPath, '_blank') - } - }); - }, - // toSBML: function (o) { - // var self = this; - // var parentID = o.parent; - // var endpoint = path.join(app.getApiPath(), "model/to-sbml")+"?path="+o.original._path; - // app.getXHR(endpoint, { - // success: function (err, response, body) { - // var node = $('#models-jstree-view').jstree().get_node(parentID); - // self.refreshJSTree() - // self.selectNode(node, body.File) - // } - // }); - // }, - // renameNode: function (o) { - // var self = this - // var text = o.text; - // var parent = $('#models-jstree-view').jstree().get_node(o.parent) - // var extensionWarning = $(this.queryByHook('extension-warning')); - // var nameWarning = $(this.queryByHook('rename-warning')); - // extensionWarning.collapse('show') - // $('#models-jstree-view').jstree().edit(o, null, function(node, status) { - // if(text != node.text){ - // let name = node.type === "root" ? node.text + ".proj" : node.text - // var endpoint = path.join(app.getApiPath(), "file/rename")+"?path="+ o.original._path+"&name="+name - // app.getXHR(endpoint, { - // always: function (err, response, body) { - // if(parent.type === "root"){ - // self.refreshJSTree(); - // }else{ - // $('#models-jstree-view').jstree().refresh_node(parent); - // } - // }, - // success: function (err, response, body) { - // if(body.changed) { - // nameWarning.text(body.message); - // nameWarning.collapse('show'); - // window.scrollTo(0,0); - // setTimeout(_.bind(self.hideNameWarning, self), 10000); - // } - // node.original._path = body._path; - // } - // }); - // } - // extensionWarning.collapse('hide'); - // nameWarning.collapse('hide'); - // }); - // }, - // hideNameWarning: function () { - // $(this.queryByHook('rename-warning')).collapse('hide') - // }, - // getExportData: function (o, asZip) { - // var self = this; - // let nodeType = o.original.type - // let isJSON = nodeType === "sbml-model" ? false : true - // if(nodeType === "sbml-model"){ - // var dataType = "plain-text" - // var identifier = "file/download" - // }else if(nodeType === "domain") { - // var dataType = "json" - // var identifier = "spatial-model/load-domain" - // }else if(asZip) { - // var dataType = "zip" - // var identifier = "file/download-zip" - // }else{ - // var dataType = "json" - // var identifier = "file/json-data" - // } - // if(nodeType === "domain") { - // var queryStr = "?domain_path=" + o.original._path - // }else{ - // var queryStr = "?path="+o.original._path - // if(dataType === "json"){ - // queryStr = queryStr.concat("&for=None") - // }else if(dataType === "zip"){ - // queryStr = queryStr.concat("&action=generate") - // } - // } - // var endpoint = path.join(app.getApiPath(), identifier)+queryStr - // app.getXHR(endpoint, { - // success: function (err, response, body) { - // if(dataType === "json") { - // let data = nodeType === "domain" ? body.domain : body; - // self.exportToJsonFile(data, o.original.text); - // }else if(dataType === "zip") { - // var node = $('#models-jstree-view').jstree().get_node(o.parent); - // if(node.type === "root"){ - // self.refreshJSTree(); - // }else{ - // $('#models-jstree-view').jstree().refresh_node(node); - // } - // self.exportToZipFile(body.Path); - // }else{ - // self.exportToFile(body, o.original.text); - // } - // } - // }); - // }, - // exportToJsonFile: function (fileData, fileName) { - // let dataStr = JSON.stringify(fileData); - // let dataURI = 'data:application/json;charset=utf-8,' + encodeURIComponent(dataStr); - // let exportFileDefaultName = fileName - - // let linkElement = document.createElement('a'); - // linkElement.setAttribute('href', dataURI); - // linkElement.setAttribute('download', exportFileDefaultName); - // linkElement.click(); - // }, - // exportToFile: function (fileData, fileName) { - // let dataURI = 'data:text/plain;charset=utf-8,' + encodeURIComponent(fileData); - - // let linkElement = document.createElement('a'); - // linkElement.setAttribute('href', dataURI); - // linkElement.setAttribute('download', fileName); - // linkElement.click(); - // }, - // exportToZipFile: function (o) { - // var targetPath = o - // if(o.original){ - // targetPath = o.original._path - // } - // var endpoint = path.join(app.getBasePath(), "/files", targetPath); - // window.open(endpoint) - // }, - // validateName(input, rename = false) { - // var error = "" - // if(input.endsWith('/')) { - // error = 'forward' - // } - // var invalidChars = "`~!@#$%^&*=+[{]}\"|:;'<,>?\\" - // if(rename) { - // invalidChars += "/" - // } - // for(var i = 0; i < input.length; i++) { - // if(invalidChars.includes(input.charAt(i))) { - // error = error === "" || error === "special" ? "special" : "both" - // } - // } - // return error - // }, - newWorkflowGroup: function (o) { - var self = this - if(document.querySelector("#newWorkflowGroupModal")) { - document.querySelector("#newWorkflowGroupModal").remove() - } - let modal = $(modals.newWorkflowGroupModalHtml()).modal(); - let okBtn = document.querySelector('#newWorkflowGroupModal .ok-model-btn'); - let input = document.querySelector('#newWorkflowGroupModal #workflowGroupNameInput'); - input.addEventListener("keyup", function (event) { - if(event.keyCode === 13){ - event.preventDefault(); - okBtn.click(); - } - }); - input.addEventListener("input", function (e) { - var endErrMsg = document.querySelector('#newWorkflowGroupModal #workflowGroupNameInputEndCharError') - var charErrMsg = document.querySelector('#newWorkflowGroupModal #workflowGroupNameInputSpecCharError') - let error = self.validateName(input.value) - okBtn.disabled = error !== "" - charErrMsg.style.display = error === "both" || error === "special" ? "block" : "none" - endErrMsg.style.display = error === "both" || error === "forward" ? "block" : "none" - }); - okBtn.addEventListener("click", function (e) { - if(Boolean(input.value)) { - modal.modal('hide') - var parentPath = self.parent.model.directory - if(o && o.original && o.original._path !== "/") { - parentPath = o.original._path - } - var workflowGroupName = input.value.trim() + ".wkgp" - var workflowGroupPath = path.join(parentPath, workflowGroupName) - let endpoint = path.join(app.getApiPath(), "project/new-workflow-group")+"?path="+workflowGroupPath - app.getXHR(endpoint, { - success: function (err, response, body) { - if(o){//directory was created with context menu option - var node = $('#models-jstree-view').jstree().get_node(o); - if(node.type === "root"){ - self.refreshJSTree(); - }else{ - $('#models-jstree-view').jstree().refresh_node(node); - } - }else{//directory was created with create directory button - self.refreshJSTree(); - } - self.updateParent('workflow-group'); - }, - error: function (err, response, body) { - let errorModel = $(modals.newProjectOrWorkflowGroupErrorHtml(body.Reason, body.Message)).modal(); - } - }); - } - }) - }, - // handleAddExistingModelClick: function () { - // this.addExistingModel(undefined) - // }, - // addExistingModel: function (o) { - // var self = this - // if(document.querySelector('#newProjectModelModal')){ - // document.querySelector('#newProjectModelModal').remove() - // } - // let mdlListEP = path.join(app.getApiPath(), 'project/add-existing-model') + "?path="+self.parent.model.directory - // app.getXHR(mdlListEP, { - // always: function (err, response, body) { - // let modal = $(modals.newProjectModelHtml(body.files)).modal(); - // let okBtn = document.querySelector('#newProjectModelModal .ok-model-btn'); - // let select = document.querySelector('#newProjectModelModal #modelFileInput'); - // let location = document.querySelector('#newProjectModelModal #modelPathInput'); - // select.addEventListener("change", function (e) { - // okBtn.disabled = e.target.value && body.paths[e.target.value].length >= 2; - // if(body.paths[e.target.value].length >= 2) { - // var locations = body.paths[e.target.value].map(function (path) { - // return ``; - // }); - // locations.unshift(``); - // locations = locations.join(" "); - // $("#modelPathInput").find('option').remove().end().append(locations); - // $("#location-container").css("display", "block"); - // }else{ - // $("#location-container").css("display", "none"); - // $("#modelPathInput").find('option').remove().end(); - // } - // }); - // location.addEventListener("change", function (e) { - // okBtn.disabled = !Boolean(e.target.value); - // }); - // okBtn.addEventListener("click", function (e) { - // modal.modal('hide'); - // let mdlPath = body.paths[select.value].length < 2 ? body.paths[select.value][0] : location.value; - // let queryString = "?path="+self.parent.model.directory+"&mdlPath="+mdlPath; - // let endpoint = path.join(app.getApiPath(), 'project/add-existing-model') + queryString; - // app.postXHR(endpoint, null, { - // success: function (err, response, body) { - // let successModal = $(modals.newProjectModelSuccessHtml(body.message)).modal(); - // self.updateParent("model"); - // self.refreshJSTree(); - // }, - // error: function (err, response, body) { - // let errorModal = $(modals.newProjectModelErrorHtml(body.Reason, body.Message)).modal(); - // } - // }); - // }); - // } - // }); - // }, - // newWorkflow: function (o, type) { - // let self = this; - // let model = new Model({ - // directory: o.original._path - // }); - // app.getXHR(model.url(), { - // success: function (err, response, body) { - // model.set(body); - // model.updateValid(); - // if(model.valid){ - // app.newWorkflow(self, o.original._path, o.type === "spatial", type); - // }else{ - // let title = "Model Errors Detected"; - // let endpoint = path.join(app.getBasePath(), "stochss/models/edit") + '?path=' + model.directory + '&validate'; - // let message = 'Errors were detected in you model
click here to fix your model'; - // $(modals.modelErrorHtml(title, message)).modal(); - // } - // } - // }); - // }, - // addModel: function (parentPath, modelName, message) { - // var endpoint = path.join(app.getBasePath(), "stochss/models/edit") - // if(parentPath.endsWith(".proj")) { - // let queryString = "?path=" + parentPath + "&mdlFile=" + modelName - // let newMdlEP = path.join(app.getApiPath(), "project/new-model") + queryString - // app.getXHR(newMdlEP, { - // success: function (err, response, body) { - // endpoint += "?path="+body.path; - // window.location.href = endpoint; - // }, - // error: function (err, response, body) { - // let title = "Model Already Exists"; - // let message = "A model already exists with that name"; - // let errorModel = $(modals.newProjectOrWorkflowGroupErrorHtml(title, message)).modal(); - // } - // }); - // }else{ - // let modelPath = path.join(parentPath, modelName) - // let queryString = "?path="+modelPath+"&message="+message; - // endpoint += queryString - // let existEP = path.join(app.getApiPath(), "model/exists")+queryString - // app.getXHR(existEP, { - // always: function (err, response, body) { - // if(body.exists) { - // let title = "Model Already Exists"; - // let message = "A model already exists with that name"; - // let errorModel = $(modals.newProjectOrWorkflowGroupErrorHtml(title, message)).modal(); - // }else{ - // window.location.href = endpoint; - // } - // } - // }); - // } - // }, - // newModelOrDirectory: function (o, isModel, isSpatial) { - // var self = this - // if(document.querySelector('#newModalModel')) { - // document.querySelector('#newModalModel').remove() - // } - // let modal = $(modals.renderCreateModalHtml(isModel, isSpatial)).modal(); - // let okBtn = document.querySelector('#newModalModel .ok-model-btn'); - // let input = document.querySelector('#newModalModel #modelNameInput'); - // input.addEventListener("keyup", function (event) { - // if(event.keyCode === 13){ - // event.preventDefault(); - // okBtn.click(); - // } - // }); - // input.addEventListener("input", function (e) { - // var endErrMsg = document.querySelector('#newModalModel #modelNameInputEndCharError') - // var charErrMsg = document.querySelector('#newModalModel #modelNameInputSpecCharError') - // let error = self.validateName(input.value) - // okBtn.disabled = error !== "" || input.value.trim() === "" - // charErrMsg.style.display = error === "both" || error === "special" ? "block" : "none" - // endErrMsg.style.display = error === "both" || error === "forward" ? "block" : "none" - // }); - // okBtn.addEventListener('click', function (e) { - // if (Boolean(input.value)) { - // modal.modal('hide') - // var parentPath = self.parent.model.directory - // if(o && o.original && o.original._path !== "/"){ - // parentPath = o.original._path - // } - // if(isModel) { - // let ext = isSpatial ? ".smdl" : ".mdl"; - // let modelName = !o || (o && o.type === "root") ? input.value.trim().split("/").pop() + ext : input.value.trim() + ext; - // let message = modelName !== input.value.trim() + ext? - // "Warning: Models are saved directly in StochSS Projects and cannot be saved to the "+input.value.trim().split("/")[0]+" directory in the project.

Your model will be saved directly in your project.

" : "" - // if(message){ - // let warningModal = $(modals.newProjectModelWarningHtml(message)).modal() - // let yesBtn = document.querySelector('#newProjectModelWarningModal .yes-modal-btn'); - // yesBtn.addEventListener('click', function (e) { - // warningModal.modal('hide'); - // self.addModel(parentPath, modelName, message); - // }); - // }else{ - // self.addModel(parentPath, modelName, message); - // } - // }else{ - // let dirName = input.value.trim(); - // let endpoint = path.join(app.getApiPath(), "directory/create")+"?path="+path.join(parentPath, dirName); - // app.getXHR(endpoint, { - // success: function (err, response, body) { - // if(o){//directory was created with context menu option - // var node = $('#models-jstree-view').jstree().get_node(o); - // if(node.type === "root"){ - // self.refreshJSTree(); - // }else{ - // $('#models-jstree-view').jstree().refresh_node(node); - // } - // }else{//directory was created with create directory button - // self.refreshJSTree(); - // } - // }, - // error: function (err, response, body) { - // body = JSON.parse(body); - // let errorModal = $(modals.newDirectoryErrorHtml(body.Reason, body.Message)).modal(); - // } - // }); - // } - // } - // }); - // }, - // handleCreateDirectoryClick: function (e) { - // this.newModelOrDirectory(undefined, false, false); - // }, - handleCreateWorkflowGroupClick: function (e) { - this.newWorkflowGroup(undefined) - }, - // handleCreateModelClick: function (e) { - // let isSpatial = e.target.dataset.type === "spatial" - // this.newModelOrDirectory(undefined, true, isSpatial); - // }, - // handelCreateDomainClick: function (e) { - // let queryStr = "?domainPath=" + this.parent.model.directory + "&new" - // window.location.href = path.join(app.getBasePath(), "stochss/domain/edit") + queryStr - // }, - // handleExtractModelClick: function (o) { - // let self = this - // let projectParent = path.dirname(this.parent.model.directory) === '.' ? "" : path.dirname(this.parent.model.directory) - // let queryString = "?srcPath="+o.original._path+"&dstPath="+path.join(projectParent, o.original._path.split('/').pop()) - // let endpoint = path.join(app.getApiPath(), "project/extract-model")+queryString - // app.getXHR(endpoint, { - // success: function (err, response, body) { - // let successModel = $(modals.projectExportSuccessHtml("Model", body)).modal(); - // }, - // error: function (err, response, body) { - // body = JSON.parse(body); - // let successModel = $(modals.projectExportErrorHtml(body.Reason, body.message)).modal(); - // } - // }); - // }, - // handleExportWorkflowClick: function (o) { - // let self = this - // let projectParent = path.dirname(this.parent.model.directory) === '.' ? "" : path.dirname(this.parent.model.directory) - // let queryString = "?srcPath="+o.original._path+"&dstPath="+path.join(projectParent, o.original._path.split('/').pop()) - // let endpoint = path.join(app.getApiPath(), "project/extract-workflow")+queryString - // app.getXHR(endpoint, { - // success: function (err, response, body) { - // let successModel = $(modals.projectExportSuccessHtml("Workflow", body)).modal(); - // }, - // error: function (err, response, body) { - // body = JSON.parse(body); - // let successModel = $(modals.projectExportErrorHtml(body.Reason, body.message)).modal(); - // } - // }); - // }, - handleExportCombineClick: function (o, download) { - let target = o.original._path - this.parent.exportAsCombine() - }, - // showContextMenuForNode: function (e) { - // $('#models-jstree-view').jstree().show_contextmenu(this.nodeForContextMenu) - // }, - // editWorkflowModel: function (o) { - // let endpoint = path.join(app.getApiPath(), "workflow/edit-model")+"?path="+o.original._path - // app.getXHR(endpoint, { - // success: function (err, response, body) { - // if(body.error){ - // let title = o.text + " Not Found"; - // let message = body.error; - // let modal = $(modals.duplicateWorkflowHtml(title, message)).modal(); - // }else{ - // window.location.href = path.join(app.routePrefix, "models/edit")+"?path="+body.file; - // } - // } - // }); - // }, - // extractAll: function (o) { - // let self = this; - // let queryStr = "?path=" + o.original._path; - // let endpoint = path.join(app.getApiPath(), "file/unzip") + queryStr; - // app.getXHR(endpoint, { - // success: function (err, response, body) { - // let node = $('#models-jstree-view').jstree().get_node(o.parent); - // if(node.type === "root"){ - // self.refreshJSTree(); - // }else{ - // $('#models-jstree-view').jstree().refresh_node(node); - // } - // }, - // error: function (err, response, body) { - // let modal = $(modals.newProjectModelErrorHtml(body.Reason, body.message)).modal(); - // } - // }); - // }, - // publishNotebookPresentation: function (o) { - // let queryStr = "?path=" + o.original._path; - // let endpoint = path.join(app.getApiPath(), "notebook/presentation") + queryStr; - // app.getXHR(endpoint, { - // success: function (err, response, body) { - // let title = body.message; - // let linkHeaders = "Shareable Presentation"; - // let links = body.links; - // $(modals.presentationLinks(title, linkHeaders, links)).modal(); - // let copyBtn = document.querySelector('#presentationLinksModal #copy-to-clipboard'); - // copyBtn.addEventListener('click', function (e) { - // let onFulfilled = (value) => { - // $("#copy-link-success").css("display", "inline-block"); - // } - // let onReject = (reason) => { - // let msg = $("#copy-link-failed"); - // msg.html(reason); - // msg.css("display", "inline-block"); - // } - // app.copyToClipboard(links.presentation, onFulfilled, onReject); - // }); - // }, - // error: function (err, response, body) { - // $(modals.newProjectModelErrorHtml(body.Reason, body.Message)).modal(); - // } - // }); - // }, - // setupJstree: function () { - // var self = this; - // $.jstree.defaults.contextmenu.items = (o, cb) => { - // let nodeType = o.original.type - // let zipTypes = ["workflow", "folder", "other", "root", "workflow-group"] - // let asZip = zipTypes.includes(nodeType) - // refresh context menu option - // let refresh = { - // "Refresh" : { - // "label" : "Refresh", - // "_disabled" : false, - // "_class" : "font-weight-bold", - // "separator_before" : false, - // "separator_after" : o.text !== "trash", - // "action" : function (data) { - // if(nodeType === "root"){ - // self.refreshJSTree(); - // }else{ - // $('#models-jstree-view').jstree().refresh_node(o); - // } - // } - // } - // } - // For notebooks, workflows, sbml models, and other files - // let open = { - // "Open" : { - // "label" : "Open", - // "_disabled" : false, - // "_class" : "font-weight-bolder", - // "separator_before" : false, - // "separator_after" : true, - // "action" : function (data) { - // if(nodeType === "workflow"){ - // window.location.href = path.join(app.getBasePath(), "stochss/workflow/edit")+"?path="+o.original._path+"&type=none"; - // // }else if(nodeType === "project"){ - // // window.location.href = path.join(app.getBasePath(), "stochss/project/manager")+"?path="+o.original._path - // }else if(nodeType === "domain") { - // let queryStr = "?domainPath=" + o.original._path - // window.location.href = path.join(app.getBasePath(), "stochss/domain/edit") + queryStr - // }else{ - // if(nodeType === "notebook") { - // var identifier = "notebooks" - // }else if(nodeType === "sbml-model") { - // var identifier = "edit" - // }else{ - // var identifier = "view" - // } - // window.open(path.join(app.getBasePath(), identifier, o.original._path)); - // } - // } - // } - // } - // project contect menu option - // let project = { - // "Add_Model" : { - // "label" : "Add Model", - // "_disabled" : false, - // "separator_before" : false, - // "separator_after" : false, - // "submenu" : { - // "New_model" : { - // "label" : "New Model", - // "_disabled" : false, - // "separator_before" : false, - // "separator_after" : false, - // "submenu" : { - // "spatial" : { - // "label" : "Spatial (beta)", - // "_disabled" : false, - // "separator_before" : false, - // "separator_after" : false, - // "action" : function (data) { - // self.newModelOrDirectory(o, true, true); - // } - // }, - // "nonspatial" : { - // "label" : "Non-Spatial", - // "_disabled" : false, - // "separator_before" : false, - // "separator_after" : false, - // "action" : function (data) { - // self.newModelOrDirectory(o, true, false); - // } - // } - // } - // }, - // "Existing Model" : { - // "label" : "Existing Model", - // "_disabled" : false, - // "separator_before" : false, - // "separator_after" : false, - // "action" : function (data) { - // self.addExistingModel(o) - // } - // } - // } - // } - // } - // option for uploading files - // let uploadFile = { - // "Upload": { - // "label" : o.type === "root" ? "File" : "Upload File", - // "_disabled" : false, - // "separator_before" : false, - // "separator_after" : o.type !== "root", - // "action" : function (data) { - // self.uploadFile(o, "file") - // } - // } - // } - // // all upload options - // let uploadAll = { - // "Upload" : { - // "label" : "Upload File", - // "_disabled" : false, - // "separator_before" : false, - // "separator_after" : true, - // "submenu" : { - // "Model" : { - // "label" : "StochSS Model", - // "_disabled" : false, - // "separator_before" : false, - // "separator_after" : false, - // "action" : function (data) { - // self.uploadFile(o, "model") - // } - // }, - // "SBML" : { - // "label" : "SBML Model", - // "_disabled" : false, - // "separator_before" : false, - // "separator_after" : false, - // "action" : function (data) { - // self.uploadFile(o, "sbml") - // } - // }, - // "File" : uploadFile.Upload - // } - // } - // } - // common to folder and root - // let commonFolder = { - // "New_Directory" : { - // "label" : "New Directory", - // "_disabled" : false, - // "separator_before" : false, - // "separator_after" : false, - // "action" : function (data) { - // self.newModelOrDirectory(o, false, false); - // } - // }, - // "New Domain" : { - // "label" : "New Domain (beta)", - // "_disabled" : false, - // "separator_before" : false, - // "separator_after" : false, - // "action" : function (data) { - // let queryStr = "?domainPath=" + o.original._path + "&new" - // window.location.href = path.join(app.getBasePath(), "stochss/domain/edit") + queryStr - // } - // }, - // "Upload": o.type === "root" ? uploadAll.Upload : uploadFile.Upload - // } - // if(o.type === "root" || o.type === "workflow-group" || o.type === "workflow") - // var downloadLabel = "as .zip" - // else if(asZip) - // var downloadLabel = "Download as .zip" - // else - // var downloadLabel = "Download" - // let download = { - // "Download" : { - // "label" : downloadLabel, - // "_disabled" : false, - // "separator_before" : false, - // "separator_after" : !(o.type === "root" || o.type === "workflow-group" || o.type === "workflow"), - // "action" : function (data) { - // if(o.original.text.endsWith('.zip')){ - // self.exportToZipFile(o); - // }else{ - // self.getExportData(o, asZip) - // } - // } - // } - // } - // download options for .zip and COMBINE - // let downloadWCombine = { - // "Download" : { - // "label" : "Download", - // "_disabled" : false, - // "separator_before" : false, - // "separator_after" : true, - // "submenu" : { - // "DownloadAsZip": download.Download, - // "downloadAsCombine" : { - // "label" : "as COMBINE", - // "_disabled" : true, - // "separator_before" : false, - // "separator_after" : false, - // "action" : function (data) { - // self.handleExportCombineClick(o, true) - // } - // } - // } - // } - // } - // menu option for creating new workflows - // let newWorkflow = { - // "ensembleSimulation" : { - // "label" : "Ensemble Simulation", - // "_disabled" : false, - // "separator_before" : false, - // "separator_after" : false, - // "action" : function (data) { - // self.newWorkflow(o, "Ensemble Simulation") - // } - // }, - // "parameterSweep" : { - // "label" : "Parameter Sweep", - // "_disabled" : false, - // "separator_before" : false, - // "separator_after" : false, - // "action" : function (data) { - // self.newWorkflow(o, "Parameter Sweep") - // } - // }, - // "jupyterNotebook" : { - // "label" : "Jupyter Notebook", - // "_disabled" : false, - // "separator_before" : false, - // "separator_after" : false, - // "action" : function (data) { - // window.location.href = path.join(app.getBasePath(), "stochss/workflow/selection")+"?path="+o.original._path; - // } - // } - // } - // common to all models - // let commonModel = { - // "Edit" : { - // "label" : "Edit", - // "_disabled" : false, - // "_class" : "font-weight-bolder", - // "separator_before" : false, - // "separator_after" : true, - // "action" : function (data) { - // window.location.href = path.join(app.getBasePath(), "stochss/models/edit")+"?path="+o.original._path; - // } - // }, - // "Extract" : { - // "label" : "Extract", - // "_disabled" : false, - // "separator_before" : false, - // "separator_after" : false, - // "action" : function (data) { - // self.handleExtractModelClick(o); - // } - // }, - // "New Workflow" : { - // "label" : "New Workflow", - // "_disabled" : false, - // "separator_before" : false, - // "separator_after" : false, - // "submenu" : o.type === "nonspatial" ? newWorkflow : {"jupyterNotebook":newWorkflow.jupyterNotebook} - // } - // } - // convert options for non-spatial models - // let modelConvert = { - // "Convert" : { - // "label" : "Convert", - // "_disabled" : false, - // "separator_before" : false, - // "separator_after" : true, - // "submenu" : { - // "Convert to Spatial" : { - // "label" : "To Spatial Model", - // "_disabled" : false, - // "separator_before" : false, - // "separator_after" : false, - // "action" : function (data) { - // self.toSpatial(o) - // } - // }, - // "Convert to SBML" : { - // "label" : "To SBML Model", - // "_disabled" : false, - // "separator_before" : false, - // "separator_after" : false, - // "action" : function (data) { - // self.toSBML(o) - // } - // } - // } - // } - // } - // convert options for spatial models - // let spatialConvert = { - // "Convert" : { - // "label" : "Convert", - // "_disabled" : false, - // "separator_before" : false, - // "separator_after" : true, - // "submenu" : { - // "Convert to Model" : { - // "label" : "Convert to Model", - // "_disabled" : false, - // "separator_before" : false, - // "separator_after" : false, - // "action" : function (data) { - // self.toModel(o, "Spatial"); - // } - // } - // } - // } - // } - // specific to workflows - // let workflow = { - // "Start/Restart Workflow" : { - // "label" : (o.original._status === "ready") ? "Start Workflow" : "Restart Workflow", - // "_disabled" : true, - // "separator_before" : false, - // "separator_after" : false, - // "action" : function (data) { - - // } - // }, - // "Stop Workflow" : { - // "label" : "Stop Workflow", - // "_disabled" : true, - // "separator_before" : false, - // "separator_after" : false, - // "action" : function (data) { - - // } - // }, - // "Model" : { - // "label" : "Model", - // "_disabled" : false, - // "separator_before" : false, - // "separator_after" : false, - // "submenu" : { - // "Edit" : { - // "label" : " Edit", - // "_disabled" : (!o.original._newFormat && o.original._status !== "ready"), - // "separator_before" : false, - // "separator_after" : false, - // "action" : function (data) { - // self.editWorkflowModel(o) - // } - // }, - // "Extract" : { - // "label" : "Extract", - // "_disabled" : (o.original._newFormat && !o.original._hasJobs), - // "separator_before" : false, - // "separator_after" : false, - // "action" : function (data) { - // self.duplicateFileOrDirectory(o, "wkfl_model") - // } - // } - // } - // }, - // "Extract" : { - // "label" : "Extract", - // "_disabled" : false, - // "separator_before" : false, - // "separator_after" : true, - // "action" : function (data) { - // self.handleExportWorkflowClick(o) - // } - // }, - // } - // Specific to sbml files - // let sbml = { - // "Convert" : { - // "label" : "Convert", - // "_disabled" : false, - // "separator_before" : false, - // "separator_after" : false, - // "submenu" : { - // "Convert to Model" : { - // "label" : "To Model", - // "_disabled" : false, - // "separator_before" : false, - // "separator_after" : false, - // "action" : function (data) { - // self.toModel(o, "SBML"); - // } - // } - // } - // } - // } - // common to all type except root and trash - // let common = { - // "Rename" : { - // "label" : "Rename", - // "_disabled" : (o.type === "workflow" && o.original._status === "running"), - // "separator_before" : false, - // "separator_after" : false, - // "action" : function (data) { - // self.renameNode(o); - // } - // }, - // "Duplicate" : { - // "label" : (nodeType === "workflow") ? "Duplicate as new" : "Duplicate", - // "_disabled" : (nodeType === "project" || nodeType === "workflow-group"), - // "separator_before" : false, - // "separator_after" : false, - // "action" : function (data) { - // self.duplicateFileOrDirectory(o, null) - // } - // }, - // "Delete" : { - // "label" : "Delete", - // "_disabled" : false, - // "separator_before" : false, - // "separator_after" : false, - // "action" : function (data) { - // self.deleteFile(o); - // } - // } - // } - //Specific to zip archives - // let extractAll = { - // "extractAll" : { - // "label" : "Extract All", - // "_disabled" : false, - // "separator_before" : false, - // "separator_after" : false, - // "action" : function (data) { - // self.extractAll(o); - // } - // } - // } - // let notebook = { - // "publish" : { - // "label" : "Publish", - // "_disabled" : false, - // "separator_before" : false, - // "separator_after" : false, - // "action" : function (data) { - // self.publishNotebookPresentation(o); - // } - // } - // } - // if (o.type === 'root'){ - // return $.extend(refresh, project, commonFolder, downloadWCombine, {"Rename": common.Rename}) - // } - // if (o.text === "trash"){ // Trash node - // return refresh - // } - // if (o.original._path.includes(".proj/trash/")) { //item in trash - // return {"Delete": common.Delete} - // } - // if (o.type === 'folder') { // folder node - // return $.extend(refresh, commonFolder, download, common) - // } - // if (o.type === 'spatial') { // spatial model node - // return $.extend(commonModel, spatialConvert, download, common) - // } - // if (o.type === 'nonspatial') { // model node - // return $.extend(commonModel, modelConvert, download, common) - // } - // if (o.type === 'workflow-group') { - // return $.extend(refresh, downloadWCombine) - // } - // if (o.type === 'workflow') { // workflow node - // return $.extend(open, workflow, downloadWCombine, common) - // } - // if (o.text.endsWith(".zip")) { // zip archove node - // return $.extend(open, extractAll, download, common) - // } - // if (o.type === 'notebook') { // notebook node - // if(app.getBasePath() === "/") { - // return $.extend(open, download, common) - // } - // return $.extend(open, notebook, download, common) - // } - // if (o.type === 'other') { // other nodes - // return $.extend(open, download, common) - // } - // if (o.type === 'sbml-model') { // sbml model node - // return $.extend(open, sbml, common) - // } - // if (o.type === "domain") { // domain node - // return $.extend(open, common) - // } - // } - // $(document).ready(function () { - // $(document).on('shown.bs.modal', function (e) { - // $('[autofocus]', e.target).focus(); - // }); - // $(document).on('dnd_start.vakata', function (data, element, helper, event) { - // $('#models-jstree-view').jstree().load_all() - // }); - // $('#models-jstree-view').jstree(self.treeSettings).bind("loaded.jstree", function (event, data) { - // self.jstreeIsLoaded = true - // }).bind("refresh.jstree", function (event, data) { - // self.jstreeIsLoaded = true - // }); - // $('#models-jstree-view').on('click.jstree', function(e) { - // var parent = e.target.parentElement - // var _node = parent.children[parent.children.length - 1] - // var node = $('#models-jstree-view').jstree().get_node(_node) - // if(_node.nodeName === "A" && $('#models-jstree-view').jstree().is_loaded(node) && node.type === "folder"){ - // $('#models-jstree-view').jstree().refresh_node(node) - // }else{ - // let optionsButton = $(self.queryByHook("options-for-node")) - // if(!self.nodeForContextMenu){ - // optionsButton.prop('disabled', false) - // } - // optionsButton.text("Actions for " + node.original.text) - // self.nodeForContextMenu = node; - // } - // }); - // $('#models-jstree-view').on('dblclick.jstree', function(e) { - // var file = e.target.text - // var node = $('#models-jstree-view').jstree().get_node(e.target) - // var _path = node.original._path; - // if(!_path.includes(".proj/trash/")){ - // if(file.endsWith('.mdl') || file.endsWith('.smdl')){ - // window.location.href = path.join(app.getBasePath(), "stochss/models/edit")+"?path="+_path; - // }else if(file.endsWith('.ipynb')){ - // var notebookPath = path.join(app.getBasePath(), "notebooks", _path) - // window.open(notebookPath, '_blank') - // }else if(file.endsWith('.sbml')){ - // var openPath = path.join(app.getBasePath(), "edit", _path) - // window.open(openPath, '_blank') - // }else if(file.endsWith('.proj')){ - // window.location.href = path.join(app.getBasePath(), "stochss/project/manager")+"?path="+_path; - // }else if(file.endsWith('.wkfl')){ - // window.location.href = path.join(app.getBasePath(), "stochss/workflow/edit")+"?path="+_path+"&type=none"; - // }else if(file.endsWith('.domn')) { - // let queryStr = "?domainPath=" + _path - // window.location.href = path.join(app.getBasePath(), "stochss/domain/edit") + queryStr - // }else if(node.type === "folder" && $('#models-jstree-view').jstree().is_open(node) && $('#models-jstree-view').jstree().is_loaded(node)){ - // $('#models-jstree-view').jstree().refresh_node(node) - // }else if(node.type === "other"){ - // var openPath = path.join(app.getBasePath(), "view", _path); - // window.open(openPath, "_blank"); - // } - // } - // }); - // }) - // }, - changeCollapseButtonText: function (e) { - app.changeCollapseButtonText(this, e); -// } -// }); \ No newline at end of file From dce67c3fa75bb497689c34143b55a50d4458b755 Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Thu, 2 Sep 2021 09:42:21 -0400 Subject: [PATCH 137/186] Renamed the file-browser.js file to browser.js. --- client/pages/{file-browser.js => browser.js} | 0 webpack.config.js | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename client/pages/{file-browser.js => browser.js} (100%) diff --git a/client/pages/file-browser.js b/client/pages/browser.js similarity index 100% rename from client/pages/file-browser.js rename to client/pages/browser.js diff --git a/webpack.config.js b/webpack.config.js index 440717e916..738e530859 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -6,7 +6,7 @@ module.exports = { entry: { home: './client/pages/users-home.js', quickstart: './client/pages/quickstart.js', - browser: './client/pages/file-browser.js', + browser: './client/pages/browser.js', editor: './client/pages/model-editor.js', domainEditor: './client/pages/domain-editor.js', workflowSelection: './client/pages/workflow-selection.js', From b812e56b3b838adb9e3ef7a448b39809bf863211 Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Thu, 2 Sep 2021 09:51:52 -0400 Subject: [PATCH 138/186] Removed old code from the browser file. --- client/pages/browser.js | 1428 +-------------------------------------- 1 file changed, 2 insertions(+), 1426 deletions(-) diff --git a/client/pages/browser.js b/client/pages/browser.js index ff9a028002..f368c1ae69 100644 --- a/client/pages/browser.js +++ b/client/pages/browser.js @@ -16,10 +16,8 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . */ -let jstree = require('jstree'); -let path = require('path'); let $ = require('jquery'); -let _ = require('underscore'); +let path = require('path'); //support files let app = require('../app'); let modals = require('../modals'); @@ -28,7 +26,6 @@ let Collection = require('ampersand-collection'); // form presentation browser //model let Project = require('../models/project'); // from project browser let Presentation = require('../models/presentation'); // form presentation browser -let Model = require('../models/model'); //views let PageView = require('./base'); let EditProjectView = require('../views/edit-project'); // from project browser @@ -43,17 +40,9 @@ let FileBrowser = PageView.extend({ pageTitle: 'StochSS | File Browser', template: template, events: { - // 'click [data-hook=refresh-jstree]' : 'refreshJSTree', - // 'click [data-hook=options-for-node]' : 'showContextMenuForNode', - // 'click [data-hook=new-directory]' : 'handleCreateDirectoryClick', - // 'click [data-hook=new-project]' : 'handleCreateProjectClick', - // 'click [data-hook=new-model]' : 'handleCreateModelClick', - // 'click [data-hook=new-domain]' : 'handleCreateDomain', - // 'click [data-hook=upload-file-btn]' : 'handleUploadFileClick', 'click [data-hook=file-browser-help]' : function () { let modal = $(modals.operationInfoModalHtml('file-browser')).modal(); }, - // 'click [data-hook=empty-trash]' : 'emptyTrash', 'click [data-hook=collapse-projects]' : 'changeCollapseButtonText', 'click [data-hook=collapse-presentations]' : 'changeCollapseButtonText', 'click [data-hook=collapse-files]' : 'changeCollapseButtonText' @@ -76,104 +65,6 @@ let FileBrowser = PageView.extend({ self.renderPresentationView(body.presentations); } }); - // End block from presentation browser - // this.root = "none" - // this.ajaxData = { - // "url" : function (node) { - // if(node.parent === null){ - // return path.join(app.getApiPath(), "file/browser-list")+"?path="+self.root - // } - // return path.join(app.getApiPath(), "file/browser-list")+"?path="+ node.original._path - // }, - // "dataType" : "json", - // "data" : function (node) { - // return { 'id' : node.id} - // }, - // } - // this.treeSettings = { - // 'plugins': ['types', 'wholerow', 'changed', 'contextmenu', 'dnd'], - // 'core': {'multiple' : false, 'animation': 0, - // 'check_callback': function (op, node, par, pos, more) { - // if(op === "rename_node" && self.validateName(pos, true) !== ""){ - // document.querySelector("#renameSpecialCharError").style.display = "block" - // setTimeout(function () { - // document.querySelector("#renameSpecialCharError").style.display = "none" - // }, 5000) - // return false - // } - // if(op === 'move_node' && node && node.type && node.type === "workflow" && node.original && node.original._status && node.original._status === "running"){ - // return false - // } - // if(op === 'move_node' && more && more.ref && more.ref.type && !(more.ref.type == 'folder' || more.ref.type == 'root')){ - // return false - // } - // if(op === 'move_node' && more && more.ref && more.ref.original && path.dirname(more.ref.original._path).split("/").includes("trash")){ - // return false - // } - // if(op === 'move_node' && more && more.ref && more.ref.type && more.ref.type === 'folder' && more.ref.text !== "trash"){ - // if(!more.ref.state.loaded){ - // return false - // } - // var exists = false - // var BreakException = {} - // try{ - // more.ref.children.forEach(function (child) { - // var child_node = $('#models-jstree').jstree().get_node(child) - // exists = child_node.text === node.text - // if(exists){ - // throw BreakException; - // } - // }) - // }catch{ - // return false; - // } - // } - // if(op === 'move_node' && more && (pos != 0 || more.pos !== "i") && !more.core){ - // return false - // } - // if(op === 'move_node' && more && more.core) { - // var newDir = par.type !== "root" ? par.original._path : "" - // var file = node.original._path.split('/').pop() - // var oldPath = node.original._path - // let queryStr = "?srcPath="+oldPath+"&dstPath="+path.join(newDir, file) - // var endpoint = path.join(app.getApiPath(), "file/move")+queryStr - // app.getXHR(endpoint, { - // success: function (err, response, body) { - // node.original._path = path.join(newDir, file); - // if(node.type === "folder") { - // $('#models-jstree').jstree().refresh_node(node); - // }else if(newDir.endsWith("trash")) { - // $(self.queryByHook('empty-trash')).prop('disabled', false); - // $('#models-jstree').jstree().refresh_node(par); - // }else if(oldPath.split("/").includes("trash")) { - // $('#models-jstree').jstree().refresh_node(par); - // } - // }, - // error: function (err, response, body) { - // body = JSON.parse(body); - // $('#models-jstree').jstree().refresh(); - // } - // }); - // } - // return true - // }, - // 'themes': {'stripes': true, 'variant': 'large'}, - // 'data': this.ajaxData, - // }, - // 'types' : { - // 'root' : {"icon": "jstree-icon jstree-folder"}, - // 'folder' : {"icon": "jstree-icon jstree-folder"}, - // 'spatial' : {"icon": "jstree-icon jstree-file"}, - // 'nonspatial' : {"icon": "jstree-icon jstree-file"}, - // 'project' : {"icon": "jstree-icon jstree-file"}, - // 'workflow-group' : {"icon": "jstree-icon jstree-file"}, - // 'workflow' : {"icon": "jstree-icon jstree-file"}, - // 'notebook' : {"icon": "jstree-icon jstree-file"}, - // 'domain' : {"icon": "jstree-icon jstree-file"}, - // 'sbml-model' : {"icon": "jstree-icon jstree-file"}, - // 'other' : {"icon": "jstree-icon jstree-file"}, - // }, - // } }, render: function (attrs, options) { PageView.prototype.render.apply(this, arguments) @@ -181,21 +72,6 @@ let FileBrowser = PageView.extend({ configKey: "file" }); app.registerRenderSubview(this, jstreeView, "jstree-view-container"); - // var self = this; - // this.nodeForContextMenu = ""; - // this.renderWithTemplate(); - // this.jstreeIsLoaded = false - // window.addEventListener('pageshow', function (e) { - // var navType = window.performance.navigation.type - // if(navType === 2){ - // window.location.reload() - // } - // }); - // this.setupJstree(function () { - // setTimeout(function () { - // self.refreshInitialJSTree(); - // }, 3000); - // }); $(document).on('hide.bs.modal', '.modal', function (e) { e.target.remove() }); @@ -222,1307 +98,7 @@ let FileBrowser = PageView.extend({ }, changeCollapseButtonText: function (e) { app.changeCollapseButtonText(this, e); - }, - // refreshJSTree: function () { - // this.jstreeIsLoaded = false - // $('#models-jstree').jstree().deselect_all(true) - // $('#models-jstree').jstree().refresh() - // }, - // refreshInitialJSTree: function () { - // var self = this; - // var count = $('#models-jstree').jstree()._model.data['#'].children.length; - // if(count == 0) { - // self.refreshJSTree(); - // setTimeout(function () { - // self.refreshInitialJSTree(); - // }, 3000); - // } - // }, - // selectNode: function (node, fileName) { - // let self = this - // if(!this.jstreeIsLoaded || !$('#models-jstree').jstree().is_loaded(node) && $('#models-jstree').jstree().is_loading(node)) { - // setTimeout(_.bind(self.selectNode, self, node, fileName), 1000); - // }else{ - // node = $('#models-jstree').jstree().get_node(node) - // var child = "" - // for(var i = 0; i < node.children.length; i++) { - // var child = $('#models-jstree').jstree().get_node(node.children[i]) - // if(child.original.text === fileName) { - // $('#models-jstree').jstree().select_node(child) - // let optionsButton = $(self.queryByHook("options-for-node")) - // if(!self.nodeForContextMenu){ - // optionsButton.prop('disabled', false) - // } - // optionsButton.text("Actions for " + child.original.text) - // self.nodeForContextMenu = child; - // break - // } - // } - // } - // }, - // handleUploadFileClick: function (e) { - // let type = e.target.dataset.type - // this.uploadFile(undefined, type) - // }, - // uploadFile: function (o, type) { - // var self = this - // if(document.querySelector('#uploadFileModal')) { - // document.querySelector('#uploadFileModal').remove() - // } - // if(this.browser == undefined) { - // this.browser = app.getBrowser(); - // } - // if(this.isSafariV14Plus == undefined){ - // this.isSafariV14Plus = (this.browser.name === "Safari" && this.browser.version >= 14) - // } - // let modal = $(modals.uploadFileHtml(type, this.isSafariV14Plus)).modal(); - // let uploadBtn = document.querySelector('#uploadFileModal .upload-modal-btn'); - // let fileInput = document.querySelector('#uploadFileModal #fileForUpload'); - // let input = document.querySelector('#uploadFileModal #fileNameInput'); - // let fileCharErrMsg = document.querySelector('#uploadFileModal #fileSpecCharError') - // let nameEndErrMsg = document.querySelector('#uploadFileModal #fileNameInputEndCharError') - // let nameCharErrMsg = document.querySelector('#uploadFileModal #fileNameInputSpecCharError') - // let nameUsageMsg = document.querySelector('#uploadFileModal #fileNameUsageMessage') - // fileInput.addEventListener('change', function (e) { - // let fileErr = !fileInput.files.length ? "" : self.validateName(fileInput.files[0].name) - // let nameErr = self.validateName(input.value) - // if(!fileInput.files.length) { - // uploadBtn.disabled = true - // fileCharErrMsg.style.display = 'none' - // }else if(fileErr === "" || (Boolean(input.value) && nameErr === "")){ - // uploadBtn.disabled = false - // fileCharErrMsg.style.display = 'none' - // }else{ - // uploadBtn.disabled = true - // fileCharErrMsg.style.display = 'block' - // } - // }) - // input.addEventListener("input", function (e) { - // let fileErr = !fileInput.files.length ? "" : self.validateName(fileInput.files[0].name) - // let nameErr = self.validateName(input.value) - // if(!fileInput.files.length) { - // uploadBtn.disabled = true - // fileCharErrMsg.style.display = 'none' - // }else if(fileErr === "" || (Boolean(input.value) && nameErr === "")){ - // uploadBtn.disabled = false - // fileCharErrMsg.style.display = 'none' - // }else{ - // uploadBtn.disabled = true - // fileCharErrMsg.style.display = 'block' - // } - // nameCharErrMsg.style.display = nameErr === "both" || nameErr === "special" ? "block" : "none" - // nameEndErrMsg.style.display = nameErr === "both" || nameErr === "forward" ? "block" : "none" - // nameUsageMsg.style.display = nameErr !== "" ? "block" : "none" - // }); - // uploadBtn.addEventListener('click', function (e) { - // let file = fileInput.files[0] - // var fileinfo = {"type":type,"name":"","path":"/"} - // if(o && o.original){ - // fileinfo.path = o.original._path - // } - // if(Boolean(input.value) && self.validateName(input.value) === ""){ - // fileinfo.name = input.value.trim() - // } - // let formData = new FormData() - // formData.append("datafile", file) - // formData.append("fileinfo", JSON.stringify(fileinfo)) - // let endpoint = path.join(app.getApiPath(), 'file/upload'); - // app.postXHR(endpoint, formData, { - // success: function (err, response, body) { - // body = JSON.parse(body); - // if(o){ - // var node = $('#models-jstree').jstree().get_node(o.parent); - // if(node.type === "root" || node.type === "#"){ - // self.refreshJSTree(); - // }else{ - // $('#models-jstree').jstree().refresh_node(node); - // } - // }else{ - // self.refreshJSTree(); - // } - // if(body.errors.length > 0){ - // let errorModal = $(modals.uploadFileErrorsHtml(file.name, type, body.message, body.errors)).modal(); - // } - // }, - // error: function (err, response, body) { - // body = JSON.parse(body); - // let zipErrorModal = $(modals.projectExportErrorHtml(resp.Reason, resp.Message)).modal(); - // } - // }, false); - // modal.modal('hide') - // }); - // }, - // deleteFile: function (o) { - // var fileType = o.type - // if(fileType === "nonspatial") - // fileType = "model"; - // else if(fileType === "spatial") - // fileType = "spatial model" - // else if(fileType === "sbml-model") - // fileType = "sbml model" - // else if(fileType === "other") - // fileType = "file" - // var self = this - // if(document.querySelector('#deleteFileModal')) { - // document.querySelector('#deleteFileModal').remove() - // } - // let modal = $(modals.deleteFileHtml(fileType)).modal(); - // let yesBtn = document.querySelector('#deleteFileModal .yes-modal-btn'); - // yesBtn.addEventListener('click', function (e) { - // var endpoint = path.join(app.getApiPath(), "file/delete")+"?path="+o.original._path - // app.getXHR(endpoint, { - // success: function (err, response, body) { - // var node = $('#models-jstree').jstree().get_node(o.parent); - // if(node.type === "root"){ - // self.refreshJSTree(); - // let actionsBtn = $(self.queryByHook("options-for-node")); - // if(actionsBtn.text().endsWith(o.text)) { - // actionsBtn.text("Actions"); - // actionsBtn.prop("disabled", true); - // self.nodeForContextMenu = ""; - // } - // }else{ - // $('#models-jstree').jstree().refresh_node(node); - // } - // }, - // error: function (err, response, body) { - // body = JSON.parse(body); - // } - // }); - // modal.modal('hide') - // }); - // }, - // duplicateFileOrDirectory: function(o, type) { - // var self = this; - // var parentID = o.parent; - // var queryStr = "?path="+o.original._path - // if(!type && o.original.type === 'folder'){ - // type = "directory" - // }else if(!type && o.original.type === 'workflow'){ - // type = "workflow" - // }else if(!type){ - // type = "file" - // } - // if(type === "directory"){ - // var identifier = "directory/duplicate" - // }else if(type === "workflow" || type === "wkfl_model"){ - // var timeStamp = type === "workflow" ? this.getTimeStamp() : "None" - // var identifier = "workflow/duplicate" - // queryStr = queryStr.concat("&target="+type+"&stamp="+timeStamp) - // }else{ - // var identifier = "file/duplicate" - // } - // var endpoint = path.join(app.getApiPath(), identifier)+queryStr - // app.getXHR(endpoint, { - // success: function (err, response, body) { - // var node = $('#models-jstree').jstree().get_node(parentID); - // if(node.type === "root"){ - // self.refreshJSTree(); - // }else{ - // $('#models-jstree').jstree().refresh_node(node); - // } - // if(type === "workflow"){ - // var message = "" - // if(body.error){ - // message = body.error; - // }else{ - // message = "The model for "+body.File+" is located here: "+body.mdlPath+""; - // } - // let modal = $(modals.duplicateWorkflowHtml(body.File, message)).modal(); - // } - // self.selectNode(node, body.File); - // } - // }); - // }, - // getTimeStamp: function () { - // var date = new Date(); - // var year = date.getFullYear(); - // var month = date.getMonth() + 1; - // if(month < 10){ - // month = "0" + month - // } - // var day = date.getDate(); - // if(day < 10){ - // day = "0" + day - // } - // var hours = date.getHours(); - // if(hours < 10){ - // hours = "0" + hours - // } - // var minutes = date.getMinutes(); - // if(minutes < 10){ - // minutes = "0" + minutes - // } - // var seconds = date.getSeconds(); - // if(seconds < 10){ - // seconds = "0" + seconds - // } - // return "_" + month + day + year + "_" + hours + minutes + seconds; - // }, - // toSpatial: function (o) { - // var self = this; - // var parentID = o.parent; - // var endpoint = path.join(app.getApiPath(), "model/to-spatial")+"?path="+o.original._path; - // app.getXHR(endpoint, { - // success: function (err, response, body) { - // var node = $('#models-jstree').jstree().get_node(parentID); - // if(node.type === "root"){ - // self.refreshJSTree(); - // }else{ - // $('#models-jstree').jstree().refresh_node(node); - // } - // self.selectNode(node, body.File); - // } - // }); - // }, - // toModel: function (o, from) { - // var self = this; - // var parentID = o.parent; - // if(from === "Spatial"){ - // var identifier = "spatial/to-model" - // }else{ - // var identifier = "sbml/to-model" - // } - // let endpoint = path.join(app.getApiPath(), identifier)+"?path="+o.original._path; - // app.getXHR(endpoint, { - // success: function (err, response, body) { - // var node = $('#models-jstree').jstree().get_node(parentID); - // if(node.type === "root"){ - // self.refreshJSTree(); - // }else{ - // $('#models-jstree').jstree().refresh_node(node); - // } - // self.selectNode(node, body.File); - // if(from === "SBML" && body.errors.length > 0){ - // var title = ""; - // var msg = body.message; - // var errors = body.errors; - // let modal = $(modals.sbmlToModelHtml(msg, errors)).modal(); - // } - // } - // }); - // }, - toNotebook: function (o, type) { - let self = this - var endpoint = "" - if(type === "model"){ - endpoint = path.join(app.getApiPath(), "model/to-notebook")+"?path="+o.original._path - }else{ - endpoint = path.join(app.getApiPath(), "workflow/notebook")+"?type=none&path="+o.original._path - } - app.getXHR(endpoint, { - success: function (err, response, body) { - var node = $('#models-jstree').jstree().get_node(o.parent); - if(node.type === 'root'){ - self.refreshJSTree(); - }else{ - $('#models-jstree').jstree().refresh_node(node); - } - var notebookPath = path.join(app.getBasePath(), "notebooks", body.FilePath); - self.selectNode(node, body.File); - window.open(notebookPath); - } - }); - }, - // toSBML: function (o) { - // var self = this; - // var parentID = o.parent; - // var endpoint = path.join(app.getApiPath(), "model/to-sbml")+"?path="+o.original._path; - // app.getXHR(endpoint, { - // success: function (err, response, body) { - // var node = $('#models-jstree').jstree().get_node(parentID); - // if(node.type === "root"){ - // self.refreshJSTree(); - // }else{ - // $('#models-jstree').jstree().refresh_node(node); - // } - // self.selectNode(node, body.File); - // } - // }); - // }, - // renameNode: function (o) { - // var self = this - // var text = o.text; - // var parent = $('#models-jstree').jstree().get_node(o.parent) - // var extensionWarning = $(this.queryByHook('extension-warning')); - // var nameWarning = $(this.queryByHook('rename-warning')); - // extensionWarning.collapse('show') - // $('#models-jstree').jstree().edit(o, null, function(node, status) { - // if(text != node.text){ - // var endpoint = path.join(app.getApiPath(), "file/rename")+"?path="+ o.original._path+"&name="+node.text - // app.getXHR(endpoint, { - // always: function (err, response, body) { - // if(parent.type === "root"){ - // self.refreshJSTree(); - // }else{ - // $('#models-jstree').jstree().refresh_node(parent); - // } - // }, - // success: function (err, response, body) { - // if(body.changed) { - // nameWarning.text(body.message); - // nameWarning.collapse('show'); - // window.scrollTo(0,0); - // setTimeout(_.bind(self.hideNameWarning, self), 10000); - // } - // node.original._path = body._path; - // } - // }); - // } - // extensionWarning.collapse('hide'); - // nameWarning.collapse('hide'); - // }); - // }, - // hideNameWarning: function () { - // $(this.queryByHook('rename-warning')).collapse('hide') - // }, - // getExportData: function (o, asZip) { - // var self = this; - // let nodeType = o.original.type - // let isJSON = nodeType === "sbml-model" ? false : true - // if(nodeType === "sbml-model"){ - // var dataType = "plain-text" - // var identifier = "file/download" - // }else if(nodeType === "domain") { - // var dataType = "json" - // var identifier = "spatial-model/load-domain" - // }else if(asZip) { - // var dataType = "zip" - // var identifier = "file/download-zip" - // }else{ - // var dataType = "json" - // var identifier = "file/json-data" - // } - // if(nodeType === "domain") { - // var queryStr = "?domain_path=" + o.original._path - // }else{ - // var queryStr = "?path="+o.original._path - // if(dataType === "json"){ - // queryStr = queryStr.concat("&for=None") - // }else if(dataType === "zip"){ - // queryStr = queryStr.concat("&action=generate") - // } - // } - // var endpoint = path.join(app.getApiPath(), identifier)+queryStr - // app.getXHR(endpoint, { - // success: function (err, response, body) { - // if(dataType === "json") { - // let data = nodeType === "domain" ? body.domain : body; - // self.exportToJsonFile(data, o.original.text); - // }else if(dataType === "zip") { - // var node = $('#models-jstree').jstree().get_node(o.parent); - // if(node.type === "root"){ - // self.refreshJSTree(); - // }else{ - // $('#models-jstree').jstree().refresh_node(node); - // } - // self.exportToZipFile(body.Path); - // }else{ - // self.exportToFile(body, o.original.text); - // } - // }, - // error: function (err, response, body) { - // if(dataType === "plain-text") { - // body = JSON.parse(body); - // } - // } - // }); - // }, - // exportToJsonFile: function (fileData, fileName) { - // let dataStr = JSON.stringify(fileData); - // let dataURI = 'data:application/json;charset=utf-8,' + encodeURIComponent(dataStr); - // let exportFileDefaultName = fileName - - // let linkElement = document.createElement('a'); - // linkElement.setAttribute('href', dataURI); - // linkElement.setAttribute('download', exportFileDefaultName); - // linkElement.click(); - // }, - // exportToFile: function (fileData, fileName) { - // let dataURI = 'data:text/plain;charset=utf-8,' + encodeURIComponent(fileData); - - // let linkElement = document.createElement('a'); - // linkElement.setAttribute('href', dataURI); - // linkElement.setAttribute('download', fileName); - // linkElement.click(); - // }, - // exportToZipFile: function (o) { - // var targetPath = o - // if(o.original){ - // targetPath = o.original._path - // } - // var endpoint = path.join(app.getBasePath(), "/files", targetPath); - // window.open(endpoint) - // }, - // validateName: function (input, rename = false) { - // var error = "" - // if(input.endsWith('/')) { - // error = 'forward' - // } - // var invalidChars = "`~!@#$%^&*=+[{]}\"|:;'<,>?\\" - // if(rename) { - // invalidChars += "/" - // } - // for(var i = 0; i < input.length; i++) { - // if(invalidChars.includes(input.charAt(i))) { - // error = error === "" || error === "special" ? "special" : "both" - // } - // } - // return error - // }, - // newProjectOrWorkflowGroup: function (o, isProject) { - // var self = this - // if(document.querySelector("#newProjectModal")) { - // document.querySelector("#newProjectModal").remove() - // } - // var modal = $(modals.newProjectModalHtml()).modal(); - // var okBtn = document.querySelector('#newProjectModal .ok-model-btn'); - // var input = document.querySelector('#newProjectModal #projectNameInput'); - // input.addEventListener("keyup", function (event) { - // if(event.keyCode === 13){ - // event.preventDefault(); - // okBtn.click(); - // } - // }); - // input.addEventListener("input", function (e) { - // var endErrMsg = document.querySelector('#newProjectModal #projectNameInputEndCharError') - // var charErrMsg = document.querySelector('#newProjectModal #projectNameInputSpecCharError') - // let error = self.validateName(input.value) - // okBtn.disabled = error !== "" || input.value.trim() === "" - // charErrMsg.style.display = error === "both" || error === "special" ? "block" : "none" - // endErrMsg.style.display = error === "both" || error === "forward" ? "block" : "none" - // }); - // okBtn.addEventListener("click", function (e) { - // if(Boolean(input.value)) { - // modal.modal('hide') - // var parentPath = "" - // if(o && o.original && o.original.type !== "root") { - // parentPath = o.original._path - // } - // var projectName = input.value.trim() + ".proj" - // var projectPath = path.join(parentPath, projectName) - // var endpoint = path.join(app.getApiPath(), "project/new-project")+"?path="+projectPath - // app.getXHR(endpoint, { - // success: function (err, response, body) { - // let queryStr = "?path=" + body.path; - // let endpoint = path.join(app.getBasePath(), 'stochss/project/manager') + queryStr; - // window.location.href = endpoint; - // }, - // error: function (err, response, body) { - // let errorModel = $(modals.newProjectOrWorkflowGroupErrorHtml(body.Reason, body.Message)).modal(); - // } - // }); - // } - // }); - // }, - // newWorkflow: function (o, type) { - // let self = this; - // let model = new Model({ - // directory: o.original._path - // }); - // app.getXHR(model.url(), { - // success: function (err, response, body) { - // model.set(body); - // model.updateValid(); - // if(model.valid){ - // app.newWorkflow(self, o.original._path, o.type === "spatial", type); - // }else{ - // let title = "Model Errors Detected"; - // let endpoint = path.join(app.getBasePath(), "stochss/models/edit") + '?path=' + model.directory + '&validate'; - // let message = 'Errors were detected in you model
click here to fix your model'; - // $(modals.modelErrorHtml(title, message)).modal(); - // } - // } - // }); - // }, - // addExistingModel: function (o) { - // var self = this - // if(document.querySelector('#newProjectModelModal')){ - // document.querySelector('#newProjectModelModal').remove() - // } - // let mdlListEP = path.join(app.getApiPath(), 'project/add-existing-model') + "?path="+o.original._path - // app.getXHR(mdlListEP, { - // always: function (err, response, body) { - // let modal = $(modals.newProjectModelHtml(body.files)).modal(); - // let okBtn = document.querySelector('#newProjectModelModal .ok-model-btn'); - // let select = document.querySelector('#newProjectModelModal #modelFileInput'); - // let location = document.querySelector('#newProjectModelModal #modelPathInput'); - // select.addEventListener("change", function (e) { - // okBtn.disabled = e.target.value && body.paths[e.target.value].length >= 2; - // if(body.paths[e.target.value].length >= 2) { - // var locations = body.paths[e.target.value].map(function (path) { - // return ``; - // }); - // locations.unshift(``); - // locations = locations.join(" "); - // $("#modelPathInput").find('option').remove().end().append(locations); - // $("#location-container").css("display", "block"); - // }else{ - // $("#location-container").css("display", "none"); - // $("#modelPathInput").find('option').remove().end(); - // } - // }); - // location.addEventListener("change", function (e) { - // okBtn.disabled = !Boolean(e.target.value); - // }); - // okBtn.addEventListener("click", function (e) { - // let mdlPath = body.paths[select.value].length < 2 ? body.paths[select.value][0] : location.value; - // let queryString = "?path="+o.original._path+"&mdlPath="+mdlPath; - // let endpoint = path.join(app.getApiPath(), 'project/add-existing-model') + queryString; - // app.postXHR(endpoint, null, { - // success: function (err, response, body) { - // let successModal = $(modals.newProjectModelSuccessHtml(body.message)).modal(); - // }, - // error: function (err, response, body) { - // let errorModal = $(modals.newProjectModelErrorHtml(body.Reason, body.Message)).modal(); - // } - // }); - // modal.modal('hide'); - // }); - // } - // }); - // }, - // addModel: function (parentPath, modelName, message) { - // var endpoint = path.join(app.getBasePath(), "stochss/models/edit") - // if(parentPath.endsWith(".proj")) { - // let queryString = "?path=" + parentPath + "&mdlFile=" + modelName - // let newMdlEP = path.join(app.getApiPath(), "project/new-model") + queryString - // app.getXHR(newMdlEP, { - // success: function (err, response, body) { - // endpoint += "?path="+body.path; - // window.location.href = endpoint; - // }, - // error: function (err, response, body) { - // let title = "Model Already Exists"; - // let message = "A model already exists with that name"; - // let errorModel = $(modals.newProjectOrWorkflowGroupErrorHtml(title, message)).modal(); - // } - // }); - // }else{ - // let modelPath = path.join(parentPath, modelName) - // let queryString = "?path="+modelPath+"&message="+message; - // endpoint += queryString - // let existEP = path.join(app.getApiPath(), "model/exists")+queryString - // app.getXHR(existEP, { - // always: function (err, response, body) { - // if(body.exists) { - // let title = "Model Already Exists"; - // let message = "A model already exists with that name"; - // let errorModel = $(modals.newProjectOrWorkflowGroupErrorHtml(title, message)).modal(); - // }else{ - // window.location.href = endpoint; - // } - // } - // }); - // } - // }, - // newModelOrDirectory: function (o, isModel, isSpatial) { - // var self = this - // if(document.querySelector('#newModalModel')) { - // document.querySelector('#newModalModel').remove() - // } - // let modal = $(modals.renderCreateModalHtml(isModel, isSpatial)).modal(); - // let okBtn = document.querySelector('#newModalModel .ok-model-btn'); - // let input = document.querySelector('#newModalModel #modelNameInput'); - // input.addEventListener("keyup", function (event) { - // if(event.keyCode === 13){ - // event.preventDefault(); - // okBtn.click(); - // } - // }); - // input.addEventListener("input", function (e) { - // var endErrMsg = document.querySelector('#newModalModel #modelNameInputEndCharError') - // var charErrMsg = document.querySelector('#newModalModel #modelNameInputSpecCharError') - // let error = self.validateName(input.value) - // okBtn.disabled = error !== "" || input.value.trim() === "" - // charErrMsg.style.display = error === "both" || error === "special" ? "block" : "none" - // endErrMsg.style.display = error === "both" || error === "forward" ? "block" : "none" - // }); - // okBtn.addEventListener('click', function (e) { - // if (Boolean(input.value)) { - // modal.modal('hide') - // var parentPath = "" - // if(o && o.original && o.original.type !== "root"){ - // parentPath = o.original._path - // } - // if(isModel) { - // let ext = isSpatial ? ".smdl" : ".mdl" - // let modelName = o && o.type === "project" ? input.value.trim().split("/").pop() + ext : input.value.trim() + ext; - // let message = modelName !== input.value.trim() + ext? - // "Warning: Models are saved directly in StochSS Projects and cannot be saved to the "+input.value.trim().split("/")[0]+" directory in the project.

Do you wish to save your model directly in your project?

" : "" - // if(message){ - // let warningModal = $(modals.newProjectModelWarningHtml(message)).modal() - // let yesBtn = document.querySelector('#newProjectModelWarningModal .yes-modal-btn'); - // yesBtn.addEventListener('click', function (e) { - // warningModal.modal('hide') - // self.addModel(parentPath, modelName, message); - // }); - // }else{ - // self.addModel(parentPath, modelName, message); - // } - // }else{ - // let dirName = input.value.trim(); - // let endpoint = path.join(app.getApiPath(), "directory/create")+"?path="+path.join(parentPath, dirName); - // app.getXHR(endpoint, { - // success: function (err, response, body) { - // if(o){//directory was created with context menu option - // var node = $('#models-jstree').jstree().get_node(o); - // if(node.type === "root"){ - // self.refreshJSTree(); - // }else{ - // $('#models-jstree').jstree().refresh_node(node); - // } - // }else{//directory was created with create directory button - // self.refreshJSTree(); - // } - // }, - // error: function (err, response, body) { - // body = JSON.parse(body); - // let errorModal = $(modals.newDirectoryErrorHtml(body.Reason, body.Message)).modal(); - // } - // }); - // } - // } - // }); - // }, - // handleCreateDirectoryClick: function (e) { - // this.newModelOrDirectory(undefined, false, false); - // }, - // handleCreateProjectClick: function (e) { - // this.newProjectOrWorkflowGroup(undefined, true) - // }, - // handleCreateModelClick: function (e) { - // let isSpatial = e.target.dataset.type === "spatial" - // this.newModelOrDirectory(undefined, true, isSpatial); - // }, - // handleCreateDomain: function (e) { - // let queryStr = "?domainPath=/&new" - // window.location.href = path.join(app.getBasePath(), "stochss/domain/edit") + queryStr - // }, - // showContextMenuForNode: function (e) { - // $('#models-jstree').jstree().show_contextmenu(this.nodeForContextMenu) - // }, - // editWorkflowModel: function (o) { - // let endpoint = path.join(app.getApiPath(), "workflow/edit-model")+"?path="+o.original._path - // app.getXHR(endpoint, { - // success: function (err, response, body) { - // if(body.error){ - // let title = o.text + " Not Found"; - // let message = body.error; - // let modal = $(modals.duplicateWorkflowHtml(title, message)).modal(); - // }else{ - // window.location.href = path.join(app.routePrefix, "models/edit")+"?path="+body.file; - // } - // } - // }); - // }, - // extractAll: function (o) { - // let self = this; - // let queryStr = "?path=" + o.original._path; - // let endpoint = path.join(app.getApiPath(), "file/unzip") + queryStr; - // app.getXHR(endpoint, { - // success: function (err, response, body) { - // let node = $('#models-jstree').jstree().get_node(o.parent); - // if(node.type === "root"){ - // self.refreshJSTree(); - // }else{ - // $('#models-jstree').jstree().refresh_node(node); - // } - // }, - // error: function (err, response, body) { - // let modal = $(modals.newProjectModelErrorHtml(body.Reason, body.message)).modal(); - // } - // }); - // }, - // moveToTrash: function (o) { - // if(document.querySelector('#moveToTrashConfirmModal')) { - // document.querySelector('#moveToTrashConfirmModal').remove(); - // } - // let self = this; - // let modal = $(modals.moveToTrashConfirmHtml("model")).modal(); - // let yesBtn = document.querySelector('#moveToTrashConfirmModal .yes-modal-btn'); - // yesBtn.addEventListener('click', function (e) { - // modal.modal('hide'); - // let queryStr = "?srcPath=" + o.original._path + "&dstPath=" + path.join("trash", o.text) - // let endpoint = path.join(app.getApiPath(), "file/move") + queryStr - // app.getXHR(endpoint, { - // always: function (err, response, body) { - // $(self.queryByHook('empty-trash')).prop('disabled', false); - // $('#models-jstree').jstree().refresh(); - // } - // }); - // }); - // }, - // emptyTrash: function (e) { - // if(document.querySelector("#emptyTrashConfirmModal")) { - // document.querySelector("#emptyTrashConfirmModal").remove() - // } - // let self = this; - // let modal = $(modals.emptyTrashConfirmHtml()).modal(); - // let yesBtn = document.querySelector('#emptyTrashConfirmModal .yes-modal-btn'); - // yesBtn.addEventListener('click', function (e) { - // modal.modal('hide'); - // let endpoint = path.join(app.getApiPath(), "file/empty-trash") + "?path=trash"; - // app.getXHR(endpoint, { - // success: function (err, response, body) { - // self.refreshJSTree(); - // $(self.queryByHook('empty-trash')).prop('disabled', true); - // } - // }); - // }); - // }, - // publishNotebookPresentation: function (o) { - // let queryStr = "?path=" + o.original._path; - // let endpoint = path.join(app.getApiPath(), "notebook/presentation") + queryStr; - // app.getXHR(endpoint, { - // success: function (err, response, body) { - // let title = body.message; - // let linkHeaders = "Shareable Presentation"; - // let links = body.links; - // $(modals.presentationLinks(title, linkHeaders, links)).modal(); - // let copyBtn = document.querySelector('#presentationLinksModal #copy-to-clipboard'); - // copyBtn.addEventListener('click', function (e) { - // let onFulfilled = (value) => { - // $("#copy-link-success").css("display", "inline-block"); - // } - // let onReject = (reason) => { - // let msg = $("#copy-link-failed"); - // msg.html(reason); - // msg.css("display", "inline-block"); - // } - // app.copyToClipboard(links.presentation, onFulfilled, onReject); - // }); - // }, - // error: function (err, response, body) { - // $(modals.newProjectModelErrorHtml(body.Reason, body.Message)).modal(); - // } - // }); - // }, - // setupJstree: function () { - // var self = this; - // $.jstree.defaults.contextmenu.items = (o, cb) => { - // let optionsButton = $(self.queryByHook("options-for-node")) - // if(!self.nodeForContextMenu){ - // optionsButton.prop('disabled', false) - // } - // optionsButton.text("Actions for " + o.original.text) - // self.nodeForContextMenu = o; - // let nodeType = o.original.type - // let zipTypes = ["workflow", "folder", "other", "project", "workflow-group"] - // let asZip = zipTypes.includes(nodeType) - // common to all type except root - // let common = { - // "Download" : { - // "label" : asZip ? "Download as .zip" : "Download", - // "_disabled" : false, - // "separator_before" : true, - // "separator_after" : false, - // "action" : function (data) { - // if(o.original.text.endsWith('.zip')){ - // self.exportToZipFile(o); - // }else{ - // self.getExportData(o, asZip) - // } - // } - // }, - // "Rename" : { - // "label" : "Rename", - // "_disabled" : (o.type === "workflow" && o.original._status === "running"), - // "separator_before" : false, - // "separator_after" : false, - // "action" : function (data) { - // self.renameNode(o); - // } - // }, - // "Duplicate" : { - // "label" : (nodeType === "workflow") ? "Duplicate as new" : "Duplicate", - // "_disabled" : (nodeType === "project"), - // "separator_before" : false, - // "separator_after" : false, - // "action" : function (data) { - // self.duplicateFileOrDirectory(o, null) - // } - // }, - // "MoveToTrash" : { - // "label" : "Move To Trash", - // "_disabled" : false, - // "separator_before" : false, - // "separator_after" : false, - // "action" : function (data) { - // self.moveToTrash(o); - // } - // } - // } - // let delete_node = { - // "Delete" : { - // "label" : "Delete", - // "_disabled" : false, - // "separator_before" : false, - // "separator_after" : false, - // "action" : function (data) { - // self.deleteFile(o); - // } - // } - // } - // common to root and folders - // let folder = { - // "Refresh" : { - // "label" : "Refresh", - // "_disabled" : false, - // "_class" : "font-weight-bold", - // "separator_before" : false, - // "separator_after" : true, - // "action" : function (data) { - // if(nodeType === "root"){ - // self.refreshJSTree(); - // }else{ - // $('#models-jstree').jstree().refresh_node(o); - // } - // } - // }, - // "New_Directory" : { - // "label" : "New Directory", - // "_disabled" : false, - // "separator_before" : false, - // "separator_after" : false, - // "action" : function (data) { - // self.newModelOrDirectory(o, false, false); - // } - // }, - // "New Project" : { - // "label" : "New Project", - // "_disabled" : false, - // "separator_before" : false, - // "separator_after" : false, - // "action" : function (data) { - // self.newProjectOrWorkflowGroup(o, true) - // } - // }, - // "New_model" : { - // "label" : "New Model", - // "_disabled" : false, - // "separator_before" : false, - // "separator_after" : false, - // "submenu" : { - // "spatial" : { - // "label" : "Spatial (beta)", - // "_disabled" : false, - // "separator_before" : false, - // "separator_after" : false, - // "action" : function (data) { - // self.newModelOrDirectory(o, true, true); - // } - // }, - // "nonspatial" : { - // "label" : "Non-Spatial", - // "_disabled" : false, - // "separator_before" : false, - // "separator_after" : false, - // "action" : function (data) { - // self.newModelOrDirectory(o, true, false); - // } - // } - // } - // }, - // "New Domain" : { - // "label" : "New Domain (beta)", - // "_disabled" : false, - // "separator_before" : false, - // "separator_after" : false, - // "action" : function (data) { - // let queryStr = "?domainPath=" + o.original._path + "&new" - // window.location.href = path.join(app.getBasePath(), "stochss/domain/edit") + queryStr - // } - // }, - // "Upload" : { - // "label" : "Upload File", - // "_disabled" : false, - // "separator_before" : false, - // "separator_after" : false, - // "submenu" : { - // "Model" : { - // "label" : "StochSS Model", - // "_disabled" : false, - // "separator_before" : false, - // "separator_after" : false, - // "action" : function (data) { - // self.uploadFile(o, "model") - // } - // }, - // "SBML" : { - // "label" : "SBML Model", - // "_disabled" : false, - // "separator_before" : false, - // "separator_after" : false, - // "action" : function (data) { - // self.uploadFile(o, "sbml") - // } - // }, - // "File" : { - // "label" : "File", - // "_disabled" : false, - // "separator_before" : false, - // "separator_after" : false, - // "action" : function (data) { - // self.uploadFile(o, "file") - // } - // } - // } - // } - // } - // common to both spatial and non-spatial models - // let newWorkflow = { - // "ensembleSimulation" : { - // "label" : "Ensemble Simulation", - // "_disabled" : false, - // "separator_before" : false, - // "separator_after" : false, - // "action" : function (data) { - // self.newWorkflow(o, "Ensemble Simulation") - // } - // }, - // "parameterSweep" : { - // "label" : "Parameter Sweep", - // "_disabled" : false, - // "separator_before" : false, - // "separator_after" : false, - // "action" : function (data) { - // self.newWorkflow(o, "Parameter Sweep") - // } - // }, - // "jupyterNotebook" : { - // "label" : "Jupyter Notebook", - // "_disabled" : false, - // "separator_before" : false, - // "separator_after" : false, - // "action" : function (data) { - // window.location.href = path.join(app.getBasePath(), "stochss/workflow/selection")+"?path="+o.original._path; - // } - // } - // } - // let model = { - // "Edit" : { - // "label" : "Edit", - // "_disabled" : false, - // "_class" : "font-weight-bolder", - // "separator_before" : false, - // "separator_after" : true, - // "action" : function (data) { - // window.location.href = path.join(app.getBasePath(), "stochss/models/edit")+"?path="+o.original._path; - // } - // }, - // "New Workflow" : { - // "label" : "New Workflow", - // "_disabled" : false, - // "separator_before" : false, - // "separator_after" : false, - // "submenu" : o.type === "nonspatial" ? newWorkflow : {"jupyterNotebook":newWorkflow.jupyterNotebook} - // } - // } - // convert options for spatial models - // let spatialConvert = { - // "Convert" : { - // "label" : "Convert", - // "_disabled" : false, - // "separator_before" : false, - // "separator_after" : false, - // "submenu" : { - // "Convert to Model" : { - // "label" : "Convert to Model", - // "_disabled" : false, - // "separator_before" : false, - // "separator_after" : false, - // "action" : function (data) { - // self.toModel(o, "Spatial"); - // } - // }, - // "Convert to Notebook" : { - // "label" : "Convert to Notebook", - // "_disabled" : false, - // "separator_before" : false, - // "separator_after" : false, - // "action" : function (data) { - // self.toNotebook(o, "model") - // } - // } - // } - // } - // } - // convert options for non-spatial models - // let modelConvert = { - // "Convert" : { - // "label" : "Convert", - // "_disabled" : false, - // "separator_before" : false, - // "separator_after" : false, - // "submenu" : { - // "Convert to Spatial" : { - // "label" : "To Spatial Model", - // "_disabled" : false, - // "separator_before" : false, - // "separator_after" : false, - // "action" : function (data) { - // self.toSpatial(o) - // } - // }, - // "Convert to Notebook" : { - // "label" : "To Notebook", - // "_disabled" : false, - // "separator_before" : false, - // "separator_after" : false, - // "action" : function (data) { - // self.toNotebook(o, "model") - // } - // }, - // "Convert to SBML" : { - // "label" : "To SBML Model", - // "_disabled" : false, - // "separator_before" : false, - // "separator_after" : false, - // "action" : function (data) { - // self.toSBML(o) - // } - // } - // } - // } - // } - // For notebooks, workflows, sbml models, and other files - // let open = { - // "Open" : { - // "label" : "Open", - // "_disabled" : false, - // "_class" : "font-weight-bolder", - // "separator_before" : false, - // "separator_after" : true, - // "action" : function (data) { - // if(nodeType === "workflow"){ - // // window.location.href = path.join(app.getBasePath(), "stochss/workflow/edit")+"?path="+o.original._path+"&type=none"; - // // }else if(nodeType === "project"){ - // // window.location.href = path.join(app.getBasePath(), "stochss/project/manager")+"?path="+o.original._path - // }else if(nodeType === "domain") { - // let queryStr = "?domainPath=" + o.original._path - // window.location.href = path.join(app.getBasePath(), "stochss/domain/edit") + queryStr - // }else{ - // if(nodeType === "notebook") { - // var identifier = "notebooks" - // }else if(nodeType === "sbml-model") { - // var identifier = "edit" - // }else{ - // var identifier = "view" - // } - // window.open(path.join(app.getBasePath(), identifier, o.original._path)); - // } - // } - // } - // } - // let project = { - // "Add Model" : { - // "label" : "Add Model", - // "_disabled" : false, - // "separator_before" : false, - // "separator_after" : false, - // "submenu" : { - // "New Model" : folder.New_model, - // "Existing Model" : { - // "label" : "Existing Model", - // "_disabled" : false, - // "separator_before" : false, - // "separator_after" : false, - // "action" : function (data) { - // self.addExistingModel(o) - // } - // } - // } - // } - // } - // specific to workflows - // let workflow = { - // "Start/Restart Workflow" : { - // "label" : (o.original._status === "ready") ? "Start Workflow" : "Restart Workflow", - // "_disabled" : true, - // "separator_before" : false, - // "separator_after" : false, - // "action" : function (data) { - - // } - // }, - // "Stop Workflow" : { - // "label" : "Stop Workflow", - // "_disabled" : true, - // "separator_before" : false, - // "separator_after" : false, - // "action" : function (data) { - - // } - // }, - // "Model" : { - // "label" : "Model", - // "_disabled" : false, - // "separator_before" : false, - // "separator_after" : false, - // "submenu" : { - // "Edit" : { - // "label" : " Edit", - // "_disabled" : (!o.original._newFormat && o.original._status !== "ready"), - // "separator_before" : false, - // "separator_after" : false, - // "action" : function (data) { - // self.editWorkflowModel(o) - // } - // }, - // "Extract" : { - // "label" : "Extract", - // "_disabled" : (o.original._newFormat && !o.original._hasJobs), - // "separator_before" : false, - // "separator_after" : false, - // "action" : function (data) { - // self.duplicateFileOrDirectory(o, "wkfl_model") - // } - // } - // } - // } - // } - // Specific to sbml files - // let sbml = { - // "Convert" : { - // "label" : "Convert", - // "_disabled" : false, - // "separator_before" : false, - // "separator_after" : false, - // "submenu" : { - // "Convert to Model" : { - // "label" : "To Model", - // "_disabled" : false, - // "separator_before" : false, - // "separator_after" : false, - // "action" : function (data) { - // self.toModel(o, "SBML"); - // } - // } - // } - // } - // } - //Specific to zip archives - // let extractAll = { - // "extractAll" : { - // "label" : "Extract All", - // "_disabled" : false, - // "separator_before" : false, - // "separator_after" : false, - // "action" : function (data) { - // self.extractAll(o); - // } - // } - // } - // let notebook = { - // "publish" : { - // "label" : "Publish", - // "_disabled" : false, - // "separator_before" : false, - // "separator_after" : false, - // "action" : function (data) { - // self.publishNotebookPresentation(o); - // } - // } - // } - // if (o.type === 'root'){ - // return folder - // } - // if (o.text === "trash") {//Trash node - // return {"Refresh": folder.Refresh} - // } - // if (o.original._path.split("/")[0] === "trash") { // item in trash - // return delete_node - // } - // if (o.type === 'folder') { // folder node - // return $.extend(folder, common) - // } - // if (o.type === 'spatial') { // spatial model node - // return $.extend(model, spatialConvert, common) - // } - // if (o.type === 'nonspatial') { // model node - // return $.extend(model, modelConvert, common) - // } - // if (o.type === 'project'){ - // return $.extend(open, project, common) - // } - // if (o.type === 'workflow') { // workflow node - // return $.extend(open, workflow, common) - // } - // if (o.text.endsWith(".zip")) { // zip archive node - // return $.extend(open, extractAll, common) - // } - // if (o.type === 'notebook') { // notebook node - // if(app.getBasePath() === "/") { - // return $.extend(open, common) - // } - // return $.extend(open, notebook, common) - // } - // if (o.type === 'other') { // other nodes - // return $.extend(open, common) - // } - // if (o.type === 'sbml-model') { // sbml model node - // return $.extend(open, sbml, common) - // } - // if (o.type === "domain") { // domain node - // return $.extend(open, common) - // } - // } - // $(document).on('shown.bs.modal', function (e) { - // $('[autofocus]', e.target).focus(); - // }); - // $(document).on('dnd_start.vakata', function (data, element, helper, event) { - // $('#models-jstree').jstree().load_all() - // }); - // $('#models-jstree').jstree(this.treeSettings).bind("loaded.jstree", function (event, data) { - // self.jstreeIsLoaded = true - // }).bind("refresh.jstree", function (event, data) { - // self.jstreeIsLoaded = true - // }); - // $('#models-jstree').on('click.jstree', function(e) { - // var parent = e.target.parentElement - // var _node = parent.children[parent.children.length - 1] - // var node = $('#models-jstree').jstree().get_node(_node) - // if(_node.nodeName === "A" && $('#models-jstree').jstree().is_loaded(node) && node.type === "folder"){ - // $('#models-jstree').jstree().refresh_node(node) - // }else{ - // let optionsButton = $(self.queryByHook("options-for-node")) - // if(!self.nodeForContextMenu){ - // optionsButton.prop('disabled', false) - // } - // optionsButton.text("Actions for " + node.original.text) - // self.nodeForContextMenu = node; - // } - // }); - // $('#models-jstree').on('dblclick.jstree', function(e) { - // var file = e.target.text - // var node = $('#models-jstree').jstree().get_node(e.target) - // var _path = node.original._path; - // if(!(_path.split("/")[0] === "trash")) { - // if(file.endsWith('.mdl') || file.endsWith('.smdl')){ - // window.location.href = path.join(app.getBasePath(), "stochss/models/edit")+"?path="+_path; - // }else if(file.endsWith('.ipynb')){ - // var notebookPath = path.join(app.getBasePath(), "notebooks", _path) - // window.open(notebookPath, '_blank') - // }else if(file.endsWith('.sbml')){ - // var openPath = path.join(app.getBasePath(), "edit", _path) - // window.open(openPath, '_blank') - // }else if(file.endsWith('.proj')){ - // window.location.href = path.join(app.getBasePath(), "stochss/project/manager")+"?path="+_path; - // }else if(file.endsWith('.wkfl')){ - // window.location.href = path.join(app.getBasePath(), "stochss/workflow/edit")+"?path="+_path+"&type=none"; - // }else if(file.endsWith('.domn')) { - // let queryStr = "?domainPath=" + _path - // window.location.href = path.join(app.getBasePath(), "stochss/domain/edit") + queryStr - // }else if(node.type === "folder" && $('#models-jstree').jstree().is_open(node) && $('#models-jstree').jstree().is_loaded(node)){ - // $('#models-jstree').jstree().refresh_node(node) - // }else if(node.type === "other"){ - // var openPath = path.join(app.getBasePath(), "view", _path); - // window.open(openPath, "_blank"); - // } - // } - // }); - // } + } }); initPage(FileBrowser); From 84c27552d472aae2f316be60e493df87ee32b489 Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Thu, 2 Sep 2021 09:59:25 -0400 Subject: [PATCH 139/186] Removed the file browser view template and renamed the browser template. --- client/pages/browser.js | 2 +- client/templates/includes/fileBrowserView.pug | 44 ------------------- .../pages/{fileBrowser.pug => browser.pug} | 0 3 files changed, 1 insertion(+), 45 deletions(-) delete mode 100644 client/templates/includes/fileBrowserView.pug rename client/templates/pages/{fileBrowser.pug => browser.pug} (100%) diff --git a/client/pages/browser.js b/client/pages/browser.js index f368c1ae69..236947e5b6 100644 --- a/client/pages/browser.js +++ b/client/pages/browser.js @@ -32,7 +32,7 @@ let EditProjectView = require('../views/edit-project'); // from project browser let PresentationView = require('../views/presentation-view'); // form presentation browser let JSTreeView = require('../views/jstree-view'); //templates -let template = require('../templates/pages/fileBrowser.pug'); +let template = require('../templates/pages/browser.pug'); import initPage from './page.js'; diff --git a/client/templates/includes/fileBrowserView.pug b/client/templates/includes/fileBrowserView.pug deleted file mode 100644 index 3d1e23dc30..0000000000 --- a/client/templates/includes/fileBrowserView.pug +++ /dev/null @@ -1,44 +0,0 @@ -div#browse-files.card.card-body - - div - - h3.inline Browse Files - div.inline - button.btn.btn-outline-collapse(data-toggle="collapse", data-target="#collapse-browse-files", id="collapse-browse-files-btn" data-hook="collapse-browse-files") + - - div.collapse(id="collapse-browse-files") - - div.alert-warning(class="collapse", id="extension-warning" data-hook="extension-warning") You should avoid changing the file extension unless you know what you are doing! - - div.alert-warning(class="collapse", id="rename-warning" data-hook="rename-warning") MESSAGE - - div.alert-danger(id="renameSpecialCharError" style="display: none;") Names can only include the following characters: (0-9), (a-z), (A-Z) and (., -, _, (, or )) - - div#models-jstree-view - - div - - button.btn.btn-primary.inline.box-shadow( - id="project-browse-files-add-btn" - data-hook="new-file-directory", - data-toggle="dropdown", - aria-haspopup="true", - aria-expanded="false", - type="button" - ) + - - ul.dropdown-menu(aria-labelledby="project-browse-files-add-btn") - li.dropdown-item(id="new-directory" data-hook="new-directory") Create Directory - li.dropdown-item(id="browser-new-model" data-hook="browser-new-model" data-type="model") Create Model - li.dropdown-item(id="browser-new-spatial-model" data-hook="browser-new-model" data-type="spatial") Create Spatial Model (beta) - li.dropdown-item(id="browser-new-domain" data-hook="browser-new-domain") Create Domain (beta) - li.dropdown-divider - li.dropdown-item(id="browser-existing-model" data-hook="browser-existing-model") Add Existing Model - li.dropdown-divider - li.dropdown-item(id="upload-file-btn-bf" data-hook="upload-file-btn-bf", data-type="model") Upload StochSS Model - li.dropdown-item(id="upload-file-btn-bf" data-hook="upload-file-btn-bf", data-type="sbml") Upload SBML Model - li.dropdown-item(id="upload-file-btn-bf" data-hook="upload-file-btn-bf", data-type="file") Upload File - - button.btn.btn-primary.box-shadow(id="options-for-node" data-hook="options-for-node" disabled) Actions - - button.btn.btn-primary.inline.box-shadow(id="refresh-jstree" data-hook="refresh-jstree") Refresh \ No newline at end of file diff --git a/client/templates/pages/fileBrowser.pug b/client/templates/pages/browser.pug similarity index 100% rename from client/templates/pages/fileBrowser.pug rename to client/templates/pages/browser.pug From 48d62cb96a0adf6f6f302668765e2e6a4917a165 Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Thu, 2 Sep 2021 10:31:33 -0400 Subject: [PATCH 140/186] Updated the validate name function in app. Removed all local deffinitions of validate name. Refactored validation to reference validate name from app. --- client/app.js | 16 ++++---- client/pages/domain-editor.js | 18 +-------- client/pages/project-manager.js | 15 +------- client/pages/users-home.js | 65 +-------------------------------- client/views/jstree-view.js | 26 +++---------- 5 files changed, 16 insertions(+), 124 deletions(-) diff --git a/client/app.js b/client/app.js index e249597e0d..6356895f1c 100644 --- a/client/app.js +++ b/client/app.js @@ -132,21 +132,21 @@ let getBrowser = () => { return {"name":BrowserDetect.browser,"version":BrowserDetect.version}; } -let validateName = (input, rename = false) => { - var error = "" +let validateName = (input, {rename=false, saveAs=true}={}) { + var error = ""; if(input.endsWith('/')) { - error = 'forward' + error = 'forward'; } - var invalidChars = "`~!@#$%^&*=+[{]}\"|:;'<,>?\\" - if(rename) { - invalidChars += "/" + var invalidChars = "`~!@#$%^&*=+[{]}\"|:;'<,>?\\"; + if(rename || !saveAs) { + invalidChars += "/"; } for(var i = 0; i < input.length; i++) { if(invalidChars.includes(input.charAt(i))) { - error = error === "" || error === "special" ? "special" : "both" + error = error === "" || error === "special" ? "special" : "both"; } } - return error + return error; } let newWorkflow = (parent, mdlPath, isSpatial, type) => { diff --git a/client/pages/domain-editor.js b/client/pages/domain-editor.js index ef6bc1e3ab..6daa3b8bff 100644 --- a/client/pages/domain-editor.js +++ b/client/pages/domain-editor.js @@ -182,7 +182,7 @@ let DomainEditor = PageView.extend({ input.addEventListener("input", function (e) { var endErrMsg = document.querySelector('#newModalModel #modelNameInputEndCharError') var charErrMsg = document.querySelector('#newModalModel #modelNameInputSpecCharError') - let error = self.validateName(input.value) + let error = app.validateName(input.value) okBtn.disabled = error !== "" || input.value.trim() === "" charErrMsg.style.display = error === "both" || error === "special" ? "block" : "none" endErrMsg.style.display = error === "both" || error === "forward" ? "block" : "none" @@ -892,22 +892,6 @@ let DomainEditor = PageView.extend({ } }, updateValid: function () {}, - validateName(input, rename = false) { - var error = "" - if(input.endsWith('/')) { - error = 'forward' - } - var invalidChars = "`~!@#$%^&*=+[{]}\"|:;'<,>?\\" - if(rename) { - invalidChars += "/" - } - for(var i = 0; i < input.length; i++) { - if(invalidChars.includes(input.charAt(i))) { - error = error === "" || error === "special" ? "special" : "both" - } - } - return error - }, toggleDomainError: function () { let errorMsg = $(this.queryByHook('domain-error')) if(!this.domain.valid) { diff --git a/client/pages/project-manager.js b/client/pages/project-manager.js index fc791a9145..45248d3766 100644 --- a/client/pages/project-manager.js +++ b/client/pages/project-manager.js @@ -194,7 +194,7 @@ let ProjectManager = PageView.extend({ input.addEventListener("input", function (e) { var endErrMsg = document.querySelector('#newModalModel #modelNameInputEndCharError') var charErrMsg = document.querySelector('#newModalModel #modelNameInputSpecCharError') - let error = self.validateName(input.value) + let error = app.validateName(input.value, {saveAs=false}) okBtn.disabled = error !== "" || input.value.trim() === "" charErrMsg.style.display = error === "both" || error === "special" ? "block" : "none" endErrMsg.style.display = error === "both" || error === "forward" ? "block" : "none" @@ -442,19 +442,6 @@ let ProjectManager = PageView.extend({ } }); }, - validateName(input) { - var error = ""; - if(input.endsWith('/')) { - error = 'forward'; - } - let invalidChars = "`~!@#$%^&*=+[{]}\"|:;'<,>?\\"; - for(var i = 0; i < input.length; i++) { - if(invalidChars.includes(input.charAt(i))) { - error = error === "" || error === "special" ? "special" : "both"; - } - } - return error; - } }); initPage(ProjectManager) \ No newline at end of file diff --git a/client/pages/users-home.js b/client/pages/users-home.js index b416cb70e0..b9f7529721 100644 --- a/client/pages/users-home.js +++ b/client/pages/users-home.js @@ -36,10 +36,8 @@ import initPage from './page.js'; let usersHomePage = PageView.extend({ template: template, events: { - 'click [data-hook=new-model-btn]' : 'handleNewModelClick', 'click [data-hook=new-project-btn]' : 'handleNewProjectClick', 'click [data-hook=browse-projects-btn]' : 'handleBrowseProjectsClick', - 'click [data-hook=browse-files-btn]' : 'handleBrowseFilesClick', 'click [data-hook=quickstart-btn]' : 'handleQuickstartClick' }, initialize: function (attrs, options) { @@ -68,63 +66,6 @@ let usersHomePage = PageView.extend({ $("#presentations").css("display", "none"); } }, - validateName(input) { - var error = "" - if(input.endsWith('/')) { - error = 'forward' - } - let invalidChars = "`~!@#$%^&*=+[{]}\"|:;'<,>?\\" - for(var i = 0; i < input.length; i++) { - if(invalidChars.includes(input.charAt(i))) { - error = error === "" || error === "special" ? "special" : "both" - } - } - return error - }, - handleNewModelClick: function (e) { - let self = this - if(document.querySelector("#newModalModel")) { - document.querySelector("#newModalModel").remove() - } - let modal = $(modals.renderCreateModalHtml(true, false)).modal() - let okBtn = document.querySelector("#newModalModel .ok-model-btn") - let input = document.querySelector("#newModalModel #modelNameInput") - input.focus() - input.addEventListener("keyup", function (event) { - if(event.keyCode === 13){ - event.preventDefault(); - okBtn.click(); - } - }); - input.addEventListener("input", function (e) { - var endErrMsg = document.querySelector('#newModalModel #modelNameInputEndCharError') - var charErrMsg = document.querySelector('#newModalModel #modelNameInputSpecCharError') - let error = self.validateName(input.value) - okBtn.disabled = error !== "" || input.value.trim() === "" - charErrMsg.style.display = error === "both" || error === "special" ? "block" : "none" - endErrMsg.style.display = error === "both" || error === "forward" ? "block" : "none" - }); - okBtn.addEventListener("click", function (e) { - if(Boolean(input.value)){ - modal.modal('hide') - let modelPath = input.value + '.mdl' - let queryString = "?path="+modelPath - let existEP = path.join(app.getApiPath(), "model/exists")+queryString - app.getXHR(existEP, { - always: function (err, response, body) { - if(body.exists) { - let title = "Model Already Exists"; - let message = "A model already exists with that name"; - let errorModel = $(modals.newProjectOrWorkflowGroupErrorHtml(title, message)).modal(); - }else{ - let endpoint = path.join(app.getBasePath(), "stochss/models/edit")+queryString; - self.navToPage(endpoint); - } - } - }); - } - }); - }, handleNewProjectClick: function (e) { let self = this if(document.querySelector("#newProjectModal")) { @@ -143,7 +84,7 @@ let usersHomePage = PageView.extend({ input.addEventListener("input", function (e) { var endErrMsg = document.querySelector('#newProjectModal #projectNameInputEndCharError') var charErrMsg = document.querySelector('#newProjectModal #projectNameInputSpecCharError') - let error = self.validateName(input.value) + let error = app.validateName(input.value) okBtn.disabled = error !== "" || input.value.trim() === "" charErrMsg.style.display = error === "both" || error === "special" ? "block" : "none" endErrMsg.style.display = error === "both" || error === "forward" ? "block" : "none" @@ -171,10 +112,6 @@ let usersHomePage = PageView.extend({ let endpoint = path.join(app.getBasePath(), "stochss/project/browser") this.navToPage(endpoint) }, - handleBrowseFilesClick: function (e) { - let endpoint = path.join(app.getBasePath(), "stochss/files") - this.navToPage(endpoint) - }, handleQuickstartClick: function (e) { let endpoint = path.join(app.getBasePath(), "stochss/quickstart") this.navToPage(endpoint) diff --git a/client/views/jstree-view.js b/client/views/jstree-view.js index fb7d2d1924..bb9a8193b0 100644 --- a/client/views/jstree-view.js +++ b/client/views/jstree-view.js @@ -75,7 +75,7 @@ module.exports = View.extend({ 'multiple': false, 'animation': 0, 'check_callback': (op, node, par, pos, more) => { - if(op === "rename_node" && this.validateName(pos, {rename: true}) !== ""){ + if(op === "rename_node" && app.validateName(pos, {rename: true}) !== ""){ let err = $("#renameSpecialCharError"); err.css("display", "block"); setTimeout(() => { @@ -122,7 +122,7 @@ module.exports = View.extend({ input.addEventListener("input", (e) => { let endErrMsg = document.querySelector(`${modalID} ${inputID}EndCharError`); let charErrMsg = document.querySelector(`${modalID} ${inputID}SpecCharError`); - let error = this.validateName(input.value, {saveAs: !inProject}); + let error = app.validateName(input.value, {saveAs: !inProject}); okBtn.disabled = error !== "" || input.value.trim() === ""; charErrMsg.style.display = error === "both" || error === "special" ? "block" : "none"; endErrMsg.style.display = error === "both" || error === "forward" ? "block" : "none"; @@ -1053,22 +1053,6 @@ module.exports = View.extend({ showContextMenuForNode: function (e) { $('#files-jstree').jstree().show_contextmenu(this.nodeForContextMenu); }, - validateName: function (input, {rename = false, saveAs = true}={}) { - var error = ""; - if(input.endsWith('/')) { - error = 'forward'; - } - var invalidChars = "`~!@#$%^&*=+[{]}\"|:;'<,>?\\"; - if(rename || !saveAs) { - invalidChars += "/"; - } - for(var i = 0; i < input.length; i++) { - if(invalidChars.includes(input.charAt(i))) { - error = error === "" || error === "special" ? "special" : "both"; - } - } - return error; - }, uploadFile: function (node, dirname, type, inProject) { if(document.querySelector('#uploadFileModal')) { document.querySelector('#uploadFileModal').remove(); @@ -1100,8 +1084,8 @@ module.exports = View.extend({ } let validateFile = () => { let options = getOptions(fileInput.files[0]); - let fileErr = !fileInput.files.length ? "" : this.validateName(fileInput.files[0].name, options); - let nameErr = this.validateName(input.value, options); + let fileErr = !fileInput.files.length ? "" : app.validateName(fileInput.files[0].name, options); + let nameErr = app.validateName(input.value, options); if(!fileInput.files.length) { uploadBtn.disabled = true; fileCharErrMsg.style.display = 'none'; @@ -1128,7 +1112,7 @@ module.exports = View.extend({ let file = fileInput.files[0]; let options = getOptions(file); let fileinfo = {type: type, name: "", path: dirname}; - if(Boolean(input.value) && this.validateName(input.value.trim(), options) === ""){ + if(Boolean(input.value) && app.validateName(input.value.trim(), options) === ""){ fileinfo.name = input.value.trim(); } let formData = new FormData(); From e873a5f38d6776f7c298345bc28332e15635f2e6 Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Thu, 2 Sep 2021 10:53:34 -0400 Subject: [PATCH 141/186] Fixed issues with validate name update. --- client/app.js | 2 +- client/pages/project-manager.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/client/app.js b/client/app.js index 6356895f1c..08b4f3e228 100644 --- a/client/app.js +++ b/client/app.js @@ -132,7 +132,7 @@ let getBrowser = () => { return {"name":BrowserDetect.browser,"version":BrowserDetect.version}; } -let validateName = (input, {rename=false, saveAs=true}={}) { +let validateName = (input, {rename=false, saveAs=true}={}) => { var error = ""; if(input.endsWith('/')) { error = 'forward'; diff --git a/client/pages/project-manager.js b/client/pages/project-manager.js index 45248d3766..26dc874942 100644 --- a/client/pages/project-manager.js +++ b/client/pages/project-manager.js @@ -194,7 +194,7 @@ let ProjectManager = PageView.extend({ input.addEventListener("input", function (e) { var endErrMsg = document.querySelector('#newModalModel #modelNameInputEndCharError') var charErrMsg = document.querySelector('#newModalModel #modelNameInputSpecCharError') - let error = app.validateName(input.value, {saveAs=false}) + let error = app.validateName(input.value, {saveAs: false}) okBtn.disabled = error !== "" || input.value.trim() === "" charErrMsg.style.display = error === "both" || error === "special" ? "block" : "none" endErrMsg.style.display = error === "both" || error === "forward" ? "block" : "none" From 79fb746488eea4d542ea0728c57f6c1141c4926b Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Thu, 2 Sep 2021 10:58:09 -0400 Subject: [PATCH 142/186] Made the validateName function public. --- client/app.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/client/app.js b/client/app.js index 08b4f3e228..85221bde91 100644 --- a/client/app.js +++ b/client/app.js @@ -234,7 +234,8 @@ module.exports = { postXHR: postXHR, tooltipSetup: tooltipSetup, documentSetup: documentSetup, - copyToClipboard: copyToClipboard + copyToClipboard: copyToClipboard, + validateName: validateName }; From f8b4b0f21ff5f738727746621266c93e73898542 Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Thu, 2 Sep 2021 10:59:09 -0400 Subject: [PATCH 143/186] Moved the window reload function back to the browser file. --- client/views/jstree-view.js | 6 ------ 1 file changed, 6 deletions(-) diff --git a/client/views/jstree-view.js b/client/views/jstree-view.js index bb9a8193b0..d5b6b34965 100644 --- a/client/views/jstree-view.js +++ b/client/views/jstree-view.js @@ -98,12 +98,6 @@ module.exports = View.extend({ render: function (attrs, options) { View.prototype.render.apply(this, arguments); this.config.setup(this); - window.addEventListener('pageshow', (e) => { - let navType = window.performance.navigation.type; - if(navType === 2){ - window.location.reload(); - } - }); this.setupJstree(() => { setTimeout(() => { this.refreshInitialJSTree(); From da3e283571cc2c1e50c3a9dc96aa8252297c4e38 Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Thu, 2 Sep 2021 11:00:57 -0400 Subject: [PATCH 144/186] Finished merging the project browser page into the browser page. --- client/file-config.js | 7 +- client/pages/browser.js | 125 ++++++++++++++++++++++++-------- client/pages/project-browser.js | 120 +++++++++++++++--------------- 3 files changed, 159 insertions(+), 93 deletions(-) diff --git a/client/file-config.js b/client/file-config.js index 24c8c9b30f..ad11188b6f 100644 --- a/client/file-config.js +++ b/client/file-config.js @@ -351,7 +351,12 @@ let types = { 'other' : {"icon": "jstree-icon jstree-file"} } -let updateParent = (view, type) => {} +let updateParent = (view, type) => { + console.log(type) + if(type === "project") { + view.parent.update("Projects"); + } +} let validateMove = (view, node, more, pos) => { // Check if workflow is running diff --git a/client/pages/browser.js b/client/pages/browser.js index 236947e5b6..01e2f8a87d 100644 --- a/client/pages/browser.js +++ b/client/pages/browser.js @@ -44,60 +44,121 @@ let FileBrowser = PageView.extend({ let modal = $(modals.operationInfoModalHtml('file-browser')).modal(); }, 'click [data-hook=collapse-projects]' : 'changeCollapseButtonText', + 'click [data-hook=new-project-btn]' : 'handleNewProjectClick', 'click [data-hook=collapse-presentations]' : 'changeCollapseButtonText', 'click [data-hook=collapse-files]' : 'changeCollapseButtonText' }, initialize: function (attrs, options) { PageView.prototype.initialize.apply(this, arguments) - var self = this - // start block from project browser - var endpoint = path.join(app.getApiPath(), "project/load-browser"); - app.getXHR(endpoint, { - success: function (err, response, body) { - self.renderProjectsView(body.projects); + this.getProjects(); + // Start block from presentation bowser + // var self = this + // var endpoint = path.join(app.getApiPath(), "file/presentations") + // app.getXHR(endpoint, { + // success: function (err, response, body) { + // self.renderPresentationView(body.presentations); + // } + // }); + }, + render: function (attrs, options) { + PageView.prototype.render.apply(this, arguments) + window.addEventListener('pageshow', (e) => { + let navType = window.performance.navigation.type; + if(navType === 2){ + window.location.reload(); } }); - // End block from project browser - // Start block from presentation bowser - var endpoint = path.join(app.getApiPath(), "file/presentations") + this.renderJSTreeView(); + app.documentSetup(); + }, + // // Function from presentation browser + // renderPresentationView: function (presentations) { + // let options = {model: Presentation}; + // let presentCollection = new Collection(presentations, options); + // this.renderCollection( + // presentCollection, + // PresentationView, + // this.queryByHook("presentation-list") + // ); + // }, + changeCollapseButtonText: function (e) { + app.changeCollapseButtonText(this, e); + }, + getProjects: function () { + let endpoint = path.join(app.getApiPath(), "project/load-browser"); app.getXHR(endpoint, { - success: function (err, response, body) { - self.renderPresentationView(body.presentations); + success: (err, response, body) => { + this.renderProjectsView(body.projects); } }); }, - render: function (attrs, options) { - PageView.prototype.render.apply(this, arguments) - let jstreeView = new JSTreeView({ - configKey: "file" + handleNewProjectClick: function (e) { + if(document.querySelector("#newProjectModal")) { + document.querySelector("#newProjectModal").remove(); + } + let modal = $(modals.createProjectHtml()).modal(); + let input = document.querySelector("#newProjectModal #projectNameInput"); + let okBtn = document.querySelector("#newProjectModal .ok-model-btn"); + input.focus(); + input.addEventListener("keyup", (event) => { + if(event.keyCode === 13){ + event.preventDefault(); + okBtn.click(); + } }); - app.registerRenderSubview(this, jstreeView, "jstree-view-container"); - $(document).on('hide.bs.modal', '.modal', function (e) { - e.target.remove() + input.addEventListener("input", (e) => { + let endErrMsg = document.querySelector('#newProjectModal #projectNameInputEndCharError'); + let charErrMsg = document.querySelector('#newProjectModal #projectNameInputSpecCharError'); + let error = app.validateName(input.value); + okBtn.disabled = error !== ""; + charErrMsg.style.display = error === "both" || error === "special" ? "block" : "none"; + endErrMsg.style.display = error === "both" || error === "forward" ? "block" : "none"; + }); + okBtn.addEventListener("click", (e) => { + modal.modal('hide'); + let queryStr = `?path=${input.value.trim()}.proj`; + let endpoint = path.join(app.getApiPath(), "project/new-project") + queryStr; + app.getXHR(endpoint, { + success: (err, response, body) => { + let queryStr = `?path=${body.path}`; + window.location.href = path.join(app.getBasePath(), "stochss/project/manager") + queryStr; + }, + error: (err, response, body) => { + if(document.querySelector("#errorModal")) { + document.querySelector("#errorModal").remove(); + } + let errorModal = $(modals.errorHtml(body.Reason, body.Message)).modal(); + } + }); }); }, - // Function from project browser + renderJSTreeView: function () { + if(this.jstreeView) { + this.jstreeView.remove(); + } + this.jstreeView = new JSTreeView({ + configKey: "file" + }); + app.registerRenderSubview(this, this.jstreeView, "jstree-view-container"); + }, renderProjectsView: function (projects) { + if(this.projectsView) { + this.projectsView.remove(); + } let options = {model: Project, comparator: 'parentDir'}; let projectCollection = new Collection(projects, options); - this.renderCollection( + this.projectsView = this.renderCollection( projectCollection, EditProjectView, this.queryByHook("projects-view-container") ); }, - // Function from presentation browser - renderPresentationView: function (presentations) { - let options = {model: Presentation}; - let presentCollection = new Collection(presentations, options); - this.renderCollection( - presentCollection, - PresentationView, - this.queryByHook("presentation-list") - ); - }, - changeCollapseButtonText: function (e) { - app.changeCollapseButtonText(this, e); + update: function (target) { + if(target === "Projects") { + this.getProjects(); + }else if(target === "Files") { + this.jstreeView.refreshJSTree(null); + } } }); diff --git a/client/pages/project-browser.js b/client/pages/project-browser.js index 6286c611a8..0e56186e9c 100644 --- a/client/pages/project-browser.js +++ b/client/pages/project-browser.js @@ -34,66 +34,66 @@ let template = require('../templates/pages/projectBrowser.pug'); import initPage from './page.js'; let projectBrowser = PageView.extend({ - template: template, - events: { - 'click [data-hook=new-project-btn]' : 'handleNewProjectClick' - }, - initialize: function (attrs, options) { - PageView.prototype.initialize.apply(this, arguments); - let self = this; - let endpoint = path.join(app.getApiPath(), "project/load-browser"); - app.getXHR(endpoint, { - success: function (err, response, body) { - self.projects = body.projects; - self.renderProjectsView(); - } - }); - }, - render: function (attrs, options) { - PageView.prototype.render.apply(this, arguments) - $(document).on('hide.bs.modal', '.modal', function (e) { - e.target.remove() - }); - }, - handleNewProjectClick: function (e) { - let self = this; - if(document.querySelector("#newProjectModal")) { - document.querySelector("#newProjectModal").remove(); - } - let modal = $(modals.newProjectModalHtml()).modal(); - let input = document.querySelector("#newProjectModal #projectNameInput"); - input.focus(); - let okBtn = document.querySelector("#newProjectModal .ok-model-btn"); - input.addEventListener("keyup", function (event) { - if(event.keyCode === 13){ - event.preventDefault(); - okBtn.click(); - } - }); - input.addEventListener("input", function (e) { - var endErrMsg = document.querySelector('#newProjectModal #projectNameInputEndCharError'); - var charErrMsg = document.querySelector('#newProjectModal #projectNameInputSpecCharError'); - let error = self.validateName(input.value); - okBtn.disabled = error !== ""; - charErrMsg.style.display = error === "both" || error === "special" ? "block" : "none"; - endErrMsg.style.display = error === "both" || error === "forward" ? "block" : "none"; - }); - okBtn.addEventListener("click", function (e) { - if(Boolean(input.value)) { - modal.modal('hide'); - let projectPath = input.value.trim() + ".proj"; - let endpoint = path.join(app.getApiPath(), "project/new-project") + "?path=" + projectPath; - app.getXHR(endpoint, { - success: function (err, response, body) { - window.location.href = path.join(app.getBasePath(), "stochss/project/manager")+"?path="+body.path; - }, - error: function (err, response, body) { - let errorModel = $(modals.newProjectOrWorkflowGroupErrorHtml(body.Reason, body.Message)).modal(); - } - }); - } - }); - }, + // template: template, + // events: { + // 'click [data-hook=new-project-btn]' : 'handleNewProjectClick' + // }, + // initialize: function (attrs, options) { + // PageView.prototype.initialize.apply(this, arguments); + // let self = this; + // let endpoint = path.join(app.getApiPath(), "project/load-browser"); + // app.getXHR(endpoint, { + // success: function (err, response, body) { + // self.projects = body.projects; + // self.renderProjectsView(); + // } + // }); + // }, + // render: function (attrs, options) { + // PageView.prototype.render.apply(this, arguments) + // $(document).on('hide.bs.modal', '.modal', function (e) { + // e.target.remove() + // }); + // }, + // handleNewProjectClick: function (e) { + // let self = this; + // if(document.querySelector("#newProjectModal")) { + // document.querySelector("#newProjectModal").remove(); + // } + // let modal = $(modals.newProjectModalHtml()).modal(); + // let input = document.querySelector("#newProjectModal #projectNameInput"); + // input.focus(); + // let okBtn = document.querySelector("#newProjectModal .ok-model-btn"); + // input.addEventListener("keyup", function (event) { + // if(event.keyCode === 13){ + // event.preventDefault(); + // okBtn.click(); + // } + // }); + // input.addEventListener("input", function (e) { + // var endErrMsg = document.querySelector('#newProjectModal #projectNameInputEndCharError'); + // var charErrMsg = document.querySelector('#newProjectModal #projectNameInputSpecCharError'); + // let error = self.validateName(input.value); + // okBtn.disabled = error !== ""; + // charErrMsg.style.display = error === "both" || error === "special" ? "block" : "none"; + // endErrMsg.style.display = error === "both" || error === "forward" ? "block" : "none"; + // }); + // okBtn.addEventListener("click", function (e) { + // if(Boolean(input.value)) { + // modal.modal('hide'); + // let projectPath = input.value.trim() + ".proj"; + // let endpoint = path.join(app.getApiPath(), "project/new-project") + "?path=" + projectPath; + // app.getXHR(endpoint, { + // success: function (err, response, body) { + // window.location.href = path.join(app.getBasePath(), "stochss/project/manager")+"?path="+body.path; + // }, + // error: function (err, response, body) { + // let errorModel = $(modals.newProjectOrWorkflowGroupErrorHtml(body.Reason, body.Message)).modal(); + // } + // }); + // } + // }); + // }, renderProjectsView: function () { if(this.projectsView) { this.projectsView.remove() From f7fdf53ba9e81b4d808403d00b3581471fdc68a7 Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Thu, 2 Sep 2021 11:10:00 -0400 Subject: [PATCH 145/186] Removed the project browser page. --- .gitignore | 1 - client/pages/project-browser.js | 119 ---------------------- client/pages/users-home.js | 2 +- client/templates/pages/projectBrowser.pug | 9 -- stochss/handlers/__init__.py | 1 - stochss/handlers/pages.py | 17 ---- webpack.config.js | 8 -- 7 files changed, 1 insertion(+), 156 deletions(-) delete mode 100644 client/pages/project-browser.js delete mode 100644 client/templates/pages/projectBrowser.pug diff --git a/.gitignore b/.gitignore index e96d3327a9..101668ad28 100644 --- a/.gitignore +++ b/.gitignore @@ -11,7 +11,6 @@ stochss/dist/stochss-project-manager.html stochss/dist/multiple-plots-page.html stochss/dist/stochss-domain-editor.html stochss/dist/stochss-loading-page.html -stochss/dist/stochss-project-browser.html stochss/dist/stochss-quick-start.html stochss/dist/stochss-user-home.html jupyterhub/templates/page.html diff --git a/client/pages/project-browser.js b/client/pages/project-browser.js deleted file mode 100644 index 0e56186e9c..0000000000 --- a/client/pages/project-browser.js +++ /dev/null @@ -1,119 +0,0 @@ -/* -StochSS is a platform for simulating biochemical systems -Copyright (C) 2019-2021 StochSS developers. - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program. If not, see . -*/ - -let $ = require('jquery'); -let path = require('path'); -//support files -let app = require('../app'); -let modals = require('../modals'); -//models -let Project = require('../models/project'); -//collections -let Collection = require('ampersand-collection'); -//views -let PageView = require('./base'); -let EditProjectView = require('../views/edit-project'); -//templates -let template = require('../templates/pages/projectBrowser.pug'); - -import initPage from './page.js'; - -let projectBrowser = PageView.extend({ - // template: template, - // events: { - // 'click [data-hook=new-project-btn]' : 'handleNewProjectClick' - // }, - // initialize: function (attrs, options) { - // PageView.prototype.initialize.apply(this, arguments); - // let self = this; - // let endpoint = path.join(app.getApiPath(), "project/load-browser"); - // app.getXHR(endpoint, { - // success: function (err, response, body) { - // self.projects = body.projects; - // self.renderProjectsView(); - // } - // }); - // }, - // render: function (attrs, options) { - // PageView.prototype.render.apply(this, arguments) - // $(document).on('hide.bs.modal', '.modal', function (e) { - // e.target.remove() - // }); - // }, - // handleNewProjectClick: function (e) { - // let self = this; - // if(document.querySelector("#newProjectModal")) { - // document.querySelector("#newProjectModal").remove(); - // } - // let modal = $(modals.newProjectModalHtml()).modal(); - // let input = document.querySelector("#newProjectModal #projectNameInput"); - // input.focus(); - // let okBtn = document.querySelector("#newProjectModal .ok-model-btn"); - // input.addEventListener("keyup", function (event) { - // if(event.keyCode === 13){ - // event.preventDefault(); - // okBtn.click(); - // } - // }); - // input.addEventListener("input", function (e) { - // var endErrMsg = document.querySelector('#newProjectModal #projectNameInputEndCharError'); - // var charErrMsg = document.querySelector('#newProjectModal #projectNameInputSpecCharError'); - // let error = self.validateName(input.value); - // okBtn.disabled = error !== ""; - // charErrMsg.style.display = error === "both" || error === "special" ? "block" : "none"; - // endErrMsg.style.display = error === "both" || error === "forward" ? "block" : "none"; - // }); - // okBtn.addEventListener("click", function (e) { - // if(Boolean(input.value)) { - // modal.modal('hide'); - // let projectPath = input.value.trim() + ".proj"; - // let endpoint = path.join(app.getApiPath(), "project/new-project") + "?path=" + projectPath; - // app.getXHR(endpoint, { - // success: function (err, response, body) { - // window.location.href = path.join(app.getBasePath(), "stochss/project/manager")+"?path="+body.path; - // }, - // error: function (err, response, body) { - // let errorModel = $(modals.newProjectOrWorkflowGroupErrorHtml(body.Reason, body.Message)).modal(); - // } - // }); - // } - // }); - // }, - renderProjectsView: function () { - if(this.projectsView) { - this.projectsView.remove() - } - let projects = new Collection(this.projects, {model: Project, comparator: 'parentDir'}) - this.projectsView = this.renderCollection(projects, EditProjectView, this.queryByHook("projects-view-container")) - }, - validateName(input) { - var error = ""; - if(input.endsWith('/')) { - error = 'forward'; - } - let invalidChars = "`~!@#$%^&*=+[{]}\"|:;'<,>?\\"; - for(var i = 0; i < input.length; i++) { - if(invalidChars.includes(input.charAt(i))) { - error = error === "" || error === "special" ? "special" : "both"; - } - } - return error; - } -}); - -initPage(projectBrowser); diff --git a/client/pages/users-home.js b/client/pages/users-home.js index b9f7529721..79238c483b 100644 --- a/client/pages/users-home.js +++ b/client/pages/users-home.js @@ -109,7 +109,7 @@ let usersHomePage = PageView.extend({ }); }, handleBrowseProjectsClick: function (e) { - let endpoint = path.join(app.getBasePath(), "stochss/project/browser") + let endpoint = path.join(app.getBasePath(), "stochss/files#project-browser-section") this.navToPage(endpoint) }, handleQuickstartClick: function (e) { diff --git a/client/templates/pages/projectBrowser.pug b/client/templates/pages/projectBrowser.pug deleted file mode 100644 index 9fb26fdafc..0000000000 --- a/client/templates/pages/projectBrowser.pug +++ /dev/null @@ -1,9 +0,0 @@ -section.page - - h2 Project Browser - - table.table - - tbody(id="projects-view-container" data-hook="projects-view-container") - - button.btn.btn-outline-primary.box-shadow(id="new-project-btn" data-hook="new-project-btn") New \ No newline at end of file diff --git a/stochss/handlers/__init__.py b/stochss/handlers/__init__.py index 734f612054..30adcddd42 100644 --- a/stochss/handlers/__init__.py +++ b/stochss/handlers/__init__.py @@ -46,7 +46,6 @@ def get_page_handlers(route_start): (r'/stochss/workflow/selection\/?', WorkflowSelectionHandler), (r'/stochss/workflow/edit\/?', WorkflowEditorHandler), (r'/stochss/quickstart\/?', QuickstartHandler), - (r'/stochss/project/browser\/?', ProjectBrowserHandler), (r'/stochss/project/manager\/?', ProjectManagerHandler), (r'/stochss/loading-page\/?', LoadingPageHandler), (r'/stochss/multiple-plots\/?', MultiplePlotsHandler), diff --git a/stochss/handlers/pages.py b/stochss/handlers/pages.py index e2961bb4fa..7ca148a5c0 100644 --- a/stochss/handlers/pages.py +++ b/stochss/handlers/pages.py @@ -197,23 +197,6 @@ async def get(self): self.render("stochss-workflow-manager.html", server_path=self.get_server_path()) -class ProjectBrowserHandler(PageHandler): - ''' - ################################################################################################ - StochSS Project Browser Page Handler - ################################################################################################ - ''' - @web.authenticated - async def get(self): - ''' - Render the StochSS project browser page. - - Attributes - ---------- - ''' - self.render("stochss-project-browser.html", server_path=self.get_server_path()) - - class ProjectManagerHandler(PageHandler): ''' ################################################################################################ diff --git a/webpack.config.js b/webpack.config.js index 738e530859..379a96947b 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -11,7 +11,6 @@ module.exports = { domainEditor: './client/pages/domain-editor.js', workflowSelection: './client/pages/workflow-selection.js', workflowEditor: './client/pages/workflow-manager.js', - projectBrowser: './client/pages/project-browser.js', projectManager: './client/pages/project-manager.js', loadingPage: './client/pages/loading-page.js', multiplePlots: './client/pages/multiple-plots.js' @@ -71,13 +70,6 @@ module.exports = { name: 'workflowEditor', inject: false }), - new HtmlWebpackPlugin({ - title: 'StochSS | Project Browser', - filename: 'stochss-project-browser.html', - template: 'page_template.pug', - name: 'projectBrowser', - inject: false - }), new HtmlWebpackPlugin({ title: 'StochSS | Project Manager', filename: 'stochss-project-manager.html', From 4c833b282a9746b9e821f07ee29d3d2419c9da52 Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Thu, 2 Sep 2021 11:25:26 -0400 Subject: [PATCH 146/186] Updated the layout for the project browser section to grid. Added call to update parent when a project is removed. --- client/templates/includes/editProject.pug | 44 +++++++++++++---------- client/templates/pages/browser.pug | 6 ++-- client/views/edit-project.js | 18 ++++++---- 3 files changed, 40 insertions(+), 28 deletions(-) diff --git a/client/templates/includes/editProject.pug b/client/templates/includes/editProject.pug index 4104a043ca..c5e1dd9c1f 100644 --- a/client/templates/includes/editProject.pug +++ b/client/templates/includes/editProject.pug @@ -1,18 +1,26 @@ -tr - - td - a.btn.btn-outline-secondary.box-shadow.name( - id=this.model.elementID+"-open-btn" - href=this.model.open - role="button" - style="width: 100%" - )=this.model.name - - td - div=this.model.location - - td - button.btn.btn-outline-secondary.box-shadow( - id=this.model.elementID+"-remove-btn" - data-hook="remove-project-btn" - ) \ No newline at end of file +div.mx-1 + + if(this.model.collection.indexOf(this.model) !== 0) + hr + + div.row + + div.col-sm-4 + + a.btn.btn-outline-secondary.box-shadow.name( + id=this.model.elementID+"-open-btn" + href=this.model.open + role="button" + style="width: 100%" + )=this.model.name + + div.col-sm-6 + + div=this.model.location + + div.col-sm-2 + + button.btn.btn-outline-secondary.box-shadow( + id=this.model.elementID+"-remove-btn" + data-hook="remove-project-btn" + ) diff --git a/client/templates/pages/browser.pug b/client/templates/pages/browser.pug index 21b1231601..4bd9cf3433 100644 --- a/client/templates/pages/browser.pug +++ b/client/templates/pages/browser.pug @@ -14,9 +14,9 @@ section.page div.card-body - table.table - - tbody(id="projects-view-container" data-hook="projects-view-container") + div.mb-3 + + div(id="projects-view-container" data-hook="projects-view-container") button.btn.btn-outline-primary.box-shadow(id="new-project-btn" data-hook="new-project-btn") New diff --git a/client/views/edit-project.js b/client/views/edit-project.js index 2dcf7d3f90..9ec7b4c713 100644 --- a/client/views/edit-project.js +++ b/client/views/edit-project.js @@ -41,19 +41,23 @@ module.exports = View.extend({ if(document.querySelector('#moveToTrashConfirmModal')) { document.querySelector('#moveToTrashConfirmModal').remove(); } - let self = this; let modal = $(modals.moveToTrashConfirmHtml("model")).modal(); let yesBtn = document.querySelector('#moveToTrashConfirmModal .yes-modal-btn'); - yesBtn.addEventListener('click', function (e) { + yesBtn.addEventListener('click', (e) => { modal.modal('hide'); - let queryStr = "?srcPath=" + self.model.directory + "&dstPath=" + path.join("trash", self.model.directory.split("/").pop()); - let endpoint = path.join(app.getApiPath(), "file/move") + queryStr + let queryStr = `?srcPath=${this.model.directory}&dstPath=${path.join("trash", this.model.directory.split("/").pop())}`; + let endpoint = path.join(app.getApiPath(), "file/move") + queryStr; app.getXHR(endpoint, { - success: function (err, response, body) { - self.model.collection.remove(self.model); + success: (err, response, body) => { + this.parent.update("Files"); + this.model.collection.remove(this.model); }, - error: function (err, response, body) { + error: (err, response, body) => { + if(document.querySelector("#errorModal")) { + document.querySelector("#errorModal").remove(); + } body = JSON.parse(body); + let errorModal = $(modals.errorHtml(body.Reason, body.Message)).modal(); } }); }); From b431d9c07ae237caea0f9de9f1b510c711c9e318 Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Thu, 2 Sep 2021 11:40:25 -0400 Subject: [PATCH 147/186] Finished integrating the presentation browser section into the new browser page. --- client/pages/browser.js | 45 ++++++++++++++++------------ client/pages/users-home.js | 24 +-------------- client/templates/pages/browser.pug | 4 +-- client/templates/pages/usersHome.pug | 18 ----------- client/views/main.js | 6 ++-- 5 files changed, 32 insertions(+), 65 deletions(-) diff --git a/client/pages/browser.js b/client/pages/browser.js index 01e2f8a87d..cadfd10fdd 100644 --- a/client/pages/browser.js +++ b/client/pages/browser.js @@ -51,14 +51,6 @@ let FileBrowser = PageView.extend({ initialize: function (attrs, options) { PageView.prototype.initialize.apply(this, arguments) this.getProjects(); - // Start block from presentation bowser - // var self = this - // var endpoint = path.join(app.getApiPath(), "file/presentations") - // app.getXHR(endpoint, { - // success: function (err, response, body) { - // self.renderPresentationView(body.presentations); - // } - // }); }, render: function (attrs, options) { PageView.prototype.render.apply(this, arguments) @@ -68,22 +60,25 @@ let FileBrowser = PageView.extend({ window.location.reload(); } }); - this.renderJSTreeView(); app.documentSetup(); + if(app.getBasePath() === "/") { + $("#presentations").css("display", "none"); + }else{ + this.getPresentations(); + } + this.renderJSTreeView(); }, - // // Function from presentation browser - // renderPresentationView: function (presentations) { - // let options = {model: Presentation}; - // let presentCollection = new Collection(presentations, options); - // this.renderCollection( - // presentCollection, - // PresentationView, - // this.queryByHook("presentation-list") - // ); - // }, changeCollapseButtonText: function (e) { app.changeCollapseButtonText(this, e); }, + getPresentations: function () { + let endpoint = path.join(app.getApiPath(), "file/presentations"); + app.getXHR(endpoint, { + success: (err, response, body) => { + this.renderPresentationView(body.presentations); + } + }); + }, getProjects: function () { let endpoint = path.join(app.getApiPath(), "project/load-browser"); app.getXHR(endpoint, { @@ -141,6 +136,18 @@ let FileBrowser = PageView.extend({ }); app.registerRenderSubview(this, this.jstreeView, "jstree-view-container"); }, + renderPresentationView: function (presentations) { + if(this.presentationsView) { + this.presentationsView.remove(); + } + let options = {model: Presentation}; + let presentCollection = new Collection(presentations, options); + this.presentationsView = this.renderCollection( + presentCollection, + PresentationView, + this.queryByHook("presentation-list") + ); + }, renderProjectsView: function (projects) { if(this.projectsView) { this.projectsView.remove(); diff --git a/client/pages/users-home.js b/client/pages/users-home.js index 79238c483b..36d0241c34 100644 --- a/client/pages/users-home.js +++ b/client/pages/users-home.js @@ -47,24 +47,11 @@ let usersHomePage = PageView.extend({ let queryString = "?path=" + urlParams.get("open") + "&action=open"; let endpoint = path.join(app.getBasePath(), 'stochss/loading-page') + queryString; window.location.href = endpoint; - }else{ - let self = this; - let endpoint = path.join(app.getApiPath(), "file/presentations") - app.getXHR(endpoint, { - success: function (err, response, body) { - self.renderPresentationView(body.presentations); - } - }); } }, render: function (attrs, options) { PageView.prototype.render.apply(this, arguments); - $(document).on('hide.bs.modal', '.modal', function (e) { - e.target.remove() - }); - if(app.getBasePath() === "/") { - $("#presentations").css("display", "none"); - } + app.documentSetup(); }, handleNewProjectClick: function (e) { let self = this @@ -118,15 +105,6 @@ let usersHomePage = PageView.extend({ }, navToPage: function (endpoint) { window.location.href = endpoint - }, - renderPresentationView: function (presentations) { - let options = {model: Presentation}; - let presentCollection = new Collection(presentations, options); - this.renderCollection( - presentCollection, - PresentationView, - this.queryByHook("presentation-list") - ); } }); diff --git a/client/templates/pages/browser.pug b/client/templates/pages/browser.pug index 4bd9cf3433..a634614fe5 100644 --- a/client/templates/pages/browser.pug +++ b/client/templates/pages/browser.pug @@ -20,7 +20,7 @@ section.page button.btn.btn-outline-primary.box-shadow(id="new-project-btn" data-hook="new-project-btn") New - div.card.my-4 + div.card.mt-4#presentations a.anchor#presentation-browser-section @@ -44,7 +44,7 @@ section.page div.mt-3(data-hook="presentation-list") - div.card + div.card.mt-4 a.anchor#file-browser-section diff --git a/client/templates/pages/usersHome.pug b/client/templates/pages/usersHome.pug index d325d9cada..01a19a7b2b 100644 --- a/client/templates/pages/usersHome.pug +++ b/client/templates/pages/usersHome.pug @@ -53,21 +53,3 @@ selction.page | Click here for a tutorial div.col-md-2.col-lg-3.col-xl-4 - - div#presentations.card - - div.card-header.pb-0 - - h3 Presentations - - div.card-body - - div.mx-1.row.head.align-items-baseline - - div.col-sm-8: h6 File - - div.col-sm-2: h6 Size - - div.col-sm-2: h6 Remove - - div.mt-3(data-hook="presentation-list") \ No newline at end of file diff --git a/client/views/main.js b/client/views/main.js index 37fa7343b7..c77d0ce9ce 100644 --- a/client/views/main.js +++ b/client/views/main.js @@ -129,9 +129,9 @@ module.exports = View.extend({ App.currentPage = newView; } }); - // if(app.getBasePath() === "/") { - // $("#presentation-nav-link").css("display", "none"); - // } + if(app.getBasePath() === "/") { + $("#presentation-nav-link").css("display", "none"); + } let self = this; let message = app.getBasePath() === "/" ? "Welcome to StochSS!" : "Welcome to StochSS Live!"; $("#user-logs").html(message) From 47666eca82adbda98d8d7750cc1b2ff01becbbbb Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Thu, 2 Sep 2021 12:13:43 -0400 Subject: [PATCH 148/186] Refactored the left side navbar to group projects, presentations, and files links. --- client/templates/body.pug | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/client/templates/body.pug b/client/templates/body.pug index cc4c4e4425..fcebf0b635 100644 --- a/client/templates/body.pug +++ b/client/templates/body.pug @@ -36,23 +36,25 @@ body nav.sidebar.d-md-block div.sidebar-sticky ul.nav.nav-flex-column - li.nav-item: a.nav-link(href="stochss/home", title="Users Home Page") - | Home + li.nav-item: a.nav-link(href="stochss/home" title="Users Home Page") Home - li.nav-item - div: a.nav-link(href="stochss/files#project-browser-section", title="Explore your StochSS projects") Projects + li.nav-item: a.nav-link.pb-0(href="stochss/files" title="Browser Page") Browse - li.nav-item(id="presentation-nav-link") - div: a.nav-link(href="stochss/files#presentation-browser-section", title="Explore your StochSS presentations") Presentations + ul.nav.nav-flex-column + li.nav-item.ml-3 + a.nav-link.pb-0(href="stochss/files#project-browser-section" title="Explore your StochSS projects") Projects - li.nav-item - div: a.nav-link(href="stochss/files#file-browser-section", title="Explore your StochSS files") Files + li.nav-item.ml-3(id="presentation-nav-link") + a.nav-link.pb-0(href="stochss/files#presentation-browser-section" title="Explore your StochSS presentations") Presentations + + li.nav-item.ml-3 + a.nav-link(href="stochss/files#file-browser-section" title="Explore your StochSS files") Files li.nav-item - div: a.nav-link(target="_blank", href="tree", title="Browse your files in a Jupyter interface") Jupyter + div: a.nav-link(target="_blank" href="tree" title="Browse your files in a Jupyter interface") Jupyter li.nav-item - div: a.nav-link(href="stochss/quickstart", title="Quickstart") Tutorial + div: a.nav-link(href="stochss/quickstart" title="Quickstart") Tutorial div.user-logs.card.card-body From a77c7d15b31bdc6c045e085d6d649dcf80c2a727 Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Thu, 2 Sep 2021 13:20:22 -0400 Subject: [PATCH 149/186] Added update parent call to the publish presentations function. --- client/file-config.js | 3 ++- client/pages/browser.js | 2 ++ client/views/jstree-view.js | 1 + 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/client/file-config.js b/client/file-config.js index ad11188b6f..c8730987a1 100644 --- a/client/file-config.js +++ b/client/file-config.js @@ -352,9 +352,10 @@ let types = { } let updateParent = (view, type) => { - console.log(type) if(type === "project") { view.parent.update("Projects"); + }else if(type === "Presentations") { + view.parent.update(type); } } diff --git a/client/pages/browser.js b/client/pages/browser.js index cadfd10fdd..63a9639aeb 100644 --- a/client/pages/browser.js +++ b/client/pages/browser.js @@ -165,6 +165,8 @@ let FileBrowser = PageView.extend({ this.getProjects(); }else if(target === "Files") { this.jstreeView.refreshJSTree(null); + }else if(target === "Presentations") { + this.getPresentations(); } } }); diff --git a/client/views/jstree-view.js b/client/views/jstree-view.js index d5b6b34965..7ba5c5cf2b 100644 --- a/client/views/jstree-view.js +++ b/client/views/jstree-view.js @@ -887,6 +887,7 @@ module.exports = View.extend({ success: (err, response, body) => { $(modals.presentationLinks(body.message, "Shareable Presentation", body.links)).modal(); let copyBtn = document.querySelector('#presentationLinksModal #copy-to-clipboard'); + this.config.updateParent(this, "Presentations"); copyBtn.addEventListener('click', (e) => { let onFulfilled = (value) => { $("#copy-link-success").css("display", "inline-block"); From 2d6441ade6724d03a92c6915060c8dd9a9abe247 Mon Sep 17 00:00:00 2001 From: Bryan Rumsey Date: Thu, 2 Sep 2021 15:13:17 -0400 Subject: [PATCH 150/186] Cleaned up modals. --- client/app.js | 2 +- client/job-view/views/job-results-view.js | 5 +- client/modals.js | 881 +++++++++------------- client/model-view/model-view.js | 2 +- client/pages/domain-editor.js | 14 +- client/pages/loading-page.js | 5 +- client/pages/project-manager.js | 51 +- client/pages/users-home.js | 7 +- client/views/jstree-view.js | 7 +- client/views/model-listing.js | 5 +- client/views/model-state-buttons.js | 5 +- client/views/workflow-group-listing.js | 5 +- 12 files changed, 448 insertions(+), 541 deletions(-) diff --git a/client/app.js b/client/app.js index 85221bde91..0eec92cecd 100644 --- a/client/app.js +++ b/client/app.js @@ -157,7 +157,7 @@ let newWorkflow = (parent, mdlPath, isSpatial, type) => { let ext = isSpatial ? /.smdl/g : /.mdl/g let typeCode = type === "Ensemble Simulation" ? "_ES" : "_PS"; let name = mdlPath.split('/').pop().replace(ext, typeCode) - let modal = $(modals.newWorkflowHtml(name, type)).modal(); + let modal = $(modals.createWorkflowHtml(name, type)).modal(); let okBtn = document.querySelector('#newWorkflowModal .ok-model-btn'); let input = document.querySelector('#newWorkflowModal #workflowNameInput'); okBtn.disabled = false; diff --git a/client/job-view/views/job-results-view.js b/client/job-view/views/job-results-view.js index 6181565cd5..3ff8518e59 100644 --- a/client/job-view/views/job-results-view.js +++ b/client/job-view/views/job-results-view.js @@ -352,8 +352,11 @@ module.exports = View.extend({ }); }, error: function (err, response, body) { + if(document.querySelector("#errorModal")) { + document.querySelector("#errorModal").remove(); + } self.errorAction(); - $(modals.newProjectModelErrorHtml(body.Reason, body.Message)).modal(); + $(modals.errorHtml(body.Reason, body.Message)).modal(); } }); }, diff --git a/client/modals.js b/client/modals.js index 00ecbc642a..64fa80462c 100644 --- a/client/modals.js +++ b/client/modals.js @@ -19,241 +19,252 @@ along with this program. If not, see . let help = require('./page-help') let templates = { - input : (modalID, inputID, title, label, value) => { - return ` - ` - }, - input_long : (modalID, inputID, title, label, value) => { - return ` - ` - }, - message : (modalID, title, message) => { - return ` - ` - }, - confirmation : (modalID, title) => { - return ` - ` - }, - confirmation_with_message : (modalID, title, message) => { - return ` - ` - }, - upload : (modalID, title, accept, withName=true) => { - let displayNameField = withName ? "" : " style='display: none'" - return ` - ` - }, - select : (modalID, selectID, title, label, options) => { - return ` - ` - }, - fileSelect : (modalID, fileID, locationID, title, label, files) => { - return ` - ` - }, - presentationLinks : (modalID, title, headers, links) => { - return ` -