diff --git a/services/static-webserver/client/source/class/osparc/dashboard/CardBase.js b/services/static-webserver/client/source/class/osparc/dashboard/CardBase.js index dc448fa977f4..6f875d19bb82 100644 --- a/services/static-webserver/client/source/class/osparc/dashboard/CardBase.js +++ b/services/static-webserver/client/source/class/osparc/dashboard/CardBase.js @@ -49,6 +49,7 @@ qx.Class.define("osparc.dashboard.CardBase", { "updateStudy": "qx.event.type.Data", "updateTemplate": "qx.event.type.Data", "updateTutorial": "qx.event.type.Data", + "updateFunction": "qx.event.type.Data", "updateService": "qx.event.type.Data", "updateHypertool": "qx.event.type.Data", "publishTemplate": "qx.event.type.Data", @@ -334,6 +335,7 @@ qx.Class.define("osparc.dashboard.CardBase", { check: [ "study", "template", + "function", "tutorial", "hypertool", "service", @@ -535,6 +537,11 @@ qx.Class.define("osparc.dashboard.CardBase", { owner = resourceData.prjOwner ? resourceData.prjOwner : ""; workbench = resourceData.workbench ? resourceData.workbench : {}; break; + case "function": + uuid = resourceData.uuid ? resourceData.uuid : null; + owner = ""; + workbench = resourceData.workbench ? resourceData.workbench : {}; + break; case "service": uuid = resourceData.key ? resourceData.key : null; owner = resourceData.owner ? resourceData.owner : resourceData.contact; @@ -563,26 +570,31 @@ qx.Class.define("osparc.dashboard.CardBase", { workbench }); - if ([ - "study", - "template", - "tutorial", - "hypertool" - ].includes(resourceData["resourceType"])) { - osparc.store.Services.getStudyServices(resourceData.uuid) - .then(resp => { - const services = resp["services"]; - resourceData["services"] = services; - this.setServices(services); - }) - .catch(err => { - resourceData["services"] = null; - this.setServices(null); - console.error(err); - }); + switch (resourceData["resourceType"]) { + case "study": + case "template": + case "tutorial": + case "hypertool": { + osparc.store.Services.getStudyServices(resourceData.uuid) + .then(resp => { + const services = resp["services"]; + resourceData["services"] = services; + this.setServices(services); + }) + .catch(err => { + resourceData["services"] = null; + this.setServices(null); + console.error(err); + }); + + osparc.study.Utils.guessIcon(resourceData) + .then(iconSource => this.setIcon(iconSource)); - osparc.study.Utils.guessIcon(resourceData) - .then(iconSource => this.setIcon(iconSource)); + break; + } + case "function": + this.setIcon(osparc.data.model.StudyUI.PIPELINE_ICON); + break; } }, @@ -1081,6 +1093,7 @@ qx.Class.define("osparc.dashboard.CardBase", { "updateStudy", "updateTemplate", "updateTutorial", + "updateFunction", "updateService", "updateHypertool", ].forEach(ev => { 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 ce5a78f8ced6..cd9d02ea0bbf 100644 --- a/services/static-webserver/client/source/class/osparc/dashboard/GridButtonItem.js +++ b/services/static-webserver/client/source/class/osparc/dashboard/GridButtonItem.js @@ -204,6 +204,7 @@ qx.Class.define("osparc.dashboard.GridButtonItem", { "template", "tutorial", "hypertool", + "function", ].includes(this.getResourceType())) { const dateBy = this.getChildControl("date-by"); dateBy.set({ 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 271981b06bf7..a62dcd6908f0 100644 --- a/services/static-webserver/client/source/class/osparc/dashboard/ResourceBrowserBase.js +++ b/services/static-webserver/client/source/class/osparc/dashboard/ResourceBrowserBase.js @@ -282,6 +282,7 @@ qx.Class.define("osparc.dashboard.ResourceBrowserBase", { resourcesContainer.addListener("updateStudy", e => this._updateStudyData(e.getData())); resourcesContainer.addListener("updateTemplate", e => this._updateTemplateData(e.getData())); resourcesContainer.addListener("updateTutorial", e => this._updateTutorialData(e.getData())); + resourcesContainer.addListener("updateFunction", e => this._updateFunctionData(e.getData())); resourcesContainer.addListener("updateService", e => this._updateServiceData(e.getData())); resourcesContainer.addListener("updateHypertool", e => this._updateHypertoolData(e.getData())); resourcesContainer.addListener("publishTemplate", e => this.fireDataEvent("publishTemplate", e.getData())); @@ -692,7 +693,11 @@ qx.Class.define("osparc.dashboard.ResourceBrowserBase", { } }, - updateTutorialData: function(tutorialData) { + _updateTutorialData: function(tutorialData) { + throw new Error("Abstract method called!"); + }, + + _updateFunctionData: function(functionData) { throw new Error("Abstract method called!"); }, @@ -926,6 +931,7 @@ qx.Class.define("osparc.dashboard.ResourceBrowserBase", { resourceDetails.addListener("updateStudy", e => this._updateStudyData(e.getData())); resourceDetails.addListener("updateTemplate", e => this._updateTemplateData(e.getData())); resourceDetails.addListener("updateTutorial", e => this._updateTutorialData(e.getData())); + resourceDetails.addListener("updateFunction", e => this._updateFunctionData(e.getData())); resourceDetails.addListener("updateService", e => this._updateServiceData(e.getData())); resourceDetails.addListener("updateHypertool", e => this._updateHypertoolData(e.getData())); resourceDetails.addListener("publishTemplate", e => { diff --git a/services/static-webserver/client/source/class/osparc/dashboard/ResourceBrowserFilter.js b/services/static-webserver/client/source/class/osparc/dashboard/ResourceBrowserFilter.js index 0cb9a6a55cb4..df60183ed3d6 100644 --- a/services/static-webserver/client/source/class/osparc/dashboard/ResourceBrowserFilter.js +++ b/services/static-webserver/client/source/class/osparc/dashboard/ResourceBrowserFilter.js @@ -36,6 +36,7 @@ qx.Class.define("osparc.dashboard.ResourceBrowserFilter", { events: { "templatesContext": "qx.event.type.Event", "publicTemplatesContext": "qx.event.type.Event", + "functionsContext": "qx.event.type.Event", "trashContext": "qx.event.type.Event", "changeTab": "qx.event.type.Data", "trashStudyRequested": "qx.event.type.Data", @@ -50,6 +51,7 @@ qx.Class.define("osparc.dashboard.ResourceBrowserFilter", { __workspacesAndFoldersTree: null, __templatesButton: null, __publicProjectsButton: null, + __functionsButton: null, __trashButton: null, __sharedWithButtons: null, __tagButtons: null, @@ -66,6 +68,9 @@ qx.Class.define("osparc.dashboard.ResourceBrowserFilter", { if (osparc.product.Utils.showPublicProjects()) { this._add(this.__createPublicProjects()); } + if (osparc.product.Utils.showFunctions()) { + this._add(this.__createFunctions()); + } this._add(this.__createTrashBin()); this._add(filtersSpacer); const scrollView = new qx.ui.container.Scroll(); @@ -102,6 +107,7 @@ qx.Class.define("osparc.dashboard.ResourceBrowserFilter", { this.__templatesButton.setValue(context === osparc.dashboard.StudyBrowser.CONTEXT.TEMPLATES); this.__publicProjectsButton.setValue(context === osparc.dashboard.StudyBrowser.CONTEXT.PUBLIC_TEMPLATES); + this.__functionsButton.setValue(context === osparc.dashboard.StudyBrowser.CONTEXT.FUNCTIONS); this.__trashButton.setValue(context === osparc.dashboard.StudyBrowser.CONTEXT.TRASH); }, @@ -164,6 +170,24 @@ qx.Class.define("osparc.dashboard.ResourceBrowserFilter", { return publicProjectsButton; }, + __createFunctions: function() { + const functionsButton = this.__functionsButton = new qx.ui.toolbar.RadioButton().set({ + value: false, + appearance: "filter-toggle-button", + label: this.tr("Functions"), + icon: "@MaterialIcons/functions/20", + paddingLeft: 10, // align it with the context + }); + osparc.utils.Utils.setIdToWidget(functionsButton, "functionsFilterItem"); + functionsButton.addListener("changeValue", e => { + const functionsEnabled = e.getData(); + if (functionsEnabled) { + this.fireEvent("functionsContext"); + } + }); + return functionsButton; + }, + /* TRASH BIN */ __createTrashBin: function() { const trashButton = this.__trashButton = new qx.ui.toolbar.RadioButton().set({ 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 ffa57c14175d..6af8a37004fc 100644 --- a/services/static-webserver/client/source/class/osparc/dashboard/ResourceContainerManager.js +++ b/services/static-webserver/client/source/class/osparc/dashboard/ResourceContainerManager.js @@ -76,6 +76,7 @@ qx.Class.define("osparc.dashboard.ResourceContainerManager", { "updateStudy": "qx.event.type.Data", "updateTemplate": "qx.event.type.Data", "updateTutorial": "qx.event.type.Data", + "updateFunction": "qx.event.type.Data", "updateService": "qx.event.type.Data", "updateHypertool": "qx.event.type.Data", "publishTemplate": "qx.event.type.Data", @@ -161,6 +162,9 @@ qx.Class.define("osparc.dashboard.ResourceContainerManager", { case osparc.dashboard.StudyBrowser.CONTEXT.SEARCH_PUBLIC_TEMPLATES: text = this.tr("No Public Projects found"); break; + case osparc.dashboard.StudyBrowser.CONTEXT.FUNCTIONS: + text = this.tr("No Functions found"); + break; } break; } @@ -297,6 +301,7 @@ qx.Class.define("osparc.dashboard.ResourceContainerManager", { "updateStudy", "updateTemplate", "updateTutorial", + "updateFunction", "updateService", "updateHypertool", "publishTemplate", 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 503182100313..54a0f3f20fd8 100644 --- a/services/static-webserver/client/source/class/osparc/dashboard/ResourceDetails.js +++ b/services/static-webserver/client/source/class/osparc/dashboard/ResourceDetails.js @@ -33,10 +33,13 @@ qx.Class.define("osparc.dashboard.ResourceDetails", { latestPromise = osparc.store.Study.getInstance().getOne(resourceData["uuid"]); break; } - case "function": { + case "functionedTemplate": { latestPromise = osparc.store.Templates.fetchTemplate(resourceData["uuid"]); break; } + case "function": + latestPromise = osparc.store.Functions.fetchFunction(resourceData["uuid"]); + break; case "service": { latestPromise = osparc.store.Services.getService(resourceData["key"], resourceData["version"]); break; @@ -57,7 +60,7 @@ qx.Class.define("osparc.dashboard.ResourceDetails", { case "template": case "tutorial": case "hypertool": - case "function": + case "functionedTemplate": // when getting the latest study data, the debt information was lost if (osparc.study.Utils.isInDebt(this.__resourceData)) { const studyStore = osparc.store.Study.getInstance(); @@ -69,8 +72,20 @@ qx.Class.define("osparc.dashboard.ResourceDetails", { this.__resourceModel["resourceType"] = resourceData["resourceType"]; this.__resourceData["services"] = resourceData["services"]; this.__addPages(); - }) + }); break; + case "function": { + // use function's underlying template info to fetch services metadata + // osparc.store.Templates.fetchTemplate(resourceData["uuid"]); + osparc.store.Services.getStudyServicesMetadata(latestResourceData) + .finally(() => { + this.__resourceModel = new osparc.data.model.Function(latestResourceData); + this.__resourceModel["resourceType"] = resourceData["resourceType"]; + this.__resourceData["services"] = resourceData["services"]; + this.__addPages(); + }); + break; + } case "service": { this.__resourceModel = new osparc.data.model.Service(latestResourceData); this.__resourceModel["resourceType"] = resourceData["resourceType"]; @@ -92,6 +107,7 @@ qx.Class.define("osparc.dashboard.ResourceDetails", { "updateStudy": "qx.event.type.Data", "updateTemplate": "qx.event.type.Data", "updateTutorial": "qx.event.type.Data", + "updateFunction": "qx.event.type.Data", "updateService": "qx.event.type.Data", "updateHypertool": "qx.event.type.Data", "publishTemplate": "qx.event.type.Data", @@ -157,6 +173,10 @@ qx.Class.define("osparc.dashboard.ResourceDetails", { __addToolbarButtons: function(page) { const resourceData = this.__resourceData; + if (this.__resourceData["resourceType"] === "function") { + return; // no toolbar buttons for functions + } + const toolbar = this.self().createToolbar(); page.addToHeader(toolbar); @@ -370,11 +390,17 @@ qx.Class.define("osparc.dashboard.ResourceDetails", { // removeAll osparc.utils.Utils.removeAllChildren(tabsView); - if (this.__resourceData["resourceType"] === "function") { + if (this.__resourceData["resourceType"] === "functionedTemplate") { // for now, we only want the preview page this.__addPreviewPage(); this.fireEvent("pagesAdded"); return; + } else if (this.__resourceData["resourceType"] === "function") { + this.__addInfoPage(); + // to build the preview page we need the underlying template data + // this.__addPreviewPage(); + this.fireEvent("pagesAdded"); + return; } this.__addInfoPage(); @@ -417,6 +443,9 @@ qx.Class.define("osparc.dashboard.ResourceDetails", { case "tutorial": this.fireDataEvent("updateTutorial", updatedData); break; + case "function": + this.fireDataEvent("updateFunction", updatedData); + break; case "hypertool": this.fireDataEvent("updateHypertool", updatedData); break; @@ -448,6 +477,12 @@ qx.Class.define("osparc.dashboard.ResourceDetails", { const updatedData = e.getData(); this.__fireUpdateEvent(resourceData, updatedData); }); + } else if (osparc.utils.Resources.isFunction(resourceData)) { + infoCard = new osparc.info.FunctionLarge(resourceModel); + infoCard.addListener("updateFunction", e => { + const updatedData = e.getData(); + this.__fireUpdateEvent(resourceData, updatedData); + }); } else { infoCard = new osparc.info.StudyLarge(resourceModel, false); infoCard.addListener("updateStudy", e => { 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 48fac36f4575..3135e4651951 100644 --- a/services/static-webserver/client/source/class/osparc/dashboard/StudyBrowser.js +++ b/services/static-webserver/client/source/class/osparc/dashboard/StudyBrowser.js @@ -53,6 +53,7 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { WORKSPACES: "workspaces", TEMPLATES: "templates", PUBLIC_TEMPLATES: "publicTemplates", + FUNCTIONS: "functions", TRASH: "trash", SEARCH_PROJECTS: "searchProjects", SEARCH_TEMPLATES: "searchTemplates", @@ -67,6 +68,7 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { "workspaces", "templates", "publicTemplates", + "functions", "trash", "searchProjects", "searchTemplates", @@ -104,8 +106,9 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { }, members: { - __dontShowTutorial: null, + __dontQuickStart: null, __header: null, + __sortByButton: null, __workspacesList: null, __foldersList: null, __loadingFolders: null, @@ -327,6 +330,12 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { this.__addResourcesToList(filteredTemplates); break; } + case osparc.dashboard.StudyBrowser.CONTEXT.FUNCTIONS: { + const functions = resp["data"]; + functions.forEach(func => func["resourceType"] = "function"); + this.__addResourcesToList(functions); + break; + } } if (this._resourcesContainer.getFlatList()) { this._resourcesContainer.getFlatList().nextRequest = resp["_links"]["next"]; @@ -341,8 +350,8 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { // Show Quick Start if there are no studies in the root folder of the personal workspace const quickStartInfo = osparc.product.quickStart.Utils.getQuickStart(); if (quickStartInfo) { - const dontShow = osparc.utils.Utils.localCache.getLocalStorageItem(quickStartInfo.localStorageStr); - if (dontShow === "true" || this.__dontShowTutorial) { + const dontShowQuickStart = osparc.utils.Utils.localCache.getLocalStorageItem(quickStartInfo.localStorageStr); + if (dontShowQuickStart === "true" || this.__dontQuickStart) { return; } const nStudies = "_meta" in resp ? resp["_meta"]["total"] : 0; @@ -356,7 +365,7 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { quickStartWindow.center(); quickStartWindow.open(); quickStartWindow.addListener("close", () => { - this.__dontShowTutorial = true; + this.__dontQuickStart = true; }, this); } } @@ -858,6 +867,8 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { requestParams.templateType = osparc.data.model.StudyUI.TEMPLATE_TYPE; requestParams.accessRights = "public"; break; + case osparc.dashboard.StudyBrowser.CONTEXT.FUNCTIONS: + break; case osparc.dashboard.StudyBrowser.CONTEXT.SEARCH_PROJECTS: { requestParams.type = "user"; break; @@ -919,6 +930,8 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { // The distinction is done in the frontend request = osparc.store.Templates.searchTemplatesPaginated(params, options); break; + case osparc.dashboard.StudyBrowser.CONTEXT.FUNCTIONS: + request = osparc.store.Functions.fetchFunctionsPaginated(params, options); } return request; }, @@ -931,6 +944,14 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { } }, + invalidateFunctions: function() { + osparc.store.Functions.invalidateFunctions(); + this.__resetStudiesList(); + if (this._resourcesContainer.getFlatList()) { + this._resourcesContainer.getFlatList().nextRequest = null; + } + }, + __addNewPlusButton: function() { const newPlusButton = new osparc.dashboard.NewPlusButton(); this._leftFilters.add(newPlusButton); @@ -1141,6 +1162,7 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { this._resourceFilter.addListener("templatesContext", () => this._changeContext(osparc.dashboard.StudyBrowser.CONTEXT.TEMPLATES)); this._resourceFilter.addListener("publicTemplatesContext", () => this._changeContext(osparc.dashboard.StudyBrowser.CONTEXT.PUBLIC_TEMPLATES)); + this._resourceFilter.addListener("functionsContext", () => this._changeContext(osparc.dashboard.StudyBrowser.CONTEXT.FUNCTIONS)); this._resourceFilter.addListener("trashContext", () => this._changeContext(osparc.dashboard.StudyBrowser.CONTEXT.TRASH)); this._searchBarFilter.addListener("filterChanged", e => { @@ -1156,11 +1178,17 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { case osparc.dashboard.StudyBrowser.CONTEXT.SEARCH_PUBLIC_TEMPLATES: searchContext = osparc.dashboard.StudyBrowser.CONTEXT.SEARCH_PUBLIC_TEMPLATES; break; + case osparc.dashboard.StudyBrowser.CONTEXT.FUNCTIONS: + // functions are not searchable yet + searchContext = null; + break; default: searchContext = osparc.dashboard.StudyBrowser.CONTEXT.SEARCH_PROJECTS; break; } - this._changeContext(searchContext); + if (searchContext) { + this._changeContext(searchContext); + } } else { let backToContext = osparc.dashboard.StudyBrowser.CONTEXT.PROJECTS; switch (this.getCurrentContext()) { @@ -1206,12 +1234,17 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { this._resourcesList = []; this._resourcesContainer.setResourcesToList(this._resourcesList); this._resourcesContainer.reloadCards("studies"); + // functions will disable it + this._searchBarFilter.setEnabled(true); + // workspaces will exclude it + this._toolbar.show(); + // functions will exclude it + this.__sortByButton.show(); switch (this.getCurrentContext()) { case osparc.dashboard.StudyBrowser.CONTEXT.PROJECTS: this._searchBarFilter.resetFilters(); this._searchBarFilter.getChildControl("text-field").setPlaceholder("Search in My Projects"); - this._toolbar.show(); this.__reloadFolders(); this._loadingResourcesBtn.setFetching(false); this.invalidateStudies(); @@ -1220,12 +1253,12 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { case osparc.dashboard.StudyBrowser.CONTEXT.WORKSPACES: this._searchBarFilter.resetFilters(); this._searchBarFilter.getChildControl("text-field").setPlaceholder("Search in My Projects"); + // workspaces can't be sorted and don't support list view this._toolbar.exclude(); this.__reloadWorkspaces(); break; case osparc.dashboard.StudyBrowser.CONTEXT.SEARCH_PROJECTS: this._searchBarFilter.getChildControl("text-field").setPlaceholder("Search in My Projects"); - this._toolbar.show(); this.__reloadWorkspaces(); this.__reloadFolders(); this._loadingResourcesBtn.setFetching(false); @@ -1238,7 +1271,6 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { this._searchBarFilter.resetFilters(); } this._searchBarFilter.getChildControl("text-field").setPlaceholder("Search in Templates"); - this._toolbar.show(); this._loadingResourcesBtn.setFetching(false); this.invalidateStudies(); this.__reloadStudies(); @@ -1249,15 +1281,24 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { this._searchBarFilter.resetFilters(); } this._searchBarFilter.getChildControl("text-field").setPlaceholder("Search in Public Projects"); - this._toolbar.show(); this._loadingResourcesBtn.setFetching(false); this.invalidateStudies(); this.__reloadStudies(); break; + case 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); + this.invalidateFunctions(); + this.__reloadStudies(); + break; case osparc.dashboard.StudyBrowser.CONTEXT.TRASH: this._searchBarFilter.resetFilters(); this._searchBarFilter.getChildControl("text-field").setPlaceholder("Search in My Projects"); - this._toolbar.show(); this.__reloadWorkspaces(); this.__reloadFolders(); this._loadingResourcesBtn.setFetching(false); @@ -1327,7 +1368,7 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { }, __addSortByButton: function() { - const sortByButton = new osparc.dashboard.SortedByMenuButton(); + const sortByButton = this.__sortByButton = new osparc.dashboard.SortedByMenuButton(); sortByButton.set({ appearance: "form-button-outlined" }); @@ -1611,6 +1652,20 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { this.base(arguments, templateData); }, + _updateFunctionData: function(functionData) { + functionData["resourceType"] = "function"; + + const index = this._resourcesList.findIndex(func => func["uuid"] === functionData["uuid"]); + if (index === -1) { + // add it in first position, most likely it's a new study + this._resourcesList.unshift(functionData); + } else { + this._resourcesList[index] = functionData; + } + // it will render the studies in the right order + this._reloadCards(); + }, + __removeFromStudyList: function(studyId) { const idx = this._resourcesList.findIndex(study => study["uuid"] === studyId); if (idx > -1) { @@ -1621,11 +1676,16 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { _populateCardMenu: function(card) { const studyData = card.getResourceData(); - if (studyData["resourceType"] === "template") { - // The Study Browser can also list templates - this._populateTemplateCardMenu(card); - } else { - this.__populateStudyCardMenu(card); + switch (studyData["resourceType"]) { + case "study": + this.__populateStudyCardMenu(card); + break; + case "template": + this._populateTemplateCardMenu(card); + break; + case "function": + card.getChildControl("menu-selection-stack").exclude(); + break; } }, 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 9eb45031dbbb..d91e62ab485e 100644 --- a/services/static-webserver/client/source/class/osparc/dashboard/StudyBrowserHeader.js +++ b/services/static-webserver/client/source/class/osparc/dashboard/StudyBrowserHeader.js @@ -307,6 +307,11 @@ qx.Class.define("osparc.dashboard.StudyBrowserHeader", { title.setValue(this.tr("Public Projects")); break; } + case osparc.dashboard.StudyBrowser.CONTEXT.FUNCTIONS: { + this.__setIcon("@MaterialIcons/functions/26"); + title.setValue(this.tr("Functions")); + break; + } case osparc.dashboard.StudyBrowser.CONTEXT.TRASH: { this.__setIcon("@FontAwesome5Solid/trash/24"); title.setValue(this.tr("Recently Deleted")); diff --git a/services/static-webserver/client/source/class/osparc/data/Permissions.js b/services/static-webserver/client/source/class/osparc/data/Permissions.js index 8d0a957abc9c..477b08ffd662 100644 --- a/services/static-webserver/client/source/class/osparc/data/Permissions.js +++ b/services/static-webserver/client/source/class/osparc/data/Permissions.js @@ -310,6 +310,11 @@ qx.Class.define("osparc.data.Permissions", { return false; } + // This needs to be provided by the backend + if (action === "readFunctions") { + return osparc.utils.Utils.isDevelopmentPlatform(); + } + if ( this.__functionPermissions && action in this.__functionPermissions 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 new file mode 100644 index 000000000000..58f89f7fc998 --- /dev/null +++ b/services/static-webserver/client/source/class/osparc/data/model/Function.js @@ -0,0 +1,180 @@ +/* ************************************************************************ + + 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) + +************************************************************************ */ + +/** + * Class that stores Function data. + */ + +qx.Class.define("osparc.data.model.Function", { + extend: qx.core.Object, + + /** + * @param functionData {Object} Object containing the serialized Function Data + */ + construct: function(functionData) { + this.base(arguments); + + this.set({ + uuid: functionData.uuid, + functionType: functionData.functionClass, + name: functionData.name, + description: functionData.description, + inputSchema: functionData.inputSchema || this.getInputSchema(), + outputSchema: functionData.outputSchema || this.getOutputSchema(), + defaultInputs: functionData.defaultInputs || this.getDefaultInputs(), + accessRights: functionData.accessRights || this.getAccessRights(), + creationDate: functionData.creationDate ? new Date(functionData.creationDate) : this.getCreationDate(), + lastChangeDate: functionData.lastChangeDate ? new Date(functionData.lastChangeDate) : this.getLastChangeDate(), + thumbnail: functionData.thumbnail || this.getThumbnail(), + workbenchData: functionData.workbench || this.getWorkbenchData(), + functionUIData: functionData.ui || this.getFunctionUIData(), + }); + }, + + properties: { + uuid: { + check: "String", + nullable: false, + event: "changeUuid", + init: "" + }, + + functionType: { + check: ["PROJECT"], + nullable: false, + event: "changeFunctionType", + init: null + }, + + name: { + check: "String", + nullable: false, + event: "changeName", + init: "New Study" + }, + + description: { + check: "String", + nullable: true, + event: "changeDescription", + init: null + }, + + inputSchema: { + check: "Object", + nullable: false, + event: "changeInputSchema", + init: {} + }, + + outputSchema: { + check: "Object", + nullable: false, + event: "changeOutputSchema", + init: {} + }, + + defaultInputs: { + check: "Object", + nullable: false, + event: "changeDefaultInputs", + init: {} + }, + + accessRights: { + check: "Object", + nullable: false, + event: "changeAccessRights", + init: {} + }, + + creationDate: { + check: "Date", + nullable: false, + event: "changeCreationDate", + init: new Date() + }, + + lastChangeDate: { + check: "Date", + nullable: false, + event: "changeLastChangeDate", + init: new Date() + }, + + thumbnail: { + check: "String", + nullable: true, + event: "changeThumbnail", + init: null + }, + + workbenchData: { + check: "Object", + nullable: false, + init: {}, + }, + + functionUIData: { + check: "Object", + nullable: false, + init: {}, + }, + }, + + statics: { + canIWrite: function(accessRights) { + const groupsStore = osparc.store.Groups.getInstance(); + const gIds = groupsStore.getOrganizationIds(); + gIds.push(groupsStore.getMyGroupId()); + let canWrite = false; + for (let i=0; i { + jsonObject[key] = this.get(key); + }); + return jsonObject; + }, + + patchFunction: function(functionChanges) { + return osparc.store.Study.getInstance().patchStudy(this.getUuid(), functionChanges) + .then(() => { + Object.keys(functionChanges).forEach(fieldKey => { + const upKey = qx.lang.String.firstUp(fieldKey); + const setter = "set" + upKey; + this[setter](functionChanges[fieldKey]); + }) + this.set({ + lastChangeDate: new Date() + }); + const functionData = this.serialize(); + resolve(functionData); + }) + .catch(err => reject(err)); + }, + } +}); diff --git a/services/static-webserver/client/source/class/osparc/desktop/ZoomButtons.js b/services/static-webserver/client/source/class/osparc/desktop/ZoomButtons.js index 78a61438ea1c..2da87a0cddef 100644 --- a/services/static-webserver/client/source/class/osparc/desktop/ZoomButtons.js +++ b/services/static-webserver/client/source/class/osparc/desktop/ZoomButtons.js @@ -28,8 +28,6 @@ * */ -const ZOOM_BUTTON_SIZE = 32; - qx.Class.define("osparc.desktop.ZoomButtons", { extend: qx.ui.toolbar.ToolBar, @@ -52,6 +50,10 @@ qx.Class.define("osparc.desktop.ZoomButtons", { "zoomReset": "qx.event.type.Event" }, + statics: { + ZOOM_BUTTON_SIZE: 32, + }, + members: { __buildLayout: function() { this.add(this.__getZoomOutButton()); @@ -64,9 +66,9 @@ qx.Class.define("osparc.desktop.ZoomButtons", { appearance: "form-button-outlined", padding: [5, 5], marginLeft: 10, - width: ZOOM_BUTTON_SIZE, - height: ZOOM_BUTTON_SIZE, - maxHeight: ZOOM_BUTTON_SIZE + width: this.self().ZOOM_BUTTON_SIZE, + height: this.self().ZOOM_BUTTON_SIZE, + maxHeight: this.self().ZOOM_BUTTON_SIZE, }); if (tooltip) { btn.setToolTipText(tooltip); diff --git a/services/static-webserver/client/source/class/osparc/info/FunctionLarge.js b/services/static-webserver/client/source/class/osparc/info/FunctionLarge.js new file mode 100644 index 000000000000..4e90c9642ee7 --- /dev/null +++ b/services/static-webserver/client/source/class/osparc/info/FunctionLarge.js @@ -0,0 +1,196 @@ +/* ************************************************************************ + + 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.info.FunctionLarge", { + extend: osparc.info.CardLarge, + + /** + * @param func {osparc.data.model.Function} Function model + */ + construct: function(func) { + this.base(arguments); + + this.setFunction(func); + + this.setOpenOptions(false); + + this._attachHandlers(); + }, + + events: { + "updateFunction": "qx.event.type.Data", + }, + + properties: { + function: { + check: "osparc.data.model.Function", + init: null, + nullable: false + } + }, + + members: { + __canIWrite: function() { + return osparc.data.model.Function.canIWrite(this.getFunction().getAccessRights()); + }, + + _rebuildLayout: function() { + this._removeAll(); + + const vBox = new qx.ui.container.Composite(new qx.ui.layout.VBox(10)); + + const infoElements = this.__infoElements(); + const isStudy = true; + const infoLayout = osparc.info.Utils.infoElementsToLayout(infoElements, isStudy); + vBox.add(infoLayout); + + // inputs, default inputs and outputs + const info = { + "Inputs": this.getFunction().getInputSchema()["schema_content"], + "Default Inputs": this.getFunction().getDefaultInputs(), + "Outputs": this.getFunction().getOutputSchema()["schema_content"], + }; + const divId = "function-info-viewer"; + const htmlEmbed = osparc.wrapper.JsonFormatter.getInstance().createContainer(divId); + vBox.add(htmlEmbed, { + flex: 1 + }); + vBox.addListener("appear", () => { + osparc.wrapper.JsonFormatter.getInstance().setJson(info, divId); + }); + + // Copy Id button + const text = "Function Id"; + const copyIdButton = new qx.ui.form.Button(null, "@FontAwesome5Solid/copy/12").set({ + label: text, + toolTipText: "Copy " + text, + marginTop: 15, + allowGrowX: false + }); + copyIdButton.addListener("execute", () => osparc.utils.Utils.copyTextToClipboard(this.getFunction().getUuid())); + vBox.add(copyIdButton); + + // All in a scroll container + const scrollContainer = new qx.ui.container.Scroll(); + scrollContainer.add(vBox); + + this._add(scrollContainer, { + flex: 1 + }); + }, + + __infoElements: function() { + const canIWrite = this.__canIWrite(); + + const infoLayout = { + "TITLE": { + view: osparc.info.StudyUtils.createTitle(this.getFunction()), + action: { + button: osparc.utils.Utils.getEditButton(canIWrite), + callback: canIWrite ? this.__openTitleEditor : null, + ctx: this + } + }, + "THUMBNAIL": { + view: this.__createThumbnail(), + action: null + }, + "DESCRIPTION": { + view: osparc.info.StudyUtils.createDescription(this.getFunction()), + action: { + button: osparc.utils.Utils.getEditButton(canIWrite), + callback: canIWrite ? this.__openDescriptionEditor : null, + ctx: this + } + }, + "ACCESS_RIGHTS": { + label: this.tr("Access"), + view: osparc.info.StudyUtils.createAccessRights(this.getFunction()), + action: null + }, + "CREATED": { + label: this.tr("Created"), + view: osparc.info.StudyUtils.createCreationDate(this.getFunction()), + action: null + }, + "MODIFIED": { + label: this.tr("Modified"), + view: osparc.info.StudyUtils.createLastChangeDate(this.getFunction()), + action: null + }, + }; + return infoLayout; + }, + + __createThumbnail: function() { + const maxWidth = 190; + const maxHeight = 220; + const thumb = osparc.info.StudyUtils.createThumbnail(this.getFunction(), maxWidth, maxHeight); + thumb.set({ + maxWidth: 120, + maxHeight: 139 + }); + thumb.getChildControl("image").set({ + width: 120, + height: 139, + scale: true, + }); + + return thumb; + }, + + __openTitleEditor: function() { + const title = this.tr("Edit Title"); + const titleEditor = new osparc.widget.Renamer(this.getFunction().getName(), null, title); + titleEditor.addListener("labelChanged", e => { + titleEditor.close(); + const newLabel = e.getData()["newLabel"]; + this.__patchFunction("name", newLabel); + }, this); + titleEditor.center(); + titleEditor.open(); + }, + + __openDescriptionEditor: function() { + const title = this.tr("Edit Description"); + const textEditor = new osparc.editor.MarkdownEditor(this.getFunction().getDescription()); + textEditor.setMaxHeight(570); + const win = osparc.ui.window.Window.popUpInWindow(textEditor, title, 400, 300); + textEditor.addListener("textChanged", e => { + win.close(); + const newDescription = e.getData(); + this.__patchFunction("description", newDescription); + }, this); + textEditor.addListener("cancel", () => { + win.close(); + }, this); + }, + + __patchFunction: function(fieldKey, value) { + this.getFunction().patchFunction({[fieldKey]: value}) + .then(functionData => { + this.fireDataEvent("updateFunction", functionData); + qx.event.message.Bus.getInstance().dispatchByName("updateFunction", functionData); + }) + .catch(err => { + const msg = this.tr("An issue occurred while updating the information."); + osparc.FlashMessenger.logError(err, msg); + }); + } + } +}); diff --git a/services/static-webserver/client/source/class/osparc/info/StudyLarge.js b/services/static-webserver/client/source/class/osparc/info/StudyLarge.js index 3b6c2c88b619..c97be0a9196c 100644 --- a/services/static-webserver/client/source/class/osparc/info/StudyLarge.js +++ b/services/static-webserver/client/source/class/osparc/info/StudyLarge.js @@ -236,10 +236,6 @@ qx.Class.define("osparc.info.StudyLarge", { return infoLayout; }, - __createStudyId: function() { - return osparc.info.StudyUtils.createUuid(this.getStudy()); - }, - __createThumbnail: function() { const maxWidth = 190; const maxHeight = 220; diff --git a/services/static-webserver/client/source/class/osparc/product/Utils.js b/services/static-webserver/client/source/class/osparc/product/Utils.js index 42195453a311..fc0fde7c8978 100644 --- a/services/static-webserver/client/source/class/osparc/product/Utils.js +++ b/services/static-webserver/client/source/class/osparc/product/Utils.js @@ -310,6 +310,18 @@ qx.Class.define("osparc.product.Utils", { return true; }, + showFunctions: function() { + if (!osparc.data.Permissions.getInstance().checkFunctionPermissions("readFunctions")) { + return false; + } + + return [ + "osparc", + "s4l", + "s4lacad", + ].includes(osparc.product.Utils.getProductName()); + }, + showQuality: function() { return this.isProduct("osparc"); }, diff --git a/services/static-webserver/client/source/class/osparc/store/Functions.js b/services/static-webserver/client/source/class/osparc/store/Functions.js new file mode 100644 index 000000000000..487e844c9113 --- /dev/null +++ b/services/static-webserver/client/source/class/osparc/store/Functions.js @@ -0,0 +1,216 @@ +/* ************************************************************************ + + 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.store.Functions", { + type: "static", + + statics: { + __functions: null, + __functionsPromiseCached: null, + + __createFunctionData: function(templateData, name, description, defaultInputs = {}, exposedInputs = {}, exposedOutputs = {}) { + const functionData = { + "projectId": templateData["uuid"], + "title": name, + "description": description, + "function_class": "PROJECT", + "inputSchema": { + "schema_class": "application/schema+json", + "schema_content": { + "type": "object", + "properties": {}, + "required": [] + } + }, + "outputSchema": { + "schema_class": "application/schema+json", + "schema_content": { + "type": "object", + "properties": {}, + "required": [] + } + }, + "defaultInputs": {}, + }; + + const parameters = osparc.study.Utils.extractFunctionableParameters(templateData["workbench"]); + parameters.forEach(parameter => { + const parameterKey = parameter["label"]; + if (exposedInputs[parameterKey]) { + const parameterMetadata = osparc.store.Services.getMetadata(parameter["key"], parameter["version"]); + if (parameterMetadata) { + const type = osparc.service.Utils.getParameterType(parameterMetadata); + functionData["inputSchema"]["schema_content"]["properties"][parameterKey] = { + "type": type, + }; + functionData["inputSchema"]["schema_content"]["required"].push(parameterKey); + } + } + if (parameterKey in defaultInputs) { + functionData["defaultInputs"][parameterKey] = defaultInputs[parameterKey]; + } + }); + + const probes = osparc.study.Utils.extractFunctionableProbes(templateData["workbench"]); + probes.forEach(probe => { + const probeLabel = probe["label"]; + if (exposedOutputs[probeLabel]) { + const probeMetadata = osparc.store.Services.getMetadata(probe["key"], probe["version"]); + if (probeMetadata) { + const type = osparc.service.Utils.getProbeType(probeMetadata); + functionData["outputSchema"]["schema_content"]["properties"][probeLabel] = { + "type": type, + }; + functionData["outputSchema"]["schema_content"]["required"].push(probeLabel); + } + } + }); + + return functionData; + }, + + registerFunction: function(templateData, name, description, defaultInputs, exposedInputs, exposedOutputs) { + const functionData = this.__createFunctionData(templateData, name, description, defaultInputs, exposedInputs, exposedOutputs); + const params = { + data: functionData, + }; + return osparc.data.Resources.fetch("functions", "create", params); + }, + + fetchFunctionsPaginated: function(params, options) { + const isBackendReady = false; + if (!isBackendReady) { + return new Promise(resolve => { + const response = this.__dummyResponse(); + response["params"] = params; + resolve(response); + }); + } + return osparc.data.Resources.fetch("functions", "getPage", params, options) + .then(response => { + const functions = response["data"]; + functions.forEach(func => func["resourceType"] = "function"); + return response; + }) + .catch(err => osparc.FlashMessenger.logError(err)); + }, + + fetchFunction: function(functionId) { + const isBackendReady = false; + if (!isBackendReady) { + return new Promise(resolve => { + const response = this.__dummyResponse(); + resolve(response["data"][0]); + }); + } + const params = { + url: { + "functionId": functionId + } + }; + return osparc.data.Resources.fetch("functions", "getOne", params) + .then(func => { + func["resourceType"] = "function"; + return func; + }) + .catch(err => osparc.FlashMessenger.logError(err)); + }, + + invalidateFunctions: function() { + this.__functions = null; + if (this.__functionsPromiseCached) { + this.__functionsPromiseCached = null; + } + }, + + __dummyResponse: function() { + return { + "_meta": { + "limit": 10, + "total": 1, + "offset": 0, + "count": 1 + }, + "data": [{ + "uuid": "0fab79c3-14b8-4625-a455-6dcbf74eb4f2", + "functionClass": "PROJECT", + "name": "Potential Function II", + "description": "Function description", + "inputSchema": { + "schema_class": "application/schema+json", + "schema_content": { + "type": "object", + "required": [ + "X" + ], + "properties": { + "X": { + "type": "number" + } + } + } + }, + "outputSchema": { + "schema_class": "application/schema+json", + "schema_content": { + "type": "object", + "required": [ + "Out 1", + "Out_2" + ], + "properties": { + "Out 1": { + "type": "number" + }, + "Out_2": { + "type": "number" + } + } + } + }, + "defaultInputs": { + "X": 2, + "Y": 1 + }, + "creationDate": "2025-05-16T12:22:31.063Z", + "lastChangeDate": "2025-05-16T12:22:33.804Z", + "accessRights": { + "3": { + "read": true, + "write": true, + "delete": true + }, + "5": { + "read": true, + "write": false, + "delete": false + } + }, + "thumbnail": "https://img.freepik.com/premium-vector/image-icon-design-vector-template_1309674-940.jpg", + "workbench": {"50a50309-1dfc-5ad5-b2d9-c11697641f0b": {"key": "simcore/services/comp/itis/sleeper", "version": "2.1.6", "label": "sleeper", "inputs": {"input_2": 2, "input_3": false, "input_4": 0, "input_5": 0}, "inputsRequired": [], "inputNodes": ["2e348481-5042-5148-9196-590574747297", "69873032-770a-536b-adb6-0e6ea01720a4"]}, "2e348481-5042-5148-9196-590574747297": {"key": "simcore/services/frontend/parameter/number", "version": "1.0.0", "label": "X", "inputs": {}, "inputsRequired": [], "inputNodes": [], "outputs": {"out_1": 1}, "runHash": null}, "70e1de1a-a8b0-59e3-b19e-ea20f78765ce": {"key": "simcore/services/frontend/iterator-consumer/probe/number", "version": "1.0.0", "label": "Out 1", "inputs": {"in_1": 0}, "inputsRequired": [], "inputNodes": ["50a50309-1dfc-5ad5-b2d9-c11697641f0b"]}, "69873032-770a-536b-adb6-0e6ea01720a4": {"key": "simcore/services/frontend/parameter/number", "version": "1.0.0", "label": "Y", "inputs": {}, "inputsRequired": [], "inputNodes": [], "outputs": {"out_1": 1}, "runHash": null}, "24f856c3-408c-5ab4-ad01-e99630a355fe": {"key": "simcore/services/frontend/iterator-consumer/probe/number", "version": "1.0.0", "label": "Out_2", "inputs": {"in_1": 0}, "inputsRequired": [], "inputNodes": ["50a50309-1dfc-5ad5-b2d9-c11697641f0b"]}}, + "ui": { + "workbench": {"24f856c3-408c-5ab4-ad01-e99630a355fe": {"position": {"x": 540, "y": 240}}, "2e348481-5042-5148-9196-590574747297": {"position": {"x": 120, "y": 140}}, "50a50309-1dfc-5ad5-b2d9-c11697641f0b": {"position": {"x": 300, "y": 180}}, "69873032-770a-536b-adb6-0e6ea01720a4": {"position": {"x": 120, "y": 240}}, "70e1de1a-a8b0-59e3-b19e-ea20f78765ce": {"position": {"x": 540, "y": 140}}}, + "mode": "pipeline", + }, + }], + "_links": { + "next": null, + }, + }; + }, + } +}); 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 a020653ed193..d0c326539a6d 100644 --- a/services/static-webserver/client/source/class/osparc/store/Store.js +++ b/services/static-webserver/client/source/class/osparc/store/Store.js @@ -81,6 +81,7 @@ qx.Class.define("osparc.store.Store", { "workspaces", "templates", "publicTemplates", + "functions", "trash", "searchProjects", "searchTemplates", diff --git a/services/static-webserver/client/source/class/osparc/store/Templates.js b/services/static-webserver/client/source/class/osparc/store/Templates.js index 62d90bd64008..ef03e8625dfc 100644 --- a/services/static-webserver/client/source/class/osparc/store/Templates.js +++ b/services/static-webserver/client/source/class/osparc/store/Templates.js @@ -60,8 +60,8 @@ qx.Class.define("osparc.store.Templates", { .catch(err => osparc.FlashMessenger.logError(err)); }, - fetchTemplate: function(templateId) { - return osparc.store.Study.getInstance().getOne(templateId) + fetchTemplate: function(studyId) { + return osparc.store.Study.getInstance().getOne(studyId) .catch(err => console.error(err)); }, diff --git a/services/static-webserver/client/source/class/osparc/study/CreateFunction.js b/services/static-webserver/client/source/class/osparc/study/CreateFunction.js index 0c409af43d29..adb9f3b476d7 100644 --- a/services/static-webserver/client/source/class/osparc/study/CreateFunction.js +++ b/services/static-webserver/client/source/class/osparc/study/CreateFunction.js @@ -32,69 +32,6 @@ qx.Class.define("osparc.study.CreateFunction", { this.__buildLayout(); }, - statics: { - createFunctionData: function(projectData, name, description, defaultInputs = {}, exposedInputs = {}, exposedOutputs = {}) { - const functionData = { - "projectId": projectData["uuid"], - "title": name, - "description": description, - "function_class": "PROJECT", - "inputSchema": { - "schema_class": "application/schema+json", - "schema_content": { - "type": "object", - "properties": {}, - "required": [] - } - }, - "outputSchema": { - "schema_class": "application/schema+json", - "schema_content": { - "type": "object", - "properties": {}, - "required": [] - } - }, - "defaultInputs": {}, - }; - - const parameters = osparc.study.Utils.extractFunctionableParameters(projectData["workbench"]); - parameters.forEach(parameter => { - const parameterKey = parameter["label"]; - if (exposedInputs[parameterKey]) { - const parameterMetadata = osparc.store.Services.getMetadata(parameter["key"], parameter["version"]); - if (parameterMetadata) { - const type = osparc.service.Utils.getParameterType(parameterMetadata); - functionData["inputSchema"]["schema_content"]["properties"][parameterKey] = { - "type": type, - }; - functionData["inputSchema"]["schema_content"]["required"].push(parameterKey); - } - } - if (parameterKey in defaultInputs) { - functionData["defaultInputs"][parameterKey] = defaultInputs[parameterKey]; - } - }); - - const probes = osparc.study.Utils.extractFunctionableProbes(projectData["workbench"]); - probes.forEach(probe => { - const probeLabel = probe["label"]; - if (exposedOutputs[probeLabel]) { - const probeMetadata = osparc.store.Services.getMetadata(probe["key"], probe["version"]); - if (probeMetadata) { - const type = osparc.service.Utils.getProbeType(probeMetadata); - functionData["outputSchema"]["schema_content"]["properties"][probeLabel] = { - "type": type, - }; - functionData["outputSchema"]["schema_content"]["required"].push(probeLabel); - } - } - }); - - return functionData; - } - }, - members: { __studyData: null, __form: null, @@ -335,11 +272,7 @@ qx.Class.define("osparc.study.CreateFunction", { const nameField = this.__form.getItem("name"); const descriptionField = this.__form.getItem("description"); - const functionData = this.self().createFunctionData(templateData, nameField.getValue(), descriptionField.getValue(), defaultInputs, exposedInputs, exposedOutputs); - const params = { - data: functionData, - }; - osparc.data.Resources.fetch("functions", "create", params) + osparc.store.Functions.registerFunction(templateData, nameField.getValue(), descriptionField.getValue(), defaultInputs, exposedInputs, exposedOutputs) .then(() => osparc.FlashMessenger.logAs(this.tr("Function created"), "INFO")) .catch(err => osparc.FlashMessenger.logError(err)) .finally(() => this.__createFunctionBtn.setFetching(false)); diff --git a/services/static-webserver/client/source/class/osparc/study/StudyPreview.js b/services/static-webserver/client/source/class/osparc/study/StudyPreview.js index fe2a967b2b75..209e35d13f6b 100644 --- a/services/static-webserver/client/source/class/osparc/study/StudyPreview.js +++ b/services/static-webserver/client/source/class/osparc/study/StudyPreview.js @@ -26,38 +26,30 @@ qx.Class.define("osparc.study.StudyPreview", { this._setLayout(new qx.ui.layout.VBox(5)); - this.__study = study; - - this.__buildPreview(); + const uiMode = study.getUi().getMode(); + if (["workbench", "pipeline"].includes(uiMode)) { + this.__buildPreview(study); + } }, members: { - __study: null, - - __buildPreview: function() { - const study = this.__study; - + __buildPreview: function(study) { const workbenchReady = () => { - if (!study.isPipelineEmpty()) { - const workbenchUIPreview = new osparc.workbench.WorkbenchUIPreview(); - workbenchUIPreview.setStudy(study); - workbenchUIPreview.loadModel(study.getWorkbench()); - workbenchUIPreview.setMaxHeight(550); - this._add(workbenchUIPreview); - } + const workbenchUIPreview = new osparc.workbench.WorkbenchUIPreview(); + workbenchUIPreview.setStudy(study); + workbenchUIPreview.loadModel(study.getWorkbench()); + workbenchUIPreview.setMaxHeight(550); + this._add(workbenchUIPreview); }; - const uiMode = study.getUi().getMode(); - if (["workbench", "pipeline"].includes(uiMode)) { - if (study.getWorkbench().isDeserialized()) { - workbenchReady(); - } else { - study.getWorkbench().addListenerOnce("changeDeserialized", e => { - if (e.getData()) { - workbenchReady(); - } - }, this); - } + if (study.getWorkbench().isDeserialized()) { + workbenchReady(); + } else { + study.getWorkbench().addListenerOnce("changeDeserialized", e => { + if (e.getData()) { + workbenchReady(); + } + }, this); } } } 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 2dcb2db7d46f..906313beff3a 100644 --- a/services/static-webserver/client/source/class/osparc/theme/Appearance.js +++ b/services/static-webserver/client/source/class/osparc/theme/Appearance.js @@ -129,6 +129,10 @@ qx.Theme.define("osparc.theme.Appearance", { } }, + "pb-function": { + include: "pb-template", + }, + "pb-hypertool": { include: "pb-template", }, diff --git a/services/static-webserver/client/source/class/osparc/utils/Resources.js b/services/static-webserver/client/source/class/osparc/utils/Resources.js index 9d5f3c331f91..fb6a33663d0a 100644 --- a/services/static-webserver/client/source/class/osparc/utils/Resources.js +++ b/services/static-webserver/client/source/class/osparc/utils/Resources.js @@ -39,6 +39,10 @@ qx.Class.define("osparc.utils.Resources", { return ((hypertoolData["resourceType"] === "hypertool") && ("uuid" in hypertoolData)); }, + isFunction: function(functionData) { + return ((functionData["resourceType"] === "function") && ("uuid" in functionData)); + }, + isService: function(serviceData) { return ((serviceData["resourceType"] === "service") && ("key" in serviceData) && ("version" in serviceData)); }, diff --git a/services/static-webserver/client/source/class/osparc/widget/PersistentIframe.js b/services/static-webserver/client/source/class/osparc/widget/PersistentIframe.js index 81f4d9bb7ec7..6536ba238b36 100644 --- a/services/static-webserver/client/source/class/osparc/widget/PersistentIframe.js +++ b/services/static-webserver/client/source/class/osparc/widget/PersistentIframe.js @@ -332,12 +332,21 @@ qx.Class.define("osparc.widget.PersistentIframe", { } case "openFunction": { // this is the MetaModeling service trying to show function/template information - if (data["message"] && data["message"]["functionId"]) { - const templateId = data["message"]["functionId"]; - const functionData = { - "uuid": templateId, + let functionData = null; + if (data["message"] && data["message"]["uuid"]) { + // new version, the uuid is from the function + functionData = { + "uuid": data["message"]["uuid"], "resourceType": "function", }; + } else if (data["message"] && data["message"]["functionId"]) { + // old version, the uuid is from the template + functionData = { + "uuid": data["message"]["functionId"], + "resourceType": "functionedTemplate", + }; + } + if (functionData) { const { resourceDetails, window, diff --git a/services/static-webserver/client/source/class/osparc/workbench/WorkbenchUI.js b/services/static-webserver/client/source/class/osparc/workbench/WorkbenchUI.js index 367f470b1d84..d3a0671bad6a 100644 --- a/services/static-webserver/client/source/class/osparc/workbench/WorkbenchUI.js +++ b/services/static-webserver/client/source/class/osparc/workbench/WorkbenchUI.js @@ -33,9 +33,6 @@ * */ -const BUTTON_SIZE = 38; -const NODE_INPUTS_WIDTH = 210; - qx.Class.define("osparc.workbench.WorkbenchUI", { extend: qx.ui.core.Widget, @@ -56,16 +53,8 @@ qx.Class.define("osparc.workbench.WorkbenchUI", { }, statics: { - getDashedBorderStyle(isRight) { - const side = isRight ? "right" : "left"; - const borderStyle = {}; - borderStyle["background-image"] = `linear-gradient(to bottom, #3D3D3D 50%, rgba(255, 255, 255, 0) 0%)`; - borderStyle["background-position"] = side; - borderStyle["background-size"] = "5px 50px"; - borderStyle["background-repeat"] = "repeat-y"; - return borderStyle; - }, - + BUTTON_SIZE: 38, + NODE_INPUTS_WIDTH: 210, ZOOM_VALUES: [ 0.1, 0.2, @@ -83,7 +72,17 @@ qx.Class.define("osparc.workbench.WorkbenchUI", { 2, 2.5, 3 - ] + ], + + getDashedBorderStyle(isRight) { + const side = isRight ? "right" : "left"; + const borderStyle = {}; + borderStyle["background-image"] = `linear-gradient(to bottom, #3D3D3D 50%, rgba(255, 255, 255, 0) 0%)`; + borderStyle["background-position"] = side; + borderStyle["background-size"] = "5px 50px"; + borderStyle["background-repeat"] = "repeat-y"; + return borderStyle; + }, }, events: { @@ -227,8 +226,8 @@ qx.Class.define("osparc.workbench.WorkbenchUI", { __addDeleteItemButton: function() { const deleteItemButton = this.__deleteItemButton = new qx.ui.form.Button().set({ icon: "@FontAwesome5Solid/trash/18", - width: BUTTON_SIZE, - height: BUTTON_SIZE, + width: this.self().BUTTON_SIZE, + height: this.self().BUTTON_SIZE, visibility: "excluded" }); deleteItemButton.addListener("execute", () => { @@ -271,8 +270,8 @@ qx.Class.define("osparc.workbench.WorkbenchUI", { const label = isInput ? this.tr("INPUTS") : this.tr("OUTPUTS"); const inputOutputNodesLayout = new qx.ui.container.Composite(new qx.ui.layout.VBox(5)); inputOutputNodesLayout.set({ - width: NODE_INPUTS_WIDTH, - maxWidth: NODE_INPUTS_WIDTH, + width: this.self().NODE_INPUTS_WIDTH, + maxWidth: this.self().NODE_INPUTS_WIDTH, allowGrowX: false, padding: [0, 6] }); @@ -419,7 +418,7 @@ qx.Class.define("osparc.workbench.WorkbenchUI", { nodeUI.addListener("appear", () => this.__updateNodeUIPos(nodeUI), this); - const isStudyReadOnly = this.getStudy().isReadOnly(); + const isStudyReadOnly = this.isPropertyInitialized("study") ? this.getStudy().isReadOnly() : true; nodeUI.set({ movable: !isStudyReadOnly, selectable: !isStudyReadOnly,