History

Merge branch 'serverless-domains' into serverless-domains

Dante Ruiz committed 8 years ago

Commit 364081d2 parents 7ea4042 9b8c92531 files · +597 −249Browse files

Unified · Split

31 files
  1. assignment-client/src/audio/AudioMixer.cpp +2 −2
  2. assignment-client/src/audio/AudioMixer.h +2 −0
  3. assignment-client/src/audio/AudioMixerClientData.cpp +2 −2
  4. assignment-client/src/audio/AudioMixerClientData.h +1 −0
  5. assignment-client/src/audio/AudioMixerSlave.cpp +2 −2
  6. interface/resources/fonts/hifi-glyphs.ttf +0 −0
  7. interface/resources/qml/controls-uit/Separator.qml +2 −2
  8. interface/resources/qml/controls-uit/TextField.qml +2 −3
  9. interface/resources/qml/hifi/commerce/checkout/Checkout.qml +195 −90
  10. interface/resources/qml/hifi/commerce/common/CommerceLightbox.qml +4 −0
  11. interface/resources/qml/hifi/commerce/common/FirstUseTutorial.qml +2 −2
  12. interface/resources/qml/hifi/commerce/inspectionCertificate/InspectionCertificate.qml +1 −0
  13. interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml +201 −41
  14. interface/resources/qml/hifi/commerce/purchases/Purchases.qml +76 −78
  15. interface/resources/qml/styles-uit/HifiConstants.qml +5 −1
  16. interface/src/Application.cpp +19 −15
  17. interface/src/Application.h +2 −0
  18. interface/src/avatar/MyAvatar.cpp +1 −1
  19. interface/src/commerce/Ledger.cpp +10 −0
  20. interface/src/commerce/Ledger.h +4 −0
  21. interface/src/commerce/QmlCommerce.cpp +20 −0
  22. interface/src/commerce/QmlCommerce.h +7 −0
  23. interface/src/scripting/AccountServicesScriptingInterface.h +1 −1
  24. libraries/avatars/src/AvatarData.h +2 −1
  25. libraries/avatars/src/ScriptAvatarData.cpp +1 −0
  26. libraries/avatars/src/ScriptAvatarData.h +2 −1
  27. libraries/entities/src/EntityScriptingInterface.cpp +5 −0
  28. libraries/entities/src/EntityScriptingInterface.h +6 −0
  29. libraries/render-utils/src/Shadow.slh +3 −1
  30. libraries/shared/src/PathUtils.cpp +1 −0
  31. scripts/system/marketplaces/marketplaces.js +17 −7
assignment-client/src/audio/AudioMixer.cpp+2−2
@@ -9,6 +9,8 @@ · 8 unmodified lines
99 // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
1010 //
1111
12+#include "AudioMixer.h"
13+
1214 #include <thread>
1315
1416 #include <QtCore/QJsonArray>
@@ -36,8 +38,6 @@ · 21 unmodified lines
3638 #include "AvatarAudioStream.h"
3739 #include "InjectedAudioStream.h"
3840
39−#include "AudioMixer.h"
40−
4141 static const float DEFAULT_ATTENUATION_PER_DOUBLING_IN_DISTANCE = 0.5f; // attenuation = -6dB * log2(distance)
4242 static const int DISABLE_STATIC_JITTER_FRAMES = -1;
4343 static const float DEFAULT_NOISE_MUTING_THRESHOLD = 1.0f;
assignment-client/src/audio/AudioMixer.h+2−0
@@ -18,6 +18,8 @@ · 17 unmodified lines
1818 #include <ThreadedAssignment.h>
1919 #include <UUIDHasher.h>
2020
21+#include <plugins/Forward.h>
22+
2123 #include "AudioMixerStats.h"
2224 #include "AudioMixerSlavePool.h"
2325
assignment-client/src/audio/AudioMixerClientData.cpp+2−2
@@ -9,6 +9,8 @@ · 8 unmodified lines
99 // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
1010 //
1111
12+#include "AudioMixerClientData.h"
13+
1214 #include <random>
1315
1416 #include <QtCore/QDebug>
@@ -22,8 +24,6 @@ · 7 unmodified lines
2224 #include "AudioLogging.h"
2325 #include "AudioHelpers.h"
2426 #include "AudioMixer.h"
25−#include "AudioMixerClientData.h"
26−
2727
2828 AudioMixerClientData::AudioMixerClientData(const QUuid& nodeID) :
2929 NodeData(nodeID),
assignment-client/src/audio/AudioMixerClientData.h+1−0
@@ -21,6 +21,7 @@ · 20 unmodified lines
2121 #include <AudioLimiter.h>
2222 #include <UUIDHasher.h>
2323
24+#include <plugins/Forward.h>
2425 #include <plugins/CodecPlugin.h>
2526
2627 #include "PositionalAudioStream.h"
assignment-client/src/audio/AudioMixerSlave.cpp+2−2
@@ -9,6 +9,8 @@ · 8 unmodified lines
99 // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
1010 //
1111
12+#include "AudioMixerSlave.h"
13+
1214 #include <algorithm>
1315
1416 #include <glm/glm.hpp>
@@ -34,8 +36,6 @@ · 19 unmodified lines
3436 #include "InjectedAudioStream.h"
3537 #include "AudioHelpers.h"
3638
37−#include "AudioMixerSlave.h"
38−
3939 using AudioStreamMap = AudioMixerClientData::AudioStreamMap;
4040
4141 // packet helpers
interface/resources/fonts/hifi-glyphs.ttf+0−0

Binary file not shown.

interface/resources/qml/controls-uit/Separator.qml+2−2
@@ -14,8 +14,8 @@ import "../styles-uit" · 13 unmodified lines
1414 Item {
1515 property int colorScheme: 0;
1616
17− readonly property var topColor: [ hifi.colors.baseGrayShadow, hifi.colors.faintGray ];
18− readonly property var bottomColor: [ hifi.colors.baseGrayHighlight, hifi.colors.faintGray ];
17+ readonly property var topColor: [ hifi.colors.baseGrayShadow, hifi.colors.faintGray, "#89858C" ];
18+ readonly property var bottomColor: [ hifi.colors.baseGrayHighlight, hifi.colors.faintGray, "#89858C" ];
1919
2020 // Size
2121 height: colorScheme === 0 ? 2 : 1;
interface/resources/qml/controls-uit/TextField.qml+2−3
@@ -34,11 +34,10 @@ TextField { · 33 unmodified lines
3434
3535 placeholderText: textField.placeholderText
3636
37− FontLoader { id: firaSansSemiBold; source: "../../fonts/FiraSans-SemiBold.ttf"; }
37+ FontLoader { id: firaSansRegular; source: "../../fonts/FiraSans-Regular.ttf"; }
3838 FontLoader { id: hifiGlyphs; source: "../../fonts/hifi-glyphs.ttf"; }
39− font.family: firaSansSemiBold.name
39+ font.family: firaSansRegular.name
4040 font.pixelSize: hifi.fontSizes.textFieldInput
41− font.italic: textField.text == ""
4241 height: implicitHeight + 3 // Make surrounding box higher so that highlight is vertically centered.
4342 property alias textFieldLabel: textFieldLabel
4443
interface/resources/qml/hifi/commerce/checkout/Checkout.qml+195−90
@@ -28,7 +28,7 @@ Rectangle { · 27 unmodified lines
2828 id: root;
2929 objectName: "checkout"
3030 property string activeView: "initialize";
31− property bool purchasesReceived: false;
31+ property bool ownershipStatusReceived: false;
3232 property bool balanceReceived: false;
3333 property string itemName;
3434 property string itemId;
@@ -37,11 +37,16 @@ Rectangle { · 2 unmodified lines
3737 property double balanceAfterPurchase;
3838 property bool alreadyOwned: false;
3939 property int itemPrice: -1;
40− property bool itemIsJson: true;
40+ property bool isCertified;
41+ property string itemType;
42+ property var itemTypesArray: ["entity", "wearable", "contentSet", "app", "avatar"];
43+ property var itemTypesText: ["entity", "wearable", "content set", "app", "avatar"];
44+ property var buttonTextNormal: ["REZ", "WEAR", "REPLACE CONTENT SET", "INSTALL", "WEAR"];
45+ property var buttonTextClicked: ["REZZED!", "WORN!", "CONTENT SET REPLACED!", "INSTALLED!", "AVATAR CHANGED!"]
46+ property var buttonGlyph: [hifi.glyphs.wand, hifi.glyphs.hat, hifi.glyphs.globe, hifi.glyphs.install, hifi.glyphs.avatar];
4147 property bool shouldBuyWithControlledFailure: false;
4248 property bool debugCheckoutSuccess: false;
4349 property bool canRezCertifiedItems: Entities.canRezCertified() || Entities.canRezTmpCertified();
44− property bool isWearable;
4550 property string referrer;
4651 // Style
4752 color: hifi.colors.white;
@@ -85,7 +90,9 @@ Rectangle { · 37 unmodified lines
8590 UserActivityLogger.commercePurchaseFailure(root.itemId, root.itemAuthor, root.itemPrice, !root.alreadyOwned, result.message);
8691 } else {
8792 root.itemHref = result.data.download_url;
88− root.isWearable = result.data.categories.indexOf("Wearables") > -1;
93+ if (result.data.categories.indexOf("Wearables") > -1) {
94+ root.itemType = "wearable";
95+ }
8996 root.activeView = "checkoutSuccess";
9097 UserActivityLogger.commercePurchaseSuccess(root.itemId, root.itemAuthor, root.itemPrice, !root.alreadyOwned);
9198 }
@@ -97,32 +104,53 @@ Rectangle { · 5 unmodified lines
97104 } else {
98105 root.balanceReceived = true;
99106 root.balanceAfterPurchase = result.data.balance - root.itemPrice;
100− root.setBuyText();
107+ root.refreshBuyUI();
101108 }
102109 }
103110
104− onInventoryResult: {
111+ onAlreadyOwnedResult: {
105112 if (result.status !== 'success') {
106− console.log("Failed to get purchases", result.data.message);
113+ console.log("Failed to get Already Owned status", result.data.message);
107114 } else {
108− root.purchasesReceived = true;
109− if (purchasesContains(result.data.assets, itemId)) {
110− root.alreadyOwned = true;
115+ root.ownershipStatusReceived = true;
116+ if (result.data.marketplace_item_id === root.itemId) {
117+ root.alreadyOwned = result.data.already_owned;
111118 } else {
119+ console.log("WARNING - Received 'Already Owned' status about different Marketplace ID!");
112120 root.alreadyOwned = false;
113121 }
114− root.setBuyText();
122+ root.refreshBuyUI();
115123 }
116124 }
117125 }
118126
119127 onItemIdChanged: {
120− Commerce.inventory();
128+ root.ownershipStatusReceived = false;
129+ Commerce.alreadyOwned(root.itemId);
121130 itemPreviewImage.source = "https://hifi-metaverse.s3-us-west-1.amazonaws.com/marketplace/previews/" + itemId + "/thumbnail/hifi-mp-" + itemId + ".jpg";
122131 }
123132
124133 onItemHrefChanged: {
125− itemIsJson = root.itemHref.endsWith('.json');
134+ if (root.itemHref.indexOf(".fst") > -1) {
135+ root.itemType = "avatar";
136+ } else if (root.itemHref.indexOf('.json.gz') > -1) {
137+ root.itemType = "contentSet";
138+ } else if (root.itemHref.indexOf('.app.json') > -1) {
139+ root.itemType = "app";
140+ } else if (root.itemHref.indexOf('.json') > -1) {
141+ root.itemType = "entity"; // "wearable" type handled later
142+ } else {
143+ console.log("WARNING - Item type is UNKNOWN!");
144+ root.itemType = "entity";
145+ }
146+ }
147+
148+ onItemTypeChanged: {
149+ if (root.itemType === "entity" || root.itemType === "wearable" || root.itemType === "contentSet" || root.itemType === "avatar") {
150+ root.isCertified = true;
151+ } else {
152+ root.isCertified = false;
153+ }
126154 }
127155
128156 onItemPriceChanged: {
@@ -203,7 +231,7 @@ Rectangle { · 74 unmodified lines
203231 color: hifi.colors.white;
204232
205233 Component.onCompleted: {
206− purchasesReceived = false;
234+ ownershipStatusReceived = false;
207235 balanceReceived = false;
208236 Commerce.getWalletStatus();
209237 }
@@ -278,6 +306,31 @@ Rectangle { · 68 unmodified lines
278306 anchors.left: parent.left;
279307 anchors.right: parent.right;
280308
309+ Rectangle {
310+ id: loading;
311+ z: 997;
312+ visible: !root.ownershipStatusReceived || !root.balanceReceived;
313+ anchors.fill: parent;
314+ color: Qt.rgba(0.0, 0.0, 0.0, 0.7);
315+
316+ // This object is always used in a popup.
317+ // This MouseArea is used to prevent a user from being
318+ // able to click on a button/mouseArea underneath the popup/section.
319+ MouseArea {
320+ anchors.fill: parent;
321+ hoverEnabled: true;
322+ propagateComposedEvents: false;
323+ }
324+
325+ AnimatedImage {
326+ source: "../common/images/loader.gif"
327+ width: 96;
328+ height: width;
329+ anchors.verticalCenter: parent.verticalCenter;
330+ anchors.horizontalCenter: parent.horizontalCenter;
331+ }
332+ }
333+
281334 RalewayRegular {
282335 id: confirmPurchaseText;
283336 anchors.top: parent.top;
@@ -286,8 +339,8 @@ Rectangle { · 2 unmodified lines
286339 anchors.leftMargin: 16;
287340 width: paintedWidth;
288341 height: paintedHeight;
289− text: "Confirm Purchase:";
290− color: hifi.colors.baseGray;
342+ text: "Review Purchase:";
343+ color: hifi.colors.black;
291344 size: 28;
292345 }
293346
@@ -400,7 +453,7 @@ Rectangle { · 106 unmodified lines
400453 width: root.width;
401454 // Anchors
402455 anchors.top: separator2.bottom;
403− anchors.topMargin: 16;
456+ anchors.topMargin: 0;
404457 anchors.left: parent.left;
405458 anchors.leftMargin: 16;
406459 anchors.right: parent.right;
@@ -411,8 +464,8 @@ Rectangle { · 4 unmodified lines
411464 Rectangle {
412465 id: buyTextContainer;
413466 visible: buyText.text !== "";
414− anchors.top: cancelPurchaseButton.bottom;
415− anchors.topMargin: 16;
467+ anchors.top: parent.top;
468+ anchors.topMargin: 10;
416469 anchors.left: parent.left;
417470 anchors.right: parent.right;
418471 height: buyText.height + 30;
@@ -454,32 +507,63 @@ Rectangle { · 35 unmodified lines
454507 // Alignment
455508 horizontalAlignment: Text.AlignLeft;
456509 verticalAlignment: Text.AlignVCenter;
510+ }
511+ }
457512
458− onLinkActivated: {
459− sendToScript({method: 'checkout_goToPurchases', filterText: root.itemName});
460− }
513+ // "View in My Purchases" button
514+ HifiControlsUit.Button {
515+ id: viewInMyPurchasesButton;
516+ visible: false;
517+ color: hifi.buttons.blue;
518+ colorScheme: hifi.colorSchemes.light;
519+ anchors.top: buyTextContainer.visible ? buyTextContainer.bottom : checkoutActionButtonsContainer.top;
520+ anchors.topMargin: 10;
521+ height: 50;
522+ anchors.left: parent.left;
523+ anchors.right: parent.right;
524+ text: "VIEW THIS ITEM IN MY PURCHASES";
525+ onClicked: {
526+ sendToScript({method: 'checkout_goToPurchases', filterText: root.itemName});
461527 }
462528 }
463529
464530 // "Buy" button
465531 HifiControlsUit.Button {
466532 id: buyButton;
467− enabled: (root.balanceAfterPurchase >= 0 && purchasesReceived && balanceReceived) || !itemIsJson;
468− color: hifi.buttons.blue;
533+ visible: !((root.itemType === "avatar" || root.itemType === "app") && viewInMyPurchasesButton.visible)
534+ enabled: (root.balanceAfterPurchase >= 0 && ownershipStatusReceived && balanceReceived) || (!root.isCertified);
535+ color: viewInMyPurchasesButton.visible ? hifi.buttons.white : hifi.buttons.blue;
469536 colorScheme: hifi.colorSchemes.light;
470− anchors.top: checkoutActionButtonsContainer.top;
471− anchors.topMargin: 16;
472− height: 40;
537+ anchors.top: viewInMyPurchasesButton.visible ? viewInMyPurchasesButton.bottom :
538+ (buyTextContainer.visible ? buyTextContainer.bottom : checkoutActionButtonsContainer.top);
539+ anchors.topMargin: 10;
540+ height: 50;
473541 anchors.left: parent.left;
474542 anchors.right: parent.right;
475− text: (itemIsJson ? ((purchasesReceived && balanceReceived) ? "Confirm Purchase" : "--") : "Get Item");
543+ text: ((root.isCertified) ? ((ownershipStatusReceived && balanceReceived) ?
544+ (viewInMyPurchasesButton.visible ? "Buy It Again" : "Confirm Purchase") : "--") : "Get Item");
476545 onClicked: {
477− if (itemIsJson) {
478− buyButton.enabled = false;
546+ if (root.isCertified) {
479547 if (!root.shouldBuyWithControlledFailure) {
480− Commerce.buy(itemId, itemPrice);
548+ if (root.itemType === "contentSet" && !Entities.canReplaceContent()) {
549+ lightboxPopup.titleText = "Purchase Content Set";
550+ lightboxPopup.bodyText = "You will not be able to replace this domain's content with <b>" + root.itemName +
551+ " </b>until the server owner gives you 'Replace Content' permissions.<br><br>Are you sure you want to purchase this content set?";
552+ lightboxPopup.button1text = "CANCEL";
553+ lightboxPopup.button1method = "root.visible = false;"
554+ lightboxPopup.button2text = "CONFIRM";
555+ lightboxPopup.button2method = "Commerce.buy('" + root.itemId + "', " + root.itemPrice + ");" +
556+ "root.visible = false; buyButton.enabled = false; loading.visible = true;";
557+ lightboxPopup.visible = true;
558+ } else {
559+ buyButton.enabled = false;
560+ loading.visible = true;
561+ Commerce.buy(root.itemId, root.itemPrice);
562+ }
481563 } else {
482− Commerce.buy(itemId, itemPrice, true);
564+ buyButton.enabled = false;
565+ loading.visible = true;
566+ Commerce.buy(root.itemId, root.itemPrice, true);
483567 }
484568 } else {
485569 if (urlHandler.canHandleUrl(itemHref)) {
@@ -494,9 +578,9 @@ Rectangle { · 8 unmodified lines
494578 id: cancelPurchaseButton;
495579 color: hifi.buttons.noneBorderlessGray;
496580 colorScheme: hifi.colorSchemes.light;
497− anchors.top: buyButton.bottom;
498− anchors.topMargin: 16;
499− height: 40;
581+ anchors.top: buyButton.visible ? buyButton.bottom : viewInMyPurchasesButton.bottom;
582+ anchors.topMargin: 10;
583+ height: 50;
500584 anchors.left: parent.left;
501585 anchors.right: parent.right;
502586 text: "Cancel"
@@ -522,31 +606,32 @@ Rectangle { · 19 unmodified lines
522606 anchors.top: titleBarContainer.bottom;
523607 anchors.bottom: root.bottom;
524608 anchors.left: parent.left;
525− anchors.leftMargin: 16;
609+ anchors.leftMargin: 20;
526610 anchors.right: parent.right;
527− anchors.rightMargin: 16;
611+ anchors.rightMargin: 20;
528612
529613 RalewayRegular {
530614 id: completeText;
531615 anchors.top: parent.top;
532− anchors.topMargin: 30;
616+ anchors.topMargin: 18;
533617 anchors.left: parent.left;
534618 width: paintedWidth;
535619 height: paintedHeight;
536620 text: "Thank you for your order!";
537621 color: hifi.colors.baseGray;
538− size: 28;
622+ size: 36;
539623 }
540624
541625 RalewaySemiBold {
542626 id: completeText2;
543− text: "The item " + '<font color="' + hifi.colors.blueAccent + '"><a href="#">' + root.itemName + '</a></font>' +
544− " has been added to your Purchases and a receipt will appear in your Wallet's transaction history.";
627+ text: "The " + (root.itemTypesText)[itemTypesArray.indexOf(root.itemType)] +
628+ ' <font color="' + hifi.colors.blueAccent + '"><a href="#">' + root.itemName + '</a></font>' +
629+ " has been added to your Purchases and a receipt will appear in your Wallet's transaction history.";
545630 // Text size
546− size: 20;
631+ size: 18;
547632 // Anchors
548633 anchors.top: completeText.bottom;
549− anchors.topMargin: 10;
634+ anchors.topMargin: 15;
550635 height: paintedHeight;
551636 anchors.left: parent.left;
552637 anchors.right: parent.right;
@@ -576,7 +661,7 @@ Rectangle { · 23 unmodified lines
576661
577662 RalewayBold {
578663 anchors.fill: parent;
579− text: "REZZED";
664+ text: (root.buttonTextClicked)[itemTypesArray.indexOf(root.itemType)];
580665 size: 18;
581666 color: hifi.colors.white;
582667 verticalAlignment: Text.AlignVCenter;
@@ -592,26 +677,52 @@ Rectangle { · 9 unmodified lines
592677 // "Rez" button
593678 HifiControlsUit.Button {
594679 id: rezNowButton;
595− enabled: root.canRezCertifiedItems || root.isWearable;
596− buttonGlyph: hifi.glyphs.lightning;
680+ enabled: (root.itemType === "entity" && root.canRezCertifiedItems) ||
681+ (root.itemType === "contentSet" && Entities.canReplaceContent()) ||
682+ root.itemType === "wearable" || root.itemType === "avatar";
683+ buttonGlyph: (root.buttonGlyph)[itemTypesArray.indexOf(root.itemType)];
597684 color: hifi.buttons.red;
598685 colorScheme: hifi.colorSchemes.light;
599686 anchors.top: completeText2.bottom;
600− anchors.topMargin: 30;
687+ anchors.topMargin: 27;
601688 height: 50;
602689 anchors.left: parent.left;
603690 anchors.right: parent.right;
604− text: root.isWearable ? "Wear It" : "Rez It"
691+ text: (root.buttonTextNormal)[itemTypesArray.indexOf(root.itemType)];
605692 onClicked: {
606− sendToScript({method: 'checkout_rezClicked', itemHref: root.itemHref, isWearable: root.isWearable});
607− rezzedNotifContainer.visible = true;
608− rezzedNotifContainerTimer.start();
609− UserActivityLogger.commerceEntityRezzed(root.itemId, "checkout", root.isWearable ? "rez" : "wear");
693+ if (root.itemType === "contentSet") {
694+ lightboxPopup.titleText = "Replace Content";
695+ lightboxPopup.bodyText = "Rezzing this content set will replace the existing environment and all of the items in this domain. " +
696+ "If you want to save the state of the content in this domain, create a backup before proceeding.<br><br>" +
697+ "For more information about backing up and restoring content, " +
698+ "<a href='https://docs.highfidelity.com/create-and-explore/start-working-in-your-sandbox/restoring-sandbox-content'>" +
699+ "click here to open info on your desktop browser.";
700+ lightboxPopup.button1text = "CANCEL";
701+ lightboxPopup.button1method = "root.visible = false;"
702+ lightboxPopup.button2text = "CONFIRM";
703+ lightboxPopup.button2method = "Commerce.replaceContentSet('" + root.itemHref + "');" +
704+ "root.visible = false;rezzedNotifContainer.visible = true; rezzedNotifContainerTimer.start();" +
705+ "UserActivityLogger.commerceEntityRezzed('" + root.itemId + "', 'checkout', '" + root.itemType + "');";
706+ lightboxPopup.visible = true;
707+ } else if (root.itemType === "avatar") {
708+ lightboxPopup.titleText = "Change Avatar";
709+ lightboxPopup.bodyText = "This will change your current avatar to " + root.itemName + " while retaining your wearables.";
710+ lightboxPopup.button1text = "CANCEL";
711+ lightboxPopup.button1method = "root.visible = false;"
712+ lightboxPopup.button2text = "CONFIRM";
713+ lightboxPopup.button2method = "MyAvatar.useFullAvatarURL('" + root.itemHref + "'); root.visible = false;";
714+ lightboxPopup.visible = true;
715+ } else {
716+ sendToScript({method: 'checkout_rezClicked', itemHref: root.itemHref, itemType: root.itemType});
717+ rezzedNotifContainer.visible = true;
718+ rezzedNotifContainerTimer.start();
719+ UserActivityLogger.commerceEntityRezzed(root.itemId, "checkout", root.itemType);
720+ }
610721 }
611722 }
612723 RalewaySemiBold {
613724 id: noPermissionText;
614− visible: !root.canRezCertifiedItems && !root.isWearable;
725+ visible: !root.canRezCertifiedItems && root.itemType === "entity";
615726 text: '<font color="' + hifi.colors.redAccent + '"><a href="#">You do not have Certified Rez permissions in this domain.</a></font>'
616727 // Text size
617728 size: 16;
@@ -640,7 +751,7 @@ Rectangle { · 22 unmodified lines
640751 }
641752 RalewaySemiBold {
642753 id: explainRezText;
643− visible: !root.isWearable;
754+ visible: root.itemType === "entity";
644755 text: '<font color="' + hifi.colors.redAccent + '"><a href="#">What does "Rez" mean?</a></font>'
645756 // Text size
646757 size: 16;
@@ -663,9 +774,9 @@ Rectangle { · 16 unmodified lines
663774
664775 RalewaySemiBold {
665776 id: myPurchasesLink;
666− text: '<font color="' + hifi.colors.blueAccent + '"><a href="#">View this item in My Purchases</a></font>';
777+ text: '<font color="' + hifi.colors.primaryHighlight + '"><a href="#">View this item in My Purchases</a></font>';
667778 // Text size
668− size: 20;
779+ size: 18;
669780 // Anchors
670781 anchors.top: explainRezText.visible ? explainRezText.bottom : (noPermissionText.visible ? noPermissionText.bottom : rezNowButton.bottom);
671782 anchors.topMargin: 40;
@@ -685,12 +796,12 @@ Rectangle { · 13 unmodified lines
685796
686797 RalewaySemiBold {
687798 id: walletLink;
688− text: '<font color="' + hifi.colors.blueAccent + '"><a href="#">View receipt in Wallet</a></font>';
799+ text: '<font color="' + hifi.colors.primaryHighlight + '"><a href="#">View receipt in Wallet</a></font>';
689800 // Text size
690− size: 20;
801+ size: 18;
691802 // Anchors
692803 anchors.top: myPurchasesLink.bottom;
693− anchors.topMargin: 20;
804+ anchors.topMargin: 16;
694805 height: paintedHeight;
695806 anchors.left: parent.left;
696807 anchors.right: parent.right;
@@ -708,12 +819,12 @@ Rectangle { · 11 unmodified lines
708819 RalewayRegular {
709820 id: pendingText;
710821 text: 'Your item is marked "pending" while your purchase is being confirmed. ' +
711− '<font color="' + hifi.colors.blueAccent + '"><a href="#">Learn More</a></font>';
822+ '<b><font color="' + hifi.colors.primaryHighlight + '"><a href="#">Learn More</a></font></b>';
712823 // Text size
713− size: 20;
824+ size: 18;
714825 // Anchors
715826 anchors.top: walletLink.bottom;
716− anchors.topMargin: 60;
827+ anchors.topMargin: 32;
717828 height: paintedHeight;
718829 anchors.left: parent.left;
719830 anchors.right: parent.right;
@@ -739,11 +850,10 @@ Rectangle { · 19 unmodified lines
739850 color: hifi.buttons.noneBorderlessGray;
740851 colorScheme: hifi.colorSchemes.light;
741852 anchors.bottom: parent.bottom;
742− anchors.bottomMargin: 20;
853+ anchors.bottomMargin: 54;
743854 anchors.right: parent.right;
744− anchors.rightMargin: 14;
745− width: parent.width/2 - anchors.rightMargin;
746− height: 60;
855+ width: 193;
856+ height: 44;
747857 text: "Continue Shopping";
748858 onClicked: {
749859 sendToScript({method: 'checkout_continueShopping', itemId: itemId});
@@ -851,7 +961,7 @@ Rectangle { · 101 unmodified lines
851961 buyButton.color = hifi.buttons.red;
852962 root.shouldBuyWithControlledFailure = true;
853963 } else {
854− buyButton.text = (itemIsJson ? ((purchasesReceived && balanceReceived) ? (root.alreadyOwned ? "Buy Another" : "Buy"): "--") : "Get Item");
964+ buyButton.text = (root.isCertified ? ((ownershipStatusReceived && balanceReceived) ? (root.alreadyOwned ? "Buy Another" : "Buy"): "--") : "Get Item");
855965 buyButton.color = hifi.buttons.blue;
856966 root.shouldBuyWithControlledFailure = false;
857967 }
@@ -883,7 +993,7 @@ Rectangle { · 25 unmodified lines
883993 itemHref = message.params.itemHref;
884994 referrer = message.params.referrer;
885995 itemAuthor = message.params.itemAuthor;
886− setBuyText();
996+ refreshBuyUI();
887997 break;
888998 default:
889999 console.log('Unrecognized message from marketplaces.js:', JSON.stringify(message));
@@ -891,22 +1001,13 @@ Rectangle { · 1 unmodified line
8911001 }
8921002 signal sendToScript(var message);
8931003
894− function purchasesContains(purchasesJson, id) {
895− for (var idx = 0; idx < purchasesJson.length; idx++) {
896− if(purchasesJson[idx].id === id) {
897− return true;
898− }
899− }
900− return false;
901− }
902−
903− function setBuyText() {
904− if (root.itemIsJson) {
905− if (root.purchasesReceived && root.balanceReceived) {
1004+ function refreshBuyUI() {
1005+ if (root.isCertified) {
1006+ if (root.ownershipStatusReceived && root.balanceReceived) {
9061007 if (root.balanceAfterPurchase < 0) {
9071008 if (root.alreadyOwned) {
908− buyText.text = "<b>Your Wallet does not have sufficient funds to purchase this item again.<br>" +
909− '<font color="' + hifi.colors.blueAccent + '"><a href="#">View the copy you own in My Purchases</a></font></b>';
1009+ buyText.text = "<b>Your Wallet does not have sufficient funds to purchase this item again.</b>";
1010+ viewInMyPurchasesButton.visible = true;
9101011 } else {
9111012 buyText.text = "<b>Your Wallet does not have sufficient funds to purchase this item.</b>";
9121013 }
@@ -916,15 +1017,19 @@ Rectangle { · 3 unmodified lines
9161017 buyGlyph.size = 54;
9171018 } else {
9181019 if (root.alreadyOwned) {
919− buyText.text = '<b>You already own this item.<br>Purchasing it will buy another copy.<br><font color="'
920− + hifi.colors.blueAccent + '"><a href="#">View this item in My Purchases</a></font></b>';
921− buyTextContainer.color = "#FFD6AD";
922− buyTextContainer.border.color = "#FAC07D";
923− buyGlyph.text = hifi.glyphs.alert;
924− buyGlyph.size = 46;
1020+ viewInMyPurchasesButton.visible = true;
9251021 } else {
9261022 buyText.text = "";
9271023 }
1024+
1025+ if (root.itemType === "contentSet" && !Entities.canReplaceContent()) {
1026+ buyText.text = "The domain owner must enable 'Replace Content' permissions for you in this " +
1027+ "<b>domain's server settings</b> before you can replace this domain's content with <b>" + root.itemName + "</b>";
1028+ buyTextContainer.color = "#FFC3CD";
1029+ buyTextContainer.border.color = "#F3808F";
1030+ buyGlyph.text = hifi.glyphs.alert;
1031+ buyGlyph.size = 54;
1032+ }
9281033 }
9291034 } else {
9301035 buyText.text = "";
@@ -945,8 +1050,8 @@ Rectangle { · 14 unmodified lines
9451050 root.activeView = "checkoutSuccess";
9461051 }
9471052 root.balanceReceived = false;
948− root.purchasesReceived = false;
949− Commerce.inventory();
1053+ root.ownershipStatusReceived = false;
1054+ Commerce.alreadyOwned(root.itemId);
9501055 Commerce.balance();
9511056 }
9521057
interface/resources/qml/hifi/commerce/common/CommerceLightbox.qml+4−0
@@ -100,6 +100,10 @@ Rectangle { · 99 unmodified lines
100100 size: 20;
101101 verticalAlignment: Text.AlignTop;
102102 wrapMode: Text.WordWrap;
103+
104+ onLinkActivated: {
105+ sendToParent({ method: 'commerceLightboxLinkClicked', linkUrl: link });
106+ }
103107 }
104108
105109 Item {
interface/resources/qml/hifi/commerce/common/FirstUseTutorial.qml+2−2
@@ -90,11 +90,11 @@ Rectangle { · 89 unmodified lines
9090 id: introText2;
9191 text: "My Purchases";
9292 // Text size
93− size: 28;
93+ size: 22;
9494 // Anchors
9595 anchors.top: introText1.bottom;
9696 anchors.left: parent.left;
97− anchors.leftMargin: 12;
97+ anchors.leftMargin: 24;
9898 anchors.right: parent.right;
9999 height: paintedHeight;
100100 // Style
interface/resources/qml/hifi/commerce/inspectionCertificate/InspectionCertificate.qml+1−0
@@ -208,6 +208,7 @@ Rectangle { · 207 unmodified lines
208208 // able to click on a button/mouseArea underneath the popup/section.
209209 MouseArea {
210210 anchors.fill: parent;
211+ hoverEnabled: true;
211212 propagateComposedEvents: false;
212213 }
213214
interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml+201−41
@@ -20,6 +20,7 @@ import "../../../styles-uit" · 19 unmodified lines
2020 import "../../../controls-uit" as HifiControlsUit
2121 import "../../../controls" as HifiControls
2222 import "../wallet" as HifiWallet
23+import TabletScriptingInterface 1.0
2324
2425 // references XXX from root context
2526
@@ -29,7 +30,6 @@ Item { · 3 unmodified lines
2930 id: root;
3031 property string purchaseStatus;
3132 property bool purchaseStatusChanged;
32− property bool canRezCertifiedItems: false;
3333 property string itemName;
3434 property string itemId;
3535 property string itemPreviewImageUrl;
@@ -39,7 +39,14 @@ Item { · 3 unmodified lines
3939 property int itemEdition;
4040 property int numberSold;
4141 property int limitedRun;
42− property bool isWearable;
42+ property string itemType;
43+ property var itemTypesArray: ["entity", "wearable", "contentSet", "app", "avatar"];
44+ property var buttonTextNormal: ["REZ", "WEAR", "REPLACE", "INSTALL", "WEAR"];
45+ property var buttonTextClicked: ["REZZED", "WORN", "REPLACED", "INSTALLED", "WORN"]
46+ property var buttonGlyph: [hifi.glyphs.wand, hifi.glyphs.hat, hifi.glyphs.globe, hifi.glyphs.install, hifi.glyphs.avatar];
47+ property bool showConfirmation: false;
48+ property bool hasPermissionToRezThis;
49+ property bool permissionExplanationCardVisible;
4350
4451 property string originalStatusText;
4552 property string originalStatusColor;
@@ -47,6 +54,35 @@ Item { · 1 unmodified line
4754 height: 110;
4855 width: parent.width;
4956
57+ Connections {
58+ target: Commerce;
59+
60+ onContentSetChanged: {
61+ if (contentSetHref === root.itemHref) {
62+ showConfirmation = true;
63+ }
64+ }
65+ }
66+
67+ Connections {
68+ target: MyAvatar;
69+
70+ onSkeletonModelURLChanged: {
71+ if (skeletonModelURL === root.itemHref) {
72+ showConfirmation = true;
73+ }
74+ }
75+ }
76+
77+ onItemTypeChanged: {
78+ if ((itemType === "entity" && (!Entities.canRezCertified() && !Entities.canRezTmpCertified())) ||
79+ (itemType === "contentSet" && !Entities.canReplaceContent())) {
80+ root.hasPermissionToRezThis = false;
81+ } else {
82+ root.hasPermissionToRezThis = true;
83+ }
84+ }
85+
5086 onPurchaseStatusChangedChanged: {
5187 if (root.purchaseStatusChanged === true && root.purchaseStatus === "confirmed") {
5288 root.originalStatusText = statusText.text;
@@ -57,6 +93,15 @@ Item { · 4 unmodified lines
5793 }
5894 }
5995
96+ onShowConfirmationChanged: {
97+ if (root.showConfirmation) {
98+ rezzedNotifContainer.visible = true;
99+ rezzedNotifContainerTimer.start();
100+ UserActivityLogger.commerceEntityRezzed(root.itemId, "purchases", root.itemType);
101+ root.showConfirmation = false;
102+ }
103+ }
104+
60105 Timer {
61106 id: confirmedTimer;
62107 interval: 3000;
@@ -73,10 +118,10 @@ Item { · 10 unmodified lines
73118 color: hifi.colors.white;
74119 // Size
75120 anchors.left: parent.left;
76− anchors.leftMargin: 8;
121+ anchors.leftMargin: 16;
77122 anchors.right: parent.right;
78− anchors.rightMargin: 8;
79− anchors.top: parent.top;
123+ anchors.rightMargin: 16;
124+ anchors.verticalCenter: parent.verticalCenter;
80125 height: root.height - 10;
81126
82127 Image {
@@ -96,15 +141,20 @@ Item { · 13 unmodified lines
96141 }
97142 }
98143
99−
144+ TextMetrics {
145+ id: itemNameTextMetrics;
146+ font: itemName.font;
147+ text: itemName.text;
148+ }
100149 RalewaySemiBold {
101150 id: itemName;
102151 anchors.top: itemPreviewImage.top;
103152 anchors.topMargin: 4;
104153 anchors.left: itemPreviewImage.right;
105154 anchors.leftMargin: 8;
106− anchors.right: buttonContainer.left;
107− anchors.rightMargin: 8;
155+ width: !noPermissionGlyph.visible ? (buttonContainer.x - itemPreviewImage.x - itemPreviewImage.width - anchors.leftMargin) :
156+ Math.min(itemNameTextMetrics.tightBoundingRect.width + 2,
157+ buttonContainer.x - itemPreviewImage.x - itemPreviewImage.width - anchors.leftMargin - noPermissionGlyph.width + 2);
108158 height: paintedHeight;
109159 // Text size
110160 size: 24;
@@ -130,6 +180,93 @@ Item { · 19 unmodified lines
130180 }
131181 }
132182 }
183+ HiFiGlyphs {
184+ id: noPermissionGlyph;
185+ visible: !root.hasPermissionToRezThis;
186+ anchors.verticalCenter: itemName.verticalCenter;
187+ anchors.left: itemName.right;
188+ anchors.leftMargin: itemName.truncated ? -10 : -2;
189+ text: hifi.glyphs.info;
190+ // Size
191+ size: 40;
192+ width: 32;
193+ // Style
194+ color: hifi.colors.redAccent;
195+
196+ MouseArea {
197+ anchors.fill: parent;
198+ hoverEnabled: true;
199+
200+ onEntered: {
201+ noPermissionGlyph.color = hifi.colors.redHighlight;
202+ }
203+ onExited: {
204+ noPermissionGlyph.color = hifi.colors.redAccent;
205+ }
206+ onClicked: {
207+ root.sendToPurchases({ method: 'openPermissionExplanationCard' });
208+ }
209+ }
210+ }
211+ Rectangle {
212+ id: permissionExplanationCard;
213+ z: 995;
214+ visible: root.permissionExplanationCardVisible;
215+ anchors.fill: parent;
216+ color: hifi.colors.white;
217+
218+ RalewayRegular {
219+ id: permissionExplanationText;
220+ text: {
221+ if (root.itemType === "contentSet") {
222+ "You do not have 'Replace Content' permissions in this domain. <a href='#replaceContentPermission'>Learn more</a>";
223+ } else if (root.itemType === "entity") {
224+ "You do not have 'Rez Certified' permissions in this domain. <a href='#rezCertifiedPermission'>Learn more</a>";
225+ } else {
226+ ""
227+ }
228+ }
229+ size: 16;
230+ anchors.left: parent.left;
231+ anchors.leftMargin: 30;
232+ anchors.top: parent.top;
233+ anchors.bottom: parent.bottom;
234+ anchors.right: permissionExplanationGlyph.left;
235+ color: hifi.colors.baseGray;
236+ wrapMode: Text.WordWrap;
237+ verticalAlignment: Text.AlignVCenter;
238+
239+ onLinkActivated: {
240+ sendToPurchases({method: 'showPermissionsExplanation', itemType: root.itemType});
241+ }
242+ }
243+ // "Close" button
244+ HiFiGlyphs {
245+ id: permissionExplanationGlyph;
246+ text: hifi.glyphs.close;
247+ color: hifi.colors.baseGray;
248+ size: 26;
249+ anchors.top: parent.top;
250+ anchors.bottom: parent.bottom;
251+ anchors.right: parent.right;
252+ width: 77;
253+ horizontalAlignment: Text.AlignHCenter;
254+ verticalAlignment: Text.AlignVCenter;
255+ MouseArea {
256+ anchors.fill: parent;
257+ hoverEnabled: true;
258+ onEntered: {
259+ parent.text = hifi.glyphs.closeInverted;
260+ }
261+ onExited: {
262+ parent.text = hifi.glyphs.close;
263+ }
264+ onClicked: {
265+ root.sendToPurchases({ method: 'openPermissionExplanationCard', closeAll: true });
266+ }
267+ }
268+ }
269+ }
133270
134271 Item {
135272 id: certificateContainer;
@@ -151,19 +288,19 @@ Item { · 15 unmodified lines
151288 anchors.bottom: parent.bottom;
152289 width: 32;
153290 // Style
154− color: hifi.colors.lightGray;
291+ color: hifi.colors.black;
155292 }
156293
157294 RalewayRegular {
158295 id: viewCertificateText;
159296 text: "VIEW CERTIFICATE";
160− size: 14;
297+ size: 13;
161298 anchors.left: certificateIcon.right;
162299 anchors.leftMargin: 4;
163300 anchors.top: parent.top;
164301 anchors.bottom: parent.bottom;
165302 anchors.right: parent.right;
166− color: hifi.colors.lightGray;
303+ color: hifi.colors.black;
167304 }
168305
169306 MouseArea {
@@ -173,13 +310,13 @@ Item { · 3 unmodified lines
173310 sendToPurchases({method: 'purchases_itemCertificateClicked', itemCertificateId: root.certificateId});
174311 }
175312 onEntered: {
176− certificateIcon.color = hifi.colors.black;
177− viewCertificateText.color = hifi.colors.black;
178− }
179− onExited: {
180313 certificateIcon.color = hifi.colors.lightGray;
181314 viewCertificateText.color = hifi.colors.lightGray;
182315 }
316+ onExited: {
317+ certificateIcon.color = hifi.colors.black;
318+ viewCertificateText.color = hifi.colors.black;
319+ }
183320 }
184321 }
185322
@@ -193,14 +330,14 @@ Item { · 7 unmodified lines
193330 anchors.right: buttonContainer.left;
194331 anchors.rightMargin: 2;
195332
196− FiraSansRegular {
333+ RalewayRegular {
197334 anchors.left: parent.left;
198335 anchors.top: parent.top;
199336 anchors.bottom: parent.bottom;
200337 width: paintedWidth;
201338 text: "#" + root.itemEdition;
202− size: 15;
203− color: "#cc6a6a6a";
339+ size: 13;
340+ color: hifi.colors.black;
204341 verticalAlignment: Text.AlignTop;
205342 }
206343 }
@@ -311,7 +448,7 @@ Item { · 104 unmodified lines
311448 id: rezzedNotifContainer;
312449 z: 998;
313450 visible: false;
314− color: hifi.colors.blueHighlight;
451+ color: "#1FC6A6";
315452 anchors.fill: buttonContainer;
316453 MouseArea {
317454 anchors.fill: parent;
@@ -321,8 +458,8 @@ Item { · 3 unmodified lines
321458
322459 RalewayBold {
323460 anchors.fill: parent;
324− text: "REZZED";
325− size: 18;
461+ text: (root.buttonTextClicked)[itemTypesArray.indexOf(root.itemType)];
462+ size: 15;
326463 color: hifi.colors.white;
327464 verticalAlignment: Text.AlignVCenter;
328465 horizontalAlignment: Text.AlignHCenter;
@@ -337,25 +474,47 @@ Item { · 8 unmodified lines
337474
338475 Button {
339476 id: buttonContainer;
340− property int color: hifi.buttons.red;
477+ property int color: hifi.buttons.blue;
341478 property int colorScheme: hifi.colorSchemes.light;
342479
343480 anchors.top: parent.top;
481+ anchors.topMargin: 4;
344482 anchors.bottom: parent.bottom;
483+ anchors.bottomMargin: 4;
345484 anchors.right: parent.right;
485+ anchors.rightMargin: 4;
346486 width: height;
347− enabled: (root.canRezCertifiedItems || root.isWearable) && root.purchaseStatus !== "invalidated";
487+ enabled: root.hasPermissionToRezThis &&
488+ root.purchaseStatus !== "invalidated" &&
489+ MyAvatar.skeletonModelURL !== root.itemHref;
490+
491+ onHoveredChanged: {
492+ if (hovered) {
493+ Tablet.playSound(TabletEnums.ButtonHover);
494+ }
495+ }
496+
497+ onFocusChanged: {
498+ if (focus) {
499+ Tablet.playSound(TabletEnums.ButtonHover);
500+ }
501+ }
348502
349503 onClicked: {
350− sendToPurchases({method: 'purchases_rezClicked', itemHref: root.itemHref, isWearable: root.isWearable});
351− rezzedNotifContainer.visible = true;
352− rezzedNotifContainerTimer.start();
353− UserActivityLogger.commerceEntityRezzed(root.itemId, "purchases", root.isWearable ? "rez" : "wear");
504+ Tablet.playSound(TabletEnums.ButtonClick);
505+ if (root.itemType === "contentSet") {
506+ sendToPurchases({method: 'showReplaceContentLightbox', itemHref: root.itemHref});
507+ } else if (root.itemType === "avatar") {
508+ sendToPurchases({method: 'showChangeAvatarLightbox', itemName: root.itemName, itemHref: root.itemHref});
509+ } else {
510+ sendToPurchases({method: 'purchases_rezClicked', itemHref: root.itemHref, itemType: root.itemType});
511+ root.showConfirmation = true;
512+ }
354513 }
355514
356515 style: ButtonStyle {
357−
358516 background: Rectangle {
517+ radius: 4;
359518 gradient: Gradient {
360519 GradientStop {
361520 position: 0.2
@@ -390,13 +549,13 @@ Item { · 28 unmodified lines
390549
391550 label: Item {
392551 HiFiGlyphs {
393− id: lightningIcon;
394− text: hifi.glyphs.lightning;
552+ id: rezIcon;
553+ text: (root.buttonGlyph)[itemTypesArray.indexOf(root.itemType)];
395554 // Size
396− size: 32;
555+ size: 60;
397556 // Anchors
398557 anchors.top: parent.top;
399− anchors.topMargin: 12;
558+ anchors.topMargin: 0;
400559 anchors.left: parent.left;
401560 anchors.right: parent.right;
402561 horizontalAlignment: Text.AlignHCenter;
@@ -405,18 +564,19 @@ Item { · 2 unmodified lines
405564 : hifi.buttons.disabledTextColor[control.colorScheme]
406565 }
407566 RalewayBold {
408− anchors.top: lightningIcon.bottom;
409− anchors.topMargin: -20;
567+ id: rezIconLabel;
568+ anchors.top: rezIcon.bottom;
569+ anchors.topMargin: -4;
410570 anchors.right: parent.right;
411571 anchors.left: parent.left;
412572 anchors.bottom: parent.bottom;
413573 font.capitalization: Font.AllUppercase
414574 color: enabled ? hifi.buttons.textColor[control.color]
415575 : hifi.buttons.disabledTextColor[control.colorScheme]
416− size: 16;
576+ size: 15;
417577 verticalAlignment: Text.AlignVCenter
418578 horizontalAlignment: Text.AlignHCenter
419− text: root.isWearable ? "Wear It" : "Rez It"
579+ text: MyAvatar.skeletonModelURL === root.itemHref ? "CURRENT" : (root.buttonTextNormal)[itemTypesArray.indexOf(root.itemType)];
420580 }
421581 }
422582 }
@@ -425,11 +585,11 @@ Item { · 2 unmodified lines
425585
426586 DropShadow {
427587 anchors.fill: mainContainer;
428− horizontalOffset: 3;
429− verticalOffset: 3;
430− radius: 8.0;
431− samples: 17;
432− color: "#80000000";
588+ horizontalOffset: 0;
589+ verticalOffset: 4;
590+ radius: 4.0;
591+ samples: 9
592+ color: Qt.rgba(0, 0, 0, 0.25);
433593 source: mainContainer;
434594 }
435595
interface/resources/qml/hifi/commerce/purchases/Purchases.qml+76−78
@@ -32,7 +32,6 @@ Rectangle { · 31 unmodified lines
3232 property bool securityImageResultReceived: false;
3333 property bool purchasesReceived: false;
3434 property bool punctuationMode: false;
35− property bool canRezCertifiedItems: Entities.canRezCertified() || Entities.canRezTmpCertified();
3635 property bool pendingInventoryReply: true;
3736 property bool isShowingMyItems: false;
3837 property bool isDebuggingFirstUseTutorial: false;
@@ -148,7 +147,11 @@ Rectangle { · 109 unmodified lines
148147
149148 Connections {
150149 onSendToParent: {
151− sendToScript(msg);
150+ if (msg.method === 'commerceLightboxLinkClicked') {
151+ Qt.openUrlExternally(msg.linkUrl);
152+ } else {
153+ sendToScript(msg);
154+ }
152155 }
153156 }
154157 }
@@ -297,7 +300,7 @@ Rectangle { · 142 unmodified lines
297300 anchors.left: parent.left;
298301 anchors.leftMargin: 8;
299302 anchors.right: parent.right;
300− anchors.rightMargin: 12;
303+ anchors.rightMargin: 16;
301304 anchors.top: parent.top;
302305 anchors.topMargin: 4;
303306
@@ -308,11 +311,11 @@ Rectangle { · 4 unmodified lines
308311 anchors.bottom: parent.bottom;
309312 anchors.bottomMargin: 10;
310313 anchors.left: parent.left;
311− anchors.leftMargin: 4;
314+ anchors.leftMargin: 16;
312315 width: paintedWidth;
313316 text: isShowingMyItems ? "My Items" : "My Purchases";
314− color: hifi.colors.baseGray;
315− size: 28;
317+ color: hifi.colors.black;
318+ size: 22;
316319 }
317320
318321 HifiControlsUit.TextField {
@@ -323,8 +326,8 @@ Rectangle { · 4 unmodified lines
323326 hasRoundedBorder: true;
324327 anchors.left: myText.right;
325328 anchors.leftMargin: 16;
326− anchors.top: parent.top;
327− anchors.bottom: parent.bottom;
329+ height: 39;
330+ anchors.verticalCenter: parent.verticalCenter;
328331 anchors.right: parent.right;
329332 placeholderText: "filter items";
330333
@@ -345,7 +348,7 @@ Rectangle { · 14 unmodified lines
345348
346349 HifiControlsUit.Separator {
347350 id: separator;
348− colorScheme: 1;
351+ colorScheme: 2;
349352 anchors.left: parent.left;
350353 anchors.right: parent.right;
351354 anchors.top: filterBarContainer.bottom;
@@ -365,69 +368,6 @@ Rectangle { · 13 unmodified lines
365368 id: filteredPurchasesModel;
366369 }
367370
368− Rectangle {
369− id: cantRezCertified;
370− visible: !root.canRezCertifiedItems;
371− color: "#FFC3CD";
372− radius: 4;
373− border.color: hifi.colors.redAccent;
374− border.width: 1;
375− anchors.top: separator.bottom;
376− anchors.topMargin: 12;
377− anchors.left: parent.left;
378− anchors.leftMargin: 16;
379− anchors.right: parent.right;
380− anchors.rightMargin: 16;
381− height: 80;
382−
383− HiFiGlyphs {
384− id: lightningIcon;
385− text: hifi.glyphs.lightning;
386− // Size
387− size: 36;
388− // Anchors
389− anchors.top: parent.top;
390− anchors.topMargin: 18;
391− anchors.left: parent.left;
392− anchors.leftMargin: 12;
393− horizontalAlignment: Text.AlignHCenter;
394− // Style
395− color: hifi.colors.lightGray;
396− }
397−
398− RalewayRegular {
399− text: "You don't have permission to rez certified items in this domain. " +
400− '<b><font color="' + hifi.colors.blueAccent + '"><a href="#">Learn More</a></font></b>';
401− // Text size
402− size: 18;
403− // Anchors
404− anchors.top: parent.top;
405− anchors.topMargin: 4;
406− anchors.left: lightningIcon.right;
407− anchors.leftMargin: 8;
408− anchors.right: parent.right;
409− anchors.rightMargin: 8;
410− anchors.bottom: parent.bottom;
411− anchors.bottomMargin: 4;
412− // Style
413− color: hifi.colors.baseGray;
414− wrapMode: Text.WordWrap;
415− // Alignment
416− verticalAlignment: Text.AlignVCenter;
417−
418− onLinkActivated: {
419− lightboxPopup.titleText = "Rez Permission Required";
420− lightboxPopup.bodyText = "You don't have permission to rez certified items in this domain.<br><br>" +
421− "Use the <b>GOTO app</b> to visit another domain or <b>go to your own sandbox.</b>";
422− lightboxPopup.button1text = "CLOSE";
423− lightboxPopup.button1method = "root.visible = false;"
424− lightboxPopup.button2text = "OPEN GOTO";
425− lightboxPopup.button2method = "sendToParent({method: 'purchases_openGoTo'});";
426− lightboxPopup.visible = true;
427− }
428− }
429− }
430−
431371 ListView {
432372 id: purchasesContentsList;
433373 visible: (root.isShowingMyItems && filteredPurchasesModel.count !== 0) || (!root.isShowingMyItems && filteredPurchasesModel.count !== 0);
@@ -436,13 +376,12 @@ Rectangle { · 2 unmodified lines
436376 snapMode: ListView.SnapToItem;
437377 highlightRangeMode: ListView.StrictlyEnforceRange;
438378 // Anchors
439− anchors.top: root.canRezCertifiedItems ? separator.bottom : cantRezCertified.bottom;
379+ anchors.top: separator.bottom;
440380 anchors.topMargin: 12;
441381 anchors.left: parent.left;
442382 anchors.bottom: parent.bottom;
443383 width: parent.width;
444384 delegate: PurchasedItem {
445− canRezCertifiedItems: root.canRezCertifiedItems;
446385 itemName: title;
447386 itemId: id;
448387 itemPreviewImageUrl: preview;
@@ -454,16 +393,31 @@ Rectangle { · 5 unmodified lines
454393 numberSold: model.number_sold;
455394 limitedRun: model.limited_run;
456395 displayedItemCount: model.displayedItemCount;
457− isWearable: model.categories.indexOf("Wearables") > -1;
458− anchors.topMargin: 12;
459− anchors.bottomMargin: 12;
396+ permissionExplanationCardVisible: model.permissionExplanationCardVisible;
397+ itemType: {
398+ if (model.root_file_url.indexOf(".fst") > -1) {
399+ "avatar";
400+ } else if (model.categories.indexOf("Wearables") > -1) {
401+ "wearable";
402+ } else if (model.root_file_url.endsWith('.json.gz')) {
403+ "contentSet";
404+ } else if (model.root_file_url.endsWith('.app.json')) {
405+ "app";
406+ } else if (model.root_file_url.endsWith('.json')) {
407+ "entity";
408+ } else {
409+ "unknown";
410+ }
411+ }
412+ anchors.topMargin: 10;
413+ anchors.bottomMargin: 10;
460414
461415 Connections {
462416 onSendToPurchases: {
463417 if (msg.method === 'purchases_itemInfoClicked') {
464418 sendToScript({method: 'purchases_itemInfoClicked', itemId: itemId});
465419 } else if (msg.method === "purchases_rezClicked") {
466− sendToScript({method: 'purchases_rezClicked', itemHref: itemHref, isWearable: isWearable});
420+ sendToScript({method: 'purchases_rezClicked', itemHref: itemHref, itemType: itemType});
467421 } else if (msg.method === 'purchases_itemCertificateClicked') {
468422 inspectionCertificate.visible = true;
469423 inspectionCertificate.isLightbox = true;
@@ -482,8 +436,51 @@ Rectangle { · 12 unmodified lines
482436 lightboxPopup.button1text = "CLOSE";
483437 lightboxPopup.button1method = "root.visible = false;"
484438 lightboxPopup.visible = true;
439+ } else if (msg.method === "showReplaceContentLightbox") {
440+ lightboxPopup.titleText = "Replace Content";
441+ lightboxPopup.bodyText = "Rezzing this content set will replace the existing environment and all of the items in this domain. " +
442+ "If you want to save the state of the content in this domain, create a backup before proceeding.<br><br>" +
443+ "For more information about backing up and restoring content, " +
444+ "<a href='https://docs.highfidelity.com/create-and-explore/start-working-in-your-sandbox/restoring-sandbox-content'>" +
445+ "click here to open info on your desktop browser.";
446+ lightboxPopup.button1text = "CANCEL";
447+ lightboxPopup.button1method = "root.visible = false;"
448+ lightboxPopup.button2text = "CONFIRM";
449+ lightboxPopup.button2method = "Commerce.replaceContentSet('" + msg.itemHref + "'); root.visible = false;";
450+ lightboxPopup.visible = true;
451+ } else if (msg.method === "showChangeAvatarLightbox") {
452+ lightboxPopup.titleText = "Change Avatar";
453+ lightboxPopup.bodyText = "This will change your current avatar to " + msg.itemName + " while retaining your wearables.";
454+ lightboxPopup.button1text = "CANCEL";
455+ lightboxPopup.button1method = "root.visible = false;"
456+ lightboxPopup.button2text = "CONFIRM";
457+ lightboxPopup.button2method = "MyAvatar.useFullAvatarURL('" + msg.itemHref + "'); root.visible = false;";
458+ lightboxPopup.visible = true;
459+ } else if (msg.method === "showPermissionsExplanation") {
460+ if (msg.itemType === "entity") {
461+ lightboxPopup.titleText = "Rez Certified Permission";
462+ lightboxPopup.bodyText = "You don't have permission to rez certified items in this domain.<br><br>" +
463+ "Use the <b>GOTO app</b> to visit another domain or <b>go to your own sandbox.</b>";
464+ lightboxPopup.button2text = "OPEN GOTO";
465+ lightboxPopup.button2method = "sendToParent({method: 'purchases_openGoTo'});";
466+ } else if (msg.itemType === "contentSet") {
467+ lightboxPopup.titleText = "Replace Content Permission";
468+ lightboxPopup.bodyText = "You do not have the permission 'Replace Content' in this <b>domain's server settings</b>. The domain owner " +
469+ "must enable it for you before you can replace content sets in this domain.";
470+ }
471+ lightboxPopup.button1text = "CLOSE";
472+ lightboxPopup.button1method = "root.visible = false;"
473+ lightboxPopup.visible = true;
485474 } else if (msg.method === "setFilterText") {
486475 filterBar.text = msg.filterText;
476+ } else if (msg.method === "openPermissionExplanationCard") {
477+ for (var i = 0; i < filteredPurchasesModel.count; i++) {
478+ if (i !== index || msg.closeAll) {
479+ filteredPurchasesModel.setProperty(i, "permissionExplanationCardVisible", false);
480+ } else {
481+ filteredPurchasesModel.setProperty(i, "permissionExplanationCardVisible", true);
482+ }
483+ }
487484 }
488485 }
489486 }
@@ -685,6 +682,7 @@ Rectangle { · 195 unmodified lines
685682 filteredPurchasesModel.clear();
686683 for (var i = 0; i < tempPurchasesModel.count; i++) {
687684 filteredPurchasesModel.append(tempPurchasesModel.get(i));
685+ filteredPurchasesModel.setProperty(i, 'permissionExplanationCardVisible', false);
688686 }
689687
690688 populateDisplayedItemCounts();
interface/resources/qml/styles-uit/HifiConstants.qml+5−1
@@ -218,7 +218,7 @@ Item { · 217 unmodified lines
218218 readonly property var colorStart: [ colors.white, colors.primaryHighlight, "#d42043", "#343434", Qt.rgba(0, 0, 0, 0), Qt.rgba(0, 0, 0, 0), Qt.rgba(0, 0, 0, 0), Qt.rgba(0, 0, 0, 0) ]
219219 readonly property var colorFinish: [ colors.lightGrayText, colors.blueAccent, "#94132e", colors.black, Qt.rgba(0, 0, 0, 0), Qt.rgba(0, 0, 0, 0), Qt.rgba(0, 0, 0, 0), Qt.rgba(0, 0, 0, 0) ]
220220 readonly property var hoveredColor: [ colorStart[white], colorStart[blue], colorStart[red], colorFinish[black], colorStart[none], colorStart[noneBorderless], colorStart[noneBorderlessWhite], colorStart[noneBorderlessGray] ]
221− readonly property var pressedColor: [ colorFinish[white], colorFinish[blue], colorFinish[red], colorStart[black], colorStart[none], colorStart[noneBorderless], colorStart[noneBorderlessWhite], colorStart[noneBorderlessGray] ]
221+ readonly property var pressedColor: [ colorFinish[white], colorFinish[blue], colorFinish[red], colorStart[black], colorStart[none], colorStart[noneBorderless], colorStart[noneBorderlessWhite], colors.lightGrayText ]
222222 readonly property var focusedColor: [ colors.lightGray50, colors.blueAccent, colors.redAccent, colors.darkGray, colorStart[none], colorStart[noneBorderless], colorStart[noneBorderlessWhite], colorStart[noneBorderlessGray] ]
223223 readonly property var disabledColorStart: [ colorStart[white], colors.baseGrayHighlight]
224224 readonly property var disabledColorFinish: [ colorFinish[white], colors.baseGrayShadow]
@@ -354,5 +354,9 @@ Item { · 129 unmodified lines
354354 readonly property string wallet: "\ue027"
355355 readonly property string paperPlane: "\ue028"
356356 readonly property string passphrase: "\ue029"
357+ readonly property string globe: "\ue02c"
358+ readonly property string wand: "\ue02d"
359+ readonly property string hat: "\ue02e"
360+ readonly property string install: "\ue02f"
357361 }
358362 }
interface/src/Application.cpp+19−15
@@ -6370,6 +6370,24 @@ bool Application::askToWearAvatarAttachmentUrl(const QString& url) { · 6369 unmodified lines
63706370 return true;
63716371 }
63726372
6373+void Application::replaceDomainContent(const QString& url) {
6374+ qCDebug(interfaceapp) << "Attempting to replace domain content: " << url;
6375+ QByteArray urlData(url.toUtf8());
6376+ auto limitedNodeList = DependencyManager::get<LimitedNodeList>();
6377+ limitedNodeList->eachMatchingNode([](const SharedNodePointer& node) {
6378+ return node->getType() == NodeType::EntityServer && node->getActiveSocket();
6379+ }, [&urlData, limitedNodeList](const SharedNodePointer& octreeNode) {
6380+ auto octreeFilePacket = NLPacket::create(PacketType::OctreeFileReplacementFromUrl, urlData.size(), true);
6381+ octreeFilePacket->write(urlData);
6382+ limitedNodeList->sendPacket(std::move(octreeFilePacket), *octreeNode);
6383+ });
6384+ auto addressManager = DependencyManager::get<AddressManager>();
6385+ addressManager->handleLookupString(DOMAIN_SPAWNING_POINT);
6386+ QString newHomeAddress = addressManager->getHost() + DOMAIN_SPAWNING_POINT;
6387+ qCDebug(interfaceapp) << "Setting new home bookmark to: " << newHomeAddress;
6388+ DependencyManager::get<LocationBookmarks>()->setHomeLocationToAddress(newHomeAddress);
6389+}
6390+
63736391 bool Application::askToReplaceDomainContent(const QString& url) {
63746392 QString methodDetails;
63756393 const int MAX_CHARACTERS_PER_LINE = 90;
@@ -6389,21 +6407,7 @@ bool Application::askToReplaceDomainContent(const QString& url) { · 13 unmodified lines
63896407 QString details;
63906408 if (static_cast<QMessageBox::StandardButton>(answer.toInt()) == QMessageBox::Yes) {
63916409 // Given confirmation, send request to domain server to replace content
6392− qCDebug(interfaceapp) << "Attempting to replace domain content: " << url;
6393− QByteArray urlData(url.toUtf8());
6394− auto limitedNodeList = DependencyManager::get<LimitedNodeList>();
6395− limitedNodeList->eachMatchingNode([](const SharedNodePointer& node) {
6396− return node->getType() == NodeType::EntityServer && node->getActiveSocket();
6397− }, [&urlData, limitedNodeList](const SharedNodePointer& octreeNode) {
6398− auto octreeFilePacket = NLPacket::create(PacketType::OctreeFileReplacementFromUrl, urlData.size(), true);
6399− octreeFilePacket->write(urlData);
6400− limitedNodeList->sendPacket(std::move(octreeFilePacket), *octreeNode);
6401− });
6402− auto addressManager = DependencyManager::get<AddressManager>();
6403− addressManager->handleLookupString(DOMAIN_SPAWNING_POINT);
6404− QString newHomeAddress = addressManager->getHost() + DOMAIN_SPAWNING_POINT;
6405− qCDebug(interfaceapp) << "Setting new home bookmark to: " << newHomeAddress;
6406− DependencyManager::get<LocationBookmarks>()->setHomeLocationToAddress(newHomeAddress);
6410+ replaceDomainContent(url);
64076411 details = "SuccessfulRequestToReplaceContent";
64086412 } else {
64096413 details = "UserDeclinedToReplaceContent";
interface/src/Application.h+2−0
@@ -288,6 +288,8 @@ public: · 287 unmodified lines
288288
289289 bool isServerlessMode() const { return !_serversEnabled; }
290290
291+ void replaceDomainContent(const QString& url);
292+
291293 signals:
292294 void svoImportRequested(const QString& url);
293295
interface/src/avatar/MyAvatar.cpp+1−1
@@ -1479,7 +1479,7 @@ void MyAvatar::setSkeletonModelURL(const QUrl& skeletonModelURL) { · 1478 unmodified lines
14791479 });
14801480 saveAvatarUrl();
14811481 emit skeletonChanged();
1482−
1482+ emit skeletonModelURLChanged();
14831483 }
14841484
14851485 void MyAvatar::removeAvatarEntities() {
interface/src/commerce/Ledger.cpp+10−0
@@ -49,6 +49,7 @@ Handler(balance) · 48 unmodified lines
4949 Handler(inventory)
5050 Handler(transferHfcToNode)
5151 Handler(transferHfcToUsername)
52+Handler(alreadyOwned)
5253
5354 void Ledger::send(const QString& endpoint, const QString& success, const QString& fail, QNetworkAccessManager::Operation method, AccountManagerAuth::Type authType, QJsonObject request) {
5455 auto accountManager = DependencyManager::get<AccountManager>();
@@ -336,3 +337,12 @@ void Ledger::transferHfcToUsername(const QString& hfc_key, const QString& userna · 281 unmodified lines
336337 auto transactionString = transactionDoc.toJson(QJsonDocument::Compact);
337338 signedSend("transaction", transactionString, hfc_key, "transfer_hfc_to_user", "transferHfcToUsernameSuccess", "transferHfcToUsernameFailure");
338339 }
340+
341+void Ledger::alreadyOwned(const QString& marketplaceId) {
342+ auto wallet = DependencyManager::get<Wallet>();
343+ QString endpoint = "already_owned";
344+ QJsonObject request;
345+ request["public_keys"] = QJsonArray::fromStringList(wallet->listPublicKeys());
346+ request["marketplace_item_id"] = marketplaceId;
347+ send(endpoint, "alreadyOwnedSuccess", "alreadyOwnedFailure", QNetworkAccessManager::PutOperation, AccountManagerAuth::Required, request);
348+}
interface/src/commerce/Ledger.h+4−0
@@ -35,6 +35,7 @@ public: · 34 unmodified lines
3535 void certificateInfo(const QString& certificateId);
3636 void transferHfcToNode(const QString& hfc_key, const QString& nodeID, const int& amount, const QString& optionalMessage);
3737 void transferHfcToUsername(const QString& hfc_key, const QString& username, const int& amount, const QString& optionalMessage);
38+ void alreadyOwned(const QString& marketplaceId);
3839
3940 enum CertificateStatus {
4041 CERTIFICATE_STATUS_UNKNOWN = 0,
@@ -55,6 +56,7 @@ signals: · 14 unmodified lines
5556 void certificateInfoResult(QJsonObject result);
5657 void transferHfcToNodeResult(QJsonObject result);
5758 void transferHfcToUsernameResult(QJsonObject result);
59+ void alreadyOwnedResult(QJsonObject result);
5860
5961 void updateCertificateStatus(const QString& certID, uint certStatus);
6062
@@ -79,6 +81,8 @@ public slots: · 18 unmodified lines
7981 void transferHfcToNodeFailure(QNetworkReply& reply);
8082 void transferHfcToUsernameSuccess(QNetworkReply& reply);
8183 void transferHfcToUsernameFailure(QNetworkReply& reply);
84+ void alreadyOwnedSuccess(QNetworkReply& reply);
85+ void alreadyOwnedFailure(QNetworkReply& reply);
8286
8387 private:
8488 QJsonObject apiResponse(const QString& label, QNetworkReply& reply);
interface/src/commerce/QmlCommerce.cpp+20−0
@@ -15,6 +15,8 @@ · 14 unmodified lines
1515 #include "Ledger.h"
1616 #include "Wallet.h"
1717 #include <AccountManager.h>
18+#include <Application.h>
19+#include <UserActivityLogger.h>
1820
1921 QmlCommerce::QmlCommerce() {
2022 auto ledger = DependencyManager::get<Ledger>();
@@ -28,9 +30,11 @@ QmlCommerce::QmlCommerce() { · 7 unmodified lines
2830 connect(ledger.data(), &Ledger::accountResult, this, &QmlCommerce::accountResult);
2931 connect(wallet.data(), &Wallet::walletStatusResult, this, &QmlCommerce::walletStatusResult);
3032 connect(ledger.data(), &Ledger::certificateInfoResult, this, &QmlCommerce::certificateInfoResult);
33+ connect(ledger.data(), &Ledger::alreadyOwnedResult, this, &QmlCommerce::alreadyOwnedResult);
3134 connect(ledger.data(), &Ledger::updateCertificateStatus, this, &QmlCommerce::updateCertificateStatus);
3235 connect(ledger.data(), &Ledger::transferHfcToNodeResult, this, &QmlCommerce::transferHfcToNodeResult);
3336 connect(ledger.data(), &Ledger::transferHfcToUsernameResult, this, &QmlCommerce::transferHfcToUsernameResult);
37+ connect(ledger.data(), &Ledger::transferHfcToUsernameResult, this, &QmlCommerce::transferHfcToUsernameResult);
3438
3539 auto accountManager = DependencyManager::get<AccountManager>();
3640 connect(accountManager.data(), &AccountManager::usernameChanged, this, [&]() {
@@ -163,3 +167,19 @@ void QmlCommerce::transferHfcToUsername(const QString& username, const int& amou · 126 unmodified lines
163167 QString key = keys[0];
164168 ledger->transferHfcToUsername(key, username, amount, optionalMessage);
165169 }
170+
171+void QmlCommerce::replaceContentSet(const QString& itemHref) {
172+ qApp->replaceDomainContent(itemHref);
173+ QJsonObject messageProperties = {
174+ { "status", "SuccessfulRequestToReplaceContent" },
175+ { "content_set_url", itemHref }
176+ };
177+ UserActivityLogger::getInstance().logAction("replace_domain_content", messageProperties);
178+
179+ emit contentSetChanged(itemHref);
180+}
181+
182+void QmlCommerce::alreadyOwned(const QString& marketplaceId) {
183+ auto ledger = DependencyManager::get<Ledger>();
184+ ledger->alreadyOwned(marketplaceId);
185+}
interface/src/commerce/QmlCommerce.h+7−0
@@ -42,12 +42,15 @@ signals: · 41 unmodified lines
4242 void historyResult(QJsonObject result);
4343 void accountResult(QJsonObject result);
4444 void certificateInfoResult(QJsonObject result);
45+ void alreadyOwnedResult(QJsonObject result);
4546
4647 void updateCertificateStatus(const QString& certID, uint certStatus);
4748
4849 void transferHfcToNodeResult(QJsonObject result);
4950 void transferHfcToUsernameResult(QJsonObject result);
5051
52+ void contentSetChanged(const QString& contentSetHref);
53+
5154 protected:
5255 Q_INVOKABLE void getWalletStatus();
5356
@@ -68,9 +71,13 @@ protected: · 14 unmodified lines
6871 Q_INVOKABLE void account();
6972
7073 Q_INVOKABLE void certificateInfo(const QString& certificateId);
74+ Q_INVOKABLE void alreadyOwned(const QString& marketplaceId);
7175
7276 Q_INVOKABLE void transferHfcToNode(const QString& nodeID, const int& amount, const QString& optionalMessage);
7377 Q_INVOKABLE void transferHfcToUsername(const QString& username, const int& amount, const QString& optionalMessage);
78+
79+
80+ Q_INVOKABLE void replaceContentSet(const QString& itemHref);
7481 };
7582
7683 #endif // hifi_QmlCommerce_h
interface/src/scripting/AccountServicesScriptingInterface.h+1−1
@@ -38,7 +38,7 @@ class AccountServicesScriptingInterface : public QObject { · 37 unmodified lines
3838 Q_PROPERTY(QString username READ getUsername NOTIFY myUsernameChanged)
3939 Q_PROPERTY(bool loggedIn READ loggedIn NOTIFY loggedInChanged)
4040 Q_PROPERTY(QString findableBy READ getFindableBy WRITE setFindableBy NOTIFY findableByChanged)
41− Q_PROPERTY(QUrl metaverseServerURL READ getMetaverseServerURL)
41+ Q_PROPERTY(QUrl metaverseServerURL READ getMetaverseServerURL CONSTANT)
4242
4343 public:
4444 static AccountServicesScriptingInterface* getInstance();
libraries/avatars/src/AvatarData.h+2−1
@@ -373,7 +373,7 @@ class AvatarData : public QObject, public SpatiallyNestable { · 372 unmodified lines
373373 // The result is unique among all avatars present at the time.
374374 Q_PROPERTY(QString sessionDisplayName READ getSessionDisplayName WRITE setSessionDisplayName NOTIFY sessionDisplayNameChanged)
375375 Q_PROPERTY(bool lookAtSnappingEnabled MEMBER _lookAtSnappingEnabled NOTIFY lookAtSnappingChanged)
376− Q_PROPERTY(QString skeletonModelURL READ getSkeletonModelURLFromScript WRITE setSkeletonModelURLFromScript)
376+ Q_PROPERTY(QString skeletonModelURL READ getSkeletonModelURLFromScript WRITE setSkeletonModelURLFromScript NOTIFY skeletonModelURLChanged)
377377 Q_PROPERTY(QVector<AttachmentData> attachmentData READ getAttachmentData WRITE setAttachmentData)
378378
379379 Q_PROPERTY(QStringList jointNames READ getJointNames)
@@ -702,6 +702,7 @@ public: · 322 unmodified lines
702702 signals:
703703 void displayNameChanged();
704704 void sessionDisplayNameChanged();
705+ void skeletonModelURLChanged();
705706 void lookAtSnappingChanged(bool enabled);
706707 void sessionUUIDChanged();
707708
libraries/avatars/src/ScriptAvatarData.cpp+1−0
@@ -16,6 +16,7 @@ ScriptAvatarData::ScriptAvatarData(AvatarSharedPointer avatarData) : · 15 unmodified lines
1616 {
1717 QObject::connect(avatarData.get(), &AvatarData::displayNameChanged, this, &ScriptAvatarData::displayNameChanged);
1818 QObject::connect(avatarData.get(), &AvatarData::sessionDisplayNameChanged, this, &ScriptAvatarData::sessionDisplayNameChanged);
19+ QObject::connect(avatarData.get(), &AvatarData::skeletonModelURLChanged, this, &ScriptAvatarData::skeletonModelURLChanged);
1920 QObject::connect(avatarData.get(), &AvatarData::lookAtSnappingChanged, this, &ScriptAvatarData::lookAtSnappingChanged);
2021 }
2122
libraries/avatars/src/ScriptAvatarData.h+2−1
@@ -51,7 +51,7 @@ class ScriptAvatarData : public QObject { · 50 unmodified lines
5151 //
5252 // ATTACHMENT AND JOINT PROPERTIES
5353 //
54− Q_PROPERTY(QString skeletonModelURL READ getSkeletonModelURLFromScript)
54+ Q_PROPERTY(QString skeletonModelURL READ getSkeletonModelURLFromScript NOTIFY skeletonModelURLChanged)
5555 Q_PROPERTY(QVector<AttachmentData> attachmentData READ getAttachmentData)
5656 Q_PROPERTY(QStringList jointNames READ getJointNames)
5757
@@ -132,6 +132,7 @@ public: · 74 unmodified lines
132132 signals:
133133 void displayNameChanged();
134134 void sessionDisplayNameChanged();
135+ void skeletonModelURLChanged();
135136 void lookAtSnappingChanged(bool enabled);
136137
137138 public slots:
libraries/entities/src/EntityScriptingInterface.cpp+5−0
@@ -101,6 +101,11 @@ bool EntityScriptingInterface::canWriteAssets() { · 100 unmodified lines
101101 return nodeList->getThisNodeCanWriteAssets();
102102 }
103103
104+bool EntityScriptingInterface::canReplaceContent() {
105+ auto nodeList = DependencyManager::get<NodeList>();
106+ return nodeList->getThisNodeCanReplaceContent();
107+}
108+
104109 void EntityScriptingInterface::setEntityTree(EntityTreePointer elementTree) {
105110 if (_entityTree) {
106111 disconnect(_entityTree.get(), &EntityTree::addingEntity, this, &EntityScriptingInterface::addingEntity);
libraries/entities/src/EntityScriptingInterface.h+6−0
@@ -144,6 +144,12 @@ public slots: · 143 unmodified lines
144144 */
145145 Q_INVOKABLE bool canWriteAssets();
146146
147+ /**jsdoc
148+ * @function Entities.canReplaceContent
149+ * @return {bool} `true` if the DomainServer will allow this Node/Avatar to replace the domain's content set
150+ */
151+ Q_INVOKABLE bool canReplaceContent();
152+
147153 /**jsdoc
148154 * Add a new entity with the specified properties. If `clientOnly` is true, the entity will
149155 * not be sent to the server and will only be visible/accessible on the local client.
libraries/render-utils/src/Shadow.slh+3−1
@@ -89,7 +89,9 @@ float evalShadowCascadeAttenuation(int cascadeIndex, ShadowSampleOffsets offsets · 88 unmodified lines
8989
9090 float evalShadowAttenuation(vec3 worldLightDir, vec4 worldPosition, float viewDepth, vec3 worldNormal) {
9191 ShadowSampleOffsets offsets = evalShadowFilterOffsets(worldPosition);
92− vec4 cascadeShadowCoords[2] = { vec4(0), vec4(0) };
92+ vec4 cascadeShadowCoords[2];
93+ cascadeShadowCoords[0] = vec4(0);
94+ cascadeShadowCoords[1] = vec4(0);
9395 ivec2 cascadeIndices;
9496 float cascadeMix = determineShadowCascadesOnPixel(worldPosition, viewDepth, cascadeShadowCoords, cascadeIndices);
9597
libraries/shared/src/PathUtils.cpp+1−0
@@ -129,6 +129,7 @@ QUrl PathUtils::expandToAppAbsolutePath(const QUrl& fileUrl) { · 128 unmodified lines
129129 }
130130 return url;
131131 }
132+
132133 const QString& PathUtils::qmlBaseUrl() {
133134 static const QString staticResourcePath = resourcesUrl() + "qml/";
134135 return staticResourcePath;
scripts/system/marketplaces/marketplaces.js+17−7
@@ -75,11 +75,11 @@ var selectionDisplay = null; // for gridTool.js to ignore · 74 unmodified lines
7575 tablet.pushOntoStack(MARKETPLACE_CHECKOUT_QML_PATH);
7676 tablet.sendToQml({
7777 method: 'updateCheckoutQML', params: {
78− itemId: '0d90d21c-ce7a-4990-ad18-e9d2cf991027',
79− itemName: 'Test Flaregun',
80− itemPrice: (debugError ? 10 : 17),
81− itemHref: 'http://mpassets.highfidelity.com/0d90d21c-ce7a-4990-ad18-e9d2cf991027-v1/flaregun.json',
82− categories: ["Wearables", "Miscellaneous"]
78+ itemId: '424611a2-73d0-4c03-9087-26a6a279257b',
79+ itemName: '2018-02-15 Finnegon',
80+ itemPrice: (debugError ? 10 : 3),
81+ itemHref: 'http://devmpassets.highfidelity.com/424611a2-73d0-4c03-9087-26a6a279257b-v1/finnigon.fst',
82+ categories: ["Miscellaneous"]
8383 }
8484 });
8585 }
@@ -233,13 +233,19 @@ var selectionDisplay = null; // for gridTool.js to ignore · 147 unmodified lines
233233 return position;
234234 }
235235
236− function rezEntity(itemHref, isWearable) {
236+ function rezEntity(itemHref, itemType) {
237+ var isWearable = itemType === "wearable";
237238 var success = Clipboard.importEntities(itemHref);
238239 var wearableLocalPosition = null;
239240 var wearableLocalRotation = null;
240241 var wearableLocalDimensions = null;
241242 var wearableDimensions = null;
242243
244+ if (itemType === "contentSet") {
245+ console.log("Item is a content set; codepath shouldn't go here.")
246+ return;
247+ }
248+
243249 if (isWearable) {
244250 var wearableTransforms = Settings.getValue("io.highfidelity.avatarStore.checkOut.transforms");
245251 if (!wearableTransforms) {
@@ -545,7 +551,11 @@ var selectionDisplay = null; // for gridTool.js to ignore · 299 unmodified lines
545551 break;
546552 case 'checkout_rezClicked':
547553 case 'purchases_rezClicked':
548− rezEntity(message.itemHref, message.isWearable);
554+ if (message.itemType === "app") {
555+ console.log("How did you get here? You can't buy apps yet!");
556+ } else {
557+ rezEntity(message.itemHref, message.itemType);
558+ }
549559 break;
550560 case 'header_marketplaceImageClicked':
551561 case 'purchases_backClicked':