diff --git a/interface/resources/icons/tablet-icons/market-a-msg.svg b/interface/resources/icons/tablet-icons/market-a-msg.svg new file mode 100644 index 0000000000..0ab93f3cc8 --- /dev/null +++ b/interface/resources/icons/tablet-icons/market-a-msg.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + diff --git a/interface/resources/icons/tablet-icons/market-a.svg b/interface/resources/icons/tablet-icons/market-a.svg index f8ba17301e..db2d948d7b 100644 --- a/interface/resources/icons/tablet-icons/market-a.svg +++ b/interface/resources/icons/tablet-icons/market-a.svg @@ -1,64 +1,15 @@ - - - -image/svg+xml \ No newline at end of file + + + + + + + + + diff --git a/interface/resources/icons/tablet-icons/market-i-msg.svg b/interface/resources/icons/tablet-icons/market-i-msg.svg new file mode 100644 index 0000000000..488c507c6e --- /dev/null +++ b/interface/resources/icons/tablet-icons/market-i-msg.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + diff --git a/interface/resources/icons/tablet-icons/market-i.svg b/interface/resources/icons/tablet-icons/market-i.svg index bf9aa9335f..7d11507cdb 100644 --- a/interface/resources/icons/tablet-icons/market-i.svg +++ b/interface/resources/icons/tablet-icons/market-i.svg @@ -1,23 +1,19 @@ - - + - - - - - - - - + + + + diff --git a/interface/resources/qml/controls-uit/FilterBar.qml b/interface/resources/qml/controls-uit/FilterBar.qml new file mode 100644 index 0000000000..ecae790b22 --- /dev/null +++ b/interface/resources/qml/controls-uit/FilterBar.qml @@ -0,0 +1,321 @@ +// +// FilterBar.qml +// +// Created by Zach Fox on 17 Feb 2018-03-12 +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import QtQuick 2.9 +import QtQuick.Controls 2.2 +import QtGraphicalEffects 1.0 + +import "../styles-uit" +import "../controls-uit" as HifiControls + +Item { + id: root; + + property int colorScheme: hifi.colorSchemes.light + readonly property bool isLightColorScheme: colorScheme == hifi.colorSchemes.light + readonly property bool isFaintGrayColorScheme: colorScheme == hifi.colorSchemes.faintGray + property bool error: false; + property alias textFieldHeight: textField.height; + property string placeholderText; + property alias dropdownHeight: dropdownContainer.height; + property alias text: textField.text; + property alias primaryFilterChoices: filterBarModel; + property int primaryFilter_index: -1; + property string primaryFilter_filterName: ""; + property string primaryFilter_displayName: ""; + signal accepted; + + onPrimaryFilter_indexChanged: { + if (primaryFilter_index === -1) { + primaryFilter_filterName = ""; + primaryFilter_displayName = ""; + } else { + primaryFilter_filterName = filterBarModel.get(primaryFilter_index).filterName; + primaryFilter_displayName = filterBarModel.get(primaryFilter_index).displayName; + } + } + + TextField { + id: textField; + + anchors.top: parent.top; + anchors.right: parent.right; + anchors.left: parent.left; + + font.family: "Fira Sans" + font.pixelSize: hifi.fontSizes.textFieldInput; + + placeholderText: root.primaryFilter_index === -1 ? root.placeholderText : ""; + + TextMetrics { + id: primaryFilterTextMetrics; + font.family: "FiraSans Regular"; + font.pixelSize: hifi.fontSizes.textFieldInput; + font.capitalization: Font.AllUppercase; + text: root.primaryFilter_displayName; + } + + // workaround for https://bugreports.qt.io/browse/QTBUG-49297 + Keys.onPressed: { + switch (event.key) { + case Qt.Key_Return: + case Qt.Key_Enter: + event.accepted = true; + + // emit accepted signal manually + if (acceptableInput) { + root.accepted(); + root.forceActiveFocus(); + } + break; + case Qt.Key_Backspace: + if (textField.text === "") { + primaryFilter_index = -1; + } + break; + } + } + + onAccepted: { + root.forceActiveFocus(); + } + + onActiveFocusChanged: { + if (!activeFocus) { + dropdownContainer.visible = false; + } + } + + color: { + if (isLightColorScheme) { + if (textField.activeFocus) { + hifi.colors.black + } else { + hifi.colors.lightGray + } + } else if (isFaintGrayColorScheme) { + if (textField.activeFocus) { + hifi.colors.black + } else { + hifi.colors.lightGray + } + } else { + if (textField.activeFocus) { + hifi.colors.white + } else { + hifi.colors.lightGrayText + } + } + } + + background: Rectangle { + id: mainFilterBarRectangle; + + color: { + if (isLightColorScheme) { + if (textField.activeFocus) { + hifi.colors.white + } else { + hifi.colors.textFieldLightBackground + } + } else if (isFaintGrayColorScheme) { + if (textField.activeFocus) { + hifi.colors.white + } else { + hifi.colors.faintGray50 + } + } else { + if (textField.activeFocus) { + hifi.colors.black + } else { + hifi.colors.baseGrayShadow + } + } + } + + border.color: textField.error ? hifi.colors.redHighlight : + (textField.activeFocus ? hifi.colors.primaryHighlight : (isFaintGrayColorScheme ? hifi.colors.lightGrayText : hifi.colors.lightGray)) + border.width: 1 + radius: 4 + + Item { + id: searchButtonContainer; + anchors.left: parent.left; + anchors.verticalCenter: parent.verticalCenter; + height: parent.height; + width: 42; + + // Search icon + HiFiGlyphs { + id: searchIcon; + text: hifi.glyphs.search + color: textField.color + size: 40; + anchors.left: parent.left + anchors.verticalCenter: parent.verticalCenter + width: paintedWidth; + } + + // Carat + HiFiGlyphs { + text: hifi.glyphs.caratDn; + color: textField.color; + size: 40; + anchors.left: parent.left; + anchors.leftMargin: 15; + width: paintedWidth; + } + + MouseArea { + anchors.fill: parent; + onClicked: { + textField.forceActiveFocus(); + dropdownContainer.visible = !dropdownContainer.visible; + } + } + } + + Rectangle { + z: 999; + id: primaryFilterContainer; + color: textField.activeFocus ? hifi.colors.faintGray : hifi.colors.white; + width: primaryFilterTextMetrics.tightBoundingRect.width + 14; + height: parent.height - 8; + anchors.verticalCenter: parent.verticalCenter; + anchors.left: searchButtonContainer.right; + anchors.leftMargin: 4; + visible: primaryFilterText.text !== ""; + radius: height/2; + + FiraSansRegular { + id: primaryFilterText; + text: root.primaryFilter_displayName; + anchors.fill: parent; + color: textField.activeFocus ? hifi.colors.black : hifi.colors.lightGray; + horizontalAlignment: Text.AlignHCenter; + verticalAlignment: Text.AlignVCenter; + size: hifi.fontSizes.textFieldInput; + font.capitalization: Font.AllUppercase; + } + + MouseArea { + anchors.fill: parent; + onClicked: { + textField.forceActiveFocus(); + } + } + } + + // "Clear" button + HiFiGlyphs { + text: hifi.glyphs.error + color: textField.color + size: 40 + anchors.right: parent.right + anchors.rightMargin: hifi.dimensions.textPadding - 2 + anchors.verticalCenter: parent.verticalCenter + visible: root.text !== "" || root.primaryFilter_index !== -1; + + MouseArea { + anchors.fill: parent; + onClicked: { + root.text = ""; + root.primaryFilter_index = -1; + dropdownContainer.visible = false; + textField.forceActiveFocus(); + } + } + } + } + + selectedTextColor: hifi.colors.black + selectionColor: hifi.colors.primaryHighlight + leftPadding: 44 + (root.primaryFilter_index === -1 ? 0 : primaryFilterTextMetrics.tightBoundingRect.width + 20); + rightPadding: 44; + } + + Rectangle { + id: dropdownContainer; + visible: false; + height: 50 * filterBarModel.count; + width: parent.width; + anchors.top: textField.bottom; + anchors.left: parent.left; + anchors.right: parent.right; + color: hifi.colors.white; + + ListModel { + id: filterBarModel; + } + + ListView { + id: dropdownListView; + interactive: false; + anchors.fill: parent; + model: filterBarModel; + delegate: Rectangle { + id: dropDownButton; + color: hifi.colors.white; + width: parent.width; + height: 50; + + RalewaySemiBold { + id: dropDownButtonText; + text: model.displayName; + anchors.fill: parent; + anchors.leftMargin: 12; + color: hifi.colors.baseGray; + horizontalAlignment: Text.AlignLeft; + verticalAlignment: Text.AlignVCenter; + size: 18; + } + + MouseArea { + anchors.fill: parent; + hoverEnabled: true; + propagateComposedEvents: false; + onEntered: { + dropDownButton.color = hifi.colors.blueHighlight; + } + onExited: { + dropDownButton.color = hifi.colors.white; + } + onClicked: { + textField.forceActiveFocus(); + root.primaryFilter_index = index; + dropdownContainer.visible = false; + } + } + } + } + } + + DropShadow { + anchors.fill: dropdownContainer; + horizontalOffset: 0; + verticalOffset: 4; + radius: 4.0; + samples: 9 + color: Qt.rgba(0, 0, 0, 0.25); + source: dropdownContainer; + visible: dropdownContainer.visible; + } + + function changeFilterByDisplayName(name) { + for (var i = 0; i < filterBarModel.count; i++) { + if (filterBarModel.get(i).displayName === name) { + root.primaryFilter_index = i; + return; + } + } + + console.log("Passed displayName not found in filterBarModel! primaryFilter unchanged."); + } +} diff --git a/interface/resources/qml/hifi/commerce/checkout/Checkout.qml b/interface/resources/qml/hifi/commerce/checkout/Checkout.qml index 96ffa390bf..9933953fe8 100644 --- a/interface/resources/qml/hifi/commerce/checkout/Checkout.qml +++ b/interface/resources/qml/hifi/commerce/checkout/Checkout.qml @@ -30,25 +30,31 @@ Rectangle { property string activeView: "initialize"; property bool ownershipStatusReceived: false; property bool balanceReceived: false; + property bool availableUpdatesReceived: false; + property string baseItemName: ""; property string itemName; property string itemId; property string itemHref; property string itemAuthor; + property int itemEdition: -1; + property string certificateId; property double balanceAfterPurchase; property bool alreadyOwned: false; property int itemPrice: -1; property bool isCertified; property string itemType; - property var itemTypesArray: ["entity", "wearable", "contentSet", "app", "avatar"]; - property var itemTypesText: ["entity", "wearable", "content set", "app", "avatar"]; - property var buttonTextNormal: ["REZ", "WEAR", "REPLACE CONTENT SET", "INSTALL", "WEAR"]; - property var buttonTextClicked: ["REZZED!", "WORN!", "CONTENT SET REPLACED!", "INSTALLED!", "AVATAR CHANGED!"] - property var buttonGlyph: [hifi.glyphs.wand, hifi.glyphs.hat, hifi.glyphs.globe, hifi.glyphs.install, hifi.glyphs.avatar]; + property var itemTypesArray: ["entity", "wearable", "contentSet", "app", "avatar", "unknown"]; + property var itemTypesText: ["entity", "wearable", "content set", "app", "avatar", "item"]; + property var buttonTextNormal: ["REZ", "WEAR", "REPLACE CONTENT SET", "INSTALL", "WEAR", "REZ"]; + property var buttonTextClicked: ["REZZED!", "WORN!", "CONTENT SET REPLACED!", "INSTALLED!", "AVATAR CHANGED!", "REZZED!"] + property var buttonGlyph: [hifi.glyphs.wand, hifi.glyphs.hat, hifi.glyphs.globe, hifi.glyphs.install, hifi.glyphs.avatar, hifi.glyphs.wand]; property bool shouldBuyWithControlledFailure: false; property bool debugCheckoutSuccess: false; property bool canRezCertifiedItems: Entities.canRezCertified() || Entities.canRezTmpCertified(); property string referrer; property bool isInstalled; + property bool isUpdating; + property string baseAppURL; // Style color: hifi.colors.white; Connections { @@ -103,8 +109,8 @@ Rectangle { if (result.status !== 'success') { console.log("Failed to get balance", result.data.message); } else { - root.balanceReceived = true; root.balanceAfterPurchase = result.data.balance - root.itemPrice; + root.balanceReceived = true; root.refreshBuyUI(); } } @@ -113,13 +119,13 @@ Rectangle { if (result.status !== 'success') { console.log("Failed to get Already Owned status", result.data.message); } else { - root.ownershipStatusReceived = true; if (result.data.marketplace_item_id === root.itemId) { root.alreadyOwned = result.data.already_owned; } else { console.log("WARNING - Received 'Already Owned' status about different Marketplace ID!"); root.alreadyOwned = false; } + root.ownershipStatusReceived = true; root.refreshBuyUI(); } } @@ -129,11 +135,53 @@ Rectangle { root.isInstalled = true; } } + + onAvailableUpdatesResult: { + if (result.status !== 'success') { + console.log("Failed to get Available Updates", result.data.message); + } else { + for (var i = 0; i < result.data.updates.length; i++) { + // If the ItemID of the item we're looking at matches EITHER the ID of a "base" item + // OR the ID of an "updated" item, we're updating. + if (root.itemId === result.data.updates[i].item_id || + root.itemId === result.data.updates[i].updated_item_id) { + if (root.itemEdition !== -1 && root.itemEdition !== parseInt(result.data.updates[i].edition_number)) { + continue; + } + root.isUpdating = true; + root.baseItemName = result.data.updates[i].base_item_title; + // This CertID is the one corresponding to the base item CertID that the user already owns + root.certificateId = result.data.updates[i].certificate_id; + if (root.itemType === "app") { + root.baseAppURL = result.data.updates[i].item_download_url; + } + break; + } + } + root.availableUpdatesReceived = true; + refreshBuyUI(); + } + } + + onUpdateItemResult: { + if (result.status !== 'success') { + failureErrorText.text = result.message; + root.activeView = "checkoutFailure"; + } else { + root.itemHref = result.data.download_url; + if (result.data.categories.indexOf("Wearables") > -1) { + root.itemType = "wearable"; + } + root.activeView = "checkoutSuccess"; + } + } } onItemIdChanged: { root.ownershipStatusReceived = false; Commerce.alreadyOwned(root.itemId); + root.availableUpdatesReceived = false; + Commerce.getAvailableUpdates(root.itemId); itemPreviewImage.source = "https://hifi-metaverse.s3-us-west-1.amazonaws.com/marketplace/previews/" + itemId + "/thumbnail/hifi-mp-" + itemId + ".jpg"; } @@ -161,6 +209,7 @@ Rectangle { } onItemPriceChanged: { + root.balanceReceived = false; Commerce.balance(); } @@ -240,6 +289,7 @@ Rectangle { Component.onCompleted: { ownershipStatusReceived = false; balanceReceived = false; + availableUpdatesReceived = false; Commerce.getWalletStatus(); } } @@ -316,7 +366,7 @@ Rectangle { Rectangle { id: loading; z: 997; - visible: !root.ownershipStatusReceived || !root.balanceReceived; + visible: !root.ownershipStatusReceived || !root.balanceReceived || !root.availableUpdatesReceived; anchors.fill: parent; color: hifi.colors.white; @@ -412,6 +462,7 @@ Rectangle { // "HFC" balance label HiFiGlyphs { id: itemPriceTextLabel; + visible: !(root.isUpdating && root.itemEdition > 0); text: hifi.glyphs.hfc; // Size size: 30; @@ -427,9 +478,9 @@ Rectangle { } FiraSansSemiBold { id: itemPriceText; - text: (root.itemPrice === -1) ? "--" : root.itemPrice; + text: (root.isUpdating && root.itemEdition > 0) ? "FREE\nUPDATE" : ((root.itemPrice === -1) ? "--" : root.itemPrice); // Text size - size: 26; + size: (root.isUpdating && root.itemEdition > 0) ? 20 : 26; // Anchors anchors.top: parent.top; anchors.right: parent.right; @@ -529,9 +580,13 @@ Rectangle { height: 50; anchors.left: parent.left; anchors.right: parent.right; - text: "VIEW THIS ITEM IN MY PURCHASES"; + text: root.isUpdating ? "UPDATE TO THIS ITEM FOR FREE" : "VIEW THIS ITEM IN MY PURCHASES"; onClicked: { - sendToScript({method: 'checkout_goToPurchases', filterText: root.itemName}); + if (root.isUpdating) { + sendToScript({method: 'checkout_goToPurchases', filterText: root.baseItemName}); + } else { + sendToScript({method: 'checkout_goToPurchases', filterText: root.itemName}); + } } } @@ -539,7 +594,7 @@ Rectangle { HifiControlsUit.Button { id: buyButton; visible: !((root.itemType === "avatar" || root.itemType === "app") && viewInMyPurchasesButton.visible) - enabled: (root.balanceAfterPurchase >= 0 && ownershipStatusReceived && balanceReceived) || (!root.isCertified); + enabled: (root.balanceAfterPurchase >= 0 && ownershipStatusReceived && balanceReceived && availableUpdatesReceived) || (!root.isCertified) || root.isUpdating; color: viewInMyPurchasesButton.visible ? hifi.buttons.white : hifi.buttons.blue; colorScheme: hifi.colorSchemes.light; anchors.top: viewInMyPurchasesButton.visible ? viewInMyPurchasesButton.bottom : @@ -548,10 +603,19 @@ Rectangle { height: 50; anchors.left: parent.left; anchors.right: parent.right; - text: ((root.isCertified) ? ((ownershipStatusReceived && balanceReceived) ? - (viewInMyPurchasesButton.visible ? "Buy It Again" : "Confirm Purchase") : "--") : "Get Item"); + text: (root.isUpdating && root.itemEdition > 0) ? "CONFIRM UPDATE" : (((root.isCertified) ? ((ownershipStatusReceived && balanceReceived && availableUpdatesReceived) ? + ((viewInMyPurchasesButton.visible && !root.isUpdating) ? "Buy It Again" : "Confirm Purchase") : "--") : "Get Item")); onClicked: { - if (root.isCertified) { + if (root.isUpdating && root.itemEdition > 0) { + // If we're updating an app, the existing app needs to be uninstalled. + // This call will fail/return `false` if the app isn't installed, but that's OK. + if (root.itemType === "app") { + Commerce.uninstallApp(root.baseAppURL); + } + buyButton.enabled = false; + loading.visible = true; + Commerce.updateItem(root.certificateId); + } else if (root.isCertified) { if (!root.shouldBuyWithControlledFailure) { if (root.itemType === "contentSet" && !Entities.canReplaceContent()) { lightboxPopup.titleText = "Purchase Content Set"; @@ -975,7 +1039,7 @@ Rectangle { buyButton.color = hifi.buttons.red; root.shouldBuyWithControlledFailure = true; } else { - buyButton.text = (root.isCertified ? ((ownershipStatusReceived && balanceReceived) ? (root.alreadyOwned ? "Buy Another" : "Buy"): "--") : "Get Item"); + buyButton.text = (root.isCertified ? ((ownershipStatusReceived && balanceReceived && availableUpdatesReceived) ? (root.alreadyOwned ? "Buy Another" : "Buy"): "--") : "Get Item"); buyButton.color = hifi.buttons.blue; root.shouldBuyWithControlledFailure = false; } @@ -1001,12 +1065,13 @@ Rectangle { function fromScript(message) { switch (message.method) { case 'updateCheckoutQML': - itemId = message.params.itemId; - itemName = message.params.itemName; + root.itemId = message.params.itemId; + root.itemName = message.params.itemName.trim(); root.itemPrice = message.params.itemPrice; - itemHref = message.params.itemHref; - referrer = message.params.referrer; - itemAuthor = message.params.itemAuthor; + root.itemHref = message.params.itemHref; + root.referrer = message.params.referrer; + root.itemAuthor = message.params.itemAuthor; + root.itemEdition = message.params.itemEdition || -1; refreshBuyUI(); break; default: @@ -1015,35 +1080,70 @@ Rectangle { } signal sendToScript(var message); + function canBuyAgain() { + return (root.itemType === "entity" || root.itemType === "wearable" || root.itemType === "contentSet" || root.itemType === "unknown"); + } + + function handleContentSets() { + if (root.itemType === "contentSet" && !Entities.canReplaceContent()) { + buyText.text = "The domain owner must enable 'Replace Content' permissions for you in this " + + "domain's server settings before you can replace this domain's content with " + root.itemName + ""; + buyTextContainer.color = "#FFC3CD"; + buyTextContainer.border.color = "#F3808F"; + buyGlyph.text = hifi.glyphs.alert; + buyGlyph.size = 54; + } + } + + function handleBuyAgainLogic() { + // If you can buy this item again... + if (canBuyAgain()) { + // If you can't afford another copy of the item... + if (root.balanceAfterPurchase < 0) { + // If you already own the item... + if (root.alreadyOwned) { + buyText.text = "Your Wallet does not have sufficient funds to purchase this item again."; + // Else if you don't already own the item... + } else { + buyText.text = "Your Wallet does not have sufficient funds to purchase this item."; + } + buyTextContainer.color = "#FFC3CD"; + buyTextContainer.border.color = "#F3808F"; + buyGlyph.text = hifi.glyphs.alert; + buyGlyph.size = 54; + // If you CAN afford another copy of the item... + } else { + handleContentSets(); + } + } + } + function refreshBuyUI() { if (root.isCertified) { - if (root.ownershipStatusReceived && root.balanceReceived) { - if (root.balanceAfterPurchase < 0) { - if (root.alreadyOwned) { - buyText.text = "Your Wallet does not have sufficient funds to purchase this item again."; - viewInMyPurchasesButton.visible = true; + if (root.ownershipStatusReceived && root.balanceReceived && root.availableUpdatesReceived) { + buyText.text = ""; + + // If the user IS on the checkout page for the updated version of an owned item... + if (root.isUpdating) { + // If the user HAS already selected a specific edition to update... + if (root.itemEdition > 0) { + buyText.text = "By pressing \"Confirm Update\", you agree to trade in your old item for the updated item that replaces it."; + buyTextContainer.color = "#FFFFFF"; + buyTextContainer.border.color = "#FFFFFF"; + // Else if the user HAS NOT selected a specific edition to update... } else { - buyText.text = "Your Wallet does not have sufficient funds to purchase this item."; - } - buyTextContainer.color = "#FFC3CD"; - buyTextContainer.border.color = "#F3808F"; - buyGlyph.text = hifi.glyphs.alert; - buyGlyph.size = 54; + viewInMyPurchasesButton.visible = true; + + handleBuyAgainLogic(); + } + // If the user IS NOT on the checkout page for the updated verison of an owned item... + // (i.e. they are checking out an item "normally") } else { if (root.alreadyOwned) { viewInMyPurchasesButton.visible = true; - } else { - buyText.text = ""; - } - - if (root.itemType === "contentSet" && !Entities.canReplaceContent()) { - buyText.text = "The domain owner must enable 'Replace Content' permissions for you in this " + - "domain's server settings before you can replace this domain's content with " + root.itemName + ""; - buyTextContainer.color = "#FFC3CD"; - buyTextContainer.border.color = "#F3808F"; - buyGlyph.text = hifi.glyphs.alert; - buyGlyph.size = 54; } + + handleBuyAgainLogic(); } } else { buyText.text = ""; @@ -1062,11 +1162,13 @@ Rectangle { root.activeView = "checkoutMain"; } else { root.activeView = "checkoutSuccess"; + root.ownershipStatusReceived = false; + Commerce.alreadyOwned(root.itemId); + root.availableUpdatesReceived = false; + Commerce.getAvailableUpdates(root.itemId); + root.balanceReceived = false; + Commerce.balance(); } - root.balanceReceived = false; - root.ownershipStatusReceived = false; - Commerce.alreadyOwned(root.itemId); - Commerce.balance(); } // diff --git a/interface/resources/qml/hifi/commerce/common/EmulatedMarketplaceHeader.qml b/interface/resources/qml/hifi/commerce/common/EmulatedMarketplaceHeader.qml index 8a7e809b3d..8105688131 100644 --- a/interface/resources/qml/hifi/commerce/common/EmulatedMarketplaceHeader.qml +++ b/interface/resources/qml/hifi/commerce/common/EmulatedMarketplaceHeader.qml @@ -28,6 +28,7 @@ Item { property string referrerURL: (Account.metaverseServerURL + "/marketplace?"); readonly property int additionalDropdownHeight: usernameDropdown.height - myUsernameButton.anchors.bottomMargin; property alias usernameDropdownVisible: usernameDropdown.visible; + property bool messagesWaiting: false; height: mainContainer.height + additionalDropdownHeight; @@ -38,6 +39,7 @@ Item { if (walletStatus === 0) { sendToParent({method: "needsLogIn"}); } else if (walletStatus === 5) { + Commerce.getAvailableUpdates(); Commerce.getSecurityImage(); } else if (walletStatus > 5) { console.log("ERROR in EmulatedMarketplaceHeader.qml: Unknown wallet status: " + walletStatus); @@ -58,6 +60,14 @@ Item { securityImage.source = "image://security/securityImage"; } } + + onAvailableUpdatesResult: { + if (result.status !== 'success') { + console.log("Failed to get Available Updates", result.data.message); + } else { + root.messagesWaiting = result.data.updates.length > 0; + } + } } Component.onCompleted: { @@ -134,13 +144,25 @@ Item { anchors.fill: parent; hoverEnabled: enabled; onClicked: { - sendToParent({method: 'header_goToPurchases'}); + sendToParent({ method: 'header_goToPurchases', hasUpdates: root.messagesWaiting }); } onEntered: myPurchasesText.color = hifi.colors.blueHighlight; onExited: myPurchasesText.color = hifi.colors.blueAccent; } } + Rectangle { + id: messagesWaitingLight; + visible: root.messagesWaiting; + anchors.right: myPurchasesLink.left; + anchors.rightMargin: -2; + anchors.verticalCenter: parent.verticalCenter; + height: 10; + width: height; + radius: height/2; + color: "red"; + } + TextMetrics { id: textMetrics; font.family: "Raleway" diff --git a/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml b/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml index fb8e509cde..4cfa61c9ed 100644 --- a/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml +++ b/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml @@ -48,11 +48,14 @@ Item { property bool hasPermissionToRezThis; property bool permissionExplanationCardVisible; property bool isInstalled; + property string upgradeUrl; + property string upgradeTitle; + property bool isShowingMyItems; property string originalStatusText; property string originalStatusColor; - height: 110; + height: (root.upgradeUrl === "" || root.isShowingMyItems) ? 110 : 150; width: parent.width; Connections { @@ -137,6 +140,14 @@ Item { anchors.verticalCenter: parent.verticalCenter; height: root.height - 10; + // START "incorrect indentation to prevent insane diffs" + Item { + id: itemContainer; + anchors.left: parent.left; + anchors.right: parent.right; + anchors.top: parent.top; + height: 100; + Image { id: itemPreviewImage; source: root.itemPreviewImageUrl; @@ -357,7 +368,7 @@ Item { Item { id: statusContainer; - visible: root.purchaseStatus === "pending" || root.purchaseStatus === "invalidated" || root.purchaseStatusChanged; + visible: root.purchaseStatus === "pending" || root.purchaseStatus === "invalidated" || root.purchaseStatusChanged || root.numberSold > -1; anchors.left: itemName.left; anchors.top: certificateContainer.bottom; anchors.topMargin: 8; @@ -376,7 +387,7 @@ Item { "PENDING..." } else if (root.purchaseStatus === "invalidated") { "INVALIDATED" - } else if (root.numberSold !== -1) { + } else if (root.numberSold > -1) { ("Sales: " + root.numberSold + "/" + (root.limitedRun === -1 ? "\u221e" : root.limitedRun)) } else { "" @@ -634,6 +645,48 @@ Item { } } } + } + // END "incorrect indentation to prevent insane diffs" + + Rectangle { + id: upgradeAvailableContainer; + visible: root.upgradeUrl !== "" && !root.isShowingMyItems; + anchors.top: itemContainer.bottom; + anchors.bottom: parent.bottom; + anchors.left: parent.left; + anchors.right: parent.right; + color: "#B5EAFF"; + + RalewayRegular { + id: updateAvailableText; + text: "UPDATE AVAILABLE"; + size: 13; + anchors.left: parent.left; + anchors.leftMargin: 12; + anchors.top: parent.top; + anchors.bottom: parent.bottom; + width: paintedWidth; + color: hifi.colors.black; + verticalAlignment: Text.AlignVCenter; + } + + RalewaySemiBold { + id: updateNowText; + text: "Update this item now"; + size: 13; + anchors.left: updateAvailableText.right; + anchors.leftMargin: 16; + anchors.top: parent.top; + anchors.bottom: parent.bottom; + width: paintedWidth; + color: hifi.colors.black; + verticalAlignment: Text.AlignVCenter; + + onLinkActivated: { + sendToPurchases({method: 'updateItemClicked', itemId: root.itemId, itemEdition: root.itemEdition, upgradeUrl: root.upgradeUrl}); + } + } + } } DropShadow { diff --git a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml index c505baebf4..726e6bd338 100644 --- a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml +++ b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml @@ -37,6 +37,8 @@ Rectangle { property bool isDebuggingFirstUseTutorial: false; property int pendingItemCount: 0; property string installedApps; + property bool keyboardRaised: false; + property int numUpdatesAvailable: 0; // Style color: hifi.colors.white; Connections { @@ -64,6 +66,7 @@ Rectangle { root.activeView = "purchasesMain"; root.installedApps = Commerce.getInstalledApps(); Commerce.inventory(); + Commerce.getAvailableUpdates(); } } else { console.log("ERROR in Purchases.qml: Unknown wallet status: " + walletStatus); @@ -119,6 +122,15 @@ Rectangle { root.pendingInventoryReply = false; } + + onAvailableUpdatesResult: { + if (result.status !== 'success') { + console.log("Failed to get Available Updates", result.data.message); + } else { + sendToScript({method: 'purchases_availableUpdatesReceived', numUpdates: result.data.updates.length }); + root.numUpdatesAvailable = result.data.updates.length; + } + } } Timer { @@ -273,6 +285,7 @@ Rectangle { root.activeView = "purchasesMain"; root.installedApps = Commerce.getInstalledApps(); Commerce.inventory(); + Commerce.getAvailableUpdates(); break; } } @@ -296,6 +309,7 @@ Rectangle { // FILTER BAR START // Item { + z: 997; id: filterBarContainer; // Size height: 40; @@ -321,28 +335,61 @@ Rectangle { size: 22; } - HifiControlsUit.TextField { + HifiControlsUit.FilterBar { id: filterBar; property string previousText: ""; + property string previousPrimaryFilter: ""; colorScheme: hifi.colorSchemes.faintGray; - hasClearButton: true; - hasRoundedBorder: true; + anchors.top: parent.top; + anchors.right: parent.right; anchors.left: myText.right; anchors.leftMargin: 16; - height: 39; - anchors.verticalCenter: parent.verticalCenter; - anchors.right: parent.right; + textFieldHeight: 39; + height: textFieldHeight + dropdownHeight; placeholderText: "filter items"; + Component.onCompleted: { + var choices = [ + { + "displayName": "App", + "filterName": "app" + }, + { + "displayName": "Avatar", + "filterName": "avatar" + }, + { + "displayName": "Content Set", + "filterName": "contentSet" + }, + { + "displayName": "Entity", + "filterName": "entity" + }, + { + "displayName": "Wearable", + "filterName": "wearable" + }, + { + "displayName": "Updatable", + "filterName": "updatable" + } + ] + filterBar.primaryFilterChoices.clear(); + filterBar.primaryFilterChoices.append(choices); + } + + onPrimaryFilter_displayNameChanged: { + buildFilteredPurchasesModel(); + purchasesContentsList.positionViewAtIndex(0, ListView.Beginning) + filterBar.previousPrimaryFilter = filterBar.primaryFilter_displayName; + } + onTextChanged: { buildFilteredPurchasesModel(); purchasesContentsList.positionViewAtIndex(0, ListView.Beginning) filterBar.previousText = filterBar.text; } - - onAccepted: { - focus = false; - } } } // @@ -350,6 +397,7 @@ Rectangle { // HifiControlsUit.Separator { + z: 996; id: separator; colorScheme: 2; anchors.left: parent.left; @@ -377,12 +425,11 @@ Rectangle { clip: true; model: filteredPurchasesModel; snapMode: ListView.SnapToItem; - highlightRangeMode: ListView.StrictlyEnforceRange; // Anchors anchors.top: separator.bottom; anchors.topMargin: 12; anchors.left: parent.left; - anchors.bottom: parent.bottom; + anchors.bottom: updatesAvailableBanner.visible ? updatesAvailableBanner.top : parent.bottom; width: parent.width; delegate: PurchasedItem { itemName: title; @@ -398,21 +445,10 @@ Rectangle { displayedItemCount: model.displayedItemCount; permissionExplanationCardVisible: model.permissionExplanationCardVisible; isInstalled: model.isInstalled; - itemType: { - if (model.root_file_url.indexOf(".fst") > -1) { - "avatar"; - } else if (model.categories.indexOf("Wearables") > -1) { - "wearable"; - } else if (model.root_file_url.endsWith('.json.gz')) { - "contentSet"; - } else if (model.root_file_url.endsWith('.app.json')) { - "app"; - } else if (model.root_file_url.endsWith('.json')) { - "entity"; - } else { - "unknown"; - } - } + upgradeUrl: model.upgrade_url; + upgradeTitle: model.upgrade_title; + itemType: model.itemType; + isShowingMyItems: root.isShowingMyItems; anchors.topMargin: 10; anchors.bottomMargin: 10; @@ -485,15 +521,80 @@ Rectangle { filteredPurchasesModel.setProperty(i, "permissionExplanationCardVisible", true); } } + } else if (msg.method === "updateItemClicked") { + sendToScript(msg); } } } } } + Rectangle { + id: updatesAvailableBanner; + visible: root.numUpdatesAvailable > 0 && !root.isShowingMyItems; + anchors.bottom: parent.bottom; + anchors.left: parent.left; + anchors.right: parent.right; + height: 75; + color: "#B5EAFF"; + + Rectangle { + id: updatesAvailableGlyph; + anchors.verticalCenter: parent.verticalCenter; + anchors.left: parent.left; + anchors.leftMargin: 16; + // Size + width: 10; + height: width; + radius: width/2; + // Style + color: "red"; + } + + RalewaySemiBold { + text: "You have " + root.numUpdatesAvailable + " item updates available."; + // Text size + size: 18; + // Anchors + anchors.left: updatesAvailableGlyph.right; + anchors.leftMargin: 12; + height: parent.height; + width: paintedWidth; + // Style + color: hifi.colors.black; + // Alignment + verticalAlignment: Text.AlignVCenter; + } + + MouseArea { + anchors.fill: parent; + hoverEnabled: true; + propagateComposedEvents: false; + } + + HifiControlsUit.Button { + color: hifi.buttons.white; + colorScheme: hifi.colorSchemes.dark; + anchors.verticalCenter: parent.verticalCenter; + anchors.right: parent.right; + anchors.rightMargin: 12; + width: 100; + height: 40; + text: "SHOW ME"; + onClicked: { + filterBar.text = ""; + filterBar.changeFilterByDisplayName("Updatable"); + } + } + } + Item { id: noItemsAlertContainer; - visible: !purchasesContentsList.visible && root.purchasesReceived && root.isShowingMyItems && filterBar.text === ""; + visible: !purchasesContentsList.visible && + root.purchasesReceived && + root.isShowingMyItems && + filterBar.text === "" && + filterBar.primaryFilter_displayName === ""; anchors.top: filterBarContainer.bottom; anchors.topMargin: 12; anchors.left: parent.left; @@ -539,7 +640,11 @@ Rectangle { Item { id: noPurchasesAlertContainer; - visible: !purchasesContentsList.visible && root.purchasesReceived && !root.isShowingMyItems && filterBar.text === ""; + visible: !purchasesContentsList.visible && + root.purchasesReceived && + !root.isShowingMyItems && + filterBar.text === "" && + filterBar.primaryFilter_displayName === ""; anchors.top: filterBarContainer.bottom; anchors.topMargin: 12; anchors.left: parent.left; @@ -589,7 +694,7 @@ Rectangle { HifiControlsUit.Keyboard { id: keyboard; - raised: HMD.mounted && filterBar.focus; + raised: HMD.mounted && parent.keyboardRaised; numeric: parent.punctuationMode; anchors { bottom: parent.bottom; @@ -613,6 +718,7 @@ Rectangle { console.log("Refreshing Purchases..."); root.pendingInventoryReply = true; Commerce.inventory(); + Commerce.getAvailableUpdates(); } } } @@ -660,8 +766,13 @@ Rectangle { var sameItemCount = 0; tempPurchasesModel.clear(); + for (var i = 0; i < purchasesModel.count; i++) { if (purchasesModel.get(i).title.toLowerCase().indexOf(filterBar.text.toLowerCase()) !== -1) { + if (!purchasesModel.get(i).valid) { + continue; + } + if (purchasesModel.get(i).status !== "confirmed" && !root.isShowingMyItems) { tempPurchasesModel.insert(0, purchasesModel.get(i)); } else if ((root.isShowingMyItems && purchasesModel.get(i).edition_number === "0") || @@ -671,6 +782,35 @@ Rectangle { } } + // primaryFilter filtering and adding of itemType property to model + var currentItemType, currentRootFileUrl, currentCategories; + for (var i = 0; i < tempPurchasesModel.count; i++) { + currentRootFileUrl = tempPurchasesModel.get(i).root_file_url; + currentCategories = tempPurchasesModel.get(i).categories; + + if (currentRootFileUrl.indexOf(".fst") > -1) { + currentItemType = "avatar"; + } else if (currentCategories.indexOf("Wearables") > -1) { + currentItemType = "wearable"; + } else if (currentRootFileUrl.endsWith('.json.gz')) { + currentItemType = "contentSet"; + } else if (currentRootFileUrl.endsWith('.app.json')) { + currentItemType = "app"; + } else if (currentRootFileUrl.endsWith('.json')) { + currentItemType = "entity"; + } else { + currentItemType = "unknown"; + } + if (filterBar.primaryFilter_displayName !== "" && + ((filterBar.primaryFilter_displayName === "Updatable" && tempPurchasesModel.get(i).upgrade_url === "") || + (filterBar.primaryFilter_displayName !== "Updatable" && filterBar.primaryFilter_filterName.toLowerCase() !== currentItemType.toLowerCase()))) { + tempPurchasesModel.remove(i); + i--; + } else { + tempPurchasesModel.setProperty(i, 'itemType', currentItemType); + } + } + for (var i = 0; i < tempPurchasesModel.count; i++) { if (!filteredPurchasesModel.get(i)) { sameItemCount = -1; @@ -682,12 +822,17 @@ Rectangle { } } - if (sameItemCount !== tempPurchasesModel.count || filterBar.text !== filterBar.previousText) { + if (sameItemCount !== tempPurchasesModel.count || + filterBar.text !== filterBar.previousText || + filterBar.primaryFilter !== filterBar.previousPrimaryFilter) { filteredPurchasesModel.clear(); var currentId; for (var i = 0; i < tempPurchasesModel.count; i++) { currentId = tempPurchasesModel.get(i).id; - + + if (!purchasesModel.get(i).valid) { + continue; + } filteredPurchasesModel.append(tempPurchasesModel.get(i)); filteredPurchasesModel.setProperty(i, 'permissionExplanationCardVisible', false); filteredPurchasesModel.setProperty(i, 'isInstalled', ((root.installedApps).indexOf(currentId) > -1)); @@ -736,7 +881,7 @@ Rectangle { function fromScript(message) { switch (message.method) { case 'updatePurchases': - referrerURL = message.referrerURL; + referrerURL = message.referrerURL || ""; titleBarContainer.referrerURL = message.referrerURL; filterBar.text = message.filterText ? message.filterText : ""; break; diff --git a/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml b/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml index 27660b5e9e..7a14ee060f 100644 --- a/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml +++ b/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml @@ -39,6 +39,7 @@ Item { root.noMoreHistoryData = false; root.historyRequestPending = true; Commerce.history(root.currentHistoryPage); + Commerce.getAvailableUpdates(); } else { refreshTimer.stop(); } @@ -133,6 +134,14 @@ Item { refreshTimer.start(); } } + + onAvailableUpdatesResult: { + if (result.status !== 'success') { + console.log("Failed to get Available Updates", result.data.message); + } else { + sendToScript({method: 'wallet_availableUpdatesReceived', numUpdates: result.data.updates.length }); + } + } } Connections { diff --git a/interface/src/commerce/Ledger.cpp b/interface/src/commerce/Ledger.cpp index 858af9b13d..0a9e867323 100644 --- a/interface/src/commerce/Ledger.cpp +++ b/interface/src/commerce/Ledger.cpp @@ -52,6 +52,8 @@ Handler(inventory) Handler(transferHfcToNode) Handler(transferHfcToUsername) Handler(alreadyOwned) +Handler(availableUpdates) +Handler(updateItem) void Ledger::send(const QString& endpoint, const QString& success, const QString& fail, QNetworkAccessManager::Operation method, AccountManagerAuth::Type authType, QJsonObject request) { auto accountManager = DependencyManager::get(); @@ -376,3 +378,23 @@ void Ledger::alreadyOwned(const QString& marketplaceId) { qDebug(commerce) << "User attempted to use the alreadyOwned endpoint, but cachedPublicKeys was empty!"; } } + +void Ledger::getAvailableUpdates(const QString& itemId) { + auto wallet = DependencyManager::get(); + QString endpoint = "available_updates"; + QJsonObject request; + request["public_keys"] = QJsonArray::fromStringList(wallet->listPublicKeys()); + if (!itemId.isEmpty()) { + request["marketplace_item_id"] = itemId; + } + send(endpoint, "availableUpdatesSuccess", "availableUpdatesFailure", QNetworkAccessManager::PutOperation, AccountManagerAuth::Required, request); +} + +void Ledger::updateItem(const QString& hfc_key, const QString& certificate_id) { + QJsonObject transaction; + transaction["public_key"] = hfc_key; + transaction["certificate_id"] = certificate_id; + QJsonDocument transactionDoc{ transaction }; + auto transactionString = transactionDoc.toJson(QJsonDocument::Compact); + signedSend("transaction", transactionString, hfc_key, "update_item", "updateItemSuccess", "updateItemFailure"); +} diff --git a/interface/src/commerce/Ledger.h b/interface/src/commerce/Ledger.h index 703ebda2dc..da97206bbc 100644 --- a/interface/src/commerce/Ledger.h +++ b/interface/src/commerce/Ledger.h @@ -36,6 +36,8 @@ public: void transferHfcToNode(const QString& hfc_key, const QString& nodeID, const int& amount, const QString& optionalMessage); void transferHfcToUsername(const QString& hfc_key, const QString& username, const int& amount, const QString& optionalMessage); void alreadyOwned(const QString& marketplaceId); + void getAvailableUpdates(const QString& itemId = ""); + void updateItem(const QString& hfc_key, const QString& certificate_id); enum CertificateStatus { CERTIFICATE_STATUS_UNKNOWN = 0, @@ -57,6 +59,8 @@ signals: void transferHfcToNodeResult(QJsonObject result); void transferHfcToUsernameResult(QJsonObject result); void alreadyOwnedResult(QJsonObject result); + void availableUpdatesResult(QJsonObject result); + void updateItemResult(QJsonObject result); void updateCertificateStatus(const QString& certID, uint certStatus); @@ -83,6 +87,10 @@ public slots: void transferHfcToUsernameFailure(QNetworkReply& reply); void alreadyOwnedSuccess(QNetworkReply& reply); void alreadyOwnedFailure(QNetworkReply& reply); + void availableUpdatesSuccess(QNetworkReply& reply); + void availableUpdatesFailure(QNetworkReply& reply); + void updateItemSuccess(QNetworkReply& reply); + void updateItemFailure(QNetworkReply& reply); private: QJsonObject apiResponse(const QString& label, QNetworkReply& reply); diff --git a/interface/src/commerce/QmlCommerce.cpp b/interface/src/commerce/QmlCommerce.cpp index 53ec59049f..8e956249cc 100644 --- a/interface/src/commerce/QmlCommerce.cpp +++ b/interface/src/commerce/QmlCommerce.cpp @@ -38,7 +38,8 @@ QmlCommerce::QmlCommerce() { connect(ledger.data(), &Ledger::updateCertificateStatus, this, &QmlCommerce::updateCertificateStatus); connect(ledger.data(), &Ledger::transferHfcToNodeResult, this, &QmlCommerce::transferHfcToNodeResult); connect(ledger.data(), &Ledger::transferHfcToUsernameResult, this, &QmlCommerce::transferHfcToUsernameResult); - connect(ledger.data(), &Ledger::transferHfcToUsernameResult, this, &QmlCommerce::transferHfcToUsernameResult); + connect(ledger.data(), &Ledger::availableUpdatesResult, this, &QmlCommerce::availableUpdatesResult); + connect(ledger.data(), &Ledger::updateItemResult, this, &QmlCommerce::updateItemResult); auto accountManager = DependencyManager::get(); connect(accountManager.data(), &AccountManager::usernameChanged, this, [&]() { @@ -349,3 +350,20 @@ bool QmlCommerce::openApp(const QString& itemHref) { return true; } + +void QmlCommerce::getAvailableUpdates(const QString& itemId) { + auto ledger = DependencyManager::get(); + ledger->getAvailableUpdates(itemId); +} + +void QmlCommerce::updateItem(const QString& certificateId) { + auto ledger = DependencyManager::get(); + auto wallet = DependencyManager::get(); + QStringList keys = wallet->listPublicKeys(); + if (keys.count() == 0) { + QJsonObject result{ { "status", "fail" },{ "message", "Uninitialized Wallet." } }; + return emit updateItemResult(result); + } + QString key = keys[0]; + ledger->updateItem(key, certificateId); +} diff --git a/interface/src/commerce/QmlCommerce.h b/interface/src/commerce/QmlCommerce.h index b4af4393e3..a3a0ebfd32 100644 --- a/interface/src/commerce/QmlCommerce.h +++ b/interface/src/commerce/QmlCommerce.h @@ -43,6 +43,8 @@ signals: void accountResult(QJsonObject result); void certificateInfoResult(QJsonObject result); void alreadyOwnedResult(QJsonObject result); + void availableUpdatesResult(QJsonObject result); + void updateItemResult(QJsonObject result); void updateCertificateStatus(const QString& certID, uint certStatus); @@ -89,6 +91,9 @@ protected: Q_INVOKABLE bool uninstallApp(const QString& appHref); Q_INVOKABLE bool openApp(const QString& appHref); + Q_INVOKABLE void getAvailableUpdates(const QString& itemId = ""); + Q_INVOKABLE void updateItem(const QString& certificateId); + private: QString _appsPath; }; diff --git a/scripts/system/commerce/wallet.js b/scripts/system/commerce/wallet.js index 26ffb08796..d2e7d3ffc8 100644 --- a/scripts/system/commerce/wallet.js +++ b/scripts/system/commerce/wallet.js @@ -698,6 +698,9 @@ Window.location = "hifi://BankOfHighFidelity"; } break; + case 'wallet_availableUpdatesReceived': + // NOP + break; default: print('Unrecognized message from QML:', JSON.stringify(message)); } diff --git a/scripts/system/html/js/marketplacesInject.js b/scripts/system/html/js/marketplacesInject.js index fb49de1050..864c7d92b4 100644 --- a/scripts/system/html/js/marketplacesInject.js +++ b/scripts/system/html/js/marketplacesInject.js @@ -30,6 +30,7 @@ var userIsLoggedIn = false; var walletNeedsSetup = false; var marketplaceBaseURL = "https://highfidelity.com"; + var messagesWaiting = false; function injectCommonCode(isDirectoryPage) { @@ -205,16 +206,22 @@ purchasesElement.id = "purchasesButton"; purchasesElement.setAttribute('href', "#"); - purchasesElement.innerHTML = "My Purchases"; + purchasesElement.innerHTML = ""; + if (messagesWaiting) { + purchasesElement.innerHTML += " "; + } + purchasesElement.innerHTML += "My Purchases"; // FRONTEND WEBDEV RANT: The username dropdown should REALLY not be programmed to be on the same // line as the search bar, overlaid on top of the search bar, floated right, and then relatively bumped up using "top:-50px". + $('.navbar-brand').css('margin-right', '10px'); purchasesElement.style = "height:100%;margin-top:18px;font-weight:bold;float:right;margin-right:" + (dropDownElement.offsetWidth + 30) + "px;position:relative;z-index:999;"; navbarBrandElement.parentNode.insertAdjacentElement('beforeend', purchasesElement); $('#purchasesButton').on('click', function () { EventBridge.emitWebEvent(JSON.stringify({ type: "PURCHASES", - referrerURL: window.location.href + referrerURL: window.location.href, + hasUpdates: messagesWaiting })); }); } @@ -243,7 +250,7 @@ }); } - function buyButtonClicked(id, name, author, price, href, referrer) { + function buyButtonClicked(id, name, author, price, href, referrer, edition) { EventBridge.emitWebEvent(JSON.stringify({ type: "CHECKOUT", itemId: id, @@ -251,7 +258,8 @@ itemPrice: price ? parseInt(price, 10) : 0, itemHref: href, referrer: referrer, - itemAuthor: author + itemAuthor: author, + itemEdition: edition })); } @@ -319,7 +327,8 @@ $(this).closest('.grid-item').find('.creator').find('.value').text(), $(this).closest('.grid-item').find('.item-cost').text(), $(this).attr('data-href'), - "mainPage"); + "mainPage", + -1); }); } @@ -410,7 +419,11 @@ } var cost = $('.item-cost').text(); - if (availability !== 'available') { + var isUpdating = window.location.href.indexOf('edition=') > -1; + var urlParams = new URLSearchParams(window.location.search); + if (isUpdating) { + purchaseButton.html('UPDATE FOR FREE'); + } else if (availability !== 'available') { purchaseButton.html('UNAVAILABLE (' + availability + ')'); } else if (parseInt(cost) > 0 && $('#side-info').find('#buyItemButton').size() === 0) { purchaseButton.html('PURCHASE