Merge branch 'master' of github.com:highfidelity/hifi into fix/create/missingStartMiddleFinishTooltips2

# Conflicts:
#	scripts/system/html/js/entityProperties.js
This commit is contained in:
Thijs Wenker 2018-12-07 20:24:21 +01:00
commit 3a259d8525
53 changed files with 1025 additions and 332 deletions

View file

@ -13,7 +13,7 @@
### CMake External Project Dependencies
These dependencies need not be installed manually. They are automatically downloaded on the platforms where they are required.
These dependencies need not be installed manually. They are automatically downloaded on the platforms where they are required.
- [Bullet Physics Engine](https://github.com/bulletphysics/bullet3/releases): 2.83
- [glm](https://glm.g-truc.net/0.9.8/index.html): 0.9.8
- [Oculus SDK](https://developer.oculus.com/downloads/): 1.11 (Win32) / 0.5 (Mac)
@ -22,7 +22,7 @@ These dependencies need not be installed manually. They are automatically downlo
- [QuaZip](https://sourceforge.net/projects/quazip/files/quazip/): 0.7.3
- [SDL2](https://www.libsdl.org/download-2.0.php): 2.0.3
- [Intel Threading Building Blocks](https://www.threadingbuildingblocks.org/): 4.3
- [vcpkg](https://github.com/highfidelity/vcpkg):
- [vcpkg](https://github.com/highfidelity/vcpkg):
- [VHACD](https://github.com/virneo/v-hacd)
- [zlib](http://www.zlib.net/): 1.28 (Win32 only)
- [nvtt](https://github.com/highfidelity/nvidia-texture-tools): 2.1.1 (customized)
@ -48,6 +48,18 @@ The path it needs to be set to will depend on where and how Qt5 was installed. e
export QT_CMAKE_PREFIX_PATH=/usr/local/Cellar/qt5/5.10.1/lib/cmake
export QT_CMAKE_PREFIX_PATH=/usr/local/opt/qt5/lib/cmake
#### Vcpkg
Hifi uses vcpkg to download and build dependencies.
You do not need to install vcpkg.
Building the dependencies can be lengthy and the resulting files will be stored in your OS temp directory.
However, those files can potentially get cleaned up by the OS, so in order to avoid this and having to redo the lengthy build step, you can set the following environment variable:
export HIFI_VCPKG_BASE=/path/to/directory
Where /path/to/directory is the path to a directory where you wish the build files to get stored.
#### Generating build files
Create a build directory in the root of your checkout and then run the CMake build from there. This will keep the rest of the directory clean.
@ -80,7 +92,7 @@ In the examples below the variable $NAME would be replaced by the name of the de
### Optional Components
#### Build Options
#### Build Options
The following build options can be used when running CMake
@ -89,7 +101,7 @@ The following build options can be used when running CMake
* BUILD_TESTS
* BUILD_TOOLS
#### Developer Build Options
#### Developer Build Options
* USE_GLES
* DISABLE_UI

View file

@ -19,7 +19,7 @@ If you do not wish to use the Python installation bundled with Visual Studio, yo
### Step 2. Installing CMake
Download and install the latest version of CMake 3.9.
Download and install the latest version of CMake 3.9.
Download the file named win64-x64 Installer from the [CMake Website](https://cmake.org/download/). You can access the installer on this [3.9 Version page](https://cmake.org/files/v3.9/). During installation, make sure to check "Add CMake to system PATH for all users" when prompted.
@ -35,20 +35,7 @@ Go to `Control Panel > System > Advanced System Settings > Environment Variables
* Set "Variable name": `QT_CMAKE_PREFIX_PATH`
* Set "Variable value": `C:\Qt\5.10.1\msvc2017_64\lib\cmake`
### Step 5. Installing [vcpkg](https://github.com/Microsoft/vcpkg)
* Clone the VCPKG [repository](https://github.com/Microsoft/vcpkg)
* Follow the instructions in the [readme](https://github.com/Microsoft/vcpkg/blob/master/README.md) to bootstrap vcpkg
* Note, you may need to do these in a _Developer Command Prompt_
* Set an environment variable VCPKG_ROOT to the location of the cloned repository
* Close and re-open any command prompts after setting the environment variable so that they will pick up the change
### Step 6. Installing OpenSSL via vcpkg
* In the vcpkg directory, install the 64 bit OpenSSL package with the command `.\vcpkg install openssl:x64-windows`
* Once the build completes you should have a file `ssl.h` in `${VCPKG_ROOT}/installed/x64-windows/include/openssl`
### Step 7. Running CMake to Generate Build Files
### Step 5. Running CMake to Generate Build Files
Run Command Prompt from Start and run the following commands:
```
@ -58,9 +45,9 @@ cd build
cmake .. -G "Visual Studio 15 Win64"
```
Where `%HIFI_DIR%` is the directory for the highfidelity repository.
Where `%HIFI_DIR%` is the directory for the highfidelity repository.
### Step 8. Making a Build
### Step 6. Making a Build
Open `%HIFI_DIR%\build\hifi.sln` using Visual Studio.
@ -68,7 +55,7 @@ Change the Solution Configuration (menu ribbon under the menu bar, next to the g
Run from the menu bar `Build > Build Solution`.
### Step 9. Testing Interface
### Step 7. Testing Interface
Create another environment variable (see Step #4)
* Set "Variable name": `_NO_DEBUG_HEAP`

View file

@ -18,9 +18,6 @@ $(document).ready(function(){
Settings.extraGroupsAtIndex = Settings.extraDomainGroupsAtIndex;
Settings.afterReloadActions = function() {
// append the domain selection modal
appendDomainIDButtons();
// call our method to setup the HF account button
setupHFAccountButton();
@ -52,6 +49,11 @@ $(document).ready(function(){
if (cloudWizardExit != undefined) {
$('#cloud-domains-alert').show();
}
$(Settings.DOMAIN_ID_SELECTOR).siblings('span').append("</br><strong>Changing the domain ID for a Cloud Domain may result in an incorrect status for the domain on your Cloud Domains page.</strong>");
} else {
// append the domain selection modal
appendDomainIDButtons();
}
handleAction();
@ -59,9 +61,9 @@ $(document).ready(function(){
Settings.handlePostSettings = function(formJSON) {
if (!verifyAvatarHeights()) {
return false;
}
if (!verifyAvatarHeights()) {
return false;
}
// check if we've set the basic http password
if (formJSON["security"]) {

View file

@ -36,6 +36,8 @@ Item {
property bool isCurrentlySendingAsset: false;
property string assetName: "";
property string assetCertID: "";
property string couponID: "";
property string authorizationID: "";
property string sendingPubliclyEffectImage;
property var http;
property var listModelName;
@ -108,6 +110,27 @@ Item {
}
}
onAuthorizeAssetTransferResult: {
if (!root.visible) {
return;
}
root.isCurrentlySendingAsset = false;
if (result.status === 'success') {
root.authorizationID = result.data.authorization_id;
authorizationIDText.text = root.authorizationID;
root.couponID = result.data.coupon_id;
couponIDText.text = root.couponID
if (couponIDTextField.text !== root.couponID) {
console.log("SendAsset: Returned coupon ID doesn't match client-generated coupon ID!");
}
root.nextActiveView = 'paymentSuccess';
} else {
root.nextActiveView = 'paymentFailure';
}
}
onCertificateInfoResult: {
if (result.status !== 'success') {
console.log("Failed to get certificate info", result.data.message);
@ -269,7 +292,7 @@ Item {
RalewaySemiBold {
id: sendAssetText;
text: root.assetCertID === "" ? "Send Money To:" : "Gift \"" + root.assetName + "\" To:";
text: root.assetCertID === "" ? "Send Money To:" : "Send \"" + root.assetName + "\" To:";
// Anchors
anchors.top: parent.top;
anchors.topMargin: 26;
@ -370,6 +393,51 @@ Item {
}
}
Item {
id: createCouponButton;
// Anchors
anchors.top: nearbyButton.bottom;
anchors.topMargin: 32;
anchors.horizontalCenter: parent.horizontalCenter;
height: connectionButton.height;
width: connectionButton.width;
Image {
anchors.top: parent.top;
source: "./images/coupon.svg";
height: 70;
width: parent.width;
fillMode: Image.PreserveAspectFit;
horizontalAlignment: Image.AlignHCenter;
verticalAlignment: Image.AlignTop;
mipmap: true;
}
RalewaySemiBold {
text: "Create Coupon";
// Anchors
anchors.bottom: parent.bottom;
height: 15;
width: parent.width;
// Text size
size: 18;
// Style
color: hifi.colors.baseGray;
horizontalAlignment: Text.AlignHCenter;
}
MouseArea {
anchors.fill: parent;
onClicked: {
sendAssetStep.referrer = "createCoupon";
sendAssetStep.selectedRecipientNodeID = "";
couponIDTextField.text = generateRandomCouponID();
root.nextActiveView = "sendAssetStep";
}
}
}
HifiControlsUit.Button {
id: backButton_sendAssetHome;
visible: parentAppNavBarHeight === 0;
@ -860,7 +928,7 @@ Item {
id: sendAssetStep;
z: 996;
property string referrer; // either "connections", "nearby", or "payIn"
property string referrer; // either "connections", "nearby", "payIn", or "createCoupon"
property string selectedRecipientNodeID;
property string selectedRecipientDisplayName;
property string selectedRecipientUserName;
@ -872,7 +940,8 @@ Item {
RalewaySemiBold {
id: sendAssetText_sendAssetStep;
text: sendAssetStep.referrer === "payIn" && root.assetCertID !== "" ? "Send \"" + root.assetName + "\":" :
text: ((sendAssetStep.referrer === "payIn" || sendAssetStep.referrer === "createCoupon") &&
root.assetCertID !== "") ? "Send \"" + root.assetName + "\":" :
(root.assetCertID === "" ? "Send Money To:" : "Gift \"" + root.assetName + "\" To:");
// Anchors
anchors.top: parent.top;
@ -901,12 +970,13 @@ Item {
RalewaySemiBold {
id: sendToText_sendAssetStep;
text: (root.assetCertID === "" || sendAssetStep.referrer === "payIn") ? "Send to:" : "Gift to:";
text: sendAssetStep.referrer === "createCoupon" ? "Coupon ID:" :
(root.assetCertID === "" || sendAssetStep.referrer === "payIn") ? "Send to:" : "Gift to:";
// Anchors
anchors.top: parent.top;
anchors.left: parent.left;
anchors.bottom: parent.bottom;
width: 90;
width: paintedWidth;
// Text size
size: 18;
// Style
@ -915,8 +985,10 @@ Item {
}
RecipientDisplay {
visible: sendAssetStep.referrer !== "createCoupon";
anchors.top: parent.top;
anchors.left: sendToText_sendAssetStep.right;
anchors.leftMargin: 16;
anchors.right: changeButton.left;
anchors.rightMargin: 12;
height: parent.height;
@ -929,6 +1001,68 @@ Item {
multiLineDisplay: sendAssetStep.referrer === "nearby" || sendAssetStep.referrer === "payIn";
}
Item {
id: couponIDContainer;
visible: sendAssetStep.referrer === "createCoupon";
anchors.top: parent.top;
anchors.left: sendToText_sendAssetStep.right;
anchors.right: parent.right;
height: parent.height;
RalewaySemiBold {
id: couponIDHelp;
text: "[?]";
// Anchors
anchors.left: parent.left;
anchors.leftMargin: 8;
anchors.verticalCenter: parent.verticalCenter;
height: 30;
width: paintedWidth;
// Text size
size: 18;
// Style
color: hifi.colors.blueAccent;
MouseArea {
anchors.fill: parent;
hoverEnabled: true;
onEntered: {
parent.color = hifi.colors.blueHighlight;
}
onExited: {
parent.color = hifi.colors.blueAccent;
}
onClicked: {
lightboxPopup.titleText = "Coupon ID";
lightboxPopup.bodyText = "This alphanumeric text string will be used to ensure " +
"that only you can redeem the coupon for the asset that you are sending. Keep it private!";
lightboxPopup.button1text = "CLOSE";
lightboxPopup.button1method = function() {
lightboxPopup.visible = false;
}
lightboxPopup.visible = true;
}
}
}
HifiControlsUit.TextField {
id: couponIDTextField;
colorScheme: root.assetCertID === "" ? hifi.colorSchemes.dark : hifi.colorSchemes.light;
// Anchors
anchors.verticalCenter: parent.verticalCenter;
anchors.left: couponIDHelp.right;
anchors.leftMargin: 16;
anchors.right: parent.right;
height: 50;
// Style
activeFocusOnPress: true;
activeFocusOnTab: true;
onAccepted: {
optionalMessage.focus = true;
}
}
}
// "CHANGE" button
HifiControlsUit.Button {
id: changeButton;
@ -939,7 +1073,7 @@ Item {
height: 35;
width: 100;
text: "CHANGE";
visible: sendAssetStep.referrer !== "payIn";
visible: sendAssetStep.referrer !== "payIn" && sendAssetStep.referrer !== "createCoupon";
onClicked: {
if (sendAssetStep.referrer === "connections") {
root.nextActiveView = "chooseRecipientConnection";
@ -1263,6 +1397,11 @@ Item {
root.assetCertID,
parseInt(amountTextField.text),
optionalMessage.text);
} else if (sendAssetStep.referrer === "createCoupon") {
Commerce.authorizeAssetTransfer(couponIDTextField.text || "",
root.assetCertID,
parseInt(amountTextField.text) || 1,
optionalMessage.text)
}
}
}
@ -1334,18 +1473,24 @@ Item {
Rectangle {
anchors.top: parent.top;
anchors.topMargin: root.assetCertID === "" || sendAssetStep.referrer === "payIn" ? 15 : 125;
anchors.topMargin: root.assetCertID === "" || sendAssetStep.referrer === "payIn" ||
sendAssetStep.referrer === "createCoupon" ? 15 : 125;
anchors.left: parent.left;
anchors.leftMargin: root.assetCertID === "" || sendAssetStep.referrer === "payIn" ? 15 : 50;
anchors.leftMargin: root.assetCertID === "" || sendAssetStep.referrer === "payIn" ||
sendAssetStep.referrer === "createCoupon" ? 15 : 50;
anchors.right: parent.right;
anchors.rightMargin: root.assetCertID === "" || sendAssetStep.referrer === "payIn" ? 15 : 50;
anchors.rightMargin: root.assetCertID === "" || sendAssetStep.referrer === "payIn" ||
sendAssetStep.referrer === "createCoupon" ? 15 : 50;
anchors.bottom: parent.bottom;
anchors.bottomMargin: root.assetCertID === "" || sendAssetStep.referrer === "payIn" ? 15 : 125;
anchors.bottomMargin: root.assetCertID === "" || sendAssetStep.referrer === "payIn" ||
sendAssetStep.referrer === "createCoupon" ? 15 : 125;
color: "#FFFFFF";
RalewaySemiBold {
id: paymentSentText;
text: root.assetCertID === "" ? "Payment Sent" : (sendAssetStep.referrer === "payIn" ? "Item Sent" : "Gift Sent");
text: root.assetCertID === "" ? (sendAssetStep.referrer === "createCoupon" ? "Payment Authorized" : "Payment Sent") :
(sendAssetStep.referrer === "createCoupon" ? "Item Transfer Authorized" :
(sendAssetStep.referrer === "payIn" ? "Item Sent" : "Gift Sent"));
// Anchors
anchors.top: parent.top;
anchors.topMargin: 26;
@ -1383,6 +1528,8 @@ Item {
onClicked: {
if (sendAssetStep.referrer === "payIn") {
sendToScript({method: "closeSendAsset"});
} else if (sendAssetStep.referrer === "createCoupon") {
showDidYouCopyLightbox();
} else {
root.nextActiveView = "sendAssetHome";
resetSendAssetData();
@ -1402,38 +1549,176 @@ Item {
anchors.leftMargin: 20;
anchors.right: parent.right;
anchors.rightMargin: 20;
height: 80;
height: childrenRect.height;
RalewaySemiBold {
id: sendToText_paymentSuccess;
text: "Sent To:";
// Anchors
Item {
id: sendToScriptContainer_paymentSuccess;
visible: sendAssetStep.referrer === "createCoupon";
anchors.top: parent.top;
anchors.left: parent.left;
anchors.bottom: parent.bottom;
width: 90;
// Text size
size: 18;
// Style
color: hifi.colors.baseGray;
verticalAlignment: Text.AlignVCenter;
}
RecipientDisplay {
anchors.top: parent.top;
anchors.left: sendToText_paymentSuccess.right;
anchors.right: parent.right;
height: parent.height;
textColor: hifi.colors.blueAccent;
height: childrenRect.height;
displayName: sendAssetStep.selectedRecipientDisplayName;
userName: sendAssetStep.selectedRecipientUserName;
profilePic: sendAssetStep.selectedRecipientProfilePic !== "" ? ((0 === sendAssetStep.selectedRecipientProfilePic.indexOf("http")) ?
sendAssetStep.selectedRecipientProfilePic : (Account.metaverseServerURL + sendAssetStep.selectedRecipientProfilePic)) : "";
multiLineDisplay: sendAssetStep.referrer === "nearby" || sendAssetStep.referrer === "payIn";
RalewaySemiBold {
id: authorizationIDLabel;
text: "Authorization ID:";
// Anchors
anchors.left: parent.left;
anchors.top: authorizationIDText.top;
width: paintedWidth;
// Text size
size: 18;
// Style
color: hifi.colors.baseGray;
verticalAlignment: Text.AlignVCenter;
}
RalewayRegular {
id: authorizationIDText;
text: root.authorizationID;
anchors.top: parent.top;
anchors.left: authorizationIDLabel.right;
anchors.leftMargin: 16;
anchors.right: authorizationIDClipboardButton.left;
anchors.rightMargin: 16;
// Text size
size: 18;
// Style
color: hifi.colors.baseGray;
horizontalAlignment: Text.AlignHCenter;
verticalAlignment: Text.AlignVCenter;
wrapMode: Text.WrapAnywhere;
}
Image {
id: authorizationIDClipboardButton;
source: "images/clipboard.svg"; // clipboard by Bieutuong Bon from the Noun Project
fillMode: Image.PreserveAspectFit;
// Anchors
anchors.right: parent.right;
anchors.top: authorizationIDText.top;
height: 40;
width: height;
MouseArea {
anchors.fill: parent;
onClicked: {
Window.copyToClipboard(root.authorizationID);
authorizationIDText.text = "Copied to Clipboard!\n";
authorizationIDClipboardTimer.start();
}
}
}
Timer {
id: authorizationIDClipboardTimer;
interval: 2000;
repeat: false;
onTriggered: {
authorizationIDText.text = root.authorizationID;
}
}
RalewaySemiBold {
id: couponIDLabel;
text: "Coupon ID:";
// Anchors
anchors.left: parent.left;
anchors.top: couponIDText.top;
width: authorizationIDLabel.width;
// Text size
size: 18;
// Style
color: hifi.colors.baseGray;
verticalAlignment: Text.AlignVCenter;
}
RalewayRegular {
id: couponIDText;
text: root.couponID;
anchors.top: authorizationIDText.bottom;
anchors.topMargin: 16;
anchors.left: couponIDLabel.right;
anchors.leftMargin: 16;
anchors.right: couponIDClipboardButton.left;
anchors.rightMargin: 16;
// Text size
size: 18;
// Style
color: hifi.colors.baseGray;
horizontalAlignment: Text.AlignHCenter;
verticalAlignment: Text.AlignVCenter;
wrapMode: Text.WrapAnywhere;
}
Image {
id: couponIDClipboardButton;
source: "images/clipboard.svg"; // clipboard by Bieutuong Bon from the Noun Project
fillMode: Image.PreserveAspectFit;
// Anchors
anchors.right: parent.right;
anchors.top: couponIDText.top;
height: 40;
width: height;
MouseArea {
anchors.fill: parent;
onClicked: {
Window.copyToClipboard(root.couponID);
couponIDText.text = "Copied to Clipboard!\n";
couponIDClipboardTimer.start();
}
}
}
Timer {
id: couponIDClipboardTimer;
interval: 2000;
repeat: false;
onTriggered: {
couponIDText.text = root.couponID;
}
}
}
}
Item {
id: sendToRecipientContainer_paymentSuccess;
visible: !sendToScriptContainer_paymentSuccess.visible;
anchors.top: parent.top;
anchors.left: parent.left;
anchors.right: parent.right;
height: 80;
RalewaySemiBold {
id: sendToText_paymentSuccess;
text: "Sent To:";
// Anchors
anchors.top: parent.top;
anchors.left: parent.left;
anchors.bottom: parent.bottom;
width: 90;
// Text size
size: 18;
// Style
color: hifi.colors.baseGray;
verticalAlignment: Text.AlignVCenter;
}
RecipientDisplay {
anchors.top: parent.top;
anchors.left: sendToText_paymentSuccess.right;
anchors.right: parent.right;
height: parent.height;
textColor: hifi.colors.blueAccent;
displayName: sendAssetStep.selectedRecipientDisplayName;
userName: sendAssetStep.selectedRecipientUserName;
profilePic: sendAssetStep.selectedRecipientProfilePic !== "" ? ((0 === sendAssetStep.selectedRecipientProfilePic.indexOf("http")) ?
sendAssetStep.selectedRecipientProfilePic : (Account.metaverseServerURL + sendAssetStep.selectedRecipientProfilePic)) : "";
multiLineDisplay: sendAssetStep.referrer === "nearby" || sendAssetStep.referrer === "payIn";
}
}
}
Item {
id: giftContainer_paymentSuccess;
@ -1448,7 +1733,8 @@ Item {
RalewaySemiBold {
id: gift_paymentSuccess;
text: sendAssetStep.referrer === "payIn" ? "Item:" : "Gift:";
text: sendAssetStep.referrer === "payIn" || sendAssetStep.referrer === "createCoupon" ?
"Item:" : "Gift:";
// Anchors
anchors.top: parent.top;
anchors.left: parent.left;
@ -1566,6 +1852,8 @@ Item {
onClicked: {
if (sendAssetStep.referrer === "payIn") {
sendToScript({method: "closeSendAsset"});
} else if (sendAssetStep.referrer === "createCoupon") {
showDidYouCopyLightbox();
} else {
root.nextActiveView = "sendAssetHome";
resetSendAssetData();
@ -1599,13 +1887,17 @@ Item {
Rectangle {
anchors.top: parent.top;
anchors.topMargin: root.assetCertID === "" || sendAssetStep.referrer === "payIn" ? 15 : 150;
anchors.topMargin: root.assetCertID === "" || sendAssetStep.referrer === "payIn" ||
sendAssetStep.referrer === "createCoupon" ? 15 : 150;
anchors.left: parent.left;
anchors.leftMargin: root.assetCertID === "" || sendAssetStep.referrer === "payIn" ? 15 : 50;
anchors.leftMargin: root.assetCertID === "" || sendAssetStep.referrer === "payIn" ||
sendAssetStep.referrer === "createCoupon" ? 15 : 50;
anchors.right: parent.right;
anchors.rightMargin: root.assetCertID === "" || sendAssetStep.referrer === "payIn" ? 15 : 50;
anchors.rightMargin: root.assetCertID === "" || sendAssetStep.referrer === "payIn" ||
sendAssetStep.referrer === "createCoupon" ? 15 : 50;
anchors.bottom: parent.bottom;
anchors.bottomMargin: root.assetCertID === "" || sendAssetStep.referrer === "payIn" ? 15 : 300;
anchors.bottomMargin: root.assetCertID === "" || sendAssetStep.referrer === "payIn" ||
sendAssetStep.referrer === "createCoupon" ? 15 : 300;
color: "#FFFFFF";
RalewaySemiBold {
@ -1657,8 +1949,9 @@ Item {
RalewaySemiBold {
id: paymentFailureDetailText;
text: "The recipient you specified was unable to receive your " +
(root.assetCertID === "" ? "payment." : (sendAssetStep.referrer === "payIn" ? "item." : "gift."));
text: sendAssetStep.referrer === "createCoupon" ? "The server was unable to handle your request. Please try again later." :
("The recipient you specified was unable to receive your " +
(root.assetCertID === "" ? "payment." : (sendAssetStep.referrer === "payIn" ? "item." : "gift.")));
anchors.top: paymentFailureText.bottom;
anchors.topMargin: 20;
anchors.left: parent.left;
@ -1676,7 +1969,8 @@ Item {
Item {
id: sendToContainer_paymentFailure;
visible: root.assetCertID === "" || sendAssetStep.referrer === "payIn";
visible: (root.assetCertID === "" || sendAssetStep.referrer === "payIn") &&
sendAssetStep.referrer !== "createCoupon";
anchors.top: paymentFailureDetailText.bottom;
anchors.topMargin: 8;
anchors.left: parent.left;
@ -1718,7 +2012,8 @@ Item {
Item {
id: amountContainer_paymentFailure;
visible: root.assetCertID === "";
anchors.top: sendToContainer_paymentFailure.bottom;
anchors.top: sendToContainer_paymentFailure.visible ?
sendToContainer_paymentFailure.bottom : paymentFailureDetailText.bottom;
anchors.topMargin: 16;
anchors.left: parent.left;
anchors.leftMargin: 20;
@ -1839,6 +2134,11 @@ Item {
root.assetCertID,
parseInt(amountTextField.text),
optionalMessage.text);
} else if (sendAssetStep.referrer === "createCoupon") {
Commerce.authorizeAssetTransfer(couponIDTextField.text || "",
root.assetCertID,
parseInt(amountTextField.text) || 1,
optionalMessage.text)
}
}
}
@ -1867,6 +2167,39 @@ Item {
sendAssetStep.referrer = "";
}
function generateRandomCouponID() {
var RANDOM_COUPON_ID_LENGTH = 25;
var randomCouponID = "";
var possibleCharacters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
for (var i = 0; i < RANDOM_COUPON_ID_LENGTH; i++) {
randomCouponID += possibleCharacters.charAt(Math.floor(Math.random() * possibleCharacters.length));
}
return randomCouponID;
}
function showDidYouCopyLightbox() {
lightboxPopup.titleText = "Close Confirmation";
lightboxPopup.bodyText = "Did you copy your Authorization ID and your Coupon ID?\n\n" +
"You won't be able to see your Authorization ID or your Coupon ID once " +
"you close this window.";
lightboxPopup.button1text = "GO BACK";
lightboxPopup.button1method = function() {
lightboxPopup.visible = false;
}
lightboxPopup.button2text = "I'M ALL SET";
lightboxPopup.button2method = function() {
lightboxPopup.visible = false;
root.nextActiveView = "sendAssetHome";
resetSendAssetData();
if (root.assetName !== "") {
sendSignalToParent({method: "closeSendAsset"});
}
}
lightboxPopup.visible = true;
}
//
// Function Name: fromScript()
//
@ -1908,9 +2241,15 @@ Item {
sendAssetStep.referrer = "payIn";
sendAssetStep.selectedRecipientNodeID = "";
sendAssetStep.selectedRecipientDisplayName = "Determined by script:";
sendAssetStep.selectedRecipientUserName = message.username;
sendAssetStep.selectedRecipientUserName = message.username || "";
optionalMessage.text = message.message || "No Message Provided";
if (sendAssetStep.selectedRecipientUserName === "") {
console.log("SendAsset: Script didn't specify a recipient username!");
sendAssetHome.visible = false;
return;
}
root.nextActiveView = "sendAssetStep";
break;
case 'inspectionCertificate_resetCert':

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" x="0px" y="0px" viewBox="0 0 44 55" enable-background="new 0 0 44 44" xml:space="preserve"><path d="M30.201,10.811h-1.599v-0.076c0-0.754-0.612-1.367-1.366-1.367h-2.063c0.005-0.074,0.023-0.146,0.023-0.225 c0-1.766-1.432-3.197-3.197-3.197s-3.197,1.432-3.197,3.197c0,0.078,0.018,0.15,0.023,0.225h-2.063 c-0.754,0-1.366,0.613-1.366,1.367v0.076h-1.599c-1.38,0-2.502,1.123-2.502,2.502v22.24c0,1.379,1.122,2.502,2.502,2.502h16.402 c1.38,0,2.502-1.123,2.502-2.502v-22.24C32.703,11.934,31.581,10.811,30.201,10.811z M22,7.893c0.691,0,1.251,0.559,1.251,1.25 s-0.56,1.252-1.251,1.252s-1.251-0.561-1.251-1.252S21.309,7.893,22,7.893z M31.035,35.553c0,0.459-0.374,0.834-0.834,0.834H13.799 c-0.46,0-0.834-0.375-0.834-0.834v-22.24c0-0.459,0.374-0.834,0.834-0.834h1.599v1.443h13.205v-1.443h1.599 c0.46,0,0.834,0.375,0.834,0.834V35.553z"/><rect x="15.397" y="16.648" width="13.205" height="0.975"/><rect x="15.397" y="20.402" width="13.205" height="0.973"/><rect x="15.397" y="24.154" width="13.205" height="0.975"/><rect x="15.397" y="27.908" width="13.205" height="0.973"/><rect x="15.397" y="31.66" width="13.205" height="0.975"/><text x="0" y="59" fill="#000000" font-size="5px" font-weight="bold" font-family="'Helvetica Neue', Helvetica, Arial-Unicode, Arial, Sans-serif">Created by Bieutuong Bon</text><text x="0" y="64" fill="#000000" font-size="5px" font-weight="bold" font-family="'Helvetica Neue', Helvetica, Arial-Unicode, Arial, Sans-serif">from the Noun Project</text></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View file

@ -0,0 +1,10 @@
<svg width="55" height="67" viewBox="0 0 55 67" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="39" cy="21" r="2" fill="#1398BB"/>
<circle cx="45" cy="21" r="2" fill="#1398BB"/>
<path d="M45 32H10V65L14.5 60.5L18.5 65L23 60.5L27.5 65L32 60.5L36 65L40.5 60.5L45 65V32Z" stroke="#1398BB" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M45 2V16H10V2H45Z" stroke="#1398BB" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M45 42H49C51.2091 42 53 40.2091 53 38V20C53 17.7909 51.2091 16 49 16H6C3.79086 16 2 17.7909 2 20V38C2 40.2091 3.79086 42 6 42H10" stroke="#1398BB" stroke-width="3"/>
<path d="M21 47L35 33" stroke="#1398BB" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/>
<circle cx="34" cy="46" r="3.5" stroke="#1398BB" stroke-width="3"/>
<path d="M18.1273 32C17.7319 32.5669 17.5 33.2564 17.5 34C17.5 35.933 19.067 37.5 21 37.5C22.933 37.5 24.5 35.933 24.5 34C24.5 33.2564 24.2681 32.5669 23.8727 32" stroke="#1398BB" stroke-width="3"/>
</svg>

After

Width:  |  Height:  |  Size: 1 KiB

View file

@ -270,9 +270,11 @@ Item {
model: transactionHistoryModel;
delegate: Item {
width: parent.width;
height: (model.transaction_type === "pendingCount" && model.count !== 0) ? 40 : ((model.status === "confirmed" || model.status === "invalidated") ? transactionText.height + 30 : 0);
height: (model.transaction_type === "pendingCount" && model.count !== 0) ? 40 :
(transactionContainer.visible ? transactionText.height + 30 : 0);
Item {
id: pendingCountContainer;
visible: model.transaction_type === "pendingCount" && model.count !== 0;
anchors.top: parent.top;
anchors.left: parent.left;
@ -291,7 +293,9 @@ Item {
}
Item {
visible: model.transaction_type !== "pendingCount" && (model.status === "confirmed" || model.status === "invalidated");
id: transactionContainer;
visible: model.transaction_type !== "pendingCount" &&
(model.status === "confirmed" || model.status === "invalidated");
anchors.top: parent.top;
anchors.left: parent.left;
width: parent.width;

View file

@ -58,7 +58,7 @@ void addAvatarEntities(const QVariantList& avatarEntities) {
EntityItemPropertiesFromScriptValueHonorReadOnly(scriptProperties, entityProperties);
entityProperties.setParentID(myNodeID);
entityProperties.setClientOnly(true);
entityProperties.setEntityHostType(entity::HostType::AVATAR);
entityProperties.setOwningAvatarID(myNodeID);
entityProperties.setSimulationOwner(myNodeID, AVATAR_ENTITY_SIMULATION_PRIORITY);
entityProperties.markAllChanged();

View file

@ -887,8 +887,7 @@ void MyAvatar::simulate(float deltaTime) {
moveOperator.addEntityToMoveList(entity, newCube);
}
// send an edit packet to update the entity-server about the queryAABox
// unless it is client-only
if (packetSender && !entity->getClientOnly()) {
if (packetSender && entity->isDomainEntity()) {
EntityItemProperties properties = entity->getProperties();
properties.setQueryAACubeDirty();
properties.setLastEdited(now);
@ -899,7 +898,7 @@ void MyAvatar::simulate(float deltaTime) {
entity->forEachDescendant([&](SpatiallyNestablePointer descendant) {
EntityItemPointer entityDescendant = std::dynamic_pointer_cast<EntityItem>(descendant);
if (entityDescendant && !entityDescendant->getClientOnly() && descendant->updateQueryAACube()) {
if (entityDescendant && entityDescendant->isDomainEntity() && descendant->updateQueryAACube()) {
EntityItemProperties descendantProperties;
descendantProperties.setQueryAACube(descendant->getQueryAACube());
descendantProperties.setLastEdited(now);

View file

@ -63,6 +63,7 @@ Handler(balance)
Handler(inventory)
Handler(transferAssetToNode)
Handler(transferAssetToUsername)
Handler(authorizeAssetTransfer)
Handler(alreadyOwned)
Handler(availableUpdates)
Handler(updateItem)
@ -203,6 +204,7 @@ QString transactionString(const QJsonObject& valueObject) {
int sentMoney = valueObject["sent_money"].toInt();
int receivedMoney = valueObject["received_money"].toInt();
int dateInteger = valueObject["created_at"].toInt();
QString transactionType = valueObject["transaction_type"].toString();
QString message = valueObject["message"].toString();
QDateTime createdAt(QDateTime::fromSecsSinceEpoch(dateInteger, Qt::UTC));
QString result;
@ -210,8 +212,12 @@ QString transactionString(const QJsonObject& valueObject) {
if (sentCerts <= 0 && receivedCerts <= 0 && !KNOWN_USERS.contains(valueObject["sender_name"].toString())) {
// this is an hfc transfer.
if (sentMoney > 0) {
QString recipient = userLink(valueObject["recipient_name"].toString(), valueObject["place_name"].toString());
result += QString("Money sent to %1").arg(recipient);
if (transactionType == "escrow") {
result += QString("Money transferred to coupon");
} else {
QString recipient = userLink(valueObject["recipient_name"].toString(), valueObject["place_name"].toString());
result += QString("Money sent to %1").arg(recipient);
}
} else {
QString sender = userLink(valueObject["sender_name"].toString(), valueObject["place_name"].toString());
result += QString("Money from %1").arg(sender);
@ -226,8 +232,12 @@ QString transactionString(const QJsonObject& valueObject) {
) {
// this is a non-HFC asset transfer.
if (sentCerts > 0) {
QString recipient = userLink(valueObject["recipient_name"].toString(), valueObject["place_name"].toString());
result += QString("Gift sent to %1").arg(recipient);
if (transactionType == "escrow") {
result += QString("Item transferred to coupon");
} else {
QString recipient = userLink(valueObject["recipient_name"].toString(), valueObject["place_name"].toString());
result += QString("Gift sent to %1").arg(recipient);
}
} else {
QString sender = userLink(valueObject["sender_name"].toString(), valueObject["place_name"].toString());
result += QString("Gift from %1").arg(sender);
@ -428,6 +438,7 @@ void Ledger::transferAssetToUsername(const QString& hfc_key, const QString& user
transaction["username"] = username;
transaction["quantity"] = amount;
transaction["message"] = optionalMessage;
transaction["place_name"] = DependencyManager::get<AddressManager>()->getPlaceName();
if (!certificateID.isEmpty()) {
transaction["certificate_id"] = certificateID;
}
@ -440,6 +451,20 @@ void Ledger::transferAssetToUsername(const QString& hfc_key, const QString& user
}
}
void Ledger::authorizeAssetTransfer(const QString& hfc_key, const QString& couponID, const QString& certificateID, const int& amount, const QString& optionalMessage) {
QJsonObject transaction;
transaction["public_key"] = hfc_key;
transaction["coupon_id"] = couponID;
transaction["quantity"] = amount;
transaction["message"] = optionalMessage;
if (!certificateID.isEmpty()) {
transaction["certificate_id"] = certificateID;
}
QJsonDocument transactionDoc{ transaction };
auto transactionString = transactionDoc.toJson(QJsonDocument::Compact);
signedSend("transaction", transactionString, hfc_key, "authorize", "authorizeAssetTransferSuccess", "authorizeAssetTransferFailure");
}
void Ledger::alreadyOwned(const QString& marketplaceId) {
auto wallet = DependencyManager::get<Wallet>();
QString endpoint = "already_owned";

View file

@ -36,6 +36,7 @@ public:
void certificateInfo(const QString& certificateId);
void transferAssetToNode(const QString& hfc_key, const QString& nodeID, const QString& certificateID, const int& amount, const QString& optionalMessage);
void transferAssetToUsername(const QString& hfc_key, const QString& username, const QString& certificateID, const int& amount, const QString& optionalMessage);
void authorizeAssetTransfer(const QString& hfc_key, const QString& couponID, const QString& certificateID, const int& amount, const QString& optionalMessage);
void alreadyOwned(const QString& marketplaceId);
void getAvailableUpdates(const QString& itemId = "", const int& pageNumber = 1, const int& itemsPerPage = 10);
void updateItem(const QString& hfc_key, const QString& certificate_id);
@ -59,6 +60,7 @@ signals:
void certificateInfoResult(QJsonObject result);
void transferAssetToNodeResult(QJsonObject result);
void transferAssetToUsernameResult(QJsonObject result);
void authorizeAssetTransferResult(QJsonObject result);
void alreadyOwnedResult(QJsonObject result);
void availableUpdatesResult(QJsonObject result);
void updateItemResult(QJsonObject result);
@ -86,6 +88,8 @@ public slots:
void transferAssetToNodeFailure(QNetworkReply* reply);
void transferAssetToUsernameSuccess(QNetworkReply* reply);
void transferAssetToUsernameFailure(QNetworkReply* reply);
void authorizeAssetTransferSuccess(QNetworkReply* reply);
void authorizeAssetTransferFailure(QNetworkReply* reply);
void alreadyOwnedSuccess(QNetworkReply* reply);
void alreadyOwnedFailure(QNetworkReply* reply);
void availableUpdatesSuccess(QNetworkReply* reply);

View file

@ -38,6 +38,7 @@ QmlCommerce::QmlCommerce() {
connect(ledger.data(), &Ledger::updateCertificateStatus, this, &QmlCommerce::updateCertificateStatus);
connect(ledger.data(), &Ledger::transferAssetToNodeResult, this, &QmlCommerce::transferAssetToNodeResult);
connect(ledger.data(), &Ledger::transferAssetToUsernameResult, this, &QmlCommerce::transferAssetToUsernameResult);
connect(ledger.data(), &Ledger::authorizeAssetTransferResult, this, &QmlCommerce::authorizeAssetTransferResult);
connect(ledger.data(), &Ledger::availableUpdatesResult, this, &QmlCommerce::availableUpdatesResult);
connect(ledger.data(), &Ledger::updateItemResult, this, &QmlCommerce::updateItemResult);
@ -246,6 +247,21 @@ void QmlCommerce::transferAssetToUsername(const QString& username,
ledger->transferAssetToUsername(key, username, certificateID, amount, optionalMessage);
}
void QmlCommerce::authorizeAssetTransfer(const QString& couponID,
const QString& certificateID,
const int& amount,
const QString& optionalMessage) {
auto ledger = DependencyManager::get<Ledger>();
auto wallet = DependencyManager::get<Wallet>();
QStringList keys = wallet->listPublicKeys();
if (keys.count() == 0) {
QJsonObject result{ { "status", "fail" }, { "message", "Uninitialized Wallet." } };
return emit authorizeAssetTransferResult(result);
}
QString key = keys[0];
ledger->authorizeAssetTransfer(key, couponID, certificateID, amount, optionalMessage);
}
void QmlCommerce::replaceContentSet(const QString& itemHref, const QString& certificateID) {
if (!certificateID.isEmpty()) {
auto ledger = DependencyManager::get<Ledger>();

View file

@ -51,6 +51,7 @@ signals:
void transferAssetToNodeResult(QJsonObject result);
void transferAssetToUsernameResult(QJsonObject result);
void authorizeAssetTransferResult(QJsonObject result);
void contentSetChanged(const QString& contentSetHref);
@ -84,6 +85,7 @@ protected:
Q_INVOKABLE void transferAssetToNode(const QString& nodeID, const QString& certificateID, const int& amount, const QString& optionalMessage);
Q_INVOKABLE void transferAssetToUsername(const QString& username, const QString& certificateID, const int& amount, const QString& optionalMessage);
Q_INVOKABLE void authorizeAssetTransfer(const QString& couponID, const QString& certificateID, const int& amount, const QString& optionalMessage);
Q_INVOKABLE void replaceContentSet(const QString& itemHref, const QString& certificateID);

View file

@ -35,7 +35,7 @@ void WalletScriptingInterface::proveAvatarEntityOwnershipVerification(const QUui
QSharedPointer<ContextOverlayInterface> contextOverlayInterface = DependencyManager::get<ContextOverlayInterface>();
EntityItemProperties entityProperties = DependencyManager::get<EntityScriptingInterface>()->getEntityProperties(entityID,
contextOverlayInterface->getEntityPropertyFlags());
if (entityProperties.getClientOnly()) {
if (entityProperties.getEntityHostType() == entity::HostType::AVATAR) {
if (!entityID.isNull() && entityProperties.getCertificateID().length() > 0) {
contextOverlayInterface->requestOwnershipVerification(entityID);
} else {

View file

@ -46,7 +46,7 @@ ContextOverlayInterface::ContextOverlayInterface() {
_entityPropertyFlags += PROP_DIMENSIONS;
_entityPropertyFlags += PROP_REGISTRATION_POINT;
_entityPropertyFlags += PROP_CERTIFICATE_ID;
_entityPropertyFlags += PROP_CLIENT_ONLY;
_entityPropertyFlags += PROP_ENTITY_HOST_TYPE;
_entityPropertyFlags += PROP_OWNING_AVATAR_ID;
auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>().data();
@ -296,7 +296,7 @@ void ContextOverlayInterface::requestOwnershipVerification(const QUuid& entityID
auto nodeList = DependencyManager::get<NodeList>();
if (entityProperties.verifyStaticCertificateProperties()) {
if (entityProperties.getClientOnly()) {
if (entityProperties.getEntityHostType() == entity::HostType::AVATAR) {
SharedNodePointer entityServer = nodeList->soloNodeOfType(NodeType::EntityServer);
if (entityServer) {

View file

@ -310,7 +310,7 @@ void Avatar::updateAvatarEntities() {
PerformanceTimer perfTimer("attachments");
// AVATAR ENTITY UPDATE FLOW
// - if queueEditEntityMessage sees clientOnly flag it does _myAvatar->updateAvatarEntity()
// - if queueEditEntityMessage sees avatarEntity flag it does _myAvatar->updateAvatarEntity()
// - updateAvatarEntity saves the bytes and flags the trait instance for the entity as updated
// - ClientTraitsHandler::sendChangedTraitsToMixer sends the entity bytes to the mixer which relays them to other interfaces
// - AvatarHashMap::processBulkAvatarTraits on other interfaces calls avatar->processTraitInstace
@ -389,7 +389,7 @@ void Avatar::updateAvatarEntities() {
QScriptValue scriptProperties = variantMapToScriptValue(asMap, scriptEngine);
EntityItemProperties properties;
EntityItemPropertiesFromScriptValueHonorReadOnly(scriptProperties, properties);
properties.setClientOnly(true);
properties.setEntityHostType(entity::HostType::AVATAR);
properties.setOwningAvatarID(getID());
// there's no entity-server to tell us we're the simulation owner, so always set the

View file

@ -2833,35 +2833,47 @@ void AvatarData::setAvatarEntityData(const AvatarEntityMap& avatarEntityData) {
qCDebug(avatars) << "discard suspect AvatarEntityData with size =" << avatarEntityData.size();
return;
}
std::vector<QUuid> deletedEntityIDs;
QList<QUuid> updatedEntityIDs;
_avatarEntitiesLock.withWriteLock([&] {
if (_avatarEntityData != avatarEntityData) {
// keep track of entities that were attached to this avatar but no longer are
AvatarEntityIDs previousAvatarEntityIDs = QSet<QUuid>::fromList(_avatarEntityData.keys());
_avatarEntityData = avatarEntityData;
setAvatarEntityDataChanged(true);
deletedEntityIDs.reserve(previousAvatarEntityIDs.size());
foreach (auto entityID, previousAvatarEntityIDs) {
if (!_avatarEntityData.contains(entityID)) {
_avatarEntityDetached.insert(entityID);
if (_clientTraitsHandler) {
// we have a client traits handler, so we flag this removed entity as deleted
// so that changes are sent next frame
_clientTraitsHandler->markInstancedTraitDeleted(AvatarTraits::AvatarEntity, entityID);
}
deletedEntityIDs.push_back(entityID);
}
}
if (_clientTraitsHandler) {
// if we have a client traits handler, flag any updated or created entities
// so that we send changes for them next frame
foreach (auto entityID, _avatarEntityData.keys()) {
_clientTraitsHandler->markInstancedTraitUpdated(AvatarTraits::AvatarEntity, entityID);
}
}
updatedEntityIDs = _avatarEntityData.keys();
}
});
if (_clientTraitsHandler) {
// we have a client traits handler
// flag removed entities as deleted so that changes are sent next frame
for (auto& deletedEntityID : deletedEntityIDs) {
_clientTraitsHandler->markInstancedTraitDeleted(AvatarTraits::AvatarEntity, deletedEntityID);
}
// flag any updated or created entities so that we send changes for them next frame
for (auto& entityID : updatedEntityIDs) {
_clientTraitsHandler->markInstancedTraitUpdated(AvatarTraits::AvatarEntity, entityID);
}
}
}
AvatarEntityIDs AvatarData::getAndClearRecentlyDetachedIDs() {

View file

@ -66,7 +66,7 @@ void ClientTraitsHandler::resetForNewMixer() {
}
void ClientTraitsHandler::sendChangedTraitsToMixer() {
Lock lock(_traitLock);
std::unique_lock<Mutex> lock(_traitLock);
if (hasChangedTraits() || _shouldPerformInitialSend) {
// we have at least one changed trait to send
@ -90,6 +90,14 @@ void ClientTraitsHandler::sendChangedTraitsToMixer() {
_traitStatuses.reset();
_hasChangedTraits = false;
// if this was an initial send of all traits, consider it completed
bool initialSend = _shouldPerformInitialSend;
_shouldPerformInitialSend = false;
// we can release the lock here since we've taken a copy of statuses
// and will setup the packet using the information in the copy
lock.unlock();
auto simpleIt = traitStatusesCopy.simpleCBegin();
while (simpleIt != traitStatusesCopy.simpleCEnd()) {
// because the vector contains all trait types (for access using trait type as index)
@ -111,12 +119,12 @@ void ClientTraitsHandler::sendChangedTraitsToMixer() {
auto instancedIt = traitStatusesCopy.instancedCBegin();
while (instancedIt != traitStatusesCopy.instancedCEnd()) {
for (auto& instanceIDValuePair : instancedIt->instances) {
if ((_shouldPerformInitialSend && instanceIDValuePair.value != Deleted)
if ((initialSend && instanceIDValuePair.value != Deleted)
|| instanceIDValuePair.value == Updated) {
// this is a changed trait we need to send or we haven't send out trait information yet
// ask the owning avatar to pack it
_owningAvatar->packTraitInstance(instancedIt->traitType, instanceIDValuePair.id, *traitsPacketList);
} else if (!_shouldPerformInitialSend && instanceIDValuePair.value == Deleted) {
} else if (!initialSend && instanceIDValuePair.value == Deleted) {
// pack delete for this trait instance
AvatarTraits::packInstancedTraitDelete(instancedIt->traitType, instanceIDValuePair.id,
*traitsPacketList);
@ -127,9 +135,6 @@ void ClientTraitsHandler::sendChangedTraitsToMixer() {
}
nodeList->sendPacketList(std::move(traitsPacketList), *avatarMixer);
// if this was an initial send of all traits, consider it completed
_shouldPerformInitialSend = false;
}
}

View file

@ -39,7 +39,7 @@ enum class RenderItemStatusIcon {
SIMULATION_OWNER = 3,
HAS_ACTIONS = 4,
OTHER_SIMULATION_OWNER = 5,
CLIENT_ONLY = 6,
ENTITY_HOST_TYPE = 6,
NONE = 255
};
@ -115,17 +115,20 @@ void EntityRenderer::makeStatusGetters(const EntityItemPointer& entity, Item::St
});
statusGetters.push_back([entity, myNodeID] () -> render::Item::Status::Value {
if (entity->getClientOnly()) {
if (entity->isAvatarEntity()) {
if (entity->getOwningAvatarID() == myNodeID) {
return render::Item::Status::Value(1.0f, render::Item::Status::Value::GREEN,
(unsigned char)RenderItemStatusIcon::CLIENT_ONLY);
(unsigned char)RenderItemStatusIcon::ENTITY_HOST_TYPE);
} else {
return render::Item::Status::Value(1.0f, render::Item::Status::Value::RED,
(unsigned char)RenderItemStatusIcon::CLIENT_ONLY);
(unsigned char)RenderItemStatusIcon::ENTITY_HOST_TYPE);
}
} else if (entity->isLocalEntity()) {
return render::Item::Status::Value(1.0f, render::Item::Status::Value::BLUE,
(unsigned char)RenderItemStatusIcon::ENTITY_HOST_TYPE);
}
return render::Item::Status::Value(0.0f, render::Item::Status::Value::GREEN,
(unsigned char)RenderItemStatusIcon::CLIENT_ONLY);
(unsigned char)RenderItemStatusIcon::ENTITY_HOST_TYPE);
});
}

View file

@ -84,16 +84,19 @@ void EntityEditPacketSender::queueEditEntityMessage(PacketType type,
EntityTreePointer entityTree,
EntityItemID entityItemID,
const EntityItemProperties& properties) {
if (properties.getClientOnly()) {
if (properties.getEntityHostType() == entity::HostType::AVATAR) {
if (!_myAvatar) {
qCWarning(entities) << "Suppressing entity edit message: cannot send clientOnly edit with no myAvatar";
qCWarning(entities) << "Suppressing entity edit message: cannot send avatar entity edit with no myAvatar";
} else if (properties.getOwningAvatarID() == _myAvatar->getID()) {
// this is an avatar-based entity --> update our avatar-data rather than sending to the entity-server
queueEditAvatarEntityMessage(type, entityTree, entityItemID, properties);
} else {
qCWarning(entities) << "Suppressing entity edit message: cannot send clientOnly edit for another avatar";
qCWarning(entities) << "Suppressing entity edit message: cannot send avatar entity edit for another avatar";
}
return;
} else if (properties.getEntityHostType() == entity::HostType::LOCAL) {
// Don't send edits for local entities
return;
}
if (entityTree && entityTree->isServerlessMode()) {

View file

@ -119,7 +119,7 @@ EntityPropertyFlags EntityItem::getEntityProperties(EncodeBitstreamParams& param
requestedProperties += PROP_PARENT_JOINT_INDEX;
requestedProperties += PROP_QUERY_AA_CUBE;
requestedProperties += PROP_CLIENT_ONLY;
requestedProperties += PROP_ENTITY_HOST_TYPE;
requestedProperties += PROP_OWNING_AVATAR_ID;
requestedProperties += PROP_LAST_EDITED_BY;
@ -172,7 +172,7 @@ OctreeElement::AppendState EntityItem::appendEntityData(OctreePacketData* packet
EntityPropertyFlags propertyFlags(PROP_LAST_ITEM);
EntityPropertyFlags requestedProperties = getEntityProperties(params);
requestedProperties -= PROP_CLIENT_ONLY;
requestedProperties -= PROP_ENTITY_HOST_TYPE;
requestedProperties -= PROP_OWNING_AVATAR_ID;
// If we are being called for a subsequent pass at appendEntityData() that failed to completely encode this item,
@ -1278,10 +1278,7 @@ EntityItemProperties EntityItem::getProperties(const EntityPropertyFlags& desire
EntityItemProperties properties(propertyFlags);
properties._id = getID();
properties._idSet = true;
properties._created = getCreated();
properties._lastEdited = getLastEdited();
properties.setClientOnly(getClientOnly());
properties.setOwningAvatarID(getOwningAvatarID());
properties._type = getType();
@ -1337,7 +1334,7 @@ EntityItemProperties EntityItem::getProperties(const EntityPropertyFlags& desire
COPY_ENTITY_PROPERTY_TO_PROPERTIES(localPosition, getLocalPosition);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(localRotation, getLocalOrientation);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(clientOnly, getClientOnly);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(entityHostType, getEntityHostType);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(owningAvatarID, getOwningAvatarID);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(lastEditedBy, getLastEditedBy);
@ -1479,7 +1476,7 @@ bool EntityItem::setProperties(const EntityItemProperties& properties) {
SET_ENTITY_PROPERTY_FROM_PROPERTIES(parentJointIndex, setParentJointIndex);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(queryAACube, setQueryAACube);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(clientOnly, setClientOnly);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(entityHostType, setEntityHostType);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(owningAvatarID, setOwningAvatarID);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(lastEditedBy, setLastEditedBy);
@ -2496,7 +2493,7 @@ void EntityItem::dimensionsChanged() {
bool EntityItem::getScalesWithParent() const {
// keep this logic the same as in EntityItemProperties::getScalesWithParent
if (getClientOnly()) {
if (isAvatarEntity()) {
QUuid ancestorID = findAncestorOfType(NestableType::Avatar);
return !ancestorID.isNull();
} else {
@ -3277,7 +3274,7 @@ void EntityItem::prepareForSimulationOwnershipBid(EntityItemProperties& properti
properties.setSimulationOwner(Physics::getSessionUUID(), priority);
setPendingOwnershipPriority(priority);
properties.setClientOnly(getClientOnly());
properties.setEntityHostType(getEntityHostType());
properties.setOwningAvatarID(getOwningAvatarID());
setLastBroadcast(now); // for debug/physics status icons
}
}

View file

@ -64,6 +64,18 @@ const uint64_t MAX_INCOMING_SIMULATION_UPDATE_PERIOD = MAX_OUTGOING_SIMULATION_U
class MeshProxyList;
#ifdef DOMAIN
#undef DOMAIN
#endif
namespace entity {
enum class HostType {
DOMAIN = 0,
AVATAR,
LOCAL
};
}
/// EntityItem class this is the base class for all entity types. It handles the basic properties and functionality available
/// to all other entity types. In particular: postion, size, rotation, age, lifetime, velocity, gravity. You can not instantiate
/// one directly, instead you must only construct one of it's derived classes with additional features.
@ -478,9 +490,13 @@ public:
void setScriptHasFinishedPreload(bool value);
bool isScriptPreloadFinished();
bool getClientOnly() const { return _clientOnly; }
virtual void setClientOnly(bool clientOnly) { _clientOnly = clientOnly; }
// if this entity is client-only, which avatar is it associated with?
bool isDomainEntity() const { return _hostType == entity::HostType::DOMAIN; }
bool isAvatarEntity() const { return _hostType == entity::HostType::AVATAR; }
bool isLocalEntity() const { return _hostType == entity::HostType::LOCAL; }
entity::HostType getEntityHostType() const { return _hostType; }
virtual void setEntityHostType(entity::HostType hostType) { _hostType = hostType; }
// if this entity is an avatar entity, which avatar is it associated with?
QUuid getOwningAvatarID() const { return _owningAvatarID; }
virtual void setOwningAvatarID(const QUuid& owningAvatarID) { _owningAvatarID = owningAvatarID; }
@ -673,7 +689,7 @@ protected:
QUuid _sourceUUID; /// the server node UUID we came from
bool _clientOnly { false };
entity::HostType _hostType { entity::HostType::DOMAIN };
bool _transitingWithAvatar{ false };
QUuid _owningAvatarID;

View file

@ -306,6 +306,29 @@ void EntityItemProperties::setMaterialMappingModeFromString(const QString& mater
}
}
QString EntityItemProperties::getEntityHostTypeAsString() const {
switch (_entityHostType) {
case entity::HostType::DOMAIN:
return "domain";
case entity::HostType::AVATAR:
return "avatar";
case entity::HostType::LOCAL:
return "local";
default:
return "";
}
}
void EntityItemProperties::setEntityHostTypeFromString(const QString& entityHostType) {
if (entityHostType == "domain") {
_entityHostType = entity::HostType::DOMAIN;
} else if (entityHostType == "avatar") {
_entityHostType = entity::HostType::AVATAR;
} else if (entityHostType == "local") {
_entityHostType = entity::HostType::LOCAL;
}
}
EntityPropertyFlags EntityItemProperties::getChangedProperties() const {
EntityPropertyFlags changedProperties;
@ -454,7 +477,7 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const {
CHECK_PROPERTY_CHANGE(PROP_GHOSTING_ALLOWED, ghostingAllowed);
CHECK_PROPERTY_CHANGE(PROP_FILTER_URL, filterURL);
CHECK_PROPERTY_CHANGE(PROP_CLIENT_ONLY, clientOnly);
CHECK_PROPERTY_CHANGE(PROP_ENTITY_HOST_TYPE, entityHostType);
CHECK_PROPERTY_CHANGE(PROP_OWNING_AVATAR_ID, owningAvatarID);
CHECK_PROPERTY_CHANGE(PROP_SHAPE, shape);
@ -490,12 +513,18 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const {
* @property {Entities.EntityType} type - The entity type. You cannot change the type of an entity after it's created. (Though
* its value may switch among <code>"Box"</code>, <code>"Shape"</code>, and <code>"Sphere"</code> depending on changes to
* the <code>shape</code> property set for entities of these types.) <em>Read-only.</em>
* @property {boolean} clientOnly=false - If <code>true</code> then the entity is an avatar entity; otherwise it is a server
* entity. An avatar entity follows you to each domain you visit, rendering at the same world coordinates unless it's
* parented to your avatar. <em>Value cannot be changed after the entity is created.</em><br />
* The value can also be set at entity creation by using the <code>clientOnly</code> parameter in
* @property {EntityHostType} entityHostType="domain" - How this entity will behave, including if and how it is sent to other people.
* The value can only be set at entity creation by using the <code>entityHostType</code> parameter in
* {@link Entities.addEntity}.
* @property {Uuid} owningAvatarID=Uuid.NULL - The session ID of the owning avatar if <code>clientOnly</code> is
* @property {boolean} avatarEntity=false - If <code>true</code> then the entity is an avatar entity; An avatar entity follows you to each domain you visit,
* rendering at the same world coordinates unless it's parented to your avatar. <em>Value cannot be changed after the entity is created.</em><br />
* The value can only be set at entity creation by using the <code>entityHostType</code> parameter in
* {@link Entities.addEntity}. <code>clientOnly</code> is an alias.
* @property {boolean} localEntity=false - If <code>true</code> then the entity is a local entity; Local entities only render for you and are not sent over the wire.
* <em>Value cannot be changed after the entity is created.</em><br />
* The value can only be set at entity creation by using the <code>entityHostType</code> parameter in
* {@link Entities.addEntity}.
* @property {Uuid} owningAvatarID=Uuid.NULL - The session ID of the owning avatar if <code>avatarEntity</code> is
* <code>true</code>, otherwise {@link Uuid|Uuid.NULL}. <em>Read-only.</em>
*
* @property {string} created - The UTC date and time that the entity was created, in ISO 8601 format as
@ -595,7 +624,7 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const {
* @property {number} parentJointIndex=65535 - The joint of the entity or avatar that this entity is parented to. Use
* <code>65535</code> or <code>-1</code> to parent to the entity or avatar's position and orientation rather than a joint.
* @property {Vec3} localPosition=0,0,0 - The position of the entity relative to its parent if the entity is parented,
* otherwise the same value as <code>position</code>. If the entity is parented to an avatar and is <code>clientOnly</code>
* otherwise the same value as <code>position</code>. If the entity is parented to an avatar and is an <code>avatarEntity</code>
* so that it scales with the avatar, this value remains the original local position value while the avatar scale changes.
* @property {Quat} localRotation=0,0,0,1 - The rotation of the entity relative to its parent if the entity is parented,
* otherwise the same value as <code>rotation</code>.
@ -603,8 +632,8 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const {
* otherwise the same value as <code>velocity</code>.
* @property {Vec3} localAngularVelocity=0,0,0 - The angular velocity of the entity relative to its parent if the entity is
* parented, otherwise the same value as <code>position</code>.
* @property {Vec3} localDimensions - The dimensions of the entity. If the entity is parented to an avatar and is
* <code>clientOnly</code> so that it scales with the avatar, this value remains the original dimensions value while the
* @property {Vec3} localDimensions - The dimensions of the entity. If the entity is parented to an avatar and is an
* <code>avatarEntity</code> so that it scales with the avatar, this value remains the original dimensions value while the
* avatar scale changes.
*
* @property {Entities.BoundingBox} boundingBox - The axis-aligned bounding box that tightly encloses the entity.
@ -629,7 +658,7 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const {
* @property {boolean} cloneDynamic=false - If <code>true</code> then clones created from this entity will have their
* <code>dynamic</code> property set to <code>true</code>.
* @property {boolean} cloneAvatarEntity=false - If <code>true</code> then clones created from this entity will be created as
* avatar entities: their <code>clientOnly</code> property will be set to <code>true</code>.
* avatar entities: their <code>avatarEntity</code> property will be set to <code>true</code>.
* @property {Uuid} cloneOriginID - The ID of the entity that this entity was cloned from.
*
* @property {Entities.Grab} grab - The grab-related properties.
@ -740,7 +769,7 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const {
* overlay's ID.
* To apply a material to an avatar, set the material entity's <code>parentID</code> property to the avatar's session UUID.
* To apply a material to your avatar such that it persists across domains and log-ins, create the material as an avatar entity
* by setting the <code>clientOnly</code> parameter in {@link Entities.addEntity} to <code>true</code>.
* by setting the <code>entityHostType</code> parameter in {@link Entities.addEntity} to <code>"avatar"</code>.
* Material entities render as non-scalable spheres if they don't have their parent set.
* @typedef {object} Entities.EntityProperties-Material
* @property {string} materialURL="" - URL to a {@link MaterialResource}. If you append <code>?name</code> to the URL, the
@ -1532,8 +1561,8 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_LOCAL_ANGULAR_VELOCITY, localAngularVelocity);
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_LOCAL_DIMENSIONS, localDimensions);
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_CLIENT_ONLY, clientOnly); // Gettable but not settable except at entity creation
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_OWNING_AVATAR_ID, owningAvatarID); // Gettable but not settable
COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_ENTITY_HOST_TYPE, entityHostType, getEntityHostTypeAsString()); // Gettable but not settable except at entity creation
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_OWNING_AVATAR_ID, owningAvatarID); // Gettable but not settable
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_CLONEABLE, cloneable);
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_CLONE_LIFETIME, cloneLifetime);
@ -1572,12 +1601,14 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool
COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER_NO_SKIP(renderInfo, renderInfo); // Gettable but not settable
}
// FIXME: These properties should already have been set above.
if (!psuedoPropertyFlagsActive || psueudoPropertyFlags.test(EntityPsuedoPropertyFlag::ClientOnly)) {
properties.setProperty("clientOnly", convertScriptValue(engine, getClientOnly()));
properties.setProperty("clientOnly", convertScriptValue(engine, getEntityHostType() == entity::HostType::AVATAR));
}
if (!psuedoPropertyFlagsActive || psueudoPropertyFlags.test(EntityPsuedoPropertyFlag::OwningAvatarID)) {
properties.setProperty("owningAvatarID", convertScriptValue(engine, getOwningAvatarID()));
if (!psuedoPropertyFlagsActive || psueudoPropertyFlags.test(EntityPsuedoPropertyFlag::AvatarEntity)) {
properties.setProperty("avatarEntity", convertScriptValue(engine, getEntityHostType() == entity::HostType::AVATAR));
}
if (!psuedoPropertyFlagsActive || psueudoPropertyFlags.test(EntityPsuedoPropertyFlag::LocalEntity)) {
properties.setProperty("localEntity", convertScriptValue(engine, getEntityHostType() == entity::HostType::LOCAL));
}
// FIXME - I don't think these properties are supported any more
@ -1767,7 +1798,7 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool
COPY_PROPERTY_FROM_QSCRIPTVALUE(ghostingAllowed, bool, setGhostingAllowed);
COPY_PROPERTY_FROM_QSCRIPTVALUE(filterURL, QString, setFilterURL);
COPY_PROPERTY_FROM_QSCRIPTVALUE(clientOnly, bool, setClientOnly);
COPY_PROPERTY_FROM_QSCRIPTVALUE_ENUM(entityHostType, EntityHostType);
COPY_PROPERTY_FROM_QSCRIPTVALUE(owningAvatarID, QUuid, setOwningAvatarID);
COPY_PROPERTY_FROM_QSCRIPTVALUE(dpi, uint16_t, setDPI);
@ -1933,7 +1964,7 @@ void EntityItemProperties::merge(const EntityItemProperties& other) {
COPY_PROPERTY_IF_CHANGED(ghostingAllowed);
COPY_PROPERTY_IF_CHANGED(filterURL);
COPY_PROPERTY_IF_CHANGED(clientOnly);
COPY_PROPERTY_IF_CHANGED(entityHostType);
COPY_PROPERTY_IF_CHANGED(owningAvatarID);
COPY_PROPERTY_IF_CHANGED(dpi);
@ -3223,7 +3254,7 @@ void EntityItemProperties::markAllChanged() {
_ghostingAllowedChanged = true;
_filterURLChanged = true;
_clientOnlyChanged = true;
_entityHostTypeChanged = true;
_owningAvatarIDChanged = true;
_dpiChanged = true;
@ -3725,8 +3756,8 @@ QList<QString> EntityItemProperties::listChangedProperties() {
out += "queryAACube";
}
if (clientOnlyChanged()) {
out += "clientOnly";
if (entityHostTypeChanged()) {
out += "entityHostType";
}
if (owningAvatarIDChanged()) {
out += "owningAvatarID";
@ -3801,7 +3832,7 @@ bool EntityItemProperties::getScalesWithParent() const {
if (success && parent) {
bool avatarAncestor = (parent->getNestableType() == NestableType::Avatar ||
parent->hasAncestorOfType(NestableType::Avatar));
scalesWithParent = getClientOnly() && avatarAncestor;
scalesWithParent = getEntityHostType() == entity::HostType::AVATAR && avatarAncestor;
}
}
return scalesWithParent;
@ -3959,7 +3990,13 @@ void EntityItemProperties::convertToCloneProperties(const EntityItemID& entityID
setParentJointIndex(-1);
setLifetime(getCloneLifetime());
setDynamic(getCloneDynamic());
setClientOnly(getCloneAvatarEntity());
if (getEntityHostType() != entity::HostType::LOCAL) {
setEntityHostType(getCloneAvatarEntity() ? entity::HostType::AVATAR : entity::HostType::DOMAIN);
} else {
// Local Entities clone as local entities
setEntityHostType(entity::HostType::LOCAL);
setCollisionless(true);
}
setCreated(usecTimestampNow());
setLastEdited(usecTimestampNow());
setCloneable(ENTITY_ITEM_DEFAULT_CLONEABLE);

View file

@ -280,7 +280,7 @@ public:
DEFINE_PROPERTY(PROP_GHOSTING_ALLOWED, GhostingAllowed, ghostingAllowed, bool, ZoneEntityItem::DEFAULT_GHOSTING_ALLOWED);
DEFINE_PROPERTY(PROP_FILTER_URL, FilterURL, filterURL, QString, ZoneEntityItem::DEFAULT_FILTER_URL);
DEFINE_PROPERTY(PROP_CLIENT_ONLY, ClientOnly, clientOnly, bool, false);
DEFINE_PROPERTY_REF_ENUM(PROP_ENTITY_HOST_TYPE, EntityHostType, entityHostType, entity::HostType, entity::HostType::DOMAIN);
DEFINE_PROPERTY_REF(PROP_OWNING_AVATAR_ID, OwningAvatarID, owningAvatarID, QUuid, UNKNOWN_ENTITY_ID);
DEFINE_PROPERTY_REF(PROP_DPI, DPI, dpi, uint16_t, ENTITY_ITEM_DEFAULT_DPI);
@ -590,7 +590,7 @@ inline QDebug operator<<(QDebug debug, const EntityItemProperties& properties) {
DEBUG_PROPERTY_IF_CHANGED(debug, properties, GhostingAllowed, ghostingAllowed, "");
DEBUG_PROPERTY_IF_CHANGED(debug, properties, FilterURL, filterURL, "");
DEBUG_PROPERTY_IF_CHANGED(debug, properties, ClientOnly, clientOnly, "");
DEBUG_PROPERTY_IF_CHANGED(debug, properties, EntityHostTypeAsString, entityHostType, "");
DEBUG_PROPERTY_IF_CHANGED(debug, properties, OwningAvatarID, owningAvatarID, "");
DEBUG_PROPERTY_IF_CHANGED(debug, properties, LastEditedBy, lastEditedBy, "");

View file

@ -121,7 +121,7 @@ QDebug& operator<<(QDebug& dbg, const EntityPropertyFlags& f) {
result = f.getHasProperty(PROP_FALLOFF_RADIUS) ? result + "falloffRadius " : result;
result = f.getHasProperty(PROP_FLYING_ALLOWED) ? result + "flyingAllowed " : result;
result = f.getHasProperty(PROP_GHOSTING_ALLOWED) ? result + "ghostingAllowed " : result;
result = f.getHasProperty(PROP_CLIENT_ONLY) ? result + "clientOnly " : result;
result = f.getHasProperty(PROP_ENTITY_HOST_TYPE) ? result + "entityHostType " : result;
result = f.getHasProperty(PROP_OWNING_AVATAR_ID) ? result + "owningAvatarID " : result;
result = f.getHasProperty(PROP_SHAPE) ? result + "shape " : result;
result = f.getHasProperty(PROP_DPI) ? result + "dpi " : result;

View file

@ -176,7 +176,7 @@ enum EntityPropertyList {
PROP_FLYING_ALLOWED, // can avatars in a zone fly?
PROP_GHOSTING_ALLOWED, // can avatars in a zone turn off physics?
PROP_CLIENT_ONLY, // doesn't go over wire
PROP_ENTITY_HOST_TYPE, // doesn't go over wire
PROP_OWNING_AVATAR_ID, // doesn't go over wire
PROP_SHAPE,

View file

@ -32,6 +32,8 @@ namespace EntityPsuedoPropertyFlag {
RenderInfo,
ClientOnly,
OwningAvatarID,
AvatarEntity,
LocalEntity,
NumFlags
};

View file

@ -470,7 +470,7 @@ void synchronizeEditedGrabProperties(EntityItemProperties& properties, const QSt
}
QUuid EntityScriptingInterface::addEntity(const EntityItemProperties& properties, bool clientOnly) {
QUuid EntityScriptingInterface::addEntity(const EntityItemProperties& properties, const QString& entityHostTypeString) {
PROFILE_RANGE(script_entities, __FUNCTION__);
_activityTracking.addedEntityCount++;
@ -479,9 +479,13 @@ QUuid EntityScriptingInterface::addEntity(const EntityItemProperties& properties
const auto sessionID = nodeList->getSessionUUID();
EntityItemProperties propertiesWithSimID = properties;
if (clientOnly) {
propertiesWithSimID.setClientOnly(clientOnly);
propertiesWithSimID.setEntityHostTypeFromString(entityHostTypeString);
if (propertiesWithSimID.getEntityHostType() == entity::HostType::AVATAR) {
propertiesWithSimID.setOwningAvatarID(sessionID);
} else if (propertiesWithSimID.getEntityHostType() == entity::HostType::LOCAL) {
// For now, local entities are always collisionless
// TODO: create a separate, local physics simulation that just handles local entities (and MyAvatar?)
propertiesWithSimID.setCollisionless(true);
}
propertiesWithSimID.setLastEditedBy(sessionID);
@ -570,8 +574,11 @@ QUuid EntityScriptingInterface::cloneEntity(QUuid entityIDToClone) {
bool cloneAvatarEntity = properties.getCloneAvatarEntity();
properties.convertToCloneProperties(entityIDToClone);
if (cloneAvatarEntity) {
return addEntity(properties, true);
if (properties.getEntityHostType() == entity::HostType::LOCAL) {
// Local entities are only cloned locally
return addEntity(properties, "local");
} else if (cloneAvatarEntity) {
return addEntity(properties, "avatar");
} else {
// setLastEdited timestamp to 0 to ensure this entity gets updated with the properties
// from the server-created entity, don't change this unless you know what you are doing
@ -683,6 +690,10 @@ QScriptValue EntityScriptingInterface::getMultipleEntityPropertiesInternal(QScri
psuedoPropertyFlags.set(EntityPsuedoPropertyFlag::ClientOnly);
} else if (extendedPropertyString == "owningAvatarID") {
psuedoPropertyFlags.set(EntityPsuedoPropertyFlag::OwningAvatarID);
} else if (extendedPropertyString == "avatarEntity") {
psuedoPropertyFlags.set(EntityPsuedoPropertyFlag::AvatarEntity);
} else if (extendedPropertyString == "localEntity") {
psuedoPropertyFlags.set(EntityPsuedoPropertyFlag::LocalEntity);
}
};
@ -786,7 +797,7 @@ QUuid EntityScriptingInterface::editEntity(QUuid id, const EntityItemProperties&
return;
}
if (entity->getClientOnly() && entity->getOwningAvatarID() != sessionID) {
if (entity->isAvatarEntity() && entity->getOwningAvatarID() != sessionID) {
// don't edit other avatar's avatarEntities
properties = EntityItemProperties();
return;
@ -829,7 +840,11 @@ QUuid EntityScriptingInterface::editEntity(QUuid id, const EntityItemProperties&
}
// set these to make EntityItemProperties::getScalesWithParent() work correctly
properties.setClientOnly(entity->getClientOnly());
entity::HostType entityHostType = entity->getEntityHostType();
properties.setEntityHostType(entityHostType);
if (entityHostType == entity::HostType::LOCAL) {
properties.setCollisionless(true);
}
properties.setOwningAvatarID(entity->getOwningAvatarID());
properties.setActionData(entity->getDynamicData());
@ -956,7 +971,7 @@ void EntityScriptingInterface::deleteEntity(QUuid id) {
auto nodeList = DependencyManager::get<NodeList>();
const QUuid myNodeID = nodeList->getSessionUUID();
if (entity->getClientOnly() && entity->getOwningAvatarID() != myNodeID) {
if (entity->isAvatarEntity() && entity->getOwningAvatarID() != myNodeID) {
// don't delete other avatar's avatarEntities
shouldSendDeleteToServer = false;
return;
@ -966,11 +981,11 @@ void EntityScriptingInterface::deleteEntity(QUuid id) {
shouldSendDeleteToServer = false;
} else {
// only delete local entities, server entities will round trip through the server filters
if (entity->getClientOnly() || _entityTree->isServerlessMode()) {
if (entity->isAvatarEntity() || _entityTree->isServerlessMode()) {
shouldSendDeleteToServer = false;
_entityTree->deleteEntity(entityID);
if (entity->getClientOnly() && getEntityPacketSender()->getMyAvatar()) {
if (entity->isAvatarEntity() && getEntityPacketSender()->getMyAvatar()) {
getEntityPacketSender()->getMyAvatar()->clearAvatarEntity(entityID, false);
}
}
@ -1637,14 +1652,14 @@ bool EntityScriptingInterface::actionWorker(const QUuid& entityID,
return;
}
if (entity->getClientOnly() && entity->getOwningAvatarID() != myNodeID) {
if (entity->isAvatarEntity() && entity->getOwningAvatarID() != myNodeID) {
return;
}
doTransmit = actor(simulation, entity);
_entityTree->entityChanged(entity);
if (doTransmit) {
properties.setClientOnly(entity->getClientOnly());
properties.setEntityHostType(entity->getEntityHostType());
properties.setOwningAvatarID(entity->getOwningAvatarID());
}
});

View file

@ -233,13 +233,29 @@ public slots:
*/
Q_INVOKABLE bool canReplaceContent();
/**jsdoc
* <p>How an entity is sent over the wire.</p>
* <table>
* <thead>
* <tr><th>Value</th><th>Description</th></tr>
* </thead>
* <tbody>
* <tr><td><code>domain</code></td><td>Domain entities are sent over the entity server to everyone else</td></tr>
* <tr><td><code>avatar</code></td><td>Avatar entities are sent over the avatar entity and are associated with one avatar</td></tr>
* <tr><td><code>local</code></td><td>Local entities are not sent over the wire and will only render for you, locally</td></tr>
* </tbody>
* </table>
* @typedef {string} EntityHostType
*/
/**jsdoc
* Add a new entity with specified properties.
* @function Entities.addEntity
* @param {Entities.EntityProperties} properties - The properties of the entity to create.
* @param {boolean} [clientOnly=false] - If <code>true</code>, or if <code>clientOnly</code> is set <code>true</code> in
* the properties, the entity is created as an avatar entity; otherwise it is created on the server. An avatar entity
* @param {EntityHostType} [entityHostType="domain"] - If <code>"avatar"</code> the entity is created as an avatar entity. An avatar entity
* follows you to each domain you visit, rendering at the same world coordinates unless it's parented to your avatar.
* If <code>"local"</code>, the entity is created as a local entity, which will only render for you and isn't sent over the wire.
* Otherwise it is created as a normal entity and sent over the entity server.
* @returns {Uuid} The ID of the entity if successfully created, otherwise {@link Uuid|Uuid.NULL}.
* @example <caption>Create a box entity in front of your avatar.</caption>
* var entityID = Entities.addEntity({
@ -250,7 +266,19 @@ public slots:
* });
* print("Entity created: " + entityID);
*/
Q_INVOKABLE QUuid addEntity(const EntityItemProperties& properties, bool clientOnly = false);
Q_INVOKABLE QUuid addEntity(const EntityItemProperties& properties, const QString& entityHostTypeString);
/**jsdoc
* Add a new entity with specified properties.
* @function Entities.addEntity
* @param {Entities.EntityProperties} properties - The properties of the entity to create.
* @param {boolean} [avatarEntity=false] - Whether to create an avatar entity or a domain entity
* @returns {Uuid} The ID of the entity if successfully created, otherwise {@link Uuid|Uuid.NULL}.
*/
Q_INVOKABLE QUuid addEntity(const EntityItemProperties& properties, bool avatarEntity = false) {
QString entityHostType = avatarEntity ? "avatar" : "domain";
return addEntity(properties, entityHostType);
}
/// temporary method until addEntity can be used from QJSEngine
/// Deliberately not adding jsdoc, only used internally.
@ -896,8 +924,7 @@ public slots:
Q_INVOKABLE bool appendPoint(QUuid entityID, const glm::vec3& point);
/**jsdoc
* Dumps debug information about all entities in Interface's local in-memory tree of entities it knows about &mdash; domain
* and client-only &mdash; to the program log.
* Dumps debug information about all entities in Interface's local in-memory tree of entities it knows about to the program log.
* @function Entities.dumpTree
*/
Q_INVOKABLE void dumpTree() const;
@ -1870,7 +1897,7 @@ signals:
/**jsdoc
* Triggered when an entity is added to Interface's local in-memory tree of entities it knows about. This may occur when
* entities are loaded upon visiting a domain, when the user rotates their view so that more entities become visible, and
* when a domain or client-only entity is added (e.g., by {@Entities.addEntity|addEntity}).
* when any type of entity is added (e.g., by {@Entities.addEntity|addEntity}).
* @function Entities.addingEntity
* @param {Uuid} entityID - The ID of the entity added.
* @returns {Signal}

View file

@ -542,7 +542,7 @@ EntityItemPointer EntityTree::addEntity(const EntityItemID& entityID, const Enti
return nullptr;
}
if (!properties.getClientOnly() && getIsClient() &&
if (properties.getEntityHostType() == entity::HostType::DOMAIN && getIsClient() &&
!nodeList->getThisNodeCanRez() && !nodeList->getThisNodeCanRezTmp() &&
!nodeList->getThisNodeCanRezCertified() && !nodeList->getThisNodeCanRezTmpCertified() && !_serverlessDomain && !isClone) {
return nullptr;
@ -2669,7 +2669,15 @@ bool EntityTree::readFromMap(QVariantMap& map) {
entityItemID = EntityItemID(QUuid::createUuid());
}
if (properties.getClientOnly()) {
// Convert old clientOnly bool to new entityHostType enum
// (must happen before setOwningAvatarID below)
if (contentVersion < (int)EntityVersion::EntityHostTypes) {
if (entityMap.contains("clientOnly")) {
properties.setEntityHostType(entityMap["clientOnly"].toBool() ? entity::HostType::AVATAR : entity::HostType::DOMAIN);
}
}
if (properties.getEntityHostType() == entity::HostType::AVATAR) {
auto nodeList = DependencyManager::get<NodeList>();
const QUuid myNodeID = nodeList->getSessionUUID();
properties.setOwningAvatarID(myNodeID);

View file

@ -33,7 +33,7 @@ PacketVersion versionForPacketType(PacketType packetType) {
case PacketType::EntityEdit:
case PacketType::EntityData:
case PacketType::EntityPhysics:
return static_cast<PacketVersion>(EntityVersion::MaterialRepeat);
return static_cast<PacketVersion>(EntityVersion::EntityHostTypes);
case PacketType::EntityQuery:
return static_cast<PacketVersion>(EntityQueryPacketVersion::ConicalFrustums);
case PacketType::AvatarIdentity:

View file

@ -245,7 +245,8 @@ enum class EntityVersion : PacketVersion {
GrabProperties,
ScriptGlmVectors,
FixedLightSerialization,
MaterialRepeat
MaterialRepeat,
EntityHostTypes
};
enum class EntityScriptCallMethodVersion : PacketVersion {

View file

@ -80,8 +80,8 @@ EntityMotionState::EntityMotionState(btCollisionShape* shape, EntityItemPointer
// rather than pass the legit shape pointer to the ObjectMotionState ctor above.
setShape(shape);
if (_entity->getClientOnly() && _entity->getOwningAvatarID() != Physics::getSessionUUID()) {
// client-only entities are always thus, so we cache this fact in _ownershipState
if (_entity->isAvatarEntity() && _entity->getOwningAvatarID() != Physics::getSessionUUID()) {
// avatar entities entities are always thus, so we cache this fact in _ownershipState
_ownershipState = EntityMotionState::OwnershipState::Unownable;
}
@ -430,7 +430,7 @@ bool EntityMotionState::shouldSendUpdate(uint32_t simulationStep) {
assert(entityTreeIsLocked());
// this case is prevented by setting _ownershipState to UNOWNABLE in EntityMotionState::ctor
assert(!(_entity->getClientOnly() && _entity->getOwningAvatarID() != Physics::getSessionUUID()));
assert(!(_entity->isAvatarEntity() && _entity->getOwningAvatarID() != Physics::getSessionUUID()));
if (_entity->getTransitingWithAvatar()) {
return false;
@ -595,7 +595,7 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_
EntityTreeElementPointer element = _entity->getElement();
EntityTreePointer tree = element ? element->getTree() : nullptr;
properties.setClientOnly(_entity->getClientOnly());
properties.setEntityHostType(_entity->getEntityHostType());
properties.setOwningAvatarID(_entity->getOwningAvatarID());
entityPacketSender->queueEditEntityMessage(PacketType::EntityPhysics, tree, id, properties);
@ -610,7 +610,7 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_
EntityItemProperties newQueryCubeProperties;
newQueryCubeProperties.setQueryAACube(descendant->getQueryAACube());
newQueryCubeProperties.setLastEdited(properties.getLastEdited());
newQueryCubeProperties.setClientOnly(entityDescendant->getClientOnly());
newQueryCubeProperties.setEntityHostType(entityDescendant->getEntityHostType());
newQueryCubeProperties.setOwningAvatarID(entityDescendant->getOwningAvatarID());
entityPacketSender->queueEditEntityMessage(PacketType::EntityPhysics, tree,

View file

@ -79,7 +79,7 @@ void PhysicalEntitySimulation::removeEntityInternal(EntityItemPointer entity) {
_deadEntities.insert(entity);
}
}
if (entity->getClientOnly()) {
if (entity->isAvatarEntity()) {
_deadAvatarEntities.insert(entity);
}
}
@ -339,7 +339,7 @@ void PhysicalEntitySimulation::handleDeactivatedMotionStates(const VectorOfMotio
EntityItemPointer entity = entityState->getEntity();
_entitiesToSort.insert(entity);
if (!serverlessMode) {
if (entity->getClientOnly()) {
if (entity->isAvatarEntity()) {
switch (entityState->getOwnershipState()) {
case EntityMotionState::OwnershipState::PendingBid:
_bids.removeFirst(entityState);

View file

@ -319,6 +319,7 @@ public:
glBindVertexArray(0);
glDeleteVertexArrays(1, &_vao);
_canvas->doneCurrent();
_canvas->moveToThread(_plugin.thread());
}
void update(const CompositeInfo& newCompositeInfo) { _queue.push(newCompositeInfo); }
@ -485,6 +486,7 @@ bool OpenVrDisplayPlugin::internalActivate() {
_submitCanvas->doneCurrent();
});
}
_submitCanvas->moveToThread(_submitThread.get());
}
return Parent::internalActivate();

View file

@ -321,7 +321,7 @@
"jsPropertyName": "parentMaterialName"
},
"selectSubmesh": {
"tooltip": "If enabled, \"Select Submesh\" property will show up, otherwise \"Material Name to Replace\" will be shown.",
"tooltip": "If enabled, \"Submesh to Replace\" property will show up, otherwise \"Material to Replace\" will be shown.",
"skipJSProperty": true
},
"priority": {

View file

@ -186,7 +186,7 @@ function AttachedEntitiesManager() {
delete wearProps.actionData;
delete wearProps.sittingPoints;
delete wearProps.boundingBox;
delete wearProps.clientOnly;
delete wearProps.avatarEntity;
delete wearProps.owningAvatarID;
delete wearProps.localPosition;
delete wearProps.localRotation;

View file

@ -328,8 +328,8 @@ function isGrabbable(entityID) {
return false;
}
var properties = Entities.getEntityProperties(entityID, ['clientOnly', 'grab.grabbable']);
if (properties.clientOnly) {
var properties = Entities.getEntityProperties(entityID, ['avatarEntity', 'grab.grabbable']);
if (properties.avatarEntity) {
return properties.grab.grabbable;
}
@ -337,8 +337,8 @@ function isGrabbable(entityID) {
}
function setGrabbable(entityID, grabbable) {
var properties = Entities.getEntityProperties(entityID, ['clientOnly']);
if (properties.clientOnly) {
var properties = Entities.getEntityProperties(entityID, ['avatarEntity']);
if (properties.avatarEntity) {
Entities.editEntity(entityID, { grab: { grabbable: grabbable }});
}
}

View file

@ -598,6 +598,7 @@ div.section[collapsed="true"], div.section[collapsed="true"] > .section-header {
.triple-label {
text-transform: uppercase;
text-align: center;
padding: 6px 0;
cursor: default;
}
@ -606,6 +607,10 @@ div.section[collapsed="true"], div.section[collapsed="true"] > .section-header {
margin-right: 10px;
}
.triple-item.rgb.fstuple {
display: block !important;
}
.section-header[collapsed="true"] {
margin-bottom: -21px;
}
@ -912,14 +917,17 @@ div.refresh input[type="button"] {
clear: both;
}
.draggable-number-container {
flex: 0 1 124px;
}
.draggable-number {
position: relative;
}
.draggable-number div {
height: 28px;
width: 124px;
flex: 0 1 124px;
}
.draggable-number.text {
.draggable-number .text {
position: absolute;
display: inline-block;
color: #afafaf;
background-color: #252525;
@ -931,11 +939,12 @@ div.refresh input[type="button"] {
width: 100%;
line-height: 2;
box-sizing: border-box;
z-index: 1;
}
.draggable-number.text:hover {
.draggable-number .text:hover {
cursor: ew-resize;
}
.draggable-number span {
.draggable-number .left-arrow, .draggable-number .right-arrow {
position: absolute;
display: inline-block;
font-family: HiFi-Glyphs;
@ -945,12 +954,12 @@ div.refresh input[type="button"] {
.draggable-number span:hover {
cursor: default;
}
.draggable-number.left-arrow {
.draggable-number .left-arrow {
top: 3px;
left: 0px;
transform: rotate(180deg);
}
.draggable-number.right-arrow {
.draggable-number .right-arrow {
top: 3px;
right: 0px;
}
@ -1382,6 +1391,10 @@ input[type=button]#export {
cursor: col-resize;
}
#entity-table .dragging {
background-color: #b3ecff;
}
#entity-table td {
box-sizing: border-box;
}
@ -1516,6 +1529,7 @@ input.rename-entity {
width: 258px;
min-height: 20px;
padding: 5px;
z-index: 100;
}
.create-app-tooltip .create-app-tooltip-description {
@ -1631,10 +1645,6 @@ input.number-slider {
flex-flow: column;
}
.flex-column + .flex-column {
padding-left: 50px;
}
.flex-center {
align-items: center;
}
@ -1666,3 +1676,27 @@ input.number-slider {
.collapse-icon {
cursor: pointer;
}
#property-userData-editor.error {
border: 2px solid red;
}
#property-userData-editorStatus {
color: white;
background-color: red;
padding: 5px;
display: none;
cursor: pointer;
}
#property-materialData-editor.error {
border: 2px solid red;
}
#property-materialData-editorStatus {
color: white;
background-color: red;
padding: 5px;
display: none;
cursor: pointer;
}

View file

@ -18,6 +18,7 @@
<script type="text/javascript" src="js/spinButtons.js"></script>
<script type="text/javascript" src="js/listView.js"></script>
<script type="text/javascript" src="js/entityListContextMenu.js"></script>
<script type="text/javascript" src="js/utils.js"></script>
<script type="text/javascript" src="js/entityList.js"></script>
</head>
<body onload='loaded();' id="entity-list-body">

View file

@ -23,6 +23,7 @@
<script type="text/javascript" src="js/underscore-min.js"></script>
<script type="text/javascript" src="js/createAppTooltip.js"></script>
<script type="text/javascript" src="js/draggableNumber.js"></script>
<script type="text/javascript" src="js/utils.js"></script>
<script type="text/javascript" src="js/entityProperties.js"></script>
<script src="js/jsoneditor.min.js"></script>
</head>

View file

@ -16,6 +16,7 @@
<script type="text/javascript" src="qrc:///qtwebchannel/qwebchannel.js"></script>
<script type="text/javascript" src="js/eventBridgeLoader.js"></script>
<script type="text/javascript" src="js/spinButtons.js"></script>
<script type="text/javascript" src="js/utils.js"></script>
<script type="text/javascript" src="js/gridControls.js"></script>
</head>
<body onload='loaded();'>

View file

@ -170,8 +170,8 @@ DraggableNumber.prototype = {
this.elDiv = document.createElement('div');
this.elDiv.className = "draggable-number";
this.elText = document.createElement('label');
this.elText.className = "draggable-number text";
this.elText = document.createElement('span');
this.elText.className = "text";
this.elText.innerText = " ";
this.elText.style.visibility = "visible";
this.elText.addEventListener("mousedown", this.onMouseDown);
@ -179,15 +179,15 @@ DraggableNumber.prototype = {
this.elLeftArrow = document.createElement('span');
this.elRightArrow = document.createElement('span');
this.elLeftArrow.className = 'draggable-number left-arrow';
this.elLeftArrow.className = 'left-arrow';
this.elLeftArrow.innerHTML = 'D';
this.elLeftArrow.addEventListener("click", this.onStepDown);
this.elRightArrow.className = 'draggable-number right-arrow';
this.elRightArrow.className = 'right-arrow';
this.elRightArrow.innerHTML = 'D';
this.elRightArrow.addEventListener("click", this.onStepUp);
this.elInput = document.createElement('input');
this.elInput.className = "draggable-number input";
this.elInput.className = "input";
this.elInput.setAttribute("type", "number");
if (this.min !== undefined) {
this.elInput.setAttribute("min", this.min);
@ -205,8 +205,8 @@ DraggableNumber.prototype = {
this.elInput.addEventListener("focus", this.showInput.bind(this));
this.elDiv.appendChild(this.elLeftArrow);
this.elDiv.appendChild(this.elText);
this.elDiv.appendChild(this.elInput);
this.elDiv.appendChild(this.elRightArrow);
this.elDiv.appendChild(this.elText);
}
};

View file

@ -21,6 +21,8 @@ const MAX_LENGTH_RADIUS = 9;
const MINIMUM_COLUMN_WIDTH = 24;
const SCROLLBAR_WIDTH = 20;
const RESIZER_WIDTH = 10;
const DELTA_X_MOVE_COLUMNS_THRESHOLD = 2;
const DELTA_X_COLUMN_SWAP_POSITION = 5;
const COLUMNS = {
type: {
@ -108,8 +110,8 @@ const COLUMNS = {
};
const COMPARE_ASCENDING = function(a, b) {
let va = a[currentSortColumn];
let vb = b[currentSortColumn];
let va = a[currentSortColumnID];
let vb = b[currentSortColumnID];
if (va < vb) {
return -1;
@ -172,7 +174,7 @@ let entityList = null; // The ListView
*/
let entityListContextMenu = null;
let currentSortColumn = 'type';
let currentSortColumnID = 'type';
let currentSortOrder = ASCENDING_SORT;
let elSortOrders = {};
let typeFilters = [];
@ -180,10 +182,13 @@ let isFilterInView = false;
let columns = [];
let columnsByID = {};
let currentResizeEl = null;
let startResizeEvent = null;
let lastResizeEvent = null;
let resizeColumnIndex = 0;
let startThClick = null;
let elTargetTh = null;
let elTargetSpan = null;
let targetColumnIndex = 0;
let lastColumnSwapPosition = -1;
let initialThEvent = null;
let renameTimeout = null;
let renameLastBlur = null;
let renameLastEntityID = null;
@ -230,10 +235,6 @@ const PROFILE = !ENABLE_PROFILING ? PROFILE_NOOP : function(name, fn, args) {
console.log("PROFILE-Web " + profileIndent + "(" + name + ") End " + delta + "ms");
};
debugPrint = function (message) {
console.log(message);
};
function loaded() {
openEventBridge(function() {
elEntityTable = document.getElementById("entity-table");
@ -324,10 +325,11 @@ function loaded() {
for (let columnID in COLUMNS) {
let columnData = COLUMNS[columnID];
let thID = "entity-" + columnID;
let elTh = document.createElement("th");
let thID = "entity-" + columnID;
elTh.setAttribute("id", thID);
elTh.setAttribute("data-resizable-column-id", thID);
elTh.setAttribute("columnIndex", columnIndex);
elTh.setAttribute("columnID", columnID);
if (columnData.glyph) {
let elGlyph = document.createElement("span");
elGlyph.className = "glyph";
@ -336,20 +338,20 @@ function loaded() {
} else {
elTh.innerText = columnData.columnHeader;
}
elTh.onmousedown = function() {
startThClick = this;
};
elTh.onmouseup = function() {
if (startThClick === this) {
setSortColumn(columnID);
elTh.onmousedown = function(event) {
if (event.target.nodeName === 'TH') {
elTargetTh = event.target;
targetColumnIndex = parseInt(elTargetTh.getAttribute("columnIndex"));
lastColumnSwapPosition = event.clientX;
} else if (event.target.nodeName === 'SPAN') {
elTargetSpan = event.target;
}
startThClick = null;
initialThEvent = event;
};
let elResizer = document.createElement("span");
elResizer.className = "resizer";
elResizer.innerHTML = "&nbsp;";
elResizer.setAttribute("columnIndex", columnIndex);
elResizer.onmousedown = onStartResize;
elTh.appendChild(elResizer);
@ -762,13 +764,13 @@ function loaded() {
refreshNoEntitiesMessage();
}
function setSortColumn(column) {
function setSortColumn(columnID) {
PROFILE("set-sort-column", function() {
if (currentSortColumn === column) {
if (currentSortColumnID === columnID) {
currentSortOrder *= -1;
} else {
elSortOrders[currentSortColumn].innerHTML = "";
currentSortColumn = column;
elSortOrders[currentSortColumnID].innerHTML = "";
currentSortColumnID = columnID;
currentSortOrder = ASCENDING_SORT;
}
refreshSortOrder();
@ -777,7 +779,7 @@ function loaded() {
}
function refreshSortOrder() {
elSortOrders[currentSortColumn].innerHTML = currentSortOrder === ASCENDING_SORT ? ASCENDING_STRING : DESCENDING_STRING;
elSortOrders[currentSortColumnID].innerHTML = currentSortOrder === ASCENDING_SORT ? ASCENDING_STRING : DESCENDING_STRING;
}
function refreshEntities() {
@ -1093,8 +1095,8 @@ function loaded() {
}
function onStartResize(event) {
startResizeEvent = event;
resizeColumnIndex = parseInt(this.getAttribute("columnIndex"));
lastResizeEvent = event;
resizeColumnIndex = parseInt(this.parentNode.getAttribute("columnIndex"));
event.stopPropagation();
}
@ -1137,8 +1139,37 @@ function loaded() {
entityList.refresh();
}
document.onmousemove = function(ev) {
if (startResizeEvent) {
function swapColumns(columnAIndex, columnBIndex) {
let columnA = columns[columnAIndex];
let columnB = columns[columnBIndex];
let columnATh = columns[columnAIndex].elTh;
let columnBTh = columns[columnBIndex].elTh;
let columnThParent = columnATh.parentNode;
columnThParent.removeChild(columnBTh);
columnThParent.insertBefore(columnBTh, columnATh);
columnATh.setAttribute("columnIndex", columnBIndex);
columnBTh.setAttribute("columnIndex", columnAIndex);
columnA.elResizer.setAttribute("columnIndex", columnBIndex);
columnB.elResizer.setAttribute("columnIndex", columnAIndex);
for (let i = 0; i < visibleEntities.length; ++i) {
let elRow = visibleEntities[i].elRow;
if (elRow) {
let columnACell = elRow.childNodes[columnAIndex];
let columnBCell = elRow.childNodes[columnBIndex];
elRow.removeChild(columnBCell);
elRow.insertBefore(columnBCell, columnACell);
}
}
columns[columnAIndex] = columnB;
columns[columnBIndex] = columnA;
updateColumnWidths();
}
document.onmousemove = function(event) {
if (lastResizeEvent) {
startTh = null;
let column = columns[resizeColumnIndex];
@ -1150,7 +1181,7 @@ function loaded() {
}
let fullWidth = elEntityTableBody.offsetWidth;
let dx = ev.clientX - startResizeEvent.clientX;
let dx = event.clientX - lastResizeEvent.clientX;
let dPct = dx / fullWidth;
let newColWidth = column.width + dPct;
@ -1160,14 +1191,60 @@ function loaded() {
column.width += dPct;
nextColumn.width -= dPct;
updateColumnWidths();
startResizeEvent = ev;
lastResizeEvent = event;
}
} else if (elTargetTh) {
let dxFromInitial = event.clientX - initialThEvent.clientX;
if (Math.abs(dxFromInitial) >= DELTA_X_MOVE_COLUMNS_THRESHOLD) {
elTargetTh.className = "dragging";
}
if (targetColumnIndex < columns.length - 1) {
let nextColumnIndex = targetColumnIndex + 1;
let nextColumnTh = columns[nextColumnIndex].elTh;
let nextColumnStartX = nextColumnTh.getBoundingClientRect().left;
if (event.clientX >= nextColumnStartX && event.clientX - lastColumnSwapPosition >= DELTA_X_COLUMN_SWAP_POSITION) {
swapColumns(targetColumnIndex, nextColumnIndex);
targetColumnIndex = nextColumnIndex;
lastColumnSwapPosition = event.clientX;
}
}
if (targetColumnIndex >= 1) {
let prevColumnIndex = targetColumnIndex - 1;
let prevColumnTh = columns[prevColumnIndex].elTh;
let prevColumnEndX = prevColumnTh.getBoundingClientRect().right;
if (event.clientX <= prevColumnEndX && lastColumnSwapPosition - event.clientX >= DELTA_X_COLUMN_SWAP_POSITION) {
swapColumns(prevColumnIndex, targetColumnIndex);
targetColumnIndex = prevColumnIndex;
lastColumnSwapPosition = event.clientX;
}
}
} else if (elTargetSpan) {
let dxFromInitial = event.clientX - initialThEvent.clientX;
if (Math.abs(dxFromInitial) >= DELTA_X_MOVE_COLUMNS_THRESHOLD) {
elTargetTh = elTargetSpan.parentNode;
elTargetTh.className = "dragging";
targetColumnIndex = parseInt(elTargetTh.getAttribute("columnIndex"));
lastColumnSwapPosition = event.clientX;
elTargetSpan = null;
}
}
};
document.onmouseup = function(ev) {
startResizeEvent = null;
ev.stopPropagation();
document.onmouseup = function(event) {
if (elTargetTh) {
if (elTargetTh.className !== "dragging" && elTargetTh === event.target) {
let columnID = elTargetTh.getAttribute("columnID");
setSortColumn(columnID);
}
elTargetTh.className = "";
} else if (elTargetSpan) {
let columnID = elTargetSpan.parentNode.getAttribute("columnID");
setSortColumn(columnID);
}
lastResizeEvent = null;
elTargetTh = null;
elTargetSpan = null;
initialThEvent = null;
};
function setSpaceMode(spaceMode) {
@ -1287,8 +1364,9 @@ function loaded() {
});
augmentSpinButtons();
disableDragDrop();
document.addEventListener("contextmenu", function (event) {
document.addEventListener("contextmenu", function(event) {
entityListContextMenu.close();
// Disable default right-click context menu which is not visible in the HMD and makes it seem like the app has locked

View file

@ -556,22 +556,24 @@ const GROUPS = [
{ id: "save", label: "Save Material Data", className: "black", onClick: saveMaterialData } ],
propertyID: "materialData",
},
{
label: "Select Submesh",
type: "bool",
propertyID: "selectSubmesh",
},
{
label: "Submesh to Replace",
type: "number",
min: 0,
step: 1,
propertyID: "submeshToReplace",
indentedLabel: true,
},
{
label: "Material Name to Replace",
label: "Material to Replace",
type: "string",
propertyID: "materialNameToReplace",
},
{
label: "Select Submesh",
type: "bool",
propertyID: "selectSubmesh",
indentedLabel: true,
},
{
label: "Priority",
@ -590,7 +592,7 @@ const GROUPS = [
{
label: "Material Position",
type: "vec2",
vec2Type: "xy",
vec2Type: "xyz",
min: 0,
max: 1,
step: 0.1,
@ -601,11 +603,11 @@ const GROUPS = [
{
label: "Material Scale",
type: "vec2",
vec2Type: "wh",
vec2Type: "xyz",
min: 0,
step: 0.1,
decimals: 4,
subLabels: [ "width", "height" ],
subLabels: [ "x", "y" ],
propertyID: "materialMappingScale",
},
{
@ -1435,13 +1437,13 @@ const TEXTURE_ELEMENTS = {
const JSON_EDITOR_ROW_DIV_INDEX = 2;
var elGroups = {};
var properties = {};
var colorPickers = {};
var particlePropertyUpdates = {};
var selectedEntityProperties;
var lastEntityID = null;
var createAppTooltip = new CreateAppTooltip();
let elGroups = {};
let properties = {};
let colorPickers = {};
let particlePropertyUpdates = {};
let selectedEntityProperties;
let lastEntityID = null;
let createAppTooltip = new CreateAppTooltip();
let currentSpaceMode = PROPERTY_SPACE_MODE.LOCAL;
function createElementFromHTML(htmlString) {
@ -1695,7 +1697,7 @@ function updateProperty(originalPropertyName, propertyValue, isParticleProperty)
}
}
var particleSyncDebounce = _.debounce(function () {
let particleSyncDebounce = _.debounce(function () {
updateProperties(particlePropertyUpdates);
particlePropertyUpdates = {};
}, DEBOUNCE_TIMEOUT);
@ -1829,7 +1831,7 @@ function createStringProperty(property, elProperty) {
type="text"
${propertyData.placeholder ? 'placeholder="' + propertyData.placeholder + '"' : ''}
${propertyData.readOnly ? 'readonly' : ''}></input>
`)
`);
elInput.addEventListener('change', createEmitTextPropertyUpdateFunction(property));
@ -1881,7 +1883,7 @@ function createNumberProperty(property, elProperty) {
let elementID = property.elementID;
let propertyData = property.data;
elProperty.className = "draggable-number";
elProperty.className += " draggable-number-container";
let dragStartFunction = createDragStartFunction(property);
let dragEndFunction = createDragEndFunction(property);
@ -1960,7 +1962,7 @@ function createColorProperty(property, elProperty) {
let elementID = property.elementID;
let propertyData = property.data;
elProperty.className = "rgb fstuple";
elProperty.className += " rgb fstuple";
let elColorPicker = document.createElement('div');
elColorPicker.className = "color-picker";
@ -2001,6 +2003,7 @@ function createColorProperty(property, elProperty) {
color: '000000',
submit: false, // We don't want to have a submission button
onShow: function(colpick) {
console.log("Showing");
$(colorPickerID).attr('active', 'true');
// The original color preview within the picker needs to be updated on show because
// prior to the picker being shown we don't have access to the selections' starting color.
@ -2365,31 +2368,41 @@ function saveUserData() {
saveJSONUserData(true);
}
function setJSONError(property, isError) {
$("#property-"+ property + "-editor").toggleClass('error', isError);
let $propertyUserDataEditorStatus = $("#property-"+ property + "-editorStatus");
$propertyUserDataEditorStatus.css('display', isError ? 'block' : 'none');
$propertyUserDataEditorStatus.text(isError ? 'Invalid JSON code - look for red X in your code' : '');
}
function setUserDataFromEditor(noUpdate) {
let json = null;
let errorFound = false;
try {
json = editor.get();
} catch (e) {
alert('Invalid JSON code - look for red X in your code ', +e);
errorFound = true;
}
if (json === null) {
setJSONError('userData', errorFound);
if (errorFound) {
return;
}
let text = editor.getText();
if (noUpdate) {
EventBridge.emitWebEvent(
JSON.stringify({
id: lastEntityID,
type: "saveUserData",
properties: {
userData: text
}
})
);
} else {
let text = editor.getText();
if (noUpdate === true) {
EventBridge.emitWebEvent(
JSON.stringify({
id: lastEntityID,
type: "saveUserData",
properties: {
userData: text
}
})
);
return;
} else {
updateProperty('userData', text, false);
}
updateProperty('userData', text, false);
}
}
@ -2448,7 +2461,7 @@ function multiDataUpdater(groupName, updateKeyPair, userDataElement, defaults, r
updateProperties(propertyUpdate, false);
}
var editor = null;
let editor = null;
function createJSONEditor() {
let container = document.getElementById("property-userData-editor");
@ -2511,9 +2524,10 @@ function hideUserDataSaved() {
function showStaticUserData() {
if (editor !== null) {
$('#property-userData-static').show();
$('#property-userData-static').css('height', $('#property-userData-editor').height());
$('#property-userData-static').text(editor.getText());
let $propertyUserDataStatic = $('#property-userData-static');
$propertyUserDataStatic.show();
$propertyUserDataStatic.css('height', $('#property-userData-editor').height());
$propertyUserDataStatic.text(editor.getText());
}
}
@ -2534,12 +2548,13 @@ function getEditorJSON() {
function deleteJSONEditor() {
if (editor !== null) {
setJSONError('userData', false);
editor.destroy();
editor = null;
}
}
var savedJSONTimer = null;
let savedJSONTimer = null;
function saveJSONUserData(noUpdate) {
setUserDataFromEditor(noUpdate);
@ -2584,33 +2599,35 @@ function saveMaterialData() {
function setMaterialDataFromEditor(noUpdate) {
let json = null;
let errorFound = false;
try {
json = materialEditor.get();
} catch (e) {
alert('Invalid JSON code - look for red X in your code ', +e);
errorFound = true;
}
if (json === null) {
setJSONError('materialData', errorFound);
if (errorFound) {
return;
}
let text = materialEditor.getText();
if (noUpdate) {
EventBridge.emitWebEvent(
JSON.stringify({
id: lastEntityID,
type: "saveMaterialData",
properties: {
materialData: text
}
})
);
} else {
let text = materialEditor.getText();
if (noUpdate === true) {
EventBridge.emitWebEvent(
JSON.stringify({
id: lastEntityID,
type: "saveMaterialData",
properties: {
materialData: text
}
})
);
return;
} else {
updateProperty('materialData', text, false);
}
updateProperty('materialData', text, false);
}
}
var materialEditor = null;
let materialEditor = null;
function createJSONMaterialEditor() {
let container = document.getElementById("property-materialData-editor");
@ -2673,9 +2690,10 @@ function hideMaterialDataSaved() {
function showStaticMaterialData() {
if (materialEditor !== null) {
$('#property-materialData-static').show();
$('#property-materialData-static').css('height', $('#property-materialData-editor').height());
$('#property-materialData-static').text(materialEditor.getText());
let $propertyMaterialDataStatic = $('#property-materialData-static');
$propertyMaterialDataStatic.show();
$propertyMaterialDataStatic.css('height', $('#property-materialData-editor').height());
$propertyMaterialDataStatic.text(materialEditor.getText());
}
}
@ -2696,12 +2714,13 @@ function getMaterialEditorJSON() {
function deleteJSONMaterialEditor() {
if (materialEditor !== null) {
setJSONError('materialData', false);
materialEditor.destroy();
materialEditor = null;
}
}
var savedMaterialJSONTimer = null;
let savedMaterialJSONTimer = null;
function saveJSONMaterialData(noUpdate) {
setMaterialDataFromEditor(noUpdate);
@ -2899,23 +2918,23 @@ function loaded() {
for (let i = 0; i < propertyData.properties.length; ++i) {
let innerPropertyData = propertyData.properties[i];
let elWrapper = createElementFromHTML('<div class="flex-column flex-center triple-item"><div></div></div>');
let elWrapper = createElementFromHTML('<div class="triple-item"></div>');
elProperty.appendChild(elWrapper);
let propertyID = innerPropertyData.propertyID;
let propertyName = innerPropertyData.propertyName !== undefined ? innerPropertyData.propertyName : propertyID;
let propertyElementID = "property-" + propertyID;
propertyElementID = propertyElementID.replace('.', '-');
let property = createProperty(innerPropertyData, propertyElementID, propertyName, propertyID, elWrapper);
property.isParticleProperty = group.id.includes("particles");
property.elContainer = elContainer;
property.spaceMode = propertySpaceMode;
let elLabel = createElementFromHTML(`<div class="triple-label">${innerPropertyData.label}</div>`);
createAppTooltip.registerTooltipElement(elLabel, propertyID);
elWrapper.appendChild(elLabel);
elProperty.appendChild(elWrapper);
let property = createProperty(innerPropertyData, propertyElementID, propertyName, propertyID, elWrapper.childNodes[0]);
property.isParticleProperty = group.id.includes("particles");
property.elContainer = elContainer;
property.spaceMode = propertySpaceMode;
if (property.type !== 'placeholder') {
properties[propertyID] = property;
@ -2961,7 +2980,7 @@ function loaded() {
let elServerScriptError = document.getElementById("property-serverScripts-error");
let elServerScriptStatus = document.getElementById("property-serverScripts-status");
elServerScriptError.value = data.errorInfo;
// If we just set elServerScriptError's diplay to block or none, we still end up with
// If we just set elServerScriptError's display to block or none, we still end up with
// it's parent contributing 21px bottom padding even when elServerScriptError is display:none.
// So set it's parent to block or none
elServerScriptError.parentElement.style.display = data.errorInfo ? "block" : "none";
@ -3320,12 +3339,15 @@ function loaded() {
elStaticUserData.setAttribute("id", userDataElementID + "-static");
let elUserDataEditor = document.createElement('div');
elUserDataEditor.setAttribute("id", userDataElementID + "-editor");
let elUserDataEditorStatus = document.createElement('div');
elUserDataEditorStatus.setAttribute("id", userDataElementID + "-editorStatus");
let elUserDataSaved = document.createElement('span');
elUserDataSaved.setAttribute("id", userDataElementID + "-saved");
elUserDataSaved.innerText = "Saved!";
elDiv.childNodes[JSON_EDITOR_ROW_DIV_INDEX].appendChild(elUserDataSaved);
elDiv.insertBefore(elStaticUserData, elUserData);
elDiv.insertBefore(elUserDataEditor, elUserData);
elDiv.insertBefore(elUserDataEditorStatus, elUserData);
// Material Data
let materialDataProperty = properties["materialData"];
@ -3336,12 +3358,15 @@ function loaded() {
elStaticMaterialData.setAttribute("id", materialDataElementID + "-static");
let elMaterialDataEditor = document.createElement('div');
elMaterialDataEditor.setAttribute("id", materialDataElementID + "-editor");
let elMaterialDataEditorStatus = document.createElement('div');
elMaterialDataEditorStatus.setAttribute("id", materialDataElementID + "-editorStatus");
let elMaterialDataSaved = document.createElement('span');
elMaterialDataSaved.setAttribute("id", materialDataElementID + "-saved");
elMaterialDataSaved.innerText = "Saved!";
elDiv.childNodes[JSON_EDITOR_ROW_DIV_INDEX].appendChild(elMaterialDataSaved);
elDiv.insertBefore(elStaticMaterialData, elMaterialData);
elDiv.insertBefore(elMaterialDataEditor, elMaterialData);
elDiv.insertBefore(elMaterialDataEditorStatus, elMaterialData);
// Special Property Callbacks
let elParentMaterialNameString = getPropertyInputElement("materialNameToReplace");
@ -3532,6 +3557,7 @@ function loaded() {
});
augmentSpinButtons();
disableDragDrop();
// Disable right-click context menu which is not visible in the HMD and makes it seem like the app has locked
document.addEventListener("contextmenu", function(event) {

View file

@ -108,6 +108,7 @@ function loaded() {
});
augmentSpinButtons();
disableDragDrop();
EventBridge.emitWebEvent(JSON.stringify({ type: 'init' }));
});

View file

@ -9,10 +9,6 @@
const SCROLL_ROWS = 2; // number of rows used as scrolling buffer, each time we pass this number of rows we scroll
const FIRST_ROW_INDEX = 2; // the first elRow element's index in the child nodes of the table body
debugPrint = function (message) {
console.log(message);
};
function ListView(elTableBody, elTableScroll, elTableHeaderRow, createRowFunction, updateRowFunction, clearRowFunction,
preRefreshFunction, postRefreshFunction, preResizeFunction, WINDOW_NONVARIABLE_HEIGHT) {
this.elTableBody = elTableBody;
@ -246,7 +242,7 @@ ListView.prototype = {
resize: function() {
if (!this.elTableBody || !this.elTableScroll) {
debugPrint("ListView.resize - no valid table body or table scroll element");
console.log("ListView.resize - no valid table body or table scroll element");
return;
}
this.preResizeFunction();
@ -288,7 +284,7 @@ ListView.prototype = {
initialize: function() {
if (!this.elTableBody || !this.elTableScroll) {
debugPrint("ListView.initialize - no valid table body or table scroll element");
console.log("ListView.initialize - no valid table body or table scroll element");
return;
}

View file

@ -0,0 +1,27 @@
//
// utils.js
//
// Created by David Back on 19 Nov 2018
// Copyright 2016 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
//
function disableDragDrop() {
document.addEventListener("drop", function(event) {
event.preventDefault();
});
document.addEventListener("dragover", function(event) {
event.dataTransfer.effectAllowed = "none";
event.dataTransfer.dropEffect = "none";
event.preventDefault();
});
document.addEventListener("dragenter", function(event) {
event.dataTransfer.effectAllowed = "none";
event.dataTransfer.dropEffect = "none";
event.preventDefault();
}, false);
}

View file

@ -97,9 +97,8 @@ cleanUpOldMaterialEntities = function() {
* @param width [number] width in meters of the tablet model
* @param dpi [number] dpi of web surface used to show the content.
* @param hand [number] -1 indicates no hand, Controller.Standard.RightHand or Controller.Standard.LeftHand
* @param clientOnly [bool] true indicates tablet model is only visible to client.
*/
WebTablet = function (url, width, dpi, hand, clientOnly, location, visible) {
WebTablet = function (url, width, dpi, hand, location, visible) {
var _this = this;

View file

@ -264,7 +264,7 @@ SelectionManager = (function() {
if (properties === undefined) {
properties = Entities.getEntityProperties(originalEntityID);
}
if (!properties.locked && (!properties.clientOnly || properties.owningAvatarID === MyAvatar.sessionUUID)) {
if (!properties.locked && (!properties.avatarEntity || properties.owningAvatarID === MyAvatar.sessionUUID)) {
if (nonDynamicEntityIsBeingGrabbedByAvatar(properties)) {
properties.parentID = null;
properties.parentJointIndex = null;

View file

@ -30,7 +30,7 @@ var TOOLBAR_MARGIN_Y = 0;
var marketplaceVisible = false;
var marketplaceWebTablet;
// We persist clientOnly data in the .ini file, and reconsistitute it on restart.
// We persist avatarEntity data in the .ini file, and reconsistitute it on restart.
// To keep things consistent, we pickle the tablet data in Settings, and kill any existing such on restart and domain change.
var persistenceKey = "io.highfidelity.lastDomainTablet";

View file

@ -12,7 +12,7 @@
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
const CLIENTONLY = false;
const AVATARENTITY = false;
const ENTITY_CHECK_INTERVAL = 5000; // ms = 5 seconds
const STARTUP_DELAY = 2000; // ms = 2 second
const OLD_AGE = 3500; // we recreate the entity if older than this time in seconds
@ -43,7 +43,7 @@ function addNameTag() {
dimensions: dimensionsFromName(),
position: nameTagPosition
}
nameTagEntityID = Entities.addEntity(nameTagProperties, CLIENTONLY);
nameTagEntityID = Entities.addEntity(nameTagProperties, AVATARENTITY);
}
function updateNameTag() {

View file

@ -108,7 +108,7 @@
tabletScalePercentage = getTabletScalePercentageFromSettings();
UIWebTablet = new WebTablet("hifi/tablet/TabletRoot.qml",
DEFAULT_WIDTH * (tabletScalePercentage / 100),
null, activeHand, true, null, false);
null, activeHand, null, false);
UIWebTablet.register();
HMD.tabletID = UIWebTablet.tabletEntityID;
HMD.homeButtonID = UIWebTablet.homeButtonID;