diff --git a/client/pages/file-browser.js b/client/pages/file-browser.js index a2e9255e59..94dc7d5c75 100644 --- a/client/pages/file-browser.js +++ b/client/pages/file-browser.js @@ -44,6 +44,7 @@ let FileBrowser = PageView.extend({ 'click [data-hook=file-browser-help]' : function () { let modal = $(modals.operationInfoModalHtml('file-browser')).modal(); }, + 'click [data-hook=empty-trash]' : 'emptyTrash' }, initialize: function (attrs, options) { PageView.prototype.initialize.apply(this, arguments) @@ -78,7 +79,10 @@ let FileBrowser = PageView.extend({ 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.type && more.ref.type === 'folder'){ + 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 } @@ -110,6 +114,11 @@ let FileBrowser = PageView.extend({ 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) { @@ -763,7 +772,6 @@ let FileBrowser = PageView.extend({ if(o && o.original && o.original.type !== "root"){ parentPath = o.original._path } - console.log(parentPath) if(isModel) { let ext = isSpatial ? ".smdl" : ".mdl" let modelName = o && o.type === "project" ? input.value.trim().split("/").pop() + ext : input.value.trim() + ext; @@ -853,6 +861,43 @@ 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(); + } + }); + }); + }, + 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); + } + }); + }); + }, setupJstree: function () { var self = this; $.jstree.defaults.contextmenu.items = (o, cb) => { @@ -898,6 +943,17 @@ let FileBrowser = PageView.extend({ 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, @@ -1261,6 +1317,12 @@ let FileBrowser = PageView.extend({ if (o.type === 'root'){ return folder } + if (o.text === "trash") { + return {"Refresh": folder.Refresh} + } + if (o.original._path.split("/")[0] === "trash") { + return delete_node + } if (o.type === 'folder') { return $.extend(folder, common) } @@ -1319,26 +1381,28 @@ let FileBrowser = PageView.extend({ var file = e.target.text var node = $('#models-jstree').jstree().get_node(e.target) var _path = node.original._path; - 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"); + 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/pages/project-manager.js b/client/pages/project-manager.js index fd7b4625ba..67f45073ba 100644 --- a/client/pages/project-manager.js +++ b/client/pages/project-manager.js @@ -229,7 +229,7 @@ let ProjectManager = PageView.extend({ let yesBtn = document.querySelector('#emptyTrashConfirmModal .yes-modal-btn'); yesBtn.addEventListener('click', function (e) { modal.modal('hide'); - let endpoint = path.join(app.getApiPath(), "project/empty-trash")+"?path="+path.join(self.model.directory, "trash"); + let endpoint = path.join(app.getApiPath(), "file/empty-trash")+"?path="+path.join(self.model.directory, "trash"); app.getXHR(endpoint, { success: function (err, response, body) { $(self.queryByHook('empty-project-trash')).prop('disabled', true); diff --git a/client/templates/pages/fileBrowser.pug b/client/templates/pages/fileBrowser.pug index 6446a44c6e..ea87593238 100644 --- a/client/templates/pages/fileBrowser.pug +++ b/client/templates/pages/fileBrowser.pug @@ -35,4 +35,6 @@ section.page 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 + 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/edit-project.js b/client/views/edit-project.js index a0662fccaf..042dddc754 100644 --- a/client/views/edit-project.js +++ b/client/views/edit-project.js @@ -29,7 +29,7 @@ let template = require('../templates/includes/editProject.pug'); module.exports = View.extend({ template: template, events: { - 'click [data-hook=remove-project-btn]' : 'handleRemoveProjectClick' + 'click [data-hook=remove-project-btn]' : 'handleMoveToTrash' }, initialize: function (attrs, options) { View.prototype.initialize.apply(this, arguments); @@ -37,16 +37,17 @@ module.exports = View.extend({ render: function (attrs, options) { View.prototype.render.apply(this, arguments); }, - handleRemoveProjectClick: function (e) { - if(document.querySelector('#deleteFileModal')) { - document.querySelector('#deleteFileModal').remove(); + handleMoveToTrash: function (e) { + if(document.querySelector('#moveToTrashConfirmModal')) { + document.querySelector('#moveToTrashConfirmModal').remove(); } let self = this; - let endpoint = path.join(app.getApiPath(), "file/delete")+"?path="+this.model.directory; - let modal = $(modals.deleteFileHtml("project")).modal(); - let yesBtn = document.querySelector('#deleteFileModal .yes-modal-btn'); + 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=" + self.model.directory + "&dstPath=" + path.join("trash", self.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); diff --git a/client/views/file-browser-view.js b/client/views/file-browser-view.js index 6a317fbb74..de8ef0e6d2 100644 --- a/client/views/file-browser-view.js +++ b/client/views/file-browser-view.js @@ -1379,7 +1379,7 @@ module.exports = View.extend({ if (o.text === "trash"){ return refresh } - if (o.original._path.includes("trash")) { + if (o.original._path.includes(".proj/trash/")) { return {"Delete": common.Delete} } if (o.type === 'folder') { @@ -1441,26 +1441,28 @@ module.exports = View.extend({ var file = e.target.text var node = $('#models-jstree-view').jstree().get_node(e.target) var _path = node.original._path; - 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"); + 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"); + } } }); }) diff --git a/stochss/handlers/__init__.py b/stochss/handlers/__init__.py index af846e5561..426635b43e 100644 --- a/stochss/handlers/__init__.py +++ b/stochss/handlers/__init__.py @@ -58,6 +58,7 @@ def get_page_handlers(route_start): (r"/stochss/api/file/upload\/?", UploadFileAPIHandler), (r"/stochss/api/file/upload-from-link\/?", UploadFileFromLinkAPIHandler), (r"/stochss/api/file/move\/?", MoveFileAPIHandler), + (r"/stochss/api/file/empty-trash\/?", EmptyTrashAPIHandler), (r"/stochss/api/file/delete\/?", DeleteFileAPIHandler), (r"/stochss/api/file/rename\/?", RenameAPIHandler), (r"/stochss/api/file/download\/?", DownloadAPIHandler), @@ -88,7 +89,6 @@ def get_page_handlers(route_start): (r"/stochss/api/project/add-existing-model\/?", AddExistingModelAPIHandler), (r"/stochss/api/project/extract-model\/?", ExtractModelAPIHandler), (r"/stochss/api/project/extract-workflow\/?", ExtractWorkflowAPIHandler), - (r"/stochss/api/project/empty-trash\/?", EmptyTrashAPIHandler), (r"/stochss/api/project/export-combine\/?", ExportAsCombineAPIHandler), (r"/stochss/api/project/meta-data\/?", ProjectMetaDataAPIHandler), (r"/stochss/api/project/save-annotation\/?", UpdateAnnotationAPIHandler), diff --git a/stochss/handlers/file_browser.py b/stochss/handlers/file_browser.py index 7d35d52423..50fbbb523f 100644 --- a/stochss/handlers/file_browser.py +++ b/stochss/handlers/file_browser.py @@ -101,6 +101,35 @@ async def get(self): self.finish() +class EmptyTrashAPIHandler(APIHandler): + ''' + ############################################################################## + Handler for a projects trash directory + ############################################################################## + ''' + @web.authenticated + def get(self): + ''' + Empty the trash directory. + + Attributes + ---------- + ''' + self.set_header('Content-Type', 'application/json') + path = self.get_query_argument(name="path") + log.debug("Path to the trash directory: %s", path) + try: + log.info("Emptying the trash") + folder = StochSSFolder(path=path) + resp = folder.empty() + log.debug("Response message: %s", resp) + log.info("Successfully emptied the trash") + self.write(resp) + except StochSSAPIError as err: + report_error(self, log, err) + self.finish() + + class DeleteFileAPIHandler(APIHandler): ''' ################################################################################################ diff --git a/stochss/handlers/models.py b/stochss/handlers/models.py index ee9c9284e1..a55a29a947 100644 --- a/stochss/handlers/models.py +++ b/stochss/handlers/models.py @@ -315,7 +315,8 @@ async def get(self): self.set_header('Content-Type', 'application/json') try: folder = StochSSFolder(path="") - resp = folder.get_file_list(ext=".domn") + test = lambda ext, root, file: bool("trash" in root.split("/")) + resp = folder.get_file_list(ext=".domn", test=test) log.debug("Response: %s", resp) self.write(resp) except StochSSAPIError as err: @@ -340,7 +341,8 @@ async def get(self): self.set_header('Content-Type', 'application/json') try: folder = StochSSFolder(path="") - resp = folder.get_file_list(ext=".txt") + test = lambda ext, root, file: bool("trash" in root.split("/")) + resp = folder.get_file_list(ext=".txt", test=test) log.debug("Response: %s", resp) self.write(resp) except StochSSAPIError as err: diff --git a/stochss/handlers/project.py b/stochss/handlers/project.py index 8615d23428..b0b727364a 100644 --- a/stochss/handlers/project.py +++ b/stochss/handlers/project.py @@ -171,7 +171,7 @@ def get(self): folder = StochSSFolder(path="") # file will be excluded if test passes test = lambda ext, root, file: bool(".wkfl" in root or f"{path}" in root or \ - "trash" in root) + "trash" in root.split("/")) data = folder.get_file_list(ext=[".mdl", ".smdl"], test=test) log.debug("List of models: %s", data) self.write(data) @@ -276,35 +276,6 @@ def get(self): self.finish() -class EmptyTrashAPIHandler(APIHandler): - ''' - ############################################################################## - Handler for a projects trash directory - ############################################################################## - ''' - @web.authenticated - def get(self): - ''' - Empty the trash directory. - - Attributes - ---------- - ''' - self.set_header('Content-Type', 'application/json') - path = self.get_query_argument(name="path") - log.debug("Path to the trash directory: %s", path) - try: - log.info("Emptying the trash") - folder = StochSSFolder(path=path) - resp = folder.empty() - log.debug("Response message: %s", resp) - log.info("Successfully emptied the trash") - self.write(resp) - except StochSSAPIError as err: - report_error(self, log, err) - self.finish() - - class ProjectMetaDataAPIHandler(APIHandler): ''' ############################################################################## diff --git a/stochss/handlers/util/stochss_base.py b/stochss/handlers/util/stochss_base.py index 8b6b7478fb..c17075e87f 100644 --- a/stochss/handlers/util/stochss_base.py +++ b/stochss/handlers/util/stochss_base.py @@ -97,6 +97,8 @@ def get_new_path(self, dst_path): New path for the file object from the users home directory ''' new_path = os.path.join(self.user_dir, dst_path) + if dst_path.startswith("trash/") and not "trash" in os.listdir(self.user_dir): + os.mkdir(os.path.join(self.user_dir, "trash")) if new_path.split().pop().replace('.', '', 5).isdigit(): return new_path.replace(new_path.split().pop(), "").strip() if "trash/" in new_path and os.path.exists(new_path): diff --git a/stochss/handlers/util/stochss_folder.py b/stochss/handlers/util/stochss_folder.py index 8d9200e65b..d9dbc44d4c 100644 --- a/stochss/handlers/util/stochss_folder.py +++ b/stochss/handlers/util/stochss_folder.py @@ -340,7 +340,8 @@ def get_project_list(self): Attributes ---------- ''' - data = self.get_file_list(ext=".proj", folder=True) + test = lambda ext, root, file: bool("trash" in root.split("/")) + data = self.get_file_list(ext=".proj", folder=True, test=test) projects = [] for file in data['files']: for path in data['paths'][file[0]]: diff --git a/stochss/handlers/util/stochss_workflow.py b/stochss/handlers/util/stochss_workflow.py index 597707257e..c4646e5304 100644 --- a/stochss/handlers/util/stochss_workflow.py +++ b/stochss/handlers/util/stochss_workflow.py @@ -333,7 +333,7 @@ def load(self): if ".proj" in self.path: if "WorkflowGroup1.wkgp" in self.path: proj = StochSSFolder(path=os.path.dirname(self.get_dir_name(full=True))) - test = lambda ext, root, file: ".wkfl" in root or "trash" in root + test = lambda ext, root, file: ".wkfl" in root or "trash" in root.split("/") models = proj.get_file_list(ext=[".mdl"], test=test) else: wkgp = StochSSFolder(path=self.get_dir_name(full=True)) @@ -346,7 +346,8 @@ def load(self): self.workflow['model'] = None else: root = StochSSFolder(path="") - test = lambda ext, root, file: ".wkfl" in root or ".proj" in root + test = lambda ext, root, file: ".wkfl" in root or ".proj" in root or \ + "trash" in root.split("/") models = root.get_file_list(ext=[".mdl"], test=test) if models is not None: self.workflow['models'] = models