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 1acb22a5c211..2823929df4d2 100644 --- a/services/static-webserver/client/source/class/osparc/dashboard/GridButtonItem.js +++ b/services/static-webserver/client/source/class/osparc/dashboard/GridButtonItem.js @@ -250,7 +250,8 @@ qx.Class.define("osparc.dashboard.GridButtonItem", { _applyOwner: function(value, old) { const label = this.getChildControl("subtitle-text"); if (osparc.utils.Resources.isFunction(this.getResourceData())) { - const canIWrite = Boolean(this.getResourceData()["accessRights"]["write"]); + // Functions don't have 'owner' + const canIWrite = osparc.data.model.Function.canIWrite(this.getResourceData()["accessRights"]); label.setValue(canIWrite ? "My Function" : "Read Only"); } else { const user = this.__createOwner(value); @@ -262,20 +263,15 @@ qx.Class.define("osparc.dashboard.GridButtonItem", { _applyAccessRights: function(value) { if (value && Object.keys(value).length) { const shareIcon = this.getChildControl("subtitle-icon"); - if (this.isResourceType("function")) { - // in case of functions, the access rights are actually myAccessRights - osparc.dashboard.CardBase.populateMyAccessRightsIcon(shareIcon, value); - } else { - shareIcon.addListener("tap", e => { - e.stopPropagation(); - this.openAccessRights(); - }, this); - shareIcon.addListener("pointerdown", e => e.stopPropagation()); - osparc.dashboard.CardBase.populateShareIcon(shareIcon, value); + shareIcon.addListener("tap", e => { + e.stopPropagation(); + this.openAccessRights(); + }, this); + shareIcon.addListener("pointerdown", e => e.stopPropagation()); + osparc.dashboard.CardBase.populateShareIcon(shareIcon, value); - if (this.isResourceType("study")) { - this._setStudyPermissions(value); - } + if (this.isResourceType("study")) { + this._setStudyPermissions(value); } } }, 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 2768b1cf5894..9c13fb21f357 100644 --- a/services/static-webserver/client/source/class/osparc/dashboard/ListButtonItem.js +++ b/services/static-webserver/client/source/class/osparc/dashboard/ListButtonItem.js @@ -244,7 +244,8 @@ qx.Class.define("osparc.dashboard.ListButtonItem", { _applyOwner: function(value, old) { const label = this.getChildControl("owner"); if (osparc.utils.Resources.isFunction(this.getResourceData())) { - const canIWrite = Boolean(this.getResourceData()["accessRights"]["write"]); + // Functions don't have 'owner' + const canIWrite = osparc.data.model.Function.canIWrite(this.getResourceData()["accessRights"]); label.setValue(canIWrite ? "My Function" : "Read Only"); } else { const user = this.__createOwner(value); @@ -257,20 +258,15 @@ qx.Class.define("osparc.dashboard.ListButtonItem", { _applyAccessRights: function(value) { if (value && Object.keys(value).length) { const shareIcon = this.getChildControl("shared-icon"); - if (this.isResourceType("function")) { - // in case of functions, the access rights are actually myAccessRights - osparc.dashboard.CardBase.populateMyAccessRightsIcon(shareIcon, value); - } else { - shareIcon.addListener("tap", e => { - e.stopPropagation(); - this.openAccessRights(); - }, this); - shareIcon.addListener("pointerdown", e => e.stopPropagation()); - osparc.dashboard.CardBase.populateShareIcon(shareIcon, value); + shareIcon.addListener("tap", e => { + e.stopPropagation(); + this.openAccessRights(); + }, this); + shareIcon.addListener("pointerdown", e => e.stopPropagation()); + osparc.dashboard.CardBase.populateShareIcon(shareIcon, value); - if (this.isResourceType("study")) { - this._setStudyPermissions(value); - } + if (this.isResourceType("study")) { + this._setStudyPermissions(value); } } }, 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 6af8a37004fc..a331425a5557 100644 --- a/services/static-webserver/client/source/class/osparc/dashboard/ResourceContainerManager.js +++ b/services/static-webserver/client/source/class/osparc/dashboard/ResourceContainerManager.js @@ -163,6 +163,7 @@ qx.Class.define("osparc.dashboard.ResourceContainerManager", { text = this.tr("No Public Projects found"); break; case osparc.dashboard.StudyBrowser.CONTEXT.FUNCTIONS: + case osparc.dashboard.StudyBrowser.CONTEXT.SEARCH_FUNCTIONS: text = this.tr("No Functions found"); break; } diff --git a/services/static-webserver/client/source/class/osparc/dashboard/ResourceDetails.js b/services/static-webserver/client/source/class/osparc/dashboard/ResourceDetails.js index acc86c34cddf..9014de6bca74 100644 --- a/services/static-webserver/client/source/class/osparc/dashboard/ResourceDetails.js +++ b/services/static-webserver/client/source/class/osparc/dashboard/ResourceDetails.js @@ -409,6 +409,7 @@ qx.Class.define("osparc.dashboard.ResourceDetails", { return; } else if (osparc.utils.Resources.isFunction(this.__resourceData)) { this.__addInfoPage(); + this.__addPermissionsPage(); if (this.__resourceModel.getFunctionClass() === osparc.data.model.Function.FUNCTION_CLASS.PROJECT) { this.__addPreviewPage(); } @@ -641,6 +642,12 @@ qx.Class.define("osparc.dashboard.ResourceDetails", { const updatedData = e.getData(); this.__fireUpdateEvent(resourceData, updatedData); }, this); + } else if (osparc.utils.Resources.isFunction(resourceData)) { + collaboratorsView = new osparc.share.CollaboratorsFunction(resourceData); + collaboratorsView.addListener("updateAccessRights", e => { + const updatedData = e.getData(); + this.__fireUpdateEvent(resourceData, updatedData); + }, this); } else { collaboratorsView = new osparc.share.CollaboratorsStudy(resourceData); if (osparc.utils.Resources.isStudy(resourceData)) { diff --git a/services/static-webserver/client/source/class/osparc/dashboard/SearchBarFilterExtended.js b/services/static-webserver/client/source/class/osparc/dashboard/SearchBarFilterExtended.js index 6a1723e3d83f..2a7270f6ff77 100644 --- a/services/static-webserver/client/source/class/osparc/dashboard/SearchBarFilterExtended.js +++ b/services/static-webserver/client/source/class/osparc/dashboard/SearchBarFilterExtended.js @@ -53,6 +53,7 @@ qx.Class.define("osparc.dashboard.SearchBarFilterExtended", { "searchProjects", // osparc.dashboard.StudyBrowser.CONTEXT.SEARCH_PROJECTS, "searchTemplates", // osparc.dashboard.StudyBrowser.CONTEXT.SEARCH_TEMPLATES, "searchPublicTemplates", // osparc.dashboard.StudyBrowser.CONTEXT.SEARCH_PUBLIC_TEMPLATES, + "searchFunctions" // osparc.dashboard.StudyBrowser.CONTEXT.SEARCH_FUNCTIONS ], init: null, nullable: false, @@ -62,24 +63,17 @@ qx.Class.define("osparc.dashboard.SearchBarFilterExtended", { }, statics: { - createToolbarRadioButton: function(label, icon, toolTipText = null, pos = null) { - const rButton = new qx.ui.toolbar.RadioButton().set({ - label, - icon, - toolTipText, - padding: 8, + decorateListItem: function(listItem) { + listItem.set({ gap: 8, - margin: 0, + backgroundColor: osparc.dashboard.SearchBarFilter.BG_COLOR, }); - rButton.getContentElement().setStyles({ - "border-radius": "0px" - }); - if (pos === "left") { - osparc.utils.Utils.addBorderLeftRadius(rButton); - } else if (pos === "right") { - osparc.utils.Utils.addBorderRightRadius(rButton); - } - return rButton; + }, + + createListItem: function(label, icon, model) { + const listItem = new qx.ui.form.ListItem(label, icon, model); + this.self().decorateListItem(listItem); + return listItem; }, }, @@ -90,48 +84,74 @@ qx.Class.define("osparc.dashboard.SearchBarFilterExtended", { let control; switch (id) { case "search-bar-filter": { - control = new osparc.dashboard.SearchBarFilter(this.__resourceType); + control = new osparc.dashboard.SearchBarFilter(this.__resourceType).set({ + showFilterMenu: false, + }); const textField = control.getChildControl("text-field"); textField.addListener("appear", () => { textField.focus(); textField.activate(); }); + const resetButton = control.getChildControl("reset-button"); + resetButton.set({ + paddingRight: 2, // 10-8 + opacity: 0.7, + backgroundColor: "transparent", + }); + osparc.utils.Utils.hideBorder(resetButton); this._add(control); break; } - case "context-buttons": - control = new qx.ui.toolbar.ToolBar().set({ - spacing: 0, - padding: 0, - backgroundColor: osparc.dashboard.SearchBarFilter.BG_COLOR, + case "context-drop-down": { + control = new qx.ui.form.SelectBox().set({ + minWidth: 150, }); - this._add(control); + control.getChildControl("arrow").syncAppearance(); // force sync to show the arrow + this.self().decorateListItem(control.getChildControl("atom")); + const searchBarFilter = this.getChildControl("search-bar-filter"); + searchBarFilter._addAt(control, 3); //"search-icon", "active-filters", "text-field", "reset-button" break; - case "my-projects-button": - control = this.self().createToolbarRadioButton( + } + case "my-projects-button": { + control = this.self().createListItem( this.tr("My Projects"), "@FontAwesome5Solid/file/14", - null, - "left", + "myProjects" ); - this.getChildControl("context-buttons").add(control); + const contextDropDown = this.getChildControl("context-drop-down"); + contextDropDown.add(control); break; - case "templates-button": - control = this.self().createToolbarRadioButton( + } + case "templates-button": { + control = this.self().createListItem( this.tr("Templates"), "@FontAwesome5Solid/copy/14", + "templates" ); - this.getChildControl("context-buttons").add(control); + const contextDropDown = this.getChildControl("context-drop-down"); + contextDropDown.add(control); break; - case "public-projects-button": - control = this.self().createToolbarRadioButton( + } + case "public-projects-button": { + control = this.self().createListItem( this.tr("Public Projects"), "@FontAwesome5Solid/globe/14", - null, - "right", + "publicProjects" ); - this.getChildControl("context-buttons").add(control); + const contextDropDown = this.getChildControl("context-drop-down"); + contextDropDown.add(control); break; + } + case "functions-button": { + control = this.self().createListItem( + this.tr("Functions"), + "@MaterialIcons/functions/18", + "functions" + ); + const contextDropDown = this.getChildControl("context-drop-down"); + contextDropDown.add(control); + break; + } case "filter-buttons": control = new qx.ui.toolbar.ToolBar().set({ backgroundColor: osparc.dashboard.SearchBarFilter.BG_COLOR, @@ -153,30 +173,37 @@ qx.Class.define("osparc.dashboard.SearchBarFilterExtended", { }, __buildLayout: function() { - this.getChildControl("search-bar-filter").set({ - showFilterMenu: false, - }); - - const resetButton = this.getChildControl("search-bar-filter").getChildControl("reset-button"); - resetButton.set({ - paddingRight: 2, // 10-8 - opacity: 0.7, - backgroundColor: "transparent", + const searchBarFilter = this.getChildControl("search-bar-filter"); + + const contextDropDown = this.getChildControl("context-drop-down"); + this.getChildControl("my-projects-button"); + this.getChildControl("templates-button"); + this.getChildControl("public-projects-button"); + this.getChildControl("functions-button"); + contextDropDown.addListener("changeSelection", e => { + const selection = e.getData(); + if (selection.length) { + const selectedContext = selection[0].getModel(); + switch (selectedContext) { + case "myProjects": + this.setCurrentContext(osparc.dashboard.StudyBrowser.CONTEXT.SEARCH_PROJECTS); + break; + case "templates": + this.setCurrentContext(osparc.dashboard.StudyBrowser.CONTEXT.SEARCH_TEMPLATES); + break; + case "publicProjects": + this.setCurrentContext(osparc.dashboard.StudyBrowser.CONTEXT.SEARCH_PUBLIC_TEMPLATES); + break; + case "functions": + this.setCurrentContext(osparc.dashboard.StudyBrowser.CONTEXT.SEARCH_FUNCTIONS); + break; + } + } }); - osparc.utils.Utils.hideBorder(resetButton); - - const radioGroup = new qx.ui.form.RadioGroup(); - const myProjectsButton = this.getChildControl("my-projects-button"); - const templatesButton = this.getChildControl("templates-button"); - const publicProjectsButton = this.getChildControl("public-projects-button"); - radioGroup.add(myProjectsButton, templatesButton, publicProjectsButton); - myProjectsButton.addListener("changeValue", e => e.getData() ? this.setCurrentContext(osparc.dashboard.StudyBrowser.CONTEXT.SEARCH_PROJECTS) : null, this); - templatesButton.addListener("changeValue", e => e.getData() ? this.setCurrentContext(osparc.dashboard.StudyBrowser.CONTEXT.SEARCH_TEMPLATES) : null, this); - publicProjectsButton.addListener("changeValue", e => e.getData() ? this.setCurrentContext(osparc.dashboard.StudyBrowser.CONTEXT.SEARCH_PUBLIC_TEMPLATES) : null, this); // Set initial state based on the provided initFilterData - const activeFilters = this.getChildControl("search-bar-filter").getChildControl("active-filters"); - const textField = this.getChildControl("search-bar-filter").getChildControl("text-field"); + const activeFilters = searchBarFilter.getChildControl("active-filters"); + const textField = searchBarFilter.getChildControl("text-field"); if ("sharedWith" in this.__initFilterData && this.__initFilterData["sharedWith"]) { const sharedWithOptions = osparc.dashboard.SearchBarFilter.getSharedWithOptions(this.__resourceType); const optionsFound = sharedWithOptions.find(option => option.id === this.__initFilterData["sharedWith"]); @@ -209,6 +236,7 @@ qx.Class.define("osparc.dashboard.SearchBarFilterExtended", { this.__filter("text", textField.getValue()); }, this); + const resetButton = searchBarFilter.getChildControl("reset-button"); resetButton.addListener("tap", () => { this.fireEvent("resetButtonPressed"); this.exclude(); @@ -219,24 +247,34 @@ qx.Class.define("osparc.dashboard.SearchBarFilterExtended", { if (value === old) { return; } + const contextDropDown = this.getChildControl("context-drop-down"); + const searchBarFilter = this.getChildControl("search-bar-filter"); + const sharedWithButton = this.getChildControl("shared-with-button"); + const tagsButton = this.getChildControl("tags-button"); switch (value) { case osparc.dashboard.StudyBrowser.CONTEXT.SEARCH_PROJECTS: - this.getChildControl("my-projects-button").setValue(true); - this.getChildControl("search-bar-filter").getChildControl("text-field").setPlaceholder(this.tr("Search in My projects")); - this.getChildControl("shared-with-button").setVisibility("visible"); - this.getChildControl("tags-button").setVisibility("visible"); + contextDropDown.setSelection([this.getChildControl("my-projects-button")]); + searchBarFilter.getChildControl("text-field").setPlaceholder(this.tr("Search in My projects")); + sharedWithButton.setVisibility("visible"); + tagsButton.setVisibility("visible"); break; case osparc.dashboard.StudyBrowser.CONTEXT.SEARCH_TEMPLATES: - this.getChildControl("templates-button").setValue(true); - this.getChildControl("search-bar-filter").getChildControl("text-field").setPlaceholder(this.tr("Search in Templates")); - this.getChildControl("shared-with-button").setVisibility("excluded"); - this.getChildControl("tags-button").setVisibility("visible"); + contextDropDown.setSelection([this.getChildControl("templates-button")]); + searchBarFilter.getChildControl("text-field").setPlaceholder(this.tr("Search in Templates")); + sharedWithButton.setVisibility("excluded"); + tagsButton.setVisibility("visible"); break; case osparc.dashboard.StudyBrowser.CONTEXT.SEARCH_PUBLIC_TEMPLATES: - this.getChildControl("public-projects-button").setValue(true); - this.getChildControl("search-bar-filter").getChildControl("text-field").setPlaceholder(this.tr("Search in Public projects")); - this.getChildControl("shared-with-button").setVisibility("excluded"); - this.getChildControl("tags-button").setVisibility("visible"); + contextDropDown.setSelection([this.getChildControl("public-projects-button")]); + searchBarFilter.getChildControl("text-field").setPlaceholder(this.tr("Search in Public Projects")); + sharedWithButton.setVisibility("excluded"); + tagsButton.setVisibility("visible"); + break; + case osparc.dashboard.StudyBrowser.CONTEXT.SEARCH_FUNCTIONS: + contextDropDown.setSelection([this.getChildControl("functions-button")]); + searchBarFilter.getChildControl("text-field").setPlaceholder(this.tr("Search in Functions")); + sharedWithButton.setVisibility("excluded"); + tagsButton.setVisibility("excluded"); break; } }, @@ -291,6 +329,12 @@ qx.Class.define("osparc.dashboard.SearchBarFilterExtended", { this.__sharedWithMenu, this.__tagsMenu, ]; + // handle clicks on the drop down menu that might go out of bounds + const contextDropDown = this.getChildControl("context-drop-down"); + const popup = contextDropDown.getChildControl("popup"); + if (popup.isVisible()) { + excludeElements.push(popup); + } for (let i = 0; i < excludeElements.length; i++) { if (excludeElements[i] && osparc.utils.Utils.isMouseOnElement(excludeElements[i], e)) { return; 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 4860010ad3e0..766e0d11bf50 100644 --- a/services/static-webserver/client/source/class/osparc/dashboard/StudyBrowser.js +++ b/services/static-webserver/client/source/class/osparc/dashboard/StudyBrowser.js @@ -58,6 +58,7 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { SEARCH_PROJECTS: "searchProjects", SEARCH_TEMPLATES: "searchTemplates", SEARCH_PUBLIC_TEMPLATES: "searchPublicTemplates", + SEARCH_FUNCTIONS: "searchFunctions", } }, @@ -73,6 +74,7 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { "searchProjects", // osparc.dashboard.StudyBrowser.CONTEXT.SEARCH_PROJECTS, "searchTemplates", // osparc.dashboard.StudyBrowser.CONTEXT.SEARCH_TEMPLATES, "searchPublicTemplates", // osparc.dashboard.StudyBrowser.CONTEXT.SEARCH_PUBLIC_TEMPLATES, + "searchFunctions", // osparc.dashboard.StudyBrowser.CONTEXT.SEARCH_FUNCTIONS, ], nullable: false, init: "studiesAndFolders", @@ -336,7 +338,8 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { this.__addResourcesToList(filteredTemplates); break; } - case osparc.dashboard.StudyBrowser.CONTEXT.FUNCTIONS: { + case osparc.dashboard.StudyBrowser.CONTEXT.FUNCTIONS: + case osparc.dashboard.StudyBrowser.CONTEXT.SEARCH_FUNCTIONS: { const functions = resp["data"]; functions.forEach(func => func["resourceType"] = "function"); this.__addResourcesToList(functions); @@ -886,7 +889,8 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { requestParams.accessRights = "public"; break; case osparc.dashboard.StudyBrowser.CONTEXT.FUNCTIONS: - delete requestParams.orderBy; // functions are not ordered yet + case osparc.dashboard.StudyBrowser.CONTEXT.SEARCH_FUNCTIONS: + delete requestParams.orderBy; // functions do not support ordering yet requestParams.includeExtras = "true"; break; case osparc.dashboard.StudyBrowser.CONTEXT.SEARCH_PROJECTS: { @@ -951,6 +955,10 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { break; case osparc.dashboard.StudyBrowser.CONTEXT.FUNCTIONS: request = osparc.store.Functions.fetchFunctionsPaginated(params, options); + break; + case osparc.dashboard.StudyBrowser.CONTEXT.SEARCH_FUNCTIONS: + request = osparc.store.Functions.searchFunctionsPaginated(params, options); + break; } return request; }, @@ -1185,6 +1193,10 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { case osparc.dashboard.StudyBrowser.CONTEXT.SEARCH_PUBLIC_TEMPLATES: curatedContext = osparc.dashboard.StudyBrowser.CONTEXT.SEARCH_PUBLIC_TEMPLATES; break; + case osparc.dashboard.StudyBrowser.CONTEXT.FUNCTIONS: + case osparc.dashboard.StudyBrowser.CONTEXT.SEARCH_FUNCTIONS: + curatedContext = osparc.dashboard.StudyBrowser.CONTEXT.SEARCH_FUNCTIONS; + break; default: curatedContext = osparc.dashboard.StudyBrowser.CONTEXT.SEARCH_PROJECTS; break; @@ -1214,6 +1226,7 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { osparc.dashboard.StudyBrowser.CONTEXT.SEARCH_PROJECTS, osparc.dashboard.StudyBrowser.CONTEXT.SEARCH_TEMPLATES, osparc.dashboard.StudyBrowser.CONTEXT.SEARCH_PUBLIC_TEMPLATES, + osparc.dashboard.StudyBrowser.CONTEXT.SEARCH_FUNCTIONS, ].includes(searchContext)) { this._changeContext(searchContext); } @@ -1254,11 +1267,12 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { __filterChanged: function(filterData) { let searchContext = null; let backToContext = null; + const isSearchContext = filterData && filterData.text; switch (this.getCurrentContext()) { case osparc.dashboard.StudyBrowser.CONTEXT.PROJECTS: case osparc.dashboard.StudyBrowser.CONTEXT.SEARCH_PROJECTS: case osparc.dashboard.StudyBrowser.CONTEXT.TRASH: - if (filterData && filterData.text) { + if (isSearchContext) { searchContext = osparc.dashboard.StudyBrowser.CONTEXT.SEARCH_PROJECTS; } else { backToContext = osparc.dashboard.StudyBrowser.CONTEXT.PROJECTS; @@ -1266,7 +1280,7 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { break; case osparc.dashboard.StudyBrowser.CONTEXT.TEMPLATES: case osparc.dashboard.StudyBrowser.CONTEXT.SEARCH_TEMPLATES: - if (filterData && filterData.text) { + if (isSearchContext) { searchContext = osparc.dashboard.StudyBrowser.CONTEXT.SEARCH_TEMPLATES; } else { backToContext = osparc.dashboard.StudyBrowser.CONTEXT.TEMPLATES; @@ -1274,19 +1288,22 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { break; case osparc.dashboard.StudyBrowser.CONTEXT.PUBLIC_TEMPLATES: case osparc.dashboard.StudyBrowser.CONTEXT.SEARCH_PUBLIC_TEMPLATES: - if (filterData && filterData.text) { + if (isSearchContext) { searchContext = osparc.dashboard.StudyBrowser.CONTEXT.SEARCH_PUBLIC_TEMPLATES; } else { backToContext = osparc.dashboard.StudyBrowser.CONTEXT.PUBLIC_TEMPLATES; } break; case osparc.dashboard.StudyBrowser.CONTEXT.FUNCTIONS: - // functions are not searchable yet - searchContext = null; - backToContext = osparc.dashboard.StudyBrowser.CONTEXT.FUNCTIONS; + case osparc.dashboard.StudyBrowser.CONTEXT.SEARCH_FUNCTIONS: + if (isSearchContext) { + searchContext = osparc.dashboard.StudyBrowser.CONTEXT.SEARCH_FUNCTIONS; + } else { + backToContext = osparc.dashboard.StudyBrowser.CONTEXT.FUNCTIONS; + } break; default: - if (filterData && filterData.text) { + if (isSearchContext) { searchContext = osparc.dashboard.StudyBrowser.CONTEXT.SEARCH_PROJECTS; } else { backToContext = osparc.dashboard.StudyBrowser.CONTEXT.PROJECTS; @@ -1391,10 +1408,11 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { this.__reloadStudies(); break; case osparc.dashboard.StudyBrowser.CONTEXT.FUNCTIONS: - this._searchBarFilter.resetFilters(); + case osparc.dashboard.StudyBrowser.CONTEXT.SEARCH_FUNCTIONS: + if (this.getCurrentContext() === osparc.dashboard.StudyBrowser.CONTEXT.FUNCTIONS) { + this._searchBarFilter.resetFilters(); + } this._searchBarFilter.getChildControl("text-field").setPlaceholder("Search in Functions"); - // functions can't be searched yet - this._searchBarFilter.setEnabled(false); // functions can't be sorted yet this.__sortByButton.exclude(); this._loadingResourcesBtn.setFetching(false); 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 db4e9c176c45..558d6e43fec0 100644 --- a/services/static-webserver/client/source/class/osparc/dashboard/StudyBrowserHeader.js +++ b/services/static-webserver/client/source/class/osparc/dashboard/StudyBrowserHeader.js @@ -334,6 +334,9 @@ qx.Class.define("osparc.dashboard.StudyBrowserHeader", { this.__setIcon("@FontAwesome5Solid/search/24"); title.setValue(this.tr("Public Projects results")); break; + case osparc.dashboard.StudyBrowser.CONTEXT.SEARCH_FUNCTIONS: + this.__setIcon("@FontAwesome5Solid/search/24"); + title.setValue(this.tr("Functions results")); } }, 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 32227dec8109..8e7e8014e75c 100644 --- a/services/static-webserver/client/source/class/osparc/data/Resources.js +++ b/services/static-webserver/client/source/class/osparc/data/Resources.js @@ -636,6 +636,10 @@ qx.Class.define("osparc.data.Resources", { method: "GET", url: statics.API + "/functions?include_extras=true&offset={offset}&limit={limit}" }, + getPageSearch: { + method: "GET", + url: statics.API + "/functions?include_extras=true&offset={offset}&limit={limit}&search={text}" + }, create: { method: "POST", url: statics.API + "/functions" @@ -644,6 +648,16 @@ qx.Class.define("osparc.data.Resources", { method: "PATCH", url: statics.API + "/functions/{functionId}?include_extras=true" }, + putAccessRights: { + useCache: false, + method: "PUT", + url: statics.API + "/functions/{functionId}/groups/{gId}" + }, + deleteAccessRights: { + useCache: false, + method: "DELETE", + url: statics.API + "/functions/{functionId}/groups/{gId}" + }, } }, /* diff --git a/services/static-webserver/client/source/class/osparc/data/Roles.js b/services/static-webserver/client/source/class/osparc/data/Roles.js index 9b066b35b739..aacb89f0bea1 100644 --- a/services/static-webserver/client/source/class/osparc/data/Roles.js +++ b/services/static-webserver/client/source/class/osparc/data/Roles.js @@ -120,6 +120,35 @@ qx.Class.define("osparc.data.Roles", { }, } }, + FUNCTION: { + "read": { + id: "read", + label: qx.locale.Manager.tr("User"), + longLabel: qx.locale.Manager.tr("User: Read access"), + canDo: [ + qx.locale.Manager.tr("- Can use it") + ], + accessRights: { + "execute": true, + "read": true, + "write": false + }, + }, + "write": { + id: "write", + label: qx.locale.Manager.tr("Owner"), + longLabel: qx.locale.Manager.tr("Owner: Read/Write access"), + canDo: [ + qx.locale.Manager.tr("- Can make changes"), + qx.locale.Manager.tr("- Can share it") + ], + accessRights: { + "execute": true, + "read": true, + "write": true + }, + }, + }, SERVICES: { "read": { id: "read", @@ -269,6 +298,10 @@ qx.Class.define("osparc.data.Roles", { return this.__createRolesLayout(osparc.data.Roles.STUDY); }, + createRolesFunctionInfo: function() { + return this.__createRolesLayout(osparc.data.Roles.FUNCTION); + }, + createRolesServicesInfo: function() { return this.__createRolesLayout(osparc.data.Roles.SERVICES); }, diff --git a/services/static-webserver/client/source/class/osparc/data/model/Function.js b/services/static-webserver/client/source/class/osparc/data/model/Function.js index fa1028ae4a0c..3b0e6c1fd24a 100644 --- a/services/static-webserver/client/source/class/osparc/data/model/Function.js +++ b/services/static-webserver/client/source/class/osparc/data/model/Function.js @@ -155,6 +155,16 @@ qx.Class.define("osparc.data.model.Function", { getProperties: function() { return Object.keys(qx.util.PropertyUtil.getProperties(osparc.data.model.Function)); }, + + canIWrite: function(accessRights) { + const groupsStore = osparc.store.Groups.getInstance(); + const orgIDs = groupsStore.getOrganizationIds(); + orgIDs.push(groupsStore.getMyGroupId()); + if (orgIDs.length) { + return osparc.share.CollaboratorsFunction.canGroupsWrite(accessRights, (orgIDs)); + } + return false; + }, }, members: { @@ -171,7 +181,8 @@ qx.Class.define("osparc.data.model.Function", { }, canIWrite: function() { - return Boolean(this.getAccessRights()["write"]); + const accessRights = this.getAccessRights(); + return this.self().canIWrite(accessRights); }, patchFunction: function(functionChanges) { diff --git a/services/static-webserver/client/source/class/osparc/desktop/WorkbenchView.js b/services/static-webserver/client/source/class/osparc/desktop/WorkbenchView.js index 645aed83fbf8..a322c652c3ba 100644 --- a/services/static-webserver/client/source/class/osparc/desktop/WorkbenchView.js +++ b/services/static-webserver/client/source/class/osparc/desktop/WorkbenchView.js @@ -61,7 +61,11 @@ qx.Class.define("osparc.desktop.WorkbenchView", { }, __handleIframeStateChange: function(node, iframeLayout) { - iframeLayout.removeAll(); + if (iframeLayout.classname === "osparc.viewer.NodeViewer") { + iframeLayout._removeAll(); + } else { + iframeLayout.removeAll(); + } if (node && node.getIFrame()) { const iFrame = node.getIFrame(); const src = iFrame.getSource(); @@ -71,9 +75,15 @@ qx.Class.define("osparc.desktop.WorkbenchView", { } else if (src === null || src === "about:blank") { showPage = node.getLoadingPage(); } - iframeLayout.add(showPage, { - flex: 1 - }); + if (iframeLayout.classname === "osparc.viewer.NodeViewer") { + iframeLayout._add(showPage, { + flex: 1 + }); + } else { + iframeLayout.add(showPage, { + flex: 1 + }); + } } }, diff --git a/services/static-webserver/client/source/class/osparc/desktop/credits/CreditsIndicatorButton.js b/services/static-webserver/client/source/class/osparc/desktop/credits/CreditsIndicatorButton.js index 63d83ecd3886..c5e88e283ee7 100644 --- a/services/static-webserver/client/source/class/osparc/desktop/credits/CreditsIndicatorButton.js +++ b/services/static-webserver/client/source/class/osparc/desktop/credits/CreditsIndicatorButton.js @@ -70,9 +70,8 @@ qx.Class.define("osparc.desktop.credits.CreditsIndicatorButton", { }, __handleOutsideEvent: function(event) { - const offset = 0; - const onContainer = osparc.utils.Utils.isMouseOnElement(this.__creditsContainer, event, offset); - const onButton = osparc.utils.Utils.isMouseOnElement(this, event, offset); + const onContainer = osparc.utils.Utils.isMouseOnElement(this.__creditsContainer, event); + const onButton = osparc.utils.Utils.isMouseOnElement(this, event); if (!onContainer && !onButton) { this.__hideCreditsContainer(); } diff --git a/services/static-webserver/client/source/class/osparc/share/Collaborators.js b/services/static-webserver/client/source/class/osparc/share/Collaborators.js index 3a15baabd6c4..4cd75bc5b54d 100644 --- a/services/static-webserver/client/source/class/osparc/share/Collaborators.js +++ b/services/static-webserver/client/source/class/osparc/share/Collaborators.js @@ -185,7 +185,7 @@ qx.Class.define("osparc.share.Collaborators", { }, __canIShare: function() { - if (this._resourceType === "study" && this._serializedDataCopy["workspaceId"]) { + if (this._serializedDataCopy["workspaceId"] && this._resourceType === "study") { // Access Rights are set at workspace level return false; } @@ -198,6 +198,9 @@ qx.Class.define("osparc.share.Collaborators", { case "hypertool": canIShare = osparc.data.model.Study.canIWrite(this._serializedDataCopy["accessRights"]); break; + case "function": + canIShare = osparc.data.model.Function.canIWrite(this._serializedDataCopy["accessRights"]); + break; case "service": canIShare = osparc.service.Utils.canIWrite(this._serializedDataCopy["accessRights"]); break; @@ -224,6 +227,9 @@ qx.Class.define("osparc.share.Collaborators", { case "hypertool": fullOptions = osparc.data.model.Study.canIDelete(this._serializedDataCopy["accessRights"]); break; + case "function": + fullOptions = osparc.data.model.Function.canIWrite(this._serializedDataCopy["accessRights"]); + break; case "service": fullOptions = osparc.service.Utils.canIWrite(this._serializedDataCopy["accessRights"]); break; @@ -244,17 +250,18 @@ qx.Class.define("osparc.share.Collaborators", { case "template": case "tutorial": case "hypertool": + case "tag": rolesLayout = osparc.data.Roles.createRolesStudyInfo(); break; + case "function": + rolesLayout = osparc.data.Roles.createRolesFunctionInfo(); + break; case "service": rolesLayout = osparc.data.Roles.createRolesServicesInfo(); break; case "workspace": rolesLayout = osparc.data.Roles.createRolesWorkspaceInfo(); break; - case "tag": - rolesLayout = osparc.data.Roles.createRolesStudyInfo(); - break; } return rolesLayout; }, @@ -352,7 +359,12 @@ qx.Class.define("osparc.share.Collaborators", { item.addListener("removeMember", e => { const orgMember = e.getData(); if ( - ["study", "template", "tutorial", "hypertool"].includes(this._resourceType) && + [ + "study", + "template", + "tutorial", + "hypertool", + ].includes(this._resourceType) && !osparc.share.CollaboratorsStudy.canCollaboratorBeRemoved(this._serializedDataCopy, orgMember["gid"]) ) { let msg = this.tr("Collaborator can't be removed:"); @@ -380,7 +392,12 @@ qx.Class.define("osparc.share.Collaborators", { __getLeaveStudyButton: function() { const myGid = osparc.auth.Data.getInstance().getGroupId(); if ( - ["study", "template", "tutorial", "hypertool"].includes(this._resourceType) && + [ + "study", + "template", + "tutorial", + "hypertool", + ].includes(this._resourceType) && osparc.share.CollaboratorsStudy.canCollaboratorBeRemoved(this._serializedDataCopy, myGid) ) { const leaveText = this.tr("Leave") + " " + osparc.product.Utils.getStudyAlias({ diff --git a/services/static-webserver/client/source/class/osparc/share/CollaboratorsFunction.js b/services/static-webserver/client/source/class/osparc/share/CollaboratorsFunction.js new file mode 100644 index 000000000000..fe6a1222923f --- /dev/null +++ b/services/static-webserver/client/source/class/osparc/share/CollaboratorsFunction.js @@ -0,0 +1,158 @@ +/* ************************************************************************ + + osparc - the simcore frontend + + https://osparc.io + + Copyright: + 2025 IT'IS Foundation, https://itis.swiss + + License: + MIT: https://opensource.org/licenses/MIT + + Authors: + * Odei Maiz (odeimaiz) + +************************************************************************ */ + + +qx.Class.define("osparc.share.CollaboratorsFunction", { + extend: osparc.share.Collaborators, + + /** + * @param functionData {Object} Object containing the serialized function Data + */ + construct: function(functionData) { + this._resourceType = "function"; + const functionDataCopy = osparc.utils.Utils.deepCloneObject(functionData); + + this.base(arguments, functionDataCopy); + }, + + statics: { + canGroupsWrite: function(accessRights, gIds) { + let canWrite = false; + for (let i=0; i { + newCollaborators[gid] = newAccessRights; + }); + osparc.store.Functions.addCollaborators(this._serializedDataCopy, newCollaborators) + .then(() => { + const text = resourceAlias + this.tr(" successfully shared"); + osparc.FlashMessenger.logAs(text); + this.fireDataEvent("updateAccessRights", this._serializedDataCopy); + this._reloadCollaboratorsList(); + }) + .catch(err => osparc.FlashMessenger.logError(err, this.tr("Something went wrong while sharing the ") + resourceAlias)); + }, + + _deleteMember: function(collaborator, item) { + if (item) { + item.setEnabled(false); + } + + return osparc.store.Functions.removeCollaborator(this._serializedDataCopy, collaborator["gid"]) + .then(() => { + this.fireDataEvent("updateAccessRights", this._serializedDataCopy); + osparc.FlashMessenger.logAs(collaborator["name"] + this.tr(" successfully removed")); + this._reloadCollaboratorsList(); + }) + .catch(err => osparc.FlashMessenger.logError(err, this.tr("Something went wrong while removing ") + collaborator["name"])) + .finally(() => { + if (item) { + item.setEnabled(true); + } + }); + }, + + __make: function(collaboratorGId, newAccessRights, successMsg, failureMsg, item) { + item.setEnabled(false); + + osparc.store.Functions.updateCollaborator(this._serializedDataCopy, collaboratorGId, newAccessRights) + .then(() => { + this.fireDataEvent("updateAccessRights", this._serializedDataCopy); + osparc.FlashMessenger.logAs(successMsg); + this._reloadCollaboratorsList(); + }) + .catch(err => osparc.FlashMessenger.logError(err, failureMsg)) + .finally(() => { + if (item) { + item.setEnabled(true); + } + }); + }, + + _promoteToEditor: function(collaborator, item) { + const writeAccessRole = osparc.data.Roles.FUNCTION["write"]; + this.__make( + collaborator["gid"], + writeAccessRole.accessRights, + this.tr(`Successfully promoted to ${writeAccessRole.label}`), + this.tr(`Something went wrong while promoting to ${writeAccessRole.label}`), + item + ); + }, + + _promoteToOwner: function(collaborator, item) { + osparc.FlashMessenger.logAs(this.tr("Operation not available"), "WARNING"); + }, + + _demoteToUser: async function(collaborator, item) { + const readAccessRole = osparc.data.Roles.FUNCTION["read"]; + const groupId = collaborator["gid"]; + const demoteToUser = (gid, itm) => { + this.__make( + gid, + readAccessRole.accessRights, + this.tr(`Successfully demoted to ${readAccessRole.label}`), + this.tr(`Something went wrong while demoting to ${readAccessRole.label}`), + itm + ); + }; + + const organization = osparc.store.Groups.getInstance().getOrganization(groupId); + if (organization) { + const msg = this.tr(`Demoting to ${readAccessRole.label} will remove write access to all the members of the Organization. Are you sure?`); + const win = new osparc.ui.window.Confirmation(msg).set({ + caption: this.tr("Demote"), + confirmAction: "delete", + confirmText: this.tr("Yes") + }); + win.center(); + win.open(); + win.addListener("close", () => { + if (win.getConfirmed()) { + demoteToUser(groupId, item); + } + }, this); + } else { + demoteToUser(groupId, item); + } + }, + + _demoteToEditor: function(collaborator, item) { + osparc.FlashMessenger.logAs(this.tr("Operation not available"), "WARNING"); + }, + } +}); diff --git a/services/static-webserver/client/source/class/osparc/share/CollaboratorsService.js b/services/static-webserver/client/source/class/osparc/share/CollaboratorsService.js index cb0b848a92f5..02d40a0f5ce2 100644 --- a/services/static-webserver/client/source/class/osparc/share/CollaboratorsService.js +++ b/services/static-webserver/client/source/class/osparc/share/CollaboratorsService.js @@ -54,6 +54,7 @@ qx.Class.define("osparc.share.CollaboratorsService", { return; } + // default access rights const readAccessRole = osparc.data.Roles.SERVICES["read"]; const newAccessRights = this._serializedDataCopy["accessRights"]; gids.forEach(gid => { diff --git a/services/static-webserver/client/source/class/osparc/share/CollaboratorsStudy.js b/services/static-webserver/client/source/class/osparc/share/CollaboratorsStudy.js index 9abb5dbb8660..521cd40cf624 100644 --- a/services/static-webserver/client/source/class/osparc/share/CollaboratorsStudy.js +++ b/services/static-webserver/client/source/class/osparc/share/CollaboratorsStudy.js @@ -83,9 +83,10 @@ qx.Class.define("osparc.share.CollaboratorsStudy", { return; } - const readAccessRole = osparc.data.Roles.STUDY["read"]; - const writeAccessRole = osparc.data.Roles.STUDY["write"]; if (!newAccessRights) { + // default access rights + const readAccessRole = osparc.data.Roles.STUDY["read"]; + const writeAccessRole = osparc.data.Roles.STUDY["write"]; newAccessRights = this._resourceType === "study" ? writeAccessRole.accessRights : readAccessRole.accessRights; } const resourceAlias = osparc.product.Utils.resourceTypeToAlias(this._resourceType, {firstUpperCase: true}); diff --git a/services/static-webserver/client/source/class/osparc/share/CollaboratorsTag.js b/services/static-webserver/client/source/class/osparc/share/CollaboratorsTag.js index e496db929068..eca1fbfa3e33 100644 --- a/services/static-webserver/client/source/class/osparc/share/CollaboratorsTag.js +++ b/services/static-webserver/client/source/class/osparc/share/CollaboratorsTag.js @@ -48,6 +48,7 @@ qx.Class.define("osparc.share.CollaboratorsTag", { return; } + // default access rights const readAccessRole = osparc.data.Roles.STUDY["read"]; const newCollaborators = {}; gids.forEach(gid => newCollaborators[gid] = readAccessRole.accessRights); diff --git a/services/static-webserver/client/source/class/osparc/share/CollaboratorsWorkspace.js b/services/static-webserver/client/source/class/osparc/share/CollaboratorsWorkspace.js index 87d75b011bb8..21b7543bb2ab 100644 --- a/services/static-webserver/client/source/class/osparc/share/CollaboratorsWorkspace.js +++ b/services/static-webserver/client/source/class/osparc/share/CollaboratorsWorkspace.js @@ -44,6 +44,7 @@ qx.Class.define("osparc.share.CollaboratorsWorkspace", { return; } + // default access rights const writeAccessRole = osparc.data.Roles.WORKSPACE["write"]; const newCollaborators = {}; gids.forEach(gid => newCollaborators[gid] = writeAccessRole.accessRights); diff --git a/services/static-webserver/client/source/class/osparc/share/NewCollaboratorsManager.js b/services/static-webserver/client/source/class/osparc/share/NewCollaboratorsManager.js index 365439668a08..2a43e07c6fd9 100644 --- a/services/static-webserver/client/source/class/osparc/share/NewCollaboratorsManager.js +++ b/services/static-webserver/client/source/class/osparc/share/NewCollaboratorsManager.js @@ -149,6 +149,7 @@ qx.Class.define("osparc.share.NewCollaboratorsManager", { allowGrowX: false, backgroundColor: "transparent", }); + control.getChildControl("arrow").syncAppearance(); this.getChildControl("access-rights-layout").add(control); break; case "access-rights-helper": { diff --git a/services/static-webserver/client/source/class/osparc/store/Functions.js b/services/static-webserver/client/source/class/osparc/store/Functions.js index 606e074c79cc..5913e88d0d5d 100644 --- a/services/static-webserver/client/source/class/osparc/store/Functions.js +++ b/services/static-webserver/client/source/class/osparc/store/Functions.js @@ -91,7 +91,29 @@ qx.Class.define("osparc.store.Functions", { return osparc.data.Resources.fetch("functions", "create", params); }, + curateOrderBy: function(orderBy) { + const curatedOrderBy = JSON.parse(orderBy); + switch (curatedOrderBy.field) { + case "last_change_date": + curatedOrderBy.field = "modified_at"; + break; + case "creation_date": + curatedOrderBy.field = "created_at"; + break; + case "name": + // stays the same + break; + default: + // only those three are supported + curatedOrderBy.field = "modified_at"; + } + return JSON.stringify(curatedOrderBy); + }, + fetchFunctionsPaginated: function(params, options) { + if ("orderBy" in params["url"]) { + params["url"]["orderBy"] = this.curateOrderBy(params["url"]["orderBy"]); + } return osparc.data.Resources.fetch("functions", "getPage", params, options) .then(response => { const functions = response["data"]; @@ -100,6 +122,18 @@ qx.Class.define("osparc.store.Functions", { }); }, + searchFunctionsPaginated: function(params, options) { + if ("orderBy" in params["url"]) { + params["url"]["orderBy"] = this.curateOrderBy(params["url"]["orderBy"]); + } + return osparc.data.Resources.fetch("functions", "getPageSearch", params, options) + .then(response => { + const functions = response["data"]; + functions.forEach(func => func["resourceType"] = "function"); + return response; + }); + }, + fetchFunction: function(functionId) { const params = { url: { @@ -131,6 +165,65 @@ qx.Class.define("osparc.store.Functions", { }); }, + __putCollaborator: function(functionData, gid, newPermissions) { + const params = { + url: { + "functionId": functionData["uuid"], + "gId": gid, + }, + data: newPermissions + }; + return osparc.data.Resources.fetch("functions", "putAccessRights", params) + }, + + addCollaborators: function(functionData, newCollaborators) { + const promises = []; + Object.keys(newCollaborators).forEach(gid => { + promises.push(this.__putCollaborator(functionData, gid, newCollaborators[gid])); + }); + return Promise.all(promises) + .then(() => { + Object.keys(newCollaborators).forEach(gid => { + functionData["accessRights"][gid] = newCollaborators[gid]; + }); + functionData["lastChangeDate"] = new Date().toISOString(); + }) + .catch(err => { + osparc.FlashMessenger.logError(err); + throw err; + }); + }, + + updateCollaborator: function(functionData, gid, newPermissions) { + return this.__putCollaborator(functionData, gid, newPermissions) + .then(() => { + functionData["accessRights"][gid] = newPermissions; + functionData["lastChangeDate"] = new Date().toISOString(); + }) + .catch(err => { + osparc.FlashMessenger.logError(err); + throw err; + }); + }, + + removeCollaborator: function(functionData, gid) { + const params = { + url: { + "functionId": functionData["uuid"], + "gId": gid + } + }; + return osparc.data.Resources.fetch("functions", "deleteAccessRights", params) + .then(() => { + delete functionData["accessRights"][gid]; + functionData["lastChangeDate"] = new Date().toISOString(); + }) + .catch(err => { + osparc.FlashMessenger.logError(err); + throw err; + }); + }, + invalidateFunctions: function() { this.__functions = null; if (this.__functionsPromiseCached) { diff --git a/services/static-webserver/client/source/class/osparc/store/Groups.js b/services/static-webserver/client/source/class/osparc/store/Groups.js index b040129d5a83..132124181f51 100644 --- a/services/static-webserver/client/source/class/osparc/store/Groups.js +++ b/services/static-webserver/client/source/class/osparc/store/Groups.js @@ -48,17 +48,6 @@ qx.Class.define("osparc.store.Groups", { }, }, - 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: { groupsCached: null, @@ -155,12 +144,17 @@ qx.Class.define("osparc.store.Groups", { }, getAllMyGroupIds: function() { - return [ + const allMyGroupIds = [ this.getMyGroupId(), - ...this.getOrganizationIds().map(gId => parseInt(gId)), - this.getEveryoneProductGroup().getGroupId(), - this.getEveryoneGroup().getGroupId(), - ] + ...this.getOrganizationIds().map(gId => parseInt(gId)) + ]; + if (this.getEveryoneProductGroup()) { + allMyGroupIds.push(this.getEveryoneProductGroup().getGroupId()); + } + if (this.getEveryoneGroup()) { + allMyGroupIds.push(this.getEveryoneGroup().getGroupId()); + } + return allMyGroupIds; }, getGroup: function(groupId) { 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 3958026cb6c3..b421d301c1ec 100644 --- a/services/static-webserver/client/source/class/osparc/store/Store.js +++ b/services/static-webserver/client/source/class/osparc/store/Store.js @@ -86,6 +86,7 @@ qx.Class.define("osparc.store.Store", { "searchProjects", // osparc.dashboard.StudyBrowser.CONTEXT.SEARCH_PROJECTS, "searchTemplates", // osparc.dashboard.StudyBrowser.CONTEXT.SEARCH_TEMPLATES, "searchPublicTemplates", // osparc.dashboard.StudyBrowser.CONTEXT.SEARCH_PUBLIC_TEMPLATES, + "searchFunctions", // osparc.dashboard.StudyBrowser.CONTEXT.SEARCH_FUNCTIONS, ], init: "studiesAndFolders", nullable: false, diff --git a/services/static-webserver/client/source/class/osparc/theme/Appearance.js b/services/static-webserver/client/source/class/osparc/theme/Appearance.js index fbce40af34d8..a4f37760506a 100644 --- a/services/static-webserver/client/source/class/osparc/theme/Appearance.js +++ b/services/static-webserver/client/source/class/osparc/theme/Appearance.js @@ -450,6 +450,21 @@ qx.Theme.define("osparc.theme.Appearance", { }) }, + "selectbox/arrow": { + style: () => ({ + // keep the original source + source: osparc.theme.common.Image.URLS["arrow-down"], + // keep the original paddings + paddingRight: 0, + paddingLeft: 2, + paddingTop: -3, + // ensure the arrow has explicit size + width: 16, + height: 16, + scale: true, + }) + }, + /* --------------------------------------------------------------------------- PROGRESSBAR diff --git a/services/static-webserver/client/source/class/osparc/ui/list/CollaboratorListItem.js b/services/static-webserver/client/source/class/osparc/ui/list/CollaboratorListItem.js index 7ca2bff44c64..8d87bcc668b5 100644 --- a/services/static-webserver/client/source/class/osparc/ui/list/CollaboratorListItem.js +++ b/services/static-webserver/client/source/class/osparc/ui/list/CollaboratorListItem.js @@ -75,17 +75,27 @@ qx.Class.define("osparc.ui.list.CollaboratorListItem", { members: { __getRoleInfo: function(id) { + let roleInfo = undefined; const resource = this.getResourceType(); - if (["study", "template", "tutorial", "hypertool"].includes(resource)) { - return osparc.data.Roles.STUDY[id]; - } else if (resource === "service") { - return osparc.data.Roles.SERVICES[id]; - } else if (resource === "workspace") { - return osparc.data.Roles.WORKSPACE[id]; - } else if (resource === "tag") { - return osparc.data.Roles.STUDY[id]; + switch (resource) { + case "study": + case "template": + case "tutorial": + case "hypertool": + case "tag": + roleInfo = osparc.data.Roles.STUDY[id]; + break; + case "function": + roleInfo = osparc.data.Roles.FUNCTION[id]; + break; + case "service": + roleInfo = osparc.data.Roles.SERVICES[id]; + break; + case "workspace": + roleInfo = osparc.data.Roles.WORKSPACE[id]; + break; } - return undefined; + return roleInfo; }, _createChildControlImpl: function(id) { @@ -212,9 +222,9 @@ qx.Class.define("osparc.ui.list.CollaboratorListItem", { break; } case "write": { - const resource = this.getResourceType(); - if (resource !== "service") { - // there is no owner role for services + // there might not be delete role + const deleteRole = this.__getRoleInfo("delete"); + if (deleteRole) { const promoteButton = new qx.ui.menu.Button(this.tr(`Promote to ${this.__getRoleInfo("delete").label}`)); promoteButton.addListener("execute", () => { this.fireDataEvent("promoteToOwner", {