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 5eaf26b69472..1be932d331ae 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 @@ -36,7 +36,8 @@ qx.Class.define("osparc.desktop.credits.BillingCenter", { if (osparc.product.Utils.showS4LStore()) { this.__addPurchasesPage(); - this.__addCheckoutsPage(); + // For now, do not add checkouts page + // this.__addCheckoutsPage(); } }, @@ -102,7 +103,7 @@ qx.Class.define("osparc.desktop.credits.BillingCenter", { __addPurchasesPage: function() { const title = this.tr("Purchases"); - const iconSrc = "@FontAwesome5Solid/list/22"; + const iconSrc = "@FontAwesome5Solid/shopping-bag/22"; const purchases = new osparc.desktop.credits.Purchases(); const page = this.addTab(title, iconSrc, purchases); return page; diff --git a/services/static-webserver/client/source/class/osparc/desktop/preferences/pages/BasePage.js b/services/static-webserver/client/source/class/osparc/desktop/preferences/pages/BasePage.js index a32ec216d700..449546236ca3 100644 --- a/services/static-webserver/client/source/class/osparc/desktop/preferences/pages/BasePage.js +++ b/services/static-webserver/client/source/class/osparc/desktop/preferences/pages/BasePage.js @@ -30,7 +30,17 @@ qx.Class.define("osparc.desktop.preferences.pages.BasePage", { paddingLeft: 15 }); - this.__showLabelOnTab(title) + this.__showLabelOnTab(title); + + const tabButton = this.getChildControl("button"); + if (tabButton.getIcon() && tabButton.getIcon().includes(".svg")) { + tabButton.getChildControl("icon").set({ + minWidth: 24, + minHeight: 24, + scale: true, + }); + osparc.ui.basic.SVGImage.setColorToImage(tabButton.getChildControl("icon"), "text"); + } }, members: { 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 47335fef9249..925fb0f5e949 100644 --- a/services/static-webserver/client/source/class/osparc/study/PricingUnitLicense.js +++ b/services/static-webserver/client/source/class/osparc/study/PricingUnitLicense.js @@ -85,8 +85,11 @@ qx.Class.define("osparc.study.PricingUnitLicense", { }, __rentUnit: function() { + const nCredits = this.getUnitData().getCost(); const expirationDate = osparc.study.PricingUnitLicense.getExpirationDate(); - const msg = this.getUnitData().getName() + this.tr(" will be available until ") + osparc.utils.Utils.formatDate(expirationDate); + let msg = this.getUnitData().getName() + this.tr(" will be available until ") + osparc.utils.Utils.formatDate(expirationDate); + msg += "
"; + msg += `The rental will cost ${nCredits} credits`; 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/ui/basic/SVGImage.js b/services/static-webserver/client/source/class/osparc/ui/basic/SVGImage.js index 83ef2e946468..878413af5e9d 100644 --- a/services/static-webserver/client/source/class/osparc/ui/basic/SVGImage.js +++ b/services/static-webserver/client/source/class/osparc/ui/basic/SVGImage.js @@ -127,6 +127,22 @@ qx.Class.define("osparc.ui.basic.SVGImage", { const brightnessValue = l3 => l3; const contrastValue = l4 => l4 > 50 ? 50 : l4; return `invert(${invertValue(l)}%) sepia(${sepiaValue(s)}%) saturate(${saturateValue(s)}%) hue-rotate(${h}deg) brightness(${brightnessValue(l)}%) contrast(${contrastValue(l)}%)`; + }, + + setColorToImage: function(image, keywordOrRgb) { + if (keywordOrRgb === null) { + keywordOrRgb = "text"; + } + let filterValue = this.self().keywordToCSSFilter(keywordOrRgb); + if (filterValue === null) { + const hexColor = qx.theme.manager.Color.getInstance().resolve(keywordOrRgb); + const rgbColor = qx.util.ColorUtil.hexStringToRgb(hexColor); + filterValue = this.self().rgbToCSSFilter(rgbColor); + } + const myStyle = { + "filter": filterValue + }; + image.getContentElement().setStyles(myStyle); } }, @@ -160,19 +176,7 @@ qx.Class.define("osparc.ui.basic.SVGImage", { * @param keywordOrRgb {string} predefined keyword or rgb in the following format "0,255,0" */ __applyImageColor: function(keywordOrRgb) { - if (keywordOrRgb === null) { - keywordOrRgb = "text"; - } - let filterValue = this.self().keywordToCSSFilter(keywordOrRgb); - if (filterValue === null) { - const hexColor = qx.theme.manager.Color.getInstance().resolve(keywordOrRgb); - const rgbColor = qx.util.ColorUtil.hexStringToRgb(hexColor); - filterValue = this.self().rgbToCSSFilter(rgbColor); - } - const myStyle = { - "filter": filterValue - }; - this.getChildControl("image").getContentElement().setStyles(myStyle); - } + this.self().setColorToImage(this.getChildControl("image"), keywordOrRgb); + }, } }); 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 23adae7c5fca..dc21d9af607a 100644 --- a/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelDetails.js +++ b/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelDetails.js @@ -21,7 +21,7 @@ qx.Class.define("osparc.vipMarket.AnatomicalModelDetails", { construct: function() { this.base(arguments); - const layout = new qx.ui.layout.VBox(15); + const layout = new qx.ui.layout.VBox(10); this._setLayout(layout); this.__populateLayout(); @@ -53,13 +53,10 @@ qx.Class.define("osparc.vipMarket.AnatomicalModelDetails", { this._removeAll(); const anatomicalModelsData = this.getAnatomicalModelsData(); - if (anatomicalModelsData && anatomicalModelsData["licensedResourceData"]) { - const modelInfo = this.__createModelInfo(anatomicalModelsData["licensedResourceData"]["source"]); - const pricingUnits = this.__createPricingUnits(anatomicalModelsData); - const importButton = this.__createImportSection(anatomicalModelsData); - this._add(modelInfo); - this._add(pricingUnits); - this._add(importButton); + if (anatomicalModelsData && anatomicalModelsData["licensedResources"].length) { + this.__addModelsInfo(); + this.__addPricingUnits(); + this.__addSeatsSection(); } else { const selectModelLabel = new qx.ui.basic.Label().set({ value: this.tr("Select a model for more details"), @@ -73,10 +70,45 @@ qx.Class.define("osparc.vipMarket.AnatomicalModelDetails", { } }, - __createModelInfo: function(anatomicalModelsDataSource) { - const cardLayout = new qx.ui.container.Composite(new qx.ui.layout.VBox(16)); + __addModelsInfo: function() { + const modelLayout = new qx.ui.container.Composite(new qx.ui.layout.VBox(16)); - const description = anatomicalModelsDataSource["description"] || ""; + const anatomicalModelsData = this.getAnatomicalModelsData(); + const modelsInfo = anatomicalModelsData["licensedResources"]; + if (modelsInfo.length > 1) { + const sBox = new qx.ui.form.SelectBox().set({ + minWidth: 200, + allowGrowX: false, + }); + modelsInfo.forEach(modelInfo => { + const sbItem = new qx.ui.form.ListItem(modelInfo["source"]["features"]["name"]); + sbItem.modelId = modelInfo["source"]["id"]; + sBox.add(sbItem); + }); + this._add(sBox); + sBox.addListener("changeSelection", e => { + const selection = e.getData(); + if (selection.length) { + const idxFound = modelsInfo.findIndex(mdlInfo => mdlInfo["source"]["id"] === selection[0].modelId) + this.__populateModelInfo(modelLayout, anatomicalModelsData, idxFound); + } + }, this); + this.__populateModelInfo(modelLayout, anatomicalModelsData, 0); + } else { + this.__populateModelInfo(modelLayout, anatomicalModelsData, 0); + } + + this._add(modelLayout); + }, + + __populateModelInfo: function(modelLayout, anatomicalModelsData, selectedIdx = 0) { + modelLayout.removeAll(); + + const anatomicalModel = anatomicalModelsData["licensedResources"][selectedIdx]["source"]; + const topGrid = new qx.ui.layout.Grid(8, 8); + topGrid.setColumnFlex(0, 1); + const topLayout = new qx.ui.container.Composite(topGrid); + const description = anatomicalModel["description"] || ""; const delimiter = " - "; let titleAndSubtitle = description.split(delimiter); if (titleAndSubtitle.length > 0) { @@ -87,7 +119,10 @@ qx.Class.define("osparc.vipMarket.AnatomicalModelDetails", { allowGrowX: true, allowGrowY: true, }); - cardLayout.add(titleLabel); + topLayout.add(titleLabel, { + column: 0, + row: 0, + }); titleAndSubtitle.shift(); } if (titleAndSubtitle.length > 0) { @@ -99,13 +134,49 @@ qx.Class.define("osparc.vipMarket.AnatomicalModelDetails", { allowGrowX: true, allowGrowY: true, }); - cardLayout.add(subtitleLabel); + topLayout.add(subtitleLabel, { + column: 0, + row: 1, + }); } + if (anatomicalModel["thumbnail"]) { + const manufacturerData = {}; + if (anatomicalModel["thumbnail"].includes("itis.swiss")) { + manufacturerData["label"] = "IT'IS Foundation"; + manufacturerData["link"] = "https://itis.swiss/virtual-population/"; + manufacturerData["icon"] = "https://media.licdn.com/dms/image/v2/C4D0BAQE_FGa66IyvrQ/company-logo_200_200/company-logo_200_200/0/1631341490431?e=2147483647&v=beta&t=7f_IK-ArGjPrz-1xuWolAT4S2NdaVH-e_qa8hsKRaAc"; + } else if (anatomicalModel["thumbnail"].includes("speag.swiss")) { + manufacturerData["label"] = "Speag"; + 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"])); + topLayout.add(manufacturerLink, { + column: 1, + row: 0, + rowSpan: 2, + }); + } + modelLayout.add(topLayout); const middleLayout = new qx.ui.container.Composite(new qx.ui.layout.HBox(16)); const thumbnail = new qx.ui.basic.Image().set({ - source: anatomicalModelsDataSource["thumbnail"], + source: anatomicalModel["thumbnail"], alignY: "middle", scale: true, allowGrowX: true, @@ -117,7 +188,7 @@ qx.Class.define("osparc.vipMarket.AnatomicalModelDetails", { }); middleLayout.add(thumbnail); - const features = anatomicalModelsDataSource["features"]; + const features = anatomicalModel["features"]; const featuresGrid = new qx.ui.layout.Grid(8, 8); const featuresLayout = new qx.ui.container.Composite(featuresGrid); let idx = 0; @@ -157,45 +228,75 @@ qx.Class.define("osparc.vipMarket.AnatomicalModelDetails", { } }); - const doiTitle = new qx.ui.basic.Label().set({ - value: "DOI", - font: "text-14", - alignX: "right", - marginTop: 16, - }); - featuresLayout.add(doiTitle, { - column: 0, - row: idx, - }); - - const doiToLink = doi => { - const doiLabel = new osparc.ui.basic.LinkLabel("-").set({ + if (anatomicalModel["doi"]) { + const doiTitle = new qx.ui.basic.Label().set({ + value: "DOI", font: "text-14", - alignX: "left", + alignX: "right", marginTop: 16, }); - if (doi) { - doiLabel.set({ - value: doi, - url: "https://doi.org/" + doi, - font: "link-label-14", + featuresLayout.add(doiTitle, { + column: 0, + row: idx, + }); + + const doiToLink = doi => { + const doiLabel = new osparc.ui.basic.LinkLabel("-").set({ + font: "text-14", + alignX: "left", + marginTop: 16, }); - } - return doiLabel; - }; - featuresLayout.add(doiToLink(anatomicalModelsDataSource["doi"]), { - column: 1, - row: idx, - }); + if (doi) { + doiLabel.set({ + value: doi, + url: "https://doi.org/" + doi, + font: "link-label-14", + }); + } + return doiLabel; + }; + featuresLayout.add(doiToLink(anatomicalModel["doi"]), { + column: 1, + row: idx, + }); + } middleLayout.add(featuresLayout); - cardLayout.add(middleLayout); + modelLayout.add(middleLayout); - return cardLayout; + const importButton = this.__createImportSection(anatomicalModelsData, selectedIdx); + modelLayout.add(importButton); + }, + + __createImportSection: function(anatomicalModelsData, selectedIdx) { + const importSection = new qx.ui.container.Composite(new qx.ui.layout.VBox(5).set({ + alignX: "center" + })); + + const importButton = new qx.ui.form.Button().set({ + label: this.tr("Import"), + appearance: "strong-button", + center: true, + maxWidth: 200, + alignX: "center", + }); + this.bind("openBy", importButton, "visibility", { + converter: openBy => openBy ? "visible" : "excluded" + }); + importButton.addListener("execute", () => { + this.fireDataEvent("modelImportRequested", { + modelId: anatomicalModelsData["licensedResources"][selectedIdx]["source"]["id"] + }); + }, this); + if (anatomicalModelsData["purchases"].length) { + importSection.add(importButton); + } + return importSection; }, - __createPricingUnits: function(anatomicalModelsData) { + __addPricingUnits: function() { + const anatomicalModelsData = this.getAnatomicalModelsData(); const pricingUnitsLayout = new qx.ui.container.Composite(new qx.ui.layout.HBox(10).set({ alignX: "center" })); @@ -211,7 +312,6 @@ qx.Class.define("osparc.vipMarket.AnatomicalModelDetails", { }); pUnit.addListener("rentPricingUnit", () => { this.fireDataEvent("modelPurchaseRequested", { - modelId: anatomicalModelsData["licensedResourceData"]["source"]["id"], licensedItemId: anatomicalModelsData["licensedItemId"], pricingPlanId: anatomicalModelsData["pricingPlanId"], pricingUnitId: pricingUnit.getPricingUnitId(), @@ -222,12 +322,13 @@ qx.Class.define("osparc.vipMarket.AnatomicalModelDetails", { }) .catch(err => console.error(err)); - return pricingUnitsLayout; + this._add(pricingUnitsLayout); }, - __createImportSection: function(anatomicalModelsData) { - const importSection = new qx.ui.container.Composite(new qx.ui.layout.VBox(5).set({ - alignX: "center" + __addSeatsSection: function() { + const anatomicalModelsData = this.getAnatomicalModelsData(); + const seatsSection = new qx.ui.container.Composite(new qx.ui.layout.VBox(5).set({ + alignX: "center", })); anatomicalModelsData["purchases"].forEach(purchase => { @@ -236,28 +337,10 @@ qx.Class.define("osparc.vipMarket.AnatomicalModelDetails", { value: `${purchase["numberOfSeats"]} ${seatsText} available until ${osparc.utils.Utils.formatDate(purchase["expiresAt"])}`, font: "text-14", }); - importSection.add(entry); + seatsSection.add(entry); }); - const importButton = new qx.ui.form.Button().set({ - label: this.tr("Import"), - appearance: "strong-button", - center: true, - maxWidth: 200, - alignX: "center", - }); - this.bind("openBy", importButton, "visibility", { - converter: openBy => openBy ? "visible" : "excluded" - }); - importButton.addListener("execute", () => { - this.fireDataEvent("modelImportRequested", { - modelId: anatomicalModelsData["licensedResourceData"]["source"]["id"] - }); - }, this); - if (anatomicalModelsData["purchases"].length) { - importSection.add(importButton); - } - return importSection; + this._add(seatsSection); }, } }); diff --git a/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelListItem.js b/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelListItem.js index 83568616d0f0..fb87e1e89aa7 100644 --- a/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelListItem.js +++ b/services/static-webserver/client/source/class/osparc/vipMarket/AnatomicalModelListItem.js @@ -54,11 +54,25 @@ qx.Class.define("osparc.vipMarket.AnatomicalModelListItem", { init: "selectable" }, - modelId: { - check: "Number", + licenseKey: { + check: "String", init: null, nullable: false, - event: "changeModelId", + event: "changeLicenseKey", + }, + + licenseVersion: { + check: "String", + init: null, + nullable: false, + event: "changeLicenseVersion", + }, + + licensedItemId: { + check: "String", + init: null, + nullable: false, + event: "changeLicensedItemId", }, thumbnail: { @@ -84,13 +98,6 @@ qx.Class.define("osparc.vipMarket.AnatomicalModelListItem", { event: "changeDate", }, - licensedItemId: { - check: "String", - init: null, - nullable: false, - event: "changeLicensedItemId", - }, - pricingPlanId: { check: "Number", init: null, 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 6e76681978dc..a772378afd60 100644 --- a/services/static-webserver/client/source/class/osparc/vipMarket/Market.js +++ b/services/static-webserver/client/source/class/osparc/vipMarket/Market.js @@ -41,18 +41,35 @@ qx.Class.define("osparc.vipMarket.Market", { ]) .then(values => { const licensedItems = values[0]; + const purchasedItems = values[1]; const categories = []; + const purchasedCategory = { + categoryId: "purchasedModels", + label: this.tr("Rented"), + icon: "osparc/market/RentedModels.svg", + items: [], + }; + categories.push(purchasedCategory); licensedItems.forEach(licensedItem => { - if (licensedItem["licensedResourceData"] && licensedItem["licensedResourceData"]["categoryId"]) { - const categoryId = licensedItem["licensedResourceData"]["categoryId"]; + if (purchasedItems.find(purchasedItem => purchasedItem["licensedItemId"] === licensedItem["licensedItemId"])) { + purchasedCategory["items"].push(licensedItem); + if (!openCategory) { + openCategory = purchasedCategory["categoryId"]; + } + } + if (licensedItem && licensedItem["categoryId"]) { + const categoryId = licensedItem["categoryId"]; let category = categories.find(cat => cat["categoryId"] === categoryId); if (!category) { category = { categoryId, - label: licensedItem["licensedResourceData"]["categoryDisplay"] || "Category", - icon: licensedItem["licensedResourceData"]["categoryIcon"] || "@FontAwesome5Solid/users/20", + label: licensedItem["categoryDisplay"] || "Category", + icon: licensedItem["categoryIcon"] || `osparc/market/${categoryId}.svg`, items: [], }; + if (!openCategory) { + openCategory = categoryId; + } categories.push(category); } category["items"].push(licensedItem); @@ -70,7 +87,7 @@ qx.Class.define("osparc.vipMarket.Market", { }, events: { - "importMessageSent": "qx.event.type.Data", + "importMessageSent": "qx.event.type.Event", }, properties: { @@ -83,18 +100,50 @@ qx.Class.define("osparc.vipMarket.Market", { }, members: { + __purchasedCategoryButton: null, + __purchasedCategoryMarket: null, + __buildViPMarketPage: function(marketTabInfo, licensedItems = []) { const vipMarketView = new osparc.vipMarket.VipMarket(licensedItems); vipMarketView.set({ category: marketTabInfo["categoryId"], }); this.bind("openBy", vipMarketView, "openBy"); + vipMarketView.addListener("modelPurchased", () => this.__repopulatePurchasedCategory()); 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"); + } return page; }, + __repopulatePurchasedCategory: function() { + const store = osparc.store.Store.getInstance(); + const contextWallet = store.getContextWallet(); + const walletId = contextWallet.getWalletId(); + const licensedItemsStore = osparc.store.LicensedItems.getInstance(); + Promise.all([ + licensedItemsStore.getLicensedItems(), + licensedItemsStore.getPurchasedLicensedItems(walletId), + ]) + .then(values => { + const licensedItems = values[0]; + const purchasedItems = values[1]; + let items = []; + licensedItems.forEach(licensedItem => { + if (purchasedItems.find(purchasedItem => purchasedItem["licensedItemId"] === licensedItem["licensedItemId"])) { + items.push(licensedItem); + } + }); + this.__purchasedCategoryButton.setVisibility(items.length ? "visible" : "excluded"); + this.__purchasedCategoryMarket.setLicensedItems(items); + }); + }, + __openCategory: function(category) { const viewFound = this.getChildControl("tabs-view").getChildren().find(view => view.category === category); if (viewFound) { 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 c238f5618b8e..035d7221ad4d 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 = 1035; + const width = 1050; const height = 700; this.set({ width, 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 4fa9bbac8d39..a518b3f2455f 100644 --- a/services/static-webserver/client/source/class/osparc/vipMarket/VipMarket.js +++ b/services/static-webserver/client/source/class/osparc/vipMarket/VipMarket.js @@ -18,20 +18,21 @@ qx.Class.define("osparc.vipMarket.VipMarket", { extend: qx.ui.core.Widget, - construct: function(anatomicalModels) { + construct: function(licensedItems) { this.base(arguments); this._setLayout(new qx.ui.layout.HBox(10)); this.__buildLayout(); - if (anatomicalModels) { - this.setLicensedItems(anatomicalModels); + if (licensedItems) { + this.setLicensedItems(licensedItems); } }, events: { - "importMessageSent": "qx.event.type.Data" + "modelPurchased": "qx.event.type.Event", + "importMessageSent": "qx.event.type.Event", }, properties: { @@ -50,8 +51,8 @@ qx.Class.define("osparc.vipMarket.VipMarket", { }, members: { - __anatomicalModels: null, - __anatomicalModelsModel: null, + __anatomicalBundles: null, + __anatomicalBundlesModel: null, _createChildControlImpl: function(id) { let control; @@ -127,12 +128,13 @@ qx.Class.define("osparc.vipMarket.VipMarket", { this.getChildControl("filter-text"); const modelsUIList = this.getChildControl("models-list"); - const anatomicalModelsModel = this.__anatomicalModelsModel = new qx.data.Array(); + const anatomicalModelsModel = this.__anatomicalBundlesModel = new qx.data.Array(); const membersCtrl = new qx.data.controller.List(anatomicalModelsModel, modelsUIList, "displayName"); membersCtrl.setDelegate({ createItem: () => new osparc.vipMarket.AnatomicalModelListItem(), bindItem: (ctrl, item, id) => { - ctrl.bindProperty("modelId", "modelId", null, item, id); + ctrl.bindProperty("licenseKey", "licenseKey", null, item, id); + ctrl.bindProperty("licenseVersion", "licenseVersion", null, item, id); ctrl.bindProperty("thumbnail", "thumbnail", null, item, id); ctrl.bindProperty("displayName", "displayName", null, item, id); ctrl.bindProperty("date", "date", null, item, id); @@ -150,17 +152,17 @@ qx.Class.define("osparc.vipMarket.VipMarket", { thumbnail: "@FontAwesome5Solid/spinner/32", name: this.tr("Loading"), }; - this.__anatomicalModelsModel.append(qx.data.marshal.Json.createModel(loadingModel)); + this.__anatomicalBundlesModel.append(qx.data.marshal.Json.createModel(loadingModel)); const anatomicModelDetails = this.getChildControl("models-details"); modelsUIList.addListener("changeSelection", e => { const selection = e.getData(); if (selection.length) { - const modelId = selection[0].getModelId(); - const modelFound = this.__anatomicalModels.find(anatomicalModel => anatomicalModel["modelId"] === modelId); - if (modelFound) { - anatomicModelDetails.setAnatomicalModelsData(modelFound); + const licensedItemId = selection[0].getLicensedItemId(); + const bundleFound = this.__anatomicalBundles.find(anatomicalBundle => anatomicalBundle["licensedItemId"] === licensedItemId); + if (bundleFound) { + anatomicModelDetails.setAnatomicalModelsData(bundleFound); return; } } @@ -168,90 +170,59 @@ qx.Class.define("osparc.vipMarket.VipMarket", { }, this); }, - setLicensedItems: function(licensedItems) { + setLicensedItems: function(licensedBundles) { const store = osparc.store.Store.getInstance(); const contextWallet = store.getContextWallet(); if (!contextWallet) { return; } - const licensedItemsStore = osparc.store.LicensedItems.getInstance(); const walletId = contextWallet.getWalletId(); + const licensedItemsStore = osparc.store.LicensedItems.getInstance(); licensedItemsStore.getPurchasedLicensedItems(walletId) .then(purchasesItems => { - this.__anatomicalModels = []; - licensedItems.forEach(licensedItem => { - const anatomicalModel = osparc.utils.Utils.deepCloneObject(licensedItem); - const anatomicalModelDataSource = anatomicalModel["licensedResourceData"]["source"]; - anatomicalModel["modelId"] = anatomicalModelDataSource["id"]; - anatomicalModel["thumbnail"] = ""; - anatomicalModel["date"] = null; - if (anatomicalModelDataSource["thumbnail"]) { - anatomicalModel["thumbnail"] = anatomicalModelDataSource["thumbnail"]; - } - if (anatomicalModelDataSource["features"] && anatomicalModelDataSource["features"]["date"]) { - anatomicalModel["date"] = new Date(anatomicalModelDataSource["features"]["date"]); + this.__anatomicalBundles = []; + licensedBundles.forEach(licensedBundle => { + const anatomicalBundle = osparc.utils.Utils.deepCloneObject(licensedBundle); + anatomicalBundle["licenseKey"] = anatomicalBundle["key"]; + anatomicalBundle["licenseVersion"] = anatomicalBundle["version"]; + anatomicalBundle["thumbnail"] = ""; + anatomicalBundle["date"] = null; + if (anatomicalBundle["licensedResources"] && anatomicalBundle["licensedResources"].length) { + const firstItem = anatomicalBundle["licensedResources"][0]["source"]; + if (firstItem["thumbnail"]) { + anatomicalBundle["thumbnail"] = firstItem["thumbnail"]; + } + if (firstItem["features"] && firstItem["features"]["date"]) { + anatomicalBundle["date"] = new Date(firstItem["features"]["date"]); + } } // attach license data - anatomicalModel["licensedItemId"] = licensedItem["licensedItemId"]; - anatomicalModel["pricingPlanId"] = licensedItem["pricingPlanId"]; - // attach leased data - anatomicalModel["purchases"] = []; // default - const purchasesItemsFound = purchasesItems.filter(purchasesItem => purchasesItem["licensedItemId"] === licensedItem["licensedItemId"]); + anatomicalBundle["pricingPlanId"] = licensedBundle["pricingPlanId"]; + // attach purchases data + anatomicalBundle["purchases"] = []; // default + const purchasesItemsFound = purchasesItems.filter(purchasesItem => purchasesItem["licensedItemId"] === licensedBundle["licensedItemId"]); if (purchasesItemsFound.length) { purchasesItemsFound.forEach(purchasesItemFound => { - anatomicalModel["purchases"].push({ + anatomicalBundle["purchases"].push({ expiresAt: new Date(purchasesItemFound["expireAt"]), numberOfSeats: purchasesItemFound["numOfSeats"], }) }); } - this.__anatomicalModels.push(anatomicalModel); + this.__anatomicalBundles.push(anatomicalBundle); }); this.__populateModels(); const anatomicModelDetails = this.getChildControl("models-details"); anatomicModelDetails.addListener("modelPurchaseRequested", e => { - if (!contextWallet) { - return; - } const { - modelId, licensedItemId, pricingPlanId, pricingUnitId, } = e.getData(); - let numberOfSeats = null; - const pricingUnit = osparc.store.Pricing.getInstance().getPricingUnit(pricingPlanId, pricingUnitId); - if (pricingUnit) { - const split = pricingUnit.getName().split(" "); - numberOfSeats = parseInt(split[0]); - } - licensedItemsStore.purchaseLicensedItem(licensedItemId, walletId, pricingPlanId, pricingUnitId, numberOfSeats) - .then(() => { - const expirationDate = osparc.study.PricingUnitLicense.getExpirationDate(); - const purchaseData = { - expiresAt: expirationDate, // get this info from the response - numberOfSeats, // get this info from the response - }; - - let msg = numberOfSeats; - msg += " seat" + (purchaseData["numberOfSeats"] > 1 ? "s" : ""); - msg += " rented until " + osparc.utils.Utils.formatDate(purchaseData["expiresAt"]); - osparc.FlashMessenger.getInstance().logAs(msg, "INFO"); - - const found = this.__anatomicalModels.find(model => model["modelId"] === modelId); - if (found) { - found["purchases"].push(purchaseData); - this.__populateModels(modelId); - anatomicModelDetails.setAnatomicalModelsData(found); - } - }) - .catch(err => { - const msg = err.message || this.tr("Cannot purchase model"); - osparc.FlashMessenger.getInstance().logAs(msg, "ERROR"); - }); + this.__modelPurchaseRequested(licensedItemId, pricingPlanId, pricingUnitId); }, this); anatomicModelDetails.addListener("modelImportRequested", e => { @@ -263,10 +234,10 @@ qx.Class.define("osparc.vipMarket.VipMarket", { }); }, - __populateModels: function(selectModelId) { - const models = this.__anatomicalModels; + __populateModels: function(selectLicensedItemId) { + const models = this.__anatomicalBundles; - this.__anatomicalModelsModel.removeAll(); + this.__anatomicalBundlesModel.removeAll(); const sortModel = sortBy => { models.sort((a, b) => { // first criteria @@ -298,20 +269,20 @@ qx.Class.define("osparc.vipMarket.VipMarket", { }); }; sortModel(); - models.forEach(model => this.__anatomicalModelsModel.append(qx.data.marshal.Json.createModel(model))); + models.forEach(model => this.__anatomicalBundlesModel.append(qx.data.marshal.Json.createModel(model))); this.getChildControl("sort-button").addListener("sortBy", e => { - this.__anatomicalModelsModel.removeAll(); + this.__anatomicalBundlesModel.removeAll(); const sortBy = e.getData(); sortModel(sortBy); - models.forEach(model => this.__anatomicalModelsModel.append(qx.data.marshal.Json.createModel(model))); + models.forEach(model => this.__anatomicalBundlesModel.append(qx.data.marshal.Json.createModel(model))); }, this); // select model after timeout, there is something that changes the selection to empty after populating the list setTimeout(() => { const modelsUIList = this.getChildControl("models-list"); - if (selectModelId) { - const entryFound = modelsUIList.getSelectables().find(entry => "getModelId" in entry && entry.getModelId() === selectModelId); + if (selectLicensedItemId) { + const entryFound = modelsUIList.getSelectables().find(entry => "getLicensedItemId" in entry && entry.getLicensedItemId() === selectLicensedItemId); modelsUIList.setSelection([entryFound]); } else if (modelsUIList.getSelectables().length) { // select first @@ -320,6 +291,48 @@ qx.Class.define("osparc.vipMarket.VipMarket", { }, 100); }, + __modelPurchaseRequested: function(licensedItemId, pricingPlanId, pricingUnitId) { + const store = osparc.store.Store.getInstance(); + const contextWallet = store.getContextWallet(); + if (!contextWallet) { + return; + } + const walletId = contextWallet.getWalletId(); + let numberOfSeats = null; + const pricingUnit = osparc.store.Pricing.getInstance().getPricingUnit(pricingPlanId, pricingUnitId); + if (pricingUnit) { + const split = pricingUnit.getName().split(" "); + numberOfSeats = parseInt(split[0]); + } + const licensedItemsStore = osparc.store.LicensedItems.getInstance(); + licensedItemsStore.purchaseLicensedItem(licensedItemId, walletId, pricingPlanId, pricingUnitId, numberOfSeats) + .then(() => { + const expirationDate = osparc.study.PricingUnitLicense.getExpirationDate(); + const purchaseData = { + expiresAt: expirationDate, // get this info from the response + numberOfSeats, // get this info from the response + }; + + let msg = numberOfSeats; + msg += " seat" + (purchaseData["numberOfSeats"] > 1 ? "s" : ""); + msg += " rented until " + osparc.utils.Utils.formatDate(purchaseData["expiresAt"]); + osparc.FlashMessenger.getInstance().logAs(msg, "INFO"); + + const found = this.__anatomicalBundles.find(model => model["licensedItemId"] === licensedItemId); + if (found) { + found["purchases"].push(purchaseData); + this.__populateModels(licensedItemId); + const anatomicModelDetails = this.getChildControl("models-details"); + anatomicModelDetails.setAnatomicalModelsData(found); + } + this.fireEvent("modelPurchased"); + }) + .catch(err => { + const msg = err.message || this.tr("Cannot purchase model"); + osparc.FlashMessenger.getInstance().logAs(msg, "ERROR"); + }); + }, + __sendImportModelMessage: function(modelId) { const store = osparc.store.Store.getInstance(); const currentStudy = store.getCurrentStudy(); diff --git a/services/static-webserver/client/source/resource/osparc/market/AnimalWholeBody.svg b/services/static-webserver/client/source/resource/osparc/market/AnimalWholeBody.svg new file mode 100644 index 000000000000..cbfbc598fc17 --- /dev/null +++ b/services/static-webserver/client/source/resource/osparc/market/AnimalWholeBody.svg @@ -0,0 +1,68 @@ + + + + + + + + + ModelsDownload + + + + + + + + + + + + + diff --git a/services/static-webserver/client/source/resource/osparc/market/ComputationalPhantom.svg b/services/static-webserver/client/source/resource/osparc/market/ComputationalPhantom.svg new file mode 100644 index 000000000000..806af72544a2 --- /dev/null +++ b/services/static-webserver/client/source/resource/osparc/market/ComputationalPhantom.svg @@ -0,0 +1,48 @@ + + + + + + PhantomsDownload + + + + + + diff --git a/services/static-webserver/client/source/resource/osparc/market/HumanBodyRegion.svg b/services/static-webserver/client/source/resource/osparc/market/HumanBodyRegion.svg new file mode 100644 index 000000000000..f3fd1e478485 --- /dev/null +++ b/services/static-webserver/client/source/resource/osparc/market/HumanBodyRegion.svg @@ -0,0 +1,60 @@ + + + + + + + + + HumanPartsDownload + + + + + + + + + + diff --git a/services/static-webserver/client/source/resource/osparc/market/HumanWholeBody.svg b/services/static-webserver/client/source/resource/osparc/market/HumanWholeBody.svg new file mode 100644 index 000000000000..2321ac476704 --- /dev/null +++ b/services/static-webserver/client/source/resource/osparc/market/HumanWholeBody.svg @@ -0,0 +1,51 @@ + + + ModelsDownload + diff --git a/services/static-webserver/client/source/resource/osparc/market/RentedModels.svg b/services/static-webserver/client/source/resource/osparc/market/RentedModels.svg new file mode 100644 index 000000000000..12fa9d1d5c17 --- /dev/null +++ b/services/static-webserver/client/source/resource/osparc/market/RentedModels.svg @@ -0,0 +1,53 @@ + + + + + + ModelsDownload + + + + + + + + + + ModelsDownload + + + +