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 526f7032c27..0971a7d4990 100644 --- a/services/static-webserver/client/source/class/osparc/dashboard/FolderButtonItem.js +++ b/services/static-webserver/client/source/class/osparc/dashboard/FolderButtonItem.js @@ -46,7 +46,8 @@ qx.Class.define("osparc.dashboard.FolderButtonItem", { "folderSelected": "qx.event.type.Data", "folderUpdated": "qx.event.type.Data", "moveFolderToRequested": "qx.event.type.Data", - "deleteFolderRequested": "qx.event.type.Data" + "deleteFolderRequested": "qx.event.type.Data", + "changeContext": "qx.event.type.Data", }, properties: { @@ -186,19 +187,38 @@ qx.Class.define("osparc.dashboard.FolderButtonItem", { position: "bottom-right" }); - const editButton = new qx.ui.menu.Button(this.tr("Rename..."), "@FontAwesome5Solid/pencil-alt/12"); - editButton.addListener("execute", () => this.__editFolder(), this); - menu.add(editButton); + const studyBrowserContext = osparc.store.Store.getInstance().getStudyBrowserContext(); + if ( + studyBrowserContext === "search" || + studyBrowserContext === "studiesAndFolders" + ) { + const editButton = new qx.ui.menu.Button(this.tr("Rename..."), "@FontAwesome5Solid/pencil-alt/12"); + editButton.addListener("execute", () => this.__editFolder(), this); + menu.add(editButton); + + if (studyBrowserContext === "search") { + const openLocationButton = new qx.ui.menu.Button(this.tr("Open location"), "@FontAwesome5Solid/external-link-alt/12"); + openLocationButton.addListener("execute", () => { + const folder = this.getFolder(); + this.fireDataEvent("changeContext", { + context: "studiesAndFolders", + workspaceId: folder.getWorkspaceId(), + folderId: folder.getParentFolderId(), + }); + }, this); + menu.add(openLocationButton); + } - const moveToButton = new qx.ui.menu.Button(this.tr("Move to..."), "@FontAwesome5Solid/folder/12"); - moveToButton.addListener("execute", () => this.fireDataEvent("moveFolderToRequested", this.getFolderId()), this); - menu.add(moveToButton); + const moveToButton = new qx.ui.menu.Button(this.tr("Move to..."), "@FontAwesome5Solid/folder/12"); + moveToButton.addListener("execute", () => this.fireDataEvent("moveFolderToRequested", this.getFolderId()), this); + menu.add(moveToButton); - menu.addSeparator(); + menu.addSeparator(); - const deleteButton = new qx.ui.menu.Button(this.tr("Delete"), "@FontAwesome5Solid/trash/12"); - deleteButton.addListener("execute", () => this.__deleteFolderRequested(), this); - menu.add(deleteButton); + const deleteButton = new qx.ui.menu.Button(this.tr("Delete"), "@FontAwesome5Solid/trash/12"); + deleteButton.addListener("execute", () => this.__deleteFolderRequested(), this); + menu.add(deleteButton); + } menuButton.setMenu(menu); }, 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 b2f810f313c..344507aad9a 100644 --- a/services/static-webserver/client/source/class/osparc/dashboard/ResourceBrowserBase.js +++ b/services/static-webserver/client/source/class/osparc/dashboard/ResourceBrowserBase.js @@ -280,6 +280,14 @@ qx.Class.define("osparc.dashboard.ResourceBrowserBase", { const workspaceId = e.getData(); this._workspaceSelected(workspaceId); }, this); + resourcesContainer.addListener("changeContext", e => { + const { + context, + workspaceId, + folderId, + } = e.getData(); + this._changeContext(context, workspaceId, folderId); + }, this); resourcesContainer.addListener("workspaceUpdated", e => this._workspaceUpdated(e.getData())); resourcesContainer.addListener("deleteWorkspaceRequested", e => this._deleteWorkspaceRequested(e.getData())); @@ -479,6 +487,10 @@ qx.Class.define("osparc.dashboard.ResourceBrowserBase", { throw new Error("Abstract method called!"); }, + _changeContext: function(context, workspaceId, folderId) { + throw new Error("Abstract method called!"); + }, + _folderSelected: 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 187f6b441d3..b28b5d89a04 100644 --- a/services/static-webserver/client/source/class/osparc/dashboard/ResourceContainerManager.js +++ b/services/static-webserver/client/source/class/osparc/dashboard/ResourceContainerManager.js @@ -79,6 +79,7 @@ qx.Class.define("osparc.dashboard.ResourceContainerManager", { "workspaceSelected": "qx.event.type.Data", "workspaceUpdated": "qx.event.type.Data", "deleteWorkspaceRequested": "qx.event.type.Data", + "changeContext": "qx.event.type.Data", }, statics: { @@ -419,6 +420,7 @@ qx.Class.define("osparc.dashboard.ResourceContainerManager", { "folderUpdated", "moveFolderToRequested", "deleteFolderRequested", + "changeContext", ].forEach(eName => card.addListener(eName, e => this.fireDataEvent(eName, e.getData()))); return card; }, 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 19dab7c6815..b828daf3669 100644 --- a/services/static-webserver/client/source/class/osparc/dashboard/StudyBrowser.js +++ b/services/static-webserver/client/source/class/osparc/dashboard/StudyBrowser.js @@ -171,17 +171,30 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { if ( !osparc.auth.Manager.getInstance().isLoggedIn() || !osparc.utils.DisabledPlugins.isFoldersEnabled() || - this.getCurrentContext() !== "studiesAndFolders" || + this.getCurrentContext() === "workspaces" || this.__loadingFolders ) { return; } - const workspaceId = this.getCurrentWorkspaceId(); - const folderId = this.getCurrentFolderId(); this.__loadingFolders = true; + let request = null; + switch (this.getCurrentContext()) { + case "search": { + const filterData = this._searchBarFilter.getFilterData(); + const text = filterData.text ? encodeURIComponent(filterData.text) : ""; // name, description and uuid + request = osparc.store.Folders.getInstance().searchFolders(text, this.getOrderBy()); + break; + } + case "studiesAndFolders": { + const workspaceId = this.getCurrentWorkspaceId(); + const folderId = this.getCurrentFolderId(); + request = osparc.store.Folders.getInstance().fetchFolders(folderId, workspaceId, this.getOrderBy()); + break; + } + } this.__setFoldersToList([]); - osparc.store.Folders.getInstance().fetchFolders(folderId, workspaceId, this.getOrderBy()) + request .then(folders => { this.__setFoldersToList(folders); }) @@ -384,7 +397,7 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { }, _workspaceSelected: function(workspaceId) { - this.__changeContext("studiesAndFolders", workspaceId, null); + this._changeContext("studiesAndFolders", workspaceId, null); }, _workspaceUpdated: function() { @@ -444,7 +457,7 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { }, _folderSelected: function(folderId) { - this.__changeContext("studiesAndFolders", this.getCurrentWorkspaceId(), folderId); + this._changeContext("studiesAndFolders", this.getCurrentWorkspaceId(), folderId); }, _folderUpdated: function() { @@ -653,17 +666,23 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { const requestParams = {}; requestParams.orderBy = JSON.stringify(this.getOrderBy()); - const filterData = this._searchBarFilter.getFilterData(); - // Use the ``search`` functionality only if the user types some text - // tags should only be used to filter the current context (search context ot workspace/folder context) - if (filterData.text) { - requestParams.text = filterData.text ? encodeURIComponent(filterData.text) : ""; // name, description and uuid - requestParams["tagIds"] = filterData.tags.length ? filterData.tags.join(",") : ""; - return requestParams; + switch (this.getCurrentContext()) { + case "studiesAndFolders": + requestParams.workspaceId = this.getCurrentWorkspaceId(); + requestParams.folderId = this.getCurrentFolderId(); + break; + case "search": { + // Use the ``search`` functionality only if the user types some text + // tags should only be used to filter the current context (search context ot workspace/folder context) + const filterData = this._searchBarFilter.getFilterData(); + if (filterData.text) { + requestParams.text = filterData.text ? encodeURIComponent(filterData.text) : ""; // name, description and uuid + requestParams["tagIds"] = filterData.tags.length ? filterData.tags.join(",") : ""; + } + break; + } } - requestParams.workspaceId = this.getCurrentWorkspaceId(); - requestParams.folderId = this.getCurrentFolderId(); return requestParams; }, @@ -688,10 +707,16 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { resolveWResponse: true }; - if ("text" in requestParams) { - return osparc.data.Resources.fetch("studies", "getPageSearch", params, options); + let request = null; + switch (this.getCurrentContext()) { + case "search": + request = osparc.data.Resources.fetch("studies", "getPageSearch", params, options); + break; + case "studiesAndFolders": + request = osparc.data.Resources.fetch("studies", "getPage", params, options); + break; } - return osparc.data.Resources.fetch("studies", "getPage", params, options); + return request; }, invalidateStudies: function() { @@ -886,10 +911,11 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { }); this._resourcesContainer.addListener("changeSelection", e => { + const currentContext = this.getCurrentContext(); const selection = e.getData(); studiesMoveButton.set({ - visibility: selection.length ? "visible" : "excluded", + visibility: selection.length && currentContext === "studiesAndFolders" ? "visible" : "excluded", label: selection.length > 1 ? this.tr("Move selected")+" ("+selection.length+")" : this.tr("Move") }); @@ -910,7 +936,7 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { header.addListener("locationChanged", () => { const workspaceId = header.getCurrentWorkspaceId(); const folderId = header.getCurrentFolderId(); - this.__changeContext("studiesAndFolders", workspaceId, folderId); + this._changeContext("studiesAndFolders", workspaceId, folderId); }, this); const workspacesAndFoldersTree = this._resourceFilter.getWorkspacesAndFoldersTree(); @@ -918,27 +944,27 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { const context = e.getData(); const workspaceId = context["workspaceId"]; if (workspaceId === -1) { - this.__changeContext("workspaces"); + this._changeContext("workspaces"); } else { const folderId = context["folderId"]; - this.__changeContext("studiesAndFolders", workspaceId, folderId); + this._changeContext("studiesAndFolders", workspaceId, folderId); } }, this); this._searchBarFilter.addListener("filterChanged", e => { const filterData = e.getData(); if (filterData.text) { - this.__changeContext("search"); + this._changeContext("search"); } else { const workspaceId = this.getCurrentWorkspaceId(); const folderId = this.getCurrentFolderId(); - this.__changeContext("studiesAndFolders", workspaceId, folderId); + this._changeContext("studiesAndFolders", workspaceId, folderId); } }); } }, - __changeContext: function(context, workspaceId = null, folderId = null) { + _changeContext: function(context, workspaceId = null, folderId = null) { if (osparc.utils.DisabledPlugins.isFoldersEnabled()) { if ( context !== "search" && // reload studies for a new search @@ -950,6 +976,7 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { return; } + osparc.store.Store.getInstance().setStudyBrowserContext(context); this.set({ currentContext: context, currentWorkspaceId: workspaceId, @@ -962,7 +989,7 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { this._resourcesContainer.setResourcesToList([]); if (context === "search") { - this.__setFoldersToList([]); + this.__reloadFolders(); this.__reloadStudies(); } else if (context === "workspaces") { this._searchBarFilter.resetFilters(); @@ -1342,7 +1369,7 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { __getOpenLocationMenuButton: function(studyData) { const openLocationButton = new qx.ui.menu.Button(this.tr("Open location"), "@FontAwesome5Solid/external-link-alt/12"); openLocationButton.addListener("execute", () => { - this.__changeContext("studiesAndFolders", studyData["workspaceId"], studyData["folderId"]); + this._changeContext("studiesAndFolders", studyData["workspaceId"], studyData["folderId"]); }, this); return openLocationButton; }, 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 5484107fd96..007ba33eddd 100644 --- a/services/static-webserver/client/source/class/osparc/data/Resources.js +++ b/services/static-webserver/client/source/class/osparc/data/Resources.js @@ -301,6 +301,11 @@ 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}" }, + getPageSearch: { + useCache: false, + method: "GET", + url: statics.API + "/folders:search?offset={offset}&limit={limit}&text={text}&order_by={orderBy}" + }, getOne: { method: "GET", url: statics.API + "/folders/{folderId}" @@ -1368,7 +1373,7 @@ qx.Class.define("osparc.data.Resources", { }); }, - getAllPages: function(resource, params = {}) { + getAllPages: function(resource, params = {}, endpoint = "getPage") { return new Promise((resolve, reject) => { let resources = []; let offset = 0; @@ -1377,7 +1382,6 @@ qx.Class.define("osparc.data.Resources", { } params["url"]["offset"] = offset; params["url"]["limit"] = 10; - const endpoint = "getPage"; const options = { resolveWResponse: true }; 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 1dd99d015a2..b8b9eb03b21 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,6 +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(), }); }, @@ -95,7 +96,13 @@ qx.Class.define("osparc.data.model.Folder", { nullable: true, init: null, event: "changeLastModified" - } + }, + + trashedAt: { + check: "Date", + nullable: true, + init: null, + }, }, statics: { diff --git a/services/static-webserver/client/source/class/osparc/data/model/Study.js b/services/static-webserver/client/source/class/osparc/data/model/Study.js index 598e0575d22..ab178aca669 100644 --- a/services/static-webserver/client/source/class/osparc/data/model/Study.js +++ b/services/static-webserver/client/source/class/osparc/data/model/Study.js @@ -58,7 +58,8 @@ qx.Class.define("osparc.data.model.Study", { state: studyData.state || this.getState(), quality: studyData.quality || this.getQuality(), permalink: studyData.permalink || this.getPermalink(), - dev: studyData.dev || this.getDev() + dev: studyData.dev || this.getDev(), + trashedAt: studyData.trashedAt ? new Date(studyData.trashedAt) : this.getTrashedAt(), }); const wbData = studyData.workbench || this.getWorkbench(); @@ -209,7 +210,13 @@ qx.Class.define("osparc.data.model.Study", { nullable: true, event: "changeReadOnly", init: true - } + }, + + trashedAt: { + check: "Date", + nullable: true, + init: null, + }, // ------ ignore for serializing ------ }, @@ -218,7 +225,8 @@ qx.Class.define("osparc.data.model.Study", { "permalink", "state", "pipelineRunning", - "readOnly" + "readOnly", + "trashedAt", ], IgnoreModelizationProps: [ 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 16385de935c..7deb66618bb 100644 --- a/services/static-webserver/client/source/class/osparc/store/Folders.js +++ b/services/static-webserver/client/source/class/osparc/store/Folders.js @@ -31,6 +31,17 @@ qx.Class.define("osparc.store.Folders", { "folderMoved": "qx.event.type.Data", }, + statics: { + 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: { foldersCached: null, @@ -40,7 +51,7 @@ qx.Class.define("osparc.store.Folders", { orderBy = { field: "modified_at", direction: "desc" - } + }, ) { if (osparc.auth.Data.getInstance().isGuest()) { return new Promise(resolve => { @@ -48,12 +59,7 @@ qx.Class.define("osparc.store.Folders", { }); } - const curatedOrderBy = osparc.utils.Utils.deepCloneObject(orderBy); - if (curatedOrderBy.field !== "name") { - // only "modified_at" and "name" supported - curatedOrderBy.field = "modified_at"; - } - + const curatedOrderBy = this.self().curateOrderBy(orderBy); const params = { url: { workspaceId, @@ -72,6 +78,37 @@ qx.Class.define("osparc.store.Folders", { }); }, + searchFolders: function( + text, + 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: { + text, + orderBy: JSON.stringify(curatedOrderBy), + } + }; + return osparc.data.Resources.getInstance().getAllPages("folders", params, "getPageSearch") + .then(foldersData => { + const folders = []; + foldersData.forEach(folderData => { + const folder = this.__addToCache(folderData); + folders.push(folder); + }); + return folders; + }); + }, + postFolder: function(name, parentFolderId = null, workspaceId = null) { const newFolderData = { name, @@ -141,6 +178,8 @@ qx.Class.define("osparc.store.Folders", { folder.set("createdAt", new Date(folderData["createdAt"])); } else if (key === "modifiedAt") { folder.set("lastModified", new Date(folderData["modifiedAt"])); + } else if (key === "trashedAt") { + folder.set("trashedAt", new Date(folderData["trashedAt"])); } else { folder.set(key, folderData[key]); } 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 0e015ed7811..89ccc5e51a0 100644 --- a/services/static-webserver/client/source/class/osparc/store/Store.js +++ b/services/static-webserver/client/source/class/osparc/store/Store.js @@ -66,6 +66,12 @@ qx.Class.define("osparc.store.Store", { init: null, nullable: true }, + studyBrowserContext: { + check: ["studiesAndFolders", "workspaces", "search"], + init: "studiesAndFolders", + nullable: false, + event: "changeStudyBrowserContext", + }, studies: { check: "Array", init: []