From 6a035578017935304c77ac8821a5dc5d49f3d3cf Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Tue, 23 Jan 2018 14:44:10 -0800 Subject: [PATCH 01/45] don't override sensorToWorldMatrix --- interface/src/avatar/MyAvatar.cpp | 10 ++++++---- interface/src/avatar/MyAvatar.h | 2 +- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index e93b897013..2943cf7fd8 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -1790,7 +1790,7 @@ void MyAvatar::setAnimGraphUrl(const QUrl& url) { updateSensorToWorldMatrix(); // Uses updated position/orientation and _bodySensorMatrix changes } -void MyAvatar::initAnimGraph() { +void MyAvatar::initAnimGraph(bool updateBodySensorMat) { QUrl graphUrl; if (!_prefOverrideAnimGraphUrl.get().isEmpty()) { graphUrl = _prefOverrideAnimGraphUrl.get(); @@ -1803,8 +1803,10 @@ void MyAvatar::initAnimGraph() { _skeletonModel->getRig().initAnimGraph(graphUrl); _currentAnimGraphUrl.set(graphUrl); - _bodySensorMatrix = deriveBodyFromHMDSensor(); // Based on current cached HMD position/rotation.. - updateSensorToWorldMatrix(); // Uses updated position/orientation and _bodySensorMatrix changes + if (updateBodySensorMat) { + _bodySensorMatrix = deriveBodyFromHMDSensor(); // Based on current cached HMD position/rotation.. + updateSensorToWorldMatrix(); // Uses updated position/orientation and _bodySensorMatrix changes + } } void MyAvatar::destroyAnimGraph() { @@ -1819,7 +1821,7 @@ void MyAvatar::postUpdate(float deltaTime, const render::ScenePointer& scene) { initHeadBones(); _skeletonModel->setCauterizeBoneSet(_headBoneSet); _fstAnimGraphOverrideUrl = _skeletonModel->getGeometry()->getAnimGraphOverrideUrl(); - initAnimGraph(); + initAnimGraph(false); _isAnimatingScale = true; } diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 58b49b61ff..cdcd6f4607 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -730,7 +730,7 @@ private: void updatePosition(float deltaTime); void updateCollisionSound(const glm::vec3& penetration, float deltaTime, float frequency); void initHeadBones(); - void initAnimGraph(); + void initAnimGraph(bool updateBodySensorMat = true); // Avatar Preferences QUrl _fullAvatarURLFromPreferences; From 2cbcc28bd4c860a45e0e050ae959537c6b795a02 Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Wed, 24 Jan 2018 10:44:04 -0800 Subject: [PATCH 02/45] only call init animGraph once --- interface/src/avatar/MyAvatar.cpp | 27 +++++++++++++++------------ interface/src/avatar/MyAvatar.h | 4 +++- 2 files changed, 18 insertions(+), 13 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 1475860665..5f2cbf92b2 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -1785,12 +1785,10 @@ void MyAvatar::setAnimGraphUrl(const QUrl& url) { _currentAnimGraphUrl.set(url); _skeletonModel->getRig().initAnimGraph(url); - - _bodySensorMatrix = deriveBodyFromHMDSensor(); // Based on current cached HMD position/rotation.. - updateSensorToWorldMatrix(); // Uses updated position/orientation and _bodySensorMatrix changes + connect(&(_skeletonModel->getRig()), SIGNAL(onLoadComplete()), this, SLOT(animGraphLoaded())); } -void MyAvatar::initAnimGraph(bool updateBodySensorMat) { +void MyAvatar::initAnimGraph() { QUrl graphUrl; if (!_prefOverrideAnimGraphUrl.get().isEmpty()) { graphUrl = _prefOverrideAnimGraphUrl.get(); @@ -1802,27 +1800,32 @@ void MyAvatar::initAnimGraph(bool updateBodySensorMat) { _skeletonModel->getRig().initAnimGraph(graphUrl); _currentAnimGraphUrl.set(graphUrl); - - if (updateBodySensorMat) { - _bodySensorMatrix = deriveBodyFromHMDSensor(); // Based on current cached HMD position/rotation.. - updateSensorToWorldMatrix(); // Uses updated position/orientation and _bodySensorMatrix changes - } + connect(&(_skeletonModel->getRig()), SIGNAL(onLoadComplete()), this, SLOT(animGraphLoaded())); } void MyAvatar::destroyAnimGraph() { _skeletonModel->getRig().destroyAnimGraph(); } +void MyAvatar::animGraphLoaded() { + _bodySensorMatrix = deriveBodyFromHMDSensor(); // Based on current cached HMD position/rotation.. + updateSensorToWorldMatrix(); // Uses updated position/orientation and _bodySensorMatrix changes + _isAnimatingScale = true; + disconnect(&(_skeletonModel->getRig()), SIGNAL(onLoadComplete()), this, SLOT(animGraphLoaded())); +} + void MyAvatar::postUpdate(float deltaTime, const render::ScenePointer& scene) { Avatar::postUpdate(deltaTime, scene); - if (_skeletonModel->isLoaded() && !_skeletonModel->getRig().getAnimNode()) { + if (_skeletonModel->isLoaded() && !_skeletonModel->getRig().getAnimNode() && _initHeadBones) { initHeadBones(); _skeletonModel->setCauterizeBoneSet(_headBoneSet); _fstAnimGraphOverrideUrl = _skeletonModel->getGeometry()->getAnimGraphOverrideUrl(); - initAnimGraph(false); - _isAnimatingScale = true; + initAnimGraph(); + _initHeadBones = false; + } else if (!_skeletonModel->isLoaded()) { + _initHeadBones = true; } if (_enableDebugDrawDefaultPose || _enableDebugDrawAnimPose) { diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index cdcd6f4607..6a9e0e6a38 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -566,6 +566,7 @@ public slots: void increaseSize(); void decreaseSize(); void resetSize(); + void animGraphLoaded(); void setGravity(float gravity); float getGravity(); @@ -730,7 +731,7 @@ private: void updatePosition(float deltaTime); void updateCollisionSound(const glm::vec3& penetration, float deltaTime, float frequency); void initHeadBones(); - void initAnimGraph(bool updateBodySensorMat = true); + void initAnimGraph(); // Avatar Preferences QUrl _fullAvatarURLFromPreferences; @@ -808,6 +809,7 @@ private: bool _enableDebugDrawIKConstraints { false }; bool _enableDebugDrawIKChains { false }; bool _enableDebugDrawDetailedCollision { false }; + bool _initHeadBones { false }; AudioListenerMode _audioListenerMode; glm::vec3 _customListenPosition; From b7ba7862aa929a501a7fec542974d6ce3ebb4d5c Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Wed, 24 Jan 2018 11:18:42 -0800 Subject: [PATCH 03/45] give animGrapgh loader a high priority --- libraries/animation/src/AnimNodeLoader.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/libraries/animation/src/AnimNodeLoader.cpp b/libraries/animation/src/AnimNodeLoader.cpp index 33f3d72756..8173845205 100644 --- a/libraries/animation/src/AnimNodeLoader.cpp +++ b/libraries/animation/src/AnimNodeLoader.cpp @@ -38,6 +38,8 @@ static AnimNode::Pointer loadManipulatorNode(const QJsonObject& jsonObj, const Q static AnimNode::Pointer loadInverseKinematicsNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl); static AnimNode::Pointer loadDefaultPoseNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl); +static const float ANIM_GRAPH_LOAD_PRIORITY = 10.0f; + // called after children have been loaded // returns node on success, nullptr on failure. static bool processDoNothing(AnimNode::Pointer node, const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl) { return true; } @@ -653,6 +655,7 @@ AnimNodeLoader::AnimNodeLoader(const QUrl& url) : { _resource = QSharedPointer::create(url); _resource->setSelf(_resource); + _resource->setLoadPriority(this, ANIM_GRAPH_LOAD_PRIORITY); connect(_resource.data(), &Resource::loaded, this, &AnimNodeLoader::onRequestDone); connect(_resource.data(), &Resource::failed, this, &AnimNodeLoader::onRequestError); _resource->ensureLoading(); From 5a771e3a168357708d646dcba70c79ceb7eb4cb4 Mon Sep 17 00:00:00 2001 From: LaShonda Hopper Date: Fri, 26 Jan 2018 17:08:53 -0500 Subject: [PATCH 04/45] [Case 10865] Ctrl/Cmd/Shift+Click Multi-Select for Asset Browser. * Removes manual selectionModel update from controls-uit/Tree.qml * This was interfering with the Key+Click functionality. It was introduced via Commit 99617600c47 to address a selection issue; however, it's unclear what that issue was. Selection within the Asset Browser works without it at present. * Amends the ContextMenu within the AssetBrowser to work in conjunction with the multi-selection changes. * ContextMenu will _only_ display when triggered within the current selection be it one or more items as opposed to previous behavior of selecting the item the menu was triggered on. * CopyURL is only enabled when the selection size is 1 * Rename is only enabled when the selection size is 1 * Delete is enabled when the selection size is greater than 0 Changes Committed: modified: interface/resources/qml/controls-uit/Tree.qml modified: interface/resources/qml/hifi/AssetServer.qml --- interface/resources/qml/controls-uit/Tree.qml | 4 -- interface/resources/qml/hifi/AssetServer.qml | 41 +++++++++++-------- 2 files changed, 25 insertions(+), 20 deletions(-) diff --git a/interface/resources/qml/controls-uit/Tree.qml b/interface/resources/qml/controls-uit/Tree.qml index 6bd11295b1..5199a10a27 100644 --- a/interface/resources/qml/controls-uit/Tree.qml +++ b/interface/resources/qml/controls-uit/Tree.qml @@ -202,8 +202,4 @@ TreeView { } onDoubleClicked: isExpanded(index) ? collapse(index) : expand(index) - - onClicked: { - selectionModel.setCurrentIndex(index, ItemSelectionModel.ClearAndSelect); - } } diff --git a/interface/resources/qml/hifi/AssetServer.qml b/interface/resources/qml/hifi/AssetServer.qml index 37c3c2adab..7c16b19865 100644 --- a/interface/resources/qml/hifi/AssetServer.qml +++ b/interface/resources/qml/hifi/AssetServer.qml @@ -694,7 +694,7 @@ Windows.ScrollingWindow { } } } - } + }// End_OF( itemLoader ) Rectangle { id: treeLabelToolTip @@ -731,50 +731,59 @@ Windows.ScrollingWindow { showTimer.stop(); treeLabelToolTip.visible = false; } - } + }// End_OF( treeLabelToolTip ) MouseArea { propagateComposedEvents: true anchors.fill: parent acceptedButtons: Qt.RightButton onClicked: { - if (!HMD.active) { // Popup only displays properly on desktop - var index = treeView.indexAt(mouse.x, mouse.y); - treeView.selection.setCurrentIndex(index, 0x0002); - contextMenu.currentIndex = index; - contextMenu.popup(); + if (treeView.selection.hasSelection && !HMD.active) { // Popup only displays properly on desktop + // Only display the popup if the click triggered within + // the selection. + var clickedIndex = treeView.indexAt(mouse.x, mouse.y); + var displayContextMenu = false; + for ( var i = 0; i < selectedItems; ++i) { + var currentSelectedIndex = treeView.selection.selectedIndexes[i]; + if (clickedIndex === currentSelectedIndex) { + contextMenu.popup(); + break; + } + } } } } - + Menu { id: contextMenu title: "Edit" property var url: "" - property var currentIndex: null MenuItem { text: "Copy URL" + enabled: (selectedItems == 1) onTriggered: { - copyURLToClipboard(contextMenu.currentIndex); + copyURLToClipboard(treeView.selection.currentIndex); } } MenuItem { text: "Rename" + enabled: (selectedItems == 1) onTriggered: { - renameFile(contextMenu.currentIndex); + renameFile(treeView.selection.currentIndex); } } MenuItem { text: "Delete" + enabled: (selectedItems > 0) onTriggered: { - deleteFile(contextMenu.currentIndex); + deleteFile(); } } - } - } + }// End_OF( contextMenu ) + }// End_OF( treeView ) Row { id: infoRow @@ -885,7 +894,7 @@ Windows.ScrollingWindow { "Baking compresses and optimizes files for faster network transfer and display. We recommend you bake your content to reduce initial load times for your visitors."); } } - } + }// End_OF( infoRow ) HifiControls.ContentSection { id: uploadSection @@ -945,7 +954,7 @@ Windows.ScrollingWindow { } } } - } + }// End_OF( uploadSection ) } } From dfdf28f37e4572bb95b6354be850356ad8bffa27 Mon Sep 17 00:00:00 2001 From: LaShonda Hopper Date: Fri, 26 Jan 2018 17:34:05 -0500 Subject: [PATCH 05/45] [Case 10865] Some cleanup for multi-selection (details below). * Remove setCurrentIndex call when looping over current selection. * Changed selectedItems var name to selectedItemCount to clarify what it represents. * Change path arg name to paths to clarify that there can be more than a single path contained within it. Changes Committed: modified: interface/resources/qml/hifi/AssetServer.qml --- interface/resources/qml/hifi/AssetServer.qml | 49 ++++++++++---------- 1 file changed, 24 insertions(+), 25 deletions(-) diff --git a/interface/resources/qml/hifi/AssetServer.qml b/interface/resources/qml/hifi/AssetServer.qml index 7c16b19865..30f76fecd6 100644 --- a/interface/resources/qml/hifi/AssetServer.qml +++ b/interface/resources/qml/hifi/AssetServer.qml @@ -37,7 +37,7 @@ Windows.ScrollingWindow { property var assetProxyModel: Assets.proxyModel; property var assetMappingsModel: Assets.mappingModel; property var currentDirectory; - property var selectedItems: treeView.selection.selectedIndexes.length; + property var selectedItemCount: treeView.selection.selectedIndexes.length; Settings { category: "Overlay.AssetServer" @@ -75,17 +75,17 @@ Windows.ScrollingWindow { }); } - function doDeleteFile(path) { - console.log("Deleting " + path); + function doDeleteFile(paths) { + console.log("Deleting " + paths); - Assets.deleteMappings(path, function(err) { + Assets.deleteMappings(paths, function(err) { if (err) { - console.log("Asset browser - error deleting path: ", path, err); + console.log("Asset browser - error deleting paths: ", paths, err); - box = errorMessageBox("There was an error deleting:\n" + path + "\n" + err); + box = errorMessageBox("There was an error deleting:\n" + paths + "\n" + err); box.selected.connect(reload); } else { - console.log("Asset browser - finished deleting path: ", path); + console.log("Asset browser - finished deleting paths: ", paths); reload(); } }); @@ -145,7 +145,7 @@ Windows.ScrollingWindow { function canAddToWorld(path) { var supportedExtensions = [/\.fbx\b/i, /\.obj\b/i]; - if (selectedItems > 1) { + if (selectedItemCount > 1) { return false; } @@ -155,7 +155,7 @@ Windows.ScrollingWindow { } function canRename() { - if (treeView.selection.hasSelection && selectedItems == 1) { + if (treeView.selection.hasSelection && selectedItemCount == 1) { return true; } else { return false; @@ -333,29 +333,28 @@ Windows.ScrollingWindow { }); } function deleteFile(index) { - var path = []; + var paths = []; if (!index) { - for (var i = 0; i < selectedItems; i++) { - treeView.selection.setCurrentIndex(treeView.selection.selectedIndexes[i], 0x100); - index = treeView.selection.currentIndex; - path[i] = assetProxyModel.data(index, 0x100); + for (var i = 0; i < selectedItemCount; ++i) { + index = treeView.selection.selectedIndexes[i]; + paths[i] = assetProxyModel.data(index, 0x100); } } - if (!path) { + if (!paths) { return; } var modalMessage = ""; - var items = selectedItems.toString(); + var items = selectedItemCount.toString(); var isFolder = assetProxyModel.data(treeView.selection.currentIndex, 0x101); var typeString = isFolder ? 'folder' : 'file'; - if (selectedItems > 1) { + if (selectedItemCount > 1) { modalMessage = "You are about to delete " + items + " items \nDo you want to continue?"; } else { - modalMessage = "You are about to delete the following " + typeString + ":\n" + path + "\nDo you want to continue?"; + modalMessage = "You are about to delete the following " + typeString + ":\n" + paths + "\nDo you want to continue?"; } var object = desktop.messageBox({ @@ -367,7 +366,7 @@ Windows.ScrollingWindow { }); object.selected.connect(function(button) { if (button === OriginalDialogs.StandardButton.Yes) { - doDeleteFile(path); + doDeleteFile(paths); } }); } @@ -743,7 +742,7 @@ Windows.ScrollingWindow { // the selection. var clickedIndex = treeView.indexAt(mouse.x, mouse.y); var displayContextMenu = false; - for ( var i = 0; i < selectedItems; ++i) { + for ( var i = 0; i < selectedItemCount; ++i) { var currentSelectedIndex = treeView.selection.selectedIndexes[i]; if (clickedIndex === currentSelectedIndex) { contextMenu.popup(); @@ -761,7 +760,7 @@ Windows.ScrollingWindow { MenuItem { text: "Copy URL" - enabled: (selectedItems == 1) + enabled: (selectedItemCount == 1) onTriggered: { copyURLToClipboard(treeView.selection.currentIndex); } @@ -769,7 +768,7 @@ Windows.ScrollingWindow { MenuItem { text: "Rename" - enabled: (selectedItems == 1) + enabled: (selectedItemCount == 1) onTriggered: { renameFile(treeView.selection.currentIndex); } @@ -777,7 +776,7 @@ Windows.ScrollingWindow { MenuItem { text: "Delete" - enabled: (selectedItems > 0) + enabled: (selectedItemCount > 0) onTriggered: { deleteFile(); } @@ -796,8 +795,8 @@ Windows.ScrollingWindow { function makeText() { var numPendingBakes = assetMappingsModel.numPendingBakes; - if (selectedItems > 1 || numPendingBakes === 0) { - return selectedItems + " items selected"; + if (selectedItemCount > 1 || numPendingBakes === 0) { + return selectedItemCount + " items selected"; } else { return numPendingBakes + " bakes pending" } From 70e23f3ce86f5837f24eaea39c422aeea23c64f0 Mon Sep 17 00:00:00 2001 From: LaShonda Hopper Date: Tue, 30 Jan 2018 14:00:09 -0500 Subject: [PATCH 06/45] [Case 10865] Bringing in multi-select fix from Desktop AssetServer. Changes Committed: modified: interface/resources/qml/hifi/dialogs/TabletAssetServer.qml --- .../qml/hifi/dialogs/TabletAssetServer.qml | 82 ++++++++++--------- 1 file changed, 45 insertions(+), 37 deletions(-) diff --git a/interface/resources/qml/hifi/dialogs/TabletAssetServer.qml b/interface/resources/qml/hifi/dialogs/TabletAssetServer.qml index a02496a252..9c61206592 100644 --- a/interface/resources/qml/hifi/dialogs/TabletAssetServer.qml +++ b/interface/resources/qml/hifi/dialogs/TabletAssetServer.qml @@ -39,7 +39,7 @@ Rectangle { property var assetProxyModel: Assets.proxyModel; property var assetMappingsModel: Assets.mappingModel; property var currentDirectory; - property var selectedItems: treeView.selection.selectedIndexes.length; + property var selectedItemCount: treeView.selection.selectedIndexes.length; Settings { category: "Overlay.AssetServer" @@ -76,17 +76,17 @@ Rectangle { }); } - function doDeleteFile(path) { - console.log("Deleting " + path); + function doDeleteFile(paths) { + console.log("Deleting " + paths); - Assets.deleteMappings(path, function(err) { + Assets.deleteMappings(paths, function(err) { if (err) { - console.log("Asset browser - error deleting path: ", path, err); + console.log("Asset browser - error deleting paths: ", paths, err); - box = errorMessageBox("There was an error deleting:\n" + path + "\n" + err); + box = errorMessageBox("There was an error deleting:\n" + paths + "\n" + err); box.selected.connect(reload); } else { - console.log("Asset browser - finished deleting path: ", path); + console.log("Asset browser - finished deleting paths: ", paths); reload(); } }); @@ -146,7 +146,7 @@ Rectangle { function canAddToWorld(path) { var supportedExtensions = [/\.fbx\b/i, /\.obj\b/i]; - if (selectedItems > 1) { + if (selectedItemCount > 1) { return false; } @@ -156,7 +156,7 @@ Rectangle { } function canRename() { - if (treeView.selection.hasSelection && selectedItems == 1) { + if (treeView.selection.hasSelection && selectedItemCount == 1) { return true; } else { return false; @@ -334,29 +334,28 @@ Rectangle { }); } function deleteFile(index) { - var path = []; + var paths = []; if (!index) { - for (var i = 0; i < selectedItems; i++) { - treeView.selection.setCurrentIndex(treeView.selection.selectedIndexes[i], 0x100); - index = treeView.selection.currentIndex; - path[i] = assetProxyModel.data(index, 0x100); + for (var i = 0; i < selectedItemCount; ++i) { + index = treeView.selection.selectedIndexes[i]; + paths[i] = assetProxyModel.data(index, 0x100); } } - if (!path) { + if (!paths) { return; } var modalMessage = ""; - var items = selectedItems.toString(); + var items = selectedItemCount.toString(); var isFolder = assetProxyModel.data(treeView.selection.currentIndex, 0x101); var typeString = isFolder ? 'folder' : 'file'; - if (selectedItems > 1) { + if (selectedItemCount > 1) { modalMessage = "You are about to delete " + items + " items \nDo you want to continue?"; } else { - modalMessage = "You are about to delete the following " + typeString + ":\n" + path + "\nDo you want to continue?"; + modalMessage = "You are about to delete the following " + typeString + ":\n" + paths + "\nDo you want to continue?"; } var object = tabletRoot.messageBox({ @@ -368,7 +367,7 @@ Rectangle { }); object.selected.connect(function(button) { if (button === OriginalDialogs.StandardButton.Yes) { - doDeleteFile(path); + doDeleteFile(paths); } }); } @@ -693,7 +692,7 @@ Rectangle { } } } - } + }// End_OF( itemLoader ) Rectangle { id: treeLabelToolTip @@ -730,50 +729,59 @@ Rectangle { showTimer.stop(); treeLabelToolTip.visible = false; } - } + }// End_OF( treeLabelToolTip ) MouseArea { propagateComposedEvents: true anchors.fill: parent acceptedButtons: Qt.RightButton onClicked: { - if (!HMD.active) { // Popup only displays properly on desktop - var index = treeView.indexAt(mouse.x, mouse.y); - treeView.selection.setCurrentIndex(index, 0x0002); - contextMenu.currentIndex = index; - contextMenu.popup(); + if (treeView.selection.hasSelection && !HMD.active) { // Popup only displays properly on desktop + // Only display the popup if the click triggered within + // the selection. + var clickedIndex = treeView.indexAt(mouse.x, mouse.y); + var displayContextMenu = false; + for ( var i = 0; i < selectedItemCount; ++i) { + var currentSelectedIndex = treeView.selection.selectedIndexes[i]; + if (clickedIndex === currentSelectedIndex) { + contextMenu.popup(); + break; + } + } } } } - + Menu { id: contextMenu title: "Edit" property var url: "" - property var currentIndex: null MenuItem { text: "Copy URL" + enabled: (selectedItemCount == 1) onTriggered: { - copyURLToClipboard(contextMenu.currentIndex); + copyURLToClipboard(treeView.selection.currentIndex); } } MenuItem { text: "Rename" + enabled: (selectedItemCount == 1) onTriggered: { - renameFile(contextMenu.currentIndex); + renameFile(treeView.selection.currentIndex); } } MenuItem { text: "Delete" + enabled: (selectedItemCount > 0) onTriggered: { - deleteFile(contextMenu.currentIndex); + deleteFile(); } } - } - } + }// End_OF( contextMenu ) + }// End_OF( treeView ) Row { id: infoRow @@ -786,8 +794,8 @@ Rectangle { function makeText() { var numPendingBakes = assetMappingsModel.numPendingBakes; - if (selectedItems > 1 || numPendingBakes === 0) { - return selectedItems + " items selected"; + if (selectedItemCount > 1 || numPendingBakes === 0) { + return selectedItemCount + " items selected"; } else { return numPendingBakes + " bakes pending" } @@ -884,7 +892,7 @@ Rectangle { "Baking compresses and optimizes files for faster network transfer and display. We recommend you bake your content to reduce initial load times for your visitors."); } } - } + }// End_OF( infoRow ) HifiControls.TabletContentSection { id: uploadSection @@ -961,7 +969,7 @@ Rectangle { } } } - } + }// End_OF( uploadSection ) } } From 22b33c7391a76afdb4dcef188404c0b7c56ee6a0 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Mon, 5 Feb 2018 11:22:40 +1300 Subject: [PATCH 07/45] Update Overlays circle3d JSDoc per recent code changes --- interface/src/ui/overlays/Circle3DOverlay.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/interface/src/ui/overlays/Circle3DOverlay.cpp b/interface/src/ui/overlays/Circle3DOverlay.cpp index 5e38f28a06..33f40f7c63 100644 --- a/interface/src/ui/overlays/Circle3DOverlay.cpp +++ b/interface/src/ui/overlays/Circle3DOverlay.cpp @@ -425,10 +425,10 @@ void Circle3DOverlay::setProperties(const QVariantMap& properties) { * Write-only. * @property {Color} outerColor - Sets the values of outerStartColor and outerEndColor. * Write-only. - * @property {Color} innerStartcolor - The color at the inner start point of the overlay. Write-only. - * @property {Color} innerEndColor - The color at the inner end point of the overlay. Write-only. - * @property {Color} outerStartColor - The color at the outer start point of the overlay. Write-only. - * @property {Color} outerEndColor - The color at the outer end point of the overlay. Write-only. + * @property {Color} innerStartcolor - The color at the inner start point of the overlay. + * @property {Color} innerEndColor - The color at the inner end point of the overlay. + * @property {Color} outerStartColor - The color at the outer start point of the overlay. + * @property {Color} outerEndColor - The color at the outer end point of the overlay. * @property {number} alpha=0.5 - The opacity of the overlay, 0.0 - 1.0. Setting this value also sets * the values of innerStartAlpha, innerEndAlpha, outerStartAlpha, and * outerEndAlpha. Synonym: Alpha; write-only. @@ -440,10 +440,10 @@ void Circle3DOverlay::setProperties(const QVariantMap& properties) { * Write-only. * @property {number} outerAlpha - Sets the values of outerStartAlpha and outerEndAlpha. * Write-only. - * @property {number} innerStartAlpha=0 - The alpha at the inner start point of the overlay. Write-only. - * @property {number} innerEndAlpha=0 - The alpha at the inner end point of the overlay. Write-only. - * @property {number} outerStartAlpha=0 - The alpha at the outer start point of the overlay. Write-only. - * @property {number} outerEndAlpha=0 - The alpha at the outer end point of the overlay. Write-only. + * @property {number} innerStartAlpha=0 - The alpha at the inner start point of the overlay. + * @property {number} innerEndAlpha=0 - The alpha at the inner end point of the overlay. + * @property {number} outerStartAlpha=0 - The alpha at the outer start point of the overlay. + * @property {number} outerEndAlpha=0 - The alpha at the outer end point of the overlay. * @property {boolean} hasTickMarks=false - If true, tick marks are drawn. * @property {number} majorTickMarksAngle=0 - The angle between major tick marks, in degrees. From e87067f93d0841312c540073b755100ceb458f48 Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Mon, 5 Feb 2018 15:31:37 -0800 Subject: [PATCH 08/45] fix variable naming and edge case --- interface/src/avatar/MyAvatar.cpp | 10 ++++++---- interface/src/avatar/MyAvatar.h | 2 +- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 516c601931..0ee409745f 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -1448,6 +1448,10 @@ void MyAvatar::clearJointsData() { } void MyAvatar::setSkeletonModelURL(const QUrl& skeletonModelURL) { + if (skeletonModelURL != _skeletonModelURL) { + _shouldInitHeadBones = true; + } + Avatar::setSkeletonModelURL(skeletonModelURL); _skeletonModel->setVisibleInScene(true, qApp->getMain3DScene(), render::ItemKey::TAG_BITS_NONE); _headBoneSet.clear(); @@ -1893,14 +1897,12 @@ void MyAvatar::postUpdate(float deltaTime, const render::ScenePointer& scene) { Avatar::postUpdate(deltaTime, scene); - if (_skeletonModel->isLoaded() && !_skeletonModel->getRig().getAnimNode() && _initHeadBones) { + if (_skeletonModel->isLoaded() && !_skeletonModel->getRig().getAnimNode() && _shouldInitHeadBones) { initHeadBones(); _skeletonModel->setCauterizeBoneSet(_headBoneSet); _fstAnimGraphOverrideUrl = _skeletonModel->getGeometry()->getAnimGraphOverrideUrl(); initAnimGraph(); - _initHeadBones = false; - } else if (!_skeletonModel->isLoaded()) { - _initHeadBones = true; + _shouldInitHeadBones = false; } if (_enableDebugDrawDefaultPose || _enableDebugDrawAnimPose) { diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 532eb06974..45dddb45a4 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -817,7 +817,7 @@ private: bool _enableDebugDrawIKConstraints { false }; bool _enableDebugDrawIKChains { false }; bool _enableDebugDrawDetailedCollision { false }; - bool _initHeadBones { false }; + bool _shouldInitHeadBones { true }; mutable bool _cauterizationNeedsUpdate; // do we need to scan children and update their "cauterized" state? From b6ac3484284f7d05e9322658bd281d91bdd95d1e Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Wed, 7 Feb 2018 16:30:07 -0800 Subject: [PATCH 09/45] get children of animated entity joints to follow along, again --- .../entities-renderer/src/RenderableModelEntityItem.cpp | 9 +++++++++ libraries/shared/src/SpatiallyNestable.h | 8 ++++---- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index 137203f475..c8d22bb06c 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -985,6 +985,7 @@ void RenderableModelEntityItem::copyAnimationJointDataToModel() { return; } + bool changed { false }; // relay any inbound joint changes from scripts/animation/network to the model/rig _jointDataLock.withWriteLock([&] { for (int index = 0; index < _localJointData.size(); ++index) { @@ -992,13 +993,21 @@ void RenderableModelEntityItem::copyAnimationJointDataToModel() { if (jointData.rotationDirty) { model->setJointRotation(index, true, jointData.joint.rotation, 1.0f); jointData.rotationDirty = false; + changed = true; } if (jointData.translationDirty) { model->setJointTranslation(index, true, jointData.joint.translation, 1.0f); jointData.translationDirty = false; + changed = true; } } }); + + if (changed) { + forEachChild([&](SpatiallyNestablePointer object) { + object->locationChanged(tellPhysics); + }); + } } using namespace render; diff --git a/libraries/shared/src/SpatiallyNestable.h b/libraries/shared/src/SpatiallyNestable.h index 090ca4c266..5d4793ba4e 100644 --- a/libraries/shared/src/SpatiallyNestable.h +++ b/libraries/shared/src/SpatiallyNestable.h @@ -207,6 +207,10 @@ public: void dump(const QString& prefix = "") const; + virtual void locationChanged(bool tellPhysics = true); // called when a this object's location has changed + virtual void dimensionsChanged() { _queryAACubeSet = false; } // called when a this object's dimensions have changed + virtual void parentDeleted() { } // called on children of a deleted parent + protected: const NestableType _nestableType; // EntityItem or an AvatarData QUuid _id; @@ -218,10 +222,6 @@ protected: mutable ReadWriteLockable _childrenLock; mutable QHash _children; - virtual void locationChanged(bool tellPhysics = true); // called when a this object's location has changed - virtual void dimensionsChanged() { _queryAACubeSet = false; } // called when a this object's dimensions have changed - virtual void parentDeleted() { } // called on children of a deleted parent - // _queryAACube is used to decide where something lives in the octree mutable AACube _queryAACube; mutable bool _queryAACubeSet { false }; From 31c2b8fea92c3b01ca9d8b379ee705205a1aebc5 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Wed, 7 Feb 2018 16:31:38 -0800 Subject: [PATCH 10/45] oops --- libraries/entities-renderer/src/RenderableModelEntityItem.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index c8d22bb06c..ca15382c71 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -1005,7 +1005,7 @@ void RenderableModelEntityItem::copyAnimationJointDataToModel() { if (changed) { forEachChild([&](SpatiallyNestablePointer object) { - object->locationChanged(tellPhysics); + object->locationChanged(false); }); } } From 87f77b625784aac995379f76654b4b9e4c450317 Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Mon, 22 Jan 2018 18:12:12 -0800 Subject: [PATCH 11/45] Asset Server Backup --- assignment-client/src/assets/AssetServer.cpp | 58 ++- assignment-client/src/assets/AssetServer.h | 20 +- .../src/assets/SendAssetTask.cpp | 6 +- .../src/assets/UploadAssetTask.cpp | 12 +- domain-server/src/BackupSupervisor.cpp | 409 ++++++++++++++++++ domain-server/src/BackupSupervisor.h | 86 ++++ domain-server/src/DomainServer.cpp | 19 +- domain-server/src/DomainServer.h | 5 + libraries/networking/src/AssetClient.cpp | 22 +- libraries/networking/src/AssetUtils.h | 7 +- libraries/networking/src/DomainHandler.h | 2 +- libraries/networking/src/LimitedNodeList.cpp | 32 +- libraries/networking/src/LimitedNodeList.h | 4 + libraries/networking/src/MappingRequest.cpp | 3 - libraries/networking/src/MappingRequest.h | 8 +- libraries/networking/src/NodeList.h | 4 + libraries/networking/src/PacketReceiver.cpp | 112 ++--- libraries/networking/src/udt/PacketHeaders.h | 11 + 18 files changed, 677 insertions(+), 143 deletions(-) create mode 100644 domain-server/src/BackupSupervisor.cpp create mode 100644 domain-server/src/BackupSupervisor.h diff --git a/assignment-client/src/assets/AssetServer.cpp b/assignment-client/src/assets/AssetServer.cpp index 0a868737b0..1ae65290ff 100644 --- a/assignment-client/src/assets/AssetServer.cpp +++ b/assignment-client/src/assets/AssetServer.cpp @@ -464,32 +464,41 @@ void AssetServer::handleAssetMappingOperation(QSharedPointer me auto replyPacket = NLPacketList::create(PacketType::AssetMappingOperationReply, QByteArray(), true, true); replyPacket->writePrimitive(messageID); + bool canWriteToAssetServer = true; + if (senderNode) { + canWriteToAssetServer = senderNode->getCanWriteToAssetServer(); + } + switch (operationType) { case AssetMappingOperationType::Get: - handleGetMappingOperation(*message, senderNode, *replyPacket); + handleGetMappingOperation(*message, *replyPacket); break; case AssetMappingOperationType::GetAll: - handleGetAllMappingOperation(*message, senderNode, *replyPacket); + handleGetAllMappingOperation(*replyPacket); break; case AssetMappingOperationType::Set: - handleSetMappingOperation(*message, senderNode, *replyPacket); + handleSetMappingOperation(*message, canWriteToAssetServer, *replyPacket); break; case AssetMappingOperationType::Delete: - handleDeleteMappingsOperation(*message, senderNode, *replyPacket); + handleDeleteMappingsOperation(*message, canWriteToAssetServer, *replyPacket); break; case AssetMappingOperationType::Rename: - handleRenameMappingOperation(*message, senderNode, *replyPacket); + handleRenameMappingOperation(*message, canWriteToAssetServer, *replyPacket); break; case AssetMappingOperationType::SetBakingEnabled: - handleSetBakingEnabledOperation(*message, senderNode, *replyPacket); + handleSetBakingEnabledOperation(*message, canWriteToAssetServer, *replyPacket); break; } auto nodeList = DependencyManager::get(); - nodeList->sendPacketList(std::move(replyPacket), *senderNode); + if (senderNode) { + nodeList->sendPacketList(std::move(replyPacket), *senderNode); + } else { + nodeList->sendPacketList(std::move(replyPacket), message->getSenderSockAddr()); + } } -void AssetServer::handleGetMappingOperation(ReceivedMessage& message, SharedNodePointer senderNode, NLPacketList& replyPacket) { +void AssetServer::handleGetMappingOperation(ReceivedMessage& message, NLPacketList& replyPacket) { QString assetPath = message.readString(); QUrl url { assetPath }; @@ -568,7 +577,7 @@ void AssetServer::handleGetMappingOperation(ReceivedMessage& message, SharedNode } } -void AssetServer::handleGetAllMappingOperation(ReceivedMessage& message, SharedNodePointer senderNode, NLPacketList& replyPacket) { +void AssetServer::handleGetAllMappingOperation(NLPacketList& replyPacket) { replyPacket.writePrimitive(AssetUtils::AssetServerError::NoError); uint32_t count = (uint32_t)_fileMappings.size(); @@ -591,8 +600,8 @@ void AssetServer::handleGetAllMappingOperation(ReceivedMessage& message, SharedN } } -void AssetServer::handleSetMappingOperation(ReceivedMessage& message, SharedNodePointer senderNode, NLPacketList& replyPacket) { - if (senderNode->getCanWriteToAssetServer()) { +void AssetServer::handleSetMappingOperation(ReceivedMessage& message, bool hasWriteAccess, NLPacketList& replyPacket) { + if (hasWriteAccess) { QString assetPath = message.readString(); auto assetHash = message.read(AssetUtils::SHA256_HASH_LENGTH).toHex(); @@ -614,8 +623,8 @@ void AssetServer::handleSetMappingOperation(ReceivedMessage& message, SharedNode } } -void AssetServer::handleDeleteMappingsOperation(ReceivedMessage& message, SharedNodePointer senderNode, NLPacketList& replyPacket) { - if (senderNode->getCanWriteToAssetServer()) { +void AssetServer::handleDeleteMappingsOperation(ReceivedMessage& message, bool hasWriteAccess, NLPacketList& replyPacket) { + if (hasWriteAccess) { int numberOfDeletedMappings { 0 }; message.readPrimitive(&numberOfDeletedMappings); @@ -642,8 +651,8 @@ void AssetServer::handleDeleteMappingsOperation(ReceivedMessage& message, Shared } } -void AssetServer::handleRenameMappingOperation(ReceivedMessage& message, SharedNodePointer senderNode, NLPacketList& replyPacket) { - if (senderNode->getCanWriteToAssetServer()) { +void AssetServer::handleRenameMappingOperation(ReceivedMessage& message, bool hasWriteAccess, NLPacketList& replyPacket) { + if (hasWriteAccess) { QString oldPath = message.readString(); QString newPath = message.readString(); @@ -664,8 +673,8 @@ void AssetServer::handleRenameMappingOperation(ReceivedMessage& message, SharedN } } -void AssetServer::handleSetBakingEnabledOperation(ReceivedMessage& message, SharedNodePointer senderNode, NLPacketList& replyPacket) { - if (senderNode->getCanWriteToAssetServer()) { +void AssetServer::handleSetBakingEnabledOperation(ReceivedMessage& message, bool hasWriteAccess, NLPacketList& replyPacket) { + if (hasWriteAccess) { bool enabled { true }; message.readPrimitive(&enabled); @@ -739,9 +748,14 @@ void AssetServer::handleAssetGet(QSharedPointer message, Shared } void AssetServer::handleAssetUpload(QSharedPointer message, SharedNodePointer senderNode) { + bool canWriteToAssetServer = true; + if (senderNode) { + canWriteToAssetServer = senderNode->getCanWriteToAssetServer(); + } - if (senderNode->getCanWriteToAssetServer()) { - qCDebug(asset_server) << "Starting an UploadAssetTask for upload from" << uuidStringWithoutCurlyBraces(senderNode->getUUID()); + + if (canWriteToAssetServer) { + qCDebug(asset_server) << "Starting an UploadAssetTask for upload from" << uuidStringWithoutCurlyBraces(message->getSourceID()); auto task = new UploadAssetTask(message, senderNode, _filesDirectory, _filesizeLimit); _transferTaskPool.start(task); @@ -761,7 +775,11 @@ void AssetServer::handleAssetUpload(QSharedPointer message, Sha // send off the packet auto nodeList = DependencyManager::get(); - nodeList->sendPacket(std::move(permissionErrorPacket), *senderNode); + if (senderNode) { + nodeList->sendPacket(std::move(permissionErrorPacket), *senderNode); + } else { + nodeList->sendPacket(std::move(permissionErrorPacket), message->getSenderSockAddr()); + } } } diff --git a/assignment-client/src/assets/AssetServer.h b/assignment-client/src/assets/AssetServer.h index 5e7a3c1700..c6336a3a4d 100644 --- a/assignment-client/src/assets/AssetServer.h +++ b/assignment-client/src/assets/AssetServer.h @@ -29,9 +29,6 @@ namespace std { } struct AssetMeta { - AssetMeta() { - } - int bakeVersion { 0 }; bool failedLastBake { false }; QString lastBakeErrors; @@ -60,14 +57,15 @@ private slots: void sendStatsPacket() override; private: - using Mappings = std::unordered_map; + void handleGetMappingOperation(ReceivedMessage& message, NLPacketList& replyPacket); + void handleGetAllMappingOperation(NLPacketList& replyPacket); + void handleSetMappingOperation(ReceivedMessage& message, bool hasWriteAccess, NLPacketList& replyPacket); + void handleDeleteMappingsOperation(ReceivedMessage& message, bool hasWriteAccess, NLPacketList& replyPacket); + void handleRenameMappingOperation(ReceivedMessage& message, bool hasWriteAccess, NLPacketList& replyPacket); + void handleSetBakingEnabledOperation(ReceivedMessage& message, bool hasWriteAccess, NLPacketList& replyPacket); - void handleGetMappingOperation(ReceivedMessage& message, SharedNodePointer senderNode, NLPacketList& replyPacket); - void handleGetAllMappingOperation(ReceivedMessage& message, SharedNodePointer senderNode, NLPacketList& replyPacket); - void handleSetMappingOperation(ReceivedMessage& message, SharedNodePointer senderNode, NLPacketList& replyPacket); - void handleDeleteMappingsOperation(ReceivedMessage& message, SharedNodePointer senderNode, NLPacketList& replyPacket); - void handleRenameMappingOperation(ReceivedMessage& message, SharedNodePointer senderNode, NLPacketList& replyPacket); - void handleSetBakingEnabledOperation(ReceivedMessage& message, SharedNodePointer senderNode, NLPacketList& replyPacket); + void handleAssetServerBackup(ReceivedMessage& message, NLPacketList& replyPacket); + void handleAssetServerRestore(ReceivedMessage& message, NLPacketList& replyPacket); // Mapping file operations must be called from main assignment thread only bool loadMappingsFromFile(); @@ -111,7 +109,7 @@ private: /// Remove baked paths when the original asset is deleteds void removeBakedPathsForDeletedAsset(AssetUtils::AssetHash originalAssetHash); - Mappings _fileMappings; + AssetUtils::Mappings _fileMappings; QDir _resourcesDirectory; QDir _filesDirectory; diff --git a/assignment-client/src/assets/SendAssetTask.cpp b/assignment-client/src/assets/SendAssetTask.cpp index 6da092357f..d17d254afd 100644 --- a/assignment-client/src/assets/SendAssetTask.cpp +++ b/assignment-client/src/assets/SendAssetTask.cpp @@ -112,5 +112,9 @@ void SendAssetTask::run() { } auto nodeList = DependencyManager::get(); - nodeList->sendPacketList(std::move(replyPacketList), *_senderNode); + if (_senderNode) { + nodeList->sendPacketList(std::move(replyPacketList), *_senderNode); + } else { + nodeList->sendPacketList(std::move(replyPacketList), _message->getSenderSockAddr()); + } } diff --git a/assignment-client/src/assets/UploadAssetTask.cpp b/assignment-client/src/assets/UploadAssetTask.cpp index 68bf4db5fd..f2ba431875 100644 --- a/assignment-client/src/assets/UploadAssetTask.cpp +++ b/assignment-client/src/assets/UploadAssetTask.cpp @@ -42,8 +42,7 @@ void UploadAssetTask::run() { uint64_t fileSize; buffer.read(reinterpret_cast(&fileSize), sizeof(fileSize)); - qDebug() << "UploadAssetTask reading a file of " << fileSize << "bytes from" - << uuidStringWithoutCurlyBraces(_senderNode->getUUID()); + qDebug() << "UploadAssetTask reading a file of " << fileSize << "bytes."; auto replyPacket = NLPacket::create(PacketType::AssetUploadReply, -1, true); replyPacket->writePrimitive(messageID); @@ -56,8 +55,7 @@ void UploadAssetTask::run() { auto hash = AssetUtils::hashData(fileData); auto hexHash = hash.toHex(); - qDebug() << "Hash for uploaded file from" << uuidStringWithoutCurlyBraces(_senderNode->getUUID()) - << "is: (" << hexHash << ") "; + qDebug() << "Hash for uploaded file is: (" << hexHash << ") "; QFile file { _resourcesDir.filePath(QString(hexHash)) }; @@ -103,5 +101,9 @@ void UploadAssetTask::run() { } auto nodeList = DependencyManager::get(); - nodeList->sendPacket(std::move(replyPacket), *_senderNode); + if (_senderNode) { + nodeList->sendPacket(std::move(replyPacket), *_senderNode); + } else { + nodeList->sendPacket(std::move(replyPacket), _receivedMessage->getSenderSockAddr()); + } } diff --git a/domain-server/src/BackupSupervisor.cpp b/domain-server/src/BackupSupervisor.cpp new file mode 100644 index 0000000000..829208913f --- /dev/null +++ b/domain-server/src/BackupSupervisor.cpp @@ -0,0 +1,409 @@ +// +// BackupSupervisor.cpp +// assignment-client/src +// +// Created by Clement Brisset on 1/12/18. +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "BackupSupervisor.h" + +#include +#include + +#include +#include +#include +#include +#include + +const QString BACKUPS_DIR = "backups/"; +const QString ASSETS_DIR = "files/"; +const QString MAPPINGS_PREFIX = "mappings-"; + +using namespace std; + +BackupSupervisor::BackupSupervisor() { + _backupsDirectory = PathUtils::getAppDataPath() + BACKUPS_DIR; + QDir backupDir { _backupsDirectory }; + if (!backupDir.exists()) { + backupDir.mkpath("."); + } + + _assetsDirectory = PathUtils::getAppDataPath() + BACKUPS_DIR + ASSETS_DIR; + QDir assetsDir { _assetsDirectory }; + if (!assetsDir.exists()) { + assetsDir.mkpath("."); + } + + loadAllBackups(); +} + +void BackupSupervisor::loadAllBackups() { + _backups.clear(); + _assetsInBackups.clear(); + _assetsOnDisk.clear(); + _allBackupsLoadedSuccessfully = true; + + QDir assetsDir { _assetsDirectory }; + auto assetNames = assetsDir.entryList(QDir::Files); + qDebug() << "Loading" << assetNames.size() << "assets."; + + // store all valid hashes + copy_if(begin(assetNames), end(assetNames), + inserter(_assetsOnDisk, begin(_assetsOnDisk)), AssetUtils::isValidHash); + + QDir backupsDir { _backupsDirectory }; + auto files = backupsDir.entryList({ MAPPINGS_PREFIX + "*.json" }, QDir::Files); + qDebug() << "Loading" << files.size() << "backups."; + + for (const auto& fileName : files) { + auto filePath = backupsDir.filePath(fileName); + auto success = loadBackup(filePath); + if (!success) { + qCritical() << "Failed to load backup file" << filePath; + _allBackupsLoadedSuccessfully = false; + } + } + + vector missingAssets; + set_difference(begin(_assetsInBackups), end(_assetsInBackups), + begin(_assetsOnDisk), end(_assetsOnDisk), + back_inserter(missingAssets)); + if (missingAssets.size() > 0) { + qWarning() << "Found" << missingAssets.size() << "assets missing."; + } + + vector deprecatedAssets; + set_difference(begin(_assetsOnDisk), end(_assetsOnDisk), + begin(_assetsInBackups), end(_assetsInBackups), + back_inserter(deprecatedAssets)); + + if (deprecatedAssets.size() > 0) { + qDebug() << "Found" << deprecatedAssets.size() << "assets to delete."; + if (_allBackupsLoadedSuccessfully) { + for (const auto& hash : deprecatedAssets) { + QFile::remove(_assetsDirectory + hash); + } + } else { + qWarning() << "Some backups did not load properly, aborting deleting for safety."; + } + } +} + +bool BackupSupervisor::loadBackup(const QString& backupFile) { + _backups.push_back({ backupFile.toStdString(), {}, false }); + auto& backup = _backups.back(); + + QFile file { backupFile }; + if (!file.open(QFile::ReadOnly)) { + qCritical() << "Could not open backup file:" << backupFile; + backup.corruptedBackup = true; + return false; + } + QJsonParseError error; + auto document = QJsonDocument::fromJson(file.readAll(), &error); + if (document.isNull()) { + qCritical() << "Could not parse backup file:" << backupFile; + qCritical() << " Error:" << error.errorString(); + backup.corruptedBackup = true; + return false; + } + + if (!document.isObject()) { + qCritical() << "Backup file corrupted" << backupFile; + backup.corruptedBackup = true; + return false; + } + + auto jsonObject = document.object(); + for (auto it = begin(jsonObject); it != end(jsonObject); ++it) { + if (!it.value().isString()) { + qCritical() << "Corrupted mapping in backup file" << backupFile << ":" << it.key(); + backup.corruptedBackup = true; + return false; + } + const auto& assetPath = it.key(); + const auto& assetHash = it.value().toString(); + + backup.mappings[assetPath] = assetHash; + _assetsInBackups.insert(assetHash); + } + + _backups.push_back(backup); + return true; +} + +void BackupSupervisor::backupAssetServer() { + if (backupInProgress() || restoreInProgress()) { + qWarning() << "There is already a backup/restore in progress."; + return; + } + + auto assetClient = DependencyManager::get(); + auto request = assetClient->createGetAllMappingsRequest(); + + connect(request, &GetAllMappingsRequest::finished, this, [this](GetAllMappingsRequest* request) { + qDebug() << "Got" << request->getMappings().size() << "mappings!"; + + if (request->getError() != MappingRequest::NoError) { + qCritical() << "Could not complete backup."; + qCritical() << " Error:" << request->getErrorString(); + finishBackup(); + request->deleteLater(); + return; + } + + if (!writeBackupFile(request->getMappings())) { + finishBackup(); + request->deleteLater(); + return; + } + + assert(!_backups.empty()); + const auto& mappings = _backups.back().mappings; + backupMissingFiles(mappings); + + request->deleteLater(); + }); + + startBackup(); + qDebug() << "Requesting mappings for backup!"; + request->start(); +} + +void BackupSupervisor::backupMissingFiles(const AssetUtils::Mappings& mappings) { + _assetsLeftToRequest.reserve(mappings.size()); + for (auto& mapping : mappings) { + const auto& hash = mapping.second; + if (_assetsOnDisk.find(hash) == end(_assetsOnDisk)) { + _assetsLeftToRequest.push_back(hash); + } + } + + backupNextMissingFile(); +} + +void BackupSupervisor::backupNextMissingFile() { + if (_assetsLeftToRequest.empty()) { + finishBackup(); + return; + } + + auto hash = _assetsLeftToRequest.back(); + _assetsLeftToRequest.pop_back(); + + auto assetClient = DependencyManager::get(); + auto assetRequest = assetClient->createRequest(hash); + + connect(assetRequest, &AssetRequest::finished, this, [this](AssetRequest* request) { + if (request->getError() == AssetRequest::NoError) { + qDebug() << "Got" << request->getHash(); + + bool success = writeAssetFile(request->getHash(),request->getData()); + if (!success) { + qCritical() << "Failed to write asset file" << request->getHash(); + } + } else { + qCritical() << "Failed to backup asset" << request->getHash(); + } + + backupNextMissingFile(); + + request->deleteLater(); + }); + + assetRequest->start(); +} + +bool BackupSupervisor::writeBackupFile(const AssetUtils::AssetMappings& mappings) { + auto filename = MAPPINGS_PREFIX + QDateTime::currentDateTimeUtc().toString(Qt::ISODate) + ".json"; + QFile file { PathUtils::getAppDataPath() + BACKUPS_DIR + filename }; + if (!file.open(QFile::WriteOnly)) { + qCritical() << "Could not open backup file" << file.fileName(); + return false; + } + + AssetServerBackup backup; + QJsonObject jsonObject; + for (auto& mapping : mappings) { + backup.mappings[mapping.first] = mapping.second.hash; + _assetsInBackups.insert(mapping.second.hash); + jsonObject.insert(mapping.first, mapping.second.hash); + } + + QJsonDocument document(jsonObject); + file.write(document.toJson()); + + backup.filePath = file.fileName().toStdString(); + _backups.push_back(backup); + + return true; +} + +bool BackupSupervisor::writeAssetFile(const AssetUtils::AssetHash& hash, const QByteArray& data) { + QDir assetsDir { _assetsDirectory }; + QFile file { assetsDir.filePath(hash) }; + if (!file.open(QFile::WriteOnly)) { + qCritical() << "Could not open backup file" << file.fileName(); + return false; + } + + file.write(data); + + _assetsOnDisk.insert(hash); + + return true; +} + +void BackupSupervisor::restoreAssetServer(int backupIndex) { + if (backupInProgress() || restoreInProgress()) { + qWarning() << "There is already a backup/restore in progress."; + return; + } + + auto assetClient = DependencyManager::get(); + auto request = assetClient->createGetAllMappingsRequest(); + + connect(request, &GetAllMappingsRequest::finished, this, [this, backupIndex](GetAllMappingsRequest* request) { + qDebug() << "Got" << request->getMappings().size() << "mappings!"; + + if (request->getError() == MappingRequest::NoError) { + const auto& newMappings = _backups.at(backupIndex).mappings; + computeServerStateDifference(request->getMappings(), newMappings); + + restoreAllAssets(); + } else { + finishRestore(); + } + + request->deleteLater(); + }); + + startRestore(); + qDebug() << "Requesting mappings for restore!"; + request->start(); +} + +void BackupSupervisor::computeServerStateDifference(const AssetUtils::AssetMappings& currentMappings, + const AssetUtils::Mappings& newMappings) { + _mappingsLeftToSet.reserve((int)newMappings.size()); + _assetsLeftToUpload.reserve((int)newMappings.size()); + _mappingsLeftToDelete.reserve((int)currentMappings.size()); + + set currentAssets; + for (const auto& currentMapping : currentMappings) { + const auto& currentPath = currentMapping.first; + const auto& currentHash = currentMapping.second.hash; + + if (newMappings.find(currentPath) == end(newMappings)) { + _mappingsLeftToDelete.push_back(currentPath); + } + currentAssets.insert(currentHash); + } + + for (const auto& newMapping : newMappings) { + const auto& newPath = newMapping.first; + const auto& newHash = newMapping.second; + + auto it = currentMappings.find(newPath); + if (it == end(currentMappings) || it->second.hash != newHash) { + _mappingsLeftToSet.push_back({ newPath, newHash }); + } + if (currentAssets.find(newHash) == end(currentAssets)) { + _assetsLeftToUpload.push_back(newHash); + } + } + + qDebug() << "Mappings to set:" << _mappingsLeftToSet.size(); + qDebug() << "Mappings to del:" << _mappingsLeftToDelete.size(); + qDebug() << "Assets to upload:" << _assetsLeftToUpload.size(); +} + +void BackupSupervisor::restoreAllAssets() { + restoreNextAsset(); +} + +void BackupSupervisor::restoreNextAsset() { + if (_assetsLeftToUpload.empty()) { + updateMappings(); + return; + } + + auto hash = _assetsLeftToUpload.back(); + _assetsLeftToUpload.pop_back(); + + auto assetFilename = _assetsDirectory + hash; + + auto assetClient = DependencyManager::get(); + auto request = assetClient->createUpload(assetFilename); + + connect(request, &AssetUpload::finished, this, [this](AssetUpload* request) { + if (request->getError() != AssetUpload::NoError) { + qCritical() << "Failed to restore asset:" << request->getFilename(); + qCritical() << " Error:" << request->getErrorString(); + } + + restoreNextAsset(); + + request->deleteLater(); + }); + + request->start(); +} + +void BackupSupervisor::updateMappings() { + auto assetClient = DependencyManager::get(); + for (const auto& mapping : _mappingsLeftToSet) { + auto request = assetClient->createSetMappingRequest(mapping.first, mapping.second); + connect(request, &SetMappingRequest::finished, this, [this](SetMappingRequest* request) { + if (request->getError() != MappingRequest::NoError) { + qCritical() << "Failed to set mapping:" << request->getPath(); + qCritical() << " Error:" << request->getErrorString(); + } + + if (--_mappingRequestsInFlight == 0) { + finishRestore(); + } + + request->deleteLater(); + }); + + request->start(); + ++_mappingRequestsInFlight; + } + _mappingsLeftToSet.clear(); + + auto request = assetClient->createDeleteMappingsRequest(_mappingsLeftToDelete); + connect(request, &DeleteMappingsRequest::finished, this, [this](DeleteMappingsRequest* request) { + if (request->getError() != MappingRequest::NoError) { + qCritical() << "Failed to delete mappings"; + qCritical() << " Error:" << request->getErrorString(); + } + + if (--_mappingRequestsInFlight == 0) { + finishRestore(); + } + + request->deleteLater(); + }); + _mappingsLeftToDelete.clear(); + + request->start(); + ++_mappingRequestsInFlight; +} +bool BackupSupervisor::deleteBackup(int backupIndex) { + if (backupInProgress() || restoreInProgress()) { + qWarning() << "There is a backup/restore in progress."; + return false; + } + const auto& filePath = _backups.at(backupIndex).filePath; + auto success = QFile::remove(filePath.c_str()); + + loadAllBackups(); + + return success; +} diff --git a/domain-server/src/BackupSupervisor.h b/domain-server/src/BackupSupervisor.h new file mode 100644 index 0000000000..a4129fca12 --- /dev/null +++ b/domain-server/src/BackupSupervisor.h @@ -0,0 +1,86 @@ +// +// BackupSupervisor.h +// assignment-client/src +// +// Created by Clement Brisset on 1/12/18. +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_BackupSupervisor_h +#define hifi_BackupSupervisor_h + +#include +#include + +#include + +#include + +#include + +struct AssetServerBackup { + std::string filePath; + AssetUtils::Mappings mappings; + bool corruptedBackup; +}; + +class BackupSupervisor : public QObject { + Q_OBJECT + +public: + BackupSupervisor(); + + void backupAssetServer(); + void restoreAssetServer(int backupIndex); + bool deleteBackup(int backupIndex); + + const std::vector& getBackups() const { return _backups; }; + + bool backupInProgress() const { return _backupInProgress; } + bool restoreInProgress() const { return _restoreInProgress; } + +private: + void loadAllBackups(); + bool loadBackup(const QString& backupFile); + + void startBackup() { _backupInProgress = true; } + void finishBackup() { _backupInProgress = false; } + void backupMissingFiles(const AssetUtils::Mappings& mappings); + void backupNextMissingFile(); + bool writeBackupFile(const AssetUtils::AssetMappings& mappings); + bool writeAssetFile(const AssetUtils::AssetHash& hash, const QByteArray& data); + + + void startRestore() { _restoreInProgress = true; } + void finishRestore() { _restoreInProgress = false; } + void computeServerStateDifference(const AssetUtils::AssetMappings& currentMappings, + const AssetUtils::Mappings& newMappings); + void restoreAllAssets(); + void restoreNextAsset(); + void updateMappings(); + + QString _backupsDirectory; + QString _assetsDirectory; + + // Internal storage for backups on disk + bool _allBackupsLoadedSuccessfully { false }; + std::vector _backups; + std::set _assetsInBackups; + std::set _assetsOnDisk; + + // Internal storage for backup in progress + bool _backupInProgress { false }; + std::vector _assetsLeftToRequest; + + // Internal storage for restor in progress + bool _restoreInProgress { false }; + std::vector _assetsLeftToUpload; + std::vector> _mappingsLeftToSet; + AssetUtils::AssetPathList _mappingsLeftToDelete; + int _mappingRequestsInFlight { 0 }; +}; + +#endif /* hifi_BackupSupervisor_h */ diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 22273ae85f..65053b7366 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -26,6 +26,7 @@ #include #include +#include #include #include #include @@ -343,6 +344,12 @@ void DomainServer::parseCommandLine() { DomainServer::~DomainServer() { qInfo() << "Domain Server is shutting down."; + + // cleanup the AssetClient thread + DependencyManager::destroy(); + _assetClientThread.quit(); + _assetClientThread.wait(); + // destroy the LimitedNodeList before the DomainServer QCoreApplication is down DependencyManager::destroy(); } @@ -684,11 +691,17 @@ void DomainServer::setupNodeListAndAssignments() { packetReceiver.registerListener(PacketType::ICEServerHeartbeatDenied, this, "processICEServerHeartbeatDenialPacket"); packetReceiver.registerListener(PacketType::ICEServerHeartbeatACK, this, "processICEServerHeartbeatACK"); - // add whatever static assignments that have been parsed to the queue - addStaticAssignmentsToQueue(); - // set a custom packetVersionMatch as the verify packet operator for the udt::Socket nodeList->setPacketFilterOperator(&DomainServer::isPacketVerified); + + _assetClientThread.setObjectName("AssetClient Thread"); + auto assetClient = DependencyManager::set(); + assetClient->moveToThread(&_assetClientThread); + QObject::connect(&_assetClientThread, &QThread::started, assetClient.data(), &AssetClient::init); + _assetClientThread.start(); + + // add whatever static assignments that have been parsed to the queue + addStaticAssignmentsToQueue(); } bool DomainServer::resetAccountManagerAccessToken() { diff --git a/domain-server/src/DomainServer.h b/domain-server/src/DomainServer.h index b45b8a4816..f84a47d457 100644 --- a/domain-server/src/DomainServer.h +++ b/domain-server/src/DomainServer.h @@ -18,6 +18,7 @@ #include #include #include +#include #include #include @@ -25,6 +26,7 @@ #include #include +#include "BackupSupervisor.h" #include "DomainGatekeeper.h" #include "DomainMetadata.h" #include "DomainServerSettingsManager.h" @@ -251,6 +253,9 @@ private: bool _sendICEServerAddressToMetaverseAPIRedo { false }; QHash> _pendingOAuthConnections; + BackupSupervisor _backupSupervisor; + + QThread _assetClientThread; }; diff --git a/libraries/networking/src/AssetClient.cpp b/libraries/networking/src/AssetClient.cpp index 1db93e8cb9..41d988eca4 100644 --- a/libraries/networking/src/AssetClient.cpp +++ b/libraries/networking/src/AssetClient.cpp @@ -40,7 +40,7 @@ AssetClient::AssetClient() { static_cast(dependency)->deleteLater(); }); - auto nodeList = DependencyManager::get(); + auto nodeList = DependencyManager::get(); auto& packetReceiver = nodeList->getPacketReceiver(); packetReceiver.registerListener(PacketType::AssetMappingOperationReply, this, "handleAssetMappingOperationReply"); @@ -308,7 +308,7 @@ void AssetClient::handleAssetMappingOperationReply(QSharedPointer(); + auto nodeList = DependencyManager::get(); SharedNodePointer assetServer = nodeList->soloNodeOfType(NodeType::AssetServer); if (!assetServer) { @@ -402,7 +402,7 @@ MessageID AssetClient::getAsset(const QString& hash, AssetUtils::DataOffset star return false; } - auto nodeList = DependencyManager::get(); + auto nodeList = DependencyManager::get(); SharedNodePointer assetServer = nodeList->soloNodeOfType(NodeType::AssetServer); if (assetServer) { @@ -435,7 +435,7 @@ MessageID AssetClient::getAsset(const QString& hash, AssetUtils::DataOffset star MessageID AssetClient::getAssetInfo(const QString& hash, GetInfoCallback callback) { Q_ASSERT(QThread::currentThread() == thread()); - auto nodeList = DependencyManager::get(); + auto nodeList = DependencyManager::get(); SharedNodePointer assetServer = nodeList->soloNodeOfType(NodeType::AssetServer); if (assetServer) { @@ -635,7 +635,7 @@ void AssetClient::handleCompleteCallback(const QWeakPointer& node, Message MessageID AssetClient::getAssetMapping(const AssetUtils::AssetPath& path, MappingOperationCallback callback) { Q_ASSERT(QThread::currentThread() == thread()); - auto nodeList = DependencyManager::get(); + auto nodeList = DependencyManager::get(); SharedNodePointer assetServer = nodeList->soloNodeOfType(NodeType::AssetServer); if (assetServer) { @@ -662,7 +662,7 @@ MessageID AssetClient::getAssetMapping(const AssetUtils::AssetPath& path, Mappin MessageID AssetClient::getAllAssetMappings(MappingOperationCallback callback) { Q_ASSERT(QThread::currentThread() == thread()); - auto nodeList = DependencyManager::get(); + auto nodeList = DependencyManager::get(); SharedNodePointer assetServer = nodeList->soloNodeOfType(NodeType::AssetServer); if (assetServer) { @@ -685,7 +685,7 @@ MessageID AssetClient::getAllAssetMappings(MappingOperationCallback callback) { } MessageID AssetClient::deleteAssetMappings(const AssetUtils::AssetPathList& paths, MappingOperationCallback callback) { - auto nodeList = DependencyManager::get(); + auto nodeList = DependencyManager::get(); SharedNodePointer assetServer = nodeList->soloNodeOfType(NodeType::AssetServer); if (assetServer) { @@ -716,7 +716,7 @@ MessageID AssetClient::deleteAssetMappings(const AssetUtils::AssetPathList& path MessageID AssetClient::setAssetMapping(const QString& path, const AssetUtils::AssetHash& hash, MappingOperationCallback callback) { Q_ASSERT(QThread::currentThread() == thread()); - auto nodeList = DependencyManager::get(); + auto nodeList = DependencyManager::get(); SharedNodePointer assetServer = nodeList->soloNodeOfType(NodeType::AssetServer); if (assetServer) { @@ -742,7 +742,7 @@ MessageID AssetClient::setAssetMapping(const QString& path, const AssetUtils::As } MessageID AssetClient::renameAssetMapping(const AssetUtils::AssetPath& oldPath, const AssetUtils::AssetPath& newPath, MappingOperationCallback callback) { - auto nodeList = DependencyManager::get(); + auto nodeList = DependencyManager::get(); SharedNodePointer assetServer = nodeList->soloNodeOfType(NodeType::AssetServer); if (assetServer) { @@ -769,7 +769,7 @@ MessageID AssetClient::renameAssetMapping(const AssetUtils::AssetPath& oldPath, } MessageID AssetClient::setBakingEnabled(const AssetUtils::AssetPathList& paths, bool enabled, MappingOperationCallback callback) { - auto nodeList = DependencyManager::get(); + auto nodeList = DependencyManager::get(); SharedNodePointer assetServer = nodeList->soloNodeOfType(NodeType::AssetServer); if (assetServer) { @@ -859,7 +859,7 @@ bool AssetClient::cancelUploadAssetRequest(MessageID id) { MessageID AssetClient::uploadAsset(const QByteArray& data, UploadResultCallback callback) { Q_ASSERT(QThread::currentThread() == thread()); - auto nodeList = DependencyManager::get(); + auto nodeList = DependencyManager::get(); SharedNodePointer assetServer = nodeList->soloNodeOfType(NodeType::AssetServer); if (assetServer) { diff --git a/libraries/networking/src/AssetUtils.h b/libraries/networking/src/AssetUtils.h index 42e8dfad62..6c84417aae 100644 --- a/libraries/networking/src/AssetUtils.h +++ b/libraries/networking/src/AssetUtils.h @@ -44,7 +44,9 @@ enum AssetServerError : uint8_t { AssetTooLarge, PermissionDenied, MappingOperationFailed, - FileOperationFailed + FileOperationFailed, + NoAssetServer, + LostConnection }; enum AssetMappingOperationType : uint8_t { @@ -71,7 +73,8 @@ struct MappingInfo { QString bakingErrors; }; -using AssetMapping = std::map; +using AssetMappings = std::map; +using Mappings = std::map; QUrl getATPUrl(const QString& input); AssetHash extractAssetHash(const QString& input); diff --git a/libraries/networking/src/DomainHandler.h b/libraries/networking/src/DomainHandler.h index b72c172c3e..78f9798089 100644 --- a/libraries/networking/src/DomainHandler.h +++ b/libraries/networking/src/DomainHandler.h @@ -49,7 +49,7 @@ public: const QHostAddress& getIP() const { return _sockAddr.getAddress(); } void setIPToLocalhost() { _sockAddr.setAddress(QHostAddress(QHostAddress::LocalHost)); } - const HifiSockAddr& getSockAddr() { return _sockAddr; } + const HifiSockAddr& getSockAddr() const { return _sockAddr; } void setSockAddr(const HifiSockAddr& sockAddr, const QString& hostname); unsigned short getPort() const { return _sockAddr.getPort(); } diff --git a/libraries/networking/src/LimitedNodeList.cpp b/libraries/networking/src/LimitedNodeList.cpp index 0950cb5556..2343695914 100644 --- a/libraries/networking/src/LimitedNodeList.cpp +++ b/libraries/networking/src/LimitedNodeList.cpp @@ -90,21 +90,16 @@ LimitedNodeList::LimitedNodeList(int socketListenPort, int dtlsListenPort) : updateLocalSocket(); // set &PacketReceiver::handleVerifiedPacket as the verified packet callback for the udt::Socket - _nodeSocket.setPacketHandler( - [this](std::unique_ptr packet) { + _nodeSocket.setPacketHandler([this](std::unique_ptr packet) { _packetReceiver->handleVerifiedPacket(std::move(packet)); - } - ); - _nodeSocket.setMessageHandler( - [this](std::unique_ptr packet) { + }); + _nodeSocket.setMessageHandler([this](std::unique_ptr packet) { _packetReceiver->handleVerifiedMessagePacket(std::move(packet)); - } - ); - _nodeSocket.setMessageFailureHandler( - [this](HifiSockAddr from, udt::Packet::MessageNumber messageNumber) { + }); + _nodeSocket.setMessageFailureHandler([this](HifiSockAddr from, + udt::Packet::MessageNumber messageNumber) { _packetReceiver->handleMessageFailure(from, messageNumber); - } - ); + }); // set our isPacketVerified method as the verify operator for the udt::Socket using std::placeholders::_1; @@ -309,8 +304,19 @@ bool LimitedNodeList::packetSourceAndHashMatchAndTrackBandwidth(const udt::Packe sourceNode = matchingNode.data(); } + if (!sourceNode && + sourceID == getDomainUUID() && + packet.getSenderSockAddr() == getDomainSockAddr() && + PacketTypeEnum::getDomainSourcedPackets().contains(headerType)) { + // This is a packet sourced by the domain server + + emit dataReceived(NodeType::Unassigned, packet.getPayloadSize()); + return true; + } + if (sourceNode) { - if (!PacketTypeEnum::getNonVerifiedPackets().contains(headerType)) { + if (!PacketTypeEnum::getNonVerifiedPackets().contains(headerType) && + !isDomainServer()) { QByteArray packetHeaderHash = NLPacket::verificationHashInHeader(packet); QByteArray expectedHash = NLPacket::hashForPacketAndSecret(packet, sourceNode->getConnectionSecret()); diff --git a/libraries/networking/src/LimitedNodeList.h b/libraries/networking/src/LimitedNodeList.h index e21f01a470..1b1c809279 100644 --- a/libraries/networking/src/LimitedNodeList.h +++ b/libraries/networking/src/LimitedNodeList.h @@ -124,6 +124,10 @@ public: PacketReceiver& getPacketReceiver() { return *_packetReceiver; } + virtual bool isDomainServer() const { return true; } + virtual QUuid getDomainUUID() const { assert(false); return QUuid(); } + virtual HifiSockAddr getDomainSockAddr() const { assert(false); return HifiSockAddr(); } + // use sendUnreliablePacket to send an unrelaible packet (that you do not need to move) // either to a node (via its active socket) or to a manual sockaddr qint64 sendUnreliablePacket(const NLPacket& packet, const Node& destinationNode); diff --git a/libraries/networking/src/MappingRequest.cpp b/libraries/networking/src/MappingRequest.cpp index 07639d4994..96f4b63c59 100644 --- a/libraries/networking/src/MappingRequest.cpp +++ b/libraries/networking/src/MappingRequest.cpp @@ -106,9 +106,6 @@ void GetMappingRequest::doStart() { }); }; -GetAllMappingsRequest::GetAllMappingsRequest() { -}; - void GetAllMappingsRequest::doStart() { auto assetClient = DependencyManager::get(); _mappingRequestID = assetClient->getAllAssetMappings( diff --git a/libraries/networking/src/MappingRequest.h b/libraries/networking/src/MappingRequest.h index 5286849dd1..6af8552c1e 100644 --- a/libraries/networking/src/MappingRequest.h +++ b/libraries/networking/src/MappingRequest.h @@ -120,17 +120,15 @@ private: class GetAllMappingsRequest : public MappingRequest { Q_OBJECT public: - GetAllMappingsRequest(); - - AssetUtils::AssetMapping getMappings() const { return _mappings; } + AssetUtils::AssetMappings getMappings() const { return _mappings; } signals: void finished(GetAllMappingsRequest* thisRequest); private: virtual void doStart() override; - - AssetUtils::AssetMapping _mappings; + + AssetUtils::AssetMappings _mappings; }; class SetBakingEnabledRequest : public MappingRequest { diff --git a/libraries/networking/src/NodeList.h b/libraries/networking/src/NodeList.h index 2dd9d3c869..a49cb7c3f5 100644 --- a/libraries/networking/src/NodeList.h +++ b/libraries/networking/src/NodeList.h @@ -92,6 +92,10 @@ public: void removeFromIgnoreMuteSets(const QUuid& nodeID); + virtual bool isDomainServer() const override { return false; } + virtual QUuid getDomainUUID() const override { return _domainHandler.getUUID(); } + virtual HifiSockAddr getDomainSockAddr() const override { return _domainHandler.getSockAddr(); } + public slots: void reset(bool skipDomainHandlerReset = false); void resetFromDomainHandler() { reset(true); } diff --git a/libraries/networking/src/PacketReceiver.cpp b/libraries/networking/src/PacketReceiver.cpp index 556e55beb2..0834a55148 100644 --- a/libraries/networking/src/PacketReceiver.cpp +++ b/libraries/networking/src/PacketReceiver.cpp @@ -267,10 +267,7 @@ void PacketReceiver::handleVerifiedMessage(QSharedPointer recei QMutexLocker packetListenerLocker(&_packetListenerLock); - bool listenerIsDead = false; - auto it = _messageListenerMap.find(receivedMessage->getType()); - if (it != _messageListenerMap.end() && it->method.isValid()) { auto listener = it.value(); @@ -278,82 +275,61 @@ void PacketReceiver::handleVerifiedMessage(QSharedPointer recei if ((listener.deliverPending && !justReceived) || (!listener.deliverPending && !receivedMessage->isComplete())) { return; } - - if (listener.object) { - bool success = false; + bool success = false; - Qt::ConnectionType connectionType; - // check if this is a directly connected listener - { - QMutexLocker directConnectLocker(&_directConnectSetMutex); - - connectionType = _directlyConnectedObjects.contains(listener.object) ? Qt::DirectConnection : Qt::AutoConnection; - } - - PacketType packetType = receivedMessage->getType(); - - if (matchingNode) { - matchingNode->recordBytesReceived(receivedMessage->getSize()); - - QMetaMethod metaMethod = listener.method; - - static const QByteArray QSHAREDPOINTER_NODE_NORMALIZED = QMetaObject::normalizedType("QSharedPointer"); - static const QByteArray SHARED_NODE_NORMALIZED = QMetaObject::normalizedType("SharedNodePointer"); - - // one final check on the QPointer before we go to invoke - if (listener.object) { - if (metaMethod.parameterTypes().contains(SHARED_NODE_NORMALIZED)) { - success = metaMethod.invoke(listener.object, - connectionType, - Q_ARG(QSharedPointer, receivedMessage), - Q_ARG(SharedNodePointer, matchingNode)); - - } else if (metaMethod.parameterTypes().contains(QSHAREDPOINTER_NODE_NORMALIZED)) { - success = metaMethod.invoke(listener.object, - connectionType, - Q_ARG(QSharedPointer, receivedMessage), - Q_ARG(QSharedPointer, matchingNode)); - - } else { - success = metaMethod.invoke(listener.object, - connectionType, - Q_ARG(QSharedPointer, receivedMessage)); - } - } else { - listenerIsDead = true; - } - } else { - // one final check on the QPointer before we invoke - if (listener.object) { - success = listener.method.invoke(listener.object, - Q_ARG(QSharedPointer, receivedMessage)); - } else { - listenerIsDead = true; - } - - } - - if (!success) { - qCDebug(networking).nospace() << "Error delivering packet " << packetType << " to listener " - << listener.object << "::" << qPrintable(listener.method.methodSignature()); - } - - } else { - listenerIsDead = true; + Qt::ConnectionType connectionType; + // check if this is a directly connected listener + { + QMutexLocker directConnectLocker(&_directConnectSetMutex); + connectionType = _directlyConnectedObjects.contains(listener.object) ? Qt::DirectConnection : Qt::AutoConnection; } - - if (listenerIsDead) { + + if (matchingNode) { + matchingNode->recordBytesReceived(receivedMessage->getSize()); + } + + QMetaMethod metaMethod = listener.method; + + static const QByteArray QSHAREDPOINTER_NODE_NORMALIZED = QMetaObject::normalizedType("QSharedPointer"); + static const QByteArray SHARED_NODE_NORMALIZED = QMetaObject::normalizedType("SharedNodePointer"); + + // one final check on the QPointer before we go to invoke + if (listener.object) { + if (metaMethod.parameterTypes().contains(SHARED_NODE_NORMALIZED)) { + success = metaMethod.invoke(listener.object, + connectionType, + Q_ARG(QSharedPointer, receivedMessage), + Q_ARG(SharedNodePointer, matchingNode)); + + } else if (metaMethod.parameterTypes().contains(QSHAREDPOINTER_NODE_NORMALIZED)) { + success = metaMethod.invoke(listener.object, + connectionType, + Q_ARG(QSharedPointer, receivedMessage), + Q_ARG(QSharedPointer, matchingNode)); + + } else { + success = metaMethod.invoke(listener.object, + connectionType, + Q_ARG(QSharedPointer, receivedMessage)); + } + } else { qCDebug(networking).nospace() << "Listener for packet " << receivedMessage->getType() - << " has been destroyed. Removing from listener map."; + << " has been destroyed. Removing from listener map."; it = _messageListenerMap.erase(it); - + // if it exists, remove the listener from _directlyConnectedObjects { QMutexLocker directConnectLocker(&_directConnectSetMutex); _directlyConnectedObjects.remove(listener.object); } } + + if (!success) { + qCDebug(networking).nospace() << "Error delivering packet " << receivedMessage->getType() << " to listener " + << listener.object << "::" << qPrintable(listener.method.methodSignature()); + } + } else if (it == _messageListenerMap.end()) { qCWarning(networking) << "No listener found for packet type" << receivedMessage->getType(); diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h index d186ed41c3..5757cea496 100644 --- a/libraries/networking/src/udt/PacketHeaders.h +++ b/libraries/networking/src/udt/PacketHeaders.h @@ -177,6 +177,17 @@ public: << PacketTypeEnum::Value::ReplicatedKillAvatar << PacketTypeEnum::Value::ReplicatedBulkAvatarData; return NON_SOURCED_PACKETS; } + + const static QSet getDomainSourcedPackets() { + const static QSet DOMAIN_SOURCED_PACKETS = QSet() + << PacketTypeEnum::Value::AssetMappingOperation + << PacketTypeEnum::Value::AssetMappingOperationReply + << PacketTypeEnum::Value::AssetGet + << PacketTypeEnum::Value::AssetGetReply + << PacketTypeEnum::Value::AssetUpload + << PacketTypeEnum::Value::AssetUploadReply; + return DOMAIN_SOURCED_PACKETS; + } }; using PacketType = PacketTypeEnum::Value; From 46449256ce26b905f145e214f23e16d0d580cdff Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Fri, 9 Feb 2018 11:32:54 -0800 Subject: [PATCH 12/45] CR --- .../src/assets/UploadAssetTask.cpp | 16 +++++++++++---- domain-server/src/BackupSupervisor.cpp | 20 +++++++------------ domain-server/src/BackupSupervisor.h | 2 +- 3 files changed, 20 insertions(+), 18 deletions(-) diff --git a/assignment-client/src/assets/UploadAssetTask.cpp b/assignment-client/src/assets/UploadAssetTask.cpp index f2ba431875..d3e755212a 100644 --- a/assignment-client/src/assets/UploadAssetTask.cpp +++ b/assignment-client/src/assets/UploadAssetTask.cpp @@ -41,8 +41,12 @@ void UploadAssetTask::run() { uint64_t fileSize; buffer.read(reinterpret_cast(&fileSize), sizeof(fileSize)); - - qDebug() << "UploadAssetTask reading a file of " << fileSize << "bytes."; + + if (_senderNode) { + qDebug() << "UploadAssetTask reading a file of " << fileSize << "bytes from" << uuidStringWithoutCurlyBraces(_senderNode->getUUID()); + } else { + qDebug() << "UploadAssetTask reading a file of " << fileSize << "bytes from" << _receivedMessage->getSenderSockAddr(); + } auto replyPacket = NLPacket::create(PacketType::AssetUploadReply, -1, true); replyPacket->writePrimitive(messageID); @@ -54,8 +58,12 @@ void UploadAssetTask::run() { auto hash = AssetUtils::hashData(fileData); auto hexHash = hash.toHex(); - - qDebug() << "Hash for uploaded file is: (" << hexHash << ") "; + + if (_senderNode) { + qDebug() << "Hash for uploaded file from" << uuidStringWithoutCurlyBraces(_senderNode->getUUID()) << "is: (" << hexHash << ")"; + } else { + qDebug() << "Hash for uploaded file from" << _receivedMessage->getSenderSockAddr() << "is: (" << hexHash << ")"; + } QFile file { _resourcesDir.filePath(QString(hexHash)) }; diff --git a/domain-server/src/BackupSupervisor.cpp b/domain-server/src/BackupSupervisor.cpp index 829208913f..1bf1e9e199 100644 --- a/domain-server/src/BackupSupervisor.cpp +++ b/domain-server/src/BackupSupervisor.cpp @@ -1,6 +1,6 @@ // // BackupSupervisor.cpp -// assignment-client/src +// domain-server/src // // Created by Clement Brisset on 1/12/18. // Copyright 2018 High Fidelity, Inc. @@ -106,28 +106,22 @@ bool BackupSupervisor::loadBackup(const QString& backupFile) { } QJsonParseError error; auto document = QJsonDocument::fromJson(file.readAll(), &error); - if (document.isNull()) { - qCritical() << "Could not parse backup file:" << backupFile; - qCritical() << " Error:" << error.errorString(); - backup.corruptedBackup = true; - return false; - } - - if (!document.isObject()) { - qCritical() << "Backup file corrupted" << backupFile; + if (document.isNull() || !document.isObject()) { + qCritical() << "Could not parse backup file to JSON object:" << backupFile; backup.corruptedBackup = true; return false; } auto jsonObject = document.object(); for (auto it = begin(jsonObject); it != end(jsonObject); ++it) { - if (!it.value().isString()) { + const auto& assetPath = it.key(); + const auto& assetHash = it.value().toString(); + + if (!AssetUtils::isValidHash(assetHash)) { qCritical() << "Corrupted mapping in backup file" << backupFile << ":" << it.key(); backup.corruptedBackup = true; return false; } - const auto& assetPath = it.key(); - const auto& assetHash = it.value().toString(); backup.mappings[assetPath] = assetHash; _assetsInBackups.insert(assetHash); diff --git a/domain-server/src/BackupSupervisor.h b/domain-server/src/BackupSupervisor.h index a4129fca12..f01e389c39 100644 --- a/domain-server/src/BackupSupervisor.h +++ b/domain-server/src/BackupSupervisor.h @@ -1,6 +1,6 @@ // // BackupSupervisor.h -// assignment-client/src +// domain-server/src // // Created by Clement Brisset on 1/12/18. // Copyright 2018 High Fidelity, Inc. From 5325b22b011cf8ad6b2b23dd350c1312d23fd807 Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Fri, 9 Feb 2018 16:46:11 -0800 Subject: [PATCH 13/45] working on lambdas --- interface/src/avatar/MyAvatar.cpp | 28 +++++++++++++++------------- interface/src/avatar/MyAvatar.h | 1 + 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 0ee409745f..5710dcc354 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -1448,10 +1448,20 @@ void MyAvatar::clearJointsData() { } void MyAvatar::setSkeletonModelURL(const QUrl& skeletonModelURL) { - if (skeletonModelURL != _skeletonModelURL) { - _shouldInitHeadBones = true; - } - + _skeletonModelChangeCount++; + int skeletonModelChangeCount = _skeletonModelChangeCount; + std::shared_ptr skeletonConnection = std::make_shared(); + *skeletonConnection = QObject::connect(_skeletonModel.get(), &SkeletonModel::skeletonLoaded, [this, skeletonModelChangeCount, skeletonConnection]() { + qDebug() << "checkingCount " << skeletonModelChangeCount << " -- " << _skeletonModelChangeCount; + if (skeletonModelChangeCount == _skeletonModelChangeCount && _skeletonModel->isLoaded()) { + qDebug() << "count is the same"; + initHeadBones(); + _skeletonModel->setCauterizeBoneSet(_headBoneSet); + _fstAnimGraphOverrideUrl = _skeletonModel->getGeometry()->getAnimGraphOverrideUrl(); + initAnimGraph(); + } + QObject::disconnect(*skeletonConnection); + }); Avatar::setSkeletonModelURL(skeletonModelURL); _skeletonModel->setVisibleInScene(true, qApp->getMain3DScene(), render::ItemKey::TAG_BITS_NONE); _headBoneSet.clear(); @@ -1878,6 +1888,7 @@ void MyAvatar::initAnimGraph() { _skeletonModel->getRig().initAnimGraph(graphUrl); _currentAnimGraphUrl.set(graphUrl); + qDebug() << "init anim graph"; connect(&(_skeletonModel->getRig()), SIGNAL(onLoadComplete()), this, SLOT(animGraphLoaded())); } @@ -1896,15 +1907,6 @@ void MyAvatar::animGraphLoaded() { void MyAvatar::postUpdate(float deltaTime, const render::ScenePointer& scene) { Avatar::postUpdate(deltaTime, scene); - - if (_skeletonModel->isLoaded() && !_skeletonModel->getRig().getAnimNode() && _shouldInitHeadBones) { - initHeadBones(); - _skeletonModel->setCauterizeBoneSet(_headBoneSet); - _fstAnimGraphOverrideUrl = _skeletonModel->getGeometry()->getAnimGraphOverrideUrl(); - initAnimGraph(); - _shouldInitHeadBones = false; - } - if (_enableDebugDrawDefaultPose || _enableDebugDrawAnimPose) { auto animSkeleton = _skeletonModel->getRig().getAnimSkeleton(); diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 45dddb45a4..180cf04a18 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -654,6 +654,7 @@ private: bool isMyAvatar() const override { return true; } virtual int parseDataFromBuffer(const QByteArray& buffer) override; virtual glm::vec3 getSkeletonPosition() const override; + int _skeletonModelChangeCount { 0 }; void saveAvatarScale(); From ff0b63297883a34624adaea0953078a13b3064b9 Mon Sep 17 00:00:00 2001 From: David Back Date: Thu, 8 Feb 2018 19:31:24 -0800 Subject: [PATCH 14/45] get avatars in range --- libraries/avatars/src/AvatarHashMap.cpp | 14 ++++++++++++++ libraries/avatars/src/AvatarHashMap.h | 2 ++ 2 files changed, 16 insertions(+) diff --git a/libraries/avatars/src/AvatarHashMap.cpp b/libraries/avatars/src/AvatarHashMap.cpp index 61b13d7749..c2e84f6e16 100644 --- a/libraries/avatars/src/AvatarHashMap.cpp +++ b/libraries/avatars/src/AvatarHashMap.cpp @@ -30,6 +30,20 @@ QVector AvatarHashMap::getAvatarIdentifiers() { return _avatarHash.keys().toVector(); } +QVector AvatarHashMap::getAvatarsInRange(const glm::vec3& position, float rangeMeters) const { + auto hashCopy = getHashCopy(); + QVector avatarsInRange; + auto rangeMeters2 = rangeMeters * rangeMeters; + for (const AvatarSharedPointer& sharedAvatar : hashCopy) { + glm::vec3 avatarPosition = sharedAvatar->getWorldPosition(); + auto distance2 = glm::distance2(avatarPosition, position); + if (distance2 < rangeMeters2) { + avatarsInRange.push_back(sharedAvatar->getSessionUUID()); + } + } + return avatarsInRange; +} + bool AvatarHashMap::isAvatarInRange(const glm::vec3& position, const float range) { auto hashCopy = getHashCopy(); foreach(const AvatarSharedPointer& sharedAvatar, hashCopy) { diff --git a/libraries/avatars/src/AvatarHashMap.h b/libraries/avatars/src/AvatarHashMap.h index 68fc232685..e4a485028f 100644 --- a/libraries/avatars/src/AvatarHashMap.h +++ b/libraries/avatars/src/AvatarHashMap.h @@ -35,10 +35,12 @@ class AvatarHashMap : public QObject, public Dependency { public: AvatarHash getHashCopy() { QReadLocker lock(&_hashLock); return _avatarHash; } + const AvatarHash getHashCopy() const { QReadLocker lock(&_hashLock); return _avatarHash; } int size() { return _avatarHash.size(); } // Currently, your own avatar will be included as the null avatar id. Q_INVOKABLE QVector getAvatarIdentifiers(); + Q_INVOKABLE QVector getAvatarsInRange(const glm::vec3& position, float rangeMeters) const; // Null/Default-constructed QUuids will return MyAvatar Q_INVOKABLE virtual ScriptAvatarData* getAvatar(QUuid avatarID) { return new ScriptAvatarData(getAvatarBySessionID(avatarID)); } From dc92250b1afc70233a272fdc8e135e8b1eec5673 Mon Sep 17 00:00:00 2001 From: David Back Date: Fri, 9 Feb 2018 14:59:08 -0800 Subject: [PATCH 15/45] change var names --- libraries/avatars/src/AvatarHashMap.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/libraries/avatars/src/AvatarHashMap.cpp b/libraries/avatars/src/AvatarHashMap.cpp index c2e84f6e16..b564ad6a3b 100644 --- a/libraries/avatars/src/AvatarHashMap.cpp +++ b/libraries/avatars/src/AvatarHashMap.cpp @@ -33,11 +33,11 @@ QVector AvatarHashMap::getAvatarIdentifiers() { QVector AvatarHashMap::getAvatarsInRange(const glm::vec3& position, float rangeMeters) const { auto hashCopy = getHashCopy(); QVector avatarsInRange; - auto rangeMeters2 = rangeMeters * rangeMeters; + auto rangeMetersSquared = rangeMeters * rangeMeters; for (const AvatarSharedPointer& sharedAvatar : hashCopy) { glm::vec3 avatarPosition = sharedAvatar->getWorldPosition(); - auto distance2 = glm::distance2(avatarPosition, position); - if (distance2 < rangeMeters2) { + auto distanceSquared = glm::distance2(avatarPosition, position); + if (distanceSquared < rangeMetersSquared) { avatarsInRange.push_back(sharedAvatar->getSessionUUID()); } } From d381beb2132c224c383ff797b5faf7081396dd3e Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Mon, 12 Feb 2018 10:53:42 -0800 Subject: [PATCH 16/45] cleaning up code --- interface/src/avatar/MyAvatar.cpp | 14 ++++++-------- libraries/animation/src/Rig.cpp | 4 +--- libraries/animation/src/Rig.h | 1 - 3 files changed, 7 insertions(+), 12 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 5710dcc354..67796bcc8b 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -1450,11 +1450,14 @@ void MyAvatar::clearJointsData() { void MyAvatar::setSkeletonModelURL(const QUrl& skeletonModelURL) { _skeletonModelChangeCount++; int skeletonModelChangeCount = _skeletonModelChangeCount; + Avatar::setSkeletonModelURL(skeletonModelURL); + _skeletonModel->setVisibleInScene(true, qApp->getMain3DScene(), render::ItemKey::TAG_BITS_NONE); + _headBoneSet.clear(); + _cauterizationNeedsUpdate = true; + std::shared_ptr skeletonConnection = std::make_shared(); *skeletonConnection = QObject::connect(_skeletonModel.get(), &SkeletonModel::skeletonLoaded, [this, skeletonModelChangeCount, skeletonConnection]() { - qDebug() << "checkingCount " << skeletonModelChangeCount << " -- " << _skeletonModelChangeCount; - if (skeletonModelChangeCount == _skeletonModelChangeCount && _skeletonModel->isLoaded()) { - qDebug() << "count is the same"; + if (skeletonModelChangeCount == _skeletonModelChangeCount) { initHeadBones(); _skeletonModel->setCauterizeBoneSet(_headBoneSet); _fstAnimGraphOverrideUrl = _skeletonModel->getGeometry()->getAnimGraphOverrideUrl(); @@ -1462,10 +1465,6 @@ void MyAvatar::setSkeletonModelURL(const QUrl& skeletonModelURL) { } QObject::disconnect(*skeletonConnection); }); - Avatar::setSkeletonModelURL(skeletonModelURL); - _skeletonModel->setVisibleInScene(true, qApp->getMain3DScene(), render::ItemKey::TAG_BITS_NONE); - _headBoneSet.clear(); - _cauterizationNeedsUpdate = true; emit skeletonChanged(); } @@ -1888,7 +1887,6 @@ void MyAvatar::initAnimGraph() { _skeletonModel->getRig().initAnimGraph(graphUrl); _currentAnimGraphUrl.set(graphUrl); - qDebug() << "init anim graph"; connect(&(_skeletonModel->getRig()), SIGNAL(onLoadComplete()), this, SLOT(animGraphLoaded())); } diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index ba496c5cd4..31151669c8 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -1585,14 +1585,13 @@ void Rig::updateFromControllerParameters(const ControllerParameters& params, flo } void Rig::initAnimGraph(const QUrl& url) { - if (_animGraphURL != url || (!_animNode && !_animLoading)) { + if (_animGraphURL != url || !_animNode) { _animGraphURL = url; _animNode.reset(); // load the anim graph _animLoader.reset(new AnimNodeLoader(url)); - _animLoading = true; std::weak_ptr weakSkeletonPtr = _animSkeleton; connect(_animLoader.get(), &AnimNodeLoader::success, [this, weakSkeletonPtr](AnimNode::Pointer nodeIn) { _animNode = nodeIn; @@ -1617,7 +1616,6 @@ void Rig::initAnimGraph(const QUrl& url) { auto roleState = roleAnimState.second; overrideRoleAnimation(roleState.role, roleState.url, roleState.fps, roleState.loop, roleState.firstFrame, roleState.lastFrame); } - _animLoading = false; emit onLoadComplete(); }); diff --git a/libraries/animation/src/Rig.h b/libraries/animation/src/Rig.h index a7db86abf9..7230d05e2a 100644 --- a/libraries/animation/src/Rig.h +++ b/libraries/animation/src/Rig.h @@ -283,7 +283,6 @@ protected: std::shared_ptr _animNode; std::shared_ptr _animSkeleton; std::unique_ptr _animLoader; - bool _animLoading { false }; AnimVariantMap _animVars; enum class RigRole { Idle = 0, From 1a2533257574531b23c1af9994ca159d3ded2ffd Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Mon, 12 Feb 2018 10:59:25 -0800 Subject: [PATCH 17/45] fix indenting --- interface/src/avatar/MyAvatar.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 719a919721..d5cd7bdca1 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -1469,13 +1469,13 @@ void MyAvatar::setSkeletonModelURL(const QUrl& skeletonModelURL) { std::shared_ptr skeletonConnection = std::make_shared(); *skeletonConnection = QObject::connect(_skeletonModel.get(), &SkeletonModel::skeletonLoaded, [this, skeletonModelChangeCount, skeletonConnection]() { - if (skeletonModelChangeCount == _skeletonModelChangeCount) { - initHeadBones(); - _skeletonModel->setCauterizeBoneSet(_headBoneSet); - _fstAnimGraphOverrideUrl = _skeletonModel->getGeometry()->getAnimGraphOverrideUrl(); - initAnimGraph(); - } - QObject::disconnect(*skeletonConnection); + if (skeletonModelChangeCount == _skeletonModelChangeCount) { + initHeadBones(); + _skeletonModel->setCauterizeBoneSet(_headBoneSet); + _fstAnimGraphOverrideUrl = _skeletonModel->getGeometry()->getAnimGraphOverrideUrl(); + initAnimGraph(); + } + QObject::disconnect(*skeletonConnection); }); saveAvatarUrl(); emit skeletonChanged(); From f95893f070a0552b1fd30ec7b5de1fea3018d8dc Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Mon, 12 Feb 2018 11:09:10 -0800 Subject: [PATCH 18/45] updates to electron-packager and node-notifier for CVEs --- server-console/package-lock.json | 1288 ++++++++++++++++++++---------- server-console/package.json | 4 +- 2 files changed, 851 insertions(+), 441 deletions(-) diff --git a/server-console/package-lock.json b/server-console/package-lock.json index 51b7f2c268..ba8ef3c720 100644 --- a/server-console/package-lock.json +++ b/server-console/package-lock.json @@ -5,11 +5,23 @@ "requires": true, "dependencies": { "abbrev": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.0.7.tgz", - "integrity": "sha1-W2A1su6dT7XPhZ8Iqb6BsghJGEM=", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", "dev": true }, + "ajv": { + "version": "5.5.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", + "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", + "dev": true, + "requires": { + "co": "4.6.0", + "fast-deep-equal": "1.0.0", + "fast-json-stable-stringify": "2.0.0", + "json-schema-traverse": "0.3.1" + } + }, "always-tail": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/always-tail/-/always-tail-0.2.0.tgz", @@ -28,10 +40,11 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=" }, - "ansicolors": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/ansicolors/-/ansicolors-0.2.1.tgz", - "integrity": "sha1-vgiVmQl7dKXJxKhKDNvNtivYeu8=" + "any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha1-q8av7tzqUugJzcA3au0845Y10X8=", + "dev": true }, "array-find-index": { "version": "1.0.1", @@ -40,20 +53,37 @@ "dev": true }, "asar": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/asar/-/asar-0.11.0.tgz", - "integrity": "sha1-uSbnksMV+MBIxDNx4yWwnJenZGQ=", + "version": "0.14.2", + "resolved": "https://registry.npmjs.org/asar/-/asar-0.14.2.tgz", + "integrity": "sha512-eKo4ywQDq9dC/0Pu6UJsX4PxNi5ZlC4/NQ1JORUW4xkMRrEWpoLPpkngmQ6K7ZkioVjE2ZafLMmHPAQKMO0BdA==", "dev": true, "requires": { - "chromium-pickle-js": "0.1.0", + "chromium-pickle-js": "0.2.0", "commander": "2.9.0", - "cuint": "0.2.1", + "cuint": "0.2.2", "glob": "6.0.4", - "minimatch": "3.0.0", + "minimatch": "3.0.4", "mkdirp": "0.5.1", - "mksnapshot": "0.3.0" + "mksnapshot": "0.3.1", + "tmp": "0.0.28" }, "dependencies": { + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "1.0.0", + "concat-map": "0.0.1" + } + }, "glob": { "version": "6.0.4", "resolved": "https://registry.npmjs.org/glob/-/glob-6.0.4.tgz", @@ -62,10 +92,19 @@ "requires": { "inflight": "1.0.4", "inherits": "2.0.1", - "minimatch": "3.0.0", + "minimatch": "3.0.4", "once": "1.3.3", "path-is-absolute": "1.0.0" } + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "1.1.11" + } } } }, @@ -84,6 +123,18 @@ "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=" }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", + "dev": true + }, + "author-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/author-regex/-/author-regex-1.0.0.tgz", + "integrity": "sha1-0IiFvmubv5Q5/gh8dihyRfCoFFA=", + "dev": true + }, "aws-sign2": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.6.0.tgz", @@ -103,9 +154,9 @@ "integrity": "sha1-qRzdHr7xqGZZ5w/03vAWJfwtZ1Y=" }, "base64-js": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-0.0.8.tgz", - "integrity": "sha1-EQHpVE9KdrG8OybUUsqW16NeeXg=", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.2.0.tgz", + "integrity": "sha1-o5mS1yNYSBGYK+XikLtqU9hnAPE=", "dev": true }, "binary": { @@ -147,9 +198,9 @@ } }, "bluebird": { - "version": "2.10.2", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-2.10.2.tgz", - "integrity": "sha1-AkpVFylTCIV/FPkfEQb8O1VfRGs=", + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.1.tgz", + "integrity": "sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA==", "dev": true }, "boolbase": { @@ -201,15 +252,6 @@ "map-obj": "1.0.1" } }, - "cardinal": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/cardinal/-/cardinal-0.5.0.tgz", - "integrity": "sha1-ANX2YdvUqr/ffUHOSKWlm8o1opE=", - "requires": { - "ansicolors": "0.2.1", - "redeyed": "0.5.0" - } - }, "caseless": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.11.0.tgz", @@ -249,36 +291,11 @@ } }, "chromium-pickle-js": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/chromium-pickle-js/-/chromium-pickle-js-0.1.0.tgz", - "integrity": "sha1-HUixB9ghJqLz4hHC6iX4A7pVGyE=", + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/chromium-pickle-js/-/chromium-pickle-js-0.2.0.tgz", + "integrity": "sha1-BKEGZywYsIWrd02YPfo+oTjyIgU=", "dev": true }, - "cli-table": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/cli-table/-/cli-table-0.3.1.tgz", - "integrity": "sha1-9TsFJmqLGguTSz0IIebi3FkUriM=", - "requires": { - "colors": "1.0.3" - } - }, - "cli-usage": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/cli-usage/-/cli-usage-0.1.2.tgz", - "integrity": "sha1-SXwg6vEuwneTk6m/rCJcX2y5FS0=", - "requires": { - "marked": "0.3.5", - "marked-terminal": "1.6.1", - "minimist": "0.2.0" - }, - "dependencies": { - "minimist": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.2.0.tgz", - "integrity": "sha1-Tf/lJdriuGTGbC4jxicdev3s784=" - } - } - }, "cliui": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", @@ -289,6 +306,12 @@ "wrap-ansi": "2.0.0" } }, + "co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", + "dev": true + }, "code-point-at": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.0.0.tgz", @@ -297,11 +320,6 @@ "number-is-nan": "1.0.0" } }, - "colors": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz", - "integrity": "sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs=" - }, "combined-stream": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz", @@ -318,6 +336,12 @@ "graceful-readlink": "1.0.1" } }, + "compare-version": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/compare-version/-/compare-version-0.1.2.tgz", + "integrity": "sha1-AWLsLZNR9d3VmpICy6k1NmpyUIA=", + "dev": true + }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -385,16 +409,10 @@ "resolved": "https://registry.npmjs.org/css-what/-/css-what-1.0.0.tgz", "integrity": "sha1-18wt9FGAZm+Z0rFEYmOUaeAPc2w=" }, - "ctype": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/ctype/-/ctype-0.5.3.tgz", - "integrity": "sha1-gsGMJGH3QRTvFsE1IkrQuRRMoS8=", - "dev": true - }, "cuint": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/cuint/-/cuint-0.2.1.tgz", - "integrity": "sha1-VlBFzoEnxwxr80D1kcAEin1M/rw=", + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/cuint/-/cuint-0.2.2.tgz", + "integrity": "sha1-QICG1AlVDCYxFVYZ6fp7ytw7mRs=", "dev": true }, "dashdash": { @@ -432,7 +450,7 @@ "graceful-fs": "4.1.3", "mkpath": "0.1.0", "nopt": "3.0.6", - "q": "1.4.1", + "q": "1.5.1", "readable-stream": "1.1.14", "touch": "0.0.3" } @@ -527,37 +545,205 @@ "integrity": "sha1-DboCXtM9DkW/j0DG6b487i+YbCg=" }, "electron-osx-sign": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/electron-osx-sign/-/electron-osx-sign-0.3.0.tgz", - "integrity": "sha1-SXIB38g1OMVLNPGkBexuIaAf904=", + "version": "0.4.8", + "resolved": "https://registry.npmjs.org/electron-osx-sign/-/electron-osx-sign-0.4.8.tgz", + "integrity": "sha1-8Ln63e2eHlTsNfqJh3tcbDTHvEA=", "dev": true, "requires": { + "bluebird": "3.5.1", + "compare-version": "0.1.2", + "debug": "2.6.9", + "isbinaryfile": "3.0.2", "minimist": "1.2.0", - "run-series": "1.1.4" + "plist": "2.1.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } } }, "electron-packager": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/electron-packager/-/electron-packager-6.0.2.tgz", - "integrity": "sha1-juAGaf6KNjCVAnMvz+0RfX7prCk=", + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/electron-packager/-/electron-packager-11.0.0.tgz", + "integrity": "sha512-ufyYMe3Gt6IEZm9RuG+KK3Nh+V2jZHWg9gihp8wylUNtleQihECIXtQdpPJxH9740XFERVPraNEaa7cZvDzpyw==", "dev": true, "requires": { - "asar": "0.11.0", - "electron-download": "2.1.1", - "electron-osx-sign": "0.3.0", + "asar": "0.14.2", + "debug": "3.1.0", + "electron-download": "4.1.0", + "electron-osx-sign": "0.4.8", "extract-zip": "1.5.0", - "fs-extra": "0.26.7", - "get-package-info": "0.0.2", - "minimist": "1.2.0", - "mkdirp": "0.5.1", - "mv": "2.1.1", - "ncp": "2.0.0", - "object-assign": "4.0.1", - "plist": "1.2.0", - "rcedit": "0.5.0", - "resolve": "1.1.7", - "rimraf": "2.5.2", - "run-series": "1.1.4" + "fs-extra": "5.0.0", + "get-package-info": "1.0.0", + "mz": "2.7.0", + "nodeify": "1.0.1", + "parse-author": "2.0.0", + "pify": "3.0.0", + "plist": "2.1.0", + "pruner": "0.0.7", + "rcedit": "0.9.0", + "resolve": "1.5.0", + "sanitize-filename": "1.6.1", + "semver": "5.5.0", + "yargs-parser": "8.1.0" + }, + "dependencies": { + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "electron-download": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/electron-download/-/electron-download-4.1.0.tgz", + "integrity": "sha1-v5MsdG8vh//MCdHdRy8v9rkYeEU=", + "dev": true, + "requires": { + "debug": "2.6.9", + "env-paths": "1.0.0", + "fs-extra": "2.1.2", + "minimist": "1.2.0", + "nugget": "2.0.1", + "path-exists": "3.0.0", + "rc": "1.1.6", + "semver": "5.5.0", + "sumchecker": "2.0.2" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "fs-extra": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-2.1.2.tgz", + "integrity": "sha1-BGxwFjzvmq1GsOSn+kZ/si1x3jU=", + "dev": true, + "requires": { + "graceful-fs": "4.1.3", + "jsonfile": "2.2.3" + } + } + } + }, + "fs-extra": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-5.0.0.tgz", + "integrity": "sha512-66Pm4RYbjzdyeuqudYqhFiNBbCIuI9kgRqLPSHIlXHidW8NIQtVdkM1yeZ4lXwuhbTETv3EUGMNHAAw6hiundQ==", + "dev": true, + "requires": { + "graceful-fs": "4.1.3", + "jsonfile": "4.0.0", + "universalify": "0.1.1" + }, + "dependencies": { + "jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "dev": true, + "requires": { + "graceful-fs": "4.1.11" + }, + "dependencies": { + "graceful-fs": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", + "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=", + "dev": true, + "optional": true + } + } + } + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "nugget": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/nugget/-/nugget-2.0.1.tgz", + "integrity": "sha1-IBCVpIfhrTYIGzQy+jytpPjQcbA=", + "dev": true, + "requires": { + "debug": "2.6.9", + "minimist": "1.2.0", + "pretty-bytes": "1.0.4", + "progress-stream": "1.2.0", + "request": "2.71.0", + "single-line-log": "1.1.2", + "throttleit": "0.0.2" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + } + } + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true + }, + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "dev": true + }, + "semver": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", + "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==", + "dev": true + }, + "single-line-log": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/single-line-log/-/single-line-log-1.1.2.tgz", + "integrity": "sha1-wvg/Jzo+GhbtsJlWYdoO1e8DM2Q=", + "dev": true, + "requires": { + "string-width": "1.0.1" + } + }, + "throttleit": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-0.0.2.tgz", + "integrity": "sha1-z+34jmDADdlpe2H90qg0OptoDq8=", + "dev": true + } } }, "electron-prebuilt": { @@ -583,6 +769,12 @@ "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.1.tgz", "integrity": "sha1-blwtClYhtdra7O+AuQ7ftc13cvA=" }, + "env-paths": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-1.0.0.tgz", + "integrity": "sha1-QWgTO0K7BcOKNbGuQ5fIKYqzaeA=", + "dev": true + }, "error-ex": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.0.tgz", @@ -597,11 +789,6 @@ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" }, - "esprima-fb": { - "version": "12001.1.0-dev-harmony-fb", - "resolved": "https://registry.npmjs.org/esprima-fb/-/esprima-fb-12001.1.0-dev-harmony-fb.tgz", - "integrity": "sha1-2EQAOEupXOJnjGF60kp/QICNqRU=" - }, "extend": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.0.tgz", @@ -641,6 +828,18 @@ "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.0.2.tgz", "integrity": "sha1-4QgOBljjALBilJkMxw4VAiNf1VA=" }, + "fast-deep-equal": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.0.0.tgz", + "integrity": "sha1-liVqO8l1WV6zbYLpkp0GDYk0Of8=", + "dev": true + }, + "fast-json-stable-stringify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", + "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=", + "dev": true + }, "fd-slicer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.0.1.tgz", @@ -712,20 +911,87 @@ } }, "get-package-info": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/get-package-info/-/get-package-info-0.0.2.tgz", - "integrity": "sha1-csOPvuLnZyhCSgDcFOJN0aKMI5E=", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/get-package-info/-/get-package-info-1.0.0.tgz", + "integrity": "sha1-ZDJ5ZWPigRPNlHTbvQAFKYWkmZw=", "dev": true, "requires": { - "bluebird": "3.3.5", - "lodash.get": "4.2.1", - "resolve": "1.1.7" + "bluebird": "3.5.1", + "debug": "2.6.9", + "lodash.get": "4.4.2", + "read-pkg-up": "2.0.0" }, "dependencies": { - "bluebird": { - "version": "3.3.5", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.3.5.tgz", - "integrity": "sha1-XudH8ce9lnZYtoOTZDCu51OVWjQ=", + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "dev": true, + "requires": { + "locate-path": "2.0.0" + } + }, + "load-json-file": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz", + "integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=", + "dev": true, + "requires": { + "graceful-fs": "4.1.3", + "parse-json": "2.2.0", + "pify": "2.3.0", + "strip-bom": "3.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "path-type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz", + "integrity": "sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=", + "dev": true, + "requires": { + "pify": "2.3.0" + } + }, + "read-pkg": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz", + "integrity": "sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=", + "dev": true, + "requires": { + "load-json-file": "2.0.0", + "normalize-package-data": "2.3.5", + "path-type": "2.0.0" + } + }, + "read-pkg-up": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-2.0.0.tgz", + "integrity": "sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=", + "dev": true, + "requires": { + "find-up": "2.1.0", + "read-pkg": "2.0.0" + } + }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", "dev": true } } @@ -763,6 +1029,12 @@ "resolved": "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz", "integrity": "sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE=" }, + "har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", + "dev": true + }, "har-validator": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-2.0.6.tgz", @@ -893,14 +1165,6 @@ "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=" }, - "is-absolute": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-0.1.7.tgz", - "integrity": "sha1-hHSREZ/MtftDYhfMc39/qtUPYD8=", - "requires": { - "is-relative": "0.1.3" - } - }, "is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", @@ -944,16 +1208,17 @@ "xtend": "4.0.1" } }, + "is-promise": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-1.0.1.tgz", + "integrity": "sha1-MVc3YcBX4zwukaq56W2gjO++duU=", + "dev": true + }, "is-property": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", "integrity": "sha1-V/4cTkhHTt1lsJkR8msc1Ald2oQ=" }, - "is-relative": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-0.1.3.tgz", - "integrity": "sha1-kF/uiuhvRbPsYUvDwVyGnfCHboI=" - }, "is-typedarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", @@ -970,10 +1235,16 @@ "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" }, + "isbinaryfile": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-3.0.2.tgz", + "integrity": "sha1-Sj6XTsDLqQBNP8bN5yCeppNopiE=", + "dev": true + }, "isexe": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-1.1.2.tgz", - "integrity": "sha1-NvPiLmB1CSD15yQaR2qMakInWtA=" + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" }, "isstream": { "version": "0.1.2", @@ -1000,6 +1271,12 @@ "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.2.tgz", "integrity": "sha1-UDVPGfYDkXxpX3C4Wvp3w7DyNQY=" }, + "json-schema-traverse": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", + "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=", + "dev": true + }, "json-stringify-safe": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", @@ -1051,136 +1328,34 @@ "strip-bom": "2.0.0" } }, + "locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "dev": true, + "requires": { + "p-locate": "2.0.0", + "path-exists": "3.0.0" + }, + "dependencies": { + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true + } + } + }, "lodash": { "version": "3.10.1", "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz", "integrity": "sha1-W/Rejkm6QYnhfUgnid/RW9FAt7Y=" }, - "lodash._arraycopy": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lodash._arraycopy/-/lodash._arraycopy-3.0.0.tgz", - "integrity": "sha1-due3wfH7klRzdIeKVi7Qaj5Q9uE=" - }, - "lodash._arrayeach": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lodash._arrayeach/-/lodash._arrayeach-3.0.0.tgz", - "integrity": "sha1-urFWsqkNPxu9XGU0AzSeXlkz754=" - }, - "lodash._baseassign": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/lodash._baseassign/-/lodash._baseassign-3.2.0.tgz", - "integrity": "sha1-jDigmVAPIVrQnlnxci/QxSv+Ck4=", - "requires": { - "lodash._basecopy": "3.0.1", - "lodash.keys": "3.1.2" - } - }, - "lodash._baseclone": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/lodash._baseclone/-/lodash._baseclone-3.3.0.tgz", - "integrity": "sha1-MDUZv2OT/n5C802LYw73eU41Qrc=", - "requires": { - "lodash._arraycopy": "3.0.0", - "lodash._arrayeach": "3.0.0", - "lodash._baseassign": "3.2.0", - "lodash._basefor": "3.0.3", - "lodash.isarray": "3.0.4", - "lodash.keys": "3.1.2" - } - }, - "lodash._basecopy": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz", - "integrity": "sha1-jaDmqHbPNEwK2KVIghEd08XHyjY=" - }, - "lodash._basefor": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/lodash._basefor/-/lodash._basefor-3.0.3.tgz", - "integrity": "sha1-dVC06SGO8J+tJDQ7YSAhx5tMIMI=" - }, - "lodash._bindcallback": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/lodash._bindcallback/-/lodash._bindcallback-3.0.1.tgz", - "integrity": "sha1-5THCdkTPi1epnhftlbNcdIeJOS4=" - }, - "lodash._createassigner": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/lodash._createassigner/-/lodash._createassigner-3.1.1.tgz", - "integrity": "sha1-g4pbri/aymOsIt7o4Z+k5taXCxE=", - "requires": { - "lodash._bindcallback": "3.0.1", - "lodash._isiterateecall": "3.0.9", - "lodash.restparam": "3.6.1" - } - }, - "lodash._getnative": { - "version": "3.9.1", - "resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz", - "integrity": "sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U=" - }, - "lodash._isiterateecall": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz", - "integrity": "sha1-UgOte6Ql+uhCRg5pbbnPPmqsBXw=" - }, - "lodash._stringtopath": { - "version": "4.7.1", - "resolved": "https://registry.npmjs.org/lodash._stringtopath/-/lodash._stringtopath-4.7.1.tgz", - "integrity": "sha1-1GYKFaWZeYj6WTAaO/2gXJpt494=", - "dev": true - }, - "lodash.assign": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/lodash.assign/-/lodash.assign-3.2.0.tgz", - "integrity": "sha1-POnwI0tLIiPilrj6CsH+6OvKZPo=", - "requires": { - "lodash._baseassign": "3.2.0", - "lodash._createassigner": "3.1.1", - "lodash.keys": "3.1.2" - } - }, - "lodash.clonedeep": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-3.0.2.tgz", - "integrity": "sha1-oKHkDYKl6on/WxR7hETtY9koJ9s=", - "requires": { - "lodash._baseclone": "3.3.0", - "lodash._bindcallback": "3.0.1" - } - }, "lodash.get": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.2.1.tgz", - "integrity": "sha1-bpr8h7imwCFgZnpw9sjOEHiusRs=", - "dev": true, - "requires": { - "lodash._stringtopath": "4.7.1" - } - }, - "lodash.isarguments": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.0.8.tgz", - "integrity": "sha1-W/jaiH8B8qnknAoXXNrrMYoOQ9w=" - }, - "lodash.isarray": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-3.0.4.tgz", - "integrity": "sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U=" - }, - "lodash.keys": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz", - "integrity": "sha1-TbwEcrFWvlCgsoaFXRvQsMZWCYo=", - "requires": { - "lodash._getnative": "3.9.1", - "lodash.isarguments": "3.0.8", - "lodash.isarray": "3.0.4" - } - }, - "lodash.restparam": { - "version": "3.6.1", - "resolved": "https://registry.npmjs.org/lodash.restparam/-/lodash.restparam-3.6.1.tgz", - "integrity": "sha1-k2pOMJ7zMKdkXtQUWYbIWuWyCAU=" + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=", + "dev": true }, "loud-rejection": { "version": "1.3.0", @@ -1207,23 +1382,6 @@ "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=", "dev": true }, - "marked": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/marked/-/marked-0.3.5.tgz", - "integrity": "sha1-QROhWsXXvKFYpargciRYe5+hW5Q=" - }, - "marked-terminal": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/marked-terminal/-/marked-terminal-1.6.1.tgz", - "integrity": "sha1-BM0cfIsO9I2z9oAQ1zpXqWYcbM8=", - "requires": { - "cardinal": "0.5.0", - "chalk": "1.1.3", - "cli-table": "0.3.1", - "lodash.assign": "3.2.0", - "node-emoji": "0.1.0" - } - }, "meow": { "version": "3.7.0", "resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz", @@ -1266,7 +1424,8 @@ "minimist": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true }, "mkdirp": { "version": "0.5.1", @@ -1290,180 +1449,214 @@ "dev": true }, "mksnapshot": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/mksnapshot/-/mksnapshot-0.3.0.tgz", - "integrity": "sha1-MuqYStb1MjJMaj+uZACHa4WChAc=", + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/mksnapshot/-/mksnapshot-0.3.1.tgz", + "integrity": "sha1-JQHAVldDbXQs6Vik/5LHfkDdN+Y=", "dev": true, "requires": { "decompress-zip": "0.3.0", "fs-extra": "0.26.7", - "request": "2.55.0" + "request": "2.83.0" }, "dependencies": { - "asn1": { - "version": "0.1.11", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.1.11.tgz", - "integrity": "sha1-VZvhg3bQik7E2+gId9J4GGObLfc=", - "dev": true - }, "assert-plus": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.1.5.tgz", - "integrity": "sha1-7nQAlBMALYTOxyGcasgRgS5yMWA=", - "dev": true - }, - "async": { - "version": "0.9.2", - "resolved": "https://registry.npmjs.org/async/-/async-0.9.2.tgz", - "integrity": "sha1-rqdNXmHB+JlhO/ZL2mbUx48v0X0=", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", "dev": true }, "aws-sign2": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.5.0.tgz", - "integrity": "sha1-xXED96F/wDfwLXwuZLYC6iI/fWM=", + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", "dev": true }, - "bl": { - "version": "0.9.5", - "resolved": "https://registry.npmjs.org/bl/-/bl-0.9.5.tgz", - "integrity": "sha1-wGt5evCF6gC8Unr8jvzxHeIjIFQ=", + "aws4": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.6.0.tgz", + "integrity": "sha1-g+9cqGCysy5KDe7e6MdxudtXRx4=", + "dev": true + }, + "boom": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/boom/-/boom-4.3.1.tgz", + "integrity": "sha1-T4owBctKfjiJ90kDD9JbluAdLjE=", "dev": true, "requires": { - "readable-stream": "1.0.34" + "hoek": "4.2.0" } }, "caseless": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.9.0.tgz", - "integrity": "sha1-t7Zc5r8UE4hlOc/VM/CzDv+pz4g=", + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", "dev": true }, - "combined-stream": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-0.0.7.tgz", - "integrity": "sha1-ATfmV7qlp1QcV6w3rF/AfXO03B8=", + "cryptiles": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-3.1.2.tgz", + "integrity": "sha1-qJ+7Ig9c4l7FboxKqKT9e1sNKf4=", "dev": true, "requires": { - "delayed-stream": "0.0.5" + "boom": "5.2.0" + }, + "dependencies": { + "boom": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/boom/-/boom-5.2.0.tgz", + "integrity": "sha512-Z5BTk6ZRe4tXXQlkqftmsAUANpXmuwlsF5Oov8ThoMbQRzdGTA1ngYRW160GexgOgjsFOKJz0LYhoNi+2AMBUw==", + "dev": true, + "requires": { + "hoek": "4.2.0" + } + } } }, - "delayed-stream": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-0.0.5.tgz", - "integrity": "sha1-1LH0OpPoKW3+AmlPRoC8N6MTxz8=", + "extend": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz", + "integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ=", "dev": true }, "form-data": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-0.2.0.tgz", - "integrity": "sha1-Jvi8JtpkQOKZy9z7aQNcT3em5GY=", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.1.tgz", + "integrity": "sha1-b7lPvXGIUwbXPRXMSX/kzE7NRL8=", "dev": true, "requires": { - "async": "0.9.2", - "combined-stream": "0.0.7", - "mime-types": "2.0.14" + "asynckit": "0.4.0", + "combined-stream": "1.0.5", + "mime-types": "2.1.17" } }, "har-validator": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-1.8.0.tgz", - "integrity": "sha1-2DhCsOtMQ1lgrrEIoGejqpTA7rI=", + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.0.3.tgz", + "integrity": "sha1-ukAsJmGU8VlW7xXg/PJCmT9qff0=", "dev": true, "requires": { - "bluebird": "2.10.2", - "chalk": "1.1.3", - "commander": "2.9.0", - "is-my-json-valid": "2.13.1" + "ajv": "5.5.2", + "har-schema": "2.0.0" } }, "hawk": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/hawk/-/hawk-2.3.1.tgz", - "integrity": "sha1-HnMc45RH+h0PbXB/e87r7A/R7B8=", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/hawk/-/hawk-6.0.2.tgz", + "integrity": "sha512-miowhl2+U7Qle4vdLqDdPt9m09K6yZhkLDTWGoUiUzrQCn+mHHSmfJgAyGaLRZbPmTqfFFjRV1QWCW0VWUJBbQ==", "dev": true, "requires": { - "boom": "2.10.1", - "cryptiles": "2.0.5", - "hoek": "2.16.3", - "sntp": "1.0.9" + "boom": "4.3.1", + "cryptiles": "3.1.2", + "hoek": "4.2.0", + "sntp": "2.1.0" } }, + "hoek": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/hoek/-/hoek-4.2.0.tgz", + "integrity": "sha512-v0XCLxICi9nPfYrS9RL8HbYnXi9obYAeLbSP00BmnZwCK9+Ih9WOjoZ8YoHCoav2csqn4FOz4Orldsy2dmDwmQ==", + "dev": true + }, "http-signature": { - "version": "0.10.1", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-0.10.1.tgz", - "integrity": "sha1-T72sEyVZqoMjEh5UB3nAoBKyfmY=", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", "dev": true, "requires": { - "asn1": "0.1.11", - "assert-plus": "0.1.5", - "ctype": "0.5.3" + "assert-plus": "1.0.0", + "jsprim": "1.2.2", + "sshpk": "1.7.4" } }, "mime-db": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.12.0.tgz", - "integrity": "sha1-PQxjGA9FjrENMlqqN9fFiuMS6dc=", + "version": "1.30.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.30.0.tgz", + "integrity": "sha1-dMZD2i3Z1qRTmZY0ZbJtXKfXHwE=", "dev": true }, "mime-types": { - "version": "2.0.14", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.0.14.tgz", - "integrity": "sha1-MQ4VnbI+B3+Lsit0jav6SVcUCqY=", + "version": "2.1.17", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.17.tgz", + "integrity": "sha1-Cdejk/A+mVp5+K+Fe3Cp4KsWVXo=", "dev": true, "requires": { - "mime-db": "1.12.0" + "mime-db": "1.30.0" } }, "oauth-sign": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.6.0.tgz", - "integrity": "sha1-fb6uRPbKRU4fFoRR1jB0ZzWBPOM=", + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", + "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=", "dev": true }, "qs": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-2.4.2.tgz", - "integrity": "sha1-9854jld33wtQENp/fE5zujJHD1o=", + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz", + "integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A==", "dev": true }, - "readable-stream": { - "version": "1.0.34", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", - "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", - "dev": true, - "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.1", - "isarray": "0.0.1", - "string_decoder": "0.10.31" - } - }, "request": { - "version": "2.55.0", - "resolved": "https://registry.npmjs.org/request/-/request-2.55.0.tgz", - "integrity": "sha1-11wc32eddrsQD5v/4f5VG1wk6T0=", + "version": "2.83.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.83.0.tgz", + "integrity": "sha512-lR3gD69osqm6EYLk9wB/G1W/laGWjzH90t1vEa2xuxHD5KUrSzp9pUSfTm+YC5Nxt2T8nMPEvKlhbQayU7bgFw==", "dev": true, "requires": { - "aws-sign2": "0.5.0", - "bl": "0.9.5", - "caseless": "0.9.0", - "combined-stream": "0.0.7", + "aws-sign2": "0.7.0", + "aws4": "1.6.0", + "caseless": "0.12.0", + "combined-stream": "1.0.5", + "extend": "3.0.1", "forever-agent": "0.6.1", - "form-data": "0.2.0", - "har-validator": "1.8.0", - "hawk": "2.3.1", - "http-signature": "0.10.1", + "form-data": "2.3.1", + "har-validator": "5.0.3", + "hawk": "6.0.2", + "http-signature": "1.2.0", + "is-typedarray": "1.0.0", "isstream": "0.1.2", "json-stringify-safe": "5.0.1", - "mime-types": "2.0.14", - "node-uuid": "1.4.7", - "oauth-sign": "0.6.0", - "qs": "2.4.2", + "mime-types": "2.1.17", + "oauth-sign": "0.8.2", + "performance-now": "2.1.0", + "qs": "6.5.1", + "safe-buffer": "5.1.1", "stringstream": "0.0.5", - "tough-cookie": "2.2.2", - "tunnel-agent": "0.4.2" + "tough-cookie": "2.3.3", + "tunnel-agent": "0.6.0", + "uuid": "3.2.1" } + }, + "sntp": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/sntp/-/sntp-2.1.0.tgz", + "integrity": "sha512-FL1b58BDrqS3A11lJ0zEdnJ3UOKqVxawAkF3k7F0CVN7VQ34aZrV+G8BZ1WC9ZL7NyrwsW0oviwsWDgRuVYtJg==", + "dev": true, + "requires": { + "hoek": "4.2.0" + } + }, + "tough-cookie": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.3.tgz", + "integrity": "sha1-C2GKVWW23qkL80JdBNVe3EdadWE=", + "dev": true, + "requires": { + "punycode": "1.4.1" + } + }, + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "dev": true, + "requires": { + "safe-buffer": "5.1.1" + } + }, + "uuid": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.2.1.tgz", + "integrity": "sha512-jZnMwlb9Iku/O3smGWvZhauCf6cvvpKi4BKRiliS3cxnI+Gz9j5MEpTz2UFuXiKPJocb7gnsLHwiS05ige5BEA==", + "dev": true } } }, @@ -1508,29 +1701,39 @@ } } }, + "mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dev": true, + "requires": { + "any-promise": "1.3.0", + "object-assign": "4.0.1", + "thenify-all": "1.6.0" + } + }, "ncp": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ncp/-/ncp-2.0.0.tgz", "integrity": "sha1-GVoh1sRuNh0vsSgbo4uR6d9727M=", "dev": true }, - "node-emoji": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-0.1.0.tgz", - "integrity": "sha1-P0QkpVuo7VDCVKE4WLJEVPQYJgI=" - }, "node-notifier": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/node-notifier/-/node-notifier-4.5.0.tgz", - "integrity": "sha1-Ap7pjXqbxOlsnLUb6dTzYTI/6ps=", + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/node-notifier/-/node-notifier-5.2.1.tgz", + "integrity": "sha512-MIBs+AAd6dJ2SklbbE8RUDRlIVhU8MaNLh1A9SUZDUHPiZkWLFde6UNwG41yQHZEToHgJMXqyVZ9UcS/ReOVTg==", "requires": { - "cli-usage": "0.1.2", "growly": "1.3.0", - "lodash.clonedeep": "3.0.2", - "minimist": "1.2.0", - "semver": "5.1.0", - "shellwords": "0.1.0", - "which": "1.2.4" + "semver": "5.5.0", + "shellwords": "0.1.1", + "which": "1.3.0" + }, + "dependencies": { + "semver": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", + "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==" + } } }, "node-uuid": { @@ -1538,13 +1741,23 @@ "resolved": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.7.tgz", "integrity": "sha1-baWhdmjEs91ZYjvaEc9/pMH2Cm8=" }, + "nodeify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/nodeify/-/nodeify-1.0.1.tgz", + "integrity": "sha1-ZKtpp7268DzhB7TwM1yHwLnpGx0=", + "dev": true, + "requires": { + "is-promise": "1.0.1", + "promise": "1.3.0" + } + }, "nopt": { "version": "3.0.6", "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", "integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=", "dev": true, "requires": { - "abbrev": "1.0.7" + "abbrev": "1.1.1" } }, "normalize-package-data": { @@ -1642,6 +1855,45 @@ "lcid": "1.0.0" } }, + "os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", + "dev": true + }, + "p-limit": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.2.0.tgz", + "integrity": "sha512-Y/OtIaXtUPr4/YpMv1pCL5L5ed0rumAaAeBSj12F+bSlMdys7i8oQF/GUJmfpTS/QoaRrS/k6pma29haJpsMng==", + "dev": true, + "requires": { + "p-try": "1.0.0" + } + }, + "p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "dev": true, + "requires": { + "p-limit": "1.2.0" + } + }, + "p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", + "dev": true + }, + "parse-author": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/parse-author/-/parse-author-2.0.0.tgz", + "integrity": "sha1-00YL8d3Q367tQtp1QkLmX7aEqB8=", + "dev": true, + "requires": { + "author-regex": "1.0.0" + } + }, "parse-json": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", @@ -1662,6 +1914,12 @@ "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.0.tgz", "integrity": "sha1-Jj2tpmqz8vsQv3+dJN2PPlcO+RI=" }, + "path-parse": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.5.tgz", + "integrity": "sha1-PBrfhx6pzWyUMbbqK9dKD/BVxME=", + "dev": true + }, "path-type": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", @@ -1679,6 +1937,12 @@ "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=", "dev": true }, + "performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", + "dev": true + }, "pify": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", @@ -1699,15 +1963,14 @@ } }, "plist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/plist/-/plist-1.2.0.tgz", - "integrity": "sha1-CEtQk93JJQbiWfh0uNmxr7jHlZM=", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/plist/-/plist-2.1.0.tgz", + "integrity": "sha1-V8zbeggh3yGDEhejytVOPhRqECU=", "dev": true, "requires": { - "base64-js": "0.0.8", - "util-deprecate": "1.0.2", - "xmlbuilder": "4.0.0", - "xmldom": "0.1.22" + "base64-js": "1.2.0", + "xmlbuilder": "8.2.2", + "xmldom": "0.1.27" } }, "pretty-bytes": { @@ -1735,6 +1998,55 @@ "through2": "0.2.3" } }, + "promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/promise/-/promise-1.3.0.tgz", + "integrity": "sha1-5cyaTIJ45GZP/twBx9qEhCsEAXU=", + "dev": true, + "requires": { + "is-promise": "1.0.1" + } + }, + "pruner": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/pruner/-/pruner-0.0.7.tgz", + "integrity": "sha1-NF+8s+gHARY6HXrfVrrCKaWh5ME=", + "dev": true, + "requires": { + "fs-extra": "4.0.3" + }, + "dependencies": { + "fs-extra": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-4.0.3.tgz", + "integrity": "sha512-q6rbdDd1o2mAnQreO7YADIxf/Whx4AHBiRf6d+/cVT8h44ss+lHgxf1FemcqDnQt9X3ct4McHr+JMGlYSsK7Cg==", + "dev": true, + "requires": { + "graceful-fs": "4.1.3", + "jsonfile": "4.0.0", + "universalify": "0.1.1" + } + }, + "jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "dev": true, + "requires": { + "graceful-fs": "4.1.11" + }, + "dependencies": { + "graceful-fs": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", + "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=", + "dev": true, + "optional": true + } + } + } + } + }, "pseudomap": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", @@ -1749,10 +2061,16 @@ "once": "1.3.3" } }, - "q": { + "punycode": { "version": "1.4.1", - "resolved": "https://registry.npmjs.org/q/-/q-1.4.1.tgz", - "integrity": "sha1-VXBbzZPF82c1MMLCy8DCs63cKG4=", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", + "dev": true + }, + "q": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", + "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=", "dev": true }, "qs": { @@ -1773,9 +2091,9 @@ } }, "rcedit": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/rcedit/-/rcedit-0.5.0.tgz", - "integrity": "sha1-72a1p/AxB1IUGjTiLyPCJDPBzxU=", + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/rcedit/-/rcedit-0.9.0.tgz", + "integrity": "sha1-ORDfVzRTmeKwMl9KUZAH+J5V7xw=", "dev": true }, "read-pkg": { @@ -1820,14 +2138,6 @@ "strip-indent": "1.0.1" } }, - "redeyed": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/redeyed/-/redeyed-0.5.0.tgz", - "integrity": "sha1-erAA5g7jh1rBFdKe2zLBQDxsJdE=", - "requires": { - "esprima-fb": "12001.1.0-dev-harmony-fb" - } - }, "request": { "version": "2.71.0", "resolved": "https://registry.npmjs.org/request/-/request-2.71.0.tgz", @@ -1865,10 +2175,13 @@ } }, "resolve": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", - "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=", - "dev": true + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.5.0.tgz", + "integrity": "sha512-hgoSGrc3pjzAPHNBg+KnFcK2HwlHTs/YrAGUr6qgTVUZmXv1UEXXl0bZNBKMA9fud6lRYFdPGz0xXxycPzmmiw==", + "dev": true, + "requires": { + "path-parse": "1.0.5" + } }, "rimraf": { "version": "2.5.2", @@ -1878,21 +2191,31 @@ "glob": "7.0.3" } }, - "run-series": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/run-series/-/run-series-1.1.4.tgz", - "integrity": "sha1-iac93F51ye+KtjIMChYA1qQRebk=", + "safe-buffer": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", + "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==", "dev": true }, + "sanitize-filename": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/sanitize-filename/-/sanitize-filename-1.6.1.tgz", + "integrity": "sha1-YS2hyWRz+gLczaktzVtKsWSmdyo=", + "dev": true, + "requires": { + "truncate-utf8-bytes": "1.0.2" + } + }, "semver": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/semver/-/semver-5.1.0.tgz", - "integrity": "sha1-hfLPhVBGXE3wAM99hvawVBBqueU=" + "integrity": "sha1-hfLPhVBGXE3wAM99hvawVBBqueU=", + "dev": true }, "shellwords": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/shellwords/-/shellwords-0.1.0.tgz", - "integrity": "sha1-Zq/Ue2oSky2Qccv9mKUueFzQuhQ=" + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/shellwords/-/shellwords-0.1.1.tgz", + "integrity": "sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww==" }, "signal-exit": { "version": "2.1.2", @@ -2017,6 +2340,32 @@ "integrity": "sha1-HhX7ysl9Pumb8tc7TGVrCCu6+5E=", "dev": true }, + "sumchecker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/sumchecker/-/sumchecker-2.0.2.tgz", + "integrity": "sha1-D0LBDl0F2l1C7qPlbDOZo31sWz4=", + "dev": true, + "requires": { + "debug": "2.6.9" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } + } + }, "supports-color": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", @@ -2063,6 +2412,24 @@ } } }, + "thenify": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.0.tgz", + "integrity": "sha1-5p44obq+lpsBCCB5eLn2K4hgSDk=", + "dev": true, + "requires": { + "any-promise": "1.3.0" + } + }, + "thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha1-GhkY1ALY/D+Y+/I02wvMjMEOlyY=", + "dev": true, + "requires": { + "thenify": "3.3.0" + } + }, "throttleit": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-1.0.0.tgz", @@ -2089,6 +2456,15 @@ } } }, + "tmp": { + "version": "0.0.28", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.28.tgz", + "integrity": "sha1-Fyc1t/YU6nrzlmT6hM8N5OUV0SA=", + "dev": true, + "requires": { + "os-tmpdir": "1.0.2" + } + }, "touch": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/touch/-/touch-0.0.3.tgz", @@ -2104,7 +2480,7 @@ "integrity": "sha1-bd0hvSoxQXuScn3Vhfim83YI6+4=", "dev": true, "requires": { - "abbrev": "1.0.7" + "abbrev": "1.1.1" } } } @@ -2126,6 +2502,15 @@ "integrity": "sha1-WIeWa7WCpFA6QetST301ARgVphM=", "dev": true }, + "truncate-utf8-bytes": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz", + "integrity": "sha1-QFkjkJWS1W94pYGENLC3hInKXys=", + "dev": true, + "requires": { + "utf8-byte-length": "1.0.4" + } + }, "tunnel-agent": { "version": "0.4.2", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.4.2.tgz", @@ -2143,6 +2528,18 @@ "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", "dev": true }, + "universalify": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.1.tgz", + "integrity": "sha1-+nG63UQ3r0wUiEHjs7Fl+enlkLc=", + "dev": true + }, + "utf8-byte-length": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/utf8-byte-length/-/utf8-byte-length-1.0.4.tgz", + "integrity": "sha1-9F8VDExm7uloGGUFq5P8u4rWv2E=", + "dev": true + }, "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -2167,12 +2564,11 @@ } }, "which": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/which/-/which-1.2.4.tgz", - "integrity": "sha1-FVf5YIBgTlsRs1meufRbUKnv1yI=", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.0.tgz", + "integrity": "sha512-xcJpopdamTuY5duC/KnTTNBraPK54YwpenP4lzxU8H91GudWpFv38u0CKjclE1Wi2EH2EDz5LRcHcKbCIzqGyg==", "requires": { - "is-absolute": "0.1.7", - "isexe": "1.1.2" + "isexe": "2.0.0" } }, "window-size": { @@ -2194,18 +2590,15 @@ "integrity": "sha1-HmWWmWXMvC20VIxrhKbyxa7dRzk=" }, "xmlbuilder": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-4.0.0.tgz", - "integrity": "sha1-mLj2UcowqmJANvEn0RzGbce5B6M=", - "dev": true, - "requires": { - "lodash": "3.10.1" - } + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-8.2.2.tgz", + "integrity": "sha1-aSSGc0ELS6QuGmE2VR0pIjNap3M=", + "dev": true }, "xmldom": { - "version": "0.1.22", - "resolved": "https://registry.npmjs.org/xmldom/-/xmldom-0.1.22.tgz", - "integrity": "sha1-EN5OXpZJgfA8jMcvrcCNFLbDqiY=", + "version": "0.1.27", + "resolved": "https://registry.npmjs.org/xmldom/-/xmldom-0.1.27.tgz", + "integrity": "sha1-1QH5ezvbQDr4757MIFcxh6rawOk=", "dev": true }, "xtend": { @@ -2237,6 +2630,23 @@ "y18n": "3.2.1" } }, + "yargs-parser": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-8.1.0.tgz", + "integrity": "sha512-yP+6QqN8BmrgW2ggLtTbdrOyBNSI7zBa4IykmiV5R1wl1JWNxQvWhMfMdmzIYtKU7oP3OOInY/tl2ov3BDjnJQ==", + "dev": true, + "requires": { + "camelcase": "4.1.0" + }, + "dependencies": { + "camelcase": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", + "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", + "dev": true + } + } + }, "yauzl": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.4.1.tgz", diff --git a/server-console/package.json b/server-console/package.json index 8d2a177e0a..9f47d0e4cc 100644 --- a/server-console/package.json +++ b/server-console/package.json @@ -8,7 +8,7 @@ "" ], "devDependencies": { - "electron-packager": "^6.0.2", + "electron-packager": "^11.0.0", "electron-prebuilt": "0.37.5" }, "repository": { @@ -27,7 +27,7 @@ "cheerio": "^0.19.0", "extend": "^3.0.0", "fs-extra": "^0.26.4", - "node-notifier": "^4.4.0", + "node-notifier": "^5.2.1", "os-homedir": "^1.0.1", "request": "^2.67.0", "request-progress": "1.0.2", From e15ab2ca6c49478f1b7006b829e2dcad840aa418 Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Mon, 12 Feb 2018 13:32:48 -0800 Subject: [PATCH 19/45] CR --- domain-server/src/BackupSupervisor.cpp | 7 ++----- domain-server/src/BackupSupervisor.h | 3 +-- domain-server/src/DomainServer.h | 1 - libraries/networking/src/PacketReceiver.cpp | 2 +- 4 files changed, 4 insertions(+), 9 deletions(-) diff --git a/domain-server/src/BackupSupervisor.cpp b/domain-server/src/BackupSupervisor.cpp index 1bf1e9e199..03ad5de558 100644 --- a/domain-server/src/BackupSupervisor.cpp +++ b/domain-server/src/BackupSupervisor.cpp @@ -108,6 +108,7 @@ bool BackupSupervisor::loadBackup(const QString& backupFile) { auto document = QJsonDocument::fromJson(file.readAll(), &error); if (document.isNull() || !document.isObject()) { qCritical() << "Could not parse backup file to JSON object:" << backupFile; + qCritical() << " Error:" << error.errorString(); backup.corruptedBackup = true; return false; } @@ -165,7 +166,6 @@ void BackupSupervisor::backupAssetServer() { }); startBackup(); - qDebug() << "Requesting mappings for backup!"; request->start(); } @@ -197,7 +197,7 @@ void BackupSupervisor::backupNextMissingFile() { if (request->getError() == AssetRequest::NoError) { qDebug() << "Got" << request->getHash(); - bool success = writeAssetFile(request->getHash(),request->getData()); + bool success = writeAssetFile(request->getHash(), request->getData()); if (!success) { qCritical() << "Failed to write asset file" << request->getHash(); } @@ -263,8 +263,6 @@ void BackupSupervisor::restoreAssetServer(int backupIndex) { auto request = assetClient->createGetAllMappingsRequest(); connect(request, &GetAllMappingsRequest::finished, this, [this, backupIndex](GetAllMappingsRequest* request) { - qDebug() << "Got" << request->getMappings().size() << "mappings!"; - if (request->getError() == MappingRequest::NoError) { const auto& newMappings = _backups.at(backupIndex).mappings; computeServerStateDifference(request->getMappings(), newMappings); @@ -278,7 +276,6 @@ void BackupSupervisor::restoreAssetServer(int backupIndex) { }); startRestore(); - qDebug() << "Requesting mappings for restore!"; request->start(); } diff --git a/domain-server/src/BackupSupervisor.h b/domain-server/src/BackupSupervisor.h index f01e389c39..067abdc25c 100644 --- a/domain-server/src/BackupSupervisor.h +++ b/domain-server/src/BackupSupervisor.h @@ -53,7 +53,6 @@ private: bool writeBackupFile(const AssetUtils::AssetMappings& mappings); bool writeAssetFile(const AssetUtils::AssetHash& hash, const QByteArray& data); - void startRestore() { _restoreInProgress = true; } void finishRestore() { _restoreInProgress = false; } void computeServerStateDifference(const AssetUtils::AssetMappings& currentMappings, @@ -75,7 +74,7 @@ private: bool _backupInProgress { false }; std::vector _assetsLeftToRequest; - // Internal storage for restor in progress + // Internal storage for restore in progress bool _restoreInProgress { false }; std::vector _assetsLeftToUpload; std::vector> _mappingsLeftToSet; diff --git a/domain-server/src/DomainServer.h b/domain-server/src/DomainServer.h index f84a47d457..c7d779b394 100644 --- a/domain-server/src/DomainServer.h +++ b/domain-server/src/DomainServer.h @@ -253,7 +253,6 @@ private: bool _sendICEServerAddressToMetaverseAPIRedo { false }; QHash> _pendingOAuthConnections; - BackupSupervisor _backupSupervisor; QThread _assetClientThread; }; diff --git a/libraries/networking/src/PacketReceiver.cpp b/libraries/networking/src/PacketReceiver.cpp index 0834a55148..27b57ef26c 100644 --- a/libraries/networking/src/PacketReceiver.cpp +++ b/libraries/networking/src/PacketReceiver.cpp @@ -315,7 +315,7 @@ void PacketReceiver::handleVerifiedMessage(QSharedPointer recei } } else { qCDebug(networking).nospace() << "Listener for packet " << receivedMessage->getType() - << " has been destroyed. Removing from listener map."; + << " has been destroyed. Removing from listener map."; it = _messageListenerMap.erase(it); // if it exists, remove the listener from _directlyConnectedObjects From ddcee05b14637a342dd286c93482fba289300a0f Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Tue, 13 Feb 2018 09:41:00 -0800 Subject: [PATCH 20/45] added control button for controller API --- interface/resources/controllers/keyboardMouse.json | 4 ++-- .../src/input-plugins/KeyboardMouseDevice.cpp | 9 ++++----- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/interface/resources/controllers/keyboardMouse.json b/interface/resources/controllers/keyboardMouse.json index 54d2467f78..b3f16a115e 100644 --- a/interface/resources/controllers/keyboardMouse.json +++ b/interface/resources/controllers/keyboardMouse.json @@ -88,8 +88,8 @@ ] }, - { "from": "Keyboard.W", "to": "Actions.LONGITUDINAL_FORWARD" }, - { "from": "Keyboard.S", "to": "Actions.LONGITUDINAL_BACKWARD" }, + { "from": "Keyboard.W", "when": "!Keyboard.Control", "to": "Actions.LONGITUDINAL_FORWARD" }, + { "from": "Keyboard.S", "when": "!Keyboard.Control", "to": "Actions.LONGITUDINAL_BACKWARD" }, { "from": "Keyboard.C", "to": "Actions.VERTICAL_DOWN" }, { "from": "Keyboard.E", "to": "Actions.VERTICAL_UP" }, { "from": "Keyboard.Left", "when": "Keyboard.RightMouseButton", "to": "Actions.LATERAL_LEFT" }, diff --git a/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.cpp b/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.cpp index e9ec6d8910..ef7f482e44 100755 --- a/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.cpp +++ b/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.cpp @@ -54,11 +54,9 @@ void KeyboardMouseDevice::InputDevice::focusOutEvent() { void KeyboardMouseDevice::keyPressEvent(QKeyEvent* event) { auto input = _inputDevice->makeInput((Qt::Key) event->key()); - if (!(event->modifiers() & Qt::KeyboardModifier::ControlModifier)) { - auto result = _inputDevice->_buttonPressedMap.insert(input.getChannel()); - if (result.second) { - // key pressed again ? without catching the release event ? - } + auto result = _inputDevice->_buttonPressedMap.insert(input.getChannel()); + if (result.second) { + // key pressed again ? without catching the release event ? } } @@ -237,6 +235,7 @@ controller::Input::NamedVector KeyboardMouseDevice::InputDevice::getAvailableInp availableInputs.append(Input::NamedPair(makeInput(Qt::Key_PageUp), QKeySequence(Qt::Key_PageUp).toString())); availableInputs.append(Input::NamedPair(makeInput(Qt::Key_PageDown), QKeySequence(Qt::Key_PageDown).toString())); availableInputs.append(Input::NamedPair(makeInput(Qt::Key_Tab), QKeySequence(Qt::Key_Tab).toString())); + availableInputs.append(Input::NamedPair(makeInput(Qt::Key_Control), "Control")); availableInputs.append(Input::NamedPair(makeInput(Qt::LeftButton), "LeftMouseButton")); availableInputs.append(Input::NamedPair(makeInput(Qt::MiddleButton), "MiddleMouseButton")); From 45f09dde96c55e8982840bdb7bea57f61dcdb21f Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Tue, 13 Feb 2018 10:32:15 -0800 Subject: [PATCH 21/45] address tony's requests --- interface/src/avatar/MyAvatar.cpp | 2 +- interface/src/avatar/MyAvatar.h | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index d5cd7bdca1..15ae1584f6 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -1461,7 +1461,7 @@ void MyAvatar::clearJointsData() { void MyAvatar::setSkeletonModelURL(const QUrl& skeletonModelURL) { _skeletonModelChangeCount++; - int skeletonModelChangeCount = _skeletonModelChangeCount; + int skeletonModelChangeCount = _skeletonModelChangeCount; Avatar::setSkeletonModelURL(skeletonModelURL); _skeletonModel->setVisibleInScene(true, qApp->getMain3DScene(), render::ItemKey::TAG_BITS_NONE); _headBoneSet.clear(); diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 3e063547d0..28af8b62fd 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -819,7 +819,6 @@ private: bool _enableDebugDrawIKConstraints { false }; bool _enableDebugDrawIKChains { false }; bool _enableDebugDrawDetailedCollision { false }; - bool _shouldInitHeadBones { true }; mutable bool _cauterizationNeedsUpdate; // do we need to scan children and update their "cauterized" state? From cf3d8e446376e4a4f4b9c3b637b8b835533c8892 Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Tue, 13 Feb 2018 11:07:15 -0800 Subject: [PATCH 22/45] remove cached file --- interface/resources/qml/js/Utils.jsc | Bin 6516 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 interface/resources/qml/js/Utils.jsc diff --git a/interface/resources/qml/js/Utils.jsc b/interface/resources/qml/js/Utils.jsc deleted file mode 100644 index 8da68e4e192018ee2b4f3d835ec91f36f0161eca..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6516 zcmcIoZDrF4*+OKP^~mxE_StR<97M?dYinHhXIH0prUNWm)BPSkkxI9zIccH zbDYc+&t`mYYoRZ%dcNJC7t6S;1kom{uKi%kZQQ*B-}@Ea#y#@Sit;ack*DT2py=+} zfuFX0y&bsG4t&xMe9-`o8bF@`3>v_=0VE9IX#==m0KYMS=MCVp0laDeYXyq{m#>ENBdlKi6Kn!P8Tz-gSf=^W4f1vJz z>OP?E{p$W>bw7z?g5MlwFfTkK{Y3k0%wPWl) z&?-R|Nd~OQ(pqO|>7zFD;=ilaYBV{~8D84z4za!=ysGK#f#f15cOtKd^CTCgW+(F3YvinYJCd&`a))r0Mo85U*Q-9!M*I7$0`-qB z_gRHiN|Dr;D9>%Ew0z6Wn-=@9$24G=wcYQAKr;$=G(+YT?S4gBPFhi3D=YPBjMwP(DtXg0@d zG_^09dbG0=GK0s0m5|xhXBFIQ_sX@rd#zip<)W#5p*M~l7I0kJ>Rx3@lVReJ;DNwB zDyZ3=2tQI@%!JdrBFuEc?sBA(9kgr3Y_aT2sk*B@nIUjOEkzeu#m^h8RqKk4g`M3sB#sj3Z zin4^&CHb`Bz^YaPh9305YFR;C*(6#-3l|K#%Uq~N?&pFYMV^-~(A@o8!1hi6=630V z=D8$YpkTiX7yMeo-YZonuRF86K3C#h$;)v~gtsnv`C8)2tn*te(mN*CdaZPdOV`he z*)_jyCu3zh886w%`HhxMf1eQDtm(Upqbj0Vx%OZr)gDguj!@-{XXzA|(w?PRE?x30 zEpTbky|hS!BgqTa0w)epy`_*v_84x=)^DWM#;N*^jM^Bf-(bR$)d{)5D$iF5i3b%r z#w^&U)1~N17geDbU5O$&)0=CYvjc#Aq(q)qH~AvR+Mm@cT4=hL1Xn8N@U?K^55MR2@%P2jgn8(i`O7~6 zxK$c)64LT_sgcZ&vec+z2H&rkC;mPWHh*!|K2jnUvlTP=pUo?u3hWUVoWmK4R^9)I zAzh!oRhDhE@4~X)XSJ_SUh|m1>hz)>Epi;v7^*bQ;}j93m2wi&RX?}-C7*gUq#NI@ zGOc58rVV>dQzIEaEO@PCVBLT$(j!F`RMAv8#^*$lYgTusRYURAwMF8EZetxI82B+s z`z+1wmz6ELSf7j!Xm+HgDrNgfDV*#t+ROb#9?Zyt{A%Rsvj0*NGhTn2Ds33J1MhAU zlsa=S4A+@{l}G8soHY(5De)+!Qogr%YCETuPj1^b!^~uX&&5@)|bMPIn zYR$nz?l-^k>yW^`7fQN$B&>{o^T;%79xpqkx?C&O8g-&Yt<(Ukr3_kWe<|r^QX-BN zry-R27SG-x}NZM;N{71_wRxwXjRK=*ls0BKHr)#V_Lkxd5bo6SDGTeHa_CrUP zBCi|1{m}7&DsBaO^KwJnp0@J-amzQ0+i%-y?QXk(T}a-t~cHo^z>QDFD)6)fXCSbJk% eP^J00or-VRsaUCE2A`?b=Mm4BnYXId>i+-+$aWzB From f64e5f2444343bd15a05dc0fee9fdb65477efc61 Mon Sep 17 00:00:00 2001 From: samcake Date: Tue, 13 Feb 2018 13:06:26 -0800 Subject: [PATCH 23/45] Introducing the cull group --- .../src/avatars-renderer/Avatar.cpp | 2 +- libraries/render/src/render/CullTask.cpp | 24 +++++++++++++++++++ libraries/render/src/render/Item.cpp | 10 ++++++++ libraries/render/src/render/Item.h | 19 +++++++++++++++ .../src/render/RenderFetchCullSortTask.cpp | 4 ++-- 5 files changed, 56 insertions(+), 3 deletions(-) diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp index 86635cd3bf..9cdd4e2d00 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp @@ -50,7 +50,7 @@ const glm::vec3 HAND_TO_PALM_OFFSET(0.0f, 0.12f, 0.08f); namespace render { template <> const ItemKey payloadGetKey(const AvatarSharedPointer& avatar) { - return ItemKey::Builder::opaqueShape().withTypeMeta().withTagBits(ItemKey::TAG_BITS_0 | ItemKey::TAG_BITS_1); + return ItemKey::Builder::opaqueShape().withTypeMeta().withTagBits(ItemKey::TAG_BITS_0 | ItemKey::TAG_BITS_1).withCullGroup(); } template <> const Item::Bound payloadGetBound(const AvatarSharedPointer& avatar) { return static_pointer_cast(avatar)->getBounds(); diff --git a/libraries/render/src/render/CullTask.cpp b/libraries/render/src/render/CullTask.cpp index 4dec3030ef..467cf5adbf 100644 --- a/libraries/render/src/render/CullTask.cpp +++ b/libraries/render/src/render/CullTask.cpp @@ -227,6 +227,9 @@ void CullSpatialSelection::run(const RenderContextPointer& renderContext, if (filter.test(item.getKey())) { ItemBound itemBound(id, item.getBound()); outItems.emplace_back(itemBound); + if (item.getKey().isCullGroup()) { + item.fetchMetaSubItemBounds(outItems, (*scene)); + } } } } @@ -239,6 +242,9 @@ void CullSpatialSelection::run(const RenderContextPointer& renderContext, if (filter.test(item.getKey())) { ItemBound itemBound(id, item.getBound()); outItems.emplace_back(itemBound); + if (item.getKey().isCullGroup()) { + item.fetchMetaSubItemBounds(outItems, (*scene)); + } } } } @@ -251,6 +257,9 @@ void CullSpatialSelection::run(const RenderContextPointer& renderContext, if (filter.test(item.getKey())) { ItemBound itemBound(id, item.getBound()); outItems.emplace_back(itemBound); + if (item.getKey().isCullGroup()) { + item.fetchMetaSubItemBounds(outItems, (*scene)); + } } } } @@ -263,6 +272,9 @@ void CullSpatialSelection::run(const RenderContextPointer& renderContext, if (filter.test(item.getKey())) { ItemBound itemBound(id, item.getBound()); outItems.emplace_back(itemBound); + if (item.getKey().isCullGroup()) { + item.fetchMetaSubItemBounds(outItems, (*scene)); + } } } } @@ -277,6 +289,9 @@ void CullSpatialSelection::run(const RenderContextPointer& renderContext, if (filter.test(item.getKey())) { ItemBound itemBound(id, item.getBound()); outItems.emplace_back(itemBound); + if (item.getKey().isCullGroup()) { + item.fetchMetaSubItemBounds(outItems, (*scene)); + } } } } @@ -290,6 +305,9 @@ void CullSpatialSelection::run(const RenderContextPointer& renderContext, ItemBound itemBound(id, item.getBound()); if (test.solidAngleTest(itemBound.bound)) { outItems.emplace_back(itemBound); + if (item.getKey().isCullGroup()) { + item.fetchMetaSubItemBounds(outItems, (*scene)); + } } } } @@ -304,6 +322,9 @@ void CullSpatialSelection::run(const RenderContextPointer& renderContext, ItemBound itemBound(id, item.getBound()); if (test.frustumTest(itemBound.bound)) { outItems.emplace_back(itemBound); + if (item.getKey().isCullGroup()) { + item.fetchMetaSubItemBounds(outItems, (*scene)); + } } } } @@ -319,6 +340,9 @@ void CullSpatialSelection::run(const RenderContextPointer& renderContext, if (test.frustumTest(itemBound.bound)) { if (test.solidAngleTest(itemBound.bound)) { outItems.emplace_back(itemBound); + if (item.getKey().isCullGroup()) { + item.fetchMetaSubItemBounds(outItems, (*scene)); + } } } } diff --git a/libraries/render/src/render/Item.cpp b/libraries/render/src/render/Item.cpp index 612dba076b..b8ca091c40 100644 --- a/libraries/render/src/render/Item.cpp +++ b/libraries/render/src/render/Item.cpp @@ -113,6 +113,16 @@ const ShapeKey Item::getShapeKey() const { return shapeKey; } +uint32_t Item::fetchMetaSubItemBounds(ItemBounds& subItemBounds, Scene& scene) const { + ItemIDs subItems; + auto numSubs = fetchMetaSubItems(subItems); + + for (auto id : subItems) { + subItemBounds.emplace_back(id, scene.getItem(id).getBound()); + } + return numSubs; +} + namespace render { template <> const ItemKey payloadGetKey(const PayloadProxyInterface::Pointer& payload) { if (!payload) { diff --git a/libraries/render/src/render/Item.h b/libraries/render/src/render/Item.h index ff4b3a0458..3c82e49e8d 100644 --- a/libraries/render/src/render/Item.h +++ b/libraries/render/src/render/Item.h @@ -78,11 +78,13 @@ public: INVISIBLE, // Visible or not in the scene? SHADOW_CASTER, // Item cast shadows LAYERED, // Item belongs to one of the layers different from the default layer + CULL_GROUP, // As a meta item, the culling of my sub items is based solely on my bounding box and my visibility in the FIRST_TAG_BIT, // 8 Tags available to organize the items and filter them against LAST_TAG_BIT = FIRST_TAG_BIT + NUM_TAGS, __SMALLER, // Reserved bit for spatialized item to indicate that it is smaller than expected in the cell in which it belongs (probably because it overlaps over several smaller cells) + __META_CULLED, // Reserved bit for sub item of a meta render item to indicate that the culling is tied to the culling of the meta render item, not this one NUM_FLAGS, // Not a valid flag }; @@ -122,6 +124,7 @@ public: Builder& withInvisible() { _flags.set(INVISIBLE); return (*this); } Builder& withShadowCaster() { _flags.set(SHADOW_CASTER); return (*this); } Builder& withLayered() { _flags.set(LAYERED); return (*this); } + Builder& withCullGroup() { _flags.set(CULL_GROUP); return (*this); } Builder& withTag(Tag tag) { _flags.set(FIRST_TAG_BIT + tag); return (*this); } // Set ALL the tags in one call using the Tag bits @@ -132,6 +135,9 @@ public: static Builder transparentShape() { return Builder().withTypeShape().withTransparent(); } static Builder light() { return Builder().withTypeLight(); } static Builder background() { return Builder().withViewSpace().withLayered(); } + + // Not meant to be public, part of the inner item / key / filter system + Builder& __withMetaCulled() { _flags.set(__META_CULLED); return (*this); } }; ItemKey(const Builder& builder) : ItemKey(builder._flags) {} @@ -159,6 +165,9 @@ public: bool isLayered() const { return _flags[LAYERED]; } bool isSpatial() const { return !isLayered(); } + bool isCullGroup() const { return _flags[CULL_GROUP]; } + void setCullGroup(bool cullGroup) { (cullGroup ? _flags.set(CULL_GROUP) : _flags.reset(CULL_GROUP)); } + bool isTag(Tag tag) const { return _flags[FIRST_TAG_BIT + tag]; } uint8_t getTagBits() const { return ((_flags.to_ulong() & KEY_TAG_BITS_MASK) >> FIRST_TAG_BIT); } @@ -166,6 +175,9 @@ public: bool isSmall() const { return _flags[__SMALLER]; } void setSmaller(bool smaller) { (smaller ? _flags.set(__SMALLER) : _flags.reset(__SMALLER)); } + bool isMetaCulled() const { return _flags[__META_CULLED]; } + void setMetaCulled(bool metaCulled) { (metaCulled ? _flags.set(__META_CULLED) : _flags.reset(__META_CULLED)); } + bool operator==(const ItemKey& key) { return (_flags == key._flags); } bool operator!=(const ItemKey& key) { return (_flags != key._flags); } }; @@ -221,6 +233,12 @@ public: Builder& withoutLayered() { _value.reset(ItemKey::LAYERED); _mask.set(ItemKey::LAYERED); return (*this); } Builder& withLayered() { _value.set(ItemKey::LAYERED); _mask.set(ItemKey::LAYERED); return (*this); } + Builder& withoutCullGroup() { _value.reset(ItemKey::CULL_GROUP); _mask.set(ItemKey::CULL_GROUP); return (*this); } + Builder& withCullGroup() { _value.set(ItemKey::CULL_GROUP); _mask.set(ItemKey::CULL_GROUP); return (*this); } + + Builder& withoutMetaCulled() { _value.reset(ItemKey::__META_CULLED); _mask.set(ItemKey::__META_CULLED); return (*this); } + Builder& withMetaCulled() { _value.set(ItemKey::__META_CULLED); _mask.set(ItemKey::__META_CULLED); return (*this); } + Builder& withoutTag(ItemKey::Tag tagIndex) { _value.reset(ItemKey::FIRST_TAG_BIT + tagIndex); _mask.set(ItemKey::FIRST_TAG_BIT + tagIndex); return (*this); } Builder& withTag(ItemKey::Tag tagIndex) { _value.set(ItemKey::FIRST_TAG_BIT + tagIndex); _mask.set(ItemKey::FIRST_TAG_BIT + tagIndex); return (*this); } // Set ALL the tags in one call using the Tag bits and the Tag bits touched @@ -420,6 +438,7 @@ public: // Meta Type Interface uint32_t fetchMetaSubItems(ItemIDs& subItems) const { return _payload->fetchMetaSubItems(subItems); } + uint32_t fetchMetaSubItemBounds(ItemBounds& subItemBounds, Scene& scene) const; // Access the status const StatusPointer& getStatus() const { return _payload->getStatus(); } diff --git a/libraries/render/src/render/RenderFetchCullSortTask.cpp b/libraries/render/src/render/RenderFetchCullSortTask.cpp index 6fb9ba88aa..4fe5e2f1be 100644 --- a/libraries/render/src/render/RenderFetchCullSortTask.cpp +++ b/libraries/render/src/render/RenderFetchCullSortTask.cpp @@ -22,7 +22,7 @@ void RenderFetchCullSortTask::build(JobModel& task, const Varying& input, Varyin // CPU jobs: // Fetch and cull the items from the scene - const ItemFilter filter = ItemFilter::Builder::visibleWorldItems().withoutLayered().withTagBits(tagBits, tagMask); + const ItemFilter filter = ItemFilter::Builder::visibleWorldItems().withoutLayered().withoutMetaCulled().withTagBits(tagBits, tagMask); const auto spatialFilter = render::Varying(filter); const auto fetchInput = FetchSpatialTree::Inputs(filter, glm::ivec2(0,0)).asVarying(); const auto spatialSelection = task.addJob("FetchSceneSelection", fetchInput); @@ -30,7 +30,7 @@ void RenderFetchCullSortTask::build(JobModel& task, const Varying& input, Varyin const auto culledSpatialSelection = task.addJob("CullSceneSelection", cullInputs, cullFunctor, RenderDetails::ITEM); // Overlays are not culled - const ItemFilter overlayfilter = ItemFilter::Builder().withVisible().withTagBits(tagBits, tagMask); + const ItemFilter overlayfilter = ItemFilter::Builder().withVisible().withoutMetaCulled().withTagBits(tagBits, tagMask); const auto nonspatialFilter = render::Varying(overlayfilter); const auto nonspatialSelection = task.addJob("FetchOverlaySelection", nonspatialFilter); From 3e41229e8c674c1a568c16b4b199c5a0f1d8632f Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Tue, 13 Feb 2018 13:30:17 -0800 Subject: [PATCH 24/45] Commerce: Fix wrap mode for very long P2P memos --- interface/resources/qml/hifi/commerce/wallet/WalletHome.qml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml b/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml index b980c13e3c..83c1a2035d 100644 --- a/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml +++ b/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml @@ -391,7 +391,7 @@ Item { anchors.topMargin: 15; width: 118; height: paintedHeight; - wrapMode: Text.WordWrap; + wrapMode: Text.Wrap; // Alignment horizontalAlignment: Text.AlignRight; } @@ -408,7 +408,7 @@ Item { height: paintedHeight; color: model.status === "invalidated" ? hifi.colors.redAccent : hifi.colors.baseGrayHighlight; linkColor: hifi.colors.blueAccent; - wrapMode: Text.WordWrap; + wrapMode: Text.Wrap; font.strikeout: model.status === "invalidated"; onLinkActivated: { From 1d3ae1b187cad4b2a48e5823ccfde3476e2106a1 Mon Sep 17 00:00:00 2001 From: samcake Date: Tue, 13 Feb 2018 17:50:01 -0800 Subject: [PATCH 25/45] Work in progress to get the render items beeing tagged correctly for groupCulled --- interface/src/avatar/MyAvatar.cpp | 8 ++--- interface/src/ui/overlays/ModelOverlay.cpp | 2 +- .../src/RenderableModelEntityItem.cpp | 2 +- .../render-utils/src/MeshPartPayload.cpp | 12 +++++-- libraries/render-utils/src/MeshPartPayload.h | 4 +-- libraries/render-utils/src/Model.cpp | 36 ++++++++++--------- libraries/render-utils/src/Model.h | 6 +++- libraries/render/src/render/CullTask.cpp | 9 ++--- libraries/render/src/render/Item.cpp | 7 +++- libraries/render/src/render/Item.h | 5 ++- .../src/render/RenderFetchCullSortTask.cpp | 2 +- 11 files changed, 57 insertions(+), 36 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index ba4d48fd3f..9c063b69fb 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -1111,7 +1111,7 @@ void MyAvatar::setEnableDebugDrawIKChains(bool isEnabled) { } void MyAvatar::setEnableMeshVisible(bool isEnabled) { - _skeletonModel->setVisibleInScene(isEnabled, qApp->getMain3DScene(), render::ItemKey::TAG_BITS_NONE); + _skeletonModel->setVisibleInScene(isEnabled, qApp->getMain3DScene(), render::ItemKey::TAG_BITS_NONE, true); } void MyAvatar::setEnableInverseKinematics(bool isEnabled) { @@ -1461,7 +1461,7 @@ void MyAvatar::clearJointsData() { void MyAvatar::setSkeletonModelURL(const QUrl& skeletonModelURL) { Avatar::setSkeletonModelURL(skeletonModelURL); - _skeletonModel->setVisibleInScene(true, qApp->getMain3DScene(), render::ItemKey::TAG_BITS_NONE); + _skeletonModel->setVisibleInScene(true, qApp->getMain3DScene(), render::ItemKey::TAG_BITS_NONE, true); _headBoneSet.clear(); _cauterizationNeedsUpdate = true; saveAvatarUrl(); @@ -1808,7 +1808,7 @@ void MyAvatar::attach(const QString& modelURL, const QString& jointName, void MyAvatar::setVisibleInSceneIfReady(Model* model, const render::ScenePointer& scene, bool visible) { if (model->isActive() && model->isRenderable()) { - model->setVisibleInScene(visible, scene, render::ItemKey::TAG_BITS_NONE); + model->setVisibleInScene(visible, scene, render::ItemKey::TAG_BITS_NONE, true); } } @@ -2008,7 +2008,7 @@ void MyAvatar::preDisplaySide(RenderArgs* renderArgs) { _attachmentData[i].jointName.compare("HeadTop_End", Qt::CaseInsensitive) == 0 || _attachmentData[i].jointName.compare("Face", Qt::CaseInsensitive) == 0) { _attachmentModels[i]->setVisibleInScene(shouldDrawHead, qApp->getMain3DScene(), - render::ItemKey::TAG_BITS_NONE); + render::ItemKey::TAG_BITS_NONE, true); } } } diff --git a/interface/src/ui/overlays/ModelOverlay.cpp b/interface/src/ui/overlays/ModelOverlay.cpp index 4847650163..2b2c1403ff 100644 --- a/interface/src/ui/overlays/ModelOverlay.cpp +++ b/interface/src/ui/overlays/ModelOverlay.cpp @@ -87,7 +87,7 @@ void ModelOverlay::update(float deltatime) { if (_visibleDirty) { _visibleDirty = false; // don't show overlays in mirrors - _model->setVisibleInScene(getVisible(), scene, render::ItemKey::TAG_BITS_0); + _model->setVisibleInScene(getVisible(), scene, render::ItemKey::TAG_BITS_0, false); } if (_drawInFrontDirty) { _drawInFrontDirty = false; diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index 137203f475..f477283843 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -1343,7 +1343,7 @@ void ModelEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& sce // FIXME: this seems like it could be optimized if we tracked our last known visible state in // the renderable item. As it stands now the model checks it's visible/invisible state // so most of the time we don't do anything in this function. - model->setVisibleInScene(_visible, scene, viewTaskBits); + model->setVisibleInScene(_visible, scene, viewTaskBits, false); } // TODO? early exit here when not visible? diff --git a/libraries/render-utils/src/MeshPartPayload.cpp b/libraries/render-utils/src/MeshPartPayload.cpp index 9655b60a78..e3d4818ec5 100644 --- a/libraries/render-utils/src/MeshPartPayload.cpp +++ b/libraries/render-utils/src/MeshPartPayload.cpp @@ -71,7 +71,7 @@ void MeshPartPayload::updateMaterial(graphics::MaterialPointer drawMaterial) { _drawMaterial = drawMaterial; } -void MeshPartPayload::updateKey(bool isVisible, bool isLayered, uint8_t tagBits) { +void MeshPartPayload::updateKey(bool isVisible, bool isLayered, uint8_t tagBits, bool isGroupCulled) { ItemKey::Builder builder; builder.withTypeShape(); @@ -85,6 +85,10 @@ void MeshPartPayload::updateKey(bool isVisible, bool isLayered, uint8_t tagBits) builder.withLayered(); } + if (isGroupCulled) { + builder.withMetaCulled(); + } + if (_drawMaterial) { auto matKey = _drawMaterial->getKey(); if (matKey.isTranslucent()) { @@ -403,7 +407,7 @@ void ModelMeshPartPayload::updateTransformForSkinnedMesh(const Transform& render _worldBound.transform(boundTransform); } -void ModelMeshPartPayload::updateKey(bool isVisible, bool isLayered, uint8_t tagBits) { +void ModelMeshPartPayload::updateKey(bool isVisible, bool isLayered, uint8_t tagBits, bool isGroupCulled) { ItemKey::Builder builder; builder.withTypeShape(); @@ -417,6 +421,10 @@ void ModelMeshPartPayload::updateKey(bool isVisible, bool isLayered, uint8_t tag builder.withLayered(); } + if (isGroupCulled) { + builder.withMetaCulled(); + } + if (_isBlendShaped || _isSkinned) { builder.withDeformed(); } diff --git a/libraries/render-utils/src/MeshPartPayload.h b/libraries/render-utils/src/MeshPartPayload.h index 21f9dc2e68..40efc67572 100644 --- a/libraries/render-utils/src/MeshPartPayload.h +++ b/libraries/render-utils/src/MeshPartPayload.h @@ -33,7 +33,7 @@ public: typedef render::Payload Payload; typedef Payload::DataPointer Pointer; - virtual void updateKey(bool isVisible, bool isLayered, uint8_t tagBits); + virtual void updateKey(bool isVisible, bool isLayered, uint8_t tagBits, bool isGroupCulled = false); virtual void updateMeshPart(const std::shared_ptr& drawMesh, int partIndex); @@ -99,7 +99,7 @@ public: using TransformType = glm::mat4; #endif - void updateKey(bool isVisible, bool isLayered, uint8_t tagBits) override; + void updateKey(bool isVisible, bool isLayered, uint8_t tagBits, bool isGroupCulled = false) override; void updateClusterBuffer(const std::vector& clusterTransforms); void updateTransformForSkinnedMesh(const Transform& renderTransform, const Transform& boundTransform); diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index b9ccc28c01..bb8353c746 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -271,6 +271,7 @@ void Model::updateRenderItems() { uint8_t viewTagBits = self->getViewTagBits(); bool isLayeredInFront = self->isLayeredInFront(); bool isLayeredInHUD = self->isLayeredInHUD(); + bool isGroupCulled = self->isGroupCulled(); render::Transaction transaction; for (int i = 0; i < (int) self->_modelMeshRenderItemIDs.size(); i++) { @@ -284,7 +285,7 @@ void Model::updateRenderItems() { transaction.updateItem(itemID, [modelTransform, clusterTransforms, invalidatePayloadShapeKey, isWireframe, isVisible, viewTagBits, isLayeredInFront, - isLayeredInHUD](ModelMeshPartPayload& data) { + isLayeredInHUD, isGroupCulled](ModelMeshPartPayload& data) { data.updateClusterBuffer(clusterTransforms); Transform renderTransform = modelTransform; @@ -300,7 +301,7 @@ void Model::updateRenderItems() { } data.updateTransformForSkinnedMesh(renderTransform, modelTransform); - data.updateKey(isVisible, isLayeredInFront || isLayeredInHUD, viewTagBits); + data.updateKey(isVisible, isLayeredInFront || isLayeredInHUD, viewTagBits, isGroupCulled); data.setLayer(isLayeredInFront, isLayeredInHUD); data.setShapeKey(invalidatePayloadShapeKey, isWireframe); }); @@ -684,10 +685,11 @@ void Model::calculateTriangleSets() { } } -void Model::setVisibleInScene(bool isVisible, const render::ScenePointer& scene, uint8_t viewTagBits) { - if (_isVisible != isVisible || _viewTagBits != viewTagBits) { +void Model::setVisibleInScene(bool isVisible, const render::ScenePointer& scene, uint8_t viewTagBits, bool isGroupCulled) { + if (_isVisible != isVisible || _viewTagBits != viewTagBits || _isGroupCulled != isGroupCulled) { _isVisible = isVisible; _viewTagBits = viewTagBits; + _isGroupCulled = isGroupCulled; bool isLayeredInFront = _isLayeredInFront; bool isLayeredInHUD = _isLayeredInHUD; @@ -695,14 +697,14 @@ void Model::setVisibleInScene(bool isVisible, const render::ScenePointer& scene, render::Transaction transaction; foreach (auto item, _modelMeshRenderItemsMap.keys()) { transaction.updateItem(item, [isVisible, viewTagBits, isLayeredInFront, - isLayeredInHUD](ModelMeshPartPayload& data) { - data.updateKey(isVisible, isLayeredInFront || isLayeredInHUD, viewTagBits); + isLayeredInHUD, isGroupCulled](ModelMeshPartPayload& data) { + data.updateKey(isVisible, isLayeredInFront || isLayeredInHUD, viewTagBits, isGroupCulled); }); } foreach(auto item, _collisionRenderItemsMap.keys()) { transaction.updateItem(item, [isVisible, viewTagBits, isLayeredInFront, - isLayeredInHUD](ModelMeshPartPayload& data) { - data.updateKey(isVisible, isLayeredInFront || isLayeredInHUD, viewTagBits); + isLayeredInHUD, isGroupCulled](ModelMeshPartPayload& data) { + data.updateKey(isVisible, isLayeredInFront || isLayeredInHUD, viewTagBits, isGroupCulled); }); } scene->enqueueTransaction(transaction); @@ -717,19 +719,20 @@ void Model::setLayeredInFront(bool isLayeredInFront, const render::ScenePointer& bool isVisible = _isVisible; uint8_t viewTagBits = _viewTagBits; bool isLayeredInHUD = _isLayeredInHUD; + bool isGroupCulled = _isGroupCulled; render::Transaction transaction; foreach(auto item, _modelMeshRenderItemsMap.keys()) { transaction.updateItem(item, [isVisible, viewTagBits, isLayeredInFront, - isLayeredInHUD](ModelMeshPartPayload& data) { - data.updateKey(isVisible, isLayeredInFront || isLayeredInHUD, viewTagBits); + isLayeredInHUD, isGroupCulled](ModelMeshPartPayload& data) { + data.updateKey(isVisible, isLayeredInFront || isLayeredInHUD, viewTagBits, isGroupCulled); data.setLayer(isLayeredInFront, isLayeredInHUD); }); } foreach(auto item, _collisionRenderItemsMap.keys()) { transaction.updateItem(item, [isVisible, viewTagBits, isLayeredInFront, - isLayeredInHUD](ModelMeshPartPayload& data) { - data.updateKey(isVisible, isLayeredInFront || isLayeredInHUD, viewTagBits); + isLayeredInHUD, isGroupCulled](ModelMeshPartPayload& data) { + data.updateKey(isVisible, isLayeredInFront || isLayeredInHUD, viewTagBits, isGroupCulled); data.setLayer(isLayeredInFront, isLayeredInHUD); }); } @@ -744,19 +747,20 @@ void Model::setLayeredInHUD(bool isLayeredInHUD, const render::ScenePointer& sce bool isVisible = _isVisible; uint8_t viewTagBits = _viewTagBits; bool isLayeredInFront = _isLayeredInFront; + bool isGroupCulled = _isGroupCulled; render::Transaction transaction; foreach(auto item, _modelMeshRenderItemsMap.keys()) { transaction.updateItem(item, [isVisible, viewTagBits, isLayeredInFront, - isLayeredInHUD](ModelMeshPartPayload& data) { - data.updateKey(isVisible, isLayeredInFront || isLayeredInHUD, viewTagBits); + isLayeredInHUD, isGroupCulled](ModelMeshPartPayload& data) { + data.updateKey(isVisible, isLayeredInFront || isLayeredInHUD, viewTagBits, isGroupCulled); data.setLayer(isLayeredInFront, isLayeredInHUD); }); } foreach(auto item, _collisionRenderItemsMap.keys()) { transaction.updateItem(item, [isVisible, viewTagBits, isLayeredInFront, - isLayeredInHUD](ModelMeshPartPayload& data) { - data.updateKey(isVisible, isLayeredInFront || isLayeredInHUD, viewTagBits); + isLayeredInHUD, isGroupCulled](ModelMeshPartPayload& data) { + data.updateKey(isVisible, isLayeredInFront || isLayeredInHUD, viewTagBits, isGroupCulled); data.setLayer(isLayeredInFront, isLayeredInHUD); }); } diff --git a/libraries/render-utils/src/Model.h b/libraries/render-utils/src/Model.h index ca0904f334..57d2798a66 100644 --- a/libraries/render-utils/src/Model.h +++ b/libraries/render-utils/src/Model.h @@ -86,7 +86,7 @@ public: const QUrl& getURL() const { return _url; } // new Scene/Engine rendering support - void setVisibleInScene(bool isVisible, const render::ScenePointer& scene, uint8_t viewTagBits); + void setVisibleInScene(bool isVisible, const render::ScenePointer& scene, uint8_t viewTagBits, bool isGroupCulled); void setLayeredInFront(bool isLayeredInFront, const render::ScenePointer& scene); void setLayeredInHUD(bool isLayeredInHUD, const render::ScenePointer& scene); bool needsFixupInScene() const; @@ -109,6 +109,8 @@ public: bool isLayeredInFront() const { return _isLayeredInFront; } bool isLayeredInHUD() const { return _isLayeredInHUD; } + bool isGroupCulled() const { return _isGroupCulled; } + virtual void updateRenderItems(); void setRenderItemsNeedUpdate(); bool getRenderItemsNeedUpdate() { return _renderItemsNeedUpdate; } @@ -462,6 +464,8 @@ protected: bool _isLayeredInFront { false }; bool _isLayeredInHUD { false }; + bool _isGroupCulled{ false }; + bool shouldInvalidatePayloadShapeKey(int meshIndex); private: diff --git a/libraries/render/src/render/CullTask.cpp b/libraries/render/src/render/CullTask.cpp index 467cf5adbf..b5ee251b5d 100644 --- a/libraries/render/src/render/CullTask.cpp +++ b/libraries/render/src/render/CullTask.cpp @@ -211,13 +211,14 @@ void CullSpatialSelection::run(const RenderContextPointer& renderContext, outItems.clear(); outItems.reserve(inSelection.numItems()); - const auto filter = inputs.get1(); - if (!filter.selectsNothing()) { - // Now get the bound, and + const auto srcFilter = inputs.get1(); + if (!srcFilter.selectsNothing()) { + auto filter = render::ItemFilter::Builder(srcFilter).withoutMetaCulled().build(); + + // Now get the bound, and // filter individually against the _filter // visibility cull if partially selected ( octree cell contianing it was partial) // distance cull if was a subcell item ( octree cell is way bigger than the item bound itself, so now need to test per item) - if (_skipCulling) { // inside & fit items: filter only, culling is disabled { diff --git a/libraries/render/src/render/Item.cpp b/libraries/render/src/render/Item.cpp index b8ca091c40..ed052adf6e 100644 --- a/libraries/render/src/render/Item.cpp +++ b/libraries/render/src/render/Item.cpp @@ -118,7 +118,12 @@ uint32_t Item::fetchMetaSubItemBounds(ItemBounds& subItemBounds, Scene& scene) c auto numSubs = fetchMetaSubItems(subItems); for (auto id : subItems) { - subItemBounds.emplace_back(id, scene.getItem(id).getBound()); + auto& item = scene.getItem(id); + if (item.exist()) { + subItemBounds.emplace_back(id, item.getBound()); + } else { + numSubs--; + } } return numSubs; } diff --git a/libraries/render/src/render/Item.h b/libraries/render/src/render/Item.h index 3c82e49e8d..87ad25f4b2 100644 --- a/libraries/render/src/render/Item.h +++ b/libraries/render/src/render/Item.h @@ -125,6 +125,7 @@ public: Builder& withShadowCaster() { _flags.set(SHADOW_CASTER); return (*this); } Builder& withLayered() { _flags.set(LAYERED); return (*this); } Builder& withCullGroup() { _flags.set(CULL_GROUP); return (*this); } + Builder& withMetaCulled() { _flags.set(__META_CULLED); return (*this); } Builder& withTag(Tag tag) { _flags.set(FIRST_TAG_BIT + tag); return (*this); } // Set ALL the tags in one call using the Tag bits @@ -135,9 +136,6 @@ public: static Builder transparentShape() { return Builder().withTypeShape().withTransparent(); } static Builder light() { return Builder().withTypeLight(); } static Builder background() { return Builder().withViewSpace().withLayered(); } - - // Not meant to be public, part of the inner item / key / filter system - Builder& __withMetaCulled() { _flags.set(__META_CULLED); return (*this); } }; ItemKey(const Builder& builder) : ItemKey(builder._flags) {} @@ -205,6 +203,7 @@ public: ItemKey::Flags _mask{ 0 }; public: Builder() {} + Builder(const ItemFilter& srcFilter) : _value(srcFilter._value), _mask(srcFilter._mask) {} ItemFilter build() const { return ItemFilter(_value, _mask); } diff --git a/libraries/render/src/render/RenderFetchCullSortTask.cpp b/libraries/render/src/render/RenderFetchCullSortTask.cpp index 4fe5e2f1be..14a5a632ad 100644 --- a/libraries/render/src/render/RenderFetchCullSortTask.cpp +++ b/libraries/render/src/render/RenderFetchCullSortTask.cpp @@ -22,7 +22,7 @@ void RenderFetchCullSortTask::build(JobModel& task, const Varying& input, Varyin // CPU jobs: // Fetch and cull the items from the scene - const ItemFilter filter = ItemFilter::Builder::visibleWorldItems().withoutLayered().withoutMetaCulled().withTagBits(tagBits, tagMask); + const ItemFilter filter = ItemFilter::Builder::visibleWorldItems().withoutLayered().withTagBits(tagBits, tagMask); const auto spatialFilter = render::Varying(filter); const auto fetchInput = FetchSpatialTree::Inputs(filter, glm::ivec2(0,0)).asVarying(); const auto spatialSelection = task.addJob("FetchSceneSelection", fetchInput); From 6357f36a97b4475c0d87fe69853f4d34837f26a1 Mon Sep 17 00:00:00 2001 From: samcake Date: Wed, 14 Feb 2018 10:39:14 -0800 Subject: [PATCH 26/45] Fixing names --- .../src/avatars-renderer/Avatar.cpp | 2 +- .../render-utils/src/MeshPartPayload.cpp | 4 +-- libraries/render/src/render/CullTask.cpp | 18 ++++++------- libraries/render/src/render/Item.h | 26 +++++++++---------- .../src/render/RenderFetchCullSortTask.cpp | 2 +- 5 files changed, 26 insertions(+), 26 deletions(-) diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp index 9cdd4e2d00..a2a23f2508 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp @@ -50,7 +50,7 @@ const glm::vec3 HAND_TO_PALM_OFFSET(0.0f, 0.12f, 0.08f); namespace render { template <> const ItemKey payloadGetKey(const AvatarSharedPointer& avatar) { - return ItemKey::Builder::opaqueShape().withTypeMeta().withTagBits(ItemKey::TAG_BITS_0 | ItemKey::TAG_BITS_1).withCullGroup(); + return ItemKey::Builder::opaqueShape().withTypeMeta().withTagBits(ItemKey::TAG_BITS_0 | ItemKey::TAG_BITS_1).withMetaCullGroup(); } template <> const Item::Bound payloadGetBound(const AvatarSharedPointer& avatar) { return static_pointer_cast(avatar)->getBounds(); diff --git a/libraries/render-utils/src/MeshPartPayload.cpp b/libraries/render-utils/src/MeshPartPayload.cpp index e3d4818ec5..da11535396 100644 --- a/libraries/render-utils/src/MeshPartPayload.cpp +++ b/libraries/render-utils/src/MeshPartPayload.cpp @@ -86,7 +86,7 @@ void MeshPartPayload::updateKey(bool isVisible, bool isLayered, uint8_t tagBits, } if (isGroupCulled) { - builder.withMetaCulled(); + builder.withSubMetaCulled(); } if (_drawMaterial) { @@ -422,7 +422,7 @@ void ModelMeshPartPayload::updateKey(bool isVisible, bool isLayered, uint8_t tag } if (isGroupCulled) { - builder.withMetaCulled(); + builder.withSubMetaCulled(); } if (_isBlendShaped || _isSkinned) { diff --git a/libraries/render/src/render/CullTask.cpp b/libraries/render/src/render/CullTask.cpp index b5ee251b5d..f04427540a 100644 --- a/libraries/render/src/render/CullTask.cpp +++ b/libraries/render/src/render/CullTask.cpp @@ -213,7 +213,7 @@ void CullSpatialSelection::run(const RenderContextPointer& renderContext, const auto srcFilter = inputs.get1(); if (!srcFilter.selectsNothing()) { - auto filter = render::ItemFilter::Builder(srcFilter).withoutMetaCulled().build(); + auto filter = render::ItemFilter::Builder(srcFilter).withoutSubMetaCulled().build(); // Now get the bound, and // filter individually against the _filter @@ -228,7 +228,7 @@ void CullSpatialSelection::run(const RenderContextPointer& renderContext, if (filter.test(item.getKey())) { ItemBound itemBound(id, item.getBound()); outItems.emplace_back(itemBound); - if (item.getKey().isCullGroup()) { + if (item.getKey().isMetaCullGroup()) { item.fetchMetaSubItemBounds(outItems, (*scene)); } } @@ -243,7 +243,7 @@ void CullSpatialSelection::run(const RenderContextPointer& renderContext, if (filter.test(item.getKey())) { ItemBound itemBound(id, item.getBound()); outItems.emplace_back(itemBound); - if (item.getKey().isCullGroup()) { + if (item.getKey().isMetaCullGroup()) { item.fetchMetaSubItemBounds(outItems, (*scene)); } } @@ -258,7 +258,7 @@ void CullSpatialSelection::run(const RenderContextPointer& renderContext, if (filter.test(item.getKey())) { ItemBound itemBound(id, item.getBound()); outItems.emplace_back(itemBound); - if (item.getKey().isCullGroup()) { + if (item.getKey().isMetaCullGroup()) { item.fetchMetaSubItemBounds(outItems, (*scene)); } } @@ -273,7 +273,7 @@ void CullSpatialSelection::run(const RenderContextPointer& renderContext, if (filter.test(item.getKey())) { ItemBound itemBound(id, item.getBound()); outItems.emplace_back(itemBound); - if (item.getKey().isCullGroup()) { + if (item.getKey().isMetaCullGroup()) { item.fetchMetaSubItemBounds(outItems, (*scene)); } } @@ -290,7 +290,7 @@ void CullSpatialSelection::run(const RenderContextPointer& renderContext, if (filter.test(item.getKey())) { ItemBound itemBound(id, item.getBound()); outItems.emplace_back(itemBound); - if (item.getKey().isCullGroup()) { + if (item.getKey().isMetaCullGroup()) { item.fetchMetaSubItemBounds(outItems, (*scene)); } } @@ -306,7 +306,7 @@ void CullSpatialSelection::run(const RenderContextPointer& renderContext, ItemBound itemBound(id, item.getBound()); if (test.solidAngleTest(itemBound.bound)) { outItems.emplace_back(itemBound); - if (item.getKey().isCullGroup()) { + if (item.getKey().isMetaCullGroup()) { item.fetchMetaSubItemBounds(outItems, (*scene)); } } @@ -323,7 +323,7 @@ void CullSpatialSelection::run(const RenderContextPointer& renderContext, ItemBound itemBound(id, item.getBound()); if (test.frustumTest(itemBound.bound)) { outItems.emplace_back(itemBound); - if (item.getKey().isCullGroup()) { + if (item.getKey().isMetaCullGroup()) { item.fetchMetaSubItemBounds(outItems, (*scene)); } } @@ -341,7 +341,7 @@ void CullSpatialSelection::run(const RenderContextPointer& renderContext, if (test.frustumTest(itemBound.bound)) { if (test.solidAngleTest(itemBound.bound)) { outItems.emplace_back(itemBound); - if (item.getKey().isCullGroup()) { + if (item.getKey().isMetaCullGroup()) { item.fetchMetaSubItemBounds(outItems, (*scene)); } } diff --git a/libraries/render/src/render/Item.h b/libraries/render/src/render/Item.h index 87ad25f4b2..e4dcc7ee03 100644 --- a/libraries/render/src/render/Item.h +++ b/libraries/render/src/render/Item.h @@ -78,13 +78,13 @@ public: INVISIBLE, // Visible or not in the scene? SHADOW_CASTER, // Item cast shadows LAYERED, // Item belongs to one of the layers different from the default layer - CULL_GROUP, // As a meta item, the culling of my sub items is based solely on my bounding box and my visibility in the + META_CULL_GROUP, // As a meta item, the culling of my sub items is based solely on my bounding box and my visibility in the view + SUB_META_CULLED, // As a sub item of a meta render item set as cull group, need to be set to my culling to the meta render it FIRST_TAG_BIT, // 8 Tags available to organize the items and filter them against LAST_TAG_BIT = FIRST_TAG_BIT + NUM_TAGS, __SMALLER, // Reserved bit for spatialized item to indicate that it is smaller than expected in the cell in which it belongs (probably because it overlaps over several smaller cells) - __META_CULLED, // Reserved bit for sub item of a meta render item to indicate that the culling is tied to the culling of the meta render item, not this one NUM_FLAGS, // Not a valid flag }; @@ -124,8 +124,8 @@ public: Builder& withInvisible() { _flags.set(INVISIBLE); return (*this); } Builder& withShadowCaster() { _flags.set(SHADOW_CASTER); return (*this); } Builder& withLayered() { _flags.set(LAYERED); return (*this); } - Builder& withCullGroup() { _flags.set(CULL_GROUP); return (*this); } - Builder& withMetaCulled() { _flags.set(__META_CULLED); return (*this); } + Builder& withMetaCullGroup() { _flags.set(META_CULL_GROUP); return (*this); } + Builder& withSubMetaCulled() { _flags.set(SUB_META_CULLED); return (*this); } Builder& withTag(Tag tag) { _flags.set(FIRST_TAG_BIT + tag); return (*this); } // Set ALL the tags in one call using the Tag bits @@ -163,8 +163,11 @@ public: bool isLayered() const { return _flags[LAYERED]; } bool isSpatial() const { return !isLayered(); } - bool isCullGroup() const { return _flags[CULL_GROUP]; } - void setCullGroup(bool cullGroup) { (cullGroup ? _flags.set(CULL_GROUP) : _flags.reset(CULL_GROUP)); } + bool isMetaCullGroup() const { return _flags[META_CULL_GROUP]; } + void setMetaCullGroup(bool cullGroup) { (cullGroup ? _flags.set(META_CULL_GROUP) : _flags.reset(META_CULL_GROUP)); } + + bool isSubMetaCulled() const { return _flags[SUB_META_CULLED]; } + void setSubMetaCulled(bool metaCulled) { (metaCulled ? _flags.set(SUB_META_CULLED) : _flags.reset(SUB_META_CULLED)); } bool isTag(Tag tag) const { return _flags[FIRST_TAG_BIT + tag]; } uint8_t getTagBits() const { return ((_flags.to_ulong() & KEY_TAG_BITS_MASK) >> FIRST_TAG_BIT); } @@ -173,9 +176,6 @@ public: bool isSmall() const { return _flags[__SMALLER]; } void setSmaller(bool smaller) { (smaller ? _flags.set(__SMALLER) : _flags.reset(__SMALLER)); } - bool isMetaCulled() const { return _flags[__META_CULLED]; } - void setMetaCulled(bool metaCulled) { (metaCulled ? _flags.set(__META_CULLED) : _flags.reset(__META_CULLED)); } - bool operator==(const ItemKey& key) { return (_flags == key._flags); } bool operator!=(const ItemKey& key) { return (_flags != key._flags); } }; @@ -232,11 +232,11 @@ public: Builder& withoutLayered() { _value.reset(ItemKey::LAYERED); _mask.set(ItemKey::LAYERED); return (*this); } Builder& withLayered() { _value.set(ItemKey::LAYERED); _mask.set(ItemKey::LAYERED); return (*this); } - Builder& withoutCullGroup() { _value.reset(ItemKey::CULL_GROUP); _mask.set(ItemKey::CULL_GROUP); return (*this); } - Builder& withCullGroup() { _value.set(ItemKey::CULL_GROUP); _mask.set(ItemKey::CULL_GROUP); return (*this); } + Builder& withoutMetaCullGroup() { _value.reset(ItemKey::META_CULL_GROUP); _mask.set(ItemKey::META_CULL_GROUP); return (*this); } + Builder& withMetaCullGroup() { _value.set(ItemKey::META_CULL_GROUP); _mask.set(ItemKey::META_CULL_GROUP); return (*this); } - Builder& withoutMetaCulled() { _value.reset(ItemKey::__META_CULLED); _mask.set(ItemKey::__META_CULLED); return (*this); } - Builder& withMetaCulled() { _value.set(ItemKey::__META_CULLED); _mask.set(ItemKey::__META_CULLED); return (*this); } + Builder& withoutSubMetaCulled() { _value.reset(ItemKey::SUB_META_CULLED); _mask.set(ItemKey::SUB_META_CULLED); return (*this); } + Builder& withSubMetaCulled() { _value.set(ItemKey::SUB_META_CULLED); _mask.set(ItemKey::SUB_META_CULLED); return (*this); } Builder& withoutTag(ItemKey::Tag tagIndex) { _value.reset(ItemKey::FIRST_TAG_BIT + tagIndex); _mask.set(ItemKey::FIRST_TAG_BIT + tagIndex); return (*this); } Builder& withTag(ItemKey::Tag tagIndex) { _value.set(ItemKey::FIRST_TAG_BIT + tagIndex); _mask.set(ItemKey::FIRST_TAG_BIT + tagIndex); return (*this); } diff --git a/libraries/render/src/render/RenderFetchCullSortTask.cpp b/libraries/render/src/render/RenderFetchCullSortTask.cpp index 14a5a632ad..7b9765dca1 100644 --- a/libraries/render/src/render/RenderFetchCullSortTask.cpp +++ b/libraries/render/src/render/RenderFetchCullSortTask.cpp @@ -30,7 +30,7 @@ void RenderFetchCullSortTask::build(JobModel& task, const Varying& input, Varyin const auto culledSpatialSelection = task.addJob("CullSceneSelection", cullInputs, cullFunctor, RenderDetails::ITEM); // Overlays are not culled - const ItemFilter overlayfilter = ItemFilter::Builder().withVisible().withoutMetaCulled().withTagBits(tagBits, tagMask); + const ItemFilter overlayfilter = ItemFilter::Builder().withVisible().withoutSubMetaCulled().withTagBits(tagBits, tagMask); const auto nonspatialFilter = render::Varying(overlayfilter); const auto nonspatialSelection = task.addJob("FetchOverlaySelection", nonspatialFilter); From 8853bd63884450383ed3a5aeed6f310c3d54ca49 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Wed, 14 Feb 2018 11:11:28 -0800 Subject: [PATCH 27/45] Fix spectator camera crash --- interface/src/Application.cpp | 1 + .../display-plugins/OpenGLDisplayPlugin.cpp | 43 +++++++++++-------- .../src/display-plugins/OpenGLDisplayPlugin.h | 2 +- libraries/gl/src/gl/OffscreenGLCanvas.cpp | 27 ++++++++++++ libraries/gl/src/gl/OffscreenGLCanvas.h | 3 ++ .../qml/src/qml/impl/RenderEventHandler.cpp | 1 + plugins/openvr/src/OpenVrDisplayPlugin.cpp | 2 +- 7 files changed, 59 insertions(+), 20 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 0eb36173c2..5a340f471e 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -2338,6 +2338,7 @@ void Application::initializeGL() { qFatal("Unable to make offscreen context current"); } _offscreenContext->doneCurrent(); + _offscreenContext->setThreadContext(); _renderEventHandler = new RenderEventHandler(_glWidget->qglContext()); // The UI can't be created until the primary OpenGL diff --git a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp index 40dcf1b8c7..9bd7d89634 100644 --- a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include #include @@ -130,14 +131,14 @@ public: CHECK_GL_ERROR(); _context->doneCurrent(); while (!_shutdown) { - if (_pendingMainThreadOperation) { + if (_pendingOtherThreadOperation) { PROFILE_RANGE(render, "MainThreadOp") { Lock lock(_mutex); _context->doneCurrent(); // Move the context to the main thread - _context->moveToThread(qApp->thread()); - _pendingMainThreadOperation = false; + _context->moveToThread(_targetOperationThread); + _pendingOtherThreadOperation = false; // Release the main thread to do it's action _condition.notify_one(); } @@ -146,7 +147,7 @@ public: { // Main thread does it's thing while we wait on the lock to release Lock lock(_mutex); - _condition.wait(lock, [&] { return _finishedMainThreadOperation; }); + _condition.wait(lock, [&] { return _finishedOtherThreadOperation; }); } } @@ -214,23 +215,25 @@ public: _condition.notify_one(); } - void withMainThreadContext(std::function f) { + void withOtherThreadContext(std::function f) { // Signal to the thread that there is work to be done on the main thread Lock lock(_mutex); - _pendingMainThreadOperation = true; - _finishedMainThreadOperation = false; - _condition.wait(lock, [&] { return !_pendingMainThreadOperation; }); + _targetOperationThread = QThread::currentThread(); + _pendingOtherThreadOperation = true; + _finishedOtherThreadOperation = false; + _condition.wait(lock, [&] { return !_pendingOtherThreadOperation; }); _context->makeCurrent(); f(); _context->doneCurrent(); + _targetOperationThread = nullptr; // Move the context back to the presentation thread _context->moveToThread(this); // restore control of the context to the presentation thread and signal // the end of the operation - _finishedMainThreadOperation = true; + _finishedOtherThreadOperation = true; lock.unlock(); _condition.notify_one(); } @@ -244,9 +247,11 @@ private: Mutex _mutex; // Used to allow the main thread to perform context operations Condition _condition; - bool _pendingMainThreadOperation { false }; - bool _finishedMainThreadOperation { false }; - QThread* _mainThread { nullptr }; + + + QThread* _targetOperationThread { nullptr }; + bool _pendingOtherThreadOperation { false }; + bool _finishedOtherThreadOperation { false }; std::queue _newPluginQueue; gl::Context* _context { nullptr }; }; @@ -744,10 +749,12 @@ void OpenGLDisplayPlugin::swapBuffers() { context->swapBuffers(); } -void OpenGLDisplayPlugin::withMainThreadContext(std::function f) const { +void OpenGLDisplayPlugin::withOtherThreadContext(std::function f) const { static auto presentThread = DependencyManager::get(); - presentThread->withMainThreadContext(f); - _container->makeRenderingContextCurrent(); + presentThread->withOtherThreadContext(f); + if (!OffscreenGLCanvas::restoreThreadContext()) { + qWarning("Unable to restore original OpenGL context"); + } } bool OpenGLDisplayPlugin::setDisplayTexture(const QString& name) { @@ -784,7 +791,7 @@ QImage OpenGLDisplayPlugin::getScreenshot(float aspectRatio) const { } auto glBackend = const_cast(*this).getGLBackend(); QImage screenshot(bestSize.x, bestSize.y, QImage::Format_ARGB32); - withMainThreadContext([&] { + withOtherThreadContext([&] { glBackend->downloadFramebuffer(_compositeFramebuffer, ivec4(corner, bestSize), screenshot); }); return screenshot.mirrored(false, true); @@ -797,7 +804,7 @@ QImage OpenGLDisplayPlugin::getSecondaryCameraScreenshot() const { auto glBackend = const_cast(*this).getGLBackend(); QImage screenshot(region.z, region.w, QImage::Format_ARGB32); - withMainThreadContext([&] { + withOtherThreadContext([&] { glBackend->downloadFramebuffer(secondaryCameraFramebuffer, region, screenshot); }); return screenshot.mirrored(false, true); @@ -886,7 +893,7 @@ void OpenGLDisplayPlugin::updateCompositeFramebuffer() { void OpenGLDisplayPlugin::copyTextureToQuickFramebuffer(NetworkTexturePointer networkTexture, QOpenGLFramebufferObject* target, GLsync* fenceSync) { #if !defined(USE_GLES) auto glBackend = const_cast(*this).getGLBackend(); - withMainThreadContext([&] { + withOtherThreadContext([&] { GLuint sourceTexture = glBackend->getTextureID(networkTexture->getGPUTexture()); GLuint targetTexture = target->texture(); GLuint fbo[2] {0, 0}; diff --git a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h index 1176471b40..bf06486095 100644 --- a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h @@ -119,7 +119,7 @@ protected: void renderFromTexture(gpu::Batch& batch, const gpu::TexturePointer texture, glm::ivec4 viewport, const glm::ivec4 scissor); virtual void updateFrameData(); - void withMainThreadContext(std::function f) const; + void withOtherThreadContext(std::function f) const; void present(); virtual void swapBuffers(); diff --git a/libraries/gl/src/gl/OffscreenGLCanvas.cpp b/libraries/gl/src/gl/OffscreenGLCanvas.cpp index 380ba085cc..1bde9e289e 100644 --- a/libraries/gl/src/gl/OffscreenGLCanvas.cpp +++ b/libraries/gl/src/gl/OffscreenGLCanvas.cpp @@ -16,6 +16,7 @@ #include #include +#include #include #include #include @@ -119,3 +120,29 @@ void OffscreenGLCanvas::moveToThreadWithContext(QThread* thread) { moveToThread(thread); _context->moveToThread(thread); } + +static const char* THREAD_CONTEXT_PROPERTY = "offscreenGlCanvas"; + +void OffscreenGLCanvas::setThreadContext() { + QThread::currentThread()->setProperty(THREAD_CONTEXT_PROPERTY, QVariant::fromValue(this)); +} + +bool OffscreenGLCanvas::restoreThreadContext() { + // Restore the rendering context for this thread + auto threadCanvasVariant = QThread::currentThread()->property(THREAD_CONTEXT_PROPERTY); + if (!threadCanvasVariant.isValid()) { + return false; + } + + auto threadCanvasObject = qvariant_cast(threadCanvasVariant); + auto threadCanvas = static_cast(threadCanvasObject); + if (!threadCanvas) { + return false; + } + + if (!threadCanvas->makeCurrent()) { + qFatal("Unable to restore Offscreen rendering context"); + } + + return true; +} diff --git a/libraries/gl/src/gl/OffscreenGLCanvas.h b/libraries/gl/src/gl/OffscreenGLCanvas.h index be0e0d9678..ed644b98fb 100644 --- a/libraries/gl/src/gl/OffscreenGLCanvas.h +++ b/libraries/gl/src/gl/OffscreenGLCanvas.h @@ -32,6 +32,9 @@ public: } QObject* getContextObject(); + void setThreadContext(); + static bool restoreThreadContext(); + private slots: void onMessageLogged(const QOpenGLDebugMessage &debugMessage); diff --git a/libraries/qml/src/qml/impl/RenderEventHandler.cpp b/libraries/qml/src/qml/impl/RenderEventHandler.cpp index cce1b68da9..6b66ce9314 100644 --- a/libraries/qml/src/qml/impl/RenderEventHandler.cpp +++ b/libraries/qml/src/qml/impl/RenderEventHandler.cpp @@ -58,6 +58,7 @@ void RenderEventHandler::onInitalize() { return; } + _canvas.setThreadContext(); if (!_canvas.makeCurrent()) { qFatal("Unable to make QML rendering context current on render thread"); } diff --git a/plugins/openvr/src/OpenVrDisplayPlugin.cpp b/plugins/openvr/src/OpenVrDisplayPlugin.cpp index 4b5f0e6517..2949e72c74 100644 --- a/plugins/openvr/src/OpenVrDisplayPlugin.cpp +++ b/plugins/openvr/src/OpenVrDisplayPlugin.cpp @@ -485,7 +485,7 @@ bool OpenVrDisplayPlugin::internalActivate() { if (_threadedSubmit) { _submitThread = std::make_shared(*this); if (!_submitCanvas) { - withMainThreadContext([&] { + withOtherThreadContext([&] { _submitCanvas = std::make_shared(); _submitCanvas->create(); _submitCanvas->doneCurrent(); From 624018147494aabe6a234af110985719e4b8eb88 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Tue, 30 Jan 2018 15:37:10 -0800 Subject: [PATCH 28/45] initial separation of content settings and domain settings --- .../resources/describe-settings.json | 4 + .../resources/web/base-settings-scripts.html | 7 + .../resources/web/base-settings.html | 51 + .../resources/web/content/index.shtml | 52 +- .../resources/web/content/js/content.js | 2 + domain-server/resources/web/css/style.css | 80 + domain-server/resources/web/header.html | 45 +- .../resources/web/images/hmd-w-eyes.svg | 10 + .../resources/web/js/base-settings.js | 1140 +++++++ .../{settings => }/js/bootstrap-switch.min.js | 0 .../resources/web/js/domain-server.js | 41 +- .../web/{settings => }/js/form2js.min.js | 0 domain-server/resources/web/js/shared.js | 6 +- .../resources/web/js/underscore-min.js | 1 - .../resources/web/settings/index.shtml | 111 +- .../resources/web/settings/js/settings.js | 2955 +++++------------ domain-server/src/DomainServer.cpp | 2 +- .../src/DomainServerSettingsManager.cpp | 194 +- .../src/DomainServerSettingsManager.h | 19 +- .../embedded-webserver/src/HTTPManager.cpp | 2 +- 20 files changed, 2485 insertions(+), 2237 deletions(-) create mode 100644 domain-server/resources/web/base-settings-scripts.html create mode 100644 domain-server/resources/web/base-settings.html create mode 100644 domain-server/resources/web/images/hmd-w-eyes.svg create mode 100644 domain-server/resources/web/js/base-settings.js rename domain-server/resources/web/{settings => }/js/bootstrap-switch.min.js (100%) rename domain-server/resources/web/{settings => }/js/form2js.min.js (100%) diff --git a/domain-server/resources/describe-settings.json b/domain-server/resources/describe-settings.json index c6d118a87b..705d110542 100644 --- a/domain-server/resources/describe-settings.json +++ b/domain-server/resources/describe-settings.json @@ -935,6 +935,7 @@ "name": "persistent_scripts", "type": "table", "label": "Persistent Scripts", + "content_setting": true, "help": "Add the URLs for scripts that you would like to ensure are always running in your domain.", "can_add_new_rows": true, "columns": [ @@ -1105,6 +1106,7 @@ "label": "Zones", "help": "In this table you can define a set of zones in which you can specify various audio properties.", "numbered": false, + "content_setting": true, "can_add_new_rows": true, "key": { "name": "name", @@ -1155,6 +1157,7 @@ "type": "table", "label": "Attenuation Coefficients", "help": "In this table you can set custom attenuation coefficients between audio zones", + "content_setting": true, "numbered": true, "can_order": true, "can_add_new_rows": true, @@ -1185,6 +1188,7 @@ "label": "Reverb Settings", "help": "In this table you can set reverb levels for audio zones. For a medium-sized (e.g., 100 square meter) meeting room, try a decay time of around 1.5 seconds and a wet/dry mix of 25%. For an airplane hangar or cathedral, try a decay time of 4 seconds and a wet/dry mix of 50%.", "numbered": true, + "content_setting": true, "can_add_new_rows": true, "columns": [ { diff --git a/domain-server/resources/web/base-settings-scripts.html b/domain-server/resources/web/base-settings-scripts.html new file mode 100644 index 0000000000..fe370c4675 --- /dev/null +++ b/domain-server/resources/web/base-settings-scripts.html @@ -0,0 +1,7 @@ + + + + + + + diff --git a/domain-server/resources/web/base-settings.html b/domain-server/resources/web/base-settings.html new file mode 100644 index 0000000000..13787b5fbe --- /dev/null +++ b/domain-server/resources/web/base-settings.html @@ -0,0 +1,51 @@ +
+
+
+ +
+
+ +
+ +
+ + +
+ +
+
+
+
+
diff --git a/domain-server/resources/web/content/index.shtml b/domain-server/resources/web/content/index.shtml index 0e48c1eff8..9b507f7826 100644 --- a/domain-server/resources/web/content/index.shtml +++ b/domain-server/resources/web/content/index.shtml @@ -1,45 +1,19 @@ -
-
-
- -
-
+ -
-
-
-
-

Upload Entities File

-
-
-
-

- Upload an entities file (e.g.: models.json.gz) to replace the content of this domain.
- Note: Your domain's content will be replaced by the content you upload, but the backup files of your domain's content will not immediately be changed. -

-

If your domain has any content that you would like to re-use at a later date, save a manual backup of your models.json.gz file, which is usually stored at the following paths:

- -
C:/Users/[username]/AppData/Roaming/High Fidelity/assignment-client/entities/models.json.gz
- -
/Users/[username]/Library/Application Support/High Fidelity/assignment-client/entities/models.json.gz
- -
/home/[username]/.local/share/High Fidelity/assignment-client/entities/models.json.gz
-
- -
-
- -
-
-
-
-
+ - - + + + + + diff --git a/domain-server/resources/web/content/js/content.js b/domain-server/resources/web/content/js/content.js index 2e6e084164..7b2ed37b15 100644 --- a/domain-server/resources/web/content/js/content.js +++ b/domain-server/resources/web/content/js/content.js @@ -1,5 +1,7 @@ $(document).ready(function(){ + Settings.afterReloadActions = function() {}; + function showSpinnerAlert(title) { swal({ title: title, diff --git a/domain-server/resources/web/css/style.css b/domain-server/resources/web/css/style.css index 158008fc2b..22de75a778 100644 --- a/domain-server/resources/web/css/style.css +++ b/domain-server/resources/web/css/style.css @@ -18,6 +18,14 @@ body { margin-top: 70px; } +/* unfortunate hack so that anchors go to the right place with fixed navbar */ +:target:before { + content: " "; + display: block; + height: 70px; + margin-top: -70px; +} + [hidden] { display: none !important; } @@ -345,3 +353,75 @@ table .headers + .headers td { text-align: center; margin-top: 20px; } + +@media (min-width: 768px) { + ul.nav li.dropdown-on-hover:hover ul.dropdown-menu { + display: block; + } +} + +ul.nav li.dropdown ul.dropdown-menu { + padding: 0px 0px; +} + +ul.nav li.dropdown li a { + padding-top: 7px; + padding-bottom: 7px; +} + +ul.nav li.dropdown li a:hover { + color: white; + background-color: #337ab7; +} + +ul.nav li.dropdown ul.dropdown-menu .divider { + margin: 0px 0; +} + +#visit-domain-link { + background-color: transparent; +} + +.navbar-btn { + margin-left: 10px; +} + +#save-settings-xs-button { + float: right; + margin-right: 10px; +} + +#button-bars { + display: inline-block; + float: left; +} + +#hamburger-badge { + position: relative; + top: -2px; + float: left; + margin-right: 10px; + margin-left: 0px; +} + +#restart-server { + margin-left: 0px; +} + +#restart-server:hover { + text-decoration: none; +} + +.badge { + margin-left: 5px; +} + +.panel-title { + display: inline-block; +} + +#visit-hmd-icon { + width: 25px; + position: relative; + top: -1px; +} diff --git a/domain-server/resources/web/header.html b/domain-server/resources/web/header.html index 1e32e9f02f..aff82d557e 100644 --- a/domain-server/resources/web/header.html +++ b/domain-server/resources/web/header.html @@ -17,30 +17,47 @@
- diff --git a/domain-server/resources/web/images/hmd-w-eyes.svg b/domain-server/resources/web/images/hmd-w-eyes.svg new file mode 100644 index 0000000000..c100de2f4e --- /dev/null +++ b/domain-server/resources/web/images/hmd-w-eyes.svg @@ -0,0 +1,10 @@ + + + + + diff --git a/domain-server/resources/web/js/base-settings.js b/domain-server/resources/web/js/base-settings.js new file mode 100644 index 0000000000..5e32463d61 --- /dev/null +++ b/domain-server/resources/web/js/base-settings.js @@ -0,0 +1,1140 @@ +var DomainInfo = null; + +var viewHelpers = { + getFormGroup: function(keypath, setting, values, isAdvanced) { + form_group = "
"; + setting_value = _(values).valueForKeyPath(keypath); + + if (_.isUndefined(setting_value) || _.isNull(setting_value)) { + if (_.has(setting, 'default')) { + setting_value = setting.default; + } else { + setting_value = ""; + } + } + + label_class = 'control-label'; + + function common_attrs(extra_classes) { + extra_classes = (!_.isUndefined(extra_classes) ? extra_classes : ""); + return " class='" + (setting.type !== 'checkbox' ? 'form-control' : '') + + " " + Settings.TRIGGER_CHANGE_CLASS + " " + extra_classes + "' data-short-name='" + + setting.name + "' name='" + keypath + "' " + + "id='" + (!_.isUndefined(setting.html_id) ? setting.html_id : keypath) + "'"; + } + + if (setting.type === 'checkbox') { + if (setting.label) { + form_group += "" + } + + form_group += "
" + form_group += "" + + if (setting.help) { + form_group += "" + setting.help + ""; + } + + form_group += "
" + } else { + input_type = _.has(setting, 'type') ? setting.type : "text" + + if (setting.label) { + form_group += ""; + } + + if (input_type === 'table') { + form_group += makeTable(setting, keypath, setting_value) + } else { + if (input_type === 'select') { + form_group += "" + + form_group += "" + } else if (input_type === 'button') { + // Is this a button that should link to something directly? + // If so, we use an anchor tag instead of a button tag + + if (setting.href) { + form_group += "" + + setting.button_label + ""; + } else { + form_group += ""; + } + + } else { + + if (input_type == 'integer') { + input_type = "text" + } + + form_group += "" + } + + form_group += "" + setting.help + "" + } + } + + form_group += "
" + return form_group + } +} + +function reloadSettings(callback) { + $.getJSON(Settings.endpoint, function(data){ + _.extend(data, viewHelpers) + + $('#panels').html(Settings.panelsTemplate(data)) + + Settings.data = data; + Settings.initialValues = form2js('settings-form', ".", false, cleanupFormValues, true); + + Settings.afterReloadActions(); + + // setup any bootstrap switches + $('.toggle-checkbox').bootstrapSwitch(); + + $('[data-toggle="tooltip"]').tooltip(); + + // call the callback now that settings are loaded + callback(true); + }).fail(function() { + // call the failure object since settings load faild + callback(false) + }); +} + +function postSettings(jsonSettings) { + // POST the form JSON to the domain-server settings.json endpoint so the settings are saved + $.ajax(Settings.endpoint, { + data: JSON.stringify(jsonSettings), + contentType: 'application/json', + type: 'POST' + }).done(function(data){ + if (data.status == "success") { + if ($(".save-button").html() === SAVE_BUTTON_LABEL_RESTART) { + showRestartModal(); + } else { + location.reload(true); + } + } else { + showErrorMessage("Error", SETTINGS_ERROR_MESSAGE) + reloadSettings(); + } + }).fail(function(){ + showErrorMessage("Error", SETTINGS_ERROR_MESSAGE) + reloadSettings(); + }); +} + +$(document).ready(function(){ + + $('.save-button.navbar-btn').show(); + + $.ajaxSetup({ + timeout: 20000, + }); + + reloadSettings(function(success){ + if (success) { + // if there was a hash in the URL, jump to it now + if (location.hash) { + location.href = location.hash; + } + } else { + swal({ + title: '', + type: 'error', + text: Strings.LOADING_SETTINGS_ERROR + }); + } + }); + + $('#' + Settings.FORM_ID).on('click', '.' + Settings.ADD_ROW_BUTTON_CLASS, function(){ + addTableRow($(this).closest('tr')); + }); + + $('#' + Settings.FORM_ID).on('click', '.' + Settings.DEL_ROW_BUTTON_CLASS, function(){ + deleteTableRow($(this).closest('tr')); + }); + + $('#' + Settings.FORM_ID).on('click', '.' + Settings.ADD_CATEGORY_BUTTON_CLASS, function(){ + addTableCategory($(this).closest('tr')); + }); + + $('#' + Settings.FORM_ID).on('click', '.' + Settings.DEL_CATEGORY_BUTTON_CLASS, function(){ + deleteTableCategory($(this).closest('tr')); + }); + + $('#' + Settings.FORM_ID).on('click', '.' + Settings.TOGGLE_CATEGORY_COLUMN_CLASS, function(){ + toggleTableCategory($(this).closest('tr')); + }); + + $('#' + Settings.FORM_ID).on('click', '.' + Settings.MOVE_UP_BUTTON_CLASS, function(){ + moveTableRow($(this).closest('tr'), true); + }); + + $('#' + Settings.FORM_ID).on('click', '.' + Settings.MOVE_DOWN_BUTTON_CLASS, function(){ + moveTableRow($(this).closest('tr'), false); + }); + + $('#' + Settings.FORM_ID).on('keyup', function(e){ + var $target = $(e.target); + if (e.keyCode == 13) { + if ($target.is('table input')) { + // capture enter in table input + // if we have a sibling next to us that has an input, jump to it, otherwise check if we have a glyphicon for add to click + sibling = $target.parent('td').next(); + + if (sibling.hasClass(Settings.DATA_COL_CLASS)) { + // set focus to next input + sibling.find('input').focus(); + } else { + + // jump over the re-order row, if that's what we're on + if (sibling.hasClass(Settings.REORDER_BUTTONS_CLASS)) { + sibling = sibling.next(); + } + + // for tables with categories we add the entry and setup the new row on enter + if (sibling.find("." + Settings.ADD_CATEGORY_BUTTON_CLASS).length) { + sibling.find("." + Settings.ADD_CATEGORY_BUTTON_CLASS).click(); + + // set focus to the first input in the new row + $target.closest('table').find('tr.inputs input:first').focus(); + } + + var tableRows = sibling.parent(); + var tableBody = tableRows.parent(); + + // if theres no more siblings, we should jump to a new row + if (sibling.next().length == 0 && tableRows.nextAll().length == 1) { + tableBody.find("." + Settings.ADD_ROW_BUTTON_CLASS).click(); + } + } + + } else if ($target.is('input')) { + $target.change().blur(); + } + + e.preventDefault(); + } + }); + + $('#' + Settings.FORM_ID).on('keypress', function(e){ + if (e.keyCode == 13) { + + e.preventDefault(); + } + }); + + $('#' + Settings.FORM_ID).on('change keyup paste', '.' + Settings.TRIGGER_CHANGE_CLASS , function(){ + // this input was changed, add the changed data attribute to it + $(this).attr('data-changed', true); + + badgeForDifferences($(this)); + }); + + $('#' + Settings.FORM_ID).on('switchChange.bootstrapSwitch', 'input.toggle-checkbox', function(){ + // this checkbox was changed, add the changed data attribute to it + $(this).attr('data-changed', true); + + badgeForDifferences($(this)); + }); + + // Bootstrap switch in table + $('#' + Settings.FORM_ID).on('change', 'input.table-checkbox', function () { + // Bootstrap switches in table: set the changed data attribute for all rows in table. + var row = $(this).closest('tr'); + if (row.hasClass("value-row")) { // Don't set attribute on input row switches prior to it being added to table. + row.find('td.' + Settings.DATA_COL_CLASS + ' input').attr('data-changed', true); + updateDataChangedForSiblingRows(row, true); + badgeForDifferences($(this)); + } + }); + + $('#' + Settings.FORM_ID).on('change', 'input.table-time', function() { + // Bootstrap switches in table: set the changed data attribute for all rows in table. + var row = $(this).closest('tr'); + if (row.hasClass("value-row")) { // Don't set attribute on input row switches prior to it being added to table. + row.find('td.' + Settings.DATA_COL_CLASS + ' input').attr('data-changed', true); + updateDataChangedForSiblingRows(row, true); + badgeForDifferences($(this)); + } + }); + + $('#' + Settings.FORM_ID).on('change', 'select', function(){ + $("input[name='" + $(this).attr('data-hidden-input') + "']").val($(this).val()).change(); + }); + + var panelsSource = $('#panels-template').html(); + Settings.panelsTemplate = _.template(panelsSource); +}); + +function dynamicButton(button_id, text) { + return $(""); +} + +function showSpinnerAlert(title) { + swal({ + title: title, + text: '
', + html: true, + showConfirmButton: false, + allowEscapeKey: false + }); +} + +function parseJSONResponse(xhr) { + try { + return JSON.parse(xhr.responseText); + } catch (e) { + } + return null; +} + +function validateInputs() { + // check if any new values are bad + var tables = $('table'); + + var inputsValid = true; + + var tables = $('table'); + + // clear any current invalid rows + $('tr.' + Settings.INVALID_ROW_CLASS).removeClass(Settings.INVALID_ROW_CLASS); + + function markParentRowInvalid(rowChild) { + $(rowChild).closest('tr').addClass(Settings.INVALID_ROW_CLASS); + } + + _.each(tables, function(table) { + // validate keys specificially for spaces and equality to an existing key + var newKeys = $(table).find('tr.' + Settings.NEW_ROW_CLASS + ' td.key'); + + var keyWithSpaces = false; + var empty = false; + var duplicateKey = false; + + _.each(newKeys, function(keyCell) { + var keyVal = $(keyCell).children('input').val(); + + if (keyVal.indexOf(' ') !== -1) { + keyWithSpaces = true; + markParentRowInvalid(keyCell); + return; + } + + // make sure the key isn't empty + if (keyVal.length === 0) { + empty = true + + markParentRowInvalid(input); + return; + } + + // make sure we don't have duplicate keys in the table + var otherKeys = $(table).find('td.key').not(keyCell); + _.each(otherKeys, function(otherKeyCell) { + var keyInput = $(otherKeyCell).children('input'); + + if (keyInput.length) { + if ($(keyInput).val() == keyVal) { + duplicateKey = true; + } + } else if ($(otherKeyCell).html() == keyVal) { + duplicateKey = true; + } + + if (duplicateKey) { + markParentRowInvalid(keyCell); + return; + } + }); + + }); + + if (keyWithSpaces) { + showErrorMessage("Error", "Key contains spaces"); + inputsValid = false; + return + } + + if (empty) { + showErrorMessage("Error", "Empty field(s)"); + inputsValid = false; + return + } + + if (duplicateKey) { + showErrorMessage("Error", "Two keys cannot be identical"); + inputsValid = false; + return; + } + }); + + return inputsValid; +} + +var SETTINGS_ERROR_MESSAGE = "There was a problem saving domain settings. Please try again!"; + +function saveSettings() { + + if (validateInputs()) { + // POST the form JSON to the domain-server settings.json endpoint so the settings are saved + var canPost = true; + + // disable any inputs not changed + $("input:not([data-changed])").each(function () { + $(this).prop('disabled', true); + }); + + // grab a JSON representation of the form via form2js + var formJSON = form2js('settings-form', ".", false, cleanupFormValues, true); + + // re-enable all inputs + $("input").each(function () { + $(this).prop('disabled', false); + }); + + // remove focus from the button + $(this).blur(); + + if (Settings.handlePostSettings === undefined) { + console.log("----- saveSettings() called ------"); + console.log(formJSON); + + // POST the form JSON to the domain-server settings.json endpoint so the settings are saved + postSettings(formJSON); + } else { + Settings.handlePostSettings(formJSON) + } + } +} + +$('body').on('click', '.save-button', function(e){ + saveSettings(); + return false; +}); + +function makeTable(setting, keypath, setting_value) { + var isArray = !_.has(setting, 'key'); + var categoryKey = setting.categorize_by_key; + var isCategorized = !!categoryKey && isArray; + + if (!isArray && setting.can_order) { + setting.can_order = false; + } + + var html = ""; + + if (setting.help) { + html += "" + setting.help + "" + } + + var nonDeletableRowKey = setting["non-deletable-row-key"]; + var nonDeletableRowValues = setting["non-deletable-row-values"]; + + html += ""; + + if (setting.caption) { + html += "" + } + + // Column groups + if (setting.groups) { + html += "" + _.each(setting.groups, function (group) { + html += "" + }) + if (!setting.read_only) { + if (setting.can_order) { + html += ""; + } + html += "" + } + html += "" + } + + // Column names + html += "" + + if (setting.numbered === true) { + html += "" // Row number + } + + if (setting.key) { + html += "" // Key + } + + var numVisibleColumns = 0; + _.each(setting.columns, function(col) { + if (!col.hidden) numVisibleColumns++; + html += "" // Data + }) + + if (!setting.read_only) { + if (setting.can_order) { + numVisibleColumns++; + html += ""; + } + numVisibleColumns++; + html += ""; + } + + // populate rows in the table from existing values + var row_num = 1; + + if (keypath.length > 0 && _.size(setting_value) > 0) { + var rowIsObject = setting.columns.length > 1; + + _.each(setting_value, function(row, rowIndexOrName) { + var categoryPair = {}; + var categoryValue = ""; + if (isCategorized) { + categoryValue = rowIsObject ? row[categoryKey] : row; + categoryPair[categoryKey] = categoryValue; + if (_.findIndex(setting_value, categoryPair) === rowIndexOrName) { + html += makeTableCategoryHeader(categoryKey, categoryValue, numVisibleColumns, setting.can_add_new_categories, ""); + } + } + + html += ""; + + if (setting.numbered === true) { + html += "" + } + + if (setting.key) { + html += "" + } + + var isNonDeletableRow = !setting.can_add_new_rows; + + _.each(setting.columns, function(col) { + + var colValue, colName; + if (isArray) { + colValue = rowIsObject ? row[col.name] : row; + colName = keypath + "[" + rowIndexOrName + "]" + (rowIsObject ? "." + col.name : ""); + } else { + colValue = row[col.name]; + colName = keypath + "." + rowIndexOrName + "." + col.name; + } + + isNonDeletableRow = isNonDeletableRow + || (nonDeletableRowKey === col.name && nonDeletableRowValues.indexOf(colValue) !== -1); + + if (isArray && col.type === "checkbox" && col.editable) { + html += + ""; + } else if (isArray && col.type === "time" && col.editable) { + html += + ""; + } else { + // Use a hidden input so that the values are posted. + html += + ""; + } + + }); + + if (!setting.read_only) { + if (setting.can_order) { + html += "" + } + if (isNonDeletableRow) { + html += ""; + } else { + html += ""; + } + } + + html += "" + + if (isCategorized && setting.can_add_new_rows && _.findLastIndex(setting_value, categoryPair) === rowIndexOrName) { + html += makeTableInputs(setting, categoryPair, categoryValue); + } + + row_num++ + }); + } + + // populate inputs in the table for new values + if (!setting.read_only) { + if (setting.can_add_new_categories) { + html += makeTableCategoryInput(setting, numVisibleColumns); + } + + if (setting.can_add_new_rows || setting.can_add_new_categories) { + html += makeTableHiddenInputs(setting, {}, ""); + } + } + html += "
" + setting.caption + "
" + group.label + "
#" + setting.key.label + "" + col.label + "
" + row_num + "" + rowIndexOrName + "" + + "" + + "" + + "" + + "" + + colValue + + "" + + "" + + "
" + + return html; +} + +function makeTableCategoryHeader(categoryKey, categoryValue, numVisibleColumns, canRemove, message) { + var html = + "" + + "" + + "" + + "" + categoryValue + "" + + "" + + ((canRemove) ? ( + "" + + "" + + "" + ) : ( + "" + )) + + ""; + return html; +} + +function makeTableHiddenInputs(setting, initialValues, categoryValue) { + var html = ""; + + if (setting.numbered === true) { + html += ""; + } + + if (setting.key) { + html += "\ + \ + " + } + + _.each(setting.columns, function(col) { + var defaultValue = _.has(initialValues, col.name) ? initialValues[col.name] : col.default; + if (col.type === "checkbox") { + html += + "" + + "" + + ""; + } else if (col.type === "select") { + html += "" + html += ""; + html += ""; + } else { + html += + "" + + "" + + ""; + } + }) + + if (setting.can_order) { + html += "" + } + html += "" + html += "" + + return html +} + +function makeTableCategoryInput(setting, numVisibleColumns) { + var canAddRows = setting.can_add_new_rows; + var categoryKey = setting.categorize_by_key; + var placeholder = setting.new_category_placeholder || ""; + var message = setting.new_category_message || ""; + var html = + "" + + "" + + "" + + "" + + "" + + "" + + "" + + ""; + return html; +} + +function getDescriptionForKey(key) { + for (var i in Settings.data.descriptions) { + if (Settings.data.descriptions[i].name === key) { + return Settings.data.descriptions[i]; + } + } +} + +var SAVE_BUTTON_LABEL_SAVE = "Save"; +var SAVE_BUTTON_LABEL_RESTART = "Save and restart"; +var reasonsForRestart = []; +var numChangesBySection = {}; + +function badgeForDifferences(changedElement) { + // figure out which group this input is in + var panelParentID = changedElement.closest('.panel').attr('id'); + + // if the panel contains non-grouped settings, the initial value is Settings.initialValues + var isGrouped = $('#' + panelParentID).hasClass('grouped'); + + if (isGrouped) { + var initialPanelJSON = Settings.initialValues[panelParentID] + ? Settings.initialValues[panelParentID] + : {}; + + // get a JSON representation of that section + var panelJSON = form2js(panelParentID, ".", false, cleanupFormValues, true)[panelParentID]; + } else { + var initialPanelJSON = Settings.initialValues; + + // get a JSON representation of that section + var panelJSON = form2js(panelParentID, ".", false, cleanupFormValues, true); + } + + var badgeValue = 0 + var description = getDescriptionForKey(panelParentID); + + // badge for any settings we have that are not the same or are not present in initialValues + for (var setting in panelJSON) { + if ((!_.has(initialPanelJSON, setting) && panelJSON[setting] !== "") || + (!_.isEqual(panelJSON[setting], initialPanelJSON[setting]) + && (panelJSON[setting] !== "" || _.has(initialPanelJSON, setting)))) { + badgeValue += 1; + + // add a reason to restart + if (description && description.restart != false) { + reasonsForRestart.push(setting); + } + } else { + // remove a reason to restart + if (description && description.restart != false) { + reasonsForRestart = $.grep(reasonsForRestart, function(v) { return v != setting; }); + } + } + } + + // update the list-group-item badge to have the new value + if (badgeValue == 0) { + badgeValue = "" + } + + numChangesBySection[panelParentID] = badgeValue; + + var hasChanges = badgeValue > 0; + + if (!hasChanges) { + for (var key in numChangesBySection) { + if (numChangesBySection[key] > 0) { + hasChanges = true; + break; + } + } + } + + $(".save-button").prop("disabled", !hasChanges); + $(".save-button").html(reasonsForRestart.length > 0 ? SAVE_BUTTON_LABEL_RESTART : SAVE_BUTTON_LABEL_SAVE); + + // add the badge to the navbar item and the panel header + $("a[href='" + settingsGroupAnchor(Settings.path, panelParentID) + "'] .badge").html(badgeValue); + $("#" + panelParentID + " .panel-heading .badge").html(badgeValue); + + // make the navbar dropdown show a badge that is the total of the badges of all groups + var totalChanges = 0; + $('.panel-heading .badge').each(function(index){ + if (this.innerHTML.length > 0) { + totalChanges += parseInt(this.innerHTML); + } + }); + + if (totalChanges == 0) { + totalChanges = "" + } + + var totalBadgeClass = Settings.content_settings ? '.content-settings-badge' : '.domain-settings-badge'; + + $(totalBadgeClass).html(totalChanges); + $('#hamburger-badge').html(totalChanges); +} + +function addTableRow(row) { + var table = row.parents('table'); + var isArray = table.data('setting-type') === 'array'; + var keepField = row.data("keep-field"); + + var columns = row.parent().children('.' + Settings.DATA_ROW_CLASS); + + var input_clone = row.clone(); + + // Change input row to data row + var table = row.parents("table"); + var setting_name = table.attr("name"); + row.addClass(Settings.DATA_ROW_CLASS + " " + Settings.NEW_ROW_CLASS); + + if (!isArray) { + // show the key input + var keyInput = row.children(".key").children("input"); + + // whenever the keyInput changes, re-badge for differences + keyInput.on('change keyup paste', function(){ + // update siblings in the row to have the correct name + var currentKey = $(this).val(); + + $(this).closest('tr').find('.value-col input').each(function(index){ + var input = $(this); + + if (currentKey.length > 0) { + input.attr("name", setting_name + "." + currentKey + "." + input.parent().attr('name')); + } else { + input.removeAttr("name"); + } + }) + + badgeForDifferences($(this)); + }); + } + + // if this is an array, add the row index (which is the index of the last row + 1) + // as a data attribute to the row + var row_index = 0; + if (isArray) { + var previous_row = row.siblings('.' + Settings.DATA_ROW_CLASS + ':last'); + + if (previous_row.length > 0) { + row_index = parseInt(previous_row.attr(Settings.DATA_ROW_INDEX), 10) + 1; + } else { + row_index = 0; + } + + row.attr(Settings.DATA_ROW_INDEX, row_index); + } + + var focusChanged = false; + + _.each(row.children(), function(element) { + if ($(element).hasClass("numbered")) { + // Index row + var numbers = columns.children(".numbered") + if (numbers.length > 0) { + $(element).html(parseInt(numbers.last().text()) + 1) + } else { + $(element).html(1) + } + } else if ($(element).hasClass(Settings.REORDER_BUTTONS_CLASS)) { + $(element).html("") + } else if ($(element).hasClass(Settings.ADD_DEL_BUTTONS_CLASS)) { + // Change buttons + var anchor = $(element).children("a") + anchor.removeClass(Settings.ADD_ROW_SPAN_CLASSES) + anchor.addClass(Settings.DEL_ROW_SPAN_CLASSES) + } else if ($(element).hasClass("key")) { + var input = $(element).children("input") + input.show(); + } else if ($(element).hasClass(Settings.DATA_COL_CLASS)) { + // show inputs + var input = $(element).find("input"); + input.show(); + + var isCheckbox = input.hasClass("table-checkbox"); + var isDropdown = input.hasClass("table-dropdown"); + + if (isArray) { + var key = $(element).attr('name'); + + // are there multiple columns or just one? + // with multiple we have an array of Objects, with one we have an array of whatever the value type is + var num_columns = row.children('.' + Settings.DATA_COL_CLASS).length + var newName = setting_name + "[" + row_index + "]" + (num_columns > 1 ? "." + key : ""); + + input.attr("name", newName); + + if (isDropdown) { + // default values for hidden inputs inside child selects gets cleared so we need to remind it + var selectElement = $(element).children("select"); + selectElement.attr("data-hidden-input", newName); + $(element).children("input").val(selectElement.val()); + } + } + + if (isArray && !focusChanged) { + input.focus(); + focusChanged = true; + } + + // if we are adding a dropdown, we should go ahead and make its select + // element is visible + if (isDropdown) { + $(element).children("select").attr("style", ""); + } + + if (isCheckbox) { + $(input).find("input").attr("data-changed", "true"); + } else { + input.attr("data-changed", "true"); + } + } else { + console.log("Unknown table element"); + } + }); + + if (!isArray) { + keyInput.focus(); + } + + input_clone.children('td').each(function () { + if ($(this).attr("name") !== keepField) { + $(this).find("input").val($(this).children('input').attr('data-default')); + } + }); + + if (isArray) { + updateDataChangedForSiblingRows(row, true) + + // the addition of any table row should remove the empty-array-row + row.siblings('.empty-array-row').remove() + } + + badgeForDifferences($(table)) + + row.after(input_clone) +} + +function deleteTableRow($row) { + var $table = $row.closest('table'); + var categoryName = $row.data("category"); + var isArray = $table.data('setting-type') === 'array'; + + $row.empty(); + + if (!isArray) { + if ($row.attr('name')) { + $row.html(""); + } else { + // for rows that didn't have a key, simply remove the row + $row.remove(); + } + } else { + if ($table.find('.' + Settings.DATA_ROW_CLASS + "[data-category='" + categoryName + "']").length <= 1) { + // This is the last row of the category, so delete the header + $table.find('.' + Settings.DATA_CATEGORY_CLASS + "[data-category='" + categoryName + "']").remove(); + } + + if ($table.find('.' + Settings.DATA_ROW_CLASS).length > 1) { + updateDataChangedForSiblingRows($row); + + // this isn't the last row - we can just remove it + $row.remove(); + } else { + // this is the last row, we can't remove it completely since we need to post an empty array + $row + .removeClass(Settings.DATA_ROW_CLASS) + .removeClass(Settings.NEW_ROW_CLASS) + .removeAttr("data-category") + .addClass('empty-array-row') + .html(""); + } + } + + // we need to fire a change event on one of the remaining inputs so that the sidebar badge is updated + badgeForDifferences($table); +} + +function addTableCategory($categoryInputRow) { + var $input = $categoryInputRow.find("input").first(); + var categoryValue = $input.prop("value"); + if (!categoryValue || $categoryInputRow.closest("table").find("tr[data-category='" + categoryValue + "']").length !== 0) { + $categoryInputRow.addClass("has-warning"); + + setTimeout(function () { + $categoryInputRow.removeClass("has-warning"); + }, 400); + + return; + } + + var $rowInput = $categoryInputRow.next(".inputs").clone(); + if (!$rowInput) { + console.error("Error cloning inputs"); + } + + var canAddRows = $categoryInputRow.data("can-add-rows"); + var message = $categoryInputRow.data("message"); + var categoryKey = $categoryInputRow.data("key"); + var width = 0; + $categoryInputRow + .children("td") + .each(function () { + width += $(this).prop("colSpan") || 1; + }); + + $input + .prop("value", "") + .focus(); + + $rowInput.find("td[name='" + categoryKey + "'] > input").first() + .prop("value", categoryValue); + $rowInput + .attr("data-category", categoryValue) + .addClass(Settings.NEW_ROW_CLASS); + + var $newCategoryRow = $(makeTableCategoryHeader(categoryKey, categoryValue, width, true, " - " + message)); + $newCategoryRow.addClass(Settings.NEW_ROW_CLASS); + + $categoryInputRow + .before($newCategoryRow) + .before($rowInput); + + if (canAddRows) { + $rowInput.removeAttr("hidden"); + } else { + addTableRow($rowInput); + } +} + +function deleteTableCategory($categoryHeaderRow) { + var categoryName = $categoryHeaderRow.data("category"); + + $categoryHeaderRow + .closest("table") + .find("tr[data-category='" + categoryName + "']") + .each(function () { + if ($(this).hasClass(Settings.DATA_ROW_CLASS)) { + deleteTableRow($(this)); + } else { + $(this).remove(); + } + }); +} + +function toggleTableCategory($categoryHeaderRow) { + var $icon = $categoryHeaderRow.find("." + Settings.TOGGLE_CATEGORY_SPAN_CLASS).first(); + var categoryName = $categoryHeaderRow.data("category"); + var wasExpanded = $icon.hasClass(Settings.TOGGLE_CATEGORY_EXPANDED_CLASS); + if (wasExpanded) { + $icon + .addClass(Settings.TOGGLE_CATEGORY_CONTRACTED_CLASS) + .removeClass(Settings.TOGGLE_CATEGORY_EXPANDED_CLASS); + } else { + $icon + .addClass(Settings.TOGGLE_CATEGORY_EXPANDED_CLASS) + .removeClass(Settings.TOGGLE_CATEGORY_CONTRACTED_CLASS); + } + $categoryHeaderRow + .closest("table") + .find("tr[data-category='" + categoryName + "']") + .toggleClass("contracted", wasExpanded); +} + +function moveTableRow(row, move_up) { + var table = $(row).closest('table') + var isArray = table.data('setting-type') === 'array' + if (!isArray) { + return; + } + + if (move_up) { + var prev_row = row.prev() + if (prev_row.hasClass(Settings.DATA_ROW_CLASS)) { + prev_row.before(row) + } + } else { + var next_row = row.next() + if (next_row.hasClass(Settings.DATA_ROW_CLASS)) { + next_row.after(row) + } + } + + // we need to fire a change event on one of the remaining inputs so that the sidebar badge is updated + badgeForDifferences($(table)) +} + +function updateDataChangedForSiblingRows(row, forceTrue) { + // anytime a new row is added to an array we need to set data-changed for all sibling row inputs to true + // unless it matches the inital set of values + + if (!forceTrue) { + // figure out which group this row is in + var panelParentID = row.closest('.panel').attr('id') + // get the short name for the setting from the table + var tableShortName = row.closest('table').data('short-name') + + // get a JSON representation of that section + var panelSettingJSON = form2js(panelParentID, ".", false, cleanupFormValues, true)[panelParentID][tableShortName] + if (Settings.initialValues[panelParentID]) { + var initialPanelSettingJSON = Settings.initialValues[panelParentID][tableShortName] + } else { + var initialPanelSettingJSON = {}; + } + + // if they are equal, we don't need data-changed + isTrue = !_.isEqual(panelSettingJSON, initialPanelSettingJSON) + } else { + isTrue = true + } + + row.siblings('.' + Settings.DATA_ROW_CLASS).each(function(){ + var hiddenInput = $(this).find('td.' + Settings.DATA_COL_CLASS + ' input') + if (isTrue) { + hiddenInput.attr('data-changed', isTrue) + } else { + hiddenInput.removeAttr('data-changed') + } + }) +} + +function cleanupFormValues(node) { + if (node.type && node.type === 'checkbox') { + return { name: node.name, value: node.checked ? true : false }; + } else { + return false; + } +} + +function showErrorMessage(title, message) { + swal(title, message) +} diff --git a/domain-server/resources/web/settings/js/bootstrap-switch.min.js b/domain-server/resources/web/js/bootstrap-switch.min.js similarity index 100% rename from domain-server/resources/web/settings/js/bootstrap-switch.min.js rename to domain-server/resources/web/js/bootstrap-switch.min.js diff --git a/domain-server/resources/web/js/domain-server.js b/domain-server/resources/web/js/domain-server.js index ad1509b038..aa658bce3f 100644 --- a/domain-server/resources/web/js/domain-server.js +++ b/domain-server/resources/web/js/domain-server.js @@ -23,16 +23,22 @@ function showRestartModal() { }, 1000); } +function settingsGroupAnchor(base, html_id) { + return base + "#" + html_id + "_group" +} + $(document).ready(function(){ var url = window.location; + // Will only work if string in href matches with location $('ul.nav a[href="'+ url +'"]').parent().addClass('active'); // Will also work for relative and absolute hrefs $('ul.nav a').filter(function() { return this.href == url; - }).parent().addClass('active'); - $('body').on('click', '#restart-server', function(e) { + }).parent().addClass('active'); + + $('body').on('click', '#restart-server', function(e) { swal( { title: "Are you sure?", text: "This will restart your domain server, causing your domain to be briefly offline.", @@ -45,4 +51,35 @@ $(document).ready(function(){ }); return false; }); + + var $contentDropdown = $('#content-settings-nav-dropdown'); + var $settingsDropdown = $('#domain-settings-nav-dropdown'); + + // for pages that have the settings dropdowns + if ($contentDropdown.length && $settingsDropdown.length) { + // make a JSON request to get the dropdown menus for content and settings + // we don't error handle here because the top level menu is still clickable and usables if this fails + $.getJSON('/settings-menu-groups.json', function(data){ + function makeGroupDropdownElement(group, base) { + var html_id = group.html_id ? group.html_id : group.name; + return "
  • " + group.label + "
  • "; + } + + $.each(data.content_settings, function(index, group){ + if (index > 0) { + $contentDropdown.append(""); + } + + $contentDropdown.append(makeGroupDropdownElement(group, "/content/")); + }); + + $.each(data.domain_settings, function(index, group){ + if (index > 0) { + $settingsDropdown.append(""); + } + + $settingsDropdown.append(makeGroupDropdownElement(group, "/settings/")); + }); + }); + } }); diff --git a/domain-server/resources/web/settings/js/form2js.min.js b/domain-server/resources/web/js/form2js.min.js similarity index 100% rename from domain-server/resources/web/settings/js/form2js.min.js rename to domain-server/resources/web/js/form2js.min.js diff --git a/domain-server/resources/web/js/shared.js b/domain-server/resources/web/js/shared.js index 00f699fa4e..e1870a2fa8 100644 --- a/domain-server/resources/web/js/shared.js +++ b/domain-server/resources/web/js/shared.js @@ -1,6 +1,4 @@ -var Settings = { - showAdvanced: false, - ADVANCED_CLASS: 'advanced-setting', +Object.assign(Settings, { DEPRECATED_CLASS: 'deprecated-setting', TRIGGER_CHANGE_CLASS: 'trigger-change', DATA_ROW_CLASS: 'value-row', @@ -41,7 +39,7 @@ var Settings = { FORM_ID: 'settings-form', INVALID_ROW_CLASS: 'invalid-input', DATA_ROW_INDEX: 'data-row-index' -}; +}); var URLs = { // STABLE METAVERSE_URL: https://metaverse.highfidelity.com diff --git a/domain-server/resources/web/js/underscore-min.js b/domain-server/resources/web/js/underscore-min.js index f01025b7bc..3c3eec027b 100644 --- a/domain-server/resources/web/js/underscore-min.js +++ b/domain-server/resources/web/js/underscore-min.js @@ -3,4 +3,3 @@ // (c) 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors // Underscore may be freely distributed under the MIT license. (function(){function n(n){function t(t,r,e,u,i,o){for(;i>=0&&o>i;i+=n){var a=u?u[i]:i;e=r(e,t[a],a,t)}return e}return function(r,e,u,i){e=b(e,i,4);var o=!k(r)&&m.keys(r),a=(o||r).length,c=n>0?0:a-1;return arguments.length<3&&(u=r[o?o[c]:c],c+=n),t(r,e,u,o,c,a)}}function t(n){return function(t,r,e){r=x(r,e);for(var u=O(t),i=n>0?0:u-1;i>=0&&u>i;i+=n)if(r(t[i],i,t))return i;return-1}}function r(n,t,r){return function(e,u,i){var o=0,a=O(e);if("number"==typeof i)n>0?o=i>=0?i:Math.max(i+a,o):a=i>=0?Math.min(i+1,a):i+a+1;else if(r&&i&&a)return i=r(e,u),e[i]===u?i:-1;if(u!==u)return i=t(l.call(e,o,a),m.isNaN),i>=0?i+o:-1;for(i=n>0?o:a-1;i>=0&&a>i;i+=n)if(e[i]===u)return i;return-1}}function e(n,t){var r=I.length,e=n.constructor,u=m.isFunction(e)&&e.prototype||a,i="constructor";for(m.has(n,i)&&!m.contains(t,i)&&t.push(i);r--;)i=I[r],i in n&&n[i]!==u[i]&&!m.contains(t,i)&&t.push(i)}var u=this,i=u._,o=Array.prototype,a=Object.prototype,c=Function.prototype,f=o.push,l=o.slice,s=a.toString,p=a.hasOwnProperty,h=Array.isArray,v=Object.keys,g=c.bind,y=Object.create,d=function(){},m=function(n){return n instanceof m?n:this instanceof m?void(this._wrapped=n):new m(n)};"undefined"!=typeof exports?("undefined"!=typeof module&&module.exports&&(exports=module.exports=m),exports._=m):u._=m,m.VERSION="1.8.3";var b=function(n,t,r){if(t===void 0)return n;switch(null==r?3:r){case 1:return function(r){return n.call(t,r)};case 2:return function(r,e){return n.call(t,r,e)};case 3:return function(r,e,u){return n.call(t,r,e,u)};case 4:return function(r,e,u,i){return n.call(t,r,e,u,i)}}return function(){return n.apply(t,arguments)}},x=function(n,t,r){return null==n?m.identity:m.isFunction(n)?b(n,t,r):m.isObject(n)?m.matcher(n):m.property(n)};m.iteratee=function(n,t){return x(n,t,1/0)};var _=function(n,t){return function(r){var e=arguments.length;if(2>e||null==r)return r;for(var u=1;e>u;u++)for(var i=arguments[u],o=n(i),a=o.length,c=0;a>c;c++){var f=o[c];t&&r[f]!==void 0||(r[f]=i[f])}return r}},j=function(n){if(!m.isObject(n))return{};if(y)return y(n);d.prototype=n;var t=new d;return d.prototype=null,t},w=function(n){return function(t){return null==t?void 0:t[n]}},A=Math.pow(2,53)-1,O=w("length"),k=function(n){var t=O(n);return"number"==typeof t&&t>=0&&A>=t};m.each=m.forEach=function(n,t,r){t=b(t,r);var e,u;if(k(n))for(e=0,u=n.length;u>e;e++)t(n[e],e,n);else{var i=m.keys(n);for(e=0,u=i.length;u>e;e++)t(n[i[e]],i[e],n)}return n},m.map=m.collect=function(n,t,r){t=x(t,r);for(var e=!k(n)&&m.keys(n),u=(e||n).length,i=Array(u),o=0;u>o;o++){var a=e?e[o]:o;i[o]=t(n[a],a,n)}return i},m.reduce=m.foldl=m.inject=n(1),m.reduceRight=m.foldr=n(-1),m.find=m.detect=function(n,t,r){var e;return e=k(n)?m.findIndex(n,t,r):m.findKey(n,t,r),e!==void 0&&e!==-1?n[e]:void 0},m.filter=m.select=function(n,t,r){var e=[];return t=x(t,r),m.each(n,function(n,r,u){t(n,r,u)&&e.push(n)}),e},m.reject=function(n,t,r){return m.filter(n,m.negate(x(t)),r)},m.every=m.all=function(n,t,r){t=x(t,r);for(var e=!k(n)&&m.keys(n),u=(e||n).length,i=0;u>i;i++){var o=e?e[i]:i;if(!t(n[o],o,n))return!1}return!0},m.some=m.any=function(n,t,r){t=x(t,r);for(var e=!k(n)&&m.keys(n),u=(e||n).length,i=0;u>i;i++){var o=e?e[i]:i;if(t(n[o],o,n))return!0}return!1},m.contains=m.includes=m.include=function(n,t,r,e){return k(n)||(n=m.values(n)),("number"!=typeof r||e)&&(r=0),m.indexOf(n,t,r)>=0},m.invoke=function(n,t){var r=l.call(arguments,2),e=m.isFunction(t);return m.map(n,function(n){var u=e?t:n[t];return null==u?u:u.apply(n,r)})},m.pluck=function(n,t){return m.map(n,m.property(t))},m.where=function(n,t){return m.filter(n,m.matcher(t))},m.findWhere=function(n,t){return m.find(n,m.matcher(t))},m.max=function(n,t,r){var e,u,i=-1/0,o=-1/0;if(null==t&&null!=n){n=k(n)?n:m.values(n);for(var a=0,c=n.length;c>a;a++)e=n[a],e>i&&(i=e)}else t=x(t,r),m.each(n,function(n,r,e){u=t(n,r,e),(u>o||u===-1/0&&i===-1/0)&&(i=n,o=u)});return i},m.min=function(n,t,r){var e,u,i=1/0,o=1/0;if(null==t&&null!=n){n=k(n)?n:m.values(n);for(var a=0,c=n.length;c>a;a++)e=n[a],i>e&&(i=e)}else t=x(t,r),m.each(n,function(n,r,e){u=t(n,r,e),(o>u||1/0===u&&1/0===i)&&(i=n,o=u)});return i},m.shuffle=function(n){for(var t,r=k(n)?n:m.values(n),e=r.length,u=Array(e),i=0;e>i;i++)t=m.random(0,i),t!==i&&(u[i]=u[t]),u[t]=r[i];return u},m.sample=function(n,t,r){return null==t||r?(k(n)||(n=m.values(n)),n[m.random(n.length-1)]):m.shuffle(n).slice(0,Math.max(0,t))},m.sortBy=function(n,t,r){return t=x(t,r),m.pluck(m.map(n,function(n,r,e){return{value:n,index:r,criteria:t(n,r,e)}}).sort(function(n,t){var r=n.criteria,e=t.criteria;if(r!==e){if(r>e||r===void 0)return 1;if(e>r||e===void 0)return-1}return n.index-t.index}),"value")};var F=function(n){return function(t,r,e){var u={};return r=x(r,e),m.each(t,function(e,i){var o=r(e,i,t);n(u,e,o)}),u}};m.groupBy=F(function(n,t,r){m.has(n,r)?n[r].push(t):n[r]=[t]}),m.indexBy=F(function(n,t,r){n[r]=t}),m.countBy=F(function(n,t,r){m.has(n,r)?n[r]++:n[r]=1}),m.toArray=function(n){return n?m.isArray(n)?l.call(n):k(n)?m.map(n,m.identity):m.values(n):[]},m.size=function(n){return null==n?0:k(n)?n.length:m.keys(n).length},m.partition=function(n,t,r){t=x(t,r);var e=[],u=[];return m.each(n,function(n,r,i){(t(n,r,i)?e:u).push(n)}),[e,u]},m.first=m.head=m.take=function(n,t,r){return null==n?void 0:null==t||r?n[0]:m.initial(n,n.length-t)},m.initial=function(n,t,r){return l.call(n,0,Math.max(0,n.length-(null==t||r?1:t)))},m.last=function(n,t,r){return null==n?void 0:null==t||r?n[n.length-1]:m.rest(n,Math.max(0,n.length-t))},m.rest=m.tail=m.drop=function(n,t,r){return l.call(n,null==t||r?1:t)},m.compact=function(n){return m.filter(n,m.identity)};var S=function(n,t,r,e){for(var u=[],i=0,o=e||0,a=O(n);a>o;o++){var c=n[o];if(k(c)&&(m.isArray(c)||m.isArguments(c))){t||(c=S(c,t,r));var f=0,l=c.length;for(u.length+=l;l>f;)u[i++]=c[f++]}else r||(u[i++]=c)}return u};m.flatten=function(n,t){return S(n,t,!1)},m.without=function(n){return m.difference(n,l.call(arguments,1))},m.uniq=m.unique=function(n,t,r,e){m.isBoolean(t)||(e=r,r=t,t=!1),null!=r&&(r=x(r,e));for(var u=[],i=[],o=0,a=O(n);a>o;o++){var c=n[o],f=r?r(c,o,n):c;t?(o&&i===f||u.push(c),i=f):r?m.contains(i,f)||(i.push(f),u.push(c)):m.contains(u,c)||u.push(c)}return u},m.union=function(){return m.uniq(S(arguments,!0,!0))},m.intersection=function(n){for(var t=[],r=arguments.length,e=0,u=O(n);u>e;e++){var i=n[e];if(!m.contains(t,i)){for(var o=1;r>o&&m.contains(arguments[o],i);o++);o===r&&t.push(i)}}return t},m.difference=function(n){var t=S(arguments,!0,!0,1);return m.filter(n,function(n){return!m.contains(t,n)})},m.zip=function(){return m.unzip(arguments)},m.unzip=function(n){for(var t=n&&m.max(n,O).length||0,r=Array(t),e=0;t>e;e++)r[e]=m.pluck(n,e);return r},m.object=function(n,t){for(var r={},e=0,u=O(n);u>e;e++)t?r[n[e]]=t[e]:r[n[e][0]]=n[e][1];return r},m.findIndex=t(1),m.findLastIndex=t(-1),m.sortedIndex=function(n,t,r,e){r=x(r,e,1);for(var u=r(t),i=0,o=O(n);o>i;){var a=Math.floor((i+o)/2);r(n[a])i;i++,n+=r)u[i]=n;return u};var E=function(n,t,r,e,u){if(!(e instanceof t))return n.apply(r,u);var i=j(n.prototype),o=n.apply(i,u);return m.isObject(o)?o:i};m.bind=function(n,t){if(g&&n.bind===g)return g.apply(n,l.call(arguments,1));if(!m.isFunction(n))throw new TypeError("Bind must be called on a function");var r=l.call(arguments,2),e=function(){return E(n,e,t,this,r.concat(l.call(arguments)))};return e},m.partial=function(n){var t=l.call(arguments,1),r=function(){for(var e=0,u=t.length,i=Array(u),o=0;u>o;o++)i[o]=t[o]===m?arguments[e++]:t[o];for(;e=e)throw new Error("bindAll must be passed function names");for(t=1;e>t;t++)r=arguments[t],n[r]=m.bind(n[r],n);return n},m.memoize=function(n,t){var r=function(e){var u=r.cache,i=""+(t?t.apply(this,arguments):e);return m.has(u,i)||(u[i]=n.apply(this,arguments)),u[i]};return r.cache={},r},m.delay=function(n,t){var r=l.call(arguments,2);return setTimeout(function(){return n.apply(null,r)},t)},m.defer=m.partial(m.delay,m,1),m.throttle=function(n,t,r){var e,u,i,o=null,a=0;r||(r={});var c=function(){a=r.leading===!1?0:m.now(),o=null,i=n.apply(e,u),o||(e=u=null)};return function(){var f=m.now();a||r.leading!==!1||(a=f);var l=t-(f-a);return e=this,u=arguments,0>=l||l>t?(o&&(clearTimeout(o),o=null),a=f,i=n.apply(e,u),o||(e=u=null)):o||r.trailing===!1||(o=setTimeout(c,l)),i}},m.debounce=function(n,t,r){var e,u,i,o,a,c=function(){var f=m.now()-o;t>f&&f>=0?e=setTimeout(c,t-f):(e=null,r||(a=n.apply(i,u),e||(i=u=null)))};return function(){i=this,u=arguments,o=m.now();var f=r&&!e;return e||(e=setTimeout(c,t)),f&&(a=n.apply(i,u),i=u=null),a}},m.wrap=function(n,t){return m.partial(t,n)},m.negate=function(n){return function(){return!n.apply(this,arguments)}},m.compose=function(){var n=arguments,t=n.length-1;return function(){for(var r=t,e=n[t].apply(this,arguments);r--;)e=n[r].call(this,e);return e}},m.after=function(n,t){return function(){return--n<1?t.apply(this,arguments):void 0}},m.before=function(n,t){var r;return function(){return--n>0&&(r=t.apply(this,arguments)),1>=n&&(t=null),r}},m.once=m.partial(m.before,2);var M=!{toString:null}.propertyIsEnumerable("toString"),I=["valueOf","isPrototypeOf","toString","propertyIsEnumerable","hasOwnProperty","toLocaleString"];m.keys=function(n){if(!m.isObject(n))return[];if(v)return v(n);var t=[];for(var r in n)m.has(n,r)&&t.push(r);return M&&e(n,t),t},m.allKeys=function(n){if(!m.isObject(n))return[];var t=[];for(var r in n)t.push(r);return M&&e(n,t),t},m.values=function(n){for(var t=m.keys(n),r=t.length,e=Array(r),u=0;r>u;u++)e[u]=n[t[u]];return e},m.mapObject=function(n,t,r){t=x(t,r);for(var e,u=m.keys(n),i=u.length,o={},a=0;i>a;a++)e=u[a],o[e]=t(n[e],e,n);return o},m.pairs=function(n){for(var t=m.keys(n),r=t.length,e=Array(r),u=0;r>u;u++)e[u]=[t[u],n[t[u]]];return e},m.invert=function(n){for(var t={},r=m.keys(n),e=0,u=r.length;u>e;e++)t[n[r[e]]]=r[e];return t},m.functions=m.methods=function(n){var t=[];for(var r in n)m.isFunction(n[r])&&t.push(r);return t.sort()},m.extend=_(m.allKeys),m.extendOwn=m.assign=_(m.keys),m.findKey=function(n,t,r){t=x(t,r);for(var e,u=m.keys(n),i=0,o=u.length;o>i;i++)if(e=u[i],t(n[e],e,n))return e},m.pick=function(n,t,r){var e,u,i={},o=n;if(null==o)return i;m.isFunction(t)?(u=m.allKeys(o),e=b(t,r)):(u=S(arguments,!1,!1,1),e=function(n,t,r){return t in r},o=Object(o));for(var a=0,c=u.length;c>a;a++){var f=u[a],l=o[f];e(l,f,o)&&(i[f]=l)}return i},m.omit=function(n,t,r){if(m.isFunction(t))t=m.negate(t);else{var e=m.map(S(arguments,!1,!1,1),String);t=function(n,t){return!m.contains(e,t)}}return m.pick(n,t,r)},m.defaults=_(m.allKeys,!0),m.create=function(n,t){var r=j(n);return t&&m.extendOwn(r,t),r},m.clone=function(n){return m.isObject(n)?m.isArray(n)?n.slice():m.extend({},n):n},m.tap=function(n,t){return t(n),n},m.isMatch=function(n,t){var r=m.keys(t),e=r.length;if(null==n)return!e;for(var u=Object(n),i=0;e>i;i++){var o=r[i];if(t[o]!==u[o]||!(o in u))return!1}return!0};var N=function(n,t,r,e){if(n===t)return 0!==n||1/n===1/t;if(null==n||null==t)return n===t;n instanceof m&&(n=n._wrapped),t instanceof m&&(t=t._wrapped);var u=s.call(n);if(u!==s.call(t))return!1;switch(u){case"[object RegExp]":case"[object String]":return""+n==""+t;case"[object Number]":return+n!==+n?+t!==+t:0===+n?1/+n===1/t:+n===+t;case"[object Date]":case"[object Boolean]":return+n===+t}var i="[object Array]"===u;if(!i){if("object"!=typeof n||"object"!=typeof t)return!1;var o=n.constructor,a=t.constructor;if(o!==a&&!(m.isFunction(o)&&o instanceof o&&m.isFunction(a)&&a instanceof a)&&"constructor"in n&&"constructor"in t)return!1}r=r||[],e=e||[];for(var c=r.length;c--;)if(r[c]===n)return e[c]===t;if(r.push(n),e.push(t),i){if(c=n.length,c!==t.length)return!1;for(;c--;)if(!N(n[c],t[c],r,e))return!1}else{var f,l=m.keys(n);if(c=l.length,m.keys(t).length!==c)return!1;for(;c--;)if(f=l[c],!m.has(t,f)||!N(n[f],t[f],r,e))return!1}return r.pop(),e.pop(),!0};m.isEqual=function(n,t){return N(n,t)},m.isEmpty=function(n){return null==n?!0:k(n)&&(m.isArray(n)||m.isString(n)||m.isArguments(n))?0===n.length:0===m.keys(n).length},m.isElement=function(n){return!(!n||1!==n.nodeType)},m.isArray=h||function(n){return"[object Array]"===s.call(n)},m.isObject=function(n){var t=typeof n;return"function"===t||"object"===t&&!!n},m.each(["Arguments","Function","String","Number","Date","RegExp","Error"],function(n){m["is"+n]=function(t){return s.call(t)==="[object "+n+"]"}}),m.isArguments(arguments)||(m.isArguments=function(n){return m.has(n,"callee")}),"function"!=typeof/./&&"object"!=typeof Int8Array&&(m.isFunction=function(n){return"function"==typeof n||!1}),m.isFinite=function(n){return isFinite(n)&&!isNaN(parseFloat(n))},m.isNaN=function(n){return m.isNumber(n)&&n!==+n},m.isBoolean=function(n){return n===!0||n===!1||"[object Boolean]"===s.call(n)},m.isNull=function(n){return null===n},m.isUndefined=function(n){return n===void 0},m.has=function(n,t){return null!=n&&p.call(n,t)},m.noConflict=function(){return u._=i,this},m.identity=function(n){return n},m.constant=function(n){return function(){return n}},m.noop=function(){},m.property=w,m.propertyOf=function(n){return null==n?function(){}:function(t){return n[t]}},m.matcher=m.matches=function(n){return n=m.extendOwn({},n),function(t){return m.isMatch(t,n)}},m.times=function(n,t,r){var e=Array(Math.max(0,n));t=b(t,r,1);for(var u=0;n>u;u++)e[u]=t(u);return e},m.random=function(n,t){return null==t&&(t=n,n=0),n+Math.floor(Math.random()*(t-n+1))},m.now=Date.now||function(){return(new Date).getTime()};var B={"&":"&","<":"<",">":">",'"':""","'":"'","`":"`"},T=m.invert(B),R=function(n){var t=function(t){return n[t]},r="(?:"+m.keys(n).join("|")+")",e=RegExp(r),u=RegExp(r,"g");return function(n){return n=null==n?"":""+n,e.test(n)?n.replace(u,t):n}};m.escape=R(B),m.unescape=R(T),m.result=function(n,t,r){var e=null==n?void 0:n[t];return e===void 0&&(e=r),m.isFunction(e)?e.call(n):e};var q=0;m.uniqueId=function(n){var t=++q+"";return n?n+t:t},m.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var K=/(.)^/,z={"'":"'","\\":"\\","\r":"r","\n":"n","\u2028":"u2028","\u2029":"u2029"},D=/\\|'|\r|\n|\u2028|\u2029/g,L=function(n){return"\\"+z[n]};m.template=function(n,t,r){!t&&r&&(t=r),t=m.defaults({},t,m.templateSettings);var e=RegExp([(t.escape||K).source,(t.interpolate||K).source,(t.evaluate||K).source].join("|")+"|$","g"),u=0,i="__p+='";n.replace(e,function(t,r,e,o,a){return i+=n.slice(u,a).replace(D,L),u=a+t.length,r?i+="'+\n((__t=("+r+"))==null?'':_.escape(__t))+\n'":e?i+="'+\n((__t=("+e+"))==null?'':__t)+\n'":o&&(i+="';\n"+o+"\n__p+='"),t}),i+="';\n",t.variable||(i="with(obj||{}){\n"+i+"}\n"),i="var __t,__p='',__j=Array.prototype.join,"+"print=function(){__p+=__j.call(arguments,'');};\n"+i+"return __p;\n";try{var o=new Function(t.variable||"obj","_",i)}catch(a){throw a.source=i,a}var c=function(n){return o.call(this,n,m)},f=t.variable||"obj";return c.source="function("+f+"){\n"+i+"}",c},m.chain=function(n){var t=m(n);return t._chain=!0,t};var P=function(n,t){return n._chain?m(t).chain():t};m.mixin=function(n){m.each(m.functions(n),function(t){var r=m[t]=n[t];m.prototype[t]=function(){var n=[this._wrapped];return f.apply(n,arguments),P(this,r.apply(m,n))}})},m.mixin(m),m.each(["pop","push","reverse","shift","sort","splice","unshift"],function(n){var t=o[n];m.prototype[n]=function(){var r=this._wrapped;return t.apply(r,arguments),"shift"!==n&&"splice"!==n||0!==r.length||delete r[0],P(this,r)}}),m.each(["concat","join","slice"],function(n){var t=o[n];m.prototype[n]=function(){return P(this,t.apply(this._wrapped,arguments))}}),m.prototype.value=function(){return this._wrapped},m.prototype.valueOf=m.prototype.toJSON=m.prototype.value,m.prototype.toString=function(){return""+this._wrapped},"function"==typeof define&&define.amd&&define("underscore",[],function(){return m})}).call(this); -//# sourceMappingURL=underscore-min.map \ No newline at end of file diff --git a/domain-server/resources/web/settings/index.shtml b/domain-server/resources/web/settings/index.shtml index d36330375a..d71692523a 100644 --- a/domain-server/resources/web/settings/index.shtml +++ b/domain-server/resources/web/settings/index.shtml @@ -1,104 +1,21 @@ -
    -
    -
    - -
    -
    + -
    -
    -
    - - - - - - - -
    -
    - -
    -
    - - - -
    - - -
    -
    -
    -
    - - -
    + - - - + - - - - + + + + + diff --git a/domain-server/resources/web/settings/js/settings.js b/domain-server/resources/web/settings/js/settings.js index 3faeff4482..9a31b766a6 100644 --- a/domain-server/resources/web/settings/js/settings.js +++ b/domain-server/resources/web/settings/js/settings.js @@ -1,1262 +1,20 @@ -var DomainInfo = null; - -var viewHelpers = { - getFormGroup: function(keypath, setting, values, isAdvanced) { - form_group = "
    "; - setting_value = _(values).valueForKeyPath(keypath); - - if (_.isUndefined(setting_value) || _.isNull(setting_value)) { - if (_.has(setting, 'default')) { - setting_value = setting.default; - } else { - setting_value = ""; - } - } - - label_class = 'control-label'; - - function common_attrs(extra_classes) { - extra_classes = (!_.isUndefined(extra_classes) ? extra_classes : ""); - return " class='" + (setting.type !== 'checkbox' ? 'form-control' : '') - + " " + Settings.TRIGGER_CHANGE_CLASS + " " + extra_classes + "' data-short-name='" - + setting.name + "' name='" + keypath + "' " - + "id='" + (!_.isUndefined(setting.html_id) ? setting.html_id : keypath) + "'"; - } - - if (setting.type === 'checkbox') { - if (setting.label) { - form_group += "" - } - - form_group += "
    " - form_group += "" - - if (setting.help) { - form_group += "" + setting.help + ""; - } - - form_group += "
    " - } else { - input_type = _.has(setting, 'type') ? setting.type : "text" - - if (setting.label) { - form_group += ""; - } - - if (input_type === 'table') { - form_group += makeTable(setting, keypath, setting_value) - } else { - if (input_type === 'select') { - form_group += "" - - form_group += "" - } else if (input_type === 'button') { - // Is this a button that should link to something directly? - // If so, we use an anchor tag instead of a button tag - - if (setting.href) { - form_group += "" - + setting.button_label + ""; - } else { - form_group += ""; - } - - } else { - - if (input_type == 'integer') { - input_type = "text" - } - - form_group += "" - } - - form_group += "" + setting.help + "" - } - } - - form_group += "
    " - return form_group - } -} - -var qs = (function(a) { - if (a == "") return {}; - var b = {}; - for (var i = 0; i < a.length; ++i) - { - var p=a[i].split('=', 2); - if (p.length == 1) { - b[p[0]] = ""; - } else { - b[p[0]] = decodeURIComponent(p[1].replace(/\+/g, " ")); - } - } - return b; -})(window.location.search.substr(1).split('&')); - $(document).ready(function(){ - /* - * Clamped-width. - * Usage: - *
    This long content will force clamped width
    - * - * Author: LV - */ - - $.ajaxSetup({ - timeout: 20000, - }); - - $('[data-clampedwidth]').each(function () { - var elem = $(this); - var parentPanel = elem.data('clampedwidth'); - var resizeFn = function () { - var sideBarNavWidth = $(parentPanel).width() - parseInt(elem.css('paddingLeft')) - parseInt(elem.css('paddingRight')) - parseInt(elem.css('marginLeft')) - parseInt(elem.css('marginRight')) - parseInt(elem.css('borderLeftWidth')) - parseInt(elem.css('borderRightWidth')); - elem.css('width', sideBarNavWidth); - }; - - resizeFn(); - $(window).resize(resizeFn); - }); - - $('#' + Settings.FORM_ID).on('click', '.' + Settings.ADD_ROW_BUTTON_CLASS, function(){ - addTableRow($(this).closest('tr')); - }); - - $('#' + Settings.FORM_ID).on('click', '.' + Settings.DEL_ROW_BUTTON_CLASS, function(){ - deleteTableRow($(this).closest('tr')); - }); - - $('#' + Settings.FORM_ID).on('click', '.' + Settings.ADD_CATEGORY_BUTTON_CLASS, function(){ - addTableCategory($(this).closest('tr')); - }); - - $('#' + Settings.FORM_ID).on('click', '.' + Settings.DEL_CATEGORY_BUTTON_CLASS, function(){ - deleteTableCategory($(this).closest('tr')); - }); - - $('#' + Settings.FORM_ID).on('click', '.' + Settings.TOGGLE_CATEGORY_COLUMN_CLASS, function(){ - toggleTableCategory($(this).closest('tr')); - }); - - $('#' + Settings.FORM_ID).on('click', '.' + Settings.MOVE_UP_BUTTON_CLASS, function(){ - moveTableRow($(this).closest('tr'), true); - }); - - $('#' + Settings.FORM_ID).on('click', '.' + Settings.MOVE_DOWN_BUTTON_CLASS, function(){ - moveTableRow($(this).closest('tr'), false); - }); - - $('#' + Settings.FORM_ID).on('keyup', function(e){ - var $target = $(e.target); - if (e.keyCode == 13) { - if ($target.is('table input')) { - // capture enter in table input - // if we have a sibling next to us that has an input, jump to it, otherwise check if we have a glyphicon for add to click - sibling = $target.parent('td').next(); - - if (sibling.hasClass(Settings.DATA_COL_CLASS)) { - // set focus to next input - sibling.find('input').focus(); - } else { - - // jump over the re-order row, if that's what we're on - if (sibling.hasClass(Settings.REORDER_BUTTONS_CLASS)) { - sibling = sibling.next(); - } - - // for tables with categories we add the entry and setup the new row on enter - if (sibling.find("." + Settings.ADD_CATEGORY_BUTTON_CLASS).length) { - sibling.find("." + Settings.ADD_CATEGORY_BUTTON_CLASS).click(); - - // set focus to the first input in the new row - $target.closest('table').find('tr.inputs input:first').focus(); - } - - var tableRows = sibling.parent(); - var tableBody = tableRows.parent(); - - // if theres no more siblings, we should jump to a new row - if (sibling.next().length == 0 && tableRows.nextAll().length == 1) { - tableBody.find("." + Settings.ADD_ROW_BUTTON_CLASS).click(); - } - } - - } else if ($target.is('input')) { - $target.change().blur(); - } - - e.preventDefault(); - } - }); - - $('#' + Settings.FORM_ID).on('keypress', function(e){ - if (e.keyCode == 13) { - - e.preventDefault(); - } - }); - - $('#' + Settings.FORM_ID).on('change', '.' + Settings.TRIGGER_CHANGE_CLASS , function(){ - // this input was changed, add the changed data attribute to it - $(this).attr('data-changed', true); - - badgeSidebarForDifferences($(this)); - }); - - $('#' + Settings.FORM_ID).on('switchChange.bootstrapSwitch', 'input.toggle-checkbox', function(){ - // this checkbox was changed, add the changed data attribute to it - $(this).attr('data-changed', true); - - badgeSidebarForDifferences($(this)); - }); - - // Bootstrap switch in table - $('#' + Settings.FORM_ID).on('change', 'input.table-checkbox', function () { - // Bootstrap switches in table: set the changed data attribute for all rows in table. - var row = $(this).closest('tr'); - if (row.hasClass("value-row")) { // Don't set attribute on input row switches prior to it being added to table. - row.find('td.' + Settings.DATA_COL_CLASS + ' input').attr('data-changed', true); - updateDataChangedForSiblingRows(row, true); - badgeSidebarForDifferences($(this)); - } - }); - - $('#' + Settings.FORM_ID).on('change', 'input.table-time', function() { - // Bootstrap switches in table: set the changed data attribute for all rows in table. - var row = $(this).closest('tr'); - if (row.hasClass("value-row")) { // Don't set attribute on input row switches prior to it being added to table. - row.find('td.' + Settings.DATA_COL_CLASS + ' input').attr('data-changed', true); - updateDataChangedForSiblingRows(row, true); - badgeSidebarForDifferences($(this)); - } - }); - - $('.advanced-toggle').click(function(){ - Settings.showAdvanced = !Settings.showAdvanced - var advancedSelector = $('.' + Settings.ADVANCED_CLASS) - - if (Settings.showAdvanced) { - advancedSelector.show(); - $(this).html("Hide advanced") - } else { - advancedSelector.hide(); - $(this).html("Show advanced") - } - - $(this).blur(); - }) - - $('#' + Settings.FORM_ID).on('click', '#' + Settings.CREATE_DOMAIN_ID_BTN_ID, function(){ - $(this).blur(); - showDomainCreationAlert(false); - }) - - $('#' + Settings.FORM_ID).on('click', '#' + Settings.CHOOSE_DOMAIN_ID_BTN_ID, function(){ - $(this).blur(); - chooseFromHighFidelityDomains($(this)) - }); - - $('#' + Settings.FORM_ID).on('click', '#' + Settings.GET_TEMPORARY_NAME_BTN_ID, function(){ - $(this).blur(); - createTemporaryDomain(); - }); - - - $('#' + Settings.FORM_ID).on('change', 'select', function(){ - $("input[name='" + $(this).attr('data-hidden-input') + "']").val($(this).val()).change() - }); - - $('#' + Settings.FORM_ID).on('click', '#' + Settings.DISCONNECT_ACCOUNT_BTN_ID, function(e){ - $(this).blur(); - disonnectHighFidelityAccount(); - e.preventDefault(); - }); - - $('#' + Settings.FORM_ID).on('click', '#' + Settings.CONNECT_ACCOUNT_BTN_ID, function(e){ - $(this).blur(); - prepareAccessTokenPrompt(function(accessToken) { - // we have an access token - set the access token input with this and save settings - $(Settings.ACCESS_TOKEN_SELECTOR).val(accessToken).change(); - saveSettings(); - }); - }); - - var panelsSource = $('#panels-template').html() - Settings.panelsTemplate = _.template(panelsSource) - - var sidebarTemplate = $('#list-group-template').html() - Settings.sidebarTemplate = _.template(sidebarTemplate) - - var navbarHeight = $('.navbar').outerHeight(true); - - $('#setup-sidebar').affix({ - offset: { - top: 1, - bottom: navbarHeight - } - }); - - reloadSettings(function(success){ - if (success) { - handleAction(); - } else { - swal({ - title: '', - type: 'error', - text: Strings.LOADING_SETTINGS_ERROR - }); - } - $('body').scrollspy({ - target: '#setup-sidebar', - offset: navbarHeight - }); - }); -}); - -function getShareName(callback) { - getDomainFromAPI(function(data){ - // check if we have owner_places (for a real domain) or a name (for a temporary domain) - if (data && data.status == "success") { - var shareName; - if (data.domain.default_place_name) { - shareName = data.domain.default_place_name; - } else if (data.domain.name) { - shareName = data.domain.name; - } else if (data.domain.network_address) { - shareName = data.domain.network_address; - if (data.domain.network_port !== 40102) { - shareName += ':' + data.domain.network_port; - } - } - - callback(true, shareName); - } else { - callback(false); - } - }) -} - -function handleAction() { - // check if we were passed an action to handle - var action = qs["action"]; - - if (action == "share") { - // figure out if we already have a stored domain ID - if (Settings.data.values.metaverse.id.length > 0) { - // we need to ask the API what a shareable name for this domain is - getShareName(function(success, shareName){ - if (success) { - var shareLink = "hifi://" + shareName; - - console.log(shareLink); - - // show a dialog with a copiable share URL - swal({ - title: "Share", - type: "input", - inputPlaceholder: shareLink, - inputValue: shareLink, - text: "Copy this URL to invite friends to your domain.", - closeOnConfirm: true - }); - - $('.sweet-alert input').select(); - - } else { - // show an error alert - swal({ - title: '', - type: 'error', - text: "There was a problem retreiving domain information from High Fidelity API.", - confirmButtonText: 'Try again', - showCancelButton: true, - closeOnConfirm: false - }, function(isConfirm){ - if (isConfirm) { - // they want to try getting domain share info again - showSpinnerAlert("Requesting domain information...") - handleAction(); - } else { - swal.close(); - } - }); - } - }); - } else { - // no domain ID present, just show the share dialog - createTemporaryDomain(); - } - } -} - -function dynamicButton(button_id, text) { - return $(""); -} - -function postSettings(jsonSettings) { - // POST the form JSON to the domain-server settings.json endpoint so the settings are saved - $.ajax('/settings.json', { - data: JSON.stringify(jsonSettings), - contentType: 'application/json', - type: 'POST' - }).done(function(data){ - if (data.status == "success") { - if ($(".save-button").html() === SAVE_BUTTON_LABEL_RESTART) { - showRestartModal(); - } else { - location.reload(true); - } - } else { - showErrorMessage("Error", SETTINGS_ERROR_MESSAGE) - reloadSettings(); - } - }).fail(function(){ - showErrorMessage("Error", SETTINGS_ERROR_MESSAGE) - reloadSettings(); - }); -} - -function accessTokenIsSet() { - return Settings.data.values.metaverse.access_token.length > 0; -} - -function setupHFAccountButton() { - - var hasAccessToken = accessTokenIsSet(); - var el; - - if (hasAccessToken) { - el = "

    "; - el += ""; - el += ""; - el += "

    "; - el = $(el); - } else { - // setup an object for the settings we want our button to have - var buttonSetting = { - type: 'button', - name: 'connected_account', - label: 'Connected Account', - } - buttonSetting.help = ""; - buttonSetting.classes = "btn-primary"; - buttonSetting.button_label = "Connect High Fidelity Account"; - buttonSetting.html_id = Settings.CONNECT_ACCOUNT_BTN_ID; - - buttonSetting.href = URLs.METAVERSE_URL + "/user/tokens/new?for_domain_server=true"; - - // since we do not have an access token we change hide domain ID and auto networking settings - // without an access token niether of them can do anything - $("[data-keypath='metaverse.id']").hide(); - - // use the existing getFormGroup helper to ask for a button - el = viewHelpers.getFormGroup('', buttonSetting, Settings.data.values); - } - - // add the button group to the top of the metaverse panel - $('#metaverse .panel-body').prepend(el); -} - -function disonnectHighFidelityAccount() { - // the user clicked on the disconnect account btn - give them a sweet alert to make sure this is what they want to do - swal({ - title: "Are you sure?", - text: "This will remove your domain-server OAuth access token." - + "

    This could cause your domain to appear offline and no longer be reachable via any place names.", - type: "warning", - html: true, - showCancelButton: true - }, function(){ - // we need to post to settings to clear the access-token - $(Settings.ACCESS_TOKEN_SELECTOR).val('').change(); - // reset the domain id to get a new temporary name - $(Settings.DOMAIN_ID_SELECTOR).val('').change(); - saveSettings(); - }); -} - -function showSpinnerAlert(title) { - swal({ - title: title, - text: '
    ', - html: true, - showConfirmButton: false, - allowEscapeKey: false - }); -} - -function showDomainCreationAlert(justConnected) { - swal({ - title: 'Create new domain ID', - type: 'input', - text: 'Enter a label this machine.

    This will help you identify which domain ID belongs to which machine.

    ', - showCancelButton: true, - confirmButtonText: "Create", - closeOnConfirm: false, - html: true - }, function(inputValue){ - if (inputValue === false) { - swal.close(); - - // user cancelled domain ID creation - if we're supposed to save after cancel then save here - if (justConnected) { - saveSettings(); - } - } else { - // we're going to change the alert to a new one with a spinner while we create this domain - showSpinnerAlert('Creating domain ID'); - createNewDomainID(inputValue, justConnected); - } - }); -} - -function createNewDomainID(label, justConnected) { - // get the JSON object ready that we'll use to create a new domain - var domainJSON = { - "label": label - } - - $.post("/api/domains", domainJSON, function(data){ - // we successfully created a domain ID, set it on that field - var domainID = data.domain.id; - console.log("Setting domain id to ", data, domainID); - $(Settings.DOMAIN_ID_SELECTOR).val(domainID).change(); - - if (justConnected) { - var successText = Strings.CREATE_DOMAIN_SUCCESS_JUST_CONNECTED - } else { - var successText = Strings.CREATE_DOMAIN_SUCCESS; - } - - successText += "

    Click the button below to save your new settings and restart your domain-server."; - - // show a sweet alert to say we are all finished up and that we need to save - swal({ - title: 'Success!', - type: 'success', - text: successText, - html: true, - confirmButtonText: 'Save' - }, function(){ - saveSettings(); - }); - }, 'json').fail(function(){ - - var errorText = "There was a problem creating your new domain ID. Do you want to try again or"; - - if (justConnected) { - errorText += " just save your new access token?

    You can always create a new domain ID later."; - } else { - errorText += " cancel?" - } - - // we failed to create the new domain ID, show a sweet-alert that lets them try again or cancel - swal({ - title: '', - type: 'error', - text: errorText, - html: true, - confirmButtonText: 'Try again', - showCancelButton: true, - closeOnConfirm: false - }, function(isConfirm){ - if (isConfirm) { - // they want to try creating a domain ID again - showDomainCreationAlert(justConnected); - } else { - // they want to cancel - if (justConnected) { - // since they just connected we need to save the access token here - saveSettings(); - } - } - }); - }); -} - -function createDomainSpinner() { - var spinner = ''; - return spinner; -} - -function createDomainLoadingError(message) { - var errorEl = $(""); - errorEl.append(message + " "); - - var retryLink = $("Please click here to try again."); - retryLink.click(function(ev) { - ev.preventDefault(); - reloadDomainInfo(); - }); - errorEl.append(retryLink); - - return errorEl; -} - -function parseJSONResponse(xhr) { - try { - return JSON.parse(xhr.responseText); - } catch (e) { - } - return null; -} - -function showOrHideLabel() { - var type = getCurrentDomainIDType(); - var shouldShow = accessTokenIsSet() && (type === DOMAIN_ID_TYPE_FULL || type === DOMAIN_ID_TYPE_UNKNOWN); - $(".panel#label").toggle(shouldShow); - $("li a[href='#label']").parent().toggle(shouldShow); - return shouldShow; -} - -function setupDomainLabelSetting() { - showOrHideLabel(); - - var html = "
    " - html += " Edit"; - html += ""; - html += "
    "; - - html = $(html); - - html.find('a').click(function(ev) { - ev.preventDefault(); - - var label = DomainInfo.label === null ? "" : DomainInfo.label; - var modal_body = "
    "; - modal_body += ""; - modal_body += ""; - modal_body += "
    "; - modal_body += "
    "; - - var dialog = bootbox.dialog({ - title: 'Edit Label', - message: modal_body, - closeButton: false, - onEscape: true, - buttons: [ - { - label: 'Cancel', - className: 'edit-label-cancel-btn', - callback: function() { - dialog.modal('hide'); - } - }, - { - label: 'Save', - className: 'edit-label-save-btn btn btn-primary', - callback: function() { - var data = { - label: $('#domain-label-input').val() - }; - - $('.edit-label-cancel-btn').attr('disabled', 'disabled'); - $('.edit-label-save-btn').attr('disabled', 'disabled'); - $('.edit-label-save-btn').html("Saving..."); - - $('.error-message').hide(); - - $.ajax({ - url: '/api/domains', - type: 'PUT', - data: data, - success: function(xhr) { - dialog.modal('hide'); - reloadDomainInfo(); - }, - error: function(xhr) { - var data = parseJSONResponse(xhr); - console.log(data, data.status, data.data); - if (data.status === "fail") { - for (var key in data.data) { - var errorMsg = data.data[key]; - var errorEl = $('.error-message[data-property="' + key + '"'); - errorEl.html(errorMsg); - errorEl.show(); - } - } - $('.edit-label-cancel-btn').removeAttr('disabled'); - $('.edit-label-save-btn').removeAttr('disabled'); - $('.edit-label-save-btn').html("Save"); - } - }); - return false; - } - } - ], - callback: function(result) { - console.log("result: ", result); - } - }); - }); - - var spinner = createDomainSpinner(); - var errorEl = createDomainLoadingError("Error loading label."); - - html.append(spinner); - html.append(errorEl); - - $('div#label .panel-body').append(html); -} - -function showOrHideAutomaticNetworking() { - var type = getCurrentDomainIDType(); - if (!accessTokenIsSet() || (type !== DOMAIN_ID_TYPE_FULL && type !== DOMAIN_ID_TYPE_UNKNOWN)) { - $("[data-keypath='metaverse.automatic_networking']").hide(); - return false; - } - $("[data-keypath='metaverse.automatic_networking']").show(); - return true; -} - -function setupDomainNetworkingSettings() { - if (!showOrHideAutomaticNetworking()) { - return; - } - - var autoNetworkingSetting = Settings.data.values.metaverse.automatic_networking; - if (autoNetworkingSetting === 'full') { - return; - } - - var includeAddress = autoNetworkingSetting === 'disabled'; - - if (includeAddress) { - var label = "Network Address:Port"; - } else { - var label = "Network Port"; - } - - var lowerName = name.toLowerCase(); - var form = '
    '; - form += ''; - form += ' Edit'; - form += ''; - form += '
    This defines how nodes will connect to your domain. You can read more about automatic networking here.
    '; - form += '
    '; - - form = $(form); - - form.find('#edit-network-address-port').click(function(ev) { - ev.preventDefault(); - - var address = DomainInfo.network_address === null ? '' : DomainInfo.network_address; - var port = DomainInfo.network_port === null ? '' : DomainInfo.network_port; - var modal_body = "
    "; - if (includeAddress) { - modal_body += ""; - modal_body += ""; - modal_body += "
    "; - } - modal_body += ""; - modal_body += ""; - modal_body += "
    "; - modal_body += "
    "; - - var dialog = bootbox.dialog({ - title: 'Edit Network', - message: modal_body, - closeButton: false, - onEscape: true, - buttons: [ - { - label: 'Cancel', - className: 'edit-network-cancel-btn', - callback: function() { - dialog.modal('hide'); - } - }, - { - label: 'Save', - className: 'edit-network-save-btn btn btn-primary', - callback: function() { - var data = { - network_port: $('#network-port-input').val() - }; - if (includeAddress) { - data.network_address = $('#network-address-input').val(); - } - - $('.edit-network-cancel-btn').attr('disabled', 'disabled'); - $('.edit-network-save-btn').attr('disabled', 'disabled'); - $('.edit-network-save-btn').html("Saving..."); - - console.log('data', data); - - $('.error-message').hide(); - - $.ajax({ - url: '/api/domains', - type: 'PUT', - data: data, - success: function(xhr) { - console.log(xhr, parseJSONResponse(xhr)); - dialog.modal('hide'); - reloadDomainInfo(); - }, - error:function(xhr) { - var data = parseJSONResponse(xhr); - console.log(data, data.status, data.data); - if (data.status === "fail") { - for (var key in data.data) { - var errorMsg = data.data[key]; - console.log(key, errorMsg); - var errorEl = $('.error-message[data-property="' + key + '"'); - console.log(errorEl); - errorEl.html(errorMsg); - errorEl.show(); - } - } - $('.edit-network-cancel-btn').removeAttr('disabled'); - $('.edit-network-save-btn').removeAttr('disabled'); - $('.edit-network-save-btn').html("Save"); - } - }); - return false; - } - } - ], - callback: function(result) { - console.log("result: ", result); - } - }); - }); - - var spinner = createDomainSpinner(); - - var errorMessage = '' - if (includeAddress) { - errorMessage = "We were unable to load the network address and port."; - } else { - errorMessage = "We were unable to load the network port." - } - var errorEl = createDomainLoadingError(errorMessage); - - var autoNetworkingEl = $('div[data-keypath="metaverse.automatic_networking"]'); - autoNetworkingEl.after(spinner); - autoNetworkingEl.after(errorEl); - autoNetworkingEl.after(form); -} - - -function setupPlacesTable() { - // create a dummy table using our view helper - var placesTableSetting = { - type: 'table', - name: 'places', - label: 'Places', - html_id: Settings.PLACES_TABLE_ID, - help: "The following places currently point to this domain.
    To point places to this domain, " - + " go to the My Places " - + "page in your High Fidelity Metaverse account.", - read_only: true, - can_add_new_rows: false, - columns: [ + var qs = (function(a) { + if (a == "") return {}; + var b = {}; + for (var i = 0; i < a.length; ++i) { - "name": "name", - "label": "Name" - }, - { - "name": "path", - "label": "Viewpoint or Path" - }, - { - "name": "remove", - "label": "", - "class": "buttons" - } - ] - } - - // get a table for the places - var placesTableGroup = viewHelpers.getFormGroup('', placesTableSetting, Settings.data.values); - - // append the places table in the right place - $('#places_paths .panel-body').prepend(placesTableGroup); - //$('#' + Settings.PLACES_TABLE_ID).append(""); - - var spinner = createDomainSpinner(); - $('#' + Settings.PLACES_TABLE_ID).after($(spinner)); - - var errorEl = createDomainLoadingError("There was an error retreiving your places."); - $("#" + Settings.PLACES_TABLE_ID).after(errorEl); - - // do we have a domain ID? - if (Settings.data.values.metaverse.id.length == 0) { - // we don't have a domain ID - add a button to offer the user a chance to get a temporary one - var temporaryPlaceButton = dynamicButton(Settings.GET_TEMPORARY_NAME_BTN_ID, 'Get a temporary place name'); - $('#' + Settings.PLACES_TABLE_ID).after(temporaryPlaceButton); - } - if (accessTokenIsSet()) { - appendAddButtonToPlacesTable(); - } -} - -function placeTableRow(name, path, isTemporary, placeID) { - var name_link = "" + (isTemporary ? name + " (temporary)" : name) + ""; - - function placeEditClicked() { - editHighFidelityPlace(placeID, name, path); - } - - function placeDeleteClicked() { - var el = $(this); - var confirmString = Strings.REMOVE_PLACE_TITLE.replace("{{place}}", name); - var dialog = bootbox.dialog({ - message: confirmString, - closeButton: false, - onEscape: true, - buttons: [ - { - label: Strings.REMOVE_PLACE_CANCEL_BUTTON, - className: "delete-place-cancel-btn", - callback: function() { - dialog.modal('hide'); - } - }, - { - label: Strings.REMOVE_PLACE_DELETE_BUTTON, - className: "delete-place-confirm-btn btn btn-danger", - callback: function() { - $('.delete-place-cancel-btn').attr('disabled', 'disabled'); - $('.delete-place-confirm-btn').attr('disabled', 'disabled'); - $('.delete-place-confirm-btn').html(Strings.REMOVE_PLACE_DELETE_BUTTON_PENDING); - sendUpdatePlaceRequest( - placeID, - '', - null, - true, - function() { - reloadDomainInfo(); - dialog.modal('hide'); - }, function() { - $('.delete-place-cancel-btn').removeAttr('disabled'); - $('.delete-place-confirm-btn').removeAttr('disabled'); - $('.delete-place-confirm-btn').html(Strings.REMOVE_PLACE_DELETE_BUTTON); - bootbox.alert(Strings.REMOVE_PLACE_ERROR); - }); - return false; - } - }, - ] - }); - } - - if (isTemporary) { - var editLink = ""; - var deleteColumn = ""; - } else { - var editLink = " Edit"; - var deleteColumn = ""; - } - - var row = $("" + name_link + "" + path + editLink + "" + deleteColumn + ""); - row.find(".place-edit").click(placeEditClicked); - row.find(".place-delete").click(placeDeleteClicked); - - return row; -} - -function placeTableRowForPlaceObject(place) { - var placePathOrIndex = (place.path ? place.path : "/"); - return placeTableRow(place.name, placePathOrIndex, false, place.id); -} - -function reloadDomainInfo() { - $('#' + Settings.PLACES_TABLE_ID + " tbody tr").not('.headers').remove(); - - $('.domain-loading-show').show(); - $('.domain-loading-hide').hide(); - $('.domain-loading-error').hide(); - $('.loading-domain-info-spinner').show(); - $('#' + Settings.PLACES_TABLE_ID).append("Hello"); - - getDomainFromAPI(function(data){ - $('.loading-domain-info-spinner').hide(); - $('.domain-loading-show').hide(); - - // check if we have owner_places (for a real domain) or a name (for a temporary domain) - if (data.status == "success") { - $('.domain-loading-hide').show(); - if (data.domain.owner_places) { - // add a table row for each of these names - _.each(data.domain.owner_places, function(place){ - $('#' + Settings.PLACES_TABLE_ID + " tbody").append(placeTableRowForPlaceObject(place)); - }); - } else if (data.domain.name) { - // add a table row for this temporary domain name - $('#' + Settings.PLACES_TABLE_ID + " tbody").append(placeTableRow(data.domain.name, '/', true)); - } - - // Update label - if (showOrHideLabel()) { - var label = data.domain.label; - label = label === null ? '' : label; - $('#network-label').val(label); - } - - // Update automatic networking - if (showOrHideAutomaticNetworking()) { - var autoNetworkingSetting = Settings.data.values.metaverse.automatic_networking; - var address = data.domain.network_address === null ? "" : data.domain.network_address; - var port = data.domain.network_port === null ? "" : data.domain.network_port; - if (autoNetworkingSetting === 'disabled') { - $('#network-address-port input').val(address + ":" + port); - } else if (autoNetworkingSetting === 'ip') { - $('#network-address-port input').val(port); - } - } - - if (accessTokenIsSet()) { - appendAddButtonToPlacesTable(); - } - - } else { - $('.domain-loading-error').show(); - } - }) -} - -function appendDomainIDButtons() { - var domainIDInput = $(Settings.DOMAIN_ID_SELECTOR); - - var createButton = dynamicButton(Settings.CREATE_DOMAIN_ID_BTN_ID, Strings.CREATE_DOMAIN_BUTTON); - createButton.css('margin-top', '10px'); - var chooseButton = dynamicButton(Settings.CHOOSE_DOMAIN_ID_BTN_ID, Strings.CHOOSE_DOMAIN_BUTTON); - chooseButton.css('margin', '10px 0px 0px 10px'); - - domainIDInput.after(chooseButton); - domainIDInput.after(createButton); -} - -function editHighFidelityPlace(placeID, name, path) { - var dialog; - - var modal_body = "
    "; - modal_body += ""; - modal_body += "
    "; - - var modal_buttons = [ - { - label: Strings.EDIT_PLACE_CANCEL_BUTTON, - className: "edit-place-cancel-button", - callback: function() { - dialog.modal('hide'); - } - }, - { - label: Strings.EDIT_PLACE_CONFIRM_BUTTON, - className: 'edit-place-save-button btn btn-primary', - callback: function() { - var placePath = $('#place-path-input').val(); - - if (path == placePath) { - return true; - } - - $('.edit-place-cancel-button').attr('disabled', 'disabled'); - $('.edit-place-save-button').attr('disabled', 'disabled'); - $('.edit-place-save-button').html(Strings.EDIT_PLACE_BUTTON_PENDING); - - sendUpdatePlaceRequest( - placeID, - placePath, - null, - false, - function() { - dialog.modal('hide') - reloadDomainInfo(); - }, - function() { - $('.edit-place-cancel-button').removeAttr('disabled'); - $('.edit-place-save-button').removeAttr('disabled'); - $('.edit-place-save-button').html(Strings.EDIT_PLACE_CONFIRM_BUTTON); - } - ); - - return false; - } - } - ]; - - dialog = bootbox.dialog({ - title: Strings.EDIT_PLACE_TITLE, - closeButton: false, - onEscape: true, - message: modal_body, - buttons: modal_buttons - }) -} - -function appendAddButtonToPlacesTable() { - var addRow = $(" "); - addRow.find(".place-add").click(function(ev) { - ev.preventDefault(); - chooseFromHighFidelityPlaces(Settings.initialValues.metaverse.access_token, null, function(placeName, newDomainID) { - if (newDomainID) { - Settings.data.values.metaverse.id = newDomainID; - var domainIDEl = $("[data-keypath='metaverse.id']"); - domainIDEl.val(newDomainID); - Settings.initialValues.metaverse.id = newDomainID; - badgeSidebarForDifferences(domainIDEl); - } - reloadDomainInfo(); - }); - }); - $('#' + Settings.PLACES_TABLE_ID + " tbody").append(addRow); -} - -function chooseFromHighFidelityDomains(clickedButton) { - // setup the modal to help user pick their domain - if (Settings.initialValues.metaverse.access_token) { - - // add a spinner to the choose button - clickedButton.html("Loading domains..."); - clickedButton.attr('disabled', 'disabled'); - - // get a list of user domains from data-web - $.ajax({ - url: "/api/domains", - dataType: 'json', - jsonp: false, - success: function(data){ - - var modal_buttons = { - cancel: { - label: 'Cancel', - className: 'btn-default' - } - } - - if (data.data.domains.length) { - // setup a select box for the returned domains - modal_body = "

    Choose the High Fidelity domain you want this domain-server to represent.
    This will set your domain ID on the settings page.

    "; - domain_select = $(""); - _.each(data.data.domains, function(domain){ - var domainString = ""; - - if (domain.label) { - domainString += '"' + domain.label+ '" - '; - } - - domainString += domain.id; - - domain_select.append(""); - }) - modal_body += "" + domain_select[0].outerHTML - modal_buttons["success"] = { - label: 'Choose domain', - callback: function() { - domainID = $('#domain-name-select').val() - // set the domain ID on the form - $(Settings.DOMAIN_ID_SELECTOR).val(domainID).change(); - } - } + var p=a[i].split('=', 2); + if (p.length == 1) { + b[p[0]] = ""; } else { - modal_buttons["success"] = { - label: 'Create new domain', - callback: function() { - window.open(URLs.METAVERSE_URL + "/user/domains", '_blank'); - } - } - modal_body = "

    You do not have any domains in your High Fidelity account." + - "

    Go to your domains page to create a new one. Once your domain is created re-open this dialog to select it.

    " + b[p[0]] = decodeURIComponent(p[1].replace(/\+/g, " ")); } - - bootbox.dialog({ - title: "Choose matching domain", - onEscape: true, - message: modal_body, - buttons: modal_buttons - }) - }, - error: function() { - bootbox.alert("Failed to retrieve your domains from the High Fidelity Metaverse"); - }, - complete: function() { - // remove the spinner from the choose button - clickedButton.html("Choose from my domains") - clickedButton.removeAttr('disabled') } - }); - - } else { - bootbox.alert({ - message: "You must have an access token to query your High Fidelity domains.

    " + - "Please follow the instructions on the settings page to add an access token.", - title: "Access token required" - }) - } - } - -function createTemporaryDomain() { - swal({ - title: 'Create temporary place name', - text: "This will create a temporary place name and domain ID" - + " so other users can easily connect to your domain.

    " - + "In order to make your domain reachable, this will also enable full automatic networking.", - showCancelButton: true, - confirmButtonText: 'Create', - closeOnConfirm: false, - html: true - }, function(isConfirm){ - if (isConfirm) { - showSpinnerAlert('Creating temporary place name'); - - // make a get request to get a temporary domain - $.post(URLs.METAVERSE_URL + '/api/v1/domains/temporary', function(data){ - if (data.status == "success") { - var domain = data.data.domain; - - // we should have a new domain ID - set it on the domain ID value - $(Settings.DOMAIN_ID_SELECTOR).val(domain.id).change(); - - // we also need to make sure auto networking is set to full - $('[data-hidden-input="metaverse.automatic_networking"]').val("full").change(); - - swal({ - type: 'success', - title: 'Success!', - text: "We have created a temporary name and domain ID for you.

    " - + "Your temporary place name is " + domain.name + ".

    " - + "Press the button below to save your new settings and restart your domain-server.", - confirmButtonText: 'Save', - html: true - }, function(){ - saveSettings(); - }); - } - }); - } - }); -} - -function reloadSettings(callback) { - $.getJSON('/settings.json', function(data){ - _.extend(data, viewHelpers) - - $('.nav-stacked').html(Settings.sidebarTemplate(data)) - $('#panels').html(Settings.panelsTemplate(data)) - - Settings.data = data; - Settings.initialValues = form2js('settings-form', ".", false, cleanupFormValues, true); + return b; + })(window.location.search.substr(1).split('&')); + Settings.afterReloadActions = function() { // append the domain selection modal appendDomainIDButtons(); @@ -1291,117 +49,10 @@ function reloadSettings(callback) { } } - // setup any bootstrap switches - $('.toggle-checkbox').bootstrapSwitch(); - - $('[data-toggle="tooltip"]').tooltip(); - - // call the callback now that settings are loaded - callback(true); - }).fail(function() { - // call the failure object since settings load faild - callback(false) - }); -} - -function validateInputs() { - // check if any new values are bad - var tables = $('table'); - - var inputsValid = true; - - var tables = $('table'); - - // clear any current invalid rows - $('tr.' + Settings.INVALID_ROW_CLASS).removeClass(Settings.INVALID_ROW_CLASS); - - function markParentRowInvalid(rowChild) { - $(rowChild).closest('tr').addClass(Settings.INVALID_ROW_CLASS); + handleAction(); } - _.each(tables, function(table) { - // validate keys specificially for spaces and equality to an existing key - var newKeys = $(table).find('tr.' + Settings.NEW_ROW_CLASS + ' td.key'); - - var keyWithSpaces = false; - var empty = false; - var duplicateKey = false; - - _.each(newKeys, function(keyCell) { - var keyVal = $(keyCell).children('input').val(); - - if (keyVal.indexOf(' ') !== -1) { - keyWithSpaces = true; - markParentRowInvalid(keyCell); - return; - } - - // make sure the key isn't empty - if (keyVal.length === 0) { - empty = true - - markParentRowInvalid(input); - return; - } - - // make sure we don't have duplicate keys in the table - var otherKeys = $(table).find('td.key').not(keyCell); - _.each(otherKeys, function(otherKeyCell) { - var keyInput = $(otherKeyCell).children('input'); - - if (keyInput.length) { - if ($(keyInput).val() == keyVal) { - duplicateKey = true; - } - } else if ($(otherKeyCell).html() == keyVal) { - duplicateKey = true; - } - - if (duplicateKey) { - markParentRowInvalid(keyCell); - return; - } - }); - - }); - - if (keyWithSpaces) { - showErrorMessage("Error", "Key contains spaces"); - inputsValid = false; - return - } - - if (empty) { - showErrorMessage("Error", "Empty field(s)"); - inputsValid = false; - return - } - - if (duplicateKey) { - showErrorMessage("Error", "Two keys cannot be identical"); - inputsValid = false; - return; - } - }); - - return inputsValid; -} - -var SETTINGS_ERROR_MESSAGE = "There was a problem saving domain settings. Please try again!"; - -function saveSettings() { - - if (validateInputs()) { - // POST the form JSON to the domain-server settings.json endpoint so the settings are saved - var canPost = true; - - // disable any inputs not changed - $("input:not([data-changed])").each(function () { - $(this).prop('disabled', true); - }); - - // grab a JSON representation of the form via form2js - var formJSON = form2js('settings-form', ".", false, cleanupFormValues, true); + Settings.handlePostSettings = function(formJSON) { // check if we've set the basic http password if (formJSON["security"]) { @@ -1424,727 +75,945 @@ function saveSettings() { delete formJSON["security"]["verify_http_password"]; } else { bootbox.alert({ "message": "Passwords must match!", "title": "Password Error" }); - canPost = false; + return false; } } } - console.log("----- SAVING ------"); + console.log("----- handlePostSettings() called ------"); console.log(formJSON); - // re-enable all inputs - $("input").each(function () { - $(this).prop('disabled', false); - }); + if (formJSON["security"]) { + var username = formJSON["security"]["http_username"]; - // remove focus from the button - $(this).blur(); + var password = formJSON["security"]["http_password"]; - if (canPost) { - if (formJSON["security"]) { - var username = formJSON["security"]["http_username"]; + if ((password == sha256_digest("")) && (username == undefined || (username && username.length != 0))) { + swal({ + title: "Are you sure?", + text: "You have entered a blank password with a non-blank username. Are you sure you want to require a blank password?", + type: "warning", + showCancelButton: true, + confirmButtonColor: "#5cb85c", + confirmButtonText: "Yes!", + closeOnConfirm: true + }, + function () { + formJSON["security"]["http_password"] = ""; - var password = formJSON["security"]["http_password"]; + postSettings(formJSON); + }); - if ((password == sha256_digest("")) && (username == undefined || (username && username.length != 0))) { - swal({ - title: "Are you sure?", - text: "You have entered a blank password with a non-blank username. Are you sure you want to require a blank password?", - type: "warning", - showCancelButton: true, - confirmButtonColor: "#5cb85c", - confirmButtonText: "Yes!", - closeOnConfirm: true - }, - function () { - formJSON["security"]["http_password"] = ""; - postSettings(formJSON); - }); - return; - } + return; } - // POST the form JSON to the domain-server settings.json endpoint so the settings are saved - postSettings(formJSON); } - } -} -$('body').on('click', '.save-button', function(e){ - saveSettings(); - return false; -}); + postSettings(formJSON); + }; -function makeTable(setting, keypath, setting_value) { - var isArray = !_.has(setting, 'key'); - var categoryKey = setting.categorize_by_key; - var isCategorized = !!categoryKey && isArray; - - if (!isArray && setting.can_order) { - setting.can_order = false; - } - - var html = ""; - - if (setting.help) { - html += "" + setting.help + "" - } - - var nonDeletableRowKey = setting["non-deletable-row-key"]; - var nonDeletableRowValues = setting["non-deletable-row-values"]; - - html += ""; - - if (setting.caption) { - html += "" - } - - // Column groups - if (setting.groups) { - html += "" - _.each(setting.groups, function (group) { - html += "" - }) - if (!setting.read_only) { - if (setting.can_order) { - html += ""; - } - html += "" - } - html += "" - } - - // Column names - html += "" - - if (setting.numbered === true) { - html += "" // Row number - } - - if (setting.key) { - html += "" // Key - } - - var numVisibleColumns = 0; - _.each(setting.columns, function(col) { - if (!col.hidden) numVisibleColumns++; - html += "" // Data + $('#' + Settings.FORM_ID).on('click', '#' + Settings.CREATE_DOMAIN_ID_BTN_ID, function(){ + $(this).blur(); + showDomainCreationAlert(false); }) - if (!setting.read_only) { - if (setting.can_order) { - numVisibleColumns++; - html += ""; - } - numVisibleColumns++; - html += ""; + $('#' + Settings.FORM_ID).on('click', '#' + Settings.CHOOSE_DOMAIN_ID_BTN_ID, function(){ + $(this).blur(); + chooseFromHighFidelityDomains($(this)) + }); + + $('#' + Settings.FORM_ID).on('click', '#' + Settings.GET_TEMPORARY_NAME_BTN_ID, function(){ + $(this).blur(); + createTemporaryDomain(); + }); + + $('#' + Settings.FORM_ID).on('click', '#' + Settings.DISCONNECT_ACCOUNT_BTN_ID, function(e){ + $(this).blur(); + disonnectHighFidelityAccount(); + e.preventDefault(); + }); + + $('#' + Settings.FORM_ID).on('click', '#' + Settings.CONNECT_ACCOUNT_BTN_ID, function(e){ + $(this).blur(); + prepareAccessTokenPrompt(function(accessToken) { + // we have an access token - set the access token input with this and save settings + $(Settings.ACCESS_TOKEN_SELECTOR).val(accessToken).change(); + saveSettings(); + }); + }); + + function accessTokenIsSet() { + return Settings.data.values.metaverse.access_token.length > 0; } - // populate rows in the table from existing values - var row_num = 1; - - if (keypath.length > 0 && _.size(setting_value) > 0) { - var rowIsObject = setting.columns.length > 1; - - _.each(setting_value, function(row, rowIndexOrName) { - var categoryPair = {}; - var categoryValue = ""; - if (isCategorized) { - categoryValue = rowIsObject ? row[categoryKey] : row; - categoryPair[categoryKey] = categoryValue; - if (_.findIndex(setting_value, categoryPair) === rowIndexOrName) { - html += makeTableCategoryHeader(categoryKey, categoryValue, numVisibleColumns, setting.can_add_new_categories, ""); + function getShareName(callback) { + getDomainFromAPI(function(data){ + // check if we have owner_places (for a real domain) or a name (for a temporary domain) + if (data && data.status == "success") { + var shareName; + if (data.domain.default_place_name) { + shareName = data.domain.default_place_name; + } else if (data.domain.name) { + shareName = data.domain.name; + } else if (data.domain.network_address) { + shareName = data.domain.network_address; + if (data.domain.network_port !== 40102) { + shareName += ':' + data.domain.network_port; + } } + + callback(true, shareName); + } else { + callback(false); + } + }) + } + + function handleAction() { + // check if we were passed an action to handle + var action = qs["action"]; + + if (action == "share") { + // figure out if we already have a stored domain ID + if (Settings.data.values.metaverse.id.length > 0) { + // we need to ask the API what a shareable name for this domain is + getShareName(function(success, shareName){ + if (success) { + var shareLink = "hifi://" + shareName; + + console.log(shareLink); + + // show a dialog with a copiable share URL + swal({ + title: "Share", + type: "input", + inputPlaceholder: shareLink, + inputValue: shareLink, + text: "Copy this URL to invite friends to your domain.", + closeOnConfirm: true + }); + + $('.sweet-alert input').select(); + + } else { + // show an error alert + swal({ + title: '', + type: 'error', + text: "There was a problem retreiving domain information from High Fidelity API.", + confirmButtonText: 'Try again', + showCancelButton: true, + closeOnConfirm: false + }, function(isConfirm){ + if (isConfirm) { + // they want to try getting domain share info again + showSpinnerAlert("Requesting domain information...") + handleAction(); + } else { + swal.close(); + } + }); + } + }); + } else { + // no domain ID present, just show the share dialog + createTemporaryDomain(); + } + } + } + + function setupHFAccountButton() { + + var hasAccessToken = accessTokenIsSet(); + var el; + + if (hasAccessToken) { + el = "

    "; + el += ""; + el += ""; + el += "

    "; + el = $(el); + } else { + // setup an object for the settings we want our button to have + var buttonSetting = { + type: 'button', + name: 'connected_account', + label: 'Connected Account', + } + buttonSetting.help = ""; + buttonSetting.classes = "btn-primary"; + buttonSetting.button_label = "Connect High Fidelity Account"; + buttonSetting.html_id = Settings.CONNECT_ACCOUNT_BTN_ID; + + buttonSetting.href = URLs.METAVERSE_URL + "/user/tokens/new?for_domain_server=true"; + + // since we do not have an access token we change hide domain ID and auto networking settings + // without an access token niether of them can do anything + $("[data-keypath='metaverse.id']").hide(); + + // use the existing getFormGroup helper to ask for a button + el = viewHelpers.getFormGroup('', buttonSetting, Settings.data.values); + } + + // add the button group to the top of the metaverse panel + $('#metaverse .panel-body').prepend(el); + } + + function disonnectHighFidelityAccount() { + // the user clicked on the disconnect account btn - give them a sweet alert to make sure this is what they want to do + swal({ + title: "Are you sure?", + text: "This will remove your domain-server OAuth access token." + + "

    This could cause your domain to appear offline and no longer be reachable via any place names.", + type: "warning", + html: true, + showCancelButton: true + }, function(){ + // we need to post to settings to clear the access-token + $(Settings.ACCESS_TOKEN_SELECTOR).val('').change(); + // reset the domain id to get a new temporary name + $(Settings.DOMAIN_ID_SELECTOR).val('').change(); + saveSettings(); + }); + } + + function showDomainCreationAlert(justConnected) { + swal({ + title: 'Create new domain ID', + type: 'input', + text: 'Enter a label this machine.

    This will help you identify which domain ID belongs to which machine.

    ', + showCancelButton: true, + confirmButtonText: "Create", + closeOnConfirm: false, + html: true + }, function(inputValue){ + if (inputValue === false) { + swal.close(); + + // user cancelled domain ID creation - if we're supposed to save after cancel then save here + if (justConnected) { + saveSettings(); + } + } else { + // we're going to change the alert to a new one with a spinner while we create this domain + showSpinnerAlert('Creating domain ID'); + createNewDomainID(inputValue, justConnected); + } + }); + } + + function createNewDomainID(label, justConnected) { + // get the JSON object ready that we'll use to create a new domain + var domainJSON = { + "label": label + } + + $.post("/api/domains", domainJSON, function(data){ + // we successfully created a domain ID, set it on that field + var domainID = data.domain.id; + console.log("Setting domain id to ", data, domainID); + $(Settings.DOMAIN_ID_SELECTOR).val(domainID).change(); + + if (justConnected) { + var successText = Strings.CREATE_DOMAIN_SUCCESS_JUST_CONNECTED + } else { + var successText = Strings.CREATE_DOMAIN_SUCCESS; } - html += ""; + successText += "

    Click the button below to save your new settings and restart your domain-server."; - if (setting.numbered === true) { - html += "" + // show a sweet alert to say we are all finished up and that we need to save + swal({ + title: 'Success!', + type: 'success', + text: successText, + html: true, + confirmButtonText: 'Save' + }, function(){ + saveSettings(); + }); + }, 'json').fail(function(){ + + var errorText = "There was a problem creating your new domain ID. Do you want to try again or"; + + if (justConnected) { + errorText += " just save your new access token?

    You can always create a new domain ID later."; + } else { + errorText += " cancel?" } - if (setting.key) { - html += "" - } - - var isNonDeletableRow = !setting.can_add_new_rows; - - _.each(setting.columns, function(col) { - - var colValue, colName; - if (isArray) { - colValue = rowIsObject ? row[col.name] : row; - colName = keypath + "[" + rowIndexOrName + "]" + (rowIsObject ? "." + col.name : ""); + // we failed to create the new domain ID, show a sweet-alert that lets them try again or cancel + swal({ + title: '', + type: 'error', + text: errorText, + html: true, + confirmButtonText: 'Try again', + showCancelButton: true, + closeOnConfirm: false + }, function(isConfirm){ + if (isConfirm) { + // they want to try creating a domain ID again + showDomainCreationAlert(justConnected); } else { - colValue = row[col.name]; - colName = keypath + "." + rowIndexOrName + "." + col.name; + // they want to cancel + if (justConnected) { + // since they just connected we need to save the access token here + saveSettings(); + } + } + }); + }); + } + + function createDomainSpinner() { + var spinner = ''; + return spinner; + } + + function createDomainLoadingError(message) { + var errorEl = $(""); + errorEl.append(message + " "); + + var retryLink = $("Please click here to try again."); + retryLink.click(function(ev) { + ev.preventDefault(); + reloadDomainInfo(); + }); + errorEl.append(retryLink); + + return errorEl; + } + + function showOrHideLabel() { + var type = getCurrentDomainIDType(); + var shouldShow = accessTokenIsSet() && (type === DOMAIN_ID_TYPE_FULL || type === DOMAIN_ID_TYPE_UNKNOWN); + $(".panel#label").toggle(shouldShow); + $("li a[href='#label']").parent().toggle(shouldShow); + return shouldShow; + } + + function setupDomainLabelSetting() { + showOrHideLabel(); + + var html = "
    " + html += " Edit"; + html += ""; + html += "
    "; + + html = $(html); + + html.find('a').click(function(ev) { + ev.preventDefault(); + + var label = DomainInfo.label === null ? "" : DomainInfo.label; + var modal_body = "
    "; + modal_body += ""; + modal_body += ""; + modal_body += "
    "; + modal_body += "
    "; + + var dialog = bootbox.dialog({ + title: 'Edit Label', + message: modal_body, + closeButton: false, + onEscape: true, + buttons: [ + { + label: 'Cancel', + className: 'edit-label-cancel-btn', + callback: function() { + dialog.modal('hide'); + } + }, + { + label: 'Save', + className: 'edit-label-save-btn btn btn-primary', + callback: function() { + var data = { + label: $('#domain-label-input').val() + }; + + $('.edit-label-cancel-btn').attr('disabled', 'disabled'); + $('.edit-label-save-btn').attr('disabled', 'disabled'); + $('.edit-label-save-btn').html("Saving..."); + + $('.error-message').hide(); + + $.ajax({ + url: '/api/domains', + type: 'PUT', + data: data, + success: function(xhr) { + dialog.modal('hide'); + reloadDomainInfo(); + }, + error: function(xhr) { + var data = parseJSONResponse(xhr); + console.log(data, data.status, data.data); + if (data.status === "fail") { + for (var key in data.data) { + var errorMsg = data.data[key]; + var errorEl = $('.error-message[data-property="' + key + '"'); + errorEl.html(errorMsg); + errorEl.show(); + } + } + $('.edit-label-cancel-btn').removeAttr('disabled'); + $('.edit-label-save-btn').removeAttr('disabled'); + $('.edit-label-save-btn').html("Save"); + } + }); + return false; + } + } + ], + callback: function(result) { + console.log("result: ", result); + } + }); + }); + + var spinner = createDomainSpinner(); + var errorEl = createDomainLoadingError("Error loading label."); + + html.append(spinner); + html.append(errorEl); + + $('div#label .panel-body').append(html); + } + + function showOrHideAutomaticNetworking() { + var type = getCurrentDomainIDType(); + if (!accessTokenIsSet() || (type !== DOMAIN_ID_TYPE_FULL && type !== DOMAIN_ID_TYPE_UNKNOWN)) { + $("[data-keypath='metaverse.automatic_networking']").hide(); + return false; + } + $("[data-keypath='metaverse.automatic_networking']").show(); + return true; + } + + function setupDomainNetworkingSettings() { + if (!showOrHideAutomaticNetworking()) { + return; + } + + var autoNetworkingSetting = Settings.data.values.metaverse.automatic_networking; + if (autoNetworkingSetting === 'full') { + return; + } + + var includeAddress = autoNetworkingSetting === 'disabled'; + + if (includeAddress) { + var label = "Network Address:Port"; + } else { + var label = "Network Port"; + } + + var lowerName = name.toLowerCase(); + var form = '
    '; + form += ''; + form += ' Edit'; + form += ''; + form += '
    This defines how nodes will connect to your domain. You can read more about automatic networking here.
    '; + form += '
    '; + + form = $(form); + + form.find('#edit-network-address-port').click(function(ev) { + ev.preventDefault(); + + var address = DomainInfo.network_address === null ? '' : DomainInfo.network_address; + var port = DomainInfo.network_port === null ? '' : DomainInfo.network_port; + var modal_body = "
    "; + if (includeAddress) { + modal_body += ""; + modal_body += ""; + modal_body += "
    "; + } + modal_body += ""; + modal_body += ""; + modal_body += "
    "; + modal_body += "
    "; + + var dialog = bootbox.dialog({ + title: 'Edit Network', + message: modal_body, + closeButton: false, + onEscape: true, + buttons: [ + { + label: 'Cancel', + className: 'edit-network-cancel-btn', + callback: function() { + dialog.modal('hide'); + } + }, + { + label: 'Save', + className: 'edit-network-save-btn btn btn-primary', + callback: function() { + var data = { + network_port: $('#network-port-input').val() + }; + if (includeAddress) { + data.network_address = $('#network-address-input').val(); + } + + $('.edit-network-cancel-btn').attr('disabled', 'disabled'); + $('.edit-network-save-btn').attr('disabled', 'disabled'); + $('.edit-network-save-btn').html("Saving..."); + + console.log('data', data); + + $('.error-message').hide(); + + $.ajax({ + url: '/api/domains', + type: 'PUT', + data: data, + success: function(xhr) { + console.log(xhr, parseJSONResponse(xhr)); + dialog.modal('hide'); + reloadDomainInfo(); + }, + error:function(xhr) { + var data = parseJSONResponse(xhr); + console.log(data, data.status, data.data); + if (data.status === "fail") { + for (var key in data.data) { + var errorMsg = data.data[key]; + console.log(key, errorMsg); + var errorEl = $('.error-message[data-property="' + key + '"'); + console.log(errorEl); + errorEl.html(errorMsg); + errorEl.show(); + } + } + $('.edit-network-cancel-btn').removeAttr('disabled'); + $('.edit-network-save-btn').removeAttr('disabled'); + $('.edit-network-save-btn').html("Save"); + } + }); + return false; + } + } + ], + callback: function(result) { + console.log("result: ", result); + } + }); + }); + + var spinner = createDomainSpinner(); + + var errorMessage = '' + if (includeAddress) { + errorMessage = "We were unable to load the network address and port."; + } else { + errorMessage = "We were unable to load the network port." + } + var errorEl = createDomainLoadingError(errorMessage); + + var autoNetworkingEl = $('div[data-keypath="metaverse.automatic_networking"]'); + autoNetworkingEl.after(spinner); + autoNetworkingEl.after(errorEl); + autoNetworkingEl.after(form); + } + + + function setupPlacesTable() { + // create a dummy table using our view helper + var placesTableSetting = { + type: 'table', + name: 'places', + label: 'Places', + html_id: Settings.PLACES_TABLE_ID, + help: "The following places currently point to this domain.
    To point places to this domain, " + + " go to the My Places " + + "page in your High Fidelity Metaverse account.", + read_only: true, + can_add_new_rows: false, + columns: [ + { + "name": "name", + "label": "Name" + }, + { + "name": "path", + "label": "Viewpoint or Path" + }, + { + "name": "remove", + "label": "", + "class": "buttons" + } + ] + } + + // get a table for the places + var placesTableGroup = viewHelpers.getFormGroup('', placesTableSetting, Settings.data.values); + + // append the places table in the right place + $('#places_paths .panel-body').prepend(placesTableGroup); + //$('#' + Settings.PLACES_TABLE_ID).append(""); + + var spinner = createDomainSpinner(); + $('#' + Settings.PLACES_TABLE_ID).after($(spinner)); + + var errorEl = createDomainLoadingError("There was an error retreiving your places."); + $("#" + Settings.PLACES_TABLE_ID).after(errorEl); + + // do we have a domain ID? + if (Settings.data.values.metaverse.id.length == 0) { + // we don't have a domain ID - add a button to offer the user a chance to get a temporary one + var temporaryPlaceButton = dynamicButton(Settings.GET_TEMPORARY_NAME_BTN_ID, 'Get a temporary place name'); + $('#' + Settings.PLACES_TABLE_ID).after(temporaryPlaceButton); + } + if (accessTokenIsSet()) { + appendAddButtonToPlacesTable(); + } + } + + function placeTableRow(name, path, isTemporary, placeID) { + var name_link = "" + (isTemporary ? name + " (temporary)" : name) + ""; + + function placeEditClicked() { + editHighFidelityPlace(placeID, name, path); + } + + function placeDeleteClicked() { + var el = $(this); + var confirmString = Strings.REMOVE_PLACE_TITLE.replace("{{place}}", name); + var dialog = bootbox.dialog({ + message: confirmString, + closeButton: false, + onEscape: true, + buttons: [ + { + label: Strings.REMOVE_PLACE_CANCEL_BUTTON, + className: "delete-place-cancel-btn", + callback: function() { + dialog.modal('hide'); + } + }, + { + label: Strings.REMOVE_PLACE_DELETE_BUTTON, + className: "delete-place-confirm-btn btn btn-danger", + callback: function() { + $('.delete-place-cancel-btn').attr('disabled', 'disabled'); + $('.delete-place-confirm-btn').attr('disabled', 'disabled'); + $('.delete-place-confirm-btn').html(Strings.REMOVE_PLACE_DELETE_BUTTON_PENDING); + sendUpdatePlaceRequest( + placeID, + '', + null, + true, + function() { + reloadDomainInfo(); + dialog.modal('hide'); + }, function() { + $('.delete-place-cancel-btn').removeAttr('disabled'); + $('.delete-place-confirm-btn').removeAttr('disabled'); + $('.delete-place-confirm-btn').html(Strings.REMOVE_PLACE_DELETE_BUTTON); + bootbox.alert(Strings.REMOVE_PLACE_ERROR); + }); + return false; + } + }, + ] + }); + } + + if (isTemporary) { + var editLink = ""; + var deleteColumn = ""; + } else { + var editLink = " Edit"; + var deleteColumn = ""; + } + + var row = $("" + deleteColumn + ""); + row.find(".place-edit").click(placeEditClicked); + row.find(".place-delete").click(placeDeleteClicked); + + return row; + } + + function placeTableRowForPlaceObject(place) { + var placePathOrIndex = (place.path ? place.path : "/"); + return placeTableRow(place.name, placePathOrIndex, false, place.id); + } + + function reloadDomainInfo() { + $('#' + Settings.PLACES_TABLE_ID + " tbody tr").not('.headers').remove(); + + $('.domain-loading-show').show(); + $('.domain-loading-hide').hide(); + $('.domain-loading-error').hide(); + $('.loading-domain-info-spinner').show(); + $('#' + Settings.PLACES_TABLE_ID).append("Hello"); + + getDomainFromAPI(function(data){ + $('.loading-domain-info-spinner').hide(); + $('.domain-loading-show').hide(); + + // check if we have owner_places (for a real domain) or a name (for a temporary domain) + if (data.status == "success") { + $('.domain-loading-hide').show(); + if (data.domain.owner_places) { + // add a table row for each of these names + _.each(data.domain.owner_places, function(place){ + $('#' + Settings.PLACES_TABLE_ID + " tbody").append(placeTableRowForPlaceObject(place)); + }); + } else if (data.domain.name) { + // add a table row for this temporary domain name + $('#' + Settings.PLACES_TABLE_ID + " tbody").append(placeTableRow(data.domain.name, '/', true)); } - isNonDeletableRow = isNonDeletableRow - || (nonDeletableRowKey === col.name && nonDeletableRowValues.indexOf(colValue) !== -1); - - if (isArray && col.type === "checkbox" && col.editable) { - html += - ""; - } else if (isArray && col.type === "time" && col.editable) { - html += - ""; - } else { - // Use a hidden input so that the values are posted. - html += - ""; + // Update label + if (showOrHideLabel()) { + var label = data.domain.label; + label = label === null ? '' : label; + $('#network-label').val(label); } + // Update automatic networking + if (showOrHideAutomaticNetworking()) { + var autoNetworkingSetting = Settings.data.values.metaverse.automatic_networking; + var address = data.domain.network_address === null ? "" : data.domain.network_address; + var port = data.domain.network_port === null ? "" : data.domain.network_port; + if (autoNetworkingSetting === 'disabled') { + $('#network-address-port input').val(address + ":" + port); + } else if (autoNetworkingSetting === 'ip') { + $('#network-address-port input').val(port); + } + } + + if (accessTokenIsSet()) { + appendAddButtonToPlacesTable(); + } + + } else { + $('.domain-loading-error').show(); + } + }) + } + + function appendDomainIDButtons() { + var domainIDInput = $(Settings.DOMAIN_ID_SELECTOR); + + var createButton = dynamicButton(Settings.CREATE_DOMAIN_ID_BTN_ID, Strings.CREATE_DOMAIN_BUTTON); + createButton.css('margin-top', '10px'); + var chooseButton = dynamicButton(Settings.CHOOSE_DOMAIN_ID_BTN_ID, Strings.CHOOSE_DOMAIN_BUTTON); + chooseButton.css('margin', '10px 0px 0px 10px'); + + domainIDInput.after(chooseButton); + domainIDInput.after(createButton); + } + + function editHighFidelityPlace(placeID, name, path) { + var dialog; + + var modal_body = "
    "; + modal_body += ""; + modal_body += "
    "; + + var modal_buttons = [ + { + label: Strings.EDIT_PLACE_CANCEL_BUTTON, + className: "edit-place-cancel-button", + callback: function() { + dialog.modal('hide'); + } + }, + { + label: Strings.EDIT_PLACE_CONFIRM_BUTTON, + className: 'edit-place-save-button btn btn-primary', + callback: function() { + var placePath = $('#place-path-input').val(); + + if (path == placePath) { + return true; + } + + $('.edit-place-cancel-button').attr('disabled', 'disabled'); + $('.edit-place-save-button').attr('disabled', 'disabled'); + $('.edit-place-save-button').html(Strings.EDIT_PLACE_BUTTON_PENDING); + + sendUpdatePlaceRequest( + placeID, + placePath, + null, + false, + function() { + dialog.modal('hide') + reloadDomainInfo(); + }, + function() { + $('.edit-place-cancel-button').removeAttr('disabled'); + $('.edit-place-save-button').removeAttr('disabled'); + $('.edit-place-save-button').html(Strings.EDIT_PLACE_CONFIRM_BUTTON); + } + ); + + return false; + } + } + ]; + + dialog = bootbox.dialog({ + title: Strings.EDIT_PLACE_TITLE, + closeButton: false, + onEscape: true, + message: modal_body, + buttons: modal_buttons + }) + } + + function appendAddButtonToPlacesTable() { + var addRow = $(""); + addRow.find(".place-add").click(function(ev) { + ev.preventDefault(); + chooseFromHighFidelityPlaces(Settings.initialValues.metaverse.access_token, null, function(placeName, newDomainID) { + if (newDomainID) { + Settings.data.values.metaverse.id = newDomainID; + var domainIDEl = $("[data-keypath='metaverse.id']"); + domainIDEl.val(newDomainID); + Settings.initialValues.metaverse.id = newDomainID; + badgeForDifferences(domainIDEl); + } + reloadDomainInfo(); + }); + }); + $('#' + Settings.PLACES_TABLE_ID + " tbody").append(addRow); + } + + function chooseFromHighFidelityDomains(clickedButton) { + // setup the modal to help user pick their domain + if (Settings.initialValues.metaverse.access_token) { + + // add a spinner to the choose button + clickedButton.html("Loading domains..."); + clickedButton.attr('disabled', 'disabled'); + + // get a list of user domains from data-web + $.ajax({ + url: "/api/domains", + dataType: 'json', + jsonp: false, + success: function(data){ + + var modal_buttons = { + cancel: { + label: 'Cancel', + className: 'btn-default' + } + } + + if (data.data.domains.length) { + // setup a select box for the returned domains + modal_body = "

    Choose the High Fidelity domain you want this domain-server to represent.
    This will set your domain ID on the settings page.

    "; + domain_select = $(""); + _.each(data.data.domains, function(domain){ + var domainString = ""; + + if (domain.label) { + domainString += '"' + domain.label+ '" - '; + } + + domainString += domain.id; + + domain_select.append(""); + }) + modal_body += "" + domain_select[0].outerHTML + modal_buttons["success"] = { + label: 'Choose domain', + callback: function() { + domainID = $('#domain-name-select').val() + // set the domain ID on the form + $(Settings.DOMAIN_ID_SELECTOR).val(domainID).change(); + } + } + } else { + modal_buttons["success"] = { + label: 'Create new domain', + callback: function() { + window.open(URLs.METAVERSE_URL + "/user/domains", '_blank'); + } + } + modal_body = "

    You do not have any domains in your High Fidelity account." + + "

    Go to your domains page to create a new one. Once your domain is created re-open this dialog to select it.

    " + } + + bootbox.dialog({ + title: "Choose matching domain", + onEscape: true, + message: modal_body, + buttons: modal_buttons + }) + }, + error: function() { + bootbox.alert("Failed to retrieve your domains from the High Fidelity Metaverse"); + }, + complete: function() { + // remove the spinner from the choose button + clickedButton.html("Choose from my domains") + clickedButton.removeAttr('disabled') + } }); - if (!setting.read_only) { - if (setting.can_order) { - html += "" - } - if (isNonDeletableRow) { - html += ""; - } else { - html += ""; - } - } - - html += "" - - if (isCategorized && setting.can_add_new_rows && _.findLastIndex(setting_value, categoryPair) === rowIndexOrName) { - html += makeTableInputs(setting, categoryPair, categoryValue); - } - - row_num++ - }); - } - - // populate inputs in the table for new values - if (!setting.read_only) { - if (setting.can_add_new_categories) { - html += makeTableCategoryInput(setting, numVisibleColumns); - } - - if (setting.can_add_new_rows || setting.can_add_new_categories) { - html += makeTableHiddenInputs(setting, {}, ""); - } - } - html += "
    " + setting.caption + "
    " + group.label + "
    #" + setting.key.label + "" + col.label + "
    " + row_num + "" + rowIndexOrName + "
    " + name_link + "" + path + editLink + "
    " + - "" + - "" + - "" + - "" + - colValue + - "" + - "
    " - + "
    " - - return html; -} - -function makeTableCategoryHeader(categoryKey, categoryValue, numVisibleColumns, canRemove, message) { - var html = - "" + - "" + - "" + - "" + categoryValue + "" + - "" + - ((canRemove) ? ( - "" + - "" + - "" - ) : ( - "" - )) + - ""; - return html; -} - -function makeTableHiddenInputs(setting, initialValues, categoryValue) { - var html = ""; - - if (setting.numbered === true) { - html += ""; - } - - if (setting.key) { - html += "\ - \ - " - } - - _.each(setting.columns, function(col) { - var defaultValue = _.has(initialValues, col.name) ? initialValues[col.name] : col.default; - if (col.type === "checkbox") { - html += - "" + - "" + - ""; - } else if (col.type === "select") { - html += "" - html += ""; - html += ""; - } else { - html += - "" + - "" + - ""; - } - }) - - if (setting.can_order) { - html += "" - } - html += "" - html += "" - - return html -} - -function makeTableCategoryInput(setting, numVisibleColumns) { - var canAddRows = setting.can_add_new_rows; - var categoryKey = setting.categorize_by_key; - var placeholder = setting.new_category_placeholder || ""; - var message = setting.new_category_message || ""; - var html = - "" + - "" + - "" + - "" + - "" + - "" + - "" + - ""; - return html; -} - -function getDescriptionForKey(key) { - for (var i in Settings.data.descriptions) { - if (Settings.data.descriptions[i].name === key) { - return Settings.data.descriptions[i]; - } - } -} - -var SAVE_BUTTON_LABEL_SAVE = "Save"; -var SAVE_BUTTON_LABEL_RESTART = "Save and restart"; -var reasonsForRestart = []; -var numChangesBySection = {}; - -function badgeSidebarForDifferences(changedElement) { - // figure out which group this input is in - var panelParentID = changedElement.closest('.panel').attr('id'); - - // if the panel contains non-grouped settings, the initial value is Settings.initialValues - var isGrouped = $('#' + panelParentID).hasClass('grouped'); - - if (isGrouped) { - var initialPanelJSON = Settings.initialValues[panelParentID] - ? Settings.initialValues[panelParentID] - : {}; - - // get a JSON representation of that section - var panelJSON = form2js(panelParentID, ".", false, cleanupFormValues, true)[panelParentID]; - } else { - var initialPanelJSON = Settings.initialValues; - - // get a JSON representation of that section - var panelJSON = form2js(panelParentID, ".", false, cleanupFormValues, true); - } - - var badgeValue = 0 - var description = getDescriptionForKey(panelParentID); - - // badge for any settings we have that are not the same or are not present in initialValues - for (var setting in panelJSON) { - if ((!_.has(initialPanelJSON, setting) && panelJSON[setting] !== "") || - (!_.isEqual(panelJSON[setting], initialPanelJSON[setting]) - && (panelJSON[setting] !== "" || _.has(initialPanelJSON, setting)))) { - badgeValue += 1; - - // add a reason to restart - if (description && description.restart != false) { - reasonsForRestart.push(setting); - } - } else { - // remove a reason to restart - if (description && description.restart != false) { - reasonsForRestart = $.grep(reasonsForRestart, function(v) { return v != setting; }); - } - } - } - - // update the list-group-item badge to have the new value - if (badgeValue == 0) { - badgeValue = "" - } - - numChangesBySection[panelParentID] = badgeValue; - - var hasChanges = badgeValue > 0; - - if (!hasChanges) { - for (var key in numChangesBySection) { - if (numChangesBySection[key] > 0) { - hasChanges = true; - break; - } - } - } - - $(".save-button").prop("disabled", !hasChanges); - $(".save-button").html(reasonsForRestart.length > 0 ? SAVE_BUTTON_LABEL_RESTART : SAVE_BUTTON_LABEL_SAVE); - $("a[href='#" + panelParentID + "'] .badge").html(badgeValue); -} - -function addTableRow(row) { - var table = row.parents('table'); - var isArray = table.data('setting-type') === 'array'; - var keepField = row.data("keep-field"); - - var columns = row.parent().children('.' + Settings.DATA_ROW_CLASS); - - var input_clone = row.clone(); - - if (!isArray) { - // show the key input - var keyInput = row.children(".key").children("input"); - } - - // Change input row to data row - var table = row.parents("table"); - var setting_name = table.attr("name"); - row.addClass(Settings.DATA_ROW_CLASS + " " + Settings.NEW_ROW_CLASS); - - // if this is an array, add the row index (which is the index of the last row + 1) - // as a data attribute to the row - var row_index = 0; - if (isArray) { - var previous_row = row.siblings('.' + Settings.DATA_ROW_CLASS + ':last'); - - if (previous_row.length > 0) { - row_index = parseInt(previous_row.attr(Settings.DATA_ROW_INDEX), 10) + 1; - } else { - row_index = 0; - } - - row.attr(Settings.DATA_ROW_INDEX, row_index); - } - - var focusChanged = false; - - _.each(row.children(), function(element) { - if ($(element).hasClass("numbered")) { - // Index row - var numbers = columns.children(".numbered") - if (numbers.length > 0) { - $(element).html(parseInt(numbers.last().text()) + 1) } else { - $(element).html(1) + bootbox.alert({ + message: "You must have an access token to query your High Fidelity domains.

    " + + "Please follow the instructions on the settings page to add an access token.", + title: "Access token required" + }) } - } else if ($(element).hasClass(Settings.REORDER_BUTTONS_CLASS)) { - $(element).html("") - } else if ($(element).hasClass(Settings.ADD_DEL_BUTTONS_CLASS)) { - // Change buttons - var anchor = $(element).children("a") - anchor.removeClass(Settings.ADD_ROW_SPAN_CLASSES) - anchor.addClass(Settings.DEL_ROW_SPAN_CLASSES) - } else if ($(element).hasClass("key")) { - var input = $(element).children("input") - input.show(); - } else if ($(element).hasClass(Settings.DATA_COL_CLASS)) { - // show inputs - var input = $(element).find("input"); - input.show(); + } - var isCheckbox = input.hasClass("table-checkbox"); - var isDropdown = input.hasClass("table-dropdown"); + function createTemporaryDomain() { + swal({ + title: 'Create temporary place name', + text: "This will create a temporary place name and domain ID" + + " so other users can easily connect to your domain.

    " + + "In order to make your domain reachable, this will also enable full automatic networking.", + showCancelButton: true, + confirmButtonText: 'Create', + closeOnConfirm: false, + html: true + }, function(isConfirm){ + if (isConfirm) { + showSpinnerAlert('Creating temporary place name'); - if (isArray) { - var key = $(element).attr('name'); + // make a get request to get a temporary domain + $.post(URLs.METAVERSE_URL + '/api/v1/domains/temporary', function(data){ + if (data.status == "success") { + var domain = data.data.domain; - // are there multiple columns or just one? - // with multiple we have an array of Objects, with one we have an array of whatever the value type is - var num_columns = row.children('.' + Settings.DATA_COL_CLASS).length - var newName = setting_name + "[" + row_index + "]" + (num_columns > 1 ? "." + key : ""); + // we should have a new domain ID - set it on the domain ID value + $(Settings.DOMAIN_ID_SELECTOR).val(domain.id).change(); - input.attr("name", newName); + // we also need to make sure auto networking is set to full + $('[data-hidden-input="metaverse.automatic_networking"]').val("full").change(); - if (isDropdown) { - // default values for hidden inputs inside child selects gets cleared so we need to remind it - var selectElement = $(element).children("select"); - selectElement.attr("data-hidden-input", newName); - $(element).children("input").val(selectElement.val()); - } - } else { - // because the name of the setting in question requires the key - // setup a hook to change the HTML name of the element whenever the key changes - var colName = $(element).attr("name"); - keyInput.on('change', function(){ - input.attr("name", setting_name + "." + $(this).val() + "." + colName); + swal({ + type: 'success', + title: 'Success!', + text: "We have created a temporary name and domain ID for you.

    " + + "Your temporary place name is " + domain.name + ".

    " + + "Press the button below to save your new settings and restart your domain-server.", + confirmButtonText: 'Save', + html: true + }, function(){ + saveSettings(); + }); + } }); } - - if (!focusChanged) { - input.focus(); - focusChanged = true; - } - - // if we are adding a dropdown, we should go ahead and make its select - // element is visible - if (isDropdown) { - $(element).children("select").attr("style", ""); - } - - if (isCheckbox) { - $(input).find("input").attr("data-changed", "true"); - } else { - input.attr("data-changed", "true"); - } - } else { - console.log("Unknown table element"); - } - }); - - input_clone.children('td').each(function () { - if ($(this).attr("name") !== keepField) { - $(this).find("input").val($(this).children('input').attr('data-default')); - } - }); - - if (isArray) { - updateDataChangedForSiblingRows(row, true) - - // the addition of any table row should remove the empty-array-row - row.siblings('.empty-array-row').remove() - } - - badgeSidebarForDifferences($(table)) - - row.after(input_clone) -} - -function deleteTableRow($row) { - var $table = $row.closest('table'); - var categoryName = $row.data("category"); - var isArray = $table.data('setting-type') === 'array'; - - $row.empty(); - - if (!isArray) { - if ($row.attr('name')) { - $row.html(""); - } else { - // for rows that didn't have a key, simply remove the row - $row.remove(); - } - } else { - if ($table.find('.' + Settings.DATA_ROW_CLASS + "[data-category='" + categoryName + "']").length <= 1) { - // This is the last row of the category, so delete the header - $table.find('.' + Settings.DATA_CATEGORY_CLASS + "[data-category='" + categoryName + "']").remove(); - } - - if ($table.find('.' + Settings.DATA_ROW_CLASS).length > 1) { - updateDataChangedForSiblingRows($row); - - // this isn't the last row - we can just remove it - $row.remove(); - } else { - // this is the last row, we can't remove it completely since we need to post an empty array - $row - .removeClass(Settings.DATA_ROW_CLASS) - .removeClass(Settings.NEW_ROW_CLASS) - .removeAttr("data-category") - .addClass('empty-array-row') - .html(""); - } - } - - // we need to fire a change event on one of the remaining inputs so that the sidebar badge is updated - badgeSidebarForDifferences($table); -} - -function addTableCategory($categoryInputRow) { - var $input = $categoryInputRow.find("input").first(); - var categoryValue = $input.prop("value"); - if (!categoryValue || $categoryInputRow.closest("table").find("tr[data-category='" + categoryValue + "']").length !== 0) { - $categoryInputRow.addClass("has-warning"); - - setTimeout(function () { - $categoryInputRow.removeClass("has-warning"); - }, 400); - - return; - } - - var $rowInput = $categoryInputRow.next(".inputs").clone(); - if (!$rowInput) { - console.error("Error cloning inputs"); - } - - var canAddRows = $categoryInputRow.data("can-add-rows"); - var message = $categoryInputRow.data("message"); - var categoryKey = $categoryInputRow.data("key"); - var width = 0; - $categoryInputRow - .children("td") - .each(function () { - width += $(this).prop("colSpan") || 1; }); - - $input - .prop("value", "") - .focus(); - - $rowInput.find("td[name='" + categoryKey + "'] > input").first() - .prop("value", categoryValue); - $rowInput - .attr("data-category", categoryValue) - .addClass(Settings.NEW_ROW_CLASS); - - var $newCategoryRow = $(makeTableCategoryHeader(categoryKey, categoryValue, width, true, " - " + message)); - $newCategoryRow.addClass(Settings.NEW_ROW_CLASS); - - $categoryInputRow - .before($newCategoryRow) - .before($rowInput); - - if (canAddRows) { - $rowInput.removeAttr("hidden"); - } else { - addTableRow($rowInput); - } -} - -function deleteTableCategory($categoryHeaderRow) { - var categoryName = $categoryHeaderRow.data("category"); - - $categoryHeaderRow - .closest("table") - .find("tr[data-category='" + categoryName + "']") - .each(function () { - if ($(this).hasClass(Settings.DATA_ROW_CLASS)) { - deleteTableRow($(this)); - } else { - $(this).remove(); - } - }); -} - -function toggleTableCategory($categoryHeaderRow) { - var $icon = $categoryHeaderRow.find("." + Settings.TOGGLE_CATEGORY_SPAN_CLASS).first(); - var categoryName = $categoryHeaderRow.data("category"); - var wasExpanded = $icon.hasClass(Settings.TOGGLE_CATEGORY_EXPANDED_CLASS); - if (wasExpanded) { - $icon - .addClass(Settings.TOGGLE_CATEGORY_CONTRACTED_CLASS) - .removeClass(Settings.TOGGLE_CATEGORY_EXPANDED_CLASS); - } else { - $icon - .addClass(Settings.TOGGLE_CATEGORY_EXPANDED_CLASS) - .removeClass(Settings.TOGGLE_CATEGORY_CONTRACTED_CLASS); - } - $categoryHeaderRow - .closest("table") - .find("tr[data-category='" + categoryName + "']") - .toggleClass("contracted", wasExpanded); -} - -function moveTableRow(row, move_up) { - var table = $(row).closest('table') - var isArray = table.data('setting-type') === 'array' - if (!isArray) { - return; } - if (move_up) { - var prev_row = row.prev() - if (prev_row.hasClass(Settings.DATA_ROW_CLASS)) { - prev_row.before(row) - } - } else { - var next_row = row.next() - if (next_row.hasClass(Settings.DATA_ROW_CLASS)) { - next_row.after(row) - } - } - - // we need to fire a change event on one of the remaining inputs so that the sidebar badge is updated - badgeSidebarForDifferences($(table)) -} - -function updateDataChangedForSiblingRows(row, forceTrue) { - // anytime a new row is added to an array we need to set data-changed for all sibling row inputs to true - // unless it matches the inital set of values - - if (!forceTrue) { - // figure out which group this row is in - var panelParentID = row.closest('.panel').attr('id') - // get the short name for the setting from the table - var tableShortName = row.closest('table').data('short-name') - - // get a JSON representation of that section - var panelSettingJSON = form2js(panelParentID, ".", false, cleanupFormValues, true)[panelParentID][tableShortName] - if (Settings.initialValues[panelParentID]) { - var initialPanelSettingJSON = Settings.initialValues[panelParentID][tableShortName] - } else { - var initialPanelSettingJSON = {}; - } - - // if they are equal, we don't need data-changed - isTrue = !_.isEqual(panelSettingJSON, initialPanelSettingJSON) - } else { - isTrue = true - } - - row.siblings('.' + Settings.DATA_ROW_CLASS).each(function(){ - var hiddenInput = $(this).find('td.' + Settings.DATA_COL_CLASS + ' input') - if (isTrue) { - hiddenInput.attr('data-changed', isTrue) - } else { - hiddenInput.removeAttr('data-changed') - } - }) -} - -function cleanupFormValues(node) { - if (node.type && node.type === 'checkbox') { - return { name: node.name, value: node.checked ? true : false }; - } else { - return false; - } -} - -function showErrorMessage(title, message) { - swal(title, message) -} +}); diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 65053b7366..68a36195d9 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -501,7 +501,7 @@ void DomainServer::handleTempDomainSuccess(QNetworkReply& requestReply) { // store the new domain ID and auto network setting immediately QString newSettingsJSON = QString("{\"metaverse\": { \"id\": \"%1\", \"automatic_networking\": \"full\"}}").arg(id); auto settingsDocument = QJsonDocument::fromJson(newSettingsJSON.toUtf8()); - _settingsManager.recurseJSONObjectAndOverwriteSettings(settingsDocument.object()); + _settingsManager.recurseJSONObjectAndOverwriteSettings(settingsDocument.object(), DomainSettings); // store the new ID and auto networking setting on disk _settingsManager.persistToFile(); diff --git a/domain-server/src/DomainServerSettingsManager.cpp b/domain-server/src/DomainServerSettingsManager.cpp index 05227d35b7..52754babb3 100644 --- a/domain-server/src/DomainServerSettingsManager.cpp +++ b/domain-server/src/DomainServerSettingsManager.cpp @@ -39,8 +39,10 @@ const QString SETTINGS_DESCRIPTION_RELATIVE_PATH = "/resources/describe-settings const QString DESCRIPTION_SETTINGS_KEY = "settings"; const QString SETTING_DEFAULT_KEY = "default"; const QString DESCRIPTION_NAME_KEY = "name"; +const QString DESCRIPTION_GROUP_LABEL_KEY = "label"; const QString SETTING_DESCRIPTION_TYPE_KEY = "type"; const QString DESCRIPTION_COLUMNS_KEY = "columns"; +const QString CONTENT_SETTING_FLAG_KEY = "content_setting"; const QString SETTINGS_VIEWPOINT_KEY = "viewpoint"; @@ -63,6 +65,8 @@ DomainServerSettingsManager::DomainServerSettingsManager() { if (descriptionObject.contains(DESCRIPTION_SETTINGS_KEY)) { _descriptionArray = descriptionDocument.object()[DESCRIPTION_SETTINGS_KEY].toArray(); + splitSettingsDescription(); + return; } } @@ -78,6 +82,91 @@ DomainServerSettingsManager::DomainServerSettingsManager() { Q_ARG(int, MISSING_SETTINGS_DESC_ERROR_CODE)); } +void DomainServerSettingsManager::splitSettingsDescription() { + // construct separate description arrays for domain settings and content settings + // since they are displayed on different pages + + // along the way we also construct one object that holds the groups separated by domain settings + // and content settings, so that the DS can setup dropdown menus below "Content" and "Settings" + // headers to jump directly to a settings group on the page of either + QJsonArray domainSettingsMenuGroups; + QJsonArray contentSettingsMenuGroups; + + foreach(const QJsonValue& group, _descriptionArray) { + QJsonObject groupObject = group.toObject(); + + static const QString HIDDEN_GROUP_KEY = "hidden"; + bool groupHidden = groupObject.contains(HIDDEN_GROUP_KEY) && groupObject[HIDDEN_GROUP_KEY].toBool(); + + QJsonArray domainSettingArray; + QJsonArray contentSettingArray; + + foreach(const QJsonValue& settingDescription, groupObject[DESCRIPTION_SETTINGS_KEY].toArray()) { + QJsonObject settingDescriptionObject = settingDescription.toObject(); + + bool isContentSetting = settingDescriptionObject.contains(CONTENT_SETTING_FLAG_KEY) + && settingDescriptionObject[CONTENT_SETTING_FLAG_KEY].toBool(); + + if (isContentSetting) { + // push the setting description to the pending content setting array + contentSettingArray.push_back(settingDescriptionObject); + } else { + // push the setting description to the pending domain setting array + domainSettingArray.push_back(settingDescriptionObject); + } + } + + if (!domainSettingArray.isEmpty() || !contentSettingArray.isEmpty()) { + + // we know for sure we'll have something to add to our settings menu groups + // so setup that object for the group now, as long as the group isn't hidden alltogether + QJsonObject settingsDropdownGroup; + + if (!groupHidden) { + settingsDropdownGroup[DESCRIPTION_NAME_KEY] = groupObject[DESCRIPTION_NAME_KEY]; + settingsDropdownGroup[DESCRIPTION_GROUP_LABEL_KEY] = groupObject[DESCRIPTION_GROUP_LABEL_KEY]; + + static const QString DESCRIPTION_GROUP_HTML_ID_KEY = "html_id"; + if (groupObject.contains(DESCRIPTION_GROUP_HTML_ID_KEY)) { + settingsDropdownGroup[DESCRIPTION_GROUP_HTML_ID_KEY] = groupObject[DESCRIPTION_GROUP_HTML_ID_KEY]; + } + } + + if (!domainSettingArray.isEmpty()) { + // we have some domain settings from this group, add the group with the filtered settings + QJsonObject filteredGroupObject = groupObject; + filteredGroupObject[DESCRIPTION_SETTINGS_KEY] = domainSettingArray; + _domainSettingsDescription.push_back(filteredGroupObject); + + // if the group isn't hidden, add its information to the domain settings menu groups + if (!groupHidden) { + domainSettingsMenuGroups.push_back(settingsDropdownGroup); + } + } + + if (!contentSettingArray.isEmpty()) { + // we have some content settings from this group, add the group with the filtered settings + QJsonObject filteredGroupObject = groupObject; + filteredGroupObject[DESCRIPTION_SETTINGS_KEY] = contentSettingArray; + _contentSettingsDescription.push_back(filteredGroupObject); + + // if the group isn't hidden, add its information to the content settings menu groups + if (!groupHidden) { + contentSettingsMenuGroups.push_back(settingsDropdownGroup); + } + } + } + } + + // populate the settings menu groups with what we've collected + + static const QString SPLIT_MENU_GROUPS_DOMAIN_SETTINGS_KEY = "domain_settings"; + static const QString SPLIT_MENU_GROUPS_CONTENT_SETTINGS_KEY = "content_settings"; + + _settingsMenuGroups[SPLIT_MENU_GROUPS_DOMAIN_SETTINGS_KEY] = domainSettingsMenuGroups; + _settingsMenuGroups[SPLIT_MENU_GROUPS_CONTENT_SETTINGS_KEY] = contentSettingsMenuGroups; +} + void DomainServerSettingsManager::processSettingsRequestPacket(QSharedPointer message) { Assignment::Type type; message->readPrimitive(&type); @@ -986,48 +1075,72 @@ QVariant DomainServerSettingsManager::valueOrDefaultValueForKeyPath(const QStrin } bool DomainServerSettingsManager::handleAuthenticatedHTTPRequest(HTTPConnection *connection, const QUrl &url) { - if (connection->requestOperation() == QNetworkAccessManager::PostOperation && url.path() == SETTINGS_PATH_JSON) { - // this is a POST operation to change one or more settings - QJsonDocument postedDocument = QJsonDocument::fromJson(connection->requestContent()); - QJsonObject postedObject = postedDocument.object(); + if (connection->requestOperation() == QNetworkAccessManager::PostOperation) { + if (url.path() == SETTINGS_PATH_JSON || url.path() == CONTENT_SETTINGS_PATH_JSON) { + // this is a POST operation to change one or more settings + QJsonDocument postedDocument = QJsonDocument::fromJson(connection->requestContent()); + QJsonObject postedObject = postedDocument.object(); - // we recurse one level deep below each group for the appropriate setting - bool restartRequired = recurseJSONObjectAndOverwriteSettings(postedObject); + SettingsType endpointType = url.path() == SETTINGS_PATH_JSON ? DomainSettings : ContentSettings; - // store whatever the current _settingsMap is to file - persistToFile(); + // we recurse one level deep below each group for the appropriate setting + bool restartRequired = recurseJSONObjectAndOverwriteSettings(postedObject, endpointType); - // return success to the caller - QString jsonSuccess = "{\"status\": \"success\"}"; - connection->respond(HTTPConnection::StatusCode200, jsonSuccess.toUtf8(), "application/json"); + // store whatever the current _settingsMap is to file + persistToFile(); - // defer a restart to the domain-server, this gives our HTTPConnection enough time to respond - if (restartRequired) { - const int DOMAIN_SERVER_RESTART_TIMER_MSECS = 1000; - QTimer::singleShot(DOMAIN_SERVER_RESTART_TIMER_MSECS, qApp, SLOT(restart())); - } else { - unpackPermissions(); - apiRefreshGroupInformation(); - emit updateNodePermissions(); - emit settingsUpdated(); + // return success to the caller + QString jsonSuccess = "{\"status\": \"success\"}"; + connection->respond(HTTPConnection::StatusCode200, jsonSuccess.toUtf8(), "application/json"); + + // defer a restart to the domain-server, this gives our HTTPConnection enough time to respond + if (restartRequired) { + const int DOMAIN_SERVER_RESTART_TIMER_MSECS = 1000; + QTimer::singleShot(DOMAIN_SERVER_RESTART_TIMER_MSECS, qApp, SLOT(restart())); + } else { + unpackPermissions(); + apiRefreshGroupInformation(); + emit updateNodePermissions(); + emit settingsUpdated(); + } + + return true; } + } else if (connection->requestOperation() == QNetworkAccessManager::GetOperation) { + static const QString SETTINGS_MENU_GROUPS_PATH = "/settings-menu-groups.json"; - return true; - } else if (connection->requestOperation() == QNetworkAccessManager::GetOperation && url.path() == SETTINGS_PATH_JSON) { - // setup a JSON Object with descriptions and non-omitted settings - const QString SETTINGS_RESPONSE_DESCRIPTION_KEY = "descriptions"; - const QString SETTINGS_RESPONSE_VALUE_KEY = "values"; + if (url.path() == SETTINGS_PATH_JSON || url.path() == CONTENT_SETTINGS_PATH_JSON) { - QJsonObject rootObject; - rootObject[SETTINGS_RESPONSE_DESCRIPTION_KEY] = _descriptionArray; - rootObject[SETTINGS_RESPONSE_VALUE_KEY] = responseObjectForType("", true); - connection->respond(HTTPConnection::StatusCode200, QJsonDocument(rootObject).toJson(), "application/json"); + // setup a JSON Object with descriptions and non-omitted settings + const QString SETTINGS_RESPONSE_DESCRIPTION_KEY = "descriptions"; + const QString SETTINGS_RESPONSE_VALUE_KEY = "values"; + + QJsonObject rootObject; + + bool forDomainSettings = (url.path() == SETTINGS_PATH_JSON); + bool forContentSettings = (url.path() == CONTENT_SETTINGS_PATH_JSON);; + + rootObject[SETTINGS_RESPONSE_DESCRIPTION_KEY] = forDomainSettings + ? _domainSettingsDescription : _contentSettingsDescription; + + // grab a domain settings object for all types, filtered for the right class of settings + rootObject[SETTINGS_RESPONSE_VALUE_KEY] = responseObjectForType("", true, forDomainSettings, forContentSettings); + + connection->respond(HTTPConnection::StatusCode200, QJsonDocument(rootObject).toJson(), "application/json"); + + return true; + } else if (url.path() == SETTINGS_MENU_GROUPS_PATH) { + connection->respond(HTTPConnection::StatusCode200, QJsonDocument(_settingsMenuGroups).toJson(), "application/json"); + + return true; + } } return false; } -QJsonObject DomainServerSettingsManager::responseObjectForType(const QString& typeValue, bool isAuthenticated) { +QJsonObject DomainServerSettingsManager::responseObjectForType(const QString& typeValue, bool isAuthenticated, + bool includeDomainSettings, bool includeContentSettings) { QJsonObject responseObject; if (!typeValue.isEmpty() || isAuthenticated) { @@ -1036,8 +1149,15 @@ QJsonObject DomainServerSettingsManager::responseObjectForType(const QString& ty const QString AFFECTED_TYPES_JSON_KEY = "assignment-types"; + QJsonArray& filteredDescriptionArray = _descriptionArray; + if (includeDomainSettings && !includeContentSettings) { + filteredDescriptionArray = _domainSettingsDescription; + } else if (includeContentSettings && !includeDomainSettings) { + filteredDescriptionArray = _contentSettingsDescription; + } + // enumerate the groups in the description object to find which settings to pass - foreach(const QJsonValue& groupValue, _descriptionArray) { + foreach(const QJsonValue& groupValue, filteredDescriptionArray) { QJsonObject groupObject = groupValue.toObject(); QString groupKey = groupObject[DESCRIPTION_NAME_KEY].toString(); QJsonArray groupSettingsArray = groupObject[DESCRIPTION_SETTINGS_KEY].toArray(); @@ -1045,10 +1165,13 @@ QJsonObject DomainServerSettingsManager::responseObjectForType(const QString& ty QJsonObject groupResponseObject; foreach(const QJsonValue& settingValue, groupSettingsArray) { + const QString VALUE_HIDDEN_FLAG_KEY = "value-hidden"; QJsonObject settingObject = settingValue.toObject(); + // consider this setting as long as it isn't hidden + // and we've been asked to include this type (domain setting or content setting) if (!settingObject[VALUE_HIDDEN_FLAG_KEY].toBool()) { QJsonArray affectedTypesArray = settingObject[AFFECTED_TYPES_JSON_KEY].toArray(); if (affectedTypesArray.isEmpty()) { @@ -1212,7 +1335,8 @@ QJsonObject DomainServerSettingsManager::settingDescriptionFromGroup(const QJson return QJsonObject(); } -bool DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJsonObject& postedObject) { +bool DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJsonObject& postedObject, + SettingsType settingsType) { static const QString SECURITY_ROOT_KEY = "security"; static const QString AC_SUBNET_WHITELIST_KEY = "ac_subnet_whitelist"; static const QString BROADCASTING_KEY = "broadcasting"; @@ -1222,6 +1346,8 @@ bool DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJ auto& settingsVariant = _configMap.getConfig(); bool needRestart = false; + auto& filteredDescriptionArray = settingsType == DomainSettings ? _domainSettingsDescription : _contentSettingsDescription; + // Iterate on the setting groups foreach(const QString& rootKey, postedObject.keys()) { const QJsonValue& rootValue = postedObject[rootKey]; @@ -1236,7 +1362,7 @@ bool DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJ QJsonObject groupDescriptionObject; // we need to check the description array to see if this is a root setting or a group setting - foreach(const QJsonValue& groupValue, _descriptionArray) { + foreach(const QJsonValue& groupValue, filteredDescriptionArray) { if (groupValue.toObject()[DESCRIPTION_NAME_KEY] == rootKey) { // we matched a group - keep this since we'll use it below to update the settings groupDescriptionObject = groupValue.toObject(); @@ -1269,6 +1395,7 @@ bool DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJ if (!matchingDescriptionObject.isEmpty()) { updateSetting(rootKey, rootValue, *thisMap, matchingDescriptionObject); + if (rootKey != SECURITY_ROOT_KEY && rootKey != BROADCASTING_KEY && rootKey != SETTINGS_PATHS_KEY && rootKey != WIZARD_KEY) { needRestart = true; @@ -1286,6 +1413,7 @@ bool DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJ if (!matchingDescriptionObject.isEmpty()) { const QJsonValue& settingValue = rootValue.toObject()[settingKey]; updateSetting(settingKey, settingValue, *thisMap, matchingDescriptionObject); + if ((rootKey != SECURITY_ROOT_KEY && rootKey != BROADCASTING_KEY && rootKey != DESCRIPTION_ROOT_KEY && rootKey != WIZARD_KEY) || settingKey == AC_SUBNET_WHITELIST_KEY) { diff --git a/domain-server/src/DomainServerSettingsManager.h b/domain-server/src/DomainServerSettingsManager.h index d2f6d1e526..5e13c9f28a 100644 --- a/domain-server/src/DomainServerSettingsManager.h +++ b/domain-server/src/DomainServerSettingsManager.h @@ -13,6 +13,7 @@ #define hifi_DomainServerSettingsManager_h #include +#include #include #include @@ -28,6 +29,7 @@ const QString SETTINGS_PATHS_KEY = "paths"; const QString SETTINGS_PATH = "/settings"; const QString SETTINGS_PATH_JSON = SETTINGS_PATH + ".json"; +const QString CONTENT_SETTINGS_PATH_JSON = "/content-settings.json"; const QString AGENT_STANDARD_PERMISSIONS_KEYPATH = "security.standard_permissions"; const QString AGENT_PERMISSIONS_KEYPATH = "security.permissions"; const QString IP_PERMISSIONS_KEYPATH = "security.ip_permissions"; @@ -38,6 +40,10 @@ const QString GROUP_FORBIDDENS_KEYPATH = "security.group_forbiddens"; using GroupByUUIDKey = QPair; // groupID, rankID +enum SettingsType { + DomainSettings, + ContentSettings +}; class DomainServerSettingsManager : public QObject { Q_OBJECT @@ -123,8 +129,10 @@ private slots: private: QStringList _argumentList; - QJsonObject responseObjectForType(const QString& typeValue, bool isAuthenticated = false); - bool recurseJSONObjectAndOverwriteSettings(const QJsonObject& postedObject); + QJsonArray filteredDescriptionArray(bool isContentSettings); + QJsonObject responseObjectForType(const QString& typeValue, bool isAuthenticated = false, + bool includeDomainSettings = true, bool includeContentSettings = true); + bool recurseJSONObjectAndOverwriteSettings(const QJsonObject& postedObject, SettingsType settingsType); void updateSetting(const QString& key, const QJsonValue& newValue, QVariantMap& settingMap, const QJsonObject& settingDescription); @@ -132,8 +140,15 @@ private: void sortPermissions(); void persistToFile(); + void splitSettingsDescription(); + double _descriptionVersion; + QJsonArray _descriptionArray; + QJsonArray _domainSettingsDescription; + QJsonArray _contentSettingsDescription; + QJsonObject _settingsMenuGroups; + HifiConfigVariantMap _configMap; friend class DomainServer; diff --git a/libraries/embedded-webserver/src/HTTPManager.cpp b/libraries/embedded-webserver/src/HTTPManager.cpp index bd256578d8..fd127a2e92 100644 --- a/libraries/embedded-webserver/src/HTTPManager.cpp +++ b/libraries/embedded-webserver/src/HTTPManager.cpp @@ -79,7 +79,7 @@ bool HTTPManager::handleHTTPRequest(HTTPConnection* connection, const QUrl& url, QHash redirectHeader; redirectHeader.insert(QByteArray("Location"), redirectLocation.toUtf8()); - connection->respond(HTTPConnection::StatusCode301, "", HTTPConnection::DefaultContentType, redirectHeader); + connection->respond(HTTPConnection::StatusCode302, "", HTTPConnection::DefaultContentType, redirectHeader); } // if the last thing is a trailing slash then we want to look for index file From b0967dfc3a188fb799f0c82fd42d41f3e7272a39 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Tue, 30 Jan 2018 16:38:32 -0800 Subject: [PATCH 29/45] move some more settings to content, leave places in domain settings --- domain-server/resources/describe-settings.json | 7 +++++-- domain-server/resources/web/js/base-settings.js | 8 ++++++-- domain-server/resources/web/js/domain-server.js | 7 +++++++ domain-server/resources/web/settings/js/settings.js | 11 +++++++++-- domain-server/src/DomainServerSettingsManager.cpp | 7 ++++++- 5 files changed, 33 insertions(+), 7 deletions(-) diff --git a/domain-server/resources/describe-settings.json b/domain-server/resources/describe-settings.json index 705d110542..b9a4246895 100644 --- a/domain-server/resources/describe-settings.json +++ b/domain-server/resources/describe-settings.json @@ -55,8 +55,8 @@ ] }, { - "label": "Places / Paths", - "html_id": "places_paths", + "label": "Paths", + "html_id": "paths", "restart": false, "settings": [ { @@ -64,6 +64,7 @@ "label": "Paths", "help": "Clients can enter a path to reach an exact viewpoint in your domain.
    Add rows to the table below to map a path to a viewpoint.
    The index path ( / ) is where clients will enter if they do not enter an explicit path.", "type": "table", + "content_setting": true, "can_add_new_rows": true, "key": { "name": "path", @@ -1081,6 +1082,7 @@ "name": "attenuation_per_doubling_in_distance", "label": "Default Domain Attenuation", "help": "Factor between 0 and 1.0 (0: No attenuation, 1.0: extreme attenuation)", + "content_setting": true, "placeholder": "0.5", "default": "0.5", "advanced": false @@ -1313,6 +1315,7 @@ "name": "entityEditFilter", "label": "Filter Entity Edits", "help": "Check all entity edits against this filter function.", + "content_setting": true, "placeholder": "url whose content is like: function filter(properties) { return properties; }", "default": "", "advanced": true diff --git a/domain-server/resources/web/js/base-settings.js b/domain-server/resources/web/js/base-settings.js index 5e32463d61..aad342da63 100644 --- a/domain-server/resources/web/js/base-settings.js +++ b/domain-server/resources/web/js/base-settings.js @@ -94,9 +94,13 @@ var viewHelpers = { function reloadSettings(callback) { $.getJSON(Settings.endpoint, function(data){ - _.extend(data, viewHelpers) + _.extend(data, viewHelpers); - $('#panels').html(Settings.panelsTemplate(data)) + for (var spliceIndex in Settings.extraGroups) { + data.descriptions.splice(spliceIndex, 0, Settings.extraGroups[spliceIndex]); + } + + $('#panels').html(Settings.panelsTemplate(data)); Settings.data = data; Settings.initialValues = form2js('settings-form', ".", false, cleanupFormValues, true); diff --git a/domain-server/resources/web/js/domain-server.js b/domain-server/resources/web/js/domain-server.js index aa658bce3f..a7c8c3a4d1 100644 --- a/domain-server/resources/web/js/domain-server.js +++ b/domain-server/resources/web/js/domain-server.js @@ -79,6 +79,13 @@ $(document).ready(function(){ } $settingsDropdown.append(makeGroupDropdownElement(group, "/settings/")); + + // for domain settings, we add a dummy "Places" group that we fill + // via the API - add it to the dropdown menu in the right spot + if (index == 1) { + $settingsDropdown.append(""); + $settingsDropdown.append(makeGroupDropdownElement({ html_id: 'places', label: 'Places' }, "/settings/")); + } }); }); } diff --git a/domain-server/resources/web/settings/js/settings.js b/domain-server/resources/web/settings/js/settings.js index 9a31b766a6..d10d1304c3 100644 --- a/domain-server/resources/web/settings/js/settings.js +++ b/domain-server/resources/web/settings/js/settings.js @@ -14,6 +14,14 @@ $(document).ready(function(){ return b; })(window.location.search.substr(1).split('&')); + // define extra groups to add to description, with their splice index + Settings.extraGroups = { + 1: { + html_id: 'places', + label: 'Places' + } + } + Settings.afterReloadActions = function() { // append the domain selection modal appendDomainIDButtons(); @@ -657,8 +665,7 @@ $(document).ready(function(){ var placesTableGroup = viewHelpers.getFormGroup('', placesTableSetting, Settings.data.values); // append the places table in the right place - $('#places_paths .panel-body').prepend(placesTableGroup); - //$('#' + Settings.PLACES_TABLE_ID).append(""); + $('#places .panel-body').prepend(placesTableGroup); var spinner = createDomainSpinner(); $('#' + Settings.PLACES_TABLE_ID).after($(spinner)); diff --git a/domain-server/src/DomainServerSettingsManager.cpp b/domain-server/src/DomainServerSettingsManager.cpp index 52754babb3..f91c5af06f 100644 --- a/domain-server/src/DomainServerSettingsManager.cpp +++ b/domain-server/src/DomainServerSettingsManager.cpp @@ -123,7 +123,10 @@ void DomainServerSettingsManager::splitSettingsDescription() { QJsonObject settingsDropdownGroup; if (!groupHidden) { - settingsDropdownGroup[DESCRIPTION_NAME_KEY] = groupObject[DESCRIPTION_NAME_KEY]; + if (groupObject.contains(DESCRIPTION_NAME_KEY)) { + settingsDropdownGroup[DESCRIPTION_NAME_KEY] = groupObject[DESCRIPTION_NAME_KEY]; + } + settingsDropdownGroup[DESCRIPTION_GROUP_LABEL_KEY] = groupObject[DESCRIPTION_GROUP_LABEL_KEY]; static const QString DESCRIPTION_GROUP_HTML_ID_KEY = "html_id"; @@ -1383,7 +1386,9 @@ bool DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJ foreach(const QJsonValue& groupValue, _descriptionArray) { // find groups with root values (they don't have a group name) QJsonObject groupObject = groupValue.toObject(); + if (!groupObject.contains(DESCRIPTION_NAME_KEY)) { + // this is a group with root values - check if our setting is in here matchingDescriptionObject = settingDescriptionFromGroup(groupObject, rootKey); From 8c924ea1065f6f7e3eea80b2f92b13c6e3573710 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Tue, 30 Jan 2018 17:13:03 -0800 Subject: [PATCH 30/45] update badge colour --- domain-server/resources/web/css/style.css | 1 + 1 file changed, 1 insertion(+) diff --git a/domain-server/resources/web/css/style.css b/domain-server/resources/web/css/style.css index 22de75a778..5155ab2330 100644 --- a/domain-server/resources/web/css/style.css +++ b/domain-server/resources/web/css/style.css @@ -414,6 +414,7 @@ ul.nav li.dropdown ul.dropdown-menu .divider { .badge { margin-left: 5px; + background-color: #00B4EF !important; } .panel-title { From 2c2a6d5c603342099c8edddc5fed026d1ac1b3f5 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Tue, 30 Jan 2018 17:24:56 -0800 Subject: [PATCH 31/45] add the empty label group to domain extra groups --- domain-server/resources/describe-settings.json | 6 ------ domain-server/resources/web/settings/js/settings.js | 6 +++++- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/domain-server/resources/describe-settings.json b/domain-server/resources/describe-settings.json index b9a4246895..bd351eb173 100644 --- a/domain-server/resources/describe-settings.json +++ b/domain-server/resources/describe-settings.json @@ -1,12 +1,6 @@ { "version": 2.1, "settings": [ - { - "name": "label", - "label": "Label", - "settings": [ - ] - }, { "name": "metaverse", "label": "Metaverse / Networking", diff --git a/domain-server/resources/web/settings/js/settings.js b/domain-server/resources/web/settings/js/settings.js index d10d1304c3..39d776af8b 100644 --- a/domain-server/resources/web/settings/js/settings.js +++ b/domain-server/resources/web/settings/js/settings.js @@ -16,7 +16,11 @@ $(document).ready(function(){ // define extra groups to add to description, with their splice index Settings.extraGroups = { - 1: { + 0: { + html_id: 'label', + label: 'Label' + }, + 2: { html_id: 'places', label: 'Places' } From 6e93eb5dfa5b28da904c840ba7070323d9f513f7 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Tue, 30 Jan 2018 17:27:52 -0800 Subject: [PATCH 32/45] update SVG icon for HMD with new colour --- domain-server/resources/web/images/hmd-w-eyes.svg | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/domain-server/resources/web/images/hmd-w-eyes.svg b/domain-server/resources/web/images/hmd-w-eyes.svg index c100de2f4e..0e9081c10c 100644 --- a/domain-server/resources/web/images/hmd-w-eyes.svg +++ b/domain-server/resources/web/images/hmd-w-eyes.svg @@ -2,7 +2,10 @@ - + .st0{fill:#666666;} + +
    - <% _.each(settings, function(setting) { %> + <% _.each(split_settings[0], function(setting) { %> <% keypath = isGrouped ? group.name + "." + setting.name : setting.name %> <%= getFormGroup(keypath, setting, values, false) %> <% }); %> + + <% if (split_settings[1].length > 0) { %> + +
    + <% _.each(split_settings[1], function(setting) { %> + <% keypath = isGrouped ? group.name + "." + setting.name : setting.name %> + <%= getFormGroup(keypath, setting, values, true) %> + <% }); %> +
    + <% } %>
    <% } %> diff --git a/domain-server/resources/web/css/style.css b/domain-server/resources/web/css/style.css index 5155ab2330..8090c9f450 100644 --- a/domain-server/resources/web/css/style.css +++ b/domain-server/resources/web/css/style.css @@ -426,3 +426,7 @@ ul.nav li.dropdown ul.dropdown-menu .divider { position: relative; top: -1px; } + +.advanced-settings-section { + margin-top: 20px; +} From bb2df1eb37f30fda8a8f71be18a1b941836ebfca Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Wed, 31 Jan 2018 15:27:29 -0800 Subject: [PATCH 35/45] rename entity server section to remove settings --- domain-server/resources/describe-settings.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/domain-server/resources/describe-settings.json b/domain-server/resources/describe-settings.json index bd351eb173..cf3321f831 100644 --- a/domain-server/resources/describe-settings.json +++ b/domain-server/resources/describe-settings.json @@ -1268,7 +1268,7 @@ }, { "name": "entity_server_settings", - "label": "Entity Server Settings", + "label": "Entity Server", "assignment-types": [ 6 ], From 0ace92798d66c95987396f1b7c2d4d8989e3a42f Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Wed, 31 Jan 2018 17:18:49 -0800 Subject: [PATCH 36/45] repair 2.1 settings migration code for avatar height --- domain-server/src/DomainServerSettingsManager.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/domain-server/src/DomainServerSettingsManager.cpp b/domain-server/src/DomainServerSettingsManager.cpp index f91c5af06f..874e543a08 100644 --- a/domain-server/src/DomainServerSettingsManager.cpp +++ b/domain-server/src/DomainServerSettingsManager.cpp @@ -406,14 +406,14 @@ void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList QVariant* avatarMinScale = _configMap.valueForKeyPath(AVATAR_MIN_SCALE_KEYPATH); if (avatarMinScale) { - float scale = avatarMinScale->toFloat(); - _configMap.valueForKeyPath(AVATAR_MIN_HEIGHT_KEYPATH, scale * DEFAULT_AVATAR_HEIGHT); + auto newMinScaleVariant = _configMap.valueForKeyPath(AVATAR_MIN_HEIGHT_KEYPATH, true); + *newMinScaleVariant = avatarMinScale->toFloat() * DEFAULT_AVATAR_HEIGHT; } QVariant* avatarMaxScale = _configMap.valueForKeyPath(AVATAR_MAX_SCALE_KEYPATH); if (avatarMaxScale) { - float scale = avatarMaxScale->toFloat(); - _configMap.valueForKeyPath(AVATAR_MAX_HEIGHT_KEYPATH, scale * DEFAULT_AVATAR_HEIGHT); + auto newMaxScaleVariant = _configMap.valueForKeyPath(AVATAR_MAX_HEIGHT_KEYPATH, true); + *newMaxScaleVariant = avatarMaxScale->toFloat() * DEFAULT_AVATAR_HEIGHT; } } From 2f6b079d80dc618bb8208d644c2d9dadb79069f9 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Wed, 31 Jan 2018 17:19:07 -0800 Subject: [PATCH 37/45] quick re-organization of settings groups --- .../resources/describe-settings.json | 234 +++++++++--------- .../resources/web/js/domain-server.js | 2 +- domain-server/resources/web/js/shared.js | 4 + 3 files changed, 122 insertions(+), 118 deletions(-) diff --git a/domain-server/resources/describe-settings.json b/domain-server/resources/describe-settings.json index cf3321f831..103c045516 100644 --- a/domain-server/resources/describe-settings.json +++ b/domain-server/resources/describe-settings.json @@ -951,99 +951,6 @@ } ] }, - { - "name": "asset_server", - "label": "Asset Server (ATP)", - "assignment-types": [ 3 ], - "settings": [ - { - "name": "enabled", - "type": "checkbox", - "label": "Enabled", - "help": "Assigns an asset-server in your domain to serve files to clients via the ATP protocol (over UDP)", - "default": true, - "advanced": true - }, - { - "name": "assets_path", - "type": "string", - "label": "Assets Path", - "help": "The path to the directory assets are stored in.
    If this path is relative, it will be relative to the application data directory.
    If you change this path you will need to manually copy any existing assets from the previous directory.", - "default": "", - "advanced": true - }, - { - "name": "assets_filesize_limit", - "type": "int", - "label": "File Size Limit", - "help": "The file size limit of an asset that can be imported into the asset server in MBytes. 0 (default) means no limit on file size.", - "default": 0, - "advanced": true - } - ] - }, - { - "name": "entity_script_server", - "label": "Entity Script Server (ESS)", - "assignment-types": [ 5 ], - "settings": [ - { - "name": "entity_pps_per_script", - "label": "Entity PPS per script", - "help": "The number of packets per second (PPS) that can be sent to the entity server for each server entity script. This contributes to a total overall amount.
    Example: 1000 PPS with 5 entites gives a total PPS of 5000 that is shared among the entity scripts. A single could use 4000 PPS, leaving 1000 for the rest, for example.", - "default": 900, - "type": "int", - "advanced": true - }, - { - "name": "max_total_entity_pps", - "label": "Maximum Total Entity PPS", - "help": "The maximum total packets per seconds (PPS) that can be sent to the entity server.
    Example: 5 scripts @ 1000 PPS per script = 5000 total PPS. A maximum total PPS of 4000 would cap this 5000 total PPS to 4000.", - "default": 9000, - "type": "int", - "advanced": true - } - ] - }, - { - "name": "avatars", - "label": "Avatars", - "assignment-types": [ 1, 2 ], - "settings": [ - { - "name": "min_avatar_height", - "type": "double", - "label": "Minimum Avatar Height (meters)", - "help": "Limits the height of avatars in your domain. Must be at least 0.009.", - "placeholder": 0.4, - "default": 0.4 - }, - { - "name": "max_avatar_height", - "type": "double", - "label": "Maximum Avatar Height (meters)", - "help": "Limits the scale of avatars in your domain. Cannot be greater than 1755.", - "placeholder": 5.2, - "default": 5.2 - }, - { - "name": "avatar_whitelist", - "label": "Avatars Allowed from:", - "help": "Comma separated list of URLs (with optional paths) that avatar .fst files are allowed from. If someone attempts to use an avatar with a different domain, it will be rejected and the replacement avatar will be used. If left blank, any domain is allowed.", - "placeholder": "", - "default": "", - "advanced": true - }, - { - "name": "replacement_avatar", - "label": "Replacement Avatar for disallowed avatars", - "help": "A URL for an avatar .fst to be used when someone tries to use an avatar that is not allowed. If left blank, the generic default avatar is used.", - "placeholder": "", - "default": "", - "advanced": true - } - ] - }, { "name": "audio_threading", "label": "Audio Threading", @@ -1266,9 +1173,82 @@ } ] }, + { + "name": "avatars", + "label": "Avatars", + "assignment-types": [ 1, 2 ], + "settings": [ + { + "name": "min_avatar_height", + "type": "double", + "label": "Minimum Avatar Height (meters)", + "help": "Limits the height of avatars in your domain. Must be at least 0.009.", + "placeholder": 0.4, + "default": 0.4 + }, + { + "name": "max_avatar_height", + "type": "double", + "label": "Maximum Avatar Height (meters)", + "help": "Limits the scale of avatars in your domain. Cannot be greater than 1755.", + "placeholder": 5.2, + "default": 5.2 + }, + { + "name": "avatar_whitelist", + "label": "Avatars Allowed from:", + "help": "Comma separated list of URLs (with optional paths) that avatar .fst files are allowed from. If someone attempts to use an avatar with a different domain, it will be rejected and the replacement avatar will be used. If left blank, any domain is allowed.", + "placeholder": "", + "default": "", + "advanced": true + }, + { + "name": "replacement_avatar", + "label": "Replacement Avatar for disallowed avatars", + "help": "A URL for an avatar .fst to be used when someone tries to use an avatar that is not allowed. If left blank, the generic default avatar is used.", + "placeholder": "", + "default": "", + "advanced": true + } + ] + }, + { + "name": "avatar_mixer", + "label": "Avatar Mixer", + "assignment-types": [ + 1 + ], + "settings": [ + { + "name": "max_node_send_bandwidth", + "type": "double", + "label": "Per-Node Bandwidth", + "help": "Desired maximum send bandwidth (in Megabits per second) to each node", + "placeholder": 5.0, + "default": 5.0, + "advanced": true + }, + { + "name": "auto_threads", + "label": "Automatically determine thread count", + "type": "checkbox", + "help": "Allow system to determine number of threads (recommended)", + "default": false, + "advanced": true + }, + { + "name": "num_threads", + "label": "Number of Threads", + "help": "Threads to spin up for avatar mixing (if not automatically set)", + "placeholder": "1", + "default": "1", + "advanced": true + } + ] + }, { "name": "entity_server_settings", - "label": "Entity Server", + "label": "Entities", "assignment-types": [ 6 ], @@ -1504,35 +1484,55 @@ ] }, { - "name": "avatar_mixer", - "label": "Avatar Mixer", - "assignment-types": [ - 1 - ], + "name": "asset_server", + "label": "Asset Server (ATP)", + "assignment-types": [ 3 ], "settings": [ { - "name": "max_node_send_bandwidth", - "type": "double", - "label": "Per-Node Bandwidth", - "help": "Desired maximum send bandwidth (in Megabits per second) to each node", - "placeholder": 5.0, - "default": 5.0, - "advanced": true - }, - { - "name": "auto_threads", - "label": "Automatically determine thread count", + "name": "enabled", "type": "checkbox", - "help": "Allow system to determine number of threads (recommended)", - "default": false, + "label": "Enabled", + "help": "Assigns an asset-server in your domain to serve files to clients via the ATP protocol (over UDP)", + "default": true, "advanced": true }, { - "name": "num_threads", - "label": "Number of Threads", - "help": "Threads to spin up for avatar mixing (if not automatically set)", - "placeholder": "1", - "default": "1", + "name": "assets_path", + "type": "string", + "label": "Assets Path", + "help": "The path to the directory assets are stored in.
    If this path is relative, it will be relative to the application data directory.
    If you change this path you will need to manually copy any existing assets from the previous directory.", + "default": "", + "advanced": true + }, + { + "name": "assets_filesize_limit", + "type": "int", + "label": "File Size Limit", + "help": "The file size limit of an asset that can be imported into the asset server in MBytes. 0 (default) means no limit on file size.", + "default": 0, + "advanced": true + } + ] + }, + { + "name": "entity_script_server", + "label": "Entity Script Server (ESS)", + "assignment-types": [ 5 ], + "settings": [ + { + "name": "entity_pps_per_script", + "label": "Entity PPS per script", + "help": "The number of packets per second (PPS) that can be sent to the entity server for each server entity script. This contributes to a total overall amount.
    Example: 1000 PPS with 5 entites gives a total PPS of 5000 that is shared among the entity scripts. A single could use 4000 PPS, leaving 1000 for the rest, for example.", + "default": 900, + "type": "int", + "advanced": true + }, + { + "name": "max_total_entity_pps", + "label": "Maximum Total Entity PPS", + "help": "The maximum total packets per seconds (PPS) that can be sent to the entity server.
    Example: 5 scripts @ 1000 PPS per script = 5000 total PPS. A maximum total PPS of 4000 would cap this 5000 total PPS to 4000.", + "default": 9000, + "type": "int", "advanced": true } ] diff --git a/domain-server/resources/web/js/domain-server.js b/domain-server/resources/web/js/domain-server.js index a7c8c3a4d1..ae5a452ec9 100644 --- a/domain-server/resources/web/js/domain-server.js +++ b/domain-server/resources/web/js/domain-server.js @@ -82,7 +82,7 @@ $(document).ready(function(){ // for domain settings, we add a dummy "Places" group that we fill // via the API - add it to the dropdown menu in the right spot - if (index == 1) { + if (index == 0) { $settingsDropdown.append(""); $settingsDropdown.append(makeGroupDropdownElement({ html_id: 'places', label: 'Places' }, "/settings/")); } diff --git a/domain-server/resources/web/js/shared.js b/domain-server/resources/web/js/shared.js index e1870a2fa8..e7fc77b707 100644 --- a/domain-server/resources/web/js/shared.js +++ b/domain-server/resources/web/js/shared.js @@ -1,3 +1,7 @@ +if (typeof Settings === "undefined") { + Settings = {}; +} + Object.assign(Settings, { DEPRECATED_CLASS: 'deprecated-setting', TRIGGER_CHANGE_CLASS: 'trigger-change', From 11fe279f6f976a878a3140875ea25132e725ede7 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Thu, 1 Feb 2018 15:47:12 -0800 Subject: [PATCH 38/45] add domain settings backup and restore to web interface --- .../resources/web/content/js/content.js | 10 - domain-server/resources/web/css/style.css | 4 + domain-server/resources/web/header.html | 2 +- .../resources/web/js/base-settings.js | 10 + .../resources/web/js/domain-server.js | 7 +- domain-server/resources/web/js/shared.js | 8 +- .../resources/web/settings/js/settings.js | 86 ++++++- .../src/DomainServerSettingsManager.cpp | 227 ++++++++++++++++-- .../src/DomainServerSettingsManager.h | 7 +- 9 files changed, 316 insertions(+), 45 deletions(-) diff --git a/domain-server/resources/web/content/js/content.js b/domain-server/resources/web/content/js/content.js index 7b2ed37b15..e448952c65 100644 --- a/domain-server/resources/web/content/js/content.js +++ b/domain-server/resources/web/content/js/content.js @@ -2,16 +2,6 @@ $(document).ready(function(){ Settings.afterReloadActions = function() {}; - function showSpinnerAlert(title) { - swal({ - title: title, - text: '
    ', - html: true, - showConfirmButton: false, - allowEscapeKey: false - }); - } - var frm = $('#upload-form'); frm.submit(function (ev) { $.ajax({ diff --git a/domain-server/resources/web/css/style.css b/domain-server/resources/web/css/style.css index 8090c9f450..ed19d46fb5 100644 --- a/domain-server/resources/web/css/style.css +++ b/domain-server/resources/web/css/style.css @@ -430,3 +430,7 @@ ul.nav li.dropdown ul.dropdown-menu .divider { .advanced-settings-section { margin-top: 20px; } + +#restore-settings-button { + margin-top: 10px; +} diff --git a/domain-server/resources/web/header.html b/domain-server/resources/web/header.html index aff82d557e..a9a477c7fb 100644 --- a/domain-server/resources/web/header.html +++ b/domain-server/resources/web/header.html @@ -67,7 +67,7 @@
    diff --git a/domain-server/resources/web/js/base-settings.js b/domain-server/resources/web/js/base-settings.js index f6069a3c40..3f410d4e2c 100644 --- a/domain-server/resources/web/js/base-settings.js +++ b/domain-server/resources/web/js/base-settings.js @@ -155,7 +155,10 @@ function postSettings(jsonSettings) { $(document).ready(function(){ - $('.save-button.navbar-btn').show(); + $(document).on('click', '.save-button', function(e){ + saveSettings(); + e.preventDefault(); + }); $.ajaxSetup({ timeout: 20000, @@ -254,7 +257,7 @@ $(document).ready(function(){ } }); - $('#' + Settings.FORM_ID).on('change keyup paste', '.' + Settings.TRIGGER_CHANGE_CLASS , function(){ + $('#' + Settings.FORM_ID).on('change keyup paste', '.' + Settings.TRIGGER_CHANGE_CLASS , function(e){ // this input was changed, add the changed data attribute to it $(this).attr('data-changed', true); @@ -438,11 +441,6 @@ function saveSettings() { } } -$('body').on('click', '.save-button', function(e){ - saveSettings(); - return false; -}); - function makeTable(setting, keypath, setting_value) { var isArray = !_.has(setting, 'key'); var categoryKey = setting.categorize_by_key; @@ -788,8 +786,8 @@ function badgeForDifferences(changedElement) { } } - $(".save-button").prop("disabled", !hasChanges); - $(".save-button").html(reasonsForRestart.length > 0 ? SAVE_BUTTON_LABEL_RESTART : SAVE_BUTTON_LABEL_SAVE); + $('.save-button').prop("disabled", !hasChanges); + $('.save-button-text').html(reasonsForRestart.length > 0 ? SAVE_BUTTON_LABEL_RESTART : SAVE_BUTTON_LABEL_SAVE); // add the badge to the navbar item and the panel header $("a[href='" + settingsGroupAnchor(Settings.path, panelParentID) + "'] .badge").html(badgeValue); @@ -832,7 +830,7 @@ function addTableRow(row) { var keyInput = row.children(".key").children("input"); // whenever the keyInput changes, re-badge for differences - keyInput.on('change keyup paste', function(){ + keyInput.on('change keyup paste', function(e){ // update siblings in the row to have the correct name var currentKey = $(this).val(); @@ -844,7 +842,7 @@ function addTableRow(row) { } else { input.removeAttr("name"); } - }) + }); badgeForDifferences($(this)); }); diff --git a/domain-server/src/DomainServerSettingsManager.cpp b/domain-server/src/DomainServerSettingsManager.cpp index 401b322502..131670d221 100644 --- a/domain-server/src/DomainServerSettingsManager.cpp +++ b/domain-server/src/DomainServerSettingsManager.cpp @@ -1181,7 +1181,7 @@ bool DomainServerSettingsManager::handleAuthenticatedHTTPRequest(HTTPConnection // create a timestamped filename for the backup const QString DATETIME_FORMAT { "yyyy-MM-dd_HH-mm-ss" }; - auto backupFilename = "ds-settings-" + QDateTime::currentDateTime().toString(DATETIME_FORMAT) + ".json"; + auto backupFilename = "domain-settings_" + QDateTime::currentDateTime().toString(DATETIME_FORMAT) + ".json"; downloadHeaders.insert("Content-Disposition", QString("attachment; filename=\"%1\"").arg(backupFilename).toLocal8Bit()); From 9f2015ba469737c9d2e59f4e646d306050a4d739 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Mon, 5 Feb 2018 16:58:18 -0800 Subject: [PATCH 40/45] check correct element to determine if restart is required --- domain-server/resources/web/js/base-settings.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/domain-server/resources/web/js/base-settings.js b/domain-server/resources/web/js/base-settings.js index 3f410d4e2c..17f06f3ad1 100644 --- a/domain-server/resources/web/js/base-settings.js +++ b/domain-server/resources/web/js/base-settings.js @@ -138,7 +138,7 @@ function postSettings(jsonSettings) { type: 'POST' }).done(function(data){ if (data.status == "success") { - if ($(".save-button").html() === SAVE_BUTTON_LABEL_RESTART) { + if ($(".save-button-text").html() === SAVE_BUTTON_LABEL_RESTART) { showRestartModal(); } else { location.reload(true); From 3516a8939a23f821f3d1dfb407e1e964fad807b4 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Tue, 6 Feb 2018 16:44:51 -0800 Subject: [PATCH 41/45] flag settings in description as being excluded from backups --- .../resources/describe-settings.json | 9 ++++--- .../src/DomainServerSettingsManager.cpp | 24 ++++++++++++------- .../src/DomainServerSettingsManager.h | 2 +- 3 files changed, 22 insertions(+), 13 deletions(-) diff --git a/domain-server/resources/describe-settings.json b/domain-server/resources/describe-settings.json index 103c045516..93d703c8b3 100644 --- a/domain-server/resources/describe-settings.json +++ b/domain-server/resources/describe-settings.json @@ -9,7 +9,8 @@ "name": "access_token", "label": "Access Token", "help": "This is your OAuth access token to connect this domain-server with your High Fidelity account.
    It can be generated by clicking the 'Connect Account' button above.
    You can also go to the My Security page of your account and generate a token with the 'domains' scope and paste it here.", - "advanced": true + "advanced": true, + "backup": false }, { "name": "id", @@ -159,7 +160,8 @@ { "name": "http_username", "label": "HTTP Username", - "help": "Username used for basic HTTP authentication." + "help": "Username used for basic HTTP authentication.", + "backup": false }, { "name": "http_password", @@ -167,7 +169,8 @@ "type": "password", "help": "Password used for basic HTTP authentication. Leave this alone if you do not want to change it.", "password_placeholder": "******", - "value-hidden": true + "value-hidden": true, + "backup": false }, { "name": "verify_http_password", diff --git a/domain-server/src/DomainServerSettingsManager.cpp b/domain-server/src/DomainServerSettingsManager.cpp index 131670d221..b8d4a07238 100644 --- a/domain-server/src/DomainServerSettingsManager.cpp +++ b/domain-server/src/DomainServerSettingsManager.cpp @@ -40,6 +40,7 @@ const QString DESCRIPTION_SETTINGS_KEY = "settings"; const QString SETTING_DEFAULT_KEY = "default"; const QString DESCRIPTION_NAME_KEY = "name"; const QString DESCRIPTION_GROUP_LABEL_KEY = "label"; +const QString DESCRIPTION_BACKUP_FLAG_KEY = "backup"; const QString SETTING_DESCRIPTION_TYPE_KEY = "type"; const QString DESCRIPTION_COLUMNS_KEY = "columns"; const QString CONTENT_SETTING_FLAG_KEY = "content_setting"; @@ -1173,7 +1174,7 @@ bool DomainServerSettingsManager::handleAuthenticatedHTTPRequest(HTTPConnection } else if (url.path() == SETTINGS_BACKUP_PATH) { // grab the settings backup as an authenticated user // for the domain settings type only, excluding hidden and default values - auto currentDomainSettingsJSON = settingsResponseObjectForType("", true, true, false, false); + auto currentDomainSettingsJSON = settingsResponseObjectForType("", true, true, false, false, true); // setup headers that tell the client to download the file wth a special name Headers downloadHeaders; @@ -1240,13 +1241,15 @@ bool DomainServerSettingsManager::restoreSettingsFromObject(QJsonObject settings } foreach(const QJsonValue& descriptionSettingValue, descriptionGroupSettings) { - const QString VALUE_HIDDEN_FLAG_KEY = "value-hidden"; QJsonObject descriptionSettingObject = descriptionSettingValue.toObject(); - // we'll override this setting with the default or what is in the restore as long as it isn't hidden - if (!descriptionSettingObject[VALUE_HIDDEN_FLAG_KEY].toBool()) { + // we'll override this setting with the default or what is in the restore as long as + // it isn't specifically excluded from backups + bool isBackedUpSetting = !descriptionSettingObject.contains(DESCRIPTION_BACKUP_FLAG_KEY) + || descriptionSettingObject[DESCRIPTION_BACKUP_FLAG_KEY].toBool(); + if (isBackedUpSetting) { QString settingName = descriptionSettingObject[DESCRIPTION_NAME_KEY].toString(); // check if we have a matching setting for this in the restore @@ -1315,7 +1318,7 @@ bool DomainServerSettingsManager::restoreSettingsFromObject(QJsonObject settings QJsonObject DomainServerSettingsManager::settingsResponseObjectForType(const QString& typeValue, bool isAuthenticated, bool includeDomainSettings, bool includeContentSettings, - bool includeDefaults) { + bool includeDefaults, bool isForBackup) { QJsonObject responseObject; if (!typeValue.isEmpty() || isAuthenticated) { @@ -1347,7 +1350,11 @@ QJsonObject DomainServerSettingsManager::settingsResponseObjectForType(const QSt QJsonObject settingObject = settingValue.toObject(); // consider this setting as long as it isn't hidden - if (!settingObject[VALUE_HIDDEN_FLAG_KEY].toBool()) { + // and either this isn't for a backup or it's a value included in backups + bool includedInBackups = !settingObject.contains(DESCRIPTION_BACKUP_FLAG_KEY) + || settingObject[DESCRIPTION_BACKUP_FLAG_KEY].toBool(); + + if (!settingObject[VALUE_HIDDEN_FLAG_KEY].toBool() && (!isForBackup || includedInBackups)) { QJsonArray affectedTypesArray = settingObject[AFFECTED_TYPES_JSON_KEY].toArray(); if (affectedTypesArray.isEmpty()) { affectedTypesArray = groupObject[AFFECTED_TYPES_JSON_KEY].toArray(); @@ -1370,8 +1377,8 @@ QJsonObject DomainServerSettingsManager::settingsResponseObjectForType(const QSt variantValue = _configMap.value(settingName); } - // final check for inclusion, either we include default values - // or we don't but this isn't a default value + // final check for inclusion + // either we include default values or we don't but this isn't a default value if (includeDefaults || !variantValue.isNull()) { QJsonValue result; @@ -1407,7 +1414,6 @@ QJsonObject DomainServerSettingsManager::settingsResponseObjectForType(const QSt } } - return responseObject; } diff --git a/domain-server/src/DomainServerSettingsManager.h b/domain-server/src/DomainServerSettingsManager.h index 00707c33e3..9b2427b344 100644 --- a/domain-server/src/DomainServerSettingsManager.h +++ b/domain-server/src/DomainServerSettingsManager.h @@ -132,7 +132,7 @@ private: QJsonArray filteredDescriptionArray(bool isContentSettings); QJsonObject settingsResponseObjectForType(const QString& typeValue, bool isAuthenticated = false, bool includeDomainSettings = true, bool includeContentSettings = true, - bool includeDefaults = true); + bool includeDefaults = true, bool isForBackup = false); bool recurseJSONObjectAndOverwriteSettings(const QJsonObject& postedObject, SettingsType settingsType); void updateSetting(const QString& key, const QJsonValue& newValue, QVariantMap& settingMap, From 37b8fa2c0c84af04bb259d969c9a89c3d5c708ff Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Wed, 7 Feb 2018 15:22:33 -0800 Subject: [PATCH 42/45] put back default values in settings response --- domain-server/resources/web/js/shared.js | 7 ++++--- domain-server/src/DomainServerSettingsManager.cpp | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/domain-server/resources/web/js/shared.js b/domain-server/resources/web/js/shared.js index f5346ce024..69721ee924 100644 --- a/domain-server/resources/web/js/shared.js +++ b/domain-server/resources/web/js/shared.js @@ -164,11 +164,12 @@ function getDomainFromAPI(callback) { if (callback === undefined) { callback = function() {}; } - - var domainID = Settings.data.values.metaverse.id; - if (domainID === null || domainID === undefined || domainID === '') { + + if (!domainIDIsSet()) { callback({ status: 'fail' }); return null; + } else { + var domainID = Settings.data.values.metaverse.id; } pendingDomainRequest = $.ajax({ diff --git a/domain-server/src/DomainServerSettingsManager.cpp b/domain-server/src/DomainServerSettingsManager.cpp index b8d4a07238..8febbd5769 100644 --- a/domain-server/src/DomainServerSettingsManager.cpp +++ b/domain-server/src/DomainServerSettingsManager.cpp @@ -1162,7 +1162,7 @@ bool DomainServerSettingsManager::handleAuthenticatedHTTPRequest(HTTPConnection // and exclude default values rootObject[SETTINGS_RESPONSE_VALUE_KEY] = settingsResponseObjectForType("", true, forDomainSettings, forContentSettings, - false); + true); connection->respond(HTTPConnection::StatusCode200, QJsonDocument(rootObject).toJson(), "application/json"); From e14f46101b47c55a4bd540060655ae8e9cadeed8 Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Wed, 14 Feb 2018 13:49:43 -0800 Subject: [PATCH 43/45] fix tablet rotation when switching into and out of create mode --- scripts/system/libraries/WebTablet.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/scripts/system/libraries/WebTablet.js b/scripts/system/libraries/WebTablet.js index 05b4963280..a28de5abc2 100644 --- a/scripts/system/libraries/WebTablet.js +++ b/scripts/system/libraries/WebTablet.js @@ -47,7 +47,7 @@ function calcSpawnInfo(hand, landscape) { var headPos = (HMD.active && Camera.mode === "first person") ? HMD.position : Camera.position; var headRot = (HMD.active && Camera.mode === "first person") ? HMD.orientation : Camera.orientation; - var forward = Quat.getForward(headRot); + var forward = Quat.getForward(Quat.cancelOutRollAndPitch(headRot)); var FORWARD_OFFSET = 0.5 * MyAvatar.sensorToWorldScale; finalPosition = Vec3.sum(headPos, Vec3.multiply(FORWARD_OFFSET, forward)); var orientation = Quat.lookAt({x: 0, y: 0, z: 0}, forward, Vec3.multiplyQbyV(MyAvatar.orientation, Vec3.UNIT_Y)); @@ -269,8 +269,9 @@ WebTablet.prototype.setLandscape = function(newLandscapeValue) { } this.landscape = newLandscapeValue; + var cameraOrientation = Quat.cancelOutRollAndPitch(Camera.orientation); Overlays.editOverlay(this.tabletEntityID, - { rotation: Quat.multiply(Camera.orientation, this.landscape ? ROT_LANDSCAPE : ROT_Y_180) }); + { rotation: Quat.multiply(cameraOrientation, this.landscape ? ROT_LANDSCAPE : ROT_Y_180) }); var tabletWidth = getTabletWidthFromSettings() * MyAvatar.sensorToWorldScale; var tabletScaleFactor = tabletWidth / TABLET_NATURAL_DIMENSIONS.x; @@ -278,7 +279,7 @@ WebTablet.prototype.setLandscape = function(newLandscapeValue) { var screenWidth = 0.82 * tabletWidth; var screenHeight = 0.81 * tabletHeight; Overlays.editOverlay(this.webOverlayID, { - rotation: Quat.multiply(Camera.orientation, ROT_LANDSCAPE_WINDOW), + rotation: Quat.multiply(cameraOrientation, ROT_LANDSCAPE_WINDOW), dimensions: {x: this.landscape ? screenHeight : screenWidth, y: this.landscape ? screenWidth : screenHeight, z: 0.1} }); }; From d466c079a4c4a89738ae9d045bcb946953861e7c Mon Sep 17 00:00:00 2001 From: LaShonda Hopper Date: Wed, 14 Feb 2018 17:00:52 -0500 Subject: [PATCH 44/45] Simple fix for 801 Uncaught TypeError (details below). entityProperties.js was throwing out: 801 Uncaught TypeError: Cannot read property 'split' of undefined This moves properties.modelURL related checks behind the property.type validation check to avoid calling the function on an undefined member if the object doesn't have that key defined. Changes Committed: modified: scripts/system/html/js/entityProperties.js --- scripts/system/html/js/entityProperties.js | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/scripts/system/html/js/entityProperties.js b/scripts/system/html/js/entityProperties.js index 35235634e9..b27c852974 100644 --- a/scripts/system/html/js/entityProperties.js +++ b/scripts/system/html/js/entityProperties.js @@ -798,10 +798,13 @@ function loaded() { // HTML workaround since image is not yet a separate entity type var IMAGE_MODEL_NAME = 'default-image-model.fbx'; - var urlParts = properties.modelURL.split('/') - var propsFilename = urlParts[urlParts.length - 1]; - if (properties.type === "Model" && propsFilename === IMAGE_MODEL_NAME) { - properties.type = "Image"; + if (properties.type === "Model") { + var urlParts = properties.modelURL.split('/'); + var propsFilename = urlParts[urlParts.length - 1]; + + if (propsFilename === IMAGE_MODEL_NAME) { + properties.type = "Image"; + } } // Create class name for css ruleset filtering From 4d4b42848b2b12adb53954cac675391978ee2b71 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Wed, 14 Feb 2018 16:31:28 -0800 Subject: [PATCH 45/45] add alert for moved content settings --- domain-server/resources/web/base-settings.html | 6 ------ domain-server/resources/web/settings/index.shtml | 8 ++++++++ 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/domain-server/resources/web/base-settings.html b/domain-server/resources/web/base-settings.html index 2f0fcbe73b..da5561d03d 100644 --- a/domain-server/resources/web/base-settings.html +++ b/domain-server/resources/web/base-settings.html @@ -1,10 +1,4 @@
    -
    -
    - -
    -
    -
    diff --git a/domain-server/resources/web/settings/index.shtml b/domain-server/resources/web/settings/index.shtml index d71692523a..bdc02c5dcc 100644 --- a/domain-server/resources/web/settings/index.shtml +++ b/domain-server/resources/web/settings/index.shtml @@ -8,6 +8,14 @@ }; +
    +
    +
    +
    Your domain content settings are now available in Content
    +
    +
    +
    +