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 fa21ec7c9b7..b3c1bec4da6 100644 --- a/services/static-webserver/client/source/class/osparc/dashboard/FolderButtonItem.js +++ b/services/static-webserver/client/source/class/osparc/dashboard/FolderButtonItem.js @@ -44,6 +44,7 @@ qx.Class.define("osparc.dashboard.FolderButtonItem", { events: { "folderSelected": "qx.event.type.Data", + "folderUpdated": "qx.event.type.Data", "deleteFolderRequested": "qx.event.type.Data" }, @@ -148,9 +149,9 @@ qx.Class.define("osparc.dashboard.FolderButtonItem", { __applyFolder: function(folder) { this.getChildControl("icon"); this.set({ - cardKey: "folder-" + folder.getId() + cardKey: "folder-" + folder.getFolderId() }); - folder.bind("id", this, "folderId"); + folder.bind("folderId", this, "folderId"); folder.bind("parentId", this, "parentFolderId"); folder.bind("name", this, "title"); folder.bind("description", this, "description"); @@ -198,18 +199,18 @@ qx.Class.define("osparc.dashboard.FolderButtonItem", { folderEditor.addListener("updateFolder", () => { const newName = folderEditor.getLabel(); const newDescription = folderEditor.getDescription(); - const promises = []; - if (newName !== folder.getName()) { - promises.push(osparc.data.model.Folder.patchFolder(this.getFolderId(), "name", newName)); - } - if (newDescription !== folder.getDescription()) { - promises.push(osparc.data.model.Folder.patchFolder(this.getFolderId(), "description", newDescription)); - } - Promise.all(promises) - .then(() => folder.set({ - name: newName, - description: newDescription - })) + const updateData = { + "name": newName, + "description": newDescription + }; + osparc.data.model.Folder.putFolder(this.getFolderId(), updateData) + .then(() => { + folder.set({ + name: newName, + description: newDescription + }); + this.fireDataEvent("folderUpdated", folder.getFolderId()); + }) .catch(err => console.error(err)); win.close(); }); @@ -260,10 +261,15 @@ qx.Class.define("osparc.dashboard.FolderButtonItem", { }, __openShareWith: function() { - const title = this.tr("Share Folder"); - const permissionsView = new osparc.share.CollaboratorsFolder(this.getFolder()); - osparc.ui.window.Window.popUpInWindow(permissionsView, title); - permissionsView.addListener("updateAccessRights", () => this.__applyAccessRights(this.getFolder().getAccessRights()), this); + const disableShare = true; + if (disableShare) { + osparc.FlashMessenger.getInstance().logAs(this.tr("Not yet implemented"), "WARNING"); + } else { + const title = this.tr("Share Folder"); + const permissionsView = new osparc.share.CollaboratorsFolder(this.getFolder()); + osparc.ui.window.Window.popUpInWindow(permissionsView, title); + permissionsView.addListener("updateAccessRights", () => this.__applyAccessRights(this.getFolder().getAccessRights()), this); + } }, __deleteStudyRequested: function() { diff --git a/services/static-webserver/client/source/class/osparc/dashboard/FolderHeader.js b/services/static-webserver/client/source/class/osparc/dashboard/FolderHeader.js index 7463c668649..bd4582fd0fc 100644 --- a/services/static-webserver/client/source/class/osparc/dashboard/FolderHeader.js +++ b/services/static-webserver/client/source/class/osparc/dashboard/FolderHeader.js @@ -117,7 +117,7 @@ qx.Class.define("osparc.dashboard.FolderHeader", { } else { folderButton = new qx.ui.form.Button(this.tr("Home"), "@FontAwesome5Solid/home/14"); } - folderButton.addListener("execute", () => this.fireDataEvent("changeCurrentFolderId", folder ? folder.getId() : null), this); + folderButton.addListener("execute", () => this.fireDataEvent("changeCurrentFolderId", folder ? folder.getFolderId() : null), this); folderButton.set({ backgroundColor: "transparent", textColor: "text", diff --git a/services/static-webserver/client/source/class/osparc/dashboard/FolderTreeItem.js b/services/static-webserver/client/source/class/osparc/dashboard/FolderTreeItem.js new file mode 100644 index 00000000000..d3873d37b08 --- /dev/null +++ b/services/static-webserver/client/source/class/osparc/dashboard/FolderTreeItem.js @@ -0,0 +1,41 @@ +/* ************************************************************************ + + osparc - the simcore frontend + + https://osparc.io + + Copyright: + 2024 IT'IS Foundation, https://itis.swiss + + License: + MIT: https://opensource.org/licenses/MIT + + Authors: + * Odei Maiz (odeimaiz) + +************************************************************************ */ + +qx.Class.define("osparc.dashboard.FolderTreeItem", { + extend: qx.ui.tree.VirtualTreeItem, + + members: { + _addWidgets: function() { + this.addSpacer(); + // this.addOpenButton(); + const openButton = this.getChildControl("open"); + openButton.addListener("changeOpen", () => { + console.log("changeOpen", this); + }, this); + openButton.addListener("changeVisibility", e => { + // console.log("changeVisibility", this.getLabel() + e.getData(), this); + openButton.show(); + }, this); + this._add(openButton); + this.addIcon(); + const label = this.getChildControl("label"); + this._add(label, { + flex: 1 + }); + } + } +}); diff --git a/services/static-webserver/client/source/class/osparc/dashboard/FoldersTree.js b/services/static-webserver/client/source/class/osparc/dashboard/FoldersTree.js new file mode 100644 index 00000000000..2a597b08b92 --- /dev/null +++ b/services/static-webserver/client/source/class/osparc/dashboard/FoldersTree.js @@ -0,0 +1,131 @@ +/* ************************************************************************ + + osparc - the simcore frontend + + https://osparc.io + + Copyright: + 2024 IT'IS Foundation, https://itis.swiss + + License: + MIT: https://opensource.org/licenses/MIT + + Authors: + * Odei Maiz (odeimaiz) + +************************************************************************ */ + +qx.Class.define("osparc.dashboard.FoldersTree", { + extend: qx.ui.tree.VirtualTree, + + construct: function() { + const rootFolder = this.self().createNewEntry(null); + const root = qx.data.marshal.Json.createModel(rootFolder, true); + this.__fetchChildren(root); + + this.base(arguments, root, "label", "children"); + + this.set({ + openMode: "dbltap", + decorator: "no-border", + font: "text-14", + showLeafs: true, + paddingLeft: -10, + }); + + this.__initTree(); + }, + + events: { + "selectionChanged": "qx.event.type.Event" // tap + }, + + statics: { + createNewEntry: function(folder) { + return { + folderId: folder ? folder.getFolderId() : null, + label: folder ? folder.getName() : "Home", + children: [ + this.self().getLoadingData() + ], + loaded: false, + }; + }, + + getLoadingData: function() { + return { + folderId: -1, + label: "Loading...", + children: [], + icon: "@FontAwesome5Solid/circle-notch/12", + loaded: false, + }; + }, + + addLoadingChild: function(parentModel) { + const loadingModel = qx.data.marshal.Json.createModel(this.self().getLoadingData(), true); + parentModel.getChildren().append(loadingModel); + }, + + removeLoadingChild: function(parent) { + for (let i = parent.getChildren().getLength() - 1; i >= 0; i--) { + if (parent.getChildren().toArray()[i].getLabel() === "Loading...") { + parent.getChildren().splice(i, 1); + } + } + } + }, + + members: { + __initTree: function() { + const that = this; + this.setDelegate({ + createItem: () => new osparc.dashboard.FolderTreeItem(), + bindItem: (c, item, id) => { + c.bindDefaultProperties(item, id); + c.bindProperty("folderId", "model", null, item, id); + c.bindProperty("", "open", { + converter(value, _, __, target) { + const isOpen = target.isOpen(); + if (isOpen && !value.getLoaded()) { + // eslint-disable-next-line no-underscore-dangle + that.__fetchChildren(value); + } + return isOpen; + } + }, item, id); + }, + configureItem: item => { + item.addListener("tap", () => this.fireDataEvent("selectionChanged", item.getModel()), this); + }, + sorter: (a, b) => { + const aLabel = a.getLabel(); + if (aLabel === -1) { + return 1; + } + const bLabel = b.getLabel(); + if (bLabel === -1) { + return -1; + } + return aLabel - bLabel; + } + }); + }, + + __fetchChildren: function(parentModel) { + parentModel.setLoaded(true); + + const folderId = parentModel.getFolderId ? parentModel.getFolderId() : parentModel.getModel(); + osparc.store.Folders.getInstance().fetchFolders(folderId) + .then(folders => { + this.self().removeLoadingChild(parentModel); + folders.forEach(folder => { + const folderData = this.self().createNewEntry(folder); + const folderModel = qx.data.marshal.Json.createModel(folderData, true); + parentModel.getChildren().append(folderModel); + }); + }) + .catch(console.error); + } + } +}); diff --git a/services/static-webserver/client/source/class/osparc/dashboard/MoveStudyToFolder.js b/services/static-webserver/client/source/class/osparc/dashboard/MoveStudyToFolder.js new file mode 100644 index 00000000000..f01d64f42b4 --- /dev/null +++ b/services/static-webserver/client/source/class/osparc/dashboard/MoveStudyToFolder.js @@ -0,0 +1,92 @@ +/* ************************************************************************ + + osparc - the simcore frontend + + https://osparc.io + + Copyright: + 2024 IT'IS Foundation, https://itis.swiss + + License: + MIT: https://opensource.org/licenses/MIT + + Authors: + * Odei Maiz (odeimaiz) + +************************************************************************ */ + +qx.Class.define("osparc.dashboard.MoveStudyToFolder", { + extend: qx.ui.core.Widget, + + construct: function(studyData, currentFolderId) { + this.base(arguments); + + this.__studyData = studyData; + this.__currentFolderId = currentFolderId; + + this._setLayout(new qx.ui.layout.VBox(10)); + + this.getChildControl("current-folder"); + const foldersTree = this.getChildControl("folders-tree"); + this.getChildControl("cancel-btn"); + const moveButton = this.getChildControl("move-btn"); + + moveButton.setEnabled(false) + foldersTree.addListener("selectionChanged", e => { + const folderId = e.getData(); + moveButton.setEnabled(this.__currentFolderId !== folderId); + moveButton.addListenerOnce("execute", () => this.fireDataEvent("moveToFolder", folderId)); + }); + }, + + events: { + "cancel": "qx.event.type.Event", + "moveToFolder": "qx.event.type.Data" + }, + + members: { + __studyData: null, + __currentFolderId: null, + + _createChildControlImpl: function(id) { + let control; + switch (id) { + case "current-folder": { + const folder = osparc.store.Folders.getInstance().getFolder(this.__currentFolderId); + const currentFolderName = folder ? folder["name"] : "Home"; + control = new qx.ui.basic.Label(this.tr("Current location: ") + currentFolderName); + this._add(control); + break; + } + case "folders-tree": + control = new osparc.dashboard.FoldersTree(); + this._add(control); + break; + case "buttons-layout": + control = new qx.ui.container.Composite(new qx.ui.layout.HBox(10).set({ + alignX: "right" + })); + this._add(control); + break; + case "cancel-btn": { + const buttons = this.getChildControl("buttons-layout"); + control = new qx.ui.form.Button(this.tr("Cancel")).set({ + appearance: "form-button-text" + }); + control.addListener("execute", () => this.fireEvent("cancel"), this); + buttons.add(control); + break; + } + case "move-btn": { + const buttons = this.getChildControl("buttons-layout"); + control = new qx.ui.form.Button(this.tr("Move")).set({ + appearance: "form-button" + }); + buttons.add(control); + break; + } + } + return control || this.base(arguments, id); + } + } +}); 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 fc253808cf3..d1219cec8e8 100644 --- a/services/static-webserver/client/source/class/osparc/dashboard/ResourceBrowserBase.js +++ b/services/static-webserver/client/source/class/osparc/dashboard/ResourceBrowserBase.js @@ -252,6 +252,7 @@ qx.Class.define("osparc.dashboard.ResourceBrowserBase", { resourcesContainer.addListener("tagClicked", e => this._searchBarFilter.addTagActiveFilter(e.getData())); resourcesContainer.addListener("emptyStudyClicked", e => this._deleteResourceRequested(e.getData())); resourcesContainer.addListener("folderSelected", e => this._folderSelected(e.getData())); + resourcesContainer.addListener("folderUpdated", e => this._folderUpdated(e.getData())); resourcesContainer.addListener("deleteFolderRequested", e => this._deleteFolderRequested(e.getData())); this._addToLayout(resourcesContainer); }, @@ -451,6 +452,10 @@ qx.Class.define("osparc.dashboard.ResourceBrowserBase", { throw new Error("Abstract method called!"); }, + _folderUpdated: function(folderId) { + 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 2b66627aae6..3cb0d1a1711 100644 --- a/services/static-webserver/client/source/class/osparc/dashboard/ResourceContainerManager.js +++ b/services/static-webserver/client/source/class/osparc/dashboard/ResourceContainerManager.js @@ -30,14 +30,13 @@ qx.Class.define("osparc.dashboard.ResourceContainerManager", { this.__foldersList = []; this.__resourcesList = []; - const folders = new qx.ui.container.Composite(new qx.ui.layout.VBox(10)); + const folders = this.__foldersLayout = new qx.ui.container.Composite(new qx.ui.layout.VBox(10)); const folderHeader = this.__folderHeader = new osparc.dashboard.FolderHeader(); folders.add(folderHeader); const foldersContainer = this.__foldersContainer = new osparc.dashboard.ToggleButtonContainer(); folders.add(foldersContainer); this._add(folders); - // Only visible in master until it's connected to the backend - folders.setVisibility(osparc.utils.Utils.isDevelopmentPlatform() ? "visible" : "excluded"); + folders.setVisibility(osparc.utils.DisabledPlugins.isFoldersEnabled() ? "visible" : "excluded"); const nonGroupedContainer = this.__nonGroupedContainer = new osparc.dashboard.ToggleButtonContainer(); [ @@ -77,6 +76,7 @@ qx.Class.define("osparc.dashboard.ResourceContainerManager", { "changeSelection": "qx.event.type.Data", "changeVisibility": "qx.event.type.Data", "folderSelected": "qx.event.type.Data", + "folderUpdated": "qx.event.type.Data", "deleteFolderRequested": "qx.event.type.Data", }, @@ -103,6 +103,7 @@ qx.Class.define("osparc.dashboard.ResourceContainerManager", { members: { __foldersList: null, __resourcesList: null, + __foldersLayout: null, __folderHeader: null, __foldersContainer: null, __nonGroupedContainer: null, @@ -239,6 +240,7 @@ qx.Class.define("osparc.dashboard.ResourceContainerManager", { card.subscribeToFilterGroup("searchBarFilter"); [ "folderSelected", + "folderUpdated", "deleteFolderRequested", ].forEach(eName => card.addListener(eName, e => this.fireDataEvent(eName, e.getData()))); return card; @@ -260,6 +262,7 @@ qx.Class.define("osparc.dashboard.ResourceContainerManager", { reloadCards: function(listId) { this.__cleanAll(); + this._add(this.__foldersLayout); if (this.getGroupBy()) { const noGroupContainer = this.__createGroupContainer("no-group", "No Group", "transparent"); this._add(noGroupContainer); 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 53055b72334..ce42e27e10d 100644 --- a/services/static-webserver/client/source/class/osparc/dashboard/StudyBrowser.js +++ b/services/static-webserver/client/source/class/osparc/dashboard/StudyBrowser.js @@ -184,7 +184,8 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { if (nStudies === 0) { Promise.all([ osparc.store.Store.getInstance().getTemplates(), - osparc.service.Store.getServicesLatest() + osparc.service.Store.getServicesLatest(), + osparc.store.Folders.getInstance().fetchFolders(), ]).then(values => { const templates = values[0]; const services = values[1]; @@ -320,8 +321,8 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { case "name": sortByValue = "name"; break; - case "owner": - sortByValue = "prjOwner"; + case "prj_owner": + sortByValue = "owner"; break; case "creation_date": sortByValue = "createdAt"; @@ -374,7 +375,7 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { newFolderCard.subscribeToFilterGroup("searchBarFilter"); newFolderCard.addListener("createFolder", e => { const data = e.getData(); - osparc.store.Folders.getInstance().postFolder(data.name, data.description, currentFolder ? currentFolder.getId() : null) + osparc.store.Folders.getInstance().postFolder(data.name, data.description, currentFolder ? currentFolder.getFolderId() : null) .then(() => this.__reloadFolders()) .catch(err => console.error(err)); }) @@ -388,7 +389,18 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { __applyCurrentFolderId: function(currentFolderId) { osparc.store.Folders.getInstance().fetchFolders(currentFolderId) - .then(() => this.__reloadFolders()); + .then(() => { + this._resourcesContainer.setResourcesToList([]); + this._resourcesList = []; + this.invalidateStudies(); + + this.__reloadResources(); + }) + .catch(console.error); + }, + + _folderUpdated: function() { + this.__reloadFolders(); }, _deleteFolderRequested: function(folderId) { @@ -492,12 +504,14 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { resolveWResponse: true }; + const currentFolderId = this.getCurrentFolderId(); + params.url.folderId = currentFolderId; if (params.url.orderBy) { - return osparc.data.Resources.fetch("studies", "getPageSortBySearch", params, undefined, options); + return osparc.data.Resources.fetch("studies", "getPageFolderSortBy", params, undefined, options); } else if (params.url.search) { - return osparc.data.Resources.fetch("studies", "getPageFilterSearch", params, undefined, options); + return osparc.data.Resources.fetch("studies", "getPageFolderSearch", params, undefined, options); } - return osparc.data.Resources.fetch("studies", "getPage", params, undefined, options); + return osparc.data.Resources.fetch("studies", "getPageFolder", params, undefined, options); }, __getTextFilteredNextRequest: function(text) { @@ -516,7 +530,10 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { const options = { resolveWResponse: true }; - return osparc.data.Resources.fetch("studies", "getPageFilterSearch", params, undefined, options); + + const currentFolderId = this.getCurrentFolderId(); + params.url.folderId = currentFolderId; + return osparc.data.Resources.fetch("studies", "getPageFolderSearch", params, undefined, options); }, __getSortedByNextRequest: function() { @@ -535,7 +552,10 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { const options = { resolveWResponse: true }; - return osparc.data.Resources.fetch("studies", "getPageSortBySearch", params, undefined, options); + + const currentFolderId = this.getCurrentFolderId(); + params.url.folderId = currentFolderId; + return osparc.data.Resources.fetch("studies", "getPageFolderSortBy", params, undefined, options); }, invalidateStudies: function() { @@ -680,7 +700,7 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { _createLayout: function() { this._createResourcesLayout(); - const folderHeader = this._resourcesContainer.getFolderHeader() + const folderHeader = this._resourcesContainer.getFolderHeader(); if (folderHeader) { this.bind("currentFolderId", folderHeader, "currentFolderId"); folderHeader.addListener("changeCurrentFolderId", e => this.setCurrentFolderId(e.getData())); @@ -891,7 +911,17 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { const title = osparc.utils.Utils.getUniqueStudyName(minStudyData.name, this._resourcesList); minStudyData["name"] = title; minStudyData["description"] = ""; - this.__createStudy(minStudyData, null); + this._showLoadingPage(this.tr("Creating ") + (minStudyData.name || osparc.product.Utils.getStudyAlias())); + const params = { + data: minStudyData + }; + osparc.study.Utils.createStudyAndPoll(params) + .then(studyData => this.__startStudyAfterCreating(studyData["uuid"])) + .catch(err => { + this._hideLoadingPage(); + osparc.FlashMessenger.getInstance().logAs(err.message, "ERROR"); + console.error(err); + }); }, __newPlanBtnClicked: function(templateData, newStudyName) { @@ -901,20 +931,7 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { templateCopyData.name = title; this._showLoadingPage(this.tr("Creating ") + (newStudyName || osparc.product.Utils.getStudyAlias())); osparc.study.Utils.createStudyFromTemplate(templateCopyData, this._loadingPage) - .then(studyId => { - const openCB = () => this._hideLoadingPage(); - const cancelCB = () => { - this._hideLoadingPage(); - const params = { - url: { - "studyId": studyId - } - }; - osparc.data.Resources.fetch("studies", "delete", params, studyId); - }; - const isStudyCreation = true; - this._startStudyById(studyId, openCB, cancelCB, isStudyCreation); - }) + .then(studyId => this.__startStudyAfterCreating(studyId)) .catch(err => { this._hideLoadingPage(); osparc.FlashMessenger.getInstance().logAs(err.message, "ERROR"); @@ -926,20 +943,7 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { button.setValue(false); this._showLoadingPage(this.tr("Creating ") + osparc.product.Utils.getStudyAlias()); osparc.study.Utils.createStudyFromService(key, version, this._resourcesList, newStudyLabel) - .then(studyId => { - const openCB = () => this._hideLoadingPage(); - const cancelCB = () => { - this._hideLoadingPage(); - const params = { - url: { - "studyId": studyId - } - }; - osparc.data.Resources.fetch("studies", "delete", params, studyId); - }; - const isStudyCreation = true; - this._startStudyById(studyId, openCB, cancelCB, isStudyCreation); - }) + .then(studyId => this.__startStudyAfterCreating(studyId)) .catch(err => { this._hideLoadingPage(); osparc.FlashMessenger.getInstance().logAs(err.message, "ERROR"); @@ -947,32 +951,32 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { }); }, - __createStudy: function(minStudyData) { - this._showLoadingPage(this.tr("Creating ") + (minStudyData.name || osparc.product.Utils.getStudyAlias())); + __startStudyAfterCreating: function(studyId) { + if (this.getCurrentFolderId()) { + this.__moveStudyToFolder(studyId, this.getCurrentFolderId()); + } + const openCB = () => this._hideLoadingPage(); + const cancelCB = () => { + this._hideLoadingPage(); + const params = { + url: { + studyId + } + }; + osparc.data.Resources.fetch("studies", "delete", params, studyId); + }; + const isStudyCreation = true; + this._startStudyById(studyId, openCB, cancelCB, isStudyCreation); + }, + __moveStudyToFolder: function(studyId, folderId) { const params = { - data: minStudyData + url: { + studyId, + folderId + } }; - osparc.study.Utils.createStudyAndPoll(params) - .then(studyData => { - const openCB = () => this._hideLoadingPage(); - const cancelCB = () => { - this._hideLoadingPage(); - const params2 = { - url: { - "studyId": studyData["uuid"] - } - }; - osparc.data.Resources.fetch("studies", "delete", params2, studyData["uuid"]); - }; - const isStudyCreation = true; - this._startStudyById(studyData["uuid"], openCB, cancelCB, isStudyCreation); - }) - .catch(err => { - this._hideLoadingPage(); - osparc.FlashMessenger.getInstance().logAs(err.message, "ERROR"); - console.error(err); - }); + return osparc.data.Resources.fetch("studies", "moveToFolder", params); }, _updateStudyData: function(studyData) { @@ -1033,7 +1037,9 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { if (shareButton) { menu.add(shareButton); } + } + if (writeAccess) { const tagsButton = this._getTagsMenuButton(card); if (tagsButton) { menu.add(tagsButton); @@ -1048,6 +1054,13 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { menu.add(billingsSettingsButton); } + if (writeAccess && osparc.utils.DisabledPlugins.isFoldersEnabled()) { + const moveToFolderButton = this.__getMoveToFolderMenuButton(studyData); + if (moveToFolderButton) { + menu.add(moveToFolderButton); + } + } + if (deleteAccess) { const deleteButton = this.__getDeleteStudyMenuButton(studyData, false); if (deleteButton) { @@ -1127,6 +1140,34 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { return studyBillingSettingsButton; }, + __getMoveToFolderMenuButton: function(studyData) { + const text = osparc.utils.Utils.capitalize(this.tr("Move to Folder...")); + const studyBillingSettingsButton = new qx.ui.menu.Button(text, "@FontAwesome5Solid/folder/12"); + studyBillingSettingsButton.addListener("tap", () => { + if (Object.keys(studyData["accessRights"]).length > 1) { + osparc.FlashMessenger.getInstance().logAs(this.tr("Shared projects can't be moved yet"), "WARNING"); + } else { + const title = this.tr("Move") + " " + studyData["name"]; + const moveStudyToFolder = new osparc.dashboard.MoveStudyToFolder(studyData, this.getCurrentFolderId()); + const win = osparc.ui.window.Window.popUpInWindow(moveStudyToFolder, title, 350, 280); + moveStudyToFolder.addListener("moveToFolder", e => { + win.close(); + const folderId = e.getData(); + this.__moveStudyToFolder(studyData["uuid"], folderId) + .then(() => { + this.__removeFromStudyList(studyData["uuid"]); + }) + .catch(err => { + console.error(err); + osparc.FlashMessenger.logAs(err.message, "ERROR"); + }); + }, this); + moveStudyToFolder.addListener("cancel", () => win.close()); + } + }, this); + return studyBillingSettingsButton; + }, + __getDuplicateMenuButton: function(studyData) { const duplicateButton = new qx.ui.menu.Button(this.tr("Duplicate"), "@FontAwesome5Solid/copy/12"); duplicateButton.addListener("execute", () => this.__duplicateStudy(studyData), this); @@ -1338,7 +1379,7 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { operationPromise = osparc.store.Store.getInstance().deleteStudy(studyData.uuid); } operationPromise - .then(() => this.__removeFromStudyList(studyData.uuid, false)) + .then(() => this.__removeFromStudyList(studyData.uuid)) .catch(err => { console.error(err); osparc.FlashMessenger.getInstance().logAs(err, "ERROR"); 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 c89f8b0aeeb..749e445cdbb 100644 --- a/services/static-webserver/client/source/class/osparc/data/Resources.js +++ b/services/static-webserver/client/source/class/osparc/data/Resources.js @@ -119,19 +119,19 @@ qx.Class.define("osparc.data.Resources", { method: "GET", url: statics.API + "/projects?type=user" }, - getPage: { + getPageFolder: { method: "GET", - url: statics.API + "/projects?type=user&offset={offset}&limit={limit}" + url: statics.API + "/projects?type=user&offset={offset}&limit={limit}&folder_id={folderId}" }, - getPageFilterSearch: { + getPageFolderSearch: { useCache: false, method: "GET", - url: statics.API + "/projects?type=user&offset={offset}&limit={limit}&search={text}" + url: statics.API + "/projects?type=user&offset={offset}&limit={limit}&folder_id={folderId}&search={text}" }, - getPageSortBySearch: { + getPageFolderSortBy: { useCache: false, method: "GET", - url: statics.API + "/projects?type=user&offset={offset}&limit={limit}&order_by={orderBy}" + url: statics.API + "/projects?type=user&offset={offset}&limit={limit}&folder_id={folderId}&order_by={orderBy}" }, getOne: { useCache: false, @@ -276,6 +276,10 @@ qx.Class.define("osparc.data.Resources", { useCache: false, method: "GET", url: statics.API + "/projects/{studyId}/inactivity" + }, + moveToFolder: { + method: "PUT", + url: statics.API + "/projects/{studyId}/folders/{folderId}" } } }, @@ -293,6 +297,32 @@ qx.Class.define("osparc.data.Resources", { } } }, + "folders": { + useCache: true, + idField: "uuid", + endpoints: { + getPage: { + method: "GET", + url: statics.API + "/folders?folder_id={folderId}&offset={offset}&limit={limit}" + }, + getOne: { + method: "GET", + url: statics.API + "/folders/{folderId}" + }, + post: { + method: "POST", + url: statics.API + "/folders" + }, + update: { + method: "PUT", + url: statics.API + "/folders/{folderId}" + }, + delete: { + method: "DELETE", + url: statics.API + "/folders/{folderId}" + } + } + }, "resourceUsage": { useCache: false, endpoints: { 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 08b64a17d0a..9b8f7bcc630 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 @@ -29,19 +29,20 @@ qx.Class.define("osparc.data.model.Folder", { this.base(arguments); this.set({ - id: folderData.id, - parentId: folderData.parentFolder, + folderId: folderData.folderId, + parentId: folderData.parentFolderId, name: folderData.name, description: folderData.description, myAccessRights: folderData.myAccessRights, accessRights: folderData.accessRights, + owner: folderData.owner, createdAt: new Date(folderData.createdAt), - lastModified: new Date(folderData.lastModified), + lastModified: new Date(folderData.modifiedAt), }); }, properties: { - id: { + folderId: { check: "Number", nullable: false, init: null, @@ -83,6 +84,13 @@ qx.Class.define("osparc.data.model.Folder", { event: "changeAccessRights" }, + owner: { + check: "Number", + nullable: true, + init: null, + event: "changeOwner" + }, + createdAt: { check: "Date", nullable: true, @@ -99,8 +107,8 @@ qx.Class.define("osparc.data.model.Folder", { }, statics: { - patchFolder: function(folderId, propKey, value) { - return osparc.store.Folders.getInstance().patchFolder(folderId, propKey, value); + putFolder: function(folderId, propKey, value) { + return osparc.store.Folders.getInstance().putFolder(folderId, propKey, value); }, getProperties: function() { diff --git a/services/static-webserver/client/source/class/osparc/share/CollaboratorsFolder.js b/services/static-webserver/client/source/class/osparc/share/CollaboratorsFolder.js index cc0b7ce936a..d68df6a9dde 100644 --- a/services/static-webserver/client/source/class/osparc/share/CollaboratorsFolder.js +++ b/services/static-webserver/client/source/class/osparc/share/CollaboratorsFolder.js @@ -70,7 +70,7 @@ qx.Class.define("osparc.share.CollaboratorsFolder", { const newCollaborators = {}; gids.forEach(gid => newCollaborators[gid] = this.self().getCollaboratorAccessRight()); - osparc.store.Folders.getInstance().addCollaborators(this.__folder.getId(), newCollaborators) + osparc.store.Folders.getInstance().addCollaborators(this.__folder.getFolderId(), newCollaborators) .then(() => { this.fireDataEvent("updateAccessRights", this.__folder.serialize()); const text = this.tr("User(s) successfully added."); @@ -88,7 +88,7 @@ qx.Class.define("osparc.share.CollaboratorsFolder", { item.setEnabled(false); } - osparc.store.Folders.getInstance().removeCollaborator(this.__folder.getId(), collaborator["gid"]) + osparc.store.Folders.getInstance().removeCollaborator(this.__folder.getFolderId(), collaborator["gid"]) .then(() => { this.fireDataEvent("updateAccessRights", this.__folder.serialize()); osparc.FlashMessenger.getInstance().logAs(this.tr("Member successfully removed")); @@ -108,7 +108,7 @@ qx.Class.define("osparc.share.CollaboratorsFolder", { __make: function(collaboratorGId, newAccessRights, successMsg, failureMsg, item) { item.setEnabled(false); - osparc.store.Folders.getInstance().updateCollaborator(this.__folder.getId(), collaboratorGId, newAccessRights) + osparc.store.Folders.getInstance().updateCollaborator(this.__folder.getFolderId(), collaboratorGId, newAccessRights) .then(() => { this.fireDataEvent("updateAccessRights", this.__folder.serialize()); osparc.FlashMessenger.getInstance().logAs(successMsg); 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 3188e5b8f89..cd942578047 100644 --- a/services/static-webserver/client/source/class/osparc/store/Folders.js +++ b/services/static-webserver/client/source/class/osparc/store/Folders.js @@ -23,197 +23,89 @@ qx.Class.define("osparc.store.Folders", { this.base(arguments); this.foldersCached = []; - - this.fetchFolders(); - }, - - statics: { - FOLDER_DATA_INIT: [{ - id: 1, - parentFolder: null, - name: "Folder 1", - description: "Description Folder One", - owner: 3, - createdAt: "2024-07-11T06:28:28.527Z", - lastModified: "2024-07-13T06:28:28.527Z", - myAccessRights: { - read: true, - write: true, - delete: true - }, - accessRights: { - 3: { - read: true, - write: true, - delete: true - } - }, - }, { - id: 2, - parentFolder: null, - name: "Folder 2", - description: "Description Folder Two", - owner: 3, - createdAt: "2024-07-13T06:28:28.527Z", - lastModified: "2024-07-15T06:28:28.527Z", - myAccessRights: { - read: true, - write: true, - delete: true - }, - accessRights: { - 3: { - read: true, - write: true, - delete: true - }, - 9: { - read: true, - write: true, - delete: false - } - }, - }, { - id: 3, - parentFolder: 1, - name: "Folder 3", - description: "Description Folder Three", - owner: 3, - createdAt: "2024-07-16T06:28:28.527Z", - lastModified: "2024-07-17T06:28:28.527Z", - myAccessRights: { - read: true, - write: true, - delete: true - }, - accessRights: { - 3: { - read: true, - write: true, - delete: true - } - } - }, { - id: 4, - parentFolder: null, - name: "Folder 4", - description: "Description Folder Four", - owner: 3, - createdAt: "2024-07-17T06:28:28.527Z", - lastModified: "2024-07-18T06:28:28.527Z", - myAccessRights: { - read: true, - write: true, - delete: false - }, - accessRights: { - 3: { - read: true, - write: true, - delete: false - }, - 9: { - read: true, - write: true, - delete: true - } - } - }, { - id: 5, - parentFolder: null, - name: "Folder 5", - description: "Description Folder Five", - owner: 3, - createdAt: "2024-07-18T06:28:28.527Z", - lastModified: "2024-07-18T07:28:28.527Z", - myAccessRights: { - read: true, - write: false, - delete: false - }, - accessRights: { - 3: { - read: true, - write: false, - delete: false - }, - 9: { - read: true, - write: true, - delete: true - } - }, - }] }, members: { foldersCached: null, - fetchFolders: function(parentId = null) { - return new Promise(resolve => { - this.self().FOLDER_DATA_INIT.forEach(folderData => { - if (folderData.parentFolder === parentId) { + fetchFolders: function(folderId = null) { + const params = { + "url": { + folderId + } + }; + return osparc.data.Resources.getInstance().getAllPages("folders", params) + .then(foldersData => { + const folders = []; + foldersData.forEach(folderData => { const folder = new osparc.data.model.Folder(folderData); this.__addToCache(folder); - } + folders.push(folder); + }); + return folders; }); - resolve(); - }); }, postFolder: function(name, description, parentId = null) { - return new Promise(resolve => { - const myGroupId = osparc.auth.Data.getInstance().getGroupId(); - const newFolderData = { - id: Math.floor(Math.random() * 1000), - parentFolder: parentId, - name: name, - description: description || "", - owner: myGroupId, - createdAt: new Date().toString(), - lastModified: new Date().toString(), - myAccessRights: { - read: true, - write: true, - delete: true - }, - accessRights: {}, - }; - newFolderData["accessRights"][myGroupId] = { - read: true, - write: true, - delete: true - }; - const newFolder = new osparc.data.model.Folder(newFolderData); - this.__addToCache(newFolder); - resolve(newFolder) - }); + const newFolderData = { + parentFolderId: parentId, + name: name, + description: description || "", + }; + const params = { + data: newFolderData + }; + return osparc.data.Resources.getInstance().fetch("folders", "post", params) + .then(folderData => { + const newFolder = new osparc.data.model.Folder(folderData); + this.__addToCache(newFolder); + return newFolder; + }); }, deleteFolder: function(folderId) { return new Promise((resolve, reject) => { - const idx = this.foldersCached.findIndex(f => f.getId() === folderId); - if (idx > -1) { - this.foldersCached.splice(idx, 1); - resolve(); - } else { - reject(); - } + const params = { + "url": { + folderId + } + }; + osparc.data.Resources.getInstance().fetch("folders", "delete", params) + .then(() => { + if (this.__deleteFromCache(folderId)) { + resolve(); + } else { + reject(); + } + }) + .catch(err => reject(err)); }); }, - patchFolder: function(folderId, propKey, value) { + putFolder: function(folderId, updateData) { return new Promise((resolve, reject) => { - const folder = this.getFolder(folderId); - const upKey = qx.lang.String.firstUp(propKey); - const setter = "set" + upKey; - if (folder && setter in folder) { - folder[setter](value); - folder.setLastModified(new Date()); - resolve(); - } else { - reject(); - } + const params = { + "url": { + folderId + }, + data: updateData + }; + osparc.data.Resources.getInstance().fetch("folders", "update", params) + .then(() => { + const folder = this.getFolder(folderId); + Object.keys(updateData).forEach(propKey => { + const upKey = qx.lang.String.firstUp(propKey); + const setter = "set" + upKey; + if (folder && setter in folder) { + folder[setter](updateData[propKey]); + } + }); + folder.setLastModified(new Date()); + this.__deleteFromCache(folderId); + this.__addToCache(folder); + resolve(); + }) + .catch(err => reject(err)); }); }, @@ -275,14 +167,23 @@ qx.Class.define("osparc.store.Folders", { }, getFolder: function(folderId = null) { - return this.foldersCached.find(f => f.getId() === folderId); + return this.foldersCached.find(f => f.getFolderId() === folderId); }, __addToCache: function(folder) { - const found = this.foldersCached.find(f => f.getId() === folder.getId()); + const found = this.foldersCached.find(f => f.getFolderId() === folder.getFolderId()); if (!found) { - this.foldersCached.push(folder); + this.foldersCached.unshift(folder); + } + }, + + __deleteFromCache: function(folderId) { + const idx = this.foldersCached.findIndex(f => f.getFolderId() === folderId); + if (idx > -1) { + this.foldersCached.splice(idx, 1); + return true; } + return false; } } }); 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 3e211ccfa7c..c6c25606acc 100644 --- a/services/static-webserver/client/source/class/osparc/store/Store.js +++ b/services/static-webserver/client/source/class/osparc/store/Store.js @@ -70,6 +70,10 @@ qx.Class.define("osparc.store.Store", { check: "Array", init: [] }, + folders: { + check: "Array", + init: [] + }, studyComments: { check: "Array", init: [] diff --git a/services/static-webserver/client/source/class/osparc/utils/DisabledPlugins.js b/services/static-webserver/client/source/class/osparc/utils/DisabledPlugins.js index b3e9f88583d..0b250d0d7db 100644 --- a/services/static-webserver/client/source/class/osparc/utils/DisabledPlugins.js +++ b/services/static-webserver/client/source/class/osparc/utils/DisabledPlugins.js @@ -30,6 +30,10 @@ qx.Class.define("osparc.utils.DisabledPlugins", { META_MODELING: "WEBSERVER_META_MODELING", CLUSTERS: "WEBSERVER_CLUSTERS", + isFoldersEnabled: function() { + return osparc.utils.Utils.isDevelopmentPlatform(); + }, + isExportDisabled: function() { return this.__isPluginDisabled(this.EXPORT); },