From 716869faf5f9b9918b6ae5369581bb5560eecffd Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Wed, 19 Feb 2025 09:38:56 +0100 Subject: [PATCH 01/17] show free models category if any --- .../client/source/class/osparc/vipMarket/Market.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/services/static-webserver/client/source/class/osparc/vipMarket/Market.js b/services/static-webserver/client/source/class/osparc/vipMarket/Market.js index 3fcc2d281fc..a54e90d81f2 100644 --- a/services/static-webserver/client/source/class/osparc/vipMarket/Market.js +++ b/services/static-webserver/client/source/class/osparc/vipMarket/Market.js @@ -129,7 +129,9 @@ qx.Class.define("osparc.vipMarket.Market", { freeCategory["items"].push(licensedItem); } } - this.__buildViPMarketPage(freeCategory, freeCategory["items"]); + if (freeCategory["items"].length) { + this.__buildViPMarketPage(freeCategory, freeCategory["items"]); + } }); }, From 3bef944a4b69ad922e6f786138fb105fbcfb7c05 Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Wed, 19 Feb 2025 09:50:10 +0100 Subject: [PATCH 02/17] fix guests --- .../class/osparc/dashboard/NewPlusMenu.js | 34 ++++++++++--------- .../class/osparc/dashboard/StudyBrowser.js | 16 +++++---- 2 files changed, 27 insertions(+), 23 deletions(-) diff --git a/services/static-webserver/client/source/class/osparc/dashboard/NewPlusMenu.js b/services/static-webserver/client/source/class/osparc/dashboard/NewPlusMenu.js index 550535d63ba..b1c0b19a189 100644 --- a/services/static-webserver/client/source/class/osparc/dashboard/NewPlusMenu.js +++ b/services/static-webserver/client/source/class/osparc/dashboard/NewPlusMenu.js @@ -160,24 +160,26 @@ qx.Class.define("osparc.dashboard.NewPlusMenu", { }, __addNewStudyItems: async function() { - await osparc.data.Resources.get("templates") - .then(templates => { - const plusButtonConfig = osparc.store.Products.getInstance().getPlusButtonUiConfig(); - if (plusButtonConfig["categories"]) { - this.__addCategories(plusButtonConfig["categories"]); - } - plusButtonConfig["resources"].forEach(newStudyData => { - if (newStudyData["showDisabled"]) { - this.__addDisabledButton(newStudyData); - } else if (newStudyData["resourceType"] === "study") { - this.__addEmptyStudyButton(newStudyData); - } else if (newStudyData["resourceType"] === "template") { - this.__addFromTemplateButton(newStudyData, templates); - } else if (newStudyData["resourceType"] === "service") { - this.__addFromServiceButton(newStudyData); + const plusButtonConfig = osparc.store.Products.getInstance().getPlusButtonUiConfig(); + if (plusButtonConfig) { + await osparc.data.Resources.get("templates") + .then(templates => { + if (plusButtonConfig["categories"]) { + this.__addCategories(plusButtonConfig["categories"]); } + plusButtonConfig["resources"].forEach(newStudyData => { + if (newStudyData["showDisabled"]) { + this.__addDisabledButton(newStudyData); + } else if (newStudyData["resourceType"] === "study") { + this.__addEmptyStudyButton(newStudyData); + } else if (newStudyData["resourceType"] === "template") { + this.__addFromTemplateButton(newStudyData, templates); + } else if (newStudyData["resourceType"] === "service") { + this.__addFromServiceButton(newStudyData); + } + }); }); - }); + } }, __getLastIdxFromCategory: function(categoryId) { 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 469e3e6bcae..176a78a754e 100644 --- a/services/static-webserver/client/source/class/osparc/dashboard/StudyBrowser.js +++ b/services/static-webserver/client/source/class/osparc/dashboard/StudyBrowser.js @@ -949,13 +949,15 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { __addPlusButtons: function() { const plusButtonConfig = osparc.store.Products.getInstance().getNewStudiesUiConfig(); - plusButtonConfig["resources"].forEach(newStudyData => { - if (newStudyData["resourceType"] === "study") { - this.__addEmptyStudyPlusButton(newStudyData); - } else if (newStudyData["resourceType"] === "service") { - this.__addNewStudyFromServiceButton(newStudyData); - } - }); + if (plusButtonConfig) { + plusButtonConfig["resources"].forEach(newStudyData => { + if (newStudyData["resourceType"] === "study") { + this.__addEmptyStudyPlusButton(newStudyData); + } else if (newStudyData["resourceType"] === "service") { + this.__addNewStudyFromServiceButton(newStudyData); + } + }); + } }, __addEmptyStudyPlusButton: function(newStudyData) { From fdacd0701b6aebc5160f430eb918daa4312ac55c Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Wed, 19 Feb 2025 10:25:29 +0100 Subject: [PATCH 03/17] return cached Pricing Units --- .../client/source/class/osparc/store/Pricing.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/services/static-webserver/client/source/class/osparc/store/Pricing.js b/services/static-webserver/client/source/class/osparc/store/Pricing.js index b43ec07f806..572a4b95e0d 100644 --- a/services/static-webserver/client/source/class/osparc/store/Pricing.js +++ b/services/static-webserver/client/source/class/osparc/store/Pricing.js @@ -82,6 +82,9 @@ qx.Class.define("osparc.store.Pricing", { }, fetchPricingUnits: function(pricingPlanId) { + if (this.getPricingPlan(pricingPlanId)) { + return new Promise(resolve => resolve(this.getPricingPlan(pricingPlanId).getPricingUnits())); + } const params = { url: { pricingPlanId, From affb19133c186ac7331ad2f0cc861f5dc3c977ef Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Wed, 19 Feb 2025 10:52:56 +0100 Subject: [PATCH 04/17] minor --- .../osparc/vipMarket/AnatomicalModelDetails.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelDetails.js b/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelDetails.js index f85e3a3f7b3..e73b8a09381 100644 --- a/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelDetails.js +++ b/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelDetails.js @@ -375,12 +375,6 @@ qx.Class.define("osparc.vipMarket.AnatomicalModelDetails", { this._add(pricingUnitsLayout); pricingLayout.add(pricingUnitsLayout) - const poweredByLabel = new qx.ui.basic.Label().set({ - font: "text-14", - padding: 10, - rich: true, - alignX: "center", - }); osparc.store.LicensedItems.getInstance().getLicensedItems() .then(licensedItems => { const lowerLicensedItems = osparc.store.LicensedItems.getLowerLicensedItems(licensedItems, licensedItemData["key"], licensedItemData["version"]) @@ -394,8 +388,14 @@ qx.Class.define("osparc.vipMarket.AnatomicalModelDetails", { text += `- ${osparc.store.LicensedItems.licensedResourceNameAndVersion(licensedResource)}
`; }); }) - poweredByLabel.setValue(text); - pricingLayout.add(poweredByLabel); + const bundleText = new qx.ui.basic.Label().set({ + value: text, + font: "text-14", + padding: 10, + rich: true, + alignX: "center", + }); + pricingLayout.add(bundleText); } }); From e19df141df53557d5ac8bd6991110260aeafcef1 Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Wed, 19 Feb 2025 15:43:22 +0100 Subject: [PATCH 05/17] Market -> Model Market --- .../client/source/class/osparc/navigation/UserMenu.js | 2 +- .../client/source/class/osparc/vipMarket/MarketWindow.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/services/static-webserver/client/source/class/osparc/navigation/UserMenu.js b/services/static-webserver/client/source/class/osparc/navigation/UserMenu.js index aa385449834..47065547fff 100644 --- a/services/static-webserver/client/source/class/osparc/navigation/UserMenu.js +++ b/services/static-webserver/client/source/class/osparc/navigation/UserMenu.js @@ -86,7 +86,7 @@ qx.Class.define("osparc.navigation.UserMenu", { this.add(control); break; case "market": - control = new qx.ui.menu.Button(this.tr("Market")); + control = new qx.ui.menu.Button(this.tr("Model Market")); control.addListener("execute", () => osparc.vipMarket.MarketWindow.openWindow()); this.add(control); break; diff --git a/services/static-webserver/client/source/class/osparc/vipMarket/MarketWindow.js b/services/static-webserver/client/source/class/osparc/vipMarket/MarketWindow.js index 8f230d2a9e5..1297a02b83b 100644 --- a/services/static-webserver/client/source/class/osparc/vipMarket/MarketWindow.js +++ b/services/static-webserver/client/source/class/osparc/vipMarket/MarketWindow.js @@ -19,7 +19,7 @@ qx.Class.define("osparc.vipMarket.MarketWindow", { extend: osparc.ui.window.TabbedWindow, construct: function(nodeId, category) { - this.base(arguments, "store", this.tr("Market")); + this.base(arguments, "store", this.tr("Model Market")); osparc.utils.Utils.setIdToWidget(this, "storeWindow"); From 45b542fe4e285da479661410a1c4fd70e7085e05 Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Wed, 19 Feb 2025 16:06:09 +0100 Subject: [PATCH 06/17] renaming --- .../osparc/vipMarket/AnatomicalModelDetails.js | 13 +++++++------ .../source/class/osparc/vipMarket/VipMarket.js | 6 +++--- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelDetails.js b/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelDetails.js index e73b8a09381..0bc5afdde98 100644 --- a/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelDetails.js +++ b/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelDetails.js @@ -70,8 +70,8 @@ qx.Class.define("osparc.vipMarket.AnatomicalModelDetails", { __populateLayout: function() { this._removeAll(); - const anatomicalModelsData = this.getAnatomicalModelsData(); - if (anatomicalModelsData && anatomicalModelsData["licensedResources"].length) { + const licensedItemBundleData = this.getAnatomicalModelsData(); + if (licensedItemBundleData && licensedItemBundleData["licensedResources"].length) { this.__addModelsInfo(); this.__addPricing(); this.__addSeatsSection(); @@ -94,8 +94,8 @@ qx.Class.define("osparc.vipMarket.AnatomicalModelDetails", { this.__selectedModelLayout = new qx.ui.container.Composite(new qx.ui.layout.VBox(6)); modelLayout.add(this.__selectedModelLayout); - const anatomicalModelsData = this.getAnatomicalModelsData(); - const modelsInfo = anatomicalModelsData["licensedResources"]; + const licensedItemBundleData = this.getAnatomicalModelsData(); + const modelsInfo = licensedItemBundleData["licensedResources"]; if (modelsInfo.length > 1) { const modelSelectionLayout = new qx.ui.container.Composite(new qx.ui.layout.VBox(4)); const titleLabel = new qx.ui.basic.Label(this.tr("This bundle contains:")); @@ -133,11 +133,12 @@ qx.Class.define("osparc.vipMarket.AnatomicalModelDetails", { __populateSelectedModelInfo: function(selectedIdx = 0) { this.__selectedModelLayout.removeAll(); - const anatomicalModelsData = this.getAnatomicalModelsData(); + const licensedItemBundleData = this.getAnatomicalModelsData(); const topGrid = new qx.ui.layout.Grid(8, 6); topGrid.setColumnFlex(0, 1); const headerLayout = new qx.ui.container.Composite(topGrid); + const anatomicalModel = licensedItemBundleData["licensedResources"][selectedIdx]["source"]; const anatomicalModel = anatomicalModelsData["licensedResources"][selectedIdx]["source"]; let description = anatomicalModel["description"] || ""; description = description.replace(/SPEAG/g, " "); // remove SPEAG substring @@ -288,7 +289,7 @@ qx.Class.define("osparc.vipMarket.AnatomicalModelDetails", { this.__selectedModelLayout.add(middleLayout); - const importSection = this.__createImportSection(anatomicalModelsData, selectedIdx); + const importSection = this.__createImportSection(licensedItemBundleData, selectedIdx); this.__selectedModelLayout.add(importSection); }, diff --git a/services/static-webserver/client/source/class/osparc/vipMarket/VipMarket.js b/services/static-webserver/client/source/class/osparc/vipMarket/VipMarket.js index 1599bddbcb6..65e33f5553d 100644 --- a/services/static-webserver/client/source/class/osparc/vipMarket/VipMarket.js +++ b/services/static-webserver/client/source/class/osparc/vipMarket/VipMarket.js @@ -163,9 +163,9 @@ qx.Class.define("osparc.vipMarket.VipMarket", { const selection = e.getData(); if (selection.length) { const licensedItemId = selection[0].getLicensedItemId(); - const bundleFound = this.__anatomicalBundles.find(anatomicalBundle => anatomicalBundle["licensedItemId"] === licensedItemId); - if (bundleFound) { - anatomicModelDetails.setAnatomicalModelsData(bundleFound); + const licensedItemBundle = this.__anatomicalBundles.find(anatomicalBundle => anatomicalBundle["licensedItemId"] === licensedItemId); + if (licensedItemBundle) { + anatomicModelDetails.setAnatomicalModelsData(licensedItemBundle); return; } } From ecdfad15da142d7ab75f3a36ff1ec2100bd451f5 Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Wed, 19 Feb 2025 16:25:58 +0100 Subject: [PATCH 07/17] Term and Conditions in features --- .../class/osparc/study/PricingUnitLicense.js | 38 +------------------ .../vipMarket/AnatomicalModelDetails.js | 36 ++++++++++++++++-- .../class/osparc/vipMarket/MarketWindow.js | 2 +- 3 files changed, 34 insertions(+), 42 deletions(-) diff --git a/services/static-webserver/client/source/class/osparc/study/PricingUnitLicense.js b/services/static-webserver/client/source/class/osparc/study/PricingUnitLicense.js index a73a45a5515..08f37eafc70 100644 --- a/services/static-webserver/client/source/class/osparc/study/PricingUnitLicense.js +++ b/services/static-webserver/client/source/class/osparc/study/PricingUnitLicense.js @@ -29,13 +29,6 @@ qx.Class.define("osparc.study.PricingUnitLicense", { nullable: true, event: "changeShowRentButton" }, - - licenseUrl: { - check: "String", - init: false, - nullable: true, - event: "changeLicenseUrl" - }, }, statics: { @@ -97,27 +90,12 @@ qx.Class.define("osparc.study.PricingUnitLicense", { let msg = this.getUnitData().getName() + this.tr(" will be available until ") + osparc.utils.Utils.formatDate(expirationDate); msg += "
"; msg += `The rental will cost ${nCredits} credits`; + msg += `I hereby accept the Terms and Conditions`; const confirmationWin = new osparc.ui.window.Confirmation(msg).set({ caption: this.tr("Rent"), confirmText: this.tr("Rent"), }); - if (this.getLicenseUrl()) { - const useCacheCB = new qx.ui.form.CheckBox().set({ - value: false, - label: this.tr("Accept Terms and Conditions"), - rich: true, - }); - useCacheCB.getChildControl("label").set({ - anonymous: false, - cursor: "pointer", - }); - useCacheCB.getChildControl("label").addListener("tap", () => this.__openLicense()); - confirmationWin.addWidget(useCacheCB); - confirmationWin.getExtraWidgetsLayout().setPaddingTop(0); // reset paddingTop - useCacheCB.bind("value", confirmationWin.getConfirmButton(), "enabled"); - } - confirmationWin.open(); confirmationWin.addListener("close", () => { if (confirmationWin.getConfirmed()) { @@ -125,19 +103,5 @@ qx.Class.define("osparc.study.PricingUnitLicense", { } }, this); }, - - __openLicense: function() { - let rawLink = this.getLicenseUrl() || ""; - if (rawLink.includes("github")) { - // make sure the raw version of the link is shown - rawLink += "?raw=true"; - } - const mdWindow = new osparc.ui.markdown.MarkdownWindow(rawLink).set({ - caption: this.tr("Terms and Conditions"), - width: 800, - height: 600, - }); - mdWindow.open(); - }, } }); diff --git a/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelDetails.js b/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelDetails.js index 0bc5afdde98..6340b5cebd8 100644 --- a/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelDetails.js +++ b/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelDetails.js @@ -139,7 +139,6 @@ qx.Class.define("osparc.vipMarket.AnatomicalModelDetails", { topGrid.setColumnFlex(0, 1); const headerLayout = new qx.ui.container.Composite(topGrid); const anatomicalModel = licensedItemBundleData["licensedResources"][selectedIdx]["source"]; - const anatomicalModel = anatomicalModelsData["licensedResources"][selectedIdx]["source"]; let description = anatomicalModel["description"] || ""; description = description.replace(/SPEAG/g, " "); // remove SPEAG substring const delimiter = " - "; @@ -257,7 +256,7 @@ qx.Class.define("osparc.vipMarket.AnatomicalModelDetails", { value: "DOI", font: "text-14", alignX: "right", - marginTop: 16, + marginTop: 10, }); featuresLayout.add(doiTitle, { column: 0, @@ -268,7 +267,7 @@ qx.Class.define("osparc.vipMarket.AnatomicalModelDetails", { const doiLabel = new osparc.ui.basic.LinkLabel("-").set({ font: "text-14", alignX: "left", - marginTop: 16, + marginTop: 10, }); if (doi) { doiLabel.set({ @@ -283,6 +282,23 @@ qx.Class.define("osparc.vipMarket.AnatomicalModelDetails", { column: 1, row: idx, }); + idx++; + } + + if (licensedItemBundleData["termsOfUseUrl"] || anatomicalModel["termsOfUseUrl"]) { // remove the first one when this info goes down to the model + const tAndC = new qx.ui.basic.Label().set({ + font: "text-14", + value: this.tr("Terms and Conditions"), + rich: true, + anonymous: false, + cursor: "pointer", + }); + tAndC.addListener("tap", () => this.__openLicense(licensedItemBundleData["termsOfUseUrl"] || anatomicalModel["termsOfUseUrl"])); + featuresLayout.add(tAndC, { + column: 1, + row: idx, + }); + idx++; } middleLayout.add(featuresLayout); @@ -293,6 +309,19 @@ qx.Class.define("osparc.vipMarket.AnatomicalModelDetails", { this.__selectedModelLayout.add(importSection); }, + __openLicense: function(rawLink) { + if (rawLink.includes("github")) { + // make sure the raw version of the link is shown + rawLink += "?raw=true"; + } + const mdWindow = new osparc.ui.markdown.MarkdownWindow(rawLink).set({ + caption: this.tr("Terms and Conditions"), + width: 800, + height: 600, + }); + mdWindow.open(); + }, + __createImportSection: function(anatomicalModelsData, selectedIdx) { const importSection = new qx.ui.container.Composite(new qx.ui.layout.VBox().set({ alignX: "center" @@ -359,7 +388,6 @@ qx.Class.define("osparc.vipMarket.AnatomicalModelDetails", { }); const pUnit = new osparc.study.PricingUnitLicense(pricingUnit).set({ showRentButton: true, - licenseUrl: licensedItemData["termsOfUseUrl"], }); pUnit.addListener("rentPricingUnit", () => { this.fireDataEvent("modelPurchaseRequested", { diff --git a/services/static-webserver/client/source/class/osparc/vipMarket/MarketWindow.js b/services/static-webserver/client/source/class/osparc/vipMarket/MarketWindow.js index 1297a02b83b..4c8cb238b6d 100644 --- a/services/static-webserver/client/source/class/osparc/vipMarket/MarketWindow.js +++ b/services/static-webserver/client/source/class/osparc/vipMarket/MarketWindow.js @@ -23,7 +23,7 @@ qx.Class.define("osparc.vipMarket.MarketWindow", { osparc.utils.Utils.setIdToWidget(this, "storeWindow"); - const width = 1080; + const width = Math.min(1100, window.innerWidth); // since we go over the supported minimum, take the min const height = 700; this.set({ width, From 9cd52754b3ea954e6e8eb947f2f02ba26ec0e973 Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Wed, 19 Feb 2025 16:29:22 +0100 Subject: [PATCH 08/17] Remove bundle info from Rental section --- .../vipMarket/AnatomicalModelDetails.js | 26 +------------------ 1 file changed, 1 insertion(+), 25 deletions(-) diff --git a/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelDetails.js b/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelDetails.js index 6340b5cebd8..605632b6463 100644 --- a/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelDetails.js +++ b/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelDetails.js @@ -402,31 +402,7 @@ qx.Class.define("osparc.vipMarket.AnatomicalModelDetails", { }) .catch(err => console.error(err)); this._add(pricingUnitsLayout); - pricingLayout.add(pricingUnitsLayout) - - osparc.store.LicensedItems.getInstance().getLicensedItems() - .then(licensedItems => { - const lowerLicensedItems = osparc.store.LicensedItems.getLowerLicensedItems(licensedItems, licensedItemData["key"], licensedItemData["version"]) - if (licensedItemData["licensedResources"].length > 1 || lowerLicensedItems.length) { - let text = this.tr("This bundle gives you access to:") + "
"; - licensedItemData["licensedResources"].forEach(licensedResource => { - text += `- ${osparc.store.LicensedItems.licensedResourceNameAndVersion(licensedResource)}
`; - }); - lowerLicensedItems.forEach(lowerLicensedItem => { - lowerLicensedItem["licensedResources"].forEach(licensedResource => { - text += `- ${osparc.store.LicensedItems.licensedResourceNameAndVersion(licensedResource)}
`; - }); - }) - const bundleText = new qx.ui.basic.Label().set({ - value: text, - font: "text-14", - padding: 10, - rich: true, - alignX: "center", - }); - pricingLayout.add(bundleText); - } - }); + pricingLayout.add(pricingUnitsLayout); this._add(layout); }, From 195044762ce99aefa98c1bb77feb9846cec02817 Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Wed, 19 Feb 2025 16:40:30 +0100 Subject: [PATCH 09/17] functionality in name --- .../client/source/class/osparc/store/LicensedItems.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/services/static-webserver/client/source/class/osparc/store/LicensedItems.js b/services/static-webserver/client/source/class/osparc/store/LicensedItems.js index 1dcfaef2e23..8331e95bb8c 100644 --- a/services/static-webserver/client/source/class/osparc/store/LicensedItems.js +++ b/services/static-webserver/client/source/class/osparc/store/LicensedItems.js @@ -70,10 +70,10 @@ qx.Class.define("osparc.store.LicensedItems", { return nSeats; }, - licensedResourceNameAndVersion: function(licensedResource) { + licensedResourceTitle: function(licensedResource) { const name = licensedResource["source"]["features"]["name"] || osparc.store.LicensedItems.extractNameFromDescription(licensedResource); - const version = licensedResource["source"]["features"]["version"] || ""; - return `${name} ${version}`; + const functionality = licensedResource["source"]["features"]["functionality"] || "Static"; + return `${name}, ${functionality}`; }, extractNameFromDescription: function(licensedResource) { From 862cf06c3be96573511d8f722c22f27b92faf5c0 Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Wed, 19 Feb 2025 16:40:43 +0100 Subject: [PATCH 10/17] Vertical thumbnails with name --- .../vipMarket/AnatomicalModelDetails.js | 29 +++++++++++-------- 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelDetails.js b/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelDetails.js index 605632b6463..b60b32bd5c5 100644 --- a/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelDetails.js +++ b/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelDetails.js @@ -89,10 +89,10 @@ qx.Class.define("osparc.vipMarket.AnatomicalModelDetails", { }, __addModelsInfo: function() { - const modelLayout = new qx.ui.container.Composite(new qx.ui.layout.VBox(6)); + const modelBundleLayout = new qx.ui.container.Composite(new qx.ui.layout.VBox(6)); this.__selectedModelLayout = new qx.ui.container.Composite(new qx.ui.layout.VBox(6)); - modelLayout.add(this.__selectedModelLayout); + modelBundleLayout.add(this.__selectedModelLayout); const licensedItemBundleData = this.getAnatomicalModelsData(); const modelsInfo = licensedItemBundleData["licensedResources"]; @@ -100,26 +100,31 @@ qx.Class.define("osparc.vipMarket.AnatomicalModelDetails", { const modelSelectionLayout = new qx.ui.container.Composite(new qx.ui.layout.VBox(4)); const titleLabel = new qx.ui.basic.Label(this.tr("This bundle contains:")); modelSelectionLayout.add(titleLabel); - const thumbnailsLayout = new qx.ui.container.Composite(new qx.ui.layout.HBox(2)); - modelSelectionLayout.add(thumbnailsLayout); + const modelsLayout = new qx.ui.container.Composite(new qx.ui.layout.VBox(4)); + modelSelectionLayout.add(modelsLayout); const thumbnailTapped = idx => { this.__populateSelectedModelInfo(idx); const selectedBorderColor = qx.theme.manager.Color.getInstance().resolve("strong-main"); const unselectedBorderColor = "transparent"; - thumbnailsLayout.getChildren().forEach((thumbnail, index) => { + modelsLayout.getChildren().forEach((thumbnailAndTitle, index) => { + const thumbnail = thumbnailAndTitle.getChildren()[0]; osparc.utils.Utils.updateBorderColor(thumbnail, index === idx ? selectedBorderColor : unselectedBorderColor); }); } modelsInfo.forEach((modelInfo, idx) => { + const modelLayout = new qx.ui.container.Composite(new qx.ui.layout.HBox(4)); const miniThumbnail = this.self().createThumbnail(modelInfo["source"]["thumbnail"], 32); - miniThumbnail.set({ - toolTipText: osparc.store.LicensedItems.licensedResourceNameAndVersion(modelInfo), - }); osparc.utils.Utils.addBorder(miniThumbnail); + modelLayout.add(miniThumbnail); miniThumbnail.addListener("tap", () => thumbnailTapped(idx)); - thumbnailsLayout.add(miniThumbnail); + const title = new qx.ui.basic.Label().set({ + value: osparc.store.LicensedItems.licensedResourceTitle(modelInfo), + alignY: "middle" + }); + modelLayout.add(title); + modelsLayout.add(modelLayout); }); - modelLayout.add(modelSelectionLayout); + modelBundleLayout.add(modelSelectionLayout); thumbnailTapped(0); this.__populateSelectedModelInfo(); @@ -127,7 +132,7 @@ qx.Class.define("osparc.vipMarket.AnatomicalModelDetails", { this.__populateSelectedModelInfo(); } - this._add(modelLayout); + this._add(modelBundleLayout); }, __populateSelectedModelInfo: function(selectedIdx = 0) { @@ -445,7 +450,7 @@ qx.Class.define("osparc.vipMarket.AnatomicalModelDetails", { rowIdx++; const entryToGrid = (licensedResource, seat, row) => { - const title = osparc.store.LicensedItems.licensedResourceNameAndVersion(licensedResource); + const title = osparc.store.LicensedItems.licensedResourceTitle(licensedResource); seatsSection.add(new qx.ui.basic.Label(title).set({font: "text-14"}), { column: 0, row, From 37921b6c926c42ac006ef8b832a6113047dfd608 Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Wed, 19 Feb 2025 16:57:47 +0100 Subject: [PATCH 11/17] My Models --- .../class/osparc/study/PricingUnitLicense.js | 5 +- .../source/class/osparc/vipMarket/Market.js | 50 ++++++++----------- 2 files changed, 24 insertions(+), 31 deletions(-) diff --git a/services/static-webserver/client/source/class/osparc/study/PricingUnitLicense.js b/services/static-webserver/client/source/class/osparc/study/PricingUnitLicense.js index 08f37eafc70..00db92b60e1 100644 --- a/services/static-webserver/client/source/class/osparc/study/PricingUnitLicense.js +++ b/services/static-webserver/client/source/class/osparc/study/PricingUnitLicense.js @@ -88,9 +88,8 @@ qx.Class.define("osparc.study.PricingUnitLicense", { const nCredits = this.getUnitData().getCost(); const expirationDate = osparc.study.PricingUnitLicense.getExpirationDate(); let msg = this.getUnitData().getName() + this.tr(" will be available until ") + osparc.utils.Utils.formatDate(expirationDate); - msg += "
"; - msg += `The rental will cost ${nCredits} credits`; - msg += `I hereby accept the Terms and Conditions`; + msg += `
The rental will cost ${nCredits} credits`; + msg += `
I hereby accept the Terms and Conditions`; const confirmationWin = new osparc.ui.window.Confirmation(msg).set({ caption: this.tr("Rent"), confirmText: this.tr("Rent"), diff --git a/services/static-webserver/client/source/class/osparc/vipMarket/Market.js b/services/static-webserver/client/source/class/osparc/vipMarket/Market.js index a54e90d81f2..ec5f0d38e9a 100644 --- a/services/static-webserver/client/source/class/osparc/vipMarket/Market.js +++ b/services/static-webserver/client/source/class/osparc/vipMarket/Market.js @@ -50,8 +50,8 @@ qx.Class.define("osparc.vipMarket.Market", { }, members: { - __purchasedCategoryButton: null, - __purchasedCategoryMarket: null, + __myModelsCategoryMarket: null, + __myModelsCategoryButton: null, __populateCategories: function(openCategory) { const store = osparc.store.Store.getInstance(); @@ -68,18 +68,18 @@ qx.Class.define("osparc.vipMarket.Market", { const purchasedItems = values[1]; osparc.store.LicensedItems.populateSeatsFromPurchases(licensedItems, purchasedItems); const categories = []; - const purchasedCategory = { - categoryId: "purchasedModels", - label: this.tr("Rented"), + const availableCategory = { + categoryId: "availableModels", + label: this.tr("My Models"), icon: "osparc/market/RentedModels.svg", items: [], }; - categories.push(purchasedCategory); + categories.push(availableCategory); licensedItems.forEach(licensedItem => { if (licensedItem["seats"].length) { - purchasedCategory["items"].push(licensedItem); + availableCategory["items"].push(licensedItem); if (!openCategory) { - openCategory = purchasedCategory["categoryId"]; + openCategory = availableCategory["categoryId"]; } } if (licensedItem && licensedItem["categoryId"]) { @@ -109,29 +109,22 @@ qx.Class.define("osparc.vipMarket.Market", { this.__openCategory(openCategory); } - this.__addFreeCategory(); + this.__addFreeItems(); }); }, - __addFreeCategory: function() { - const freeCategory = { - categoryId: "freeModels", - label: this.tr("Open Access Models"), - icon: "osparc/market/RentedModels.svg", - items: [], - }; + __addFreeItems: function() { const licensedItemsStore = osparc.store.LicensedItems.getInstance(); licensedItemsStore.getLicensedItems() .then(async licensedItems => { + this.__freeItems = []; for (const licensedItem of licensedItems) { const pricingUnits = await osparc.store.Pricing.getInstance().fetchPricingUnits(licensedItem["pricingPlanId"]); if (pricingUnits.length === 1 && pricingUnits[0].getCost() === 0) { - freeCategory["items"].push(licensedItem); + this.__freeItems.push(licensedItem); } } - if (freeCategory["items"].length) { - this.__buildViPMarketPage(freeCategory, freeCategory["items"]); - } + this.__repopulateMyModelsCategory(); }); }, @@ -141,19 +134,19 @@ qx.Class.define("osparc.vipMarket.Market", { category: marketTabInfo["categoryId"], }); this.bind("openBy", vipMarketView, "openBy"); - vipMarketView.addListener("modelPurchased", () => this.__repopulatePurchasedCategory()); + vipMarketView.addListener("modelPurchased", () => this.__repopulateMyModelsCategory()); vipMarketView.addListener("importMessageSent", () => this.fireEvent("importMessageSent")); const page = this.addTab(marketTabInfo["label"], marketTabInfo["icon"], vipMarketView); page.category = marketTabInfo["categoryId"]; - if (page.category === "purchasedModels") { - this.__purchasedCategoryMarket = vipMarketView; - this.__purchasedCategoryButton = page.getChildControl("button"); - this.__purchasedCategoryButton.setVisibility(licensedItems.length ? "visible" : "excluded"); + if (page.category === "availableModels") { + this.__myModelsCategoryMarket = vipMarketView; + this.__myModelsCategoryButton = page.getChildControl("button"); + this.__myModelsCategoryButton.setVisibility(licensedItems.length ? "visible" : "excluded"); } return page; }, - __repopulatePurchasedCategory: function() { + __repopulateMyModelsCategory: function() { const store = osparc.store.Store.getInstance(); const contextWallet = store.getContextWallet(); const walletId = contextWallet.getWalletId(); @@ -172,8 +165,9 @@ qx.Class.define("osparc.vipMarket.Market", { items.push(licensedItem); } }); - this.__purchasedCategoryButton.setVisibility(items.length ? "visible" : "excluded"); - this.__purchasedCategoryMarket.setLicensedItems(items); + items = items.concat(this.__freeItems); + this.__myModelsCategoryButton.setVisibility(items.length ? "visible" : "excluded"); + this.__myModelsCategoryMarket.setLicensedItems(items); }); }, From 0b451b91b75c0bdeac9088904cc823b813dc7209 Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Wed, 19 Feb 2025 17:06:43 +0100 Subject: [PATCH 12/17] seats wording --- .../vipMarket/AnatomicalModelDetails.js | 79 +++---------------- 1 file changed, 13 insertions(+), 66 deletions(-) diff --git a/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelDetails.js b/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelDetails.js index b60b32bd5c5..282b4cc80c4 100644 --- a/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelDetails.js +++ b/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelDetails.js @@ -73,8 +73,8 @@ qx.Class.define("osparc.vipMarket.AnatomicalModelDetails", { const licensedItemBundleData = this.getAnatomicalModelsData(); if (licensedItemBundleData && licensedItemBundleData["licensedResources"].length) { this.__addModelsInfo(); - this.__addPricing(); this.__addSeatsSection(); + this.__addPricing(); } else { const selectModelLabel = new qx.ui.basic.Label().set({ value: this.tr("Select a model for more details"), @@ -417,74 +417,21 @@ qx.Class.define("osparc.vipMarket.AnatomicalModelDetails", { if (licensedItemData["seats"].length === 0) { return; } - - const layout = new qx.ui.container.Composite(new qx.ui.layout.VBox().set({ - alignX: "center", + const seatsSection = new qx.ui.container.Composite(new qx.ui.layout.VBox(5).set({ + alignX: "left", })); - osparc.store.LicensedItems.getInstance().getLicensedItems() - .then(licensedItems => { - const grid = new qx.ui.layout.Grid(15, 5); - grid.setColumnAlign(0, "left", "middle"); - grid.setColumnAlign(1, "center", "middle"); - grid.setColumnAlign(2, "right", "middle"); - const seatsSection = new qx.ui.container.Composite(grid).set({ - allowGrowX: false, - decorator: "border", - padding: 10, - }); - - let rowIdx = 0; - seatsSection.add(new qx.ui.basic.Label("Models Rented").set({font: "title-14"}), { - column: 0, - row: rowIdx, - }); - seatsSection.add(new qx.ui.basic.Label("Seats").set({font: "title-14"}), { - column: 1, - row: rowIdx, - }); - seatsSection.add(new qx.ui.basic.Label("Until").set({font: "title-14"}), { - column: 2, - row: rowIdx, - }); - rowIdx++; - - const entryToGrid = (licensedResource, seat, row) => { - const title = osparc.store.LicensedItems.licensedResourceTitle(licensedResource); - seatsSection.add(new qx.ui.basic.Label(title).set({font: "text-14"}), { - column: 0, - row, - }); - seatsSection.add(new qx.ui.basic.Label(seat["numOfSeats"].toString()).set({font: "text-14"}), { - column: 1, - row, - }); - seatsSection.add(new qx.ui.basic.Label(osparc.utils.Utils.formatDate(seat["expireAt"])).set({font: "text-14"}), { - column: 2, - row, - }); - }; - - licensedItemData["seats"].forEach(seat => { - licensedItemData["licensedResources"].forEach(licensedResource => { - entryToGrid(licensedResource, seat, rowIdx); - rowIdx++; - }); - }); - - const lowerLicensedItems = osparc.store.LicensedItems.getLowerLicensedItems(licensedItems, licensedItemData["key"], licensedItemData["version"]) - lowerLicensedItems.forEach(lowerLicensedItem => { - lowerLicensedItem["seats"].forEach(seat => { - lowerLicensedItem["licensedResources"].forEach(licensedResource => { - entryToGrid(licensedResource, seat, rowIdx); - rowIdx++; - }); - }); - }); - - layout.add(seatsSection); - this._add(layout); + licensedItemData["seats"].forEach(purchase => { + const nSeats = purchase["numOfSeats"]; + const seatsText = "seat" + (nSeats > 1 ? "s" : ""); + const entry = new qx.ui.basic.Label().set({ + value: `${nSeats} ${seatsText} available until ${osparc.utils.Utils.formatDate(purchase["expireAt"])}`, + font: "text-14", }); + seatsSection.add(entry); + }); + + this._add(seatsSection); }, } }); From 394cfd69fe5c1ac3c393b61c05f85814aa4691a8 Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Wed, 19 Feb 2025 17:09:25 +0100 Subject: [PATCH 13/17] pass categoryId to the rocket --- .../class/osparc/vipMarket/AnatomicalModelDetails.js | 3 ++- .../client/source/class/osparc/vipMarket/VipMarket.js | 8 +++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelDetails.js b/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelDetails.js index 282b4cc80c4..ca580cdd954 100644 --- a/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelDetails.js +++ b/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelDetails.js @@ -345,7 +345,8 @@ qx.Class.define("osparc.vipMarket.AnatomicalModelDetails", { }); importButton.addListener("execute", () => { this.fireDataEvent("modelImportRequested", { - modelId: anatomicalModelsData["licensedResources"][selectedIdx]["source"]["id"] + modelId: anatomicalModelsData["licensedResources"][selectedIdx]["source"]["id"], + categoryId: anatomicalModelsData["categoryId"], }); }, this); diff --git a/services/static-webserver/client/source/class/osparc/vipMarket/VipMarket.js b/services/static-webserver/client/source/class/osparc/vipMarket/VipMarket.js index 65e33f5553d..c1f2e7415b3 100644 --- a/services/static-webserver/client/source/class/osparc/vipMarket/VipMarket.js +++ b/services/static-webserver/client/source/class/osparc/vipMarket/VipMarket.js @@ -212,9 +212,10 @@ qx.Class.define("osparc.vipMarket.VipMarket", { if (!anatomicModelDetails.hasListener("modelImportRequested")) { anatomicModelDetails.addListener("modelImportRequested", e => { const { - modelId + modelId, + categoryId, } = e.getData(); - this.__sendImportModelMessage(modelId); + this.__sendImportModelMessage(modelId, categoryId); }, this); } }, @@ -317,7 +318,7 @@ qx.Class.define("osparc.vipMarket.VipMarket", { }); }, - __sendImportModelMessage: function(modelId) { + __sendImportModelMessage: function(modelId, categoryId) { const store = osparc.store.Store.getInstance(); const currentStudy = store.getCurrentStudy(); const nodeId = this.getOpenBy(); @@ -326,6 +327,7 @@ qx.Class.define("osparc.vipMarket.VipMarket", { "type": "importModel", "message": { "modelId": modelId, + "categoryId": categoryId, }, }; if (currentStudy.sendMessageToIframe(nodeId, msg)) { From 6cfe8080db9838dd3296939d2b2af391a699521b Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Wed, 19 Feb 2025 17:17:54 +0100 Subject: [PATCH 14/17] hide "Available for Importing" text if Import button is there --- .../class/osparc/vipMarket/AnatomicalModelDetails.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelDetails.js b/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelDetails.js index ca580cdd954..71d3b92053b 100644 --- a/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelDetails.js +++ b/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelDetails.js @@ -381,12 +381,16 @@ qx.Class.define("osparc.vipMarket.AnatomicalModelDetails", { osparc.store.Pricing.getInstance().fetchPricingUnits(licensedItemData["pricingPlanId"]) .then(pricingUnits => { if (pricingUnits.length === 1 && pricingUnits[0].getCost() === 0) { - const availableLabel = new qx.ui.basic.Label().set({ + const availableForImporting = new qx.ui.basic.Label().set({ font: "text-14", value: this.tr("Available for Importing"), padding: 10, }); - pricingUnitsLayout.add(availableLabel); + pricingUnitsLayout.add(availableForImporting); + // hide the text if Import button is there + this.bind("openBy", pricingLayout, "visibility", { + converter: openBy => openBy ? "excluded" : "visible" + }); } else { pricingUnits.forEach(pricingUnit => { pricingUnit.set({ From e8102c5629f71683a98a64bd825f6143837bd325 Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Wed, 19 Feb 2025 17:30:26 +0100 Subject: [PATCH 15/17] walletName on top of credits indicator --- .../osparc/desktop/credits/BillingCenter.js | 16 +++++++++++++--- .../osparc/desktop/wallets/WalletListItem.js | 1 + 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/services/static-webserver/client/source/class/osparc/desktop/credits/BillingCenter.js b/services/static-webserver/client/source/class/osparc/desktop/credits/BillingCenter.js index 1be932d331a..3093df0cd7d 100644 --- a/services/static-webserver/client/source/class/osparc/desktop/credits/BillingCenter.js +++ b/services/static-webserver/client/source/class/osparc/desktop/credits/BillingCenter.js @@ -43,16 +43,26 @@ qx.Class.define("osparc.desktop.credits.BillingCenter", { statics: { createMiniWalletView: function() { - const layout = new qx.ui.container.Composite(new qx.ui.layout.VBox(8)).set({ + const layout = new qx.ui.container.Composite(new qx.ui.layout.VBox(6)).set({ alignX: "center", minWidth: 120, maxWidth: 150 }); - const store = osparc.store.Store.getInstance(); + const walletName = new qx.ui.basic.Label().set({ + alignX: "center" + }); + layout.add(walletName); const creditsIndicator = new osparc.desktop.credits.CreditsIndicator(); - store.bind("contextWallet", creditsIndicator, "wallet"); layout.add(creditsIndicator); + const store = osparc.store.Store.getInstance(); + store.bind("contextWallet", walletName, "value", { + converter: wallet => wallet.getName() + }); + store.bind("contextWallet", walletName, "toolTipText", { + converter: wallet => wallet.getName() + }); + store.bind("contextWallet", creditsIndicator, "wallet"); layout.add(new qx.ui.core.Spacer(15, 15)); diff --git a/services/static-webserver/client/source/class/osparc/desktop/wallets/WalletListItem.js b/services/static-webserver/client/source/class/osparc/desktop/wallets/WalletListItem.js index 2c2a70a7f07..e003f05c229 100644 --- a/services/static-webserver/client/source/class/osparc/desktop/wallets/WalletListItem.js +++ b/services/static-webserver/client/source/class/osparc/desktop/wallets/WalletListItem.js @@ -81,6 +81,7 @@ qx.Class.define("osparc.desktop.wallets.WalletListItem", { control = new qx.ui.basic.Label().set({ font: "text-14" }); + control.bind("value", control, "toolTipText"); this._add(control, { row: 0, column: 0, From bc2990e3b1ce35d2648b55d1e9b4d6d8d2caa9a1 Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Wed, 19 Feb 2025 18:02:07 +0100 Subject: [PATCH 16/17] fix units caching --- .../client/source/class/osparc/store/Pricing.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/static-webserver/client/source/class/osparc/store/Pricing.js b/services/static-webserver/client/source/class/osparc/store/Pricing.js index 572a4b95e0d..884f1e3b436 100644 --- a/services/static-webserver/client/source/class/osparc/store/Pricing.js +++ b/services/static-webserver/client/source/class/osparc/store/Pricing.js @@ -82,7 +82,7 @@ qx.Class.define("osparc.store.Pricing", { }, fetchPricingUnits: function(pricingPlanId) { - if (this.getPricingPlan(pricingPlanId)) { + if (this.getPricingPlan(pricingPlanId) && this.getPricingPlan(pricingPlanId).getPricingUnits().length !== 0) { return new Promise(resolve => resolve(this.getPricingPlan(pricingPlanId).getPricingUnits())); } const params = { From f98fccaf1565839377375b2b49c5386d1aa14073 Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Wed, 19 Feb 2025 18:31:33 +0100 Subject: [PATCH 17/17] minor --- .../vipMarket/AnatomicalModelDetails.js | 42 ++++++++++--------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelDetails.js b/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelDetails.js index 71d3b92053b..f4b53ae5c6b 100644 --- a/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelDetails.js +++ b/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelDetails.js @@ -187,26 +187,28 @@ qx.Class.define("osparc.vipMarket.AnatomicalModelDetails", { manufacturerData["link"] = "https://speag.swiss/products/em-phantoms/overview-2/"; manufacturerData["icon"] = "https://media.licdn.com/dms/image/v2/D4E0BAQG2CYG28KAKbA/company-logo_200_200/company-logo_200_200/0/1700045977122/schmid__partner_engineering_ag_logo?e=2147483647&v=beta&t=6CZb1jjg5TnnzQWkrZBS9R3ebRKesdflg-_xYi4dwD8"; } - const manufacturerLink = new qx.ui.basic.Atom().set({ - label: manufacturerData["label"], - icon: manufacturerData["icon"], - font: "text-16", - gap: 10, - iconPosition: "right", - cursor: "pointer", - }); - manufacturerLink.getChildControl("icon").set({ - maxWidth: 32, - maxHeight: 32, - scale: true, - decorator: "rounded", - }); - manufacturerLink.addListener("tap", () => window.open(manufacturerData["link"])); - headerLayout.add(manufacturerLink, { - column: 1, - row: 0, - rowSpan: 2, - }); + if (Object.keys(manufacturerData).length) { + const manufacturerLink = new qx.ui.basic.Atom().set({ + label: manufacturerData["label"], + icon: manufacturerData["icon"], + font: "text-16", + gap: 10, + iconPosition: "right", + cursor: "pointer", + }); + manufacturerLink.getChildControl("icon").set({ + maxWidth: 32, + maxHeight: 32, + scale: true, + decorator: "rounded", + }); + manufacturerLink.addListener("tap", () => window.open(manufacturerData["link"])); + headerLayout.add(manufacturerLink, { + column: 1, + row: 0, + rowSpan: 2, + }); + } } this.__selectedModelLayout.add(headerLayout);