Lots of progress

This commit is contained in:
Zach Fox 2018-02-12 17:06:57 -08:00
parent f4e2b48dbb
commit 95e9eb8e4a
12 changed files with 174 additions and 53 deletions

View file

@ -37,11 +37,14 @@ Rectangle {
property double balanceAfterPurchase;
property bool alreadyOwned: false;
property int itemPrice: -1;
property bool itemIsJson: true;
property bool isCertified;
property string itemType;
property var itemTypesArray: ["entity", "wearable", "contentSet", "app", "avatar"];
property var buttonTextNormal: ["REZ IT", "WEAR IT", "SWAP CONTENT SET", "INSTALL IT", "WEAR IT"];
property var buttonTextClicked: ["REZZED", "WORN", "SWAPPED", "INSTALLED", "WORN"]
property bool shouldBuyWithControlledFailure: false;
property bool debugCheckoutSuccess: false;
property bool canRezCertifiedItems: Entities.canRezCertified() || Entities.canRezTmpCertified();
property bool isWearable;
property string referrer;
// Style
color: hifi.colors.white;
@ -85,7 +88,9 @@ Rectangle {
UserActivityLogger.commercePurchaseFailure(root.itemId, root.itemAuthor, root.itemPrice, !root.alreadyOwned, result.message);
} else {
root.itemHref = result.data.download_url;
root.isWearable = result.data.categories.indexOf("Wearables") > -1;
if (result.data.categories.indexOf("Wearables") > -1) {
root.itemType = "wearable";
}
root.activeView = "checkoutSuccess";
UserActivityLogger.commercePurchaseSuccess(root.itemId, root.itemAuthor, root.itemPrice, !root.alreadyOwned);
}
@ -122,7 +127,25 @@ Rectangle {
}
onItemHrefChanged: {
itemIsJson = root.itemHref.endsWith('.json');
if (root.itemHref.indexOf(".fst") > -1) {
root.itemType = "avatar";
} else if (root.itemHref.endsWith('.json.gz')) {
root.itemType = "contentSet";
} else if (root.itemHref.endsWith('.json')) {
root.itemType = "entity"; // "wearable" type handled later
} else if (root.itemHref.endsWith('.js')) {
root.itemType = "app";
} else {
root.itemType = "entity";
}
}
onItemTypeChanged: {
if (root.itemType === "entity" || root.itemType === "wearable" || root.itemType === "contentSet") {
root.isCertified = true;
} else {
root.isCertified = false;
}
}
onItemPriceChanged: {
@ -464,7 +487,7 @@ Rectangle {
// "Buy" button
HifiControlsUit.Button {
id: buyButton;
enabled: (root.balanceAfterPurchase >= 0 && purchasesReceived && balanceReceived) || !itemIsJson;
enabled: (root.balanceAfterPurchase >= 0 && purchasesReceived && balanceReceived) || (!root.isCertified);
color: hifi.buttons.blue;
colorScheme: hifi.colorSchemes.light;
anchors.top: checkoutActionButtonsContainer.top;
@ -472,9 +495,9 @@ Rectangle {
height: 40;
anchors.left: parent.left;
anchors.right: parent.right;
text: (itemIsJson ? ((purchasesReceived && balanceReceived) ? "Confirm Purchase" : "--") : "Get Item");
text: ((root.isCertified) ? ((purchasesReceived && balanceReceived) ? "Confirm Purchase" : "--") : "Get Item");
onClicked: {
if (itemIsJson) {
if (root.isCertified) {
buyButton.enabled = false;
if (!root.shouldBuyWithControlledFailure) {
Commerce.buy(itemId, itemPrice);
@ -576,7 +599,7 @@ Rectangle {
RalewayBold {
anchors.fill: parent;
text: "REZZED";
text: (root.buttonTextClicked)[itemTypesArray.indexOf(root.itemType)];
size: 18;
color: hifi.colors.white;
verticalAlignment: Text.AlignVCenter;
@ -592,7 +615,7 @@ Rectangle {
// "Rez" button
HifiControlsUit.Button {
id: rezNowButton;
enabled: root.canRezCertifiedItems || root.isWearable;
enabled: root.canRezCertifiedItems || root.itemType === "wearable";
buttonGlyph: hifi.glyphs.lightning;
color: hifi.buttons.red;
colorScheme: hifi.colorSchemes.light;
@ -601,17 +624,27 @@ Rectangle {
height: 50;
anchors.left: parent.left;
anchors.right: parent.right;
text: root.isWearable ? "Wear It" : "Rez It"
text: (root.buttonTextNormal)[itemTypesArray.indexOf(root.itemType)];
onClicked: {
sendToScript({method: 'checkout_rezClicked', itemHref: root.itemHref, isWearable: root.isWearable});
rezzedNotifContainer.visible = true;
rezzedNotifContainerTimer.start();
UserActivityLogger.commerceEntityRezzed(root.itemId, "checkout", root.isWearable ? "rez" : "wear");
if (root.itemType === "contentSet") {
lightboxPopup.titleText = "Replace Content";
lightboxPopup.bodyText = "Rezzing this content set will replace the existing environment and all of the items in this domain.";
lightboxPopup.button1text = "CANCEL";
lightboxPopup.button1method = "root.visible = false;"
lightboxPopup.button2text = "CONFIRM";
lightboxPopup.button2method = "sendToScript({method: 'checkout_rezClicked', itemHref: " + root.itemHref + ", itemType: " + root.itemType + "});";
lightboxPopup.visible = true;
} else {
sendToScript({method: 'checkout_rezClicked', itemHref: root.itemHref, itemType: root.itemType});
rezzedNotifContainer.visible = true;
rezzedNotifContainerTimer.start();
UserActivityLogger.commerceEntityRezzed(root.itemId, "checkout", root.itemType);
}
}
}
RalewaySemiBold {
id: noPermissionText;
visible: !root.canRezCertifiedItems && !root.isWearable;
visible: !root.canRezCertifiedItems && root.itemType !== "wearable";
text: '<font color="' + hifi.colors.redAccent + '"><a href="#">You do not have Certified Rez permissions in this domain.</a></font>'
// Text size
size: 16;
@ -640,7 +673,7 @@ Rectangle {
}
RalewaySemiBold {
id: explainRezText;
visible: !root.isWearable;
visible: root.itemType === "entity";
text: '<font color="' + hifi.colors.redAccent + '"><a href="#">What does "Rez" mean?</a></font>'
// Text size
size: 16;
@ -851,7 +884,7 @@ Rectangle {
buyButton.color = hifi.buttons.red;
root.shouldBuyWithControlledFailure = true;
} else {
buyButton.text = (itemIsJson ? ((purchasesReceived && balanceReceived) ? (root.alreadyOwned ? "Buy Another" : "Buy"): "--") : "Get Item");
buyButton.text = (root.isCertified ? ((purchasesReceived && balanceReceived) ? (root.alreadyOwned ? "Buy Another" : "Buy"): "--") : "Get Item");
buyButton.color = hifi.buttons.blue;
root.shouldBuyWithControlledFailure = false;
}
@ -901,7 +934,7 @@ Rectangle {
}
function setBuyText() {
if (root.itemIsJson) {
if (root.isCertified) {
if (root.purchasesReceived && root.balanceReceived) {
if (root.balanceAfterPurchase < 0) {
if (root.alreadyOwned) {

View file

@ -41,8 +41,11 @@ Item {
property int limitedRun;
property string itemType;
property var itemTypesArray: ["entity", "wearable", "contentSet", "app", "avatar"];
property var buttonTextNormal: ["REZ", "WEAR", "SWAP", "INSTALL", "WEAR"];
property var buttonTextClicked: ["REZZED", "WORN", "SWAPPED", "INSTALLED", "WORN"]
property var buttonTextNormal: ["REZ", "WEAR", "REPLACE", "INSTALL", "WEAR"];
property var buttonTextClicked: ["REZZED", "WORN", "REPLACED", "INSTALLED", "WORN"]
property var buttonGlyph: [hifi.glyphs.wand, hifi.glyphs.hat, hifi.glyphs.globe, hifi.glyphs.install, hifi.glyphs.avatar];
property bool showConfirmation: false;
property bool hasPermissionToRezThis;
property string originalStatusText;
property string originalStatusColor;
@ -50,6 +53,23 @@ Item {
height: 110;
width: parent.width;
Connections {
target: Commerce;
onContentSetChanged: {
if (contentSetMarketplaceID === root.itemId) {
showConfirmation = true;
}
}
}
onItemTypeChanged: {
if ((itemType === "entity" && (!Entities.canRezCertified() && !Entities.canRezTmpCertified())) ||
(itemType === "contentSet" && !Entities.canReplaceContent())) {
root.hasPermissionToRezThis = false;
}
}
onPurchaseStatusChangedChanged: {
if (root.purchaseStatusChanged === true && root.purchaseStatus === "confirmed") {
root.originalStatusText = statusText.text;
@ -60,6 +80,15 @@ Item {
}
}
onShowConfirmationChanged: {
if (root.showConfirmation) {
rezzedNotifContainer.visible = true;
rezzedNotifContainerTimer.start();
UserActivityLogger.commerceEntityRezzed(root.itemId, "purchases", root.itemType);
root.showConfirmation = false;
}
}
Timer {
id: confirmedTimer;
interval: 3000;
@ -325,7 +354,7 @@ Item {
RalewayBold {
anchors.fill: parent;
text: (root.buttonTextClicked)[itemTypesArray.indexOf(root.itemType)];
size: 18;
size: 15;
color: hifi.colors.white;
verticalAlignment: Text.AlignVCenter;
horizontalAlignment: Text.AlignHCenter;
@ -353,10 +382,12 @@ Item {
enabled: (root.canRezCertifiedItems || root.itemType === "wearable") && root.purchaseStatus !== "invalidated";
onClicked: {
sendToPurchases({method: 'purchases_rezClicked', itemHref: root.itemHref, itemType: root.itemType});
rezzedNotifContainer.visible = true;
rezzedNotifContainerTimer.start();
UserActivityLogger.commerceEntityRezzed(root.itemId, "purchases", root.itemType === "wearable" ? "rez" : "wear");
if (root.itemType === "contentSet") {
sendToPurchases({method: 'showReplaceContentLightbox', itemId: root.itemId, itemHref: root.itemHref});
} else {
sendToPurchases({method: 'purchases_rezClicked', itemHref: root.itemHref, itemType: root.itemType});
root.showConfirmation = true;
}
}
style: ButtonStyle {
@ -396,13 +427,13 @@ Item {
label: Item {
HiFiGlyphs {
id: lightningIcon;
text: hifi.glyphs.lightning;
id: rezIcon;
text: (root.buttonGlyph)[itemTypesArray.indexOf(root.itemType)];
// Size
size: 32;
size: 60;
// Anchors
anchors.top: parent.top;
anchors.topMargin: 12;
anchors.topMargin: 0;
anchors.left: parent.left;
anchors.right: parent.right;
horizontalAlignment: Text.AlignHCenter;
@ -411,8 +442,8 @@ Item {
: hifi.buttons.disabledTextColor[control.colorScheme]
}
RalewayBold {
anchors.top: lightningIcon.bottom;
anchors.topMargin: -20;
anchors.top: rezIcon.bottom;
anchors.topMargin: -4;
anchors.right: parent.right;
anchors.left: parent.left;
anchors.bottom: parent.bottom;

View file

@ -455,10 +455,16 @@ Rectangle {
limitedRun: model.limited_run;
displayedItemCount: model.displayedItemCount;
itemType: {
if (model.download_url.indexOf(".fst") > -1) {
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('.json')) {
"entity";
} else if (model.root_file_url.endsWith('.js')) {
"app";
} else {
"entity";
}
@ -490,6 +496,14 @@ Rectangle {
lightboxPopup.button1text = "CLOSE";
lightboxPopup.button1method = "root.visible = false;"
lightboxPopup.visible = true;
} else if (msg.method === "showReplaceContentLightbox") {
lightboxPopup.titleText = "Replace Content";
lightboxPopup.bodyText = "Rezzing this content set will replace the existing environment and all of the items in this domain.";
lightboxPopup.button1text = "CANCEL";
lightboxPopup.button1method = "root.visible = false;"
lightboxPopup.button2text = "CONFIRM";
lightboxPopup.button2method = "Commerce.replaceContentSet('" + msg.itemId + "', '" + msg.itemHref + "'); root.visible = false;";
lightboxPopup.visible = true;
} else if (msg.method === "setFilterText") {
filterBar.text = msg.filterText;
}

View file

@ -354,5 +354,9 @@ Item {
readonly property string wallet: "\ue027"
readonly property string paperPlane: "\ue028"
readonly property string passphrase: "\ue029"
readonly property string globe: "\ue02c"
readonly property string wand: "\ue02d"
readonly property string hat: "\ue02e"
readonly property string install: "\ue02f"
}
}

View file

@ -6176,6 +6176,24 @@ bool Application::askToWearAvatarAttachmentUrl(const QString& url) {
return true;
}
void Application::replaceDomainContent(const QString& url) {
qCDebug(interfaceapp) << "Attempting to replace domain content: " << url;
QByteArray urlData(url.toUtf8());
auto limitedNodeList = DependencyManager::get<LimitedNodeList>();
limitedNodeList->eachMatchingNode([](const SharedNodePointer& node) {
return node->getType() == NodeType::EntityServer && node->getActiveSocket();
}, [&urlData, limitedNodeList](const SharedNodePointer& octreeNode) {
auto octreeFilePacket = NLPacket::create(PacketType::OctreeFileReplacementFromUrl, urlData.size(), true);
octreeFilePacket->write(urlData);
limitedNodeList->sendPacket(std::move(octreeFilePacket), *octreeNode);
});
auto addressManager = DependencyManager::get<AddressManager>();
addressManager->handleLookupString(DOMAIN_SPAWNING_POINT);
QString newHomeAddress = addressManager->getHost() + DOMAIN_SPAWNING_POINT;
qCDebug(interfaceapp) << "Setting new home bookmark to: " << newHomeAddress;
DependencyManager::get<LocationBookmarks>()->setHomeLocationToAddress(newHomeAddress);
}
bool Application::askToReplaceDomainContent(const QString& url) {
QString methodDetails;
const int MAX_CHARACTERS_PER_LINE = 90;
@ -6195,21 +6213,7 @@ bool Application::askToReplaceDomainContent(const QString& url) {
QString details;
if (static_cast<QMessageBox::StandardButton>(answer.toInt()) == QMessageBox::Yes) {
// Given confirmation, send request to domain server to replace content
qCDebug(interfaceapp) << "Attempting to replace domain content: " << url;
QByteArray urlData(url.toUtf8());
auto limitedNodeList = DependencyManager::get<LimitedNodeList>();
limitedNodeList->eachMatchingNode([](const SharedNodePointer& node) {
return node->getType() == NodeType::EntityServer && node->getActiveSocket();
}, [&urlData, limitedNodeList](const SharedNodePointer& octreeNode) {
auto octreeFilePacket = NLPacket::create(PacketType::OctreeFileReplacementFromUrl, urlData.size(), true);
octreeFilePacket->write(urlData);
limitedNodeList->sendPacket(std::move(octreeFilePacket), *octreeNode);
});
auto addressManager = DependencyManager::get<AddressManager>();
addressManager->handleLookupString(DOMAIN_SPAWNING_POINT);
QString newHomeAddress = addressManager->getHost() + DOMAIN_SPAWNING_POINT;
qCDebug(interfaceapp) << "Setting new home bookmark to: " << newHomeAddress;
DependencyManager::get<LocationBookmarks>()->setHomeLocationToAddress(newHomeAddress);
replaceDomainContent(url);
details = "SuccessfulRequestToReplaceContent";
} else {
details = "UserDeclinedToReplaceContent";

View file

@ -284,6 +284,8 @@ public:
bool getSaveAvatarOverrideUrl() { return _saveAvatarOverrideUrl; }
void saveNextPhysicsStats(QString filename);
void replaceDomainContent(const QString& url);
signals:
void svoImportRequested(const QString& url);

View file

@ -15,6 +15,8 @@
#include "Ledger.h"
#include "Wallet.h"
#include <AccountManager.h>
#include <Application.h>
#include <UserActivityLogger.h>
QmlCommerce::QmlCommerce() {
auto ledger = DependencyManager::get<Ledger>();
@ -163,3 +165,14 @@ void QmlCommerce::transferHfcToUsername(const QString& username, const int& amou
QString key = keys[0];
ledger->transferHfcToUsername(key, username, amount, optionalMessage);
}
void QmlCommerce::replaceContentSet(const QString& id, const QString& url) {
qApp->replaceDomainContent(url);
QJsonObject messageProperties = {
{ "status", "SuccessfulRequestToReplaceContent" },
{ "content_set_url", url }
};
UserActivityLogger::getInstance().logAction("replace_domain_content", messageProperties);
emit contentSetChanged(id);
}

View file

@ -48,6 +48,8 @@ signals:
void transferHfcToNodeResult(QJsonObject result);
void transferHfcToUsernameResult(QJsonObject result);
void contentSetChanged(const QString& contentSetMarketplaceID);
protected:
Q_INVOKABLE void getWalletStatus();
@ -71,6 +73,8 @@ protected:
Q_INVOKABLE void transferHfcToNode(const QString& nodeID, const int& amount, const QString& optionalMessage);
Q_INVOKABLE void transferHfcToUsername(const QString& username, const int& amount, const QString& optionalMessage);
Q_INVOKABLE void replaceContentSet(const QString& id, const QString& url);
};
#endif // hifi_QmlCommerce_h

View file

@ -101,6 +101,11 @@ bool EntityScriptingInterface::canWriteAssets() {
return nodeList->getThisNodeCanWriteAssets();
}
bool EntityScriptingInterface::canReplaceContent() {
auto nodeList = DependencyManager::get<NodeList>();
return nodeList->getThisNodeCanReplaceContent();
}
void EntityScriptingInterface::setEntityTree(EntityTreePointer elementTree) {
if (_entityTree) {
disconnect(_entityTree.get(), &EntityTree::addingEntity, this, &EntityScriptingInterface::addingEntity);

View file

@ -144,6 +144,12 @@ public slots:
*/
Q_INVOKABLE bool canWriteAssets();
/**jsdoc
* @function Entities.canReplaceContent
* @return {bool} `true` if the DomainServer will allow this Node/Avatar to replace the domain's content set
*/
Q_INVOKABLE bool canReplaceContent();
/**jsdoc
* Add a new entity with the specified properties. If `clientOnly` is true, the entity will
* not be sent to the server and will only be visible/accessible on the local client.

View file

@ -65,7 +65,7 @@ var selectionDisplay = null; // for gridTool.js to ignore
var onMarketplaceScreen = false;
var onCommerceScreen = false;
var debugCheckout = false;
var debugCheckout = true;
var debugError = false;
function showMarketplace() {
if (!debugCheckout) {
@ -75,11 +75,11 @@ var selectionDisplay = null; // for gridTool.js to ignore
tablet.pushOntoStack(MARKETPLACE_CHECKOUT_QML_PATH);
tablet.sendToQml({
method: 'updateCheckoutQML', params: {
itemId: '0d90d21c-ce7a-4990-ad18-e9d2cf991027',
itemName: 'Test Flaregun',
itemPrice: (debugError ? 10 : 17),
itemHref: 'http://mpassets.highfidelity.com/0d90d21c-ce7a-4990-ad18-e9d2cf991027-v1/flaregun.json',
categories: ["Wearables", "Miscellaneous"]
itemId: 'e197e3d7-eafc-4aa5-9341-acee57174fe9',
itemName: 'Oasis',
itemPrice: (debugError ? 10 : 11),
itemHref: 'http://mpassets-staging.highfidelity.com/e197e3d7-eafc-4aa5-9341-acee57174fe9-v1/oasis_Aug15.json.gz',
categories: ["Miscellaneous"]
}
});
}
@ -240,6 +240,11 @@ var selectionDisplay = null; // for gridTool.js to ignore
var wearableLocalDimensions = null;
var wearableDimensions = null;
if (itemType === "contentSet") {
console.log("Item is a content set; codepath shouldn't go here.")
return;
}
if (isWearable) {
var wearableTransforms = Settings.getValue("io.highfidelity.avatarStore.checkOut.transforms");
if (!wearableTransforms) {