Merge branch 'master' of github.com:highfidelity/hifi into feature/ant-man

This commit is contained in:
Dante Ruiz 2017-09-05 16:26:51 -07:00
commit fec8482b69
46 changed files with 1082 additions and 640 deletions

View file

@ -35,7 +35,7 @@ void ScriptableAvatar::startAnimation(const QString& url, float fps, float prior
return;
}
_animation = DependencyManager::get<AnimationCache>()->getAnimation(url);
_animationDetails = AnimationDetails("", QUrl(url), fps, 0, loop, hold, false, firstFrame, lastFrame, true, firstFrame);
_animationDetails = AnimationDetails("", QUrl(url), fps, 0, loop, hold, false, firstFrame, lastFrame, true, firstFrame, false);
_maskedJoints = maskedJoints;
}

View file

@ -6,7 +6,14 @@
<title>Welcome to Interface</title>
<style>
body {
@font-face {
font-family: 'Raleway Light';
src: url('../fonts/Raleway-Light.ttf') format('truetype');
font-weight: normal;
font-style: normal;
}
body {
background: black;
width: 100%;
overflow-x: hidden;
@ -15,6 +22,14 @@
padding: 0;
}
a:link {color:inherit}
a:active {color:inherit}
a:visited {color:inherit}
a:hover {
color:inherit;
cursor: pointer;
}
#left_button {
position: absolute;
left: 70;
@ -38,6 +53,15 @@
position: absolute;
top: 0; left: 0; bottom: 0; right: 0;
}
#report_problem {
position: fixed;
top: 10;
right: 10;
font-family: "Raleway Light", sans-serif;
text-decoration: none;
color: #ddd;
}
</style>
<script>
var handControllerImageURL = null;
@ -152,6 +176,7 @@
<a href="#" id="left_button" onmousedown="cycleLeft()"></a>
<a href="#" id="right_button" onmousedown="cycleRight()"></a>
</div>
<a href="mailto:support@highfidelity.com" id="report_problem">Report Problem</a>
</body>
</html>

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 643 B

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 56 KiB

After

Width:  |  Height:  |  Size: 909 B

View file

@ -29,7 +29,6 @@ Rectangle {
property bool purchasesReceived: false;
property bool balanceReceived: false;
property bool securityImageResultReceived: false;
property bool keyFilePathIfExistsResultReceived: false;
property string itemId: "";
property string itemHref: "";
property double balanceAfterPurchase: 0;
@ -46,10 +45,15 @@ Rectangle {
root.activeView = "needsLogIn";
} else if (isLoggedIn) {
root.activeView = "initialize";
commerce.getSecurityImage();
commerce.getKeyFilePathIfExists();
commerce.balance();
commerce.inventory();
}
}
onKeyFilePathIfExistsResult: {
if (path === "" && root.activeView !== "notSetUp") {
root.activeView = "notSetUp";
} else if (path !== "" && root.activeView === "initialize") {
commerce.getSecurityImage();
}
}
@ -57,8 +61,8 @@ Rectangle {
securityImageResultReceived = true;
if (!exists && root.activeView !== "notSetUp") { // "If security image is not set up"
root.activeView = "notSetUp";
} else if (root.securityImageResultReceived && exists && root.keyFilePathIfExistsResultReceived && root.activeView === "initialize") {
root.activeView = "checkoutMain";
} else if (exists && root.activeView === "initialize") {
commerce.getWalletAuthenticatedStatus();
} else if (exists) {
// just set the source again (to be sure the change was noticed)
securityImage.source = "";
@ -66,12 +70,17 @@ Rectangle {
}
}
onKeyFilePathIfExistsResult: {
keyFilePathIfExistsResultReceived = true;
if (path === "" && root.activeView !== "notSetUp") {
root.activeView = "notSetUp";
} else if (root.securityImageResultReceived && root.keyFilePathIfExistsResultReceived && path !== "" && root.activeView === "initialize") {
onWalletAuthenticatedStatusResult: {
if (!isAuthenticated && !passphraseModal.visible) {
passphraseModal.visible = true;
} else if (isAuthenticated) {
root.activeView = "checkoutMain";
if (!balanceReceived) {
commerce.balance();
}
if (!purchasesReceived) {
commerce.inventory();
}
}
}
@ -89,8 +98,8 @@ Rectangle {
console.log("Failed to get balance", result.data.message);
} else {
root.balanceReceived = true;
hfcBalanceText.text = (parseFloat(result.data.balance/100).toFixed(2)) + " HFC";
balanceAfterPurchase = parseFloat(result.data.balance/100) - root.itemPriceFull/100;
hfcBalanceText.text = result.data.balance + " HFC";
balanceAfterPurchase = result.data.balance - root.itemPriceFull;
root.setBuyText();
}
}
@ -110,10 +119,6 @@ Rectangle {
}
}
HifiWallet.SecurityImageModel {
id: securityImageModel;
}
//
// TITLE BAR START
//
@ -195,7 +200,6 @@ Rectangle {
securityImageResultReceived = false;
purchasesReceived = false;
balanceReceived = false;
keyFilePathIfExistsResultReceived = false;
commerce.getLoginStatus();
}
}
@ -221,7 +225,20 @@ Rectangle {
}
}
HifiWallet.PassphraseModal {
id: passphraseModal;
visible: false;
anchors.top: titleBarContainer.bottom;
anchors.bottom: parent.bottom;
anchors.left: parent.left;
anchors.right: parent.right;
Connections {
onSendSignalToParent: {
sendToScript(msg);
}
}
}
//
// "WALLET NOT SET UP" START
@ -553,7 +570,7 @@ Rectangle {
}
RalewayRegular {
id: balanceAfterPurchaseText;
text: balanceAfterPurchase.toFixed(2) + " HFC";
text: balanceAfterPurchase + " HFC";
// Text size
size: balanceAfterPurchaseTextLabel.size;
// Anchors
@ -648,7 +665,7 @@ Rectangle {
sendToScript({method: 'checkout_goToPurchases'});
}
}
RalewayRegular {
id: buyText;
// Text size
@ -687,7 +704,7 @@ Rectangle {
anchors.bottom: root.bottom;
anchors.left: parent.left;
anchors.right: parent.right;
RalewayRegular {
id: completeText;
text: "<b>Purchase Complete!</b><br><br>You bought <b>" + (itemNameText.text) + "</b> by <b>" + (itemAuthorText.text) + "</b>";
@ -706,7 +723,7 @@ Rectangle {
horizontalAlignment: Text.AlignHCenter;
verticalAlignment: Text.AlignVCenter;
}
Item {
id: checkoutSuccessActionButtonsContainer;
// Size
@ -756,7 +773,7 @@ Rectangle {
}
}
}
Item {
id: continueShoppingButtonContainer;
// Size
@ -799,7 +816,7 @@ Rectangle {
anchors.bottom: root.bottom;
anchors.left: parent.left;
anchors.right: parent.right;
RalewayRegular {
id: failureHeaderText;
text: "<b>Purchase Failed.</b><br>Your Purchases and HFC balance haven't changed.";
@ -818,7 +835,7 @@ Rectangle {
horizontalAlignment: Text.AlignHCenter;
verticalAlignment: Text.AlignVCenter;
}
RalewayRegular {
id: failureErrorText;
// Text size
@ -836,7 +853,7 @@ Rectangle {
horizontalAlignment: Text.AlignHCenter;
verticalAlignment: Text.AlignVCenter;
}
Item {
id: backToMarketplaceButtonContainer;
// Size
@ -892,7 +909,7 @@ Rectangle {
itemNameText.text = message.params.itemName;
itemAuthorText.text = message.params.itemAuthor;
root.itemPriceFull = message.params.itemPrice;
itemPriceText.text = root.itemPriceFull === 0 ? "Free" : "<b>" + (parseFloat(root.itemPriceFull/100).toFixed(2)) + " HFC</b>";
itemPriceText.text = root.itemPriceFull === 0 ? "Free" : "<b>" + root.itemPriceFull + " HFC</b>";
itemHref = message.params.itemHref;
if (itemHref.indexOf('.json') === -1) {
root.itemIsJson = false;

View file

@ -28,7 +28,6 @@ Rectangle {
property string activeView: "initialize";
property string referrerURL: "";
property bool securityImageResultReceived: false;
property bool keyFilePathIfExistsResultReceived: false;
property bool purchasesReceived: false;
property bool punctuationMode: false;
// Style
@ -41,9 +40,15 @@ Rectangle {
root.activeView = "needsLogIn";
} else if (isLoggedIn) {
root.activeView = "initialize";
commerce.getSecurityImage();
commerce.getKeyFilePathIfExists();
commerce.inventory();
}
}
onKeyFilePathIfExistsResult: {
if (path === "" && root.activeView !== "notSetUp") {
root.activeView = "notSetUp";
} else if (path !== "" && root.activeView === "initialize") {
commerce.getSecurityImage();
}
}
@ -51,8 +56,8 @@ Rectangle {
securityImageResultReceived = true;
if (!exists && root.activeView !== "notSetUp") { // "If security image is not set up"
root.activeView = "notSetUp";
} else if (root.securityImageResultReceived && exists && root.keyFilePathIfExistsResultReceived && root.activeView === "initialize") {
root.activeView = "purchasesMain";
} else if (exists && root.activeView === "initialize") {
commerce.getWalletAuthenticatedStatus();
} else if (exists) {
// just set the source again (to be sure the change was noticed)
securityImage.source = "";
@ -60,12 +65,12 @@ Rectangle {
}
}
onKeyFilePathIfExistsResult: {
keyFilePathIfExistsResultReceived = true;
if (path === "" && root.activeView !== "notSetUp") {
root.activeView = "notSetUp";
} else if (root.securityImageResultReceived && root.keyFilePathIfExistsResultReceived && path !== "" && root.activeView === "initialize") {
onWalletAuthenticatedStatusResult: {
if (!isAuthenticated && !passphraseModal.visible) {
passphraseModal.visible = true;
} else if (isAuthenticated) {
root.activeView = "purchasesMain";
commerce.inventory();
}
}
@ -166,7 +171,6 @@ Rectangle {
Component.onCompleted: {
securityImageResultReceived = false;
purchasesReceived = false;
keyFilePathIfExistsResultReceived = false;
commerce.getLoginStatus();
}
}
@ -191,6 +195,21 @@ Rectangle {
commerce.getLoginStatus();
}
}
HifiWallet.PassphraseModal {
id: passphraseModal;
visible: false;
anchors.top: titleBarContainer.bottom;
anchors.bottom: parent.bottom;
anchors.left: parent.left;
anchors.right: parent.right;
Connections {
onSendSignalToParent: {
sendToScript(msg);
}
}
}
//
// "WALLET NOT SET UP" START

View file

@ -47,11 +47,26 @@ Item {
HifiControlsUit.Button {
color: hifi.buttons.black;
colorScheme: hifi.colorSchemes.dark;
anchors.bottom: helpText.bottom;
anchors.bottom: resetButton.top;
anchors.bottomMargin: 15;
anchors.horizontalCenter: parent.horizontalCenter;
height: 50;
width: 250;
text: "Testing: Reset Wallet!";
text: "DEBUG: Clear Cached Passphrase";
onClicked: {
commerce.setPassphrase("");
}
}
HifiControlsUit.Button {
id: resetButton;
color: hifi.buttons.red;
colorScheme: hifi.colorSchemes.dark;
anchors.bottom: helpText.bottom;
anchors.bottomMargin: 15;
anchors.horizontalCenter: parent.horizontalCenter;
height: 50;
width: 250;
text: "DEBUG: Reset Wallet!";
onClicked: {
commerce.reset();
sendSignalToWallet({method: 'walletReset'});

View file

@ -0,0 +1,304 @@
//
// PassphraseModal.qml
// qml/hifi/commerce/wallet
//
// PassphraseModal
//
// Created by Zach Fox on 2017-08-31
// Copyright 2017 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 Hifi 1.0 as Hifi
import QtQuick 2.5
import QtQuick.Controls 1.4
import "../../../styles-uit"
import "../../../controls-uit" as HifiControlsUit
import "../../../controls" as HifiControls
// references XXX from root context
Item {
HifiConstants { id: hifi; }
id: root;
z: 998;
property bool keyboardRaised: false;
Hifi.QmlCommerce {
id: commerce;
onSecurityImageResult: {
passphraseModalSecurityImage.source = "";
passphraseModalSecurityImage.source = "image://security/securityImage";
}
onWalletAuthenticatedStatusResult: {
submitPassphraseInputButton.enabled = true;
if (!isAuthenticated) {
errorText.text = "Authentication failed - please try again.";
} else {
root.visible = false;
}
}
}
// This object is always used in a popup.
// This MouseArea is used to prevent a user from being
// able to click on a button/mouseArea underneath the popup.
MouseArea {
anchors.fill: parent;
propagateComposedEvents: false;
}
// This will cause a bug -- if you bring up passphrase selection in HUD mode while
// in HMD while having HMD preview enabled, then move, then finish passphrase selection,
// HMD preview will stay off.
// TODO: Fix this unlikely bug
onVisibleChanged: {
if (visible) {
passphraseField.focus = true;
sendSignalToParent({method: 'disableHmdPreview'});
} else {
sendSignalToParent({method: 'maybeEnableHmdPreview'});
}
}
// Background rectangle
Rectangle {
anchors.fill: parent;
color: "black";
opacity: 0.9;
}
Rectangle {
anchors.top: parent.top;
anchors.left: parent.left;
anchors.right: parent.right;
height: 250;
color: hifi.colors.baseGray;
RalewaySemiBold {
id: instructionsText;
text: "Enter Wallet Passphrase";
size: 16;
anchors.top: parent.top;
anchors.topMargin: 30;
anchors.left: parent.left;
anchors.leftMargin: 16;
width: passphraseField.width;
height: paintedHeight;
// Style
color: hifi.colors.faintGray;
// Alignment
horizontalAlignment: Text.AlignLeft;
}
HifiControlsUit.TextField {
id: passphraseField;
anchors.top: instructionsText.bottom;
anchors.topMargin: 4;
anchors.left: instructionsText.left;
width: 280;
height: 50;
echoMode: TextInput.Password;
placeholderText: "passphrase";
onFocusChanged: {
root.keyboardRaised = focus;
}
MouseArea {
anchors.fill: parent;
onClicked: {
parent.focus = true;
root.keyboardRaised = true;
}
}
onAccepted: {
submitPassphraseInputButton.enabled = false;
commerce.setPassphrase(passphraseField.text);
}
}
// Show passphrase text
HifiControlsUit.CheckBox {
id: showPassphrase;
colorScheme: hifi.colorSchemes.dark;
anchors.left: passphraseField.left;
anchors.top: passphraseField.bottom;
anchors.topMargin: 8;
height: 30;
text: "Show passphrase as plain text";
boxSize: 24;
onClicked: {
passphraseField.echoMode = checked ? TextInput.Normal : TextInput.Password;
}
}
// Security Image
Item {
id: securityImageContainer;
// Anchors
anchors.top: instructionsText.top;
anchors.left: passphraseField.right;
anchors.leftMargin: 12;
anchors.right: parent.right;
Image {
id: passphraseModalSecurityImage;
anchors.top: parent.top;
anchors.horizontalCenter: parent.horizontalCenter;
height: 75;
width: height;
fillMode: Image.PreserveAspectFit;
mipmap: true;
source: "image://security/securityImage";
cache: false;
onVisibleChanged: {
commerce.getSecurityImage();
}
}
Image {
id: passphraseModalSecurityImageOverlay;
source: "images/lockOverlay.png";
width: passphraseModalSecurityImage.width * 0.45;
height: passphraseModalSecurityImage.height * 0.45;
anchors.bottom: passphraseModalSecurityImage.bottom;
anchors.right: passphraseModalSecurityImage.right;
mipmap: true;
opacity: 0.9;
}
// "Security image" text below pic
RalewayRegular {
text: "security image";
// Text size
size: 12;
// Anchors
anchors.top: passphraseModalSecurityImage.bottom;
anchors.topMargin: 4;
anchors.left: securityImageContainer.left;
anchors.right: securityImageContainer.right;
height: paintedHeight;
// Style
color: hifi.colors.faintGray;
// Alignment
horizontalAlignment: Text.AlignHCenter;
verticalAlignment: Text.AlignVCenter;
}
}
// Error text above buttons
RalewaySemiBold {
id: errorText;
text: "";
// Text size
size: 16;
// Anchors
anchors.bottom: passphrasePopupActionButtonsContainer.top;
anchors.bottomMargin: 4;
anchors.left: parent.left;
anchors.leftMargin: 16;
anchors.right: parent.right;
anchors.rightMargin: 16;
height: 30;
// Style
color: hifi.colors.redHighlight;
// Alignment
horizontalAlignment: Text.AlignHCenter;
verticalAlignment: Text.AlignVCenter;
}
//
// ACTION BUTTONS START
//
Item {
id: passphrasePopupActionButtonsContainer;
// Size
width: root.width;
height: 50;
// Anchors
anchors.left: parent.left;
anchors.bottom: parent.bottom;
anchors.bottomMargin: 10;
// "Cancel" button
HifiControlsUit.Button {
id: cancelPassphraseInputButton;
color: hifi.buttons.black;
colorScheme: hifi.colorSchemes.dark;
anchors.top: parent.top;
height: 40;
anchors.left: parent.left;
anchors.leftMargin: 20;
width: parent.width/2 - anchors.leftMargin*2;
text: "Cancel"
onClicked: {
sendSignalToParent({method: 'passphrasePopup_cancelClicked'});
}
}
// "Submit" button
HifiControlsUit.Button {
id: submitPassphraseInputButton;
color: hifi.buttons.blue;
colorScheme: hifi.colorSchemes.dark;
anchors.top: parent.top;
height: 40;
anchors.right: parent.right;
anchors.rightMargin: 20;
width: parent.width/2 - anchors.rightMargin*2;
text: "Submit"
onClicked: {
submitPassphraseInputButton.enabled = false;
commerce.setPassphrase(passphraseField.text);
}
}
}
}
Item {
id: keyboardContainer;
z: 999;
visible: keyboard.raised;
property bool punctuationMode: false;
anchors {
bottom: parent.bottom;
left: parent.left;
right: parent.right;
}
Image {
id: lowerKeyboardButton;
source: "images/lowerKeyboard.png";
anchors.horizontalCenter: parent.horizontalCenter;
anchors.bottom: keyboard.top;
height: 30;
width: 120;
MouseArea {
anchors.fill: parent;
onClicked: {
root.keyboardRaised = false;
}
}
}
HifiControlsUit.Keyboard {
id: keyboard;
raised: HMD.mounted && root.keyboardRaised;
numeric: parent.punctuationMode;
anchors {
bottom: parent.bottom;
left: parent.left;
right: parent.right;
}
}
}
signal sendSignalToParent(var msg);
}

View file

@ -40,8 +40,8 @@ Item {
passphrasePageSecurityImage.source = "image://security/securityImage";
}
onPassphraseSetupStatusResult: {
sendMessageToLightbox({method: 'statusResult', status: passphraseIsSetup});
onWalletAuthenticatedStatusResult: {
sendMessageToLightbox({method: 'statusResult', status: isAuthenticated});
}
}
@ -58,10 +58,6 @@ Item {
}
}
SecurityImageModel {
id: gridModel;
}
HifiControlsUit.TextField {
id: passphraseField;
anchors.top: parent.top;
@ -199,7 +195,7 @@ Item {
// Text below TextFields
RalewaySemiBold {
id: passwordReqs;
text: "Passphrase must be at least 4 characters";
text: "Passphrase must be at least 3 characters";
// Text size
size: 16;
// Anchors
@ -256,7 +252,7 @@ Item {
}
function validateAndSubmitPassphrase() {
if (passphraseField.text.length < 4) {
if (passphraseField.text.length < 3) {
setErrorText("Passphrase too short.");
return false;
} else if (passphraseField.text !== passphraseFieldAgain.text) {

View file

@ -26,8 +26,6 @@ Rectangle {
id: root;
property string activeView: "initialize";
property bool securityImageResultReceived: false;
property bool keyFilePathIfExistsResultReceived: false;
property bool keyboardRaised: false;
// Style
@ -40,25 +38,30 @@ Rectangle {
root.activeView = "needsLogIn";
} else if (isLoggedIn) {
root.activeView = "initialize";
commerce.getSecurityImage();
commerce.getKeyFilePathIfExists();
}
}
onSecurityImageResult: {
securityImageResultReceived = true;
if (!exists && root.activeView !== "notSetUp") { // "If security image is not set up"
onKeyFilePathIfExistsResult: {
if (path === "" && root.activeView !== "notSetUp") {
root.activeView = "notSetUp";
} else if (root.securityImageResultReceived && exists && root.keyFilePathIfExistsResultReceived && root.activeView === "initialize") {
root.activeView = "walletHome";
} else if (path !== "" && root.activeView === "initialize") {
commerce.getSecurityImage();
}
}
onKeyFilePathIfExistsResult: {
keyFilePathIfExistsResultReceived = true;
if (path === "" && root.activeView !== "notSetUp") {
onSecurityImageResult: {
if (!exists && root.activeView !== "notSetUp") { // "If security image is not set up"
root.activeView = "notSetUp";
} else if (root.securityImageResultReceived && root.keyFilePathIfExistsResultReceived && path !== "" && root.activeView === "initialize") {
} else if (exists && root.activeView === "initialize") {
commerce.getWalletAuthenticatedStatus();
}
}
onWalletAuthenticatedStatusResult: {
if (!isAuthenticated && passphraseModal && !passphraseModal.visible) {
passphraseModal.visible = true;
} else if (isAuthenticated) {
root.activeView = "walletHome";
}
}
@ -89,7 +92,8 @@ Rectangle {
if (msg.method === 'walletSetup_cancelClicked') {
walletSetupLightbox.visible = false;
} else if (msg.method === 'walletSetup_finished') {
root.activeView = "walletHome";
root.activeView = "initialize";
commerce.getLoginStatus();
} else if (msg.method === 'walletSetup_raiseKeyboard') {
root.keyboardRaised = true;
} else if (msg.method === 'walletSetup_lowerKeyboard') {
@ -218,6 +222,21 @@ Rectangle {
}
}
PassphraseModal {
id: passphraseModal;
visible: false;
anchors.top: titleBarContainer.bottom;
anchors.bottom: parent.bottom;
anchors.left: parent.left;
anchors.right: parent.right;
Connections {
onSendSignalToParent: {
sendToScript(msg);
}
}
}
NotSetUp {
id: notSetUp;
visible: root.activeView === "notSetUp";

View file

@ -38,7 +38,7 @@ Item {
}
onBalanceResult : {
balanceText.text = parseFloat(result.data.balance/100).toFixed(2);
balanceText.text = result.data.balance;
}
onHistoryResult : {
@ -88,13 +88,14 @@ Item {
height: 60;
Rectangle {
id: hfcBalanceField;
color: hifi.colors.darkGray;
anchors.right: parent.right;
anchors.left: parent.left;
anchors.bottom: parent.bottom;
height: parent.height - 15;
// "HFC" balance label
RalewayRegular {
FiraSansRegular {
id: balanceLabel;
text: "HFC";
// Text size
@ -106,7 +107,7 @@ Item {
anchors.rightMargin: 4;
width: paintedWidth;
// Style
color: hifi.colors.darkGray;
color: hifi.colors.lightGrayText;
// Alignment
horizontalAlignment: Text.AlignRight;
verticalAlignment: Text.AlignVCenter;
@ -121,7 +122,7 @@ Item {
}
// Balance Text
FiraSansRegular {
FiraSansSemiBold {
id: balanceText;
text: "--";
// Text size
@ -133,7 +134,7 @@ Item {
anchors.right: balanceLabel.left;
anchors.rightMargin: 4;
// Style
color: hifi.colors.darkGray;
color: hifi.colors.lightGrayText;
// Alignment
horizontalAlignment: Text.AlignRight;
verticalAlignment: Text.AlignVCenter;
@ -258,7 +259,7 @@ Item {
delegate: Item {
width: parent.width;
height: transactionText.height + 30;
RalewayRegular {
FiraSansRegular {
id: transactionText;
text: model.text;
// Style
@ -272,7 +273,7 @@ Item {
horizontalAlignment: Text.AlignLeft;
verticalAlignment: Text.AlignVCenter;
}
HifiControlsUit.Separator {
anchors.left: parent.left;
anchors.right: parent.right;
@ -288,7 +289,7 @@ Item {
}
// This should never be visible (since you immediately get 100 HFC)
RalewayRegular {
FiraSansRegular {
id: emptyTransationHistory;
size: 24;
visible: !transactionHistory.visible && root.historyReceived;

View file

@ -39,9 +39,9 @@ Rectangle {
}
}
onPassphraseSetupStatusResult: {
onWalletAuthenticatedStatusResult: {
securityImageContainer.visible = false;
if (passphraseIsSetup) {
if (isAuthenticated) {
privateKeysReadyContainer.visible = true;
} else {
choosePassphraseContainer.visible = true;
@ -117,7 +117,7 @@ Rectangle {
anchors.right: parent.right;
anchors.rightMargin: 16;
height: 280;
Connections {
onSendSignalToWallet: {
sendSignalToWallet(msg);
@ -210,7 +210,7 @@ Rectangle {
onVisibleChanged: {
if (visible) {
commerce.getPassphraseSetupStatus();
commerce.getWalletAuthenticatedStatus();
}
}

View file

@ -1,5 +1,6 @@
import QtQuick 2.5
import QtGraphicalEffects 1.0
import QtQuick.Layouts 1.3
import "../../styles-uit"
import "../audio" as HifiAudio
@ -109,15 +110,45 @@ Item {
}
}
RalewaySemiBold {
id: usernameText
text: tabletRoot.username
anchors.verticalCenter: parent.verticalCenter
Item {
width: 150
height: 50
anchors.right: parent.right
anchors.rightMargin: 20
horizontalAlignment: Text.AlignRight
font.pixelSize: 20
color: "#afafaf"
anchors.rightMargin: 30
anchors.verticalCenter: parent.verticalCenter
ColumnLayout {
anchors.fill: parent
RalewaySemiBold {
text: Account.loggedIn ? qsTr("Log out") : qsTr("Log in")
horizontalAlignment: Text.AlignRight
anchors.right: parent.right
font.pixelSize: 20
color: "#afafaf"
}
RalewaySemiBold {
visible: Account.loggedIn
height: Account.loggedIn ? parent.height/2 - parent.spacing/2 : 0
text: Account.loggedIn ? "[" + tabletRoot.usernameShort + "]" : ""
horizontalAlignment: Text.AlignRight
anchors.right: parent.right
font.pixelSize: 20
color: "#afafaf"
}
}
MouseArea {
anchors.fill: parent
onClicked: {
if (!Account.loggedIn) {
DialogsManager.showLoginDialog()
} else {
Account.logOut()
}
}
}
}
}

View file

@ -8,6 +8,7 @@ Item {
id: tabletRoot
objectName: "tabletRoot"
property string username: "Unknown user"
property string usernameShort: "Unknown user"
property var rootMenu;
property var openModal: null;
property var openMessage: null;
@ -157,6 +158,11 @@ Item {
function setUsername(newUsername) {
username = newUsername;
usernameShort = newUsername.substring(0, 8);
if (newUsername.length > 8) {
usernameShort = usernameShort + "..."
}
}
ListModel {

View file

@ -6337,11 +6337,11 @@ bool Application::askToWearAvatarAttachmentUrl(const QString& url) {
bool Application::askToReplaceDomainContent(const QString& url) {
QString methodDetails;
const int MAX_CHARACTERS_PER_LINE = 90;
if (DependencyManager::get<NodeList>()->getThisNodeCanReplaceContent()) {
QUrl originURL { url };
if (originURL.host().endsWith(MARKETPLACE_CDN_HOSTNAME)) {
// Create a confirmation dialog when this call is made
const int MAX_CHARACTERS_PER_LINE = 90;
static const QString infoText = simpleWordWrap("Your domain's content will be replaced with a new content set. "
"If you want to save what you have now, create a backup before proceeding. For more information about backing up "
"and restoring content, visit the documentation page at: ", MAX_CHARACTERS_PER_LINE) +
@ -6377,7 +6377,9 @@ bool Application::askToReplaceDomainContent(const QString& url) {
}
} else {
methodDetails = "UserDoesNotHavePermissionToReplaceContent";
OffscreenUi::warning("Unable to replace content", "You do not have permissions to replace domain content",
static const QString warningMessage = simpleWordWrap("The domain owner must enable 'Replace Content' "
"permissions for you in this domain's server settings before you can continue.", MAX_CHARACTERS_PER_LINE);
OffscreenUi::warning("You do not have permissions to replace domain content", warningMessage,
QMessageBox::Ok, QMessageBox::Ok);
}
QJsonObject messageProperties = {

View file

@ -1889,6 +1889,17 @@ void MyAvatar::preDisplaySide(RenderArgs* renderArgs) {
const bool shouldDrawHead = shouldRenderHead(renderArgs);
if (shouldDrawHead != _prevShouldDrawHead) {
_skeletonModel->setEnableCauterization(!shouldDrawHead);
for (int i = 0; i < _attachmentData.size(); i++) {
if (_attachmentData[i].jointName.compare("Head", Qt::CaseInsensitive) == 0 ||
_attachmentData[i].jointName.compare("Neck", Qt::CaseInsensitive) == 0 ||
_attachmentData[i].jointName.compare("LeftEye", Qt::CaseInsensitive) == 0 ||
_attachmentData[i].jointName.compare("RightEye", Qt::CaseInsensitive) == 0 ||
_attachmentData[i].jointName.compare("HeadTop_End", Qt::CaseInsensitive) == 0 ||
_attachmentData[i].jointName.compare("Face", Qt::CaseInsensitive) == 0) {
_attachmentModels[i]->setVisibleInScene(shouldDrawHead, qApp->getMain3DScene());
}
}
}
_prevShouldDrawHead = shouldDrawHead;
}

View file

@ -11,6 +11,7 @@
#include <QJsonObject>
#include <QJsonArray>
#include <QTimeZone>
#include <QJsonDocument>
#include "AccountManager.h"
#include "Wallet.h"
@ -45,7 +46,6 @@ Handler(buy)
Handler(receiveAt)
Handler(balance)
Handler(inventory)
Handler(history)
void Ledger::send(const QString& endpoint, const QString& success, const QString& fail, QNetworkAccessManager::Operation method, QJsonObject request) {
auto accountManager = DependencyManager::get<AccountManager>();
@ -108,6 +108,64 @@ void Ledger::inventory(const QStringList& keys) {
keysQuery("inventory", "inventorySuccess", "inventoryFailure");
}
QString nameFromKey(const QString& key, const QStringList& publicKeys) {
if (key.isNull() || key.isEmpty()) {
return "<b>Marketplace</b>";
}
if (publicKeys.contains(key)) {
return "You";
}
return "<b>Someone</b>";
}
void Ledger::historySuccess(QNetworkReply& reply) {
// here we send a historyResult with some extra stuff in it
// Namely, the styled text we'd like to show. The issue is the
// QML cannot do that easily since it doesn't know what the wallet
// public key(s) are. Let's keep it that way
QByteArray response = reply.readAll();
QJsonObject data = QJsonDocument::fromJson(response).object();
// we will need the array of public keys from the wallet
auto wallet = DependencyManager::get<Wallet>();
auto keys = wallet->listPublicKeys();
// now we need to loop through the transactions and add fancy text...
auto historyArray = data.find("data").value().toObject().find("history").value().toArray();
QJsonArray newHistoryArray;
// TODO: do this with 0 copies if possible
for(auto it = historyArray.begin(); it != historyArray.end(); it++) {
auto valueObject = (*it).toObject();
QString from = nameFromKey(valueObject["sender_key"].toString(), keys);
QString to = nameFromKey(valueObject["recipient_key"].toString(), keys);
// turns out on my machine, toLocalTime convert to some weird timezone, yet the
// systemTimeZone is correct. To avoid a strange bug with other's systems too, lets
// be explicit
#ifdef Q_OS_MAC
QDateTime createdAt = QDateTime::fromTime_t(valueObject["created_at"].toInt(), Qt::UTC);
#else
QDateTime createdAt = QDateTime::fromSecsSinceEpoch(valueObject["created_at"].toInt(), Qt::UTC);
#endif
QDateTime localCreatedAt = createdAt.toTimeZone(QTimeZone::systemTimeZone());
valueObject["text"] = QString("%1 sent %2 <b>%3 %4</b> on %5 with message \"%6\"").
arg(from, to, QString::number(valueObject["quantity"].toInt()), valueObject["asset_title"].toString(), localCreatedAt.toString(Qt::SystemLocaleShortDate), valueObject["message"].toString());
newHistoryArray.push_back(valueObject);
}
// now copy the rest of the json -- this is inefficient
// TODO: try to do this without making copies
QJsonObject newData;
newData["status"] = "success";
QJsonObject newDataData;
newDataData["history"] = newHistoryArray;
newData["data"] = newDataData;
emit historyResult(newData);
}
void Ledger::historyFailure(QNetworkReply& reply) {
failResponse("history", reply);
}
void Ledger::history(const QStringList& keys) {
keysQuery("history", "historySuccess", "historyFailure");
}
@ -117,4 +175,4 @@ void Ledger::resetSuccess(QNetworkReply& reply) { apiResponse("reset", reply); }
void Ledger::resetFailure(QNetworkReply& reply) { failResponse("reset", reply); }
void Ledger::reset() {
send("reset_user_hfc_account", "resetSuccess", "resetFailure", QNetworkAccessManager::PutOperation, QJsonObject());
}
}

View file

@ -29,6 +29,30 @@ QmlCommerce::QmlCommerce(QQuickItem* parent) : OffscreenQmlDialog(parent) {
connect(wallet.data(), &Wallet::keyFilePathIfExistsResult, this, &QmlCommerce::keyFilePathIfExistsResult);
}
void QmlCommerce::getLoginStatus() {
emit loginStatusResult(DependencyManager::get<AccountManager>()->isLoggedIn());
}
void QmlCommerce::getKeyFilePathIfExists() {
auto wallet = DependencyManager::get<Wallet>();
wallet->sendKeyFilePathIfExists();
}
void QmlCommerce::getWalletAuthenticatedStatus() {
auto wallet = DependencyManager::get<Wallet>();
emit walletAuthenticatedStatusResult(wallet->walletIsAuthenticatedWithPassphrase());
}
void QmlCommerce::getSecurityImage() {
auto wallet = DependencyManager::get<Wallet>();
wallet->getSecurityImage();
}
void QmlCommerce::chooseSecurityImage(const QString& imageFile) {
auto wallet = DependencyManager::get<Wallet>();
wallet->chooseSecurityImage(imageFile);
}
void QmlCommerce::buy(const QString& assetId, int cost, const QString& buyerUsername) {
auto ledger = DependencyManager::get<Ledger>();
auto wallet = DependencyManager::get<Wallet>();
@ -60,30 +84,14 @@ void QmlCommerce::history() {
ledger->history(wallet->listPublicKeys());
}
void QmlCommerce::chooseSecurityImage(const QString& imageFile) {
auto wallet = DependencyManager::get<Wallet>();
wallet->chooseSecurityImage(imageFile);
}
void QmlCommerce::getSecurityImage() {
auto wallet = DependencyManager::get<Wallet>();
wallet->getSecurityImage();
}
void QmlCommerce::getLoginStatus() {
emit loginStatusResult(DependencyManager::get<AccountManager>()->isLoggedIn());
}
void QmlCommerce::setPassphrase(const QString& passphrase) {
emit passphraseSetupStatusResult(true);
}
void QmlCommerce::getPassphraseSetupStatus() {
emit passphraseSetupStatusResult(false);
}
void QmlCommerce::getKeyFilePathIfExists() {
auto wallet = DependencyManager::get<Wallet>();
wallet->sendKeyFilePathIfExists();
if (wallet->getPassphrase() && !wallet->getPassphrase()->isEmpty()) {
wallet->changePassphrase(passphrase);
} else {
wallet->setPassphrase(passphrase);
}
getWalletAuthenticatedStatus();
}
void QmlCommerce::reset() {
@ -91,4 +99,4 @@ void QmlCommerce::reset() {
auto wallet = DependencyManager::get<Wallet>();
ledger->reset();
wallet->reset();
}
}

View file

@ -28,28 +28,32 @@ public:
QmlCommerce(QQuickItem* parent = nullptr);
signals:
void loginStatusResult(bool isLoggedIn);
void keyFilePathIfExistsResult(const QString& path);
void securityImageResult(bool exists);
void walletAuthenticatedStatusResult(bool isAuthenticated);
void buyResult(QJsonObject result);
// Balance and Inventory are NOT properties, because QML can't change them (without risk of failure), and
// because we can't scalably know of out-of-band changes (e.g., another machine interacting with the block chain).
void balanceResult(QJsonObject result);
void inventoryResult(QJsonObject result);
void securityImageResult(bool exists);
void loginStatusResult(bool isLoggedIn);
void passphraseSetupStatusResult(bool passphraseIsSetup);
void historyResult(QJsonObject result);
void keyFilePathIfExistsResult(const QString& path);
protected:
Q_INVOKABLE void getLoginStatus();
Q_INVOKABLE void getKeyFilePathIfExists();
Q_INVOKABLE void getSecurityImage();
Q_INVOKABLE void getWalletAuthenticatedStatus();
Q_INVOKABLE void chooseSecurityImage(const QString& imageFile);
Q_INVOKABLE void setPassphrase(const QString& passphrase);
Q_INVOKABLE void buy(const QString& assetId, int cost, const QString& buyerUsername = "");
Q_INVOKABLE void balance();
Q_INVOKABLE void inventory();
Q_INVOKABLE void history();
Q_INVOKABLE void chooseSecurityImage(const QString& imageFile);
Q_INVOKABLE void getSecurityImage();
Q_INVOKABLE void getLoginStatus();
Q_INVOKABLE void setPassphrase(const QString& passphrase);
Q_INVOKABLE void getPassphraseSetupStatus();
Q_INVOKABLE void getKeyFilePathIfExists();
Q_INVOKABLE void reset();
};

View file

@ -56,16 +56,72 @@ QString imageFilePath() {
int passwordCallback(char* password, int maxPasswordSize, int rwFlag, void* u) {
// just return a hardcoded pwd for now
auto passphrase = DependencyManager::get<Wallet>()->getPassphrase();
if (passphrase) {
if (passphrase && !passphrase->isEmpty()) {
strcpy(password, passphrase->toLocal8Bit().constData());
return static_cast<int>(passphrase->size());
} else {
// ok gotta bring up modal dialog... But right now lets just
// just keep it empty
// this shouldn't happen - so lets log it to tell us we have
// a problem with the flow...
qCCritical(commerce) << "no cached passphrase while decrypting!";
return 0;
}
}
RSA* readKeys(const char* filename) {
FILE* fp;
RSA* key = NULL;
if ((fp = fopen(filename, "rt"))) {
// file opened successfully
qCDebug(commerce) << "opened key file" << filename;
if ((key = PEM_read_RSAPublicKey(fp, NULL, NULL, NULL))) {
// now read private key
qCDebug(commerce) << "read public key";
if ((key = PEM_read_RSAPrivateKey(fp, &key, passwordCallback, NULL))) {
qCDebug(commerce) << "read private key";
fclose(fp);
return key;
}
qCDebug(commerce) << "failed to read private key";
} else {
qCDebug(commerce) << "failed to read public key";
}
fclose(fp);
} else {
qCDebug(commerce) << "failed to open key file" << filename;
}
return key;
}
bool writeKeys(const char* filename, RSA* keys) {
FILE* fp;
bool retval = false;
if ((fp = fopen(filename, "wt"))) {
if (!PEM_write_RSAPublicKey(fp, keys)) {
fclose(fp);
qCDebug(commerce) << "failed to write public key";
QFile(QString(filename)).remove();
return retval;
}
if (!PEM_write_RSAPrivateKey(fp, keys, EVP_des_ede3_cbc(), NULL, 0, passwordCallback, NULL)) {
fclose(fp);
qCDebug(commerce) << "failed to write private key";
QFile(QString(filename)).remove();
return retval;
}
retval = true;
qCDebug(commerce) << "wrote keys successfully";
fclose(fp);
} else {
qCDebug(commerce) << "failed to open key file" << filename;
}
return retval;
}
// BEGIN copied code - this will be removed/changed at some point soon
// copied (without emits for various signals) from libraries/networking/src/RSAKeypairGenerator.cpp.
// We will have a different implementation in practice, but this gives us a start for now
@ -124,25 +180,9 @@ QPair<QByteArray*, QByteArray*> generateRSAKeypair() {
}
// now lets persist them to files
// FIXME: for now I'm appending to the file if it exists. As long as we always put
// the keys in the same order, this works fine. TODO: verify this will skip over
// anything else (like an embedded image)
FILE* fp;
if ((fp = fopen(keyFilePath().toStdString().c_str(), "at"))) {
if (!PEM_write_RSAPublicKey(fp, keyPair)) {
fclose(fp);
qCDebug(commerce) << "failed to write public key";
return retval;
}
if (!PEM_write_RSAPrivateKey(fp, keyPair, EVP_des_ede3_cbc(), NULL, 0, passwordCallback, NULL)) {
fclose(fp);
qCDebug(commerce) << "failed to write private key";
return retval;
}
fclose(fp);
if (!writeKeys(keyFilePath().toStdString().c_str(), keyPair)) {
qCDebug(commerce) << "couldn't save keys!";
return retval;
}
RSA_free(keyPair);
@ -201,9 +241,6 @@ RSA* readPrivateKey(const char* filename) {
// file opened successfully
qCDebug(commerce) << "opened key file" << filename;
if ((key = PEM_read_RSAPrivateKey(fp, &key, passwordCallback, NULL))) {
// cleanup
fclose(fp);
qCDebug(commerce) << "parsed private key file successfully";
} else {
@ -215,7 +252,6 @@ RSA* readPrivateKey(const char* filename) {
}
return key;
}
static const unsigned char IVEC[16] = "IAmAnIVecYay123";
void initializeAESKeys(unsigned char* ivec, unsigned char* ckey, const QByteArray& salt) {
@ -236,6 +272,10 @@ void Wallet::setPassphrase(const QString& passphrase) {
delete _passphrase;
}
_passphrase = new QString(passphrase);
// no matter what, we now need to clear the keys as they
// need to be read using this passphrase
_publicKeys.clear();
}
// encrypt some stuff
@ -341,6 +381,38 @@ bool Wallet::decryptFile(const QString& inputFilePath, unsigned char** outputBuf
return true;
}
bool Wallet::walletIsAuthenticatedWithPassphrase() {
// try to read existing keys if they exist...
// FIXME: initialize OpenSSL elsewhere soon
initialize();
// this should always be false if we don't have a passphrase
// cached yet
if (!_passphrase || _passphrase->isEmpty()) {
return false;
}
if (_publicKeys.count() > 0) {
// we _must_ be authenticated if the publicKeys are there
return true;
}
// otherwise, we have a passphrase but no keys, so we have to check
auto publicKey = readPublicKey(keyFilePath().toStdString().c_str());
if (publicKey.size() > 0) {
if (auto key = readPrivateKey(keyFilePath().toStdString().c_str())) {
RSA_free(key);
// be sure to add the public key so we don't do this over and over
_publicKeys.push_back(publicKey.toBase64());
return true;
}
}
return false;
}
bool Wallet::createIfNeeded() {
if (_publicKeys.count() > 0) return false;
@ -512,3 +584,30 @@ void Wallet::reset() {
keyFile.remove();
imageFile.remove();
}
bool Wallet::changePassphrase(const QString& newPassphrase) {
qCDebug(commerce) << "changing passphrase";
RSA* keys = readKeys(keyFilePath().toStdString().c_str());
if (keys) {
// we read successfully, so now write to a new temp file
// save old passphrase just in case
// TODO: force re-enter?
QString oldPassphrase = *_passphrase;
setPassphrase(newPassphrase);
QString tempFileName = QString("%1.%2").arg(keyFilePath(), QString("temp"));
if (writeKeys(tempFileName.toStdString().c_str(), keys)) {
// ok, now move the temp file to the correct spot
QFile(QString(keyFilePath())).remove();
QFile(tempFileName).rename(QString(keyFilePath()));
qCDebug(commerce) << "passphrase changed successfully";
return true;
} else {
qCDebug(commerce) << "couldn't write keys";
QFile(tempFileName).remove();
setPassphrase(oldPassphrase);
return false;
}
}
qCDebug(commerce) << "couldn't read keys";
return false;
}

View file

@ -39,18 +39,21 @@ public:
void setPassphrase(const QString& passphrase);
QString* getPassphrase() { return _passphrase; }
bool getPassphraseIsCached() { return !(_passphrase->isEmpty()); }
bool walletIsAuthenticatedWithPassphrase();
bool changePassphrase(const QString& newPassphrase);
void reset();
signals:
void securityImageResult(bool exists) ;
void securityImageResult(bool exists);
void keyFilePathIfExistsResult(const QString& path);
private:
QStringList _publicKeys{};
QPixmap* _securityImage { nullptr };
QByteArray _salt {"iamsalt!"};
QString* _passphrase { new QString("pwd") };
QString* _passphrase { new QString("") };
void updateImageProvider();
bool encryptFile(const QString& inputFilePath, const QString& outputFilePath);

View file

@ -18,6 +18,8 @@ AccountScriptingInterface* AccountScriptingInterface::getInstance() {
auto accountManager = DependencyManager::get<AccountManager>();
QObject::connect(accountManager.data(), &AccountManager::profileChanged,
&sharedInstance, &AccountScriptingInterface::usernameChanged);
QObject::connect(accountManager.data(), &AccountManager::usernameChanged,
&sharedInstance, &AccountScriptingInterface::onUsernameChanged);
return &sharedInstance;
}
@ -31,6 +33,21 @@ bool AccountScriptingInterface::checkAndSignalForAccessToken() {
return accountManager->checkAndSignalForAccessToken();
}
void AccountScriptingInterface::logOut() {
auto accountManager = DependencyManager::get<AccountManager>();
return accountManager->logout();
}
AccountScriptingInterface::AccountScriptingInterface(QObject *parent): QObject(parent) {
m_loggedIn = isLoggedIn();
emit loggedInChanged(m_loggedIn);
}
void AccountScriptingInterface::onUsernameChanged(QString username) {
m_loggedIn = (username != QString());
emit loggedInChanged(m_loggedIn);
}
QString AccountScriptingInterface::getUsername() {
auto accountManager = DependencyManager::get<AccountManager>();
if (accountManager->isLoggedIn()) {

View file

@ -18,6 +18,7 @@ class AccountScriptingInterface : public QObject {
Q_OBJECT
Q_PROPERTY(QString username READ getUsername NOTIFY usernameChanged)
Q_PROPERTY(bool loggedIn READ loggedIn NOTIFY loggedInChanged)
/**jsdoc
* @namespace Account
@ -32,6 +33,7 @@ signals:
* @return {Signal}
*/
void usernameChanged();
void loggedInChanged(bool loggedIn);
public slots:
static AccountScriptingInterface* getInstance();
@ -50,6 +52,20 @@ public slots:
*/
bool isLoggedIn();
bool checkAndSignalForAccessToken();
void logOut();
public:
AccountScriptingInterface(QObject* parent = nullptr);
bool loggedIn() const {
return m_loggedIn;
}
private slots:
void onUsernameChanged(QString username);
private:
bool m_loggedIn { false };
};
#endif // hifi_AccountScriptingInterface_h

View file

@ -33,7 +33,12 @@ void DialogsManagerScriptingInterface::showAddressBar() {
void DialogsManagerScriptingInterface::hideAddressBar() {
QMetaObject::invokeMethod(DependencyManager::get<DialogsManager>().data(),
"hideAddressBar", Qt::QueuedConnection);
"hideAddressBar", Qt::QueuedConnection);
}
void DialogsManagerScriptingInterface::showLoginDialog() {
QMetaObject::invokeMethod(DependencyManager::get<DialogsManager>().data(),
"showLoginDialog", Qt::QueuedConnection);
}
void DialogsManagerScriptingInterface::showFeed() {

View file

@ -24,6 +24,7 @@ public:
public slots:
void showAddressBar();
void hideAddressBar();
void showLoginDialog();
signals:
void addressBarShown(bool visible);

View file

@ -910,6 +910,8 @@ void ModelEntityRenderer::animate(const TypedEntityPointer& entity) {
const QVector<FBXAnimationFrame>& frames = _animation->getFramesReference(); // NOTE: getFrames() is too heavy
auto& fbxJoints = _animation->getGeometry().joints;
auto& originalFbxJoints = _model->getFBXGeometry().joints;
bool allowTranslation = entity->getAnimationAllowTranslation();
int frameCount = frames.size();
if (frameCount <= 0) {
return;
@ -952,7 +954,9 @@ void ModelEntityRenderer::animate(const TypedEntityPointer& entity) {
int index = _jointMapping[j];
if (index >= 0) {
glm::mat4 translationMat;
if (index < translations.size()) {
if (!allowTranslation){
translationMat = glm::translate(originalFbxJoints[index].translation);
} else if (index < translations.size()) {
translationMat = glm::translate(translations[index]);
}
glm::mat4 rotationMat;

View file

@ -33,6 +33,7 @@ bool operator==(const AnimationPropertyGroup& a, const AnimationPropertyGroup& b
void AnimationPropertyGroup::copyToScriptValue(const EntityPropertyFlags& desiredProperties, QScriptValue& properties, QScriptEngine* engine, bool skipDefaults, EntityItemProperties& defaultEntityProperties) const {
COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_ANIMATION_URL, Animation, animation, URL, url);
COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_ANIMATION_ALLOW_TRANSLATION, Animation, animation, AllowTranslation, allowTranslation);
COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_ANIMATION_FPS, Animation, animation, FPS, fps);
COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_ANIMATION_FRAME_INDEX, Animation, animation, CurrentFrame, currentFrame);
COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_ANIMATION_PLAYING, Animation, animation, Running, running);
@ -46,6 +47,7 @@ void AnimationPropertyGroup::copyToScriptValue(const EntityPropertyFlags& desire
void AnimationPropertyGroup::copyFromScriptValue(const QScriptValue& object, bool& _defaultSettings) {
COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(animation, url, QString, setURL);
COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(animation, allowTranslation, bool, setAllowTranslation);
// legacy property support
COPY_PROPERTY_FROM_QSCRIPTVALUE_GETTER(animationURL, QString, setURL, getURL);
@ -67,6 +69,7 @@ void AnimationPropertyGroup::copyFromScriptValue(const QScriptValue& object, boo
void AnimationPropertyGroup::merge(const AnimationPropertyGroup& other) {
COPY_PROPERTY_IF_CHANGED(url);
COPY_PROPERTY_IF_CHANGED(allowTranslation);
COPY_PROPERTY_IF_CHANGED(fps);
COPY_PROPERTY_IF_CHANGED(currentFrame);
COPY_PROPERTY_IF_CHANGED(running);
@ -88,6 +91,7 @@ void AnimationPropertyGroup::setFromOldAnimationSettings(const QString& value) {
float lastFrame = getLastFrame();
bool loop = getLoop();
bool hold = getHold();
bool allowTranslation = getAllowTranslation();
QJsonDocument settingsAsJson = QJsonDocument::fromJson(value.toUtf8());
QJsonObject settingsAsJsonObject = settingsAsJson.object();
@ -122,6 +126,11 @@ void AnimationPropertyGroup::setFromOldAnimationSettings(const QString& value) {
running = settingsMap["hold"].toBool();
}
if (settingsMap.contains("allowTranslation")) {
allowTranslation = settingsMap["allowTranslation"].toBool();
}
setAllowTranslation(allowTranslation);
setFPS(fps);
setCurrentFrame(currentFrame);
setRunning(running);
@ -137,6 +146,7 @@ void AnimationPropertyGroup::debugDump() const {
qCDebug(entities) << " url:" << getURL() << " has changed:" << urlChanged();
qCDebug(entities) << " fps:" << getFPS() << " has changed:" << fpsChanged();
qCDebug(entities) << "currentFrame:" << getCurrentFrame() << " has changed:" << currentFrameChanged();
qCDebug(entities) << "allowTranslation:" << getAllowTranslation() << " has changed:" << allowTranslationChanged();
}
void AnimationPropertyGroup::listChangedProperties(QList<QString>& out) {
@ -149,6 +159,9 @@ void AnimationPropertyGroup::listChangedProperties(QList<QString>& out) {
if (currentFrameChanged()) {
out << "animation-currentFrame";
}
if (allowTranslationChanged()) {
out << "animation-allowTranslation";
}
}
@ -162,6 +175,7 @@ bool AnimationPropertyGroup::appendToEditPacket(OctreePacketData* packetData,
bool successPropertyFits = true;
APPEND_ENTITY_PROPERTY(PROP_ANIMATION_URL, getURL());
APPEND_ENTITY_PROPERTY(PROP_ANIMATION_ALLOW_TRANSLATION, getAllowTranslation());
APPEND_ENTITY_PROPERTY(PROP_ANIMATION_FPS, getFPS());
APPEND_ENTITY_PROPERTY(PROP_ANIMATION_FRAME_INDEX, getCurrentFrame());
APPEND_ENTITY_PROPERTY(PROP_ANIMATION_PLAYING, getRunning());
@ -181,6 +195,7 @@ bool AnimationPropertyGroup::decodeFromEditPacket(EntityPropertyFlags& propertyF
bool somethingChanged = false;
READ_ENTITY_PROPERTY(PROP_ANIMATION_URL, QString, setURL);
READ_ENTITY_PROPERTY(PROP_ANIMATION_ALLOW_TRANSLATION, bool, setAllowTranslation);
READ_ENTITY_PROPERTY(PROP_ANIMATION_FPS, float, setFPS);
READ_ENTITY_PROPERTY(PROP_ANIMATION_FRAME_INDEX, float, setCurrentFrame);
@ -198,7 +213,8 @@ bool AnimationPropertyGroup::decodeFromEditPacket(EntityPropertyFlags& propertyF
DECODE_GROUP_PROPERTY_HAS_CHANGED(PROP_ANIMATION_FIRST_FRAME, FirstFrame);
DECODE_GROUP_PROPERTY_HAS_CHANGED(PROP_ANIMATION_LAST_FRAME, LastFrame);
DECODE_GROUP_PROPERTY_HAS_CHANGED(PROP_ANIMATION_HOLD, Hold);
DECODE_GROUP_PROPERTY_HAS_CHANGED(PROP_ANIMATION_ALLOW_TRANSLATION, AllowTranslation);
processedBytes += bytesRead;
Q_UNUSED(somethingChanged);
@ -215,6 +231,7 @@ void AnimationPropertyGroup::markAllChanged() {
_firstFrameChanged = true;
_lastFrameChanged = true;
_holdChanged = true;
_allowTranslationChanged = true;
}
EntityPropertyFlags AnimationPropertyGroup::getChangedProperties() const {
@ -228,12 +245,14 @@ EntityPropertyFlags AnimationPropertyGroup::getChangedProperties() const {
CHECK_PROPERTY_CHANGE(PROP_ANIMATION_FIRST_FRAME, firstFrame);
CHECK_PROPERTY_CHANGE(PROP_ANIMATION_LAST_FRAME, lastFrame);
CHECK_PROPERTY_CHANGE(PROP_ANIMATION_HOLD, hold);
CHECK_PROPERTY_CHANGE(PROP_ANIMATION_ALLOW_TRANSLATION, allowTranslation);
return changedProperties;
}
void AnimationPropertyGroup::getProperties(EntityItemProperties& properties) const {
COPY_ENTITY_GROUP_PROPERTY_TO_PROPERTIES(Animation, URL, getURL);
COPY_ENTITY_GROUP_PROPERTY_TO_PROPERTIES(Animation, AllowTranslation, getAllowTranslation);
COPY_ENTITY_GROUP_PROPERTY_TO_PROPERTIES(Animation, FPS, getFPS);
COPY_ENTITY_GROUP_PROPERTY_TO_PROPERTIES(Animation, CurrentFrame, getCurrentFrame);
COPY_ENTITY_GROUP_PROPERTY_TO_PROPERTIES(Animation, Running, getRunning);
@ -247,6 +266,7 @@ bool AnimationPropertyGroup::setProperties(const EntityItemProperties& propertie
bool somethingChanged = false;
SET_ENTITY_GROUP_PROPERTY_FROM_PROPERTIES(Animation, URL, url, setURL);
SET_ENTITY_GROUP_PROPERTY_FROM_PROPERTIES(Animation, AllowTranslation, allowTranslation, setAllowTranslation);
SET_ENTITY_GROUP_PROPERTY_FROM_PROPERTIES(Animation, FPS, fps, setFPS);
SET_ENTITY_GROUP_PROPERTY_FROM_PROPERTIES(Animation, CurrentFrame, currentFrame, setCurrentFrame);
SET_ENTITY_GROUP_PROPERTY_FROM_PROPERTIES(Animation, Running, running, setRunning);
@ -268,6 +288,7 @@ EntityPropertyFlags AnimationPropertyGroup::getEntityProperties(EncodeBitstreamP
requestedProperties += PROP_ANIMATION_FIRST_FRAME;
requestedProperties += PROP_ANIMATION_LAST_FRAME;
requestedProperties += PROP_ANIMATION_HOLD;
requestedProperties += PROP_ANIMATION_ALLOW_TRANSLATION;
return requestedProperties;
}
@ -283,6 +304,7 @@ void AnimationPropertyGroup::appendSubclassData(OctreePacketData* packetData, En
bool successPropertyFits = true;
APPEND_ENTITY_PROPERTY(PROP_ANIMATION_URL, getURL());
APPEND_ENTITY_PROPERTY(PROP_ANIMATION_ALLOW_TRANSLATION, getAllowTranslation());
APPEND_ENTITY_PROPERTY(PROP_ANIMATION_FPS, getFPS());
APPEND_ENTITY_PROPERTY(PROP_ANIMATION_FRAME_INDEX, getCurrentFrame());
APPEND_ENTITY_PROPERTY(PROP_ANIMATION_PLAYING, getRunning());
@ -301,6 +323,7 @@ int AnimationPropertyGroup::readEntitySubclassDataFromBuffer(const unsigned char
const unsigned char* dataAt = data;
READ_ENTITY_PROPERTY(PROP_ANIMATION_URL, QString, setURL);
READ_ENTITY_PROPERTY(PROP_ANIMATION_ALLOW_TRANSLATION, bool, setAllowTranslation);
READ_ENTITY_PROPERTY(PROP_ANIMATION_FPS, float, setFPS);
READ_ENTITY_PROPERTY(PROP_ANIMATION_FRAME_INDEX, float, setCurrentFrame);
READ_ENTITY_PROPERTY(PROP_ANIMATION_PLAYING, bool, setRunning);

View file

@ -85,6 +85,7 @@ public:
DEFINE_PROPERTY(PROP_ANIMATION_FIRST_FRAME, FirstFrame, firstFrame, float, 0.0f); // was animationSettings.firstFrame
DEFINE_PROPERTY(PROP_ANIMATION_LAST_FRAME, LastFrame, lastFrame, float, MAXIMUM_POSSIBLE_FRAME); // was animationSettings.lastFrame
DEFINE_PROPERTY(PROP_ANIMATION_HOLD, Hold, hold, bool, false); // was animationSettings.hold
DEFINE_PROPERTY(PROP_ANIMATION_ALLOW_TRANSLATION, AllowTranslation, allowTranslation, bool, true);
protected:
friend bool operator==(const AnimationPropertyGroup& a, const AnimationPropertyGroup& b);

View file

@ -1033,6 +1033,7 @@ void EntityItemProperties::entityPropertyFlagsFromScriptValue(const QScriptValue
ADD_GROUP_PROPERTY_TO_MAP(PROP_ANIMATION_FIRST_FRAME, Animation, animation, FirstFrame, firstFrame);
ADD_GROUP_PROPERTY_TO_MAP(PROP_ANIMATION_LAST_FRAME, Animation, animation, LastFrame, lastFrame);
ADD_GROUP_PROPERTY_TO_MAP(PROP_ANIMATION_HOLD, Animation, animation, Hold, hold);
ADD_GROUP_PROPERTY_TO_MAP(PROP_ANIMATION_ALLOW_TRANSLATION, Animation, animation, AllowTranslation, allowTranslation);
ADD_GROUP_PROPERTY_TO_MAP(PROP_SKYBOX_COLOR, Skybox, skybox, Color, color);
ADD_GROUP_PROPERTY_TO_MAP(PROP_SKYBOX_URL, Skybox, skybox, URL, url);

View file

@ -40,6 +40,7 @@ enum EntityPropertyList {
PROP_ANIMATION_FPS,
PROP_ANIMATION_FRAME_INDEX,
PROP_ANIMATION_PLAYING,
PROP_ANIMATION_ALLOW_TRANSLATION,
// these properties are supported by the EntityItem base class
PROP_REGISTRATION_POINT,

View file

@ -330,6 +330,10 @@ void ModelEntityItem::setAnimationSettings(const QString& value) {
setAnimationHold(hold);
}
if (settingsMap.contains("allowTranslation")) {
bool allowTranslation = settingsMap["allowTranslation"].toBool();
setAnimationAllowTranslation(allowTranslation);
}
_dirtyFlags |= Simulation::DIRTY_UPDATEABLE;
}

View file

@ -83,7 +83,10 @@ public:
void setAnimationCurrentFrame(float value);
void setAnimationIsPlaying(bool value);
void setAnimationFPS(float value);
void setAnimationFPS(float value);
void setAnimationAllowTranslation(bool value) { _animationProperties.setAllowTranslation(value); };
bool getAnimationAllowTranslation() const { return _animationProperties.getAllowTranslation(); };
void setAnimationLoop(bool loop);
bool getAnimationLoop() const;

View file

@ -30,7 +30,7 @@ PacketVersion versionForPacketType(PacketType packetType) {
case PacketType::EntityEdit:
case PacketType::EntityData:
case PacketType::EntityPhysics:
return VERSION_ENTITIES_HAS_HIGHLIGHT_SCRIPTING_INTERFACE;
return VERSION_ENTITIES_ANIMATION_ALLOW_TRANSLATION_PROPERTIES;
case PacketType::EntityQuery:
return static_cast<PacketVersion>(EntityQueryPacketVersion::JSONFilterWithFamilyTree);
case PacketType::AvatarIdentity:

View file

@ -260,6 +260,7 @@ const PacketVersion VERSION_ENTITIES_HINGE_CONSTRAINT = 69;
const PacketVersion VERSION_ENTITIES_BULLET_DYNAMICS = 70;
const PacketVersion VERSION_ENTITIES_HAS_SHOULD_HIGHLIGHT = 71;
const PacketVersion VERSION_ENTITIES_HAS_HIGHLIGHT_SCRIPTING_INTERFACE = 72;
const PacketVersion VERSION_ENTITIES_ANIMATION_ALLOW_TRANSLATION_PROPERTIES = 73;
enum class EntityQueryPacketVersion: PacketVersion {
JSONFilter = 18,

View file

@ -92,6 +92,7 @@ void EntityMotionState::updateServerPhysicsVariables() {
Transform localTransform;
_entity->getLocalTransformAndVelocities(localTransform, _serverVelocity, _serverAngularVelocity);
_serverVariablesSet = true;
_serverPosition = localTransform.getTranslation();
_serverRotation = localTransform.getRotation();
_serverAcceleration = _entity->getAcceleration();
@ -99,18 +100,19 @@ void EntityMotionState::updateServerPhysicsVariables() {
}
void EntityMotionState::handleDeactivation() {
// copy _server data to entity
bool success;
_entity->setPosition(_serverPosition, success, false);
_entity->setOrientation(_serverRotation, success, false);
_entity->setVelocity(ENTITY_ITEM_ZERO_VEC3);
_entity->setAngularVelocity(ENTITY_ITEM_ZERO_VEC3);
// and also to RigidBody
btTransform worldTrans;
worldTrans.setOrigin(glmToBullet(_serverPosition));
worldTrans.setRotation(glmToBullet(_serverRotation));
_body->setWorldTransform(worldTrans);
// no need to update velocities... should already be zero
if (_serverVariablesSet) {
// copy _server data to entity
Transform localTransform = _entity->getLocalTransform();
localTransform.setTranslation(_serverPosition);
localTransform.setRotation(_serverRotation);
_entity->setLocalTransformAndVelocities(localTransform, ENTITY_ITEM_ZERO_VEC3, ENTITY_ITEM_ZERO_VEC3);
// and also to RigidBody
btTransform worldTrans;
worldTrans.setOrigin(glmToBullet(_entity->getPosition()));
worldTrans.setRotation(glmToBullet(_entity->getRotation()));
_body->setWorldTransform(worldTrans);
// no need to update velocities... should already be zero
}
}
// virtual
@ -339,6 +341,7 @@ bool EntityMotionState::remoteSimulationOutOfSync(uint32_t simulationStep) {
// if we've never checked before, our _lastStep will be 0, and we need to initialize our state
if (_lastStep == 0) {
btTransform xform = _body->getWorldTransform();
_serverVariablesSet = true;
_serverPosition = worldToLocal.transform(bulletToGLM(xform.getOrigin()));
_serverRotation = worldToLocal.getRotation() * bulletToGLM(xform.getRotation());
_serverVelocity = worldVelocityToLocal.transform(getBodyLinearVelocityGTSigma());

View file

@ -107,6 +107,7 @@ protected:
// Meanwhile we also keep a raw EntityItem* for internal stuff where the pointer is guaranteed valid.
EntityItem* _entity;
bool _serverVariablesSet { false };
glm::vec3 _serverPosition; // in simulation-frame (not world-frame)
glm::quat _serverRotation;
glm::vec3 _serverVelocity;

View file

@ -827,10 +827,10 @@ AnimationDetails::AnimationDetails() :
}
AnimationDetails::AnimationDetails(QString role, QUrl url, float fps, float priority, bool loop,
bool hold, bool startAutomatically, float firstFrame, float lastFrame, bool running, float currentFrame) :
bool hold, bool startAutomatically, float firstFrame, float lastFrame, bool running, float currentFrame, bool allowTranslation) :
role(role), url(url), fps(fps), priority(priority), loop(loop), hold(hold),
startAutomatically(startAutomatically), firstFrame(firstFrame), lastFrame(lastFrame),
running(running), currentFrame(currentFrame) {
running(running), currentFrame(currentFrame), allowTranslation(allowTranslation) {
}
@ -847,6 +847,7 @@ QScriptValue animationDetailsToScriptValue(QScriptEngine* engine, const Animatio
obj.setProperty("lastFrame", details.lastFrame);
obj.setProperty("running", details.running);
obj.setProperty("currentFrame", details.currentFrame);
obj.setProperty("allowTranslation", details.allowTranslation);
return obj;
}

View file

@ -197,7 +197,7 @@ class AnimationDetails {
public:
AnimationDetails();
AnimationDetails(QString role, QUrl url, float fps, float priority, bool loop,
bool hold, bool startAutomatically, float firstFrame, float lastFrame, bool running, float currentFrame);
bool hold, bool startAutomatically, float firstFrame, float lastFrame, bool running, float currentFrame, bool allowTranslation);
QString role;
QUrl url;
@ -210,6 +210,7 @@ public:
float lastFrame;
bool running;
float currentFrame;
bool allowTranslation;
};
Q_DECLARE_METATYPE(AnimationDetails);
QScriptValue animationDetailsToScriptValue(QScriptEngine* engine, const AnimationDetails& event);

View file

@ -706,7 +706,7 @@ void SpatiallyNestable::setSNScale(const glm::vec3& scale, bool& success) {
}
}
const Transform SpatiallyNestable::getLocalTransform() const {
Transform SpatiallyNestable::getLocalTransform() const {
Transform result;
_transformLock.withReadLock([&] {
result =_transform;

View file

@ -122,7 +122,7 @@ public:
virtual glm::vec3 getSNScale(int jointIndex, bool& success) const;
// object's parent's frame
virtual const Transform getLocalTransform() const;
virtual Transform getLocalTransform() const;
virtual void setLocalTransform(const Transform& transform);
virtual glm::vec3 getLocalPosition() const;

View file

@ -56,6 +56,7 @@
var isHmdPreviewDisabled = true;
function fromQml(message) {
switch (message.method) {
case 'passphrasePopup_cancelClicked':
case 'walletSetup_cancelClicked':
case 'needsLogIn_cancelClicked':
tablet.gotoHomeScreen();

View file

@ -446,6 +446,10 @@
<input type="checkbox" id="property-model-animation-hold">
<label for="property-model-animation-hold">Animation hold</label>
</div>
<div class="property checkbox indent">
<input type="checkbox" id="property-model-animation-allow-translation">
<label for="property-model-animation-allow-translation">Animation Allow Translation</label>
</div>
<div id="animation-fps" class="property number">
<label>Animation FPS</label>
<input type="number" id="property-model-animation-fps">
@ -642,10 +646,6 @@
</div>
</fieldset>
<fieldset id="polyvox" class="major">
<legend class="section-header spatial-group poly-vox-section property xyz">
Voxel volume size <span>m</span>

View file

@ -611,6 +611,7 @@ function loaded() {
var elModelAnimationLastFrame = document.getElementById("property-model-animation-last-frame");
var elModelAnimationLoop = document.getElementById("property-model-animation-loop");
var elModelAnimationHold = document.getElementById("property-model-animation-hold");
var elModelAnimationAllowTranslation = document.getElementById("property-model-animation-allow-translation");
var elModelTextures = document.getElementById("property-model-textures");
var elModelOriginalTextures = document.getElementById("property-model-original-textures");
@ -926,6 +927,7 @@ function loaded() {
elModelAnimationLastFrame.value = properties.animation.lastFrame;
elModelAnimationLoop.checked = properties.animation.loop;
elModelAnimationHold.checked = properties.animation.hold;
elModelAnimationAllowTranslation.checked = properties.animation.allowTranslation;
elModelTextures.value = properties.textures;
setTextareaScrolling(elModelTextures);
elModelOriginalTextures.value = properties.originalTextures;
@ -1276,6 +1278,7 @@ function loaded() {
elModelAnimationLastFrame.addEventListener('change', createEmitGroupNumberPropertyUpdateFunction('animation', 'lastFrame'));
elModelAnimationLoop.addEventListener('change', createEmitGroupCheckedPropertyUpdateFunction('animation', 'loop'));
elModelAnimationHold.addEventListener('change', createEmitGroupCheckedPropertyUpdateFunction('animation', 'hold'));
elModelAnimationAllowTranslation.addEventListener('change', createEmitGroupCheckedPropertyUpdateFunction('animation', 'allowTranslation'));
elModelTextures.addEventListener('change', createEmitTextPropertyUpdateFunction('textures'));

View file

@ -27,7 +27,7 @@
var isPreparing = false; // Explicitly track download request status.
var confirmAllPurchases = false; // Set this to "true" to cause Checkout.qml to popup for all items, even if free
function injectCommonCode(isDirectoryPage) {
// Supporting styles from marketplaces.css.
@ -122,7 +122,7 @@
function injectBuyButtonOnMainPage() {
var cost;
$('.grid-item').find('#price-or-edit').find('a').each(function() {
$(this).attr('data-href', $(this).attr('href'));
$(this).attr('href', '#');
@ -130,16 +130,16 @@
$(this).closest('.col-xs-3').prev().attr("class", 'col-xs-6');
$(this).closest('.col-xs-3').attr("class", 'col-xs-6');
if (parseInt(cost) > 0) {
var priceElement = $(this).find('.price')
priceElement.css({ "width": "auto", "padding": "3px 5px", "height": "26px" });
priceElement.text(parseFloat(cost / 100).toFixed(2) + ' HFC');
priceElement.text(cost + ' HFC');
priceElement.css({ "min-width": priceElement.width() + 10 });
}
});
$('.grid-item').find('#price-or-edit').find('a').on('click', function () {
buyButtonClicked($(this).closest('.grid-item').attr('data-item-id'),
$(this).closest('.grid-item').find('.item-title').text(),
@ -175,11 +175,12 @@
if (confirmAllPurchases) {
var href = $('#side-info').find('.btn').first().attr('href');
$('#side-info').find('.btn').first().attr('href', '#');
var cost = $('.item-cost').text();
if (parseInt(cost) > 0 && $('#side-info').find('#buyItemButton').size() === 0) {
$('#side-info').find('.btn').first().html('<span class="glyphicon glyphicon-download" id="buyItemButton"></span>Own Item: ' + (parseFloat(cost / 100).toFixed(2)) + ' HFC');
$('#side-info').find('.btn').first().html('<span class="glyphicon glyphicon-download" id="buyItemButton"></span>Own Item: ' + cost + ' HFC');
}
$('#side-info').find('.btn').first().on('click', function () {
@ -264,7 +265,7 @@
// Reference: https://clara.io/learn/sdk/api/export
//var XMLHTTPREQUEST_URL = "https://clara.io/api/scenes/{uuid}/export/fbx?zip=true&centerScene=true&alignSceneGround=true&fbxUnit=Meter&fbxVersion=7&fbxEmbedTextures=true&imageFormat=WebGL";
// 13 Jan 2017: Specify FBX version 5 and remove some options in order to make Clara.io site more likely to
// 13 Jan 2017: Specify FBX version 5 and remove some options in order to make Clara.io site more likely to
// be successful in generating zip files.
var XMLHTTPREQUEST_URL = "https://clara.io/api/scenes/{uuid}/export/fbx?fbxUnit=Meter&fbxVersion=5&fbxEmbedTextures=true&imageFormat=WebGL";
@ -447,7 +448,7 @@
cancelClaraDownload();
} else {
var parsedJsonMessage = JSON.parse(message);
if (parsedJsonMessage.type === "marketplaces") {
if (parsedJsonMessage.action === "inspectionModeSetting") {
confirmAllPurchases = !!parsedJsonMessage.data;

View file

@ -197,6 +197,7 @@
// Description:
// -Called when a message is received from Checkout.qml. The "message" argument is what is sent from the Checkout QML
// in the format "{method, params}", like json-rpc.
var isHmdPreviewDisabled = true;
function fromQml(message) {
switch (message.method) {
case 'checkout_setUpClicked':
@ -231,12 +232,20 @@
case 'purchases_goToMarketplaceClicked':
tablet.gotoWebScreen(MARKETPLACE_URL_INITIAL, MARKETPLACES_INJECT_SCRIPT_URL);
break;
case 'passphrasePopup_cancelClicked':
case 'needsLogIn_cancelClicked':
tablet.gotoWebScreen(MARKETPLACE_URL_INITIAL, MARKETPLACES_INJECT_SCRIPT_URL);
break;
case 'needsLogIn_loginClicked':
openLoginWindow();
break;
case 'disableHmdPreview':
isHmdPreviewDisabled = Menu.isOptionChecked("Disable Preview");
Menu.setIsOptionChecked("Disable Preview", true);
break;
case 'maybeEnableHmdPreview':
Menu.setIsOptionChecked("Disable Preview", isHmdPreviewDisabled);
break;
default:
print('Unrecognized message from Checkout.qml or Purchases.qml: ' + JSON.stringify(message));
}

View file

@ -0,0 +1,168 @@
(function () {
var FORCE_DROP_CHANNEL = "Hifi-Hand-Drop";
var proxInterval,
proxTimeout;
var _entityID;
this.preload = function (entityID) {
_entityID = entityID;
Entities.editEntity(_entityID, {
userData: '{"grabbableKey": {"grabbable": true}'
});
};
var particleTrailEntity = null;
function particleTrail() {
var props = {
type: 'ParticleEffect',
name: 'Particle',
parentID: _entityID,
isEmitting: true,
lifespan: 2.0,
maxParticles: 100,
textures: 'https://content.highfidelity.com/DomainContent/production/Particles/wispy-smoke.png',
emitRate: 50,
emitSpeed: 0,
emitterShouldTrail: true,
particleRadius: 0,
radiusSpread: 0,
radiusStart: .2,
radiusFinish: 0.1,
color: {
red: 201,
blue: 201,
green: 34
},
accelerationSpread: {
x: 0,
y: 0,
z: 0
},
alpha: 0,
alphaSpread: 0,
alphaStart: 1,
alphaFinish: 0,
polarStart: 0,
polarFinish: 0,
azimuthStart: -180,
azimuthFinish: 180
};
particleTrailEntity = Entities.addEntity(props);
}
function particleExplode() {
var entPos = Entities.getEntityProperties(_entityID, 'position').position;
var props = {
type: 'ParticleEffect',
name: 'Particle',
parentID: _entityID,
isEmitting: true,
lifespan: 2,
maxParticles: 10,
position: entPos,
textures: 'https://content.highfidelity.com/DomainContent/production/Particles/wispy-smoke.png',
emitRate: 1,
emitSpeed: 0,
emitterShouldTrail: false,
particleRadius: 1,
radiusSpread: 0,
radiusStart: 0,
radiusFinish: 1,
color: {
red: 232,
blue: 232,
green: 26
},
emitAcceleration: {
x: 0,
y: 0,
z: 0
},
alpha: 0,
alphaSpread: 0,
alphaStart: 1,
alphaFinish: .5,
polarStart: 0,
polarFinish: 0,
azimuthStart: -180,
azimuthFinish: 180
};
var explosionParticles = Entities.addEntity(props);
Entities.editEntity(_entityID, {
velocity: Vec3.ZERO,
dynamic: false
});
Script.setTimeout(function () {
Entities.deleteEntity(explosionParticles);
Entities.editEntity(_entityID, {
dynamic: true
})
}, 500);
}
function clearProxCheck() {
if (proxInterval) {
Script.clearInterval(proxInterval);
Entities.deleteEntity(particleTrailEntity);
particleTrailEntity = null;
}
if (proxTimeout) {
Script.clearTimeout(proxTimeout);
}
}
function proxCheck() {
var ballPos = Entities.getEntityProperties(_entityID, ['position']).position;
var isAnyAvatarInRange = AvatarList.isAvatarInRange(ballPos, 1);
if (isAnyAvatarInRange) {
clearProxCheck();
particleExplode();
}
}
this.startDistanceGrab = function (thisEntityID, triggerHandAndAvatarUUIDArray) {
clearProxCheck();
var triggerHand = triggerHandAndAvatarUUIDArray[0];
var avatarUUID = triggerHandAndAvatarUUIDArray[1];
var ballPos = Entities.getEntityProperties(_entityID, ['position']).position;
var MAX_DISTANCE_GRAB = 2; //meter
if (Vec3.distance(ballPos, AvatarList.getAvatar(avatarUUID).position) > MAX_DISTANCE_GRAB) {
Messages.sendMessage(FORCE_DROP_CHANNEL, triggerHand, true);
}
};
this.startNearGrab = function (thisEntityID, triggerHandAndAvatarUUIDArray) {
clearProxCheck();
};
this.releaseGrab = function (thisEntityID) {
if (particleTrailEntity === null) {
particleTrail();
}
Script.setTimeout(function () {
proxInterval = Script.setInterval(proxCheck, 50);
}, 200); // Setting a delay to give it time to leave initial avatar without proc.
proxTimeout = Script.setTimeout(function () {
clearProxCheck();
}, 10000)
};
this.collisionWithEntity = function (thisEntityID, collisionEntityID, collisionInfo) {
clearProxCheck();
};
});