diff --git a/services/static-webserver/client/source/class/osparc/dashboard/CardBase.js b/services/static-webserver/client/source/class/osparc/dashboard/CardBase.js index acb3fe35386..3bcd200c2ee 100644 --- a/services/static-webserver/client/source/class/osparc/dashboard/CardBase.js +++ b/services/static-webserver/client/source/class/osparc/dashboard/CardBase.js @@ -395,6 +395,14 @@ qx.Class.define("osparc.dashboard.CardBase", { return this.getResourceType() === resourceType; }, + isItemNotClickable: function() { + const studyBrowserContext = osparc.store.Store.getInstance().getStudyBrowserContext(); + return ( + this.getBlocked() === true || // It could be blocked by IN_USE or UNKNOWN_SERVICE + (this.isResourceType("study") && (studyBrowserContext === "trash")) // It could a trashed study + ); + }, + __applyResourceData: function(resourceData) { let uuid = null; let owner = null; @@ -776,9 +784,9 @@ qx.Class.define("osparc.dashboard.CardBase", { if (moveToButton) { moveToButton.setEnabled(osparc.study.Utils.canMoveTo(resourceData)); } - const deleteButton = menuButtons.find(menuBtn => "deleteButton" in menuBtn); - if (deleteButton) { - deleteButton.setEnabled(osparc.study.Utils.canBeDeleted(resourceData)); + const trashButton = menuButtons.find(menuBtn => "trashButton" in menuBtn); + if (trashButton) { + trashButton.setEnabled(osparc.study.Utils.canBeDeleted(resourceData)); } } }, diff --git a/services/static-webserver/client/source/class/osparc/dashboard/ContextBreadcrumbs.js b/services/static-webserver/client/source/class/osparc/dashboard/ContextBreadcrumbs.js index dba858ffad8..4358bc68848 100644 --- a/services/static-webserver/client/source/class/osparc/dashboard/ContextBreadcrumbs.js +++ b/services/static-webserver/client/source/class/osparc/dashboard/ContextBreadcrumbs.js @@ -24,6 +24,8 @@ qx.Class.define("osparc.dashboard.ContextBreadcrumbs", { this._setLayout(new qx.ui.layout.HBox(5).set({ alignY: "middle" })); + + osparc.store.Store.getInstance().addListener("changeStudyBrowserContext", () => this.__rebuild(), this); }, events: { @@ -31,14 +33,6 @@ qx.Class.define("osparc.dashboard.ContextBreadcrumbs", { }, properties: { - currentContext: { - check: ["studiesAndFolders", "workspaces", "search"], - nullable: false, - init: "studiesAndFolders", - event: "changeCurrentContext", - apply: "__rebuild" - }, - currentWorkspaceId: { check: "Number", nullable: true, @@ -60,7 +54,8 @@ qx.Class.define("osparc.dashboard.ContextBreadcrumbs", { __rebuild: function() { this._removeAll(); - if (this.getCurrentContext() !== "studiesAndFolders") { + const currentContext = osparc.store.Store.getInstance().getStudyBrowserContext(); + if (currentContext !== "studiesAndFolders") { return; } diff --git a/services/static-webserver/client/source/class/osparc/dashboard/FolderButtonItem.js b/services/static-webserver/client/source/class/osparc/dashboard/FolderButtonItem.js index 2932a774c92..ac919b73579 100644 --- a/services/static-webserver/client/source/class/osparc/dashboard/FolderButtonItem.js +++ b/services/static-webserver/client/source/class/osparc/dashboard/FolderButtonItem.js @@ -46,6 +46,8 @@ qx.Class.define("osparc.dashboard.FolderButtonItem", { "folderSelected": "qx.event.type.Data", "folderUpdated": "qx.event.type.Data", "moveFolderToRequested": "qx.event.type.Data", + "trashFolderRequested": "qx.event.type.Data", + "untrashFolderRequested": "qx.event.type.Data", "deleteFolderRequested": "qx.event.type.Data", "changeContext": "qx.event.type.Data", }, @@ -85,7 +87,7 @@ qx.Class.define("osparc.dashboard.FolderButtonItem", { check: "Date", nullable: true, apply: "__applyLastModified" - } + }, }, members: { @@ -219,6 +221,16 @@ qx.Class.define("osparc.dashboard.FolderButtonItem", { menu.addSeparator(); + const trashButton = new qx.ui.menu.Button(this.tr("Trash"), "@FontAwesome5Solid/trash/12"); + trashButton.addListener("execute", () => this.__trashFolderRequested(), this); + menu.add(trashButton); + } else if (studyBrowserContext === "trash") { + const restoreButton = new qx.ui.menu.Button(this.tr("Restore"), "@MaterialIcons/restore_from_trash/16"); + restoreButton.addListener("execute", () => this.fireDataEvent("untrashFolderRequested", this.getFolder()), this); + menu.add(restoreButton); + + menu.addSeparator(); + const deleteButton = new qx.ui.menu.Button(this.tr("Delete"), "@FontAwesome5Solid/trash/12"); osparc.utils.Utils.setIdToWidget(deleteButton, "deleteFolderMenuItem"); deleteButton.addListener("execute", () => this.__deleteFolderRequested(), this); @@ -229,7 +241,9 @@ qx.Class.define("osparc.dashboard.FolderButtonItem", { }, __itemSelected: function(newVal) { - if (newVal) { + const studyBrowserContext = osparc.store.Store.getInstance().getStudyBrowserContext(); + // do not allow selecting workspace + if (studyBrowserContext !== "trash" && newVal) { this.fireDataEvent("folderSelected", this.getFolderId()); } this.setValue(false); @@ -262,6 +276,24 @@ qx.Class.define("osparc.dashboard.FolderButtonItem", { folderEditor.addListener("cancel", () => win.close()); }, + __trashFolderRequested: function() { + const trashDays = osparc.store.StaticInfo.getInstance().getTrashRetentionDays(); + let msg = this.tr("Are you sure you want to move the Folder and all its content to the trash?"); + msg += "

" + this.tr("It will be permanently deleted after ") + trashDays + " days."; + const confirmationWin = new osparc.ui.window.Confirmation(msg).set({ + caption: this.tr("Move to Trash"), + confirmText: this.tr("Move to Trash"), + confirmAction: "delete" + }); + confirmationWin.center(); + confirmationWin.open(); + confirmationWin.addListener("close", () => { + if (confirmationWin.getConfirmed()) { + this.fireDataEvent("trashFolderRequested", this.getFolderId()); + } + }, this); + }, + __deleteFolderRequested: function() { const msg = this.tr("Are you sure you want to delete") + " " + this.getTitle() + "?"; const confirmationWin = new osparc.ui.window.Confirmation(msg).set({ diff --git a/services/static-webserver/client/source/class/osparc/dashboard/GridButtonItem.js b/services/static-webserver/client/source/class/osparc/dashboard/GridButtonItem.js index 828a0c74ba7..e9019262342 100644 --- a/services/static-webserver/client/source/class/osparc/dashboard/GridButtonItem.js +++ b/services/static-webserver/client/source/class/osparc/dashboard/GridButtonItem.js @@ -188,8 +188,7 @@ qx.Class.define("osparc.dashboard.GridButtonItem", { }, __itemSelected: function() { - // It could be blocked by IN_USE or UNKNOWN_SERVICE - if (this.getBlocked() === true) { + if (this.isItemNotClickable()) { this.setValue(false); return; } diff --git a/services/static-webserver/client/source/class/osparc/dashboard/ListButtonItem.js b/services/static-webserver/client/source/class/osparc/dashboard/ListButtonItem.js index 71f59b970df..9c433550185 100644 --- a/services/static-webserver/client/source/class/osparc/dashboard/ListButtonItem.js +++ b/services/static-webserver/client/source/class/osparc/dashboard/ListButtonItem.js @@ -274,8 +274,7 @@ qx.Class.define("osparc.dashboard.ListButtonItem", { }, __itemSelected: function() { - // It could be blocked by IN_USE or UNKNOWN_SERVICE - if (this.getBlocked() === true) { + if (this.isItemNotClickable()) { this.setValue(false); return; } diff --git a/services/static-webserver/client/source/class/osparc/dashboard/ResourceBrowserBase.js b/services/static-webserver/client/source/class/osparc/dashboard/ResourceBrowserBase.js index ca565c756f3..c007ca05f7e 100644 --- a/services/static-webserver/client/source/class/osparc/dashboard/ResourceBrowserBase.js +++ b/services/static-webserver/client/source/class/osparc/dashboard/ResourceBrowserBase.js @@ -244,7 +244,7 @@ qx.Class.define("osparc.dashboard.ResourceBrowserBase", { this._addToLayout(searchBarFilter); }, - _createResourcesLayout: function() { + _createResourcesLayout: function(flatListId) { const toolbar = this._toolbar = new qx.ui.toolbar.ToolBar().set({ backgroundColor: "transparent", spacing: 10, @@ -255,7 +255,13 @@ qx.Class.define("osparc.dashboard.ResourceBrowserBase", { this.__viewModeLayout = new qx.ui.toolbar.Part(); - const resourcesContainer = this._resourcesContainer = new osparc.dashboard.ResourceContainerManager(); + const resourcesContainer = this._resourcesContainer = new osparc.dashboard.ResourceContainerManager(this._resourceType); + if (flatListId) { + const list = this._resourcesContainer.getFlatList(); + if (list) { + osparc.utils.Utils.setIdToWidget(list, flatListId); + } + } if (this._resourceType === "study") { const viewMode = osparc.utils.Utils.localCache.getLocalStorageItem("studiesViewMode"); if (viewMode) { @@ -270,6 +276,8 @@ qx.Class.define("osparc.dashboard.ResourceBrowserBase", { resourcesContainer.addListener("emptyStudyClicked", e => this._deleteResourceRequested(e.getData())); resourcesContainer.addListener("folderUpdated", e => this._folderUpdated(e.getData())); resourcesContainer.addListener("moveFolderToRequested", e => this._moveFolderToRequested(e.getData())); + resourcesContainer.addListener("trashFolderRequested", e => this._trashFolderRequested(e.getData())); + resourcesContainer.addListener("untrashFolderRequested", e => this._untrashFolderRequested(e.getData())); resourcesContainer.addListener("deleteFolderRequested", e => this._deleteFolderRequested(e.getData())); resourcesContainer.addListener("folderSelected", e => { const folderId = e.getData(); @@ -288,6 +296,8 @@ qx.Class.define("osparc.dashboard.ResourceBrowserBase", { this._changeContext(context, workspaceId, folderId); }, this); resourcesContainer.addListener("workspaceUpdated", e => this._workspaceUpdated(e.getData())); + resourcesContainer.addListener("trashWorkspaceRequested", e => this._trashWorkspaceRequested(e.getData())); + resourcesContainer.addListener("untrashWorkspaceRequested", e => this._untrashWorkspaceRequested(e.getData())); resourcesContainer.addListener("deleteWorkspaceRequested", e => this._deleteWorkspaceRequested(e.getData())); this._addToLayout(resourcesContainer); @@ -502,6 +512,14 @@ qx.Class.define("osparc.dashboard.ResourceBrowserBase", { throw new Error("Abstract method called!"); }, + _trashFolderRequested: function(folderId) { + throw new Error("Abstract method called!"); + }, + + _untrashFolderRequested: function(folder) { + throw new Error("Abstract method called!"); + }, + _deleteFolderRequested: function(folderId) { throw new Error("Abstract method called!"); }, diff --git a/services/static-webserver/client/source/class/osparc/dashboard/ResourceContainerManager.js b/services/static-webserver/client/source/class/osparc/dashboard/ResourceContainerManager.js index ba1485f024f..b33d5015231 100644 --- a/services/static-webserver/client/source/class/osparc/dashboard/ResourceContainerManager.js +++ b/services/static-webserver/client/source/class/osparc/dashboard/ResourceContainerManager.js @@ -18,7 +18,7 @@ qx.Class.define("osparc.dashboard.ResourceContainerManager", { extend: qx.ui.core.Widget, - construct: function() { + construct: function(resourceType) { this.base(arguments); this._setLayout(new qx.ui.layout.VBox(15)); @@ -27,19 +27,20 @@ qx.Class.define("osparc.dashboard.ResourceContainerManager", { paddingBottom: 60 }); - this.__foldersList = []; this.__workspacesList = []; + this.__foldersList = []; this.__resourcesList = []; this.__groupedContainersList = []; + if (resourceType === "study") { + const workspacesContainer = this.__workspacesContainer = new osparc.dashboard.ToggleButtonContainer(); + this._add(workspacesContainer); + workspacesContainer.setVisibility(osparc.utils.DisabledPlugins.isFoldersEnabled() ? "visible" : "excluded"); - const workspacesContainer = this.__workspacesContainer = new osparc.dashboard.ToggleButtonContainer(); - workspacesContainer.setVisibility(osparc.utils.DisabledPlugins.isFoldersEnabled() ? "visible" : "excluded"); - - - const foldersContainer = this.__foldersContainer = new osparc.dashboard.ToggleButtonContainer(); - this._add(foldersContainer); - foldersContainer.setVisibility(osparc.utils.DisabledPlugins.isFoldersEnabled() ? "visible" : "excluded"); + const foldersContainer = this.__foldersContainer = new osparc.dashboard.ToggleButtonContainer(); + this._add(foldersContainer); + foldersContainer.setVisibility(osparc.utils.DisabledPlugins.isFoldersEnabled() ? "visible" : "excluded"); + } const nonGroupedContainer = this.__nonGroupedContainer = this.__createFlatList(); this._add(nonGroupedContainer); @@ -75,9 +76,13 @@ qx.Class.define("osparc.dashboard.ResourceContainerManager", { "folderSelected": "qx.event.type.Data", "folderUpdated": "qx.event.type.Data", "moveFolderToRequested": "qx.event.type.Data", + "trashFolderRequested": "qx.event.type.Data", + "untrashFolderRequested": "qx.event.type.Data", "deleteFolderRequested": "qx.event.type.Data", "workspaceSelected": "qx.event.type.Data", "workspaceUpdated": "qx.event.type.Data", + "trashWorkspaceRequested": "qx.event.type.Data", + "untrashWorkspaceRequested": "qx.event.type.Data", "deleteWorkspaceRequested": "qx.event.type.Data", "changeContext": "qx.event.type.Data", }, @@ -260,9 +265,13 @@ qx.Class.define("osparc.dashboard.ResourceContainerManager", { }, __cleanAll: function() { - if (this.__workspacesContainer) { - this.__workspacesContainer.removeAll(); + if (this._getChildren().includes(this.__nonGroupedContainer)) { + this._remove(this.__nonGroupedContainer); + } + if (this._getChildren().includes(this.__groupedContainers)) { + this._remove(this.__groupedContainers); } + if (this.__nonGroupedContainer) { this.__nonGroupedContainer.removeAll(); this.__nonGroupedContainer = null; @@ -274,7 +283,6 @@ qx.Class.define("osparc.dashboard.ResourceContainerManager", { groupedContainer.getContentContainer().removeAll(); }); this.__groupedContainersList = []; - this._removeAll(); }, __addFoldersContainer: function() { @@ -299,9 +307,6 @@ qx.Class.define("osparc.dashboard.ResourceContainerManager", { __rebuildLayout: function(resourceType) { this.__cleanAll(); - if (osparc.utils.DisabledPlugins.isFoldersEnabled()) { - this.__addFoldersContainer(); - } if (this.getGroupBy()) { const noGroupContainer = this.__createGroupContainer("no-group", "No Group", "transparent"); this.__groupedContainers.add(noGroupContainer); @@ -361,8 +366,9 @@ qx.Class.define("osparc.dashboard.ResourceContainerManager", { }, reloadWorkspaces: function() { - this.__cleanAll(); - this._add(this.__workspacesContainer); + if (this.__workspacesContainer) { + this.__workspacesContainer.removeAll(); + } let workspacesCards = []; this.__workspacesList.forEach(workspaceData => workspacesCards.push(this.__workspaceToCard(workspaceData))); return workspacesCards; @@ -383,6 +389,8 @@ qx.Class.define("osparc.dashboard.ResourceContainerManager", { [ "workspaceSelected", "workspaceUpdated", + "trashWorkspaceRequested", + "untrashWorkspaceRequested", "deleteWorkspaceRequested", ].forEach(eName => card.addListener(eName, e => this.fireDataEvent(eName, e.getData()))); return card; @@ -419,6 +427,8 @@ qx.Class.define("osparc.dashboard.ResourceContainerManager", { "folderSelected", "folderUpdated", "moveFolderToRequested", + "trashFolderRequested", + "untrashFolderRequested", "deleteFolderRequested", "changeContext", ].forEach(eName => card.addListener(eName, e => this.fireDataEvent(eName, e.getData()))); @@ -494,7 +504,7 @@ qx.Class.define("osparc.dashboard.ResourceContainerManager", { } else { groupContainer.exclude(); } - this._add(groupContainer); + this.__groupedContainers.add(groupContainer); this.__moveNoGroupToLast(); } const card = this.__createCard(resourceData); diff --git a/services/static-webserver/client/source/class/osparc/dashboard/ResourceFilter.js b/services/static-webserver/client/source/class/osparc/dashboard/ResourceFilter.js index 0c452e3e33a..dcda538841c 100644 --- a/services/static-webserver/client/source/class/osparc/dashboard/ResourceFilter.js +++ b/services/static-webserver/client/source/class/osparc/dashboard/ResourceFilter.js @@ -34,6 +34,7 @@ qx.Class.define("osparc.dashboard.ResourceFilter", { }, events: { + "trashContext": "qx.event.type.Event", "changeSharedWith": "qx.event.type.Data", "changeSelectedTags": "qx.event.type.Data", "changeServiceType": "qx.event.type.Data" @@ -42,6 +43,7 @@ qx.Class.define("osparc.dashboard.ResourceFilter", { members: { __resourceType: null, __workspacesAndFoldersTree: null, + __trashButton: null, __sharedWithButtons: null, __tagButtons: null, __serviceTypeButtons: null, @@ -49,6 +51,7 @@ qx.Class.define("osparc.dashboard.ResourceFilter", { __buildLayout: function() { if (this.__resourceType === "study" && osparc.utils.DisabledPlugins.isFoldersEnabled()) { this._add(this.__createWorkspacesAndFoldersTree()); + this._add(this.__createTrashBin()); } else { this._add(this.__createSharedWithFilterLayout()); } @@ -62,6 +65,16 @@ qx.Class.define("osparc.dashboard.ResourceFilter", { } }, + contextChanged: function(context, workspaceId, folderId) { + this.__workspacesAndFoldersTree.set({ + currentWorkspaceId: workspaceId, + currentFolderId: folderId, + }); + this.__workspacesAndFoldersTree.contextChanged(context); + + this.__trashButton.setValue(context === "trash"); + }, + /* WORKSPACES AND FOLDERS */ __createWorkspacesAndFoldersTree: function() { const workspacesAndFoldersTree = this.__workspacesAndFoldersTree = new osparc.dashboard.WorkspacesAndFoldersTree(); @@ -85,6 +98,76 @@ qx.Class.define("osparc.dashboard.ResourceFilter", { }, /* /WORKSPACES AND FOLDERS */ + /* TRASH BIN */ + __createTrashBin: function() { + const trashButton = this.__trashButton = new qx.ui.toolbar.RadioButton().set({ + value: false, + appearance: "filter-toggle-button", + label: this.tr("Trash"), + icon: "@FontAwesome5Solid/trash/18", + }); + trashButton.addListener("changeValue", e => { + const trashEnabled = e.getData(); + if (trashEnabled) { + this.fireEvent("trashContext"); + } + }); + this.evaluateTrashEmpty(); + return trashButton; + }, + + evaluateTrashEmpty: function() { + const studiesParams = { + url: { + offset: 0, + limit: 1, // just one + orderBy: JSON.stringify({ + field: "last_change_date", + direction: "desc" + }), + } + }; + const foldersParams = { + url: { + offset: 0, + limit: 1, // just one + orderBy: JSON.stringify({ + field: "modified_at", + direction: "desc" + }), + } + }; + const workspacesParams = { + url: { + offset: 0, + limit: 1, // just one + orderBy: JSON.stringify({ + field: "modified_at", + direction: "desc" + }), + } + }; + Promise.all([ + osparc.data.Resources.fetch("studies", "getPageTrashed", studiesParams), + osparc.data.Resources.fetch("folders", "getPageTrashed", foldersParams), + osparc.data.Resources.fetch("workspaces", "getPageTrashed", workspacesParams), + ]) + .then(values => { + const nTrashedStudies = values[0].length; + const nTrashedFolders = values[1].length; + const nTrashedWorkspaces = values[2].length; + this.setTrashEmpty((nTrashedStudies+nTrashedFolders+nTrashedWorkspaces) === 0); + }) + .catch(err => console.error(err)); + }, + + setTrashEmpty: function(isEmpty) { + this.__trashButton.set({ + textColor: isEmpty ? "text" : "danger-red" + }); + }, + /* /TRASH BIN */ + /* SHARED WITH */ __createSharedWithFilterLayout: function() { const sharedWithLayout = new qx.ui.container.Composite(new qx.ui.layout.VBox(5)); diff --git a/services/static-webserver/client/source/class/osparc/dashboard/ServiceBrowser.js b/services/static-webserver/client/source/class/osparc/dashboard/ServiceBrowser.js index 15278743e54..5fbaa4ebaf7 100644 --- a/services/static-webserver/client/source/class/osparc/dashboard/ServiceBrowser.js +++ b/services/static-webserver/client/source/class/osparc/dashboard/ServiceBrowser.js @@ -129,11 +129,7 @@ qx.Class.define("osparc.dashboard.ServiceBrowser", { // LAYOUT // _createLayout: function() { this._createSearchBar(); - this._createResourcesLayout(); - const list = this._resourcesContainer.getFlatList(); - if (list) { - osparc.utils.Utils.setIdToWidget(list, "servicesList"); - } + this._createResourcesLayout("servicesList"); this.__addNewServiceButtons(); this._toolbar.add(new qx.ui.core.Spacer(), { diff --git a/services/static-webserver/client/source/class/osparc/dashboard/StudyBrowser.js b/services/static-webserver/client/source/class/osparc/dashboard/StudyBrowser.js index 882848dd295..c7ef8f916f2 100644 --- a/services/static-webserver/client/source/class/osparc/dashboard/StudyBrowser.js +++ b/services/static-webserver/client/source/class/osparc/dashboard/StudyBrowser.js @@ -42,6 +42,9 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { construct: function() { this._resourceType = "study"; this.base(arguments); + + const store = osparc.store.Store.getInstance(); + this.bind("currentContext", store, "studyBrowserContext"); }, events: { @@ -50,7 +53,7 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { properties: { currentContext: { - check: ["studiesAndFolders", "workspaces", "search"], + check: ["studiesAndFolders", "workspaces", "search", "trash"], nullable: false, init: "studiesAndFolders", event: "changeCurrentContext" @@ -96,6 +99,7 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { __workspacesList: null, __foldersList: null, __loadingFolders: null, + __loadingWorkspaces: null, // overridden initResources: function() { @@ -160,10 +164,48 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { }, __reloadWorkspaces: function() { + if ( + !osparc.auth.Manager.getInstance().isLoggedIn() || + !osparc.utils.DisabledPlugins.isFoldersEnabled() || + this.getCurrentContext() === "studiesAndFolders" || + this.getCurrentContext() === "search" || // not yet implemented for workspaces + this.__loadingWorkspaces + ) { + return; + } + + let request = null; + switch (this.getCurrentContext()) { + case "search": { + const filterData = this._searchBarFilter.getFilterData(); + const text = filterData.text ? encodeURIComponent(filterData.text) : ""; + request = osparc.store.Workspaces.getInstance().searchWorkspaces(text); + break; + } + case "workspaces": { + request = osparc.store.Workspaces.getInstance().fetchWorkspaces(); + break; + } + case "trash": + request = osparc.store.Workspaces.getInstance().fetchAllTrashedWorkspaces(); + break; + } + + this.__loadingWorkspaces = true; this.__setWorkspacesToList([]); - osparc.store.Workspaces.getInstance().fetchWorkspaces() + request .then(workspaces => { this.__setWorkspacesToList(workspaces); + if (this.getCurrentContext() === "trash") { + if (workspaces.length) { + this.__header.getChildControl("empty-trash-button").show(); + } + } + }) + .catch(console.error) + .finally(() => { + this.__addNewWorkspaceButton(); + this.__loadingWorkspaces = null; }); }, @@ -177,7 +219,6 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { return; } - this.__loadingFolders = true; let request = null; switch (this.getCurrentContext()) { case "search": { @@ -192,14 +233,27 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { request = osparc.store.Folders.getInstance().fetchFolders(folderId, workspaceId, this.getOrderBy()); break; } + case "trash": + request = osparc.store.Folders.getInstance().fetchAllTrashedFolders(this.getOrderBy()); + break; } + + this.__loadingFolders = true; this.__setFoldersToList([]); request .then(folders => { this.__setFoldersToList(folders); + if (this.getCurrentContext() === "trash") { + if (folders.length) { + this.__header.getChildControl("empty-trash-button").show(); + } + } }) .catch(console.error) - .finally(() => this.__loadingFolders = null); + .finally(() => { + this.__addNewFolderButton(); + this.__loadingFolders = null; + }); }, __reloadStudies: function() { @@ -257,6 +311,12 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { this._resourcesContainer.getFlatList().nextRequest = resp["_links"]["next"]; } + if (this.getCurrentContext() === "trash") { + if (this._resourcesList.length) { + this.__header.getChildControl("empty-trash-button").show(); + } + } + // Show Quick Start if there are no studies in the root folder of the personal workspace const quickStartInfo = osparc.product.quickStart.Utils.getQuickStart(); if (quickStartInfo) { @@ -267,6 +327,7 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { const nStudies = "_meta" in resp ? resp["_meta"]["total"] : 0; if ( nStudies === 0 && + this.getCurrentContext() === "studiesAndFolders" && this.getCurrentWorkspaceId() === null && this.getCurrentFolderId() === null ) { @@ -354,9 +415,6 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { const cards = this._resourcesContainer.reloadCards("studies"); this.__configureStudyCards(cards); - // they were removed in the above reloadCards - this.__reloadFolders(); - this.__addNewStudyButtons(); const loadMoreBtn = this.__createLoadMoreButton(); @@ -382,6 +440,12 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { __reloadWorkspaceCards: function() { this._resourcesContainer.setWorkspacesToList(this.__workspacesList); this._resourcesContainer.reloadWorkspaces(); + }, + + __addNewWorkspaceButton: function() { + if (this.getCurrentContext() !== "workspaces") { + return; + } const newWorkspaceCard = new osparc.dashboard.WorkspaceButtonNew(); newWorkspaceCard.setCardKey("new-workspace"); @@ -401,13 +465,44 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { }, _workspaceUpdated: function() { - this.__reloadWorkspaceCards(); + this.__reloadWorkspaces(); + }, + + _trashWorkspaceRequested: function(workspaceId) { + osparc.store.Workspaces.getInstance().trashWorkspace(workspaceId) + .then(() => { + this.__reloadWorkspaces(); + const msg = this.tr("Successfully moved to Trash"); + osparc.FlashMessenger.getInstance().logAs(msg, "INFO"); + this._resourceFilter.setTrashEmpty(false); + }) + .catch(err => { + console.error(err); + osparc.FlashMessenger.getInstance().logAs(err, "ERROR"); + }); + }, + + _untrashWorkspaceRequested: function(workspace) { + osparc.store.Workspaces.getInstance().untrashWorkspace(workspace) + .then(() => { + this.__reloadWorkspaces(); + const msg = this.tr("Successfully restored"); + osparc.FlashMessenger.getInstance().logAs(msg, "INFO"); + this._resourceFilter.evaluateTrashEmpty(); + }) + .catch(err => { + console.error(err); + osparc.FlashMessenger.getInstance().logAs(err, "ERROR"); + }); }, _deleteWorkspaceRequested: function(workspaceId) { osparc.store.Workspaces.getInstance().deleteWorkspace(workspaceId) .then(() => { this.__reloadWorkspaces(); + const msg = this.tr("Successfully deleted"); + osparc.FlashMessenger.getInstance().logAs(msg, "INFO"); + this._resourceFilter.evaluateTrashEmpty(); }) .catch(err => { console.error(err); @@ -420,8 +515,6 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { __reloadFolderCards: function() { this._resourcesContainer.setFoldersToList(this.__foldersList); this._resourcesContainer.reloadFolders(); - - this.__addNewFolderButton(); }, __addNewFolderButton: function() { @@ -544,9 +637,42 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { .catch(err => console.error(err)); }, + _trashFolderRequested: function(folderId) { + osparc.store.Folders.getInstance().trashFolder(folderId, this.getCurrentWorkspaceId()) + .then(() => { + this.__reloadFolders(); + const msg = this.tr("Successfully moved to Trash"); + osparc.FlashMessenger.getInstance().logAs(msg, "INFO"); + this._resourceFilter.setTrashEmpty(false); + }) + .catch(err => { + console.error(err); + osparc.FlashMessenger.getInstance().logAs(err, "ERROR"); + }) + }, + + _untrashFolderRequested: function(folder) { + osparc.store.Folders.getInstance().untrashFolder(folder) + .then(() => { + this.__reloadFolders(); + const msg = this.tr("Successfully restored"); + osparc.FlashMessenger.getInstance().logAs(msg, "INFO"); + this._resourceFilter.evaluateTrashEmpty(); + }) + .catch(err => { + console.error(err); + osparc.FlashMessenger.getInstance().logAs(err, "ERROR"); + }) + }, + _deleteFolderRequested: function(folderId) { osparc.store.Folders.getInstance().deleteFolder(folderId, this.getCurrentWorkspaceId()) - .then(() => this.__reloadFolders()) + .then(() => { + this.__reloadFolders(); + const msg = this.tr("Successfully deleted"); + osparc.FlashMessenger.getInstance().logAs(msg, "INFO"); + this._resourceFilter.evaluateTrashEmpty(); + }) .catch(err => console.error(err)); }, // /FOLDERS @@ -555,7 +681,7 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { cards.forEach(card => { card.setMultiSelectionMode(this.getMultiSelection()); card.addListener("tap", e => { - if (card.getBlocked() === true) { + if (card.isItemNotClickable()) { card.setValue(false); } else { this.__itemClicked(card, e.getNativeEvent().shiftKey); @@ -634,6 +760,11 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { // loose equality: will do a Number to String conversion if necessary sameContext &= key in reqParams && reqParams[key] == value; }); + // both ways + Object.entries(reqParams).forEach(([key, value]) => { + // loose equality: will do a Number to String conversion if necessary + sameContext &= key in currentParams && currentParams[key] == value; + }); return !sameContext; }, @@ -711,6 +842,9 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { let request = null; switch (this.getCurrentContext()) { + case "trash": + request = osparc.data.Resources.fetch("studies", "getPageTrashed", params, options); + break; case "search": request = osparc.data.Resources.fetch("studies", "getPageSearch", params, options); break; @@ -864,15 +998,11 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { if (osparc.utils.DisabledPlugins.isFoldersEnabled()) { const header = this.__header = new osparc.dashboard.StudyBrowserHeader(); + this.__header.addListener("emptyTrashRequested", () => this.__emptyTrash(), this); this._addToLayout(header); } - this._createResourcesLayout(); - - const list = this._resourcesContainer.getFlatList(); - if (list) { - osparc.utils.Utils.setIdToWidget(list, "studiesList"); - } + this._createResourcesLayout("studiesList"); const importStudyButton = this.__createImportButton(); const isDisabled = osparc.utils.DisabledPlugins.isImportDisabled(); @@ -882,10 +1012,13 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { const selectStudiesButton = this.__createSelectButton(); this._toolbar.add(selectStudiesButton); - const studiesMoveButton = this.__createMoveStudiesButton(false); + const studiesMoveButton = this.__createMoveStudiesButton(); this._toolbar.add(studiesMoveButton); - const studiesDeleteButton = this.__createDeleteButton(false); + const studiesTrashButton = this.__createTrashStudiesButton(); + this._toolbar.add(studiesTrashButton); + + const studiesDeleteButton = this.__createDeleteStudiesButton(); this._toolbar.add(studiesDeleteButton); this._toolbar.add(new qx.ui.core.Spacer(), { @@ -924,8 +1057,13 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { label: selection.length > 1 ? this.tr("Move selected")+" ("+selection.length+")" : this.tr("Move") }); + studiesTrashButton.set({ + visibility: selection.length && currentContext === "studiesAndFolders" ? "visible" : "excluded", + label: selection.length > 1 ? this.tr("Trash selected")+" ("+selection.length+")" : this.tr("Trash") + }); + studiesDeleteButton.set({ - visibility: selection.length ? "visible" : "excluded", + visibility: selection.length && currentContext === "trash" ? "visible" : "excluded", label: selection.length > 1 ? this.tr("Delete selected")+" ("+selection.length+")" : this.tr("Delete") }); }); @@ -956,6 +1094,10 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { } }, this); + this._resourceFilter.addListener("trashContext", () => { + this._changeContext("trash"); + }); + this._searchBarFilter.addListener("filterChanged", e => { const filterData = e.getData(); if (filterData.text) { @@ -987,41 +1129,56 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { currentWorkspaceId: workspaceId, currentFolderId: folderId, }); - this._loadingResourcesBtn.setFetching(false); this.resetSelection(); this.setMultiSelection(false); - this.invalidateStudies(); - this._resourcesContainer.setResourcesToList([]); + + // reset lists + this.__setWorkspacesToList([]); + this.__setFoldersToList([]); + this._resourcesList = []; + this._resourcesContainer.setResourcesToList(this._resourcesList); + this._resourcesContainer.reloadCards("studies"); this._toolbar.show(); - if (context === "search") { - this.__reloadFolders(); - this.__reloadStudies(); - } else if (context === "workspaces") { - this._toolbar.hide(); - this._searchBarFilter.resetFilters(); - this.__reloadWorkspaces(); - } else if (context === "studiesAndFolders") { - this._searchBarFilter.resetFilters(); - this.__reloadFolders(); - this.__reloadStudies(); + switch (this.getCurrentContext()) { + case "studiesAndFolders": + this._searchBarFilter.resetFilters(); + this.__reloadFolders(); + this._loadingResourcesBtn.setFetching(false); + this.invalidateStudies(); + this.__reloadStudies(); + break; + case "workspaces": + this._toolbar.exclude(); + this._searchBarFilter.resetFilters(); + this.__reloadWorkspaces(); + break; + case "search": + this.__reloadWorkspaces(); + this.__reloadFolders(); + this._loadingResourcesBtn.setFetching(false); + this.invalidateStudies(); + this.__reloadStudies(); + break; + case "trash": + this._searchBarFilter.resetFilters(); + this.__reloadWorkspaces(); + this.__reloadFolders(); + this._loadingResourcesBtn.setFetching(false); + this.invalidateStudies(); + this.__reloadStudies(); + break; } // notify header const header = this.__header; header.set({ - currentContext: context, currentWorkspaceId: workspaceId, currentFolderId: folderId, }); - // notify workspacesAndFoldersTree - const workspacesAndFoldersTree = this._resourceFilter.getWorkspacesAndFoldersTree(); - workspacesAndFoldersTree.set({ - currentWorkspaceId: workspaceId, - currentFolderId: folderId, - }); - workspacesAndFoldersTree.contextChanged(context); + // notify Filters on the left + this._resourceFilter.contextChanged(context, workspaceId, folderId); } }, @@ -1129,7 +1286,32 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { return moveStudiesButton; }, - __createDeleteButton: function() { + __createTrashStudiesButton: function() { + const trashButton = new qx.ui.form.Button(this.tr("Trash"), "@FontAwesome5Solid/trash/14").set({ + appearance: "danger-button", + visibility: "excluded" + }); + osparc.utils.Utils.setIdToWidget(trashButton, "deleteStudiesBtn"); + trashButton.addListener("execute", () => { + const selection = this._resourcesContainer.getSelection(); + const preferencesSettings = osparc.Preferences.getInstance(); + if (preferencesSettings.getConfirmDeleteStudy()) { + const win = this.__createConfirmTrashWindow(selection.map(button => button.getTitle())); + win.center(); + win.open(); + win.addListener("close", () => { + if (win.getConfirmed()) { + this.__trashStudies(selection.map(button => this.__getStudyData(button.getUuid(), false)), false); + } + }, this); + } else { + this.__trashStudies(selection.map(button => this.__getStudyData(button.getUuid(), false)), false); + } + }, this); + return trashButton; + }, + + __createDeleteStudiesButton: function() { const deleteButton = new qx.ui.form.Button(this.tr("Delete"), "@FontAwesome5Solid/trash/14").set({ appearance: "danger-button", visibility: "excluded" @@ -1139,7 +1321,7 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { const selection = this._resourcesContainer.getSelection(); const preferencesSettings = osparc.Preferences.getInstance(); if (preferencesSettings.getConfirmDeleteStudy()) { - const win = this.__createConfirmWindow(selection.map(button => button.getTitle())); + const win = this.__createConfirmDeleteWindow(selection.map(button => button.getTitle())); win.center(); win.open(); win.addListener("close", () => { @@ -1154,6 +1336,20 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { return deleteButton; }, + __emptyTrash: function() { + const win = this.__createConfirmEmptyTrashWindow(); + win.center(); + win.open(); + win.addListener("close", () => { + if (win.getConfirmed()) { + osparc.data.Resources.fetch("trash", "delete") + .then(() => { + this.__resetStudiesList(); + }); + } + }, this); + }, + __createSelectButton: function() { const selectButton = new qx.ui.form.ToggleButton().set({ appearance: "form-button-outlined", @@ -1297,9 +1493,25 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { const menu = card.getMenu(); const studyData = card.getResourceData(); + const trashed = Boolean(studyData["trashedAt"]); const writeAccess = osparc.data.model.Study.canIWrite(studyData["accessRights"]); const deleteAccess = osparc.data.model.Study.canIDelete(studyData["accessRights"]); + if (this.getCurrentContext() === "trash") { + if (trashed) { + if (writeAccess) { + const untrashButton = this.__getUntrashStudyMenuButton(studyData); + menu.add(untrashButton); + } + if (deleteAccess) { + const deleteButton = this.__getDeleteStudyMenuButton(studyData, false); + menu.addSeparator(); + menu.add(deleteButton); + } + } + return; + } + const openButton = this._getOpenMenuButton(studyData); if (openButton) { menu.add(openButton); @@ -1363,11 +1575,14 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { } if (deleteAccess) { + menu.addSeparator(); + const trashButton = this.__getTrashStudyMenuButton(studyData, false); + menu.add(trashButton); + } else if (this.__deleteOrRemoveMe(studyData) === "remove") { + // if I'm a collaborator, let me remove myself from the study. In that case it would be a Delete for me + menu.addSeparator(); const deleteButton = this.__getDeleteStudyMenuButton(studyData, false); - if (deleteButton) { - menu.addSeparator(); - menu.add(deleteButton); - } + menu.add(deleteButton); } card.evaluateMenuButtons(); @@ -1549,13 +1764,33 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { }, _deleteResourceRequested: function(studyId) { - this.__deleteStudyRequested(this.__getStudyData(studyId)); + if (this.getCurrentContext() === "trash") { + this.__deleteStudyRequested(this.__getStudyData(studyId)); + } else { + this.__trashStudyRequested(this.__getStudyData(studyId)); + } + }, + + __trashStudyRequested: function(studyData) { + const preferencesSettings = osparc.Preferences.getInstance(); + if (preferencesSettings.getConfirmDeleteStudy()) { + const win = this.__createConfirmTrashWindow([studyData.name]); + win.center(); + win.open(); + win.addListener("close", () => { + if (win.getConfirmed()) { + this.__trashStudy(studyData); + } + }, this); + } else { + this.__trashStudy(studyData); + } }, __deleteStudyRequested: function(studyData) { const preferencesSettings = osparc.Preferences.getInstance(); if (preferencesSettings.getConfirmDeleteStudy()) { - const win = this.__createConfirmWindow([studyData.name]); + const win = this.__deleteOrRemoveMe(studyData) === "remove" ? this.__createConfirmRemoveForMeWindow(studyData.name) : this.__createConfirmDeleteWindow([studyData.name]); win.center(); win.open(); win.addListener("close", () => { @@ -1568,6 +1803,27 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { } }, + __getTrashStudyMenuButton: function(studyData) { + const trashButton = new qx.ui.menu.Button(this.tr("Trash"), "@FontAwesome5Solid/trash/12"); + trashButton["trashButton"] = true; + trashButton.set({ + appearance: "menu-button" + }); + osparc.utils.Utils.setIdToWidget(trashButton, "studyItemMenuDelete"); + trashButton.addListener("execute", () => this.__trashStudyRequested(studyData), this); + return trashButton; + }, + + __getUntrashStudyMenuButton: function(studyData) { + const restoreButton = new qx.ui.menu.Button(this.tr("Restore"), "@MaterialIcons/restore_from_trash/16"); + restoreButton["untrashButton"] = true; + restoreButton.set({ + appearance: "menu-button" + }); + restoreButton.addListener("execute", () => this.__untrashStudy(studyData), this); + return restoreButton; + }, + __getDeleteStudyMenuButton: function(studyData) { const deleteButton = new qx.ui.menu.Button(this.tr("Delete"), "@FontAwesome5Solid/trash/12"); deleteButton["deleteButton"] = true; @@ -1730,17 +1986,60 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { req.send(body); }, - __doDeleteStudy: function(studyData) { + __untrashStudy: function(studyData) { + osparc.store.Store.getInstance().untrashStudy(studyData.uuid) + .then(() => { + this.__removeFromStudyList(studyData.uuid); + const msg = this.tr("Successfully restored"); + osparc.FlashMessenger.getInstance().logAs(msg, "INFO"); + this._resourceFilter.evaluateTrashEmpty(); + }) + .catch(err => { + console.error(err); + osparc.FlashMessenger.getInstance().logAs(err, "ERROR"); + }) + .finally(() => this.resetSelection()); + }, + + __trashStudy: function(studyData) { + osparc.store.Store.getInstance().trashStudy(studyData.uuid) + .then(() => { + this.__removeFromStudyList(studyData.uuid); + const msg = this.tr("Successfully moved to Trash"); + osparc.FlashMessenger.getInstance().logAs(msg, "INFO"); + this._resourceFilter.setTrashEmpty(false); + }) + .catch(err => { + console.error(err); + osparc.FlashMessenger.getInstance().logAs(err, "ERROR"); + }) + .finally(() => this.resetSelection()); + }, + + __trashStudies: function(studiesData) { + studiesData.forEach(studyData => this.__trashStudy(studyData)); + }, + + __deleteOrRemoveMe: function(studyData) { + const deleteAccess = osparc.data.model.Study.canIDelete(studyData["accessRights"]); const myGid = osparc.auth.Data.getInstance().getGroupId(); const collabGids = Object.keys(studyData["accessRights"]); const amICollaborator = collabGids.indexOf(myGid) > -1; + return (!deleteAccess && collabGids.length > 1 && amICollaborator) ? "remove" : "delete"; + }, + __removeMeFromCollaborators: function(studyData) { + const arCopy = osparc.utils.Utils.deepCloneObject(studyData["accessRights"]); + // remove me from collaborators + const myGid = osparc.auth.Data.getInstance().getGroupId(); + delete arCopy[myGid]; + return osparc.info.StudyUtils.patchStudyData(studyData, "accessRights", arCopy); + }, + + __doDeleteStudy: function(studyData) { let operationPromise = null; - if (collabGids.length > 1 && amICollaborator) { - const arCopy = osparc.utils.Utils.deepCloneObject(studyData["accessRights"]); - // remove collaborator - delete arCopy[myGid]; - operationPromise = osparc.info.StudyUtils.patchStudyData(studyData, "accessRights", arCopy); + if (this.__deleteOrRemoveMe(studyData) === "remove") { + operationPromise = this.__removeMeFromCollaborators(studyData); } else { // delete study operationPromise = osparc.store.Store.getInstance().deleteStudy(studyData.uuid); @@ -1758,10 +2057,41 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { studiesData.forEach(studyData => this.__doDeleteStudy(studyData)); }, - __createConfirmWindow: function(studyNames) { - const rUSure = this.tr("Are you sure you want to delete"); + __createConfirmTrashWindow: function(studyNames) { + let msg = this.tr("Are you sure you want to move"); + if (studyNames.length > 1) { + const studiesText = osparc.product.Utils.getStudyAlias({plural: true}); + msg += ` ${studyNames.length} ${studiesText} ` + } else { + msg += ` '${studyNames[0]}' `; + } + msg += this.tr("to the Trash?"); + const trashDays = osparc.store.StaticInfo.getInstance().getTrashRetentionDays(); + msg += "

" + (studyNames.length > 1 ? "They" : "It") + this.tr(` will be permanently deleted after ${trashDays} days.`); + const confirmationWin = new osparc.ui.window.Confirmation(msg).set({ + caption: this.tr("Move to Trash"), + confirmText: this.tr("Move to Trash"), + confirmAction: "delete" + }); + osparc.utils.Utils.setIdToWidget(confirmationWin.getConfirmButton(), "confirmDeleteStudyBtn"); + return confirmationWin; + }, + + __createConfirmRemoveForMeWindow: function(studyName) { + const msg = `'${studyName} ` + this.tr("will be removed from your list. Collaborators will still have access."); + const confirmationWin = new osparc.ui.window.Confirmation(msg).set({ + caption: this.tr("Remove"), + confirmText: this.tr("Remove"), + confirmAction: "delete" + }); + osparc.utils.Utils.setIdToWidget(confirmationWin.getConfirmButton(), "confirmDeleteStudyBtn"); + return confirmationWin; + }, + + __createConfirmDeleteWindow: function(studyNames) { + let msg = this.tr("Are you sure you want to delete"); const studyAlias = osparc.product.Utils.getStudyAlias({plural: studyNames.length > 1}); - const msg = rUSure + (studyNames.length > 1 ? ` ${studyNames.length} ${studyAlias}?` : ` ${studyNames[0]}?`) + msg += (studyNames.length > 1 ? ` ${studyNames.length} ${studyAlias}?` : ` ${studyNames[0]}?`); const confirmationWin = new osparc.ui.window.Confirmation(msg).set({ caption: this.tr("Delete") + " " + studyAlias, confirmText: this.tr("Delete"), @@ -1771,6 +2101,16 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { return confirmationWin; }, + __createConfirmEmptyTrashWindow: function() { + const msg = this.tr("Items in the bin will be permanently deleted"); + const confirmationWin = new osparc.ui.window.Confirmation(msg).set({ + caption: this.tr("Delete"), + confirmText: this.tr("Delete forever"), + confirmAction: "delete" + }); + return confirmationWin; + }, + // TASKS // __tasksReceived: function(tasks) { tasks.forEach(taskData => this._taskDataReceived(taskData)); diff --git a/services/static-webserver/client/source/class/osparc/dashboard/StudyBrowserHeader.js b/services/static-webserver/client/source/class/osparc/dashboard/StudyBrowserHeader.js index 87a6a366b58..cb2e130cfcf 100644 --- a/services/static-webserver/client/source/class/osparc/dashboard/StudyBrowserHeader.js +++ b/services/static-webserver/client/source/class/osparc/dashboard/StudyBrowserHeader.js @@ -16,7 +16,7 @@ ************************************************************************ */ /** - * Widget used for displaying a Workspace information + * Widget used for displaying a Study Browser's context information * */ @@ -35,27 +35,20 @@ qx.Class.define("osparc.dashboard.StudyBrowserHeader", { alignY: "middle", }); - this.__spacers = []; - this.initCurrentWorkspaceId(); this.initCurrentFolderId(); + + osparc.store.Store.getInstance().addListener("changeStudyBrowserContext", () => this.__buildLayout(), this); }, events: { "locationChanged": "qx.event.type.Data", "workspaceUpdated": "qx.event.type.Data", - "deleteWorkspaceRequested": "qx.event.type.Data" + "deleteWorkspaceRequested": "qx.event.type.Data", + "emptyTrashRequested": "qx.event.type.Event", }, properties: { - currentContext: { - check: ["studiesAndFolders", "workspaces", "search"], - nullable: false, - init: "studiesAndFolders", - event: "changeCurrentContext", - apply: "__buildLayout" - }, - currentWorkspaceId: { check: "Number", nullable: true, @@ -76,7 +69,7 @@ qx.Class.define("osparc.dashboard.StudyBrowserHeader", { nullable: false, init: {}, event: "changeAccessRights", - apply: "__applyAccessRights" + apply: "__updateShareInfo" }, myAccessRights: { @@ -89,7 +82,17 @@ qx.Class.define("osparc.dashboard.StudyBrowserHeader", { }, statics: { - HEIGHT: 36 + HEIGHT: 36, + POS: { + ICON: 0, + TITLE: 1, + BREADCRUMBS: 2, + EDIT_BUTTON: 3, + SHARE_LAYOUT: 4, + ROLE_LAYOUT: 5, + DESCRIPTION: 2, + EMPTY_TRASH_BUTTON: 3, + } }, members: { @@ -106,26 +109,25 @@ qx.Class.define("osparc.dashboard.StudyBrowserHeader", { allowGrowY: true, decorator: "rounded", }); - this._add(control); + this._addAt(control, this.self().POS.ICON); break; case "title": control = new qx.ui.basic.Label().set({ font: "text-16", alignY: "middle", }); - this._add(control); + this._addAt(control, this.self().POS.TITLE); break; case "breadcrumbs": control = new osparc.dashboard.ContextBreadcrumbs(); this.bind("currentWorkspaceId", control, "currentWorkspaceId"); this.bind("currentFolderId", control, "currentFolderId"); - this.bind("currentContext", control, "currentContext"); control.bind("currentWorkspaceId", this, "currentWorkspaceId"); control.bind("currentFolderId", this, "currentFolderId"); control.addListener("locationChanged", e => { this.fireDataEvent("locationChanged", e.getData()) }); - this._add(control); + this._addAt(control, this.self().POS.BREADCRUMBS); break; case "edit-button": control = new qx.ui.form.MenuButton().set({ @@ -142,14 +144,15 @@ qx.Class.define("osparc.dashboard.StudyBrowserHeader", { control.getContentElement().setStyles({ "border-radius": `${22 / 2}px` }); - this._add(control); + this._addAt(control, this.self().POS.EDIT_BUTTON); break; case "share-layout": - this.__addSpacer(); control = new qx.ui.container.Composite(new qx.ui.layout.HBox(10).set({ - alignY: "middle" - })); - this._add(control); + alignY: "middle", + })).set({ + marginLeft: 10, + }); + this._addAt(control, this.self().POS.SHARE_LAYOUT); break; case "share-text": { control = new qx.ui.basic.Label().set({ @@ -160,11 +163,12 @@ qx.Class.define("osparc.dashboard.StudyBrowserHeader", { break; } case "role-layout": - this.__addSpacer(); control = new qx.ui.container.Composite(new qx.ui.layout.HBox(5).set({ - alignY: "middle" - })); - this._add(control); + alignY: "middle", + })).set({ + marginLeft: 10, + }); + this._addAt(control, this.self().POS.ROLE_LAYOUT); break; case "role-text": { control = new qx.ui.basic.Label().set({ @@ -180,6 +184,24 @@ qx.Class.define("osparc.dashboard.StudyBrowserHeader", { layout.addAt(control, 1); break; } + case "description": { + control = new qx.ui.basic.Label().set({ + font: "text-14", + alignY: "middle", + }); + this._addAt(control, this.self().POS.DESCRIPTION); + break; + } + case "empty-trash-button": { + control = new qx.ui.form.Button(this.tr("Empty Trash"), "@FontAwesome5Solid/trash/14").set({ + appearance: "danger-button", + allowGrowY: false, + alignY: "middle", + }); + control.addListener("execute", () => this.fireEvent("emptyTrashRequested")); + this._addAt(control, this.self().POS.EMPTY_TRASH_BUTTON); + break; + } } return control || this.base(arguments, id); @@ -198,66 +220,82 @@ qx.Class.define("osparc.dashboard.StudyBrowserHeader", { __buildLayout: function() { this.getChildControl("icon"); const title = this.getChildControl("title"); - title.resetCursor(); - title.removeListener("tap", this.__titleTapped, this); - this.getChildControl("breadcrumbs"); - this.getChildControl("edit-button").exclude(); - this.resetAccessRights(); - this.resetMyAccessRights(); - - const currentContext = this.getCurrentContext(); - if (currentContext === "search") { - this.__setIcon("@FontAwesome5Solid/search/24"); - title.set({ - value: this.tr("Search results"), - }); - } else if (currentContext === "workspaces") { - this.__setIcon(osparc.store.Workspaces.iconPath(32)); - title.set({ - value: this.tr("Shared Workspaces"), - }) - } else if (currentContext === "studiesAndFolders") { - const workspaceId = this.getCurrentWorkspaceId(); - title.setCursor("pointer"); - title.addListener("tap", this.__titleTapped, this); - const workspace = osparc.store.Workspaces.getInstance().getWorkspace(workspaceId); - if (workspace) { - const thumbnail = workspace.getThumbnail(); - this.__setIcon(thumbnail ? thumbnail : osparc.store.Workspaces.iconPath(32)); - workspace.bind("name", title, "value"); - workspace.bind("accessRights", this, "accessRights"); - workspace.bind("myAccessRights", this, "myAccessRights"); - } else { - this.__setIcon("@FontAwesome5Solid/home/30"); - title.setValue(this.tr("My Workspace")); - } - } - }, - __addSpacer: function() { - const spacer = new qx.ui.basic.Label("-").set({ - font: "text-16", - alignY: "middle", + const locationBreadcrumbs = this.getChildControl("breadcrumbs").set({ + visibility: "excluded" + }); + const editWorkspace = this.getChildControl("edit-button").set({ + visibility: "excluded" + }); + const shareWorkspaceLayout = this.getChildControl("share-layout").set({ + visibility: "excluded" + }); + const roleWorkspaceLayout = this.getChildControl("role-layout").set({ + visibility: "excluded" + }); + + const description = this.getChildControl("description").set({ + visibility: "excluded" }); - this.__spacers.push(spacer); - this._add(spacer); + // the study browser will take care of making it visible + this.getChildControl("empty-trash-button").set({ + visibility: "excluded" + }); + + const currentContext = osparc.store.Store.getInstance().getStudyBrowserContext(); + switch (currentContext) { + case "studiesAndFolders": { + const workspaceId = this.getCurrentWorkspaceId(); + title.setCursor("pointer"); + title.addListener("tap", this.__titleTapped, this); + locationBreadcrumbs.show(); + const workspace = osparc.store.Workspaces.getInstance().getWorkspace(workspaceId); + if (workspace) { + const thumbnail = workspace.getThumbnail(); + this.__setIcon(thumbnail ? thumbnail : osparc.store.Workspaces.iconPath(32)); + workspace.bind("name", title, "value"); + editWorkspace.show(); + shareWorkspaceLayout.show(); + roleWorkspaceLayout.show(); + workspace.bind("accessRights", this, "accessRights"); + workspace.bind("myAccessRights", this, "myAccessRights"); + } else { + this.__setIcon("@FontAwesome5Solid/home/30"); + title.setValue(this.tr("My Workspace")); + } + break; + } + case "workspaces": + this.__setIcon(osparc.store.Workspaces.iconPath(32)); + title.setValue(this.tr("Shared Workspaces")); + break; + case "search": + this.__setIcon("@FontAwesome5Solid/search/24"); + title.setValue(this.tr("Search results")); + break; + case "trash": { + this.__setIcon("@FontAwesome5Solid/trash/20"); + title.setValue(this.tr("Trash")); + const trashDays = osparc.store.StaticInfo.getInstance().getTrashRetentionDays(); + description.set({ + value: this.tr(`Items in the bin will be permanently deleted after ${trashDays} days.`), + visibility: "visible", + }); + break; + } + } }, - __resetIcon: function() { + __setIcon: function(source) { + // reset icon first const icon = this.getChildControl("icon"); const image = icon.getChildControl("image"); image.resetSource(); icon.getContentElement().setStyles({ "background-image": "none" }); - }, - __setIcon: function(source) { - this.__resetIcon(); - - const icon = this.getChildControl("icon"); if (source.includes("@")) { - const image = icon.getChildControl("image"); image.set({ source }); @@ -272,10 +310,6 @@ qx.Class.define("osparc.dashboard.StudyBrowserHeader", { } }, - __showSpacers: function(show) { - this.__spacers.forEach(spacer => spacer.setVisibility(show ? "visible" : "excluded")); - }, - __getShareIcon: function() { // reset previous const layout = this.getChildControl("share-layout"); @@ -292,7 +326,7 @@ qx.Class.define("osparc.dashboard.StudyBrowserHeader", { return shareIcon; }, - __applyAccessRights: function(accessRights) { + __updateShareInfo: function(accessRights) { const shareIcon = this.__getShareIcon(); const shareText = this.getChildControl("share-text"); if (accessRights && Object.keys(accessRights).length) { @@ -300,11 +334,9 @@ qx.Class.define("osparc.dashboard.StudyBrowserHeader", { shareText.setValue(Object.keys(accessRights).length + " members"); shareIcon.show(); shareText.show(); - this.__showSpacers(true); } else { shareIcon.exclude(); shareText.exclude(); - this.__showSpacers(false); } }, @@ -312,7 +344,8 @@ qx.Class.define("osparc.dashboard.StudyBrowserHeader", { const editButton = this.getChildControl("edit-button"); const roleText = this.getChildControl("role-text"); const roleIcon = this.getChildControl("role-icon"); - if (value && Object.keys(value).length) { + const currentContext = osparc.store.Store.getInstance().getStudyBrowserContext(); + if (currentContext === "studiesAndFolders" && value && Object.keys(value).length) { editButton.setVisibility(value["delete"] ? "visible" : "excluded"); const menu = new qx.ui.menu.Menu().set({ position: "bottom-right" @@ -328,12 +361,10 @@ qx.Class.define("osparc.dashboard.StudyBrowserHeader", { roleText.setValue(osparc.data.Roles.WORKSPACE[val].label); roleText.show(); roleIcon.show(); - this.__showSpacers(true); } else { editButton.exclude(); roleText.exclude(); roleIcon.exclude(); - this.__showSpacers(false); } }, @@ -346,6 +377,7 @@ qx.Class.define("osparc.dashboard.StudyBrowserHeader", { win.close(); this.__buildLayout(); }, this); + workspaceEditor.addListener("cancel", () => win.close()); }, __openShareWith: function() { @@ -355,7 +387,7 @@ qx.Class.define("osparc.dashboard.StudyBrowserHeader", { const win = osparc.ui.window.Window.popUpInWindow(permissionsView, title, 500, 400); permissionsView.addListener("updateAccessRights", () => { win.close(); - this.__applyAccessRights(workspace.getAccessRights()); + this.__updateShareInfo(workspace.getAccessRights()); }, this); }, } diff --git a/services/static-webserver/client/source/class/osparc/dashboard/TemplateBrowser.js b/services/static-webserver/client/source/class/osparc/dashboard/TemplateBrowser.js index 3ff37cd8412..0b6fc8ccd26 100644 --- a/services/static-webserver/client/source/class/osparc/dashboard/TemplateBrowser.js +++ b/services/static-webserver/client/source/class/osparc/dashboard/TemplateBrowser.js @@ -246,11 +246,7 @@ qx.Class.define("osparc.dashboard.TemplateBrowser", { // LAYOUT // _createLayout: function() { this._createSearchBar(); - this._createResourcesLayout(); - const list = this._resourcesContainer.getFlatList(); - if (list) { - osparc.utils.Utils.setIdToWidget(list, "templatesList"); - } + this._createResourcesLayout("templatesList"); const updateAllButton = this.__createUpdateAllButton(); if (updateAllButton) { diff --git a/services/static-webserver/client/source/class/osparc/dashboard/WorkspaceButtonItem.js b/services/static-webserver/client/source/class/osparc/dashboard/WorkspaceButtonItem.js index 187be3598e2..eb777ca5dd7 100644 --- a/services/static-webserver/client/source/class/osparc/dashboard/WorkspaceButtonItem.js +++ b/services/static-webserver/client/source/class/osparc/dashboard/WorkspaceButtonItem.js @@ -45,7 +45,9 @@ qx.Class.define("osparc.dashboard.WorkspaceButtonItem", { events: { "workspaceSelected": "qx.event.type.Data", "workspaceUpdated": "qx.event.type.Data", - "deleteWorkspaceRequested": "qx.event.type.Data" + "trashWorkspaceRequested": "qx.event.type.Data", + "untrashWorkspaceRequested": "qx.event.type.Data", + "deleteWorkspaceRequested": "qx.event.type.Data", }, properties: { @@ -183,31 +185,46 @@ qx.Class.define("osparc.dashboard.WorkspaceButtonItem", { position: "bottom-right" }); - const editButton = new qx.ui.menu.Button(this.tr("Edit..."), "@FontAwesome5Solid/pencil-alt/12"); - editButton.addListener("execute", () => { - const workspace = this.getWorkspace(); - const workspaceEditor = new osparc.editor.WorkspaceEditor(workspace); - const title = this.tr("Edit Workspace"); - const win = osparc.ui.window.Window.popUpInWindow(workspaceEditor, title, 300, 150); - workspaceEditor.addListener("workspaceUpdated", () => { - win.close(); - this.fireDataEvent("workspaceUpdated", workspace.getWorkspaceId()); + const studyBrowserContext = osparc.store.Store.getInstance().getStudyBrowserContext(); + if ( + studyBrowserContext === "search" || + studyBrowserContext === "workspaces" + ) { + const editButton = new qx.ui.menu.Button(this.tr("Edit..."), "@FontAwesome5Solid/pencil-alt/12"); + editButton.addListener("execute", () => { + const workspace = this.getWorkspace(); + const workspaceEditor = new osparc.editor.WorkspaceEditor(workspace); + const title = this.tr("Edit Workspace"); + const win = osparc.ui.window.Window.popUpInWindow(workspaceEditor, title, 300, 150); + workspaceEditor.addListener("workspaceUpdated", () => { + win.close(); + this.fireDataEvent("workspaceUpdated", workspace.getWorkspaceId()); + }); + workspaceEditor.addListener("cancel", () => win.close()); }); - workspaceEditor.addListener("cancel", () => win.close()); - }); - menu.add(editButton); + menu.add(editButton); + + const shareButton = new qx.ui.menu.Button(this.tr("Share..."), "@FontAwesome5Solid/share-alt/12"); + shareButton.addListener("execute", () => this.__openShareWith(), this); + menu.add(shareButton); - const shareButton = new qx.ui.menu.Button(this.tr("Share..."), "@FontAwesome5Solid/share-alt/12"); - shareButton.addListener("execute", () => this.__openShareWith(), this); - menu.add(shareButton); + menu.addSeparator(); - menu.addSeparator(); + const trashButton = new qx.ui.menu.Button(this.tr("Trash"), "@FontAwesome5Solid/trash/12"); + trashButton.addListener("execute", () => this.__trashWorkspaceRequested(), this); + menu.add(trashButton); + } else if (studyBrowserContext === "trash") { + const restoreButton = new qx.ui.menu.Button(this.tr("Restore"), "@MaterialIcons/restore_from_trash/16"); + restoreButton.addListener("execute", () => this.fireDataEvent("untrashWorkspaceRequested", this.getWorkspace()), this); + menu.add(restoreButton); - const deleteButton = new qx.ui.menu.Button(this.tr("Delete"), "@FontAwesome5Solid/trash/12"); - osparc.utils.Utils.setIdToWidget(deleteButton, "deleteWorkspaceMenuItem"); - deleteButton.addListener("execute", () => this.__deleteWorkspaceRequested(), this); - menu.add(deleteButton); + menu.addSeparator(); + const deleteButton = new qx.ui.menu.Button(this.tr("Delete"), "@FontAwesome5Solid/trash/12"); + osparc.utils.Utils.setIdToWidget(deleteButton, "deleteWorkspaceMenuItem"); + deleteButton.addListener("execute", () => this.__deleteWorkspaceRequested(), this); + menu.add(deleteButton); + } menuButton.setMenu(menu); } }, @@ -237,7 +254,9 @@ qx.Class.define("osparc.dashboard.WorkspaceButtonItem", { }, __itemSelected: function(newVal) { - if (newVal) { + const studyBrowserContext = osparc.store.Store.getInstance().getStudyBrowserContext(); + // do not allow selecting workspace + if (studyBrowserContext !== "trash" && newVal) { this.fireDataEvent("workspaceSelected", this.getWorkspaceId()); } this.setValue(false); @@ -250,6 +269,24 @@ qx.Class.define("osparc.dashboard.WorkspaceButtonItem", { permissionsView.addListener("updateAccessRights", () => this.__applyAccessRights(this.getWorkspace().getAccessRights()), this); }, + __trashWorkspaceRequested: function() { + const trashDays = osparc.store.StaticInfo.getInstance().getTrashRetentionDays(); + let msg = this.tr("Are you sure you want to move the Workspace and all its content to the trash?"); + msg += "

" + this.tr("It will be permanently deleted after ") + trashDays + " days."; + const confirmationWin = new osparc.ui.window.Confirmation(msg).set({ + caption: this.tr("Move to Trash"), + confirmText: this.tr("Move to Trash"), + confirmAction: "delete" + }); + confirmationWin.center(); + confirmationWin.open(); + confirmationWin.addListener("close", () => { + if (confirmationWin.getConfirmed()) { + this.fireDataEvent("trashWorkspaceRequested", this.getWorkspaceId()); + } + }, this); + }, + __deleteWorkspaceRequested: function() { let msg = this.tr("Are you sure you want to delete") + " " + this.getTitle() + "?"; msg += "
" + this.tr("All the content of the workspace will be deleted."); diff --git a/services/static-webserver/client/source/class/osparc/dashboard/WorkspacesAndFoldersTree.js b/services/static-webserver/client/source/class/osparc/dashboard/WorkspacesAndFoldersTree.js index 533c90c4643..da5a21797f1 100644 --- a/services/static-webserver/client/source/class/osparc/dashboard/WorkspacesAndFoldersTree.js +++ b/services/static-webserver/client/source/class/osparc/dashboard/WorkspacesAndFoldersTree.js @@ -46,7 +46,7 @@ qx.Class.define("osparc.dashboard.WorkspacesAndFoldersTree", { this.__initTree(); // preselect "My Workspace" - this.contextChanged(null, null); + this.contextChanged("studiesAndFolders"); osparc.store.Folders.getInstance().addListener("folderAdded", e => { const folder = e.getData(); diff --git a/services/static-webserver/client/source/class/osparc/dashboard/WorkspacesAndFoldersTreeItem.js b/services/static-webserver/client/source/class/osparc/dashboard/WorkspacesAndFoldersTreeItem.js index 13793622cb7..75f120a86c5 100644 --- a/services/static-webserver/client/source/class/osparc/dashboard/WorkspacesAndFoldersTreeItem.js +++ b/services/static-webserver/client/source/class/osparc/dashboard/WorkspacesAndFoldersTreeItem.js @@ -24,6 +24,7 @@ qx.Class.define("osparc.dashboard.WorkspacesAndFoldersTreeItem", { this.set({ indent: 12, // defaults to 19, decorator: "rounded", + padding: 2, maxWidth: osparc.dashboard.ResourceBrowserBase.SIDE_SPACER_WIDTH - 12, }); diff --git a/services/static-webserver/client/source/class/osparc/data/Resources.js b/services/static-webserver/client/source/class/osparc/data/Resources.js index 8ff5eb822ba..ad1b51cc840 100644 --- a/services/static-webserver/client/source/class/osparc/data/Resources.js +++ b/services/static-webserver/client/source/class/osparc/data/Resources.js @@ -119,6 +119,16 @@ qx.Class.define("osparc.data.Resources", { method: "GET", url: statics.API + "/projects?type=user" }, + getOne: { + useCache: false, + method: "GET", + url: statics.API + "/projects/{studyId}" + }, + getActive: { + useCache: false, + method: "GET", + url: statics.API + "/projects/active?client_session_id={tabId}" + }, getPage: { useCache: false, method: "GET", @@ -129,15 +139,10 @@ qx.Class.define("osparc.data.Resources", { method: "GET", url: statics.API + "/projects:search?offset={offset}&limit={limit}&text={text}&tag_ids={tagIds}&order_by={orderBy}" }, - getOne: { - useCache: false, - method: "GET", - url: statics.API + "/projects/{studyId}" - }, - getActive: { + getPageTrashed: { useCache: false, method: "GET", - url: statics.API + "/projects/active?client_session_id={tabId}" + url: statics.API + "/projects?filters={%22trashed%22:%22true%22}&offset={offset}&limit={limit}&order_by={orderBy}" }, postToTemplate: { method: "POST", @@ -187,6 +192,14 @@ qx.Class.define("osparc.data.Resources", { method: "PATCH", url: statics.API + "/projects/{studyId}" }, + trash: { + method: "POST", + url: statics.API + "/projects/{studyId}:trash" + }, + untrash: { + method: "POST", + url: statics.API + "/projects/{studyId}:untrash" + }, delete: { method: "DELETE", url: statics.API + "/projects/{studyId}" @@ -301,14 +314,19 @@ qx.Class.define("osparc.data.Resources", { method: "GET", url: statics.API + "/folders?workspace_id={workspaceId}&folder_id={folderId}&offset={offset}&limit={limit}&order_by={orderBy}" }, + getOne: { + method: "GET", + url: statics.API + "/folders/{folderId}" + }, getPageSearch: { useCache: false, method: "GET", url: statics.API + "/folders:search?offset={offset}&limit={limit}&text={text}&order_by={orderBy}" }, - getOne: { + getPageTrashed: { + useCache: false, method: "GET", - url: statics.API + "/folders/{folderId}" + url: statics.API + "/folders?filters={%22trashed%22:%22true%22}&offset={offset}&limit={limit}&order_by={orderBy}" }, post: { method: "POST", @@ -326,6 +344,14 @@ qx.Class.define("osparc.data.Resources", { method: "PUT", url: statics.API + "/folders/{folderId}/folders/{workspaceId}" }, + trash: { + method: "POST", + url: statics.API + "/folders/{folderId}:trash" + }, + untrash: { + method: "POST", + url: statics.API + "/folders/{folderId}:untrash" + }, } }, "workspaces": { @@ -338,6 +364,16 @@ qx.Class.define("osparc.data.Resources", { method: "GET", url: statics.API + "/workspaces/{workspaceId}" }, + getPageSearch: { + useCache: false, + method: "GET", + url: statics.API + "/workspaces:search?offset={offset}&limit={limit}&text={text}&order_by={orderBy}" + }, + getPageTrashed: { + useCache: false, + method: "GET", + url: statics.API + "/workspaces?filters={%22trashed%22:%22true%22}&offset={offset}&limit={limit}&order_by={orderBy}" + }, post: { method: "POST", url: statics.API + "/workspaces" @@ -350,6 +386,14 @@ qx.Class.define("osparc.data.Resources", { method: "DELETE", url: statics.API + "/workspaces/{workspaceId}" }, + trash: { + method: "POST", + url: statics.API + "/workspaces/{workspaceId}:trash" + }, + untrash: { + method: "POST", + url: statics.API + "/workspaces/{workspaceId}:untrash" + }, postAccessRights: { method: "POST", url: statics.API + "/workspaces/{workspaceId}/groups/{groupId}" @@ -402,6 +446,18 @@ qx.Class.define("osparc.data.Resources", { } }, + /* + * TRASH + */ + "trash": { + endpoints: { + delete: { + method: "DELETE", + url: statics.API + "/trash" + } + } + }, + /* * SNAPSHOTS */ diff --git a/services/static-webserver/client/source/class/osparc/data/model/Folder.js b/services/static-webserver/client/source/class/osparc/data/model/Folder.js index b8b9eb03b21..6a255a45dc5 100644 --- a/services/static-webserver/client/source/class/osparc/data/model/Folder.js +++ b/services/static-webserver/client/source/class/osparc/data/model/Folder.js @@ -37,7 +37,7 @@ qx.Class.define("osparc.data.model.Folder", { owner: folderData.owner, createdAt: new Date(folderData.createdAt), lastModified: new Date(folderData.modifiedAt), - trashedAt: folderData.trashedAt ? new Date(folderData.trashedAt) : this.getTrashedAt(), + trashedAt: folderData.trashedAt ? new Date(folderData.trashedAt) : null, }); }, diff --git a/services/static-webserver/client/source/class/osparc/data/model/IframeHandler.js b/services/static-webserver/client/source/class/osparc/data/model/IframeHandler.js index fa037642af4..583b31f979e 100644 --- a/services/static-webserver/client/source/class/osparc/data/model/IframeHandler.js +++ b/services/static-webserver/client/source/class/osparc/data/model/IframeHandler.js @@ -300,7 +300,7 @@ qx.Class.define("osparc.data.model.IframeHandler", { if (response.status < 400) { this.__serviceReadyIn(srvUrl); } else { - console.log(`Connecting: ${srvUrl} is not reachable. Status: ${response.status}`); + console.error(`Connecting: ${srvUrl} is not reachable. Status: ${response.status}`); retry(); } }) diff --git a/services/static-webserver/client/source/class/osparc/data/model/Workspace.js b/services/static-webserver/client/source/class/osparc/data/model/Workspace.js index 56023d1eb4e..65592fb1789 100644 --- a/services/static-webserver/client/source/class/osparc/data/model/Workspace.js +++ b/services/static-webserver/client/source/class/osparc/data/model/Workspace.js @@ -37,6 +37,8 @@ qx.Class.define("osparc.data.model.Workspace", { accessRights: workspaceData.accessRights, createdAt: new Date(workspaceData.createdAt), modifiedAt: new Date(workspaceData.modifiedAt), + trashedAt: workspaceData.trashedAt ? new Date(workspaceData.trashedAt) : null, + trashedBy: workspaceData.trashedBy, }); }, @@ -95,7 +97,19 @@ qx.Class.define("osparc.data.model.Workspace", { nullable: true, init: null, event: "changeModifiedAt" - } + }, + + trashedAt: { + check: "Date", + nullable: true, + init: null, + }, + + trashedBy: { + check: "Number", + nullable: true, + init: null, + }, }, statics: { diff --git a/services/static-webserver/client/source/class/osparc/store/Folders.js b/services/static-webserver/client/source/class/osparc/store/Folders.js index d6e83d8fb23..4206cb212dd 100644 --- a/services/static-webserver/client/source/class/osparc/store/Folders.js +++ b/services/static-webserver/client/source/class/osparc/store/Folders.js @@ -78,6 +78,33 @@ qx.Class.define("osparc.store.Folders", { }); }, + fetchAllTrashedFolders: function(orderBy = { + field: "modified_at", + direction: "desc" + }) { + if (osparc.auth.Data.getInstance().isGuest()) { + return new Promise(resolve => { + resolve([]); + }); + } + + const curatedOrderBy = this.self().curateOrderBy(orderBy); + const params = { + url: { + orderBy: JSON.stringify(curatedOrderBy), + } + }; + return osparc.data.Resources.getInstance().getAllPages("folders", params, "getPageTrashed") + .then(trashedFoldersData => { + const folders = []; + trashedFoldersData.forEach(folderData => { + const folder = this.__addToCache(folderData); + folders.push(folder); + }); + return folders; + }); + }, + searchFolders: function( text, orderBy = { @@ -126,6 +153,37 @@ qx.Class.define("osparc.store.Folders", { }); }, + trashFolder: function(folderId, workspaceId) { + const params = { + "url": { + folderId + } + }; + return osparc.data.Resources.getInstance().fetch("folders", "trash", params) + .then(() => { + const folder = this.getFolder(folderId); + if (folder) { + this.__deleteFromCache(folderId, workspaceId); + this.fireDataEvent("folderRemoved", folder); + } + }) + .catch(console.error); + }, + + untrashFolder: function(folder) { + const params = { + "url": { + folderId: folder.getFolderId(), + } + }; + return osparc.data.Resources.getInstance().fetch("folders", "untrash", params) + .then(() => { + this.foldersCached.unshift(folder); + this.fireDataEvent("folderAdded", folder); + }) + .catch(console.error); + }, + deleteFolder: function(folderId, workspaceId) { const params = { "url": { diff --git a/services/static-webserver/client/source/class/osparc/store/StaticInfo.js b/services/static-webserver/client/source/class/osparc/store/StaticInfo.js index 1681a801cf4..2ac96fd58b0 100644 --- a/services/static-webserver/client/source/class/osparc/store/StaticInfo.js +++ b/services/static-webserver/client/source/class/osparc/store/StaticInfo.js @@ -69,6 +69,16 @@ qx.Class.define("osparc.store.StaticInfo", { return null; }, + getTrashRetentionDays: function() { + const staticKey = "webserverProjects"; + const wsStaticData = this.getValue(staticKey); + const key = "PROJECTS_TRASH_RETENTION_DAYS"; + if (key in wsStaticData) { + return wsStaticData[key]; + } + return "unknown"; + }, + getAccountDeletionRetentionDays: function() { const staticKey = "webserverLogin"; const wsStaticData = this.getValue(staticKey); diff --git a/services/static-webserver/client/source/class/osparc/store/Store.js b/services/static-webserver/client/source/class/osparc/store/Store.js index d2c2d24104e..7b94b336852 100644 --- a/services/static-webserver/client/source/class/osparc/store/Store.js +++ b/services/static-webserver/client/source/class/osparc/store/Store.js @@ -67,7 +67,7 @@ qx.Class.define("osparc.store.Store", { nullable: true }, studyBrowserContext: { - check: ["studiesAndFolders", "workspaces", "search"], + check: ["studiesAndFolders", "workspaces", "search", "trash"], init: "studiesAndFolders", nullable: false, event: "changeStudyBrowserContext", @@ -413,6 +413,43 @@ qx.Class.define("osparc.store.Store", { } }, + trashStudy: function(studyId) { + const params = { + url: { + studyId + } + }; + return new Promise((resolve, reject) => { + osparc.data.Resources.fetch("studies", "trash", params) + .then(() => { + this.remove("studies", "uuid", studyId); + resolve(); + }) + .catch(err => { + console.error(err); + reject(err); + }); + }); + }, + + untrashStudy: function(studyId) { + const params = { + url: { + studyId + } + }; + return new Promise((resolve, reject) => { + osparc.data.Resources.fetch("studies", "untrash", params) + .then(() => { + resolve(); + }) + .catch(err => { + console.error(err); + reject(err); + }); + }); + }, + getTemplate: function(templateId) { const templates = this.getTemplates(); return templates.find(template => template["uuid"] === templateId); diff --git a/services/static-webserver/client/source/class/osparc/store/Workspaces.js b/services/static-webserver/client/source/class/osparc/store/Workspaces.js index 253ac714a1d..924312639de 100644 --- a/services/static-webserver/client/source/class/osparc/store/Workspaces.js +++ b/services/static-webserver/client/source/class/osparc/store/Workspaces.js @@ -46,6 +46,15 @@ qx.Class.define("osparc.store.Workspaces", { thumbnail, }; }, + + curateOrderBy: function(orderBy) { + const curatedOrderBy = osparc.utils.Utils.deepCloneObject(orderBy); + if (curatedOrderBy.field !== "name") { + // only "modified_at" and "name" supported + curatedOrderBy.field = "modified_at"; + } + return curatedOrderBy; + }, }, members: { @@ -61,26 +70,105 @@ qx.Class.define("osparc.store.Workspaces", { return osparc.data.Resources.getInstance().getAllPages("workspaces") .then(workspacesData => { workspacesData.forEach(workspaceData => { - const workspace = new osparc.data.model.Workspace(workspaceData); - this.__addToCache(workspace); + this.__addToCache(workspaceData); }); return this.workspacesCached; }); }, + fetchAllTrashedWorkspaces: function(orderBy = { + field: "modified_at", + direction: "desc" + }) { + if (osparc.auth.Data.getInstance().isGuest()) { + return new Promise(resolve => { + resolve([]); + }); + } + + const curatedOrderBy = this.self().curateOrderBy(orderBy); + const params = { + url: { + orderBy: JSON.stringify(curatedOrderBy), + } + }; + return osparc.data.Resources.getInstance().getAllPages("workspaces", params, "getPageTrashed") + .then(trashedWorkspacesData => { + const workspaces = []; + trashedWorkspacesData.forEach(workspaceData => { + const workspace = this.__addToCache(workspaceData); + workspaces.push(workspace); + }); + return workspaces; + }); + }, + + searchWorkspaces: function(text) { + if (osparc.auth.Data.getInstance().isGuest()) { + return new Promise(resolve => { + resolve([]); + }); + } + + const params = { + url: { + text, + } + }; + return osparc.data.Resources.getInstance().getAllPages("workspaces", params, "getPageSearch") + .then(workspacesData => { + const workspaces = []; + workspacesData.forEach(workspaceData => { + const workspace = this.__addToCache(workspaceData); + workspaces.push(workspace); + }); + return workspaces; + }); + }, + postWorkspace: function(newWorkspaceData) { const params = { data: newWorkspaceData }; return osparc.data.Resources.getInstance().fetch("workspaces", "post", params) .then(workspaceData => { - const newWorkspace = new osparc.data.model.Workspace(workspaceData); - this.__addToCache(newWorkspace); + const newWorkspace = this.__addToCache(workspaceData); this.fireDataEvent("workspaceAdded", newWorkspace); return newWorkspace; }); }, + trashWorkspace: function(workspaceId) { + const params = { + "url": { + workspaceId + } + }; + return osparc.data.Resources.getInstance().fetch("workspaces", "trash", params) + .then(() => { + const workspace = this.getWorkspace(workspaceId); + if (workspace) { + this.__deleteFromCache(workspaceId); + this.fireDataEvent("workspaceRemoved", workspace); + } + }) + .catch(console.error); + }, + + untrashWorkspace: function(workspace) { + const params = { + "url": { + workspaceId: workspace.getWorkspaceId(), + } + }; + return osparc.data.Resources.getInstance().fetch("workspaces", "untrash", params) + .then(() => { + this.workspacesCached.unshift(workspace); + this.fireDataEvent("workspaceAdded", workspace); + }) + .catch(console.error); + }, + deleteWorkspace: function(workspaceId) { const params = { "url": { @@ -201,11 +289,27 @@ qx.Class.define("osparc.store.Workspaces", { return this.workspacesCached; }, - __addToCache: function(workspace) { - const found = this.workspacesCached.find(w => w.getWorkspaceId() === workspace.getWorkspaceId()); - if (!found) { + __addToCache: function(workspaceData) { + let workspace = this.workspacesCached.find(w => w.getWorkspaceId() === workspaceData["workspaceId"]); + if (workspace) { + const props = Object.keys(qx.util.PropertyUtil.getProperties(osparc.data.model.Workspace)); + // put + Object.keys(workspaceData).forEach(key => { + if (key === "createdAt") { + workspace.set("createdAt", new Date(workspaceData["createdAt"])); + } else if (key === "modifiedAt") { + workspace.set("modifiedAt", new Date(workspaceData["modifiedAt"])); + } else if (key === "trashedAt") { + workspace.set("trashedAt", new Date(workspaceData["trashedAt"])); + } else if (props.includes(key)) { + workspace.set(key, workspaceData[key]); + } + }); + } else { + workspace = new osparc.data.model.Workspace(workspaceData); this.workspacesCached.unshift(workspace); } + return workspace; }, __deleteFromCache: function(workspaceId) {