From 6a035578017935304c77ac8821a5dc5d49f3d3cf Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Tue, 23 Jan 2018 14:44:10 -0800 Subject: [PATCH 01/89] 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/89] 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/89] 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/89] [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/89] [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/89] [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 3a7290c3ede88e7e402c1547d6133621c19c62fa Mon Sep 17 00:00:00 2001 From: Elisa Lupin-Jimenez Date: Mon, 8 Jan 2018 12:15:13 -0800 Subject: [PATCH 07/89] starting 2d image entity type --- libraries/entities/src/EntityTypes.h | 1 + libraries/entities/src/FlatImageEntity.cpp | 74 ++++++++++++++++++++++ libraries/entities/src/FlatImageEntity.h | 41 ++++++++++++ 3 files changed, 116 insertions(+) create mode 100644 libraries/entities/src/FlatImageEntity.cpp create mode 100644 libraries/entities/src/FlatImageEntity.h diff --git a/libraries/entities/src/EntityTypes.h b/libraries/entities/src/EntityTypes.h index 316bf2b813..dac36f28dd 100644 --- a/libraries/entities/src/EntityTypes.h +++ b/libraries/entities/src/EntityTypes.h @@ -49,6 +49,7 @@ public: PolyVox, PolyLine, Shape, + Image, LAST = Shape } EntityType; diff --git a/libraries/entities/src/FlatImageEntity.cpp b/libraries/entities/src/FlatImageEntity.cpp new file mode 100644 index 0000000000..9eb3f64bcb --- /dev/null +++ b/libraries/entities/src/FlatImageEntity.cpp @@ -0,0 +1,74 @@ +// +// FlatImageEntity.cpp +// libraries/entities/src +// +// Created by Elisa Lupin-Jimenez on 1/3/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 + +#include +#include +#include +#include + +#include "EntitiesLogging.h" +#include "EntityItemProperties.h" +#include "EntityTree.h" +#include "EntityTreeElement.h" +#include "ResourceCache.h" +#include "ShapeEntityItem.h" +#include "FlatImageEntity.h" + +const QString FlatImageEntity::DEFAULT_IMAGE_URL = QString(""); + +EntityItemPointer FlatImageEntity::factory(const EntityItemID& entityID, const EntityItemProperties& properties) { + EntityItemPointer entity(new FlatImageEntity(entityID), [](EntityItem* ptr) { ptr->deleteLater(); }); + entity->setProperties(properties); + return entity; +} + +FlatImageEntity::FlatImageEntity(const EntityItemID& entityItemID) : EntityItem(entityItemID) { + _type = EntityTypes::Image; +} + +EntityItemProperties FlatImageEntity::getProperties(EntityPropertyFlags desiredProperties) const { + EntityItemProperties properties = EntityItem::getProperties(desiredProperties); // get the properties from our base class + properties.setShape("Image"); + return properties; +} + +bool FlatImageEntity::setProperties(const EntityItemProperties& properties) { + bool somethingChanged = EntityItem::setProperties(properties); // set the properties in our base class + + SET_ENTITY_PROPERTY_FROM_PROPERTIES(shape, setShape); + + if (somethingChanged) { + bool wantDebug = false; + if (wantDebug) { + uint64_t now = usecTimestampNow(); + int elapsed = now - getLastEdited(); + qCDebug(entities) << "FlatImageEntity::setProperties() AFTER update... edited AGO=" << elapsed << + "now=" << now << " getLastEdited()=" << getLastEdited(); + } + setLastEdited(properties.getLastEdited()); + } + return somethingChanged; +} + +void FlatImageEntity::appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params, + EntityTreeElementExtraEncodeDataPointer modelTreeElementExtraEncodeData, + EntityPropertyFlags& requestedProperties, + EntityPropertyFlags& propertyFlags, + EntityPropertyFlags& propertiesDidntFit, + int& propertyCount, + OctreeElement::AppendState& appendState) const { + + bool successPropertyFits = true; + APPEND_ENTITY_PROPERTY(PROP_SHAPE, entity::stringFromShape(getShape())); +} + diff --git a/libraries/entities/src/FlatImageEntity.h b/libraries/entities/src/FlatImageEntity.h new file mode 100644 index 0000000000..08c13f8f9f --- /dev/null +++ b/libraries/entities/src/FlatImageEntity.h @@ -0,0 +1,41 @@ +// +// FlatImageEntity.h +// libraries/entities/src +// +// Created by Elisa Lupin-Jimenez on 1/3/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_FlatImageEntity_h +#define hifi_FlatImageEntity_h + +#include "EntityItem.h" + +class FlatImageEntity : public EntityItem { +public: + static EntityItemPointer factory(const EntityItemID& entityID, const EntityItemProperties& properties); + + FlatImageEntity(const EntityItemID& entityItemID); + + ALLOW_INSTANTIATION // This class can be instantiated + + // methods for getting/setting all properties of an entity + virtual EntityItemProperties getProperties(EntityPropertyFlags desiredProperties = EntityPropertyFlags()) const override; + virtual bool setProperties(const EntityItemProperties& properties) override; + + virtual void appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params, + EntityTreeElementExtraEncodeDataPointer entityTreeElementExtraEncodeData, + EntityPropertyFlags& requestedProperties, + EntityPropertyFlags& propertyFlags, + EntityPropertyFlags& propertiesDidntFit, + int& propertyCount, + OctreeElement::AppendState& appendState) const override; + + static const QString DEFAULT_IMAGE_URL; + +}; + +#endif // hifi_FlatImageEntity_h From fdca8ab93eef09e21af8883e20b5072441300b80 Mon Sep 17 00:00:00 2001 From: Elisa Lupin-Jimenez Date: Tue, 9 Jan 2018 17:46:24 -0800 Subject: [PATCH 08/89] added image button to edit.js, working on connecting to cpp --- .../resources/qml/hifi/tablet/EditTabView.qml | 12 ++++++ .../entities/src/EntityItemProperties.cpp | 4 ++ libraries/entities/src/EntityItemProperties.h | 1 + libraries/entities/src/EntityPropertyFlags.h | 3 ++ libraries/entities/src/EntityTypes.cpp | 1 + libraries/entities/src/EntityTypes.h | 2 +- libraries/entities/src/FlatImageEntity.cpp | 43 +++++++++++++++++-- libraries/entities/src/FlatImageEntity.h | 12 ++++++ scripts/system/edit.js | 14 +++++- 9 files changed, 87 insertions(+), 5 deletions(-) diff --git a/interface/resources/qml/hifi/tablet/EditTabView.qml b/interface/resources/qml/hifi/tablet/EditTabView.qml index e94325f399..482469d355 100644 --- a/interface/resources/qml/hifi/tablet/EditTabView.qml +++ b/interface/resources/qml/hifi/tablet/EditTabView.qml @@ -101,6 +101,18 @@ TabView { } } + // for image + NewEntityButton { + icon: "icons/create-icons/25-web-1-01.svg" + text: "IMAGE" + onClicked: { + editRoot.sendToScript({ + method: "newEntityButtonClicked", params: { buttonName: "newImageButton" } + }); + editTabView.currentIndex = 2 + } + } + NewEntityButton { icon: "icons/create-icons/25-web-1-01.svg" text: "WEB" diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index e2a5ddf8b5..a1c51b650f 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -1369,6 +1369,10 @@ OctreeElement::AppendState EntityItemProperties::encodeEntityEditPacket(PacketTy APPEND_ENTITY_PROPERTY(PROP_DPI, properties.getDPI()); } + if (properties.getType() == EntityTypes::Image) { + APPEND_ENTITY_PROPERTY(PROP_IMAGE_URL, properties.getImageURL()); + } + if (properties.getType() == EntityTypes::Text) { APPEND_ENTITY_PROPERTY(PROP_TEXT, properties.getText()); APPEND_ENTITY_PROPERTY(PROP_LINE_HEIGHT, properties.getLineHeight()); diff --git a/libraries/entities/src/EntityItemProperties.h b/libraries/entities/src/EntityItemProperties.h index 3e0770f386..5bd6836336 100644 --- a/libraries/entities/src/EntityItemProperties.h +++ b/libraries/entities/src/EntityItemProperties.h @@ -194,6 +194,7 @@ public: DEFINE_PROPERTY_GROUP(Haze, haze, HazePropertyGroup); DEFINE_PROPERTY_GROUP(Animation, animation, AnimationPropertyGroup); DEFINE_PROPERTY_REF(PROP_SOURCE_URL, SourceUrl, sourceUrl, QString, ""); + DEFINE_PROPERTY_REF(PROP_IMAGE_URL, ImageURL, imageURL, QString, ""); DEFINE_PROPERTY(PROP_LINE_WIDTH, LineWidth, lineWidth, float, LineEntityItem::DEFAULT_LINE_WIDTH); DEFINE_PROPERTY_REF(LINE_POINTS, LinePoints, linePoints, QVector, QVector()); DEFINE_PROPERTY_REF(PROP_HREF, Href, href, QString, ""); diff --git a/libraries/entities/src/EntityPropertyFlags.h b/libraries/entities/src/EntityPropertyFlags.h index 90438ab01c..4768ebed86 100644 --- a/libraries/entities/src/EntityPropertyFlags.h +++ b/libraries/entities/src/EntityPropertyFlags.h @@ -42,6 +42,9 @@ enum EntityPropertyList { PROP_ANIMATION_ALLOW_TRANSLATION, PROP_RELAY_PARENT_JOINTS, + // for image + PROP_IMAGE_URL, + // these properties are supported by the EntityItem base class PROP_REGISTRATION_POINT, PROP_ANGULAR_VELOCITY, diff --git a/libraries/entities/src/EntityTypes.cpp b/libraries/entities/src/EntityTypes.cpp index cb17c28fd7..e53b9d02f6 100644 --- a/libraries/entities/src/EntityTypes.cpp +++ b/libraries/entities/src/EntityTypes.cpp @@ -89,6 +89,7 @@ EntityItemPointer EntityTypes::constructEntityItem(EntityType entityType, const EntityItemPointer newEntityItem = NULL; EntityTypeFactory factory = NULL; if (entityType >= 0 && entityType <= LAST) { + qCDebug(entities) << "type: " << entityType; factory = _factories[entityType]; } if (factory) { diff --git a/libraries/entities/src/EntityTypes.h b/libraries/entities/src/EntityTypes.h index dac36f28dd..8d986c8090 100644 --- a/libraries/entities/src/EntityTypes.h +++ b/libraries/entities/src/EntityTypes.h @@ -48,8 +48,8 @@ public: Line, PolyVox, PolyLine, - Shape, Image, + Shape, LAST = Shape } EntityType; diff --git a/libraries/entities/src/FlatImageEntity.cpp b/libraries/entities/src/FlatImageEntity.cpp index 9eb3f64bcb..66a5702747 100644 --- a/libraries/entities/src/FlatImageEntity.cpp +++ b/libraries/entities/src/FlatImageEntity.cpp @@ -38,14 +38,14 @@ FlatImageEntity::FlatImageEntity(const EntityItemID& entityItemID) : EntityItem( EntityItemProperties FlatImageEntity::getProperties(EntityPropertyFlags desiredProperties) const { EntityItemProperties properties = EntityItem::getProperties(desiredProperties); // get the properties from our base class - properties.setShape("Image"); + properties.setShape("Quad"); return properties; } bool FlatImageEntity::setProperties(const EntityItemProperties& properties) { bool somethingChanged = EntityItem::setProperties(properties); // set the properties in our base class - SET_ENTITY_PROPERTY_FROM_PROPERTIES(shape, setShape); + //SET_ENTITY_PROPERTY_FROM_PROPERTIES(shape, setShape); if (somethingChanged) { bool wantDebug = false; @@ -60,6 +60,13 @@ bool FlatImageEntity::setProperties(const EntityItemProperties& properties) { return somethingChanged; } +// TODO: eventually only include properties changed since the params.nodeData->getLastTimeBagEmpty() time +EntityPropertyFlags FlatImageEntity::getEntityProperties(EncodeBitstreamParams& params) const { + EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params); + + return requestedProperties; +} + void FlatImageEntity::appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params, EntityTreeElementExtraEncodeDataPointer modelTreeElementExtraEncodeData, EntityPropertyFlags& requestedProperties, @@ -69,6 +76,36 @@ void FlatImageEntity::appendSubclassData(OctreePacketData* packetData, EncodeBit OctreeElement::AppendState& appendState) const { bool successPropertyFits = true; - APPEND_ENTITY_PROPERTY(PROP_SHAPE, entity::stringFromShape(getShape())); + // Using "Quad" shape as defined in ShapeEntityItem.cpp + APPEND_ENTITY_PROPERTY(PROP_SHAPE, "Quad"); +} + +int FlatImageEntity::readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead, + ReadBitstreamToTreeParams& args, + EntityPropertyFlags& propertyFlags, bool overwriteLocalData, + bool& somethingChanged) { + + int bytesRead = 0; + const unsigned char* dataAt = data; + + return bytesRead; +} + +void ShapeEntityItem::setUnscaledDimensions(const glm::vec3& value) { + const float MAX_FLAT_DIMENSION = 0.0001f; + if (value.y > MAX_FLAT_DIMENSION) { + // enforce flatness in Y + glm::vec3 newDimensions = value; + newDimensions.y = MAX_FLAT_DIMENSION; + EntityItem::setUnscaledDimensions(newDimensions); + } else { + EntityItem::setUnscaledDimensions(value); + } +} + +QString FlatImageEntity::getImageURL() const { + return resultWithReadLock([&] { + return _imageURL; + }); } diff --git a/libraries/entities/src/FlatImageEntity.h b/libraries/entities/src/FlatImageEntity.h index 08c13f8f9f..4dd91b1215 100644 --- a/libraries/entities/src/FlatImageEntity.h +++ b/libraries/entities/src/FlatImageEntity.h @@ -26,6 +26,8 @@ public: virtual EntityItemProperties getProperties(EntityPropertyFlags desiredProperties = EntityPropertyFlags()) const override; virtual bool setProperties(const EntityItemProperties& properties) override; + EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const override; + virtual void appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params, EntityTreeElementExtraEncodeDataPointer entityTreeElementExtraEncodeData, EntityPropertyFlags& requestedProperties, @@ -34,7 +36,17 @@ public: int& propertyCount, OctreeElement::AppendState& appendState) const override; + int readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead, + ReadBitstreamToTreeParams& args, + EntityPropertyFlags& propertyFlags, bool overwriteLocalData, + bool& somethingChanged) override; + + static const QString DEFAULT_IMAGE_URL; + QString getImageURL() const; + +protected: + QString _imageURL; }; diff --git a/scripts/system/edit.js b/scripts/system/edit.js index 863c185cb4..4241468de1 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -251,7 +251,8 @@ var toolBar = (function () { // Align entity with Avatar orientation. properties.rotation = MyAvatar.orientation; - var PRE_ADJUST_ENTITY_TYPES = ["Box", "Sphere", "Shape", "Text", "Web"]; + // added image here + var PRE_ADJUST_ENTITY_TYPES = ["Box", "Sphere", "Shape", "Text", "Image", "Web"]; if (PRE_ADJUST_ENTITY_TYPES.indexOf(properties.type) !== -1) { // Adjust position of entity per bounding box prior to creating it. @@ -286,6 +287,7 @@ var toolBar = (function () { properties.userData = JSON.stringify({ grabbableKey: { grabbable: false } }); } + print("properties.type: " + properties.type); entityID = Entities.addEntity(properties); if (properties.type === "ParticleEffect") { @@ -538,6 +540,16 @@ var toolBar = (function () { }); }); + // for image button + addButton("newImageButton", "web-01.svg", function () { + print("new image message is received"); + createNewEntity({ + type: "Image", + dimensions: DEFAULT_DIMENSIONS, + sourceUrl: "https://highfidelity.com/" + }); + }); + addButton("newWebButton", "web-01.svg", function () { createNewEntity({ type: "Web", From fc0e87d5eaa13cf7c97a1ab2ab8a8eac264576d5 Mon Sep 17 00:00:00 2001 From: Elisa Lupin-Jimenez Date: Wed, 10 Jan 2018 17:08:55 -0800 Subject: [PATCH 09/89] more infrastructure links --- .../entities/src/EntityItemProperties.cpp | 9 ++++ .../entities/src/EntityScriptingInterface.cpp | 2 + libraries/entities/src/EntityTree.cpp | 1 + libraries/entities/src/EntityTypes.cpp | 4 +- ...latImageEntity.cpp => ImageEntityItem.cpp} | 42 ++++++++++++------- .../{FlatImageEntity.h => ImageEntityItem.h} | 13 +++--- scripts/system/edit.js | 2 +- 7 files changed, 51 insertions(+), 22 deletions(-) rename libraries/entities/src/{FlatImageEntity.cpp => ImageEntityItem.cpp} (70%) rename libraries/entities/src/{FlatImageEntity.h => ImageEntityItem.h} (85%) diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index a1c51b650f..fc8ca18fb6 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -590,6 +590,11 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_SKYBOX_MODE, skyboxMode, getSkyboxModeAsString()); } + // Image only + if (_type == EntityTypes::Image) { + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_IMAGE_URL, imageURL); + } + // Web only if (_type == EntityTypes::Web) { COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_SOURCE_URL, sourceUrl); @@ -1734,6 +1739,10 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_DPI, uint16_t, setDPI); } + if (properties.getType() == EntityTypes::Image) { + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_IMAGE_URL, QString, setImageURL); + } + if (properties.getType() == EntityTypes::Text) { READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_TEXT, QString, setText); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_LINE_HEIGHT, float, setLineHeight); diff --git a/libraries/entities/src/EntityScriptingInterface.cpp b/libraries/entities/src/EntityScriptingInterface.cpp index 4342f0e683..cab4251d97 100644 --- a/libraries/entities/src/EntityScriptingInterface.cpp +++ b/libraries/entities/src/EntityScriptingInterface.cpp @@ -266,6 +266,8 @@ QUuid EntityScriptingInterface::addEntity(const EntityItemProperties& properties bool success = true; if (_entityTree) { _entityTree->withWriteLock([&] { + propertiesWithSimID.getType(); + qCDebug(entities) << "check 2 type: " << propertiesWithSimID.getType(); EntityItemPointer entity = _entityTree->addEntity(id, propertiesWithSimID); if (entity) { if (propertiesWithSimID.queryAACubeRelatedPropertyChanged()) { diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index bf29f3bec9..56c3aa216c 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -514,6 +514,7 @@ EntityItemPointer EntityTree::addEntity(const EntityItemID& entityID, const Enti // construct the instance of the entity EntityTypes::EntityType type = props.getType(); + qCDebug(entities) << "check 3 type: " << type; result = EntityTypes::constructEntityItem(type, entityID, props); if (result) { diff --git a/libraries/entities/src/EntityTypes.cpp b/libraries/entities/src/EntityTypes.cpp index e53b9d02f6..2e4a0e89a9 100644 --- a/libraries/entities/src/EntityTypes.cpp +++ b/libraries/entities/src/EntityTypes.cpp @@ -28,6 +28,7 @@ #include "LineEntityItem.h" #include "PolyVoxEntityItem.h" #include "PolyLineEntityItem.h" +#include "ImageEntityItem.h" #include "ShapeEntityItem.h" QMap EntityTypes::_typeToNameMap; @@ -47,6 +48,7 @@ REGISTER_ENTITY_TYPE(Zone) REGISTER_ENTITY_TYPE(Line) REGISTER_ENTITY_TYPE(PolyVox) REGISTER_ENTITY_TYPE(PolyLine) +REGISTER_ENTITY_TYPE(Image) REGISTER_ENTITY_TYPE(Shape) REGISTER_ENTITY_TYPE_WITH_FACTORY(Box, ShapeEntityItem::boxFactory) REGISTER_ENTITY_TYPE_WITH_FACTORY(Sphere, ShapeEntityItem::sphereFactory) @@ -89,7 +91,7 @@ EntityItemPointer EntityTypes::constructEntityItem(EntityType entityType, const EntityItemPointer newEntityItem = NULL; EntityTypeFactory factory = NULL; if (entityType >= 0 && entityType <= LAST) { - qCDebug(entities) << "type: " << entityType; + qCDebug(entities) << "check 4 type: " << entityType; factory = _factories[entityType]; } if (factory) { diff --git a/libraries/entities/src/FlatImageEntity.cpp b/libraries/entities/src/ImageEntityItem.cpp similarity index 70% rename from libraries/entities/src/FlatImageEntity.cpp rename to libraries/entities/src/ImageEntityItem.cpp index 66a5702747..a94374f7b5 100644 --- a/libraries/entities/src/FlatImageEntity.cpp +++ b/libraries/entities/src/ImageEntityItem.cpp @@ -1,5 +1,5 @@ // -// FlatImageEntity.cpp +// ImageEntityItem.cpp // libraries/entities/src // // Created by Elisa Lupin-Jimenez on 1/3/18. @@ -22,27 +22,27 @@ #include "EntityTreeElement.h" #include "ResourceCache.h" #include "ShapeEntityItem.h" -#include "FlatImageEntity.h" +#include "ImageEntityItem.h" -const QString FlatImageEntity::DEFAULT_IMAGE_URL = QString(""); +const QString ImageEntityItem::DEFAULT_IMAGE_URL = QString(""); -EntityItemPointer FlatImageEntity::factory(const EntityItemID& entityID, const EntityItemProperties& properties) { - EntityItemPointer entity(new FlatImageEntity(entityID), [](EntityItem* ptr) { ptr->deleteLater(); }); +EntityItemPointer ImageEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) { + EntityItemPointer entity(new ImageEntityItem(entityID), [](EntityItem* ptr) { ptr->deleteLater(); }); entity->setProperties(properties); return entity; } -FlatImageEntity::FlatImageEntity(const EntityItemID& entityItemID) : EntityItem(entityItemID) { +ImageEntityItem::ImageEntityItem(const EntityItemID& entityItemID) : EntityItem(entityItemID) { _type = EntityTypes::Image; } -EntityItemProperties FlatImageEntity::getProperties(EntityPropertyFlags desiredProperties) const { +EntityItemProperties ImageEntityItem::getProperties(EntityPropertyFlags desiredProperties) const { EntityItemProperties properties = EntityItem::getProperties(desiredProperties); // get the properties from our base class properties.setShape("Quad"); return properties; } -bool FlatImageEntity::setProperties(const EntityItemProperties& properties) { +bool ImageEntityItem::setProperties(const EntityItemProperties& properties) { bool somethingChanged = EntityItem::setProperties(properties); // set the properties in our base class //SET_ENTITY_PROPERTY_FROM_PROPERTIES(shape, setShape); @@ -52,7 +52,7 @@ bool FlatImageEntity::setProperties(const EntityItemProperties& properties) { if (wantDebug) { uint64_t now = usecTimestampNow(); int elapsed = now - getLastEdited(); - qCDebug(entities) << "FlatImageEntity::setProperties() AFTER update... edited AGO=" << elapsed << + qCDebug(entities) << "ImageEntityItem::setProperties() AFTER update... edited AGO=" << elapsed << "now=" << now << " getLastEdited()=" << getLastEdited(); } setLastEdited(properties.getLastEdited()); @@ -61,13 +61,13 @@ bool FlatImageEntity::setProperties(const EntityItemProperties& properties) { } // TODO: eventually only include properties changed since the params.nodeData->getLastTimeBagEmpty() time -EntityPropertyFlags FlatImageEntity::getEntityProperties(EncodeBitstreamParams& params) const { +EntityPropertyFlags ImageEntityItem::getEntityProperties(EncodeBitstreamParams& params) const { EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params); return requestedProperties; } -void FlatImageEntity::appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params, +void ImageEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params, EntityTreeElementExtraEncodeDataPointer modelTreeElementExtraEncodeData, EntityPropertyFlags& requestedProperties, EntityPropertyFlags& propertyFlags, @@ -80,7 +80,7 @@ void FlatImageEntity::appendSubclassData(OctreePacketData* packetData, EncodeBit APPEND_ENTITY_PROPERTY(PROP_SHAPE, "Quad"); } -int FlatImageEntity::readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead, +int ImageEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead, ReadBitstreamToTreeParams& args, EntityPropertyFlags& propertyFlags, bool overwriteLocalData, bool& somethingChanged) { @@ -91,7 +91,7 @@ int FlatImageEntity::readEntitySubclassDataFromBuffer(const unsigned char* data, return bytesRead; } -void ShapeEntityItem::setUnscaledDimensions(const glm::vec3& value) { +/*void ShapeEntityItem::setUnscaledDimensions(const glm::vec3& value) { const float MAX_FLAT_DIMENSION = 0.0001f; if (value.y > MAX_FLAT_DIMENSION) { // enforce flatness in Y @@ -101,9 +101,23 @@ void ShapeEntityItem::setUnscaledDimensions(const glm::vec3& value) { } else { EntityItem::setUnscaledDimensions(value); } +}*/ + +void ImageEntityItem::setImageURL(const QString& value) { + withWriteLock([&] { + if (_imageURL != value) { + auto newURL = QUrl::fromUserInput(value); + + if (newURL.isValid()) { + _imageURL = newURL.toDisplayString(); + } else { + qCDebug(entities) << "Clearing image entity source URL since" << value << "cannot be parsed to a valid URL."; + } + } + }); } -QString FlatImageEntity::getImageURL() const { +QString ImageEntityItem::getImageURL() const { return resultWithReadLock([&] { return _imageURL; }); diff --git a/libraries/entities/src/FlatImageEntity.h b/libraries/entities/src/ImageEntityItem.h similarity index 85% rename from libraries/entities/src/FlatImageEntity.h rename to libraries/entities/src/ImageEntityItem.h index 4dd91b1215..55e8fc7b36 100644 --- a/libraries/entities/src/FlatImageEntity.h +++ b/libraries/entities/src/ImageEntityItem.h @@ -1,5 +1,5 @@ // -// FlatImageEntity.h +// ImageEntityItem.h // libraries/entities/src // // Created by Elisa Lupin-Jimenez on 1/3/18. @@ -9,16 +9,16 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#ifndef hifi_FlatImageEntity_h -#define hifi_FlatImageEntity_h +#ifndef hifi_ImageEntityItem_h +#define hifi_ImageEntityItem_h #include "EntityItem.h" -class FlatImageEntity : public EntityItem { +class ImageEntityItem : public EntityItem { public: static EntityItemPointer factory(const EntityItemID& entityID, const EntityItemProperties& properties); - FlatImageEntity(const EntityItemID& entityItemID); + ImageEntityItem(const EntityItemID& entityItemID); ALLOW_INSTANTIATION // This class can be instantiated @@ -43,6 +43,7 @@ public: static const QString DEFAULT_IMAGE_URL; + virtual void setImageURL(const QString& value); QString getImageURL() const; protected: @@ -50,4 +51,4 @@ protected: }; -#endif // hifi_FlatImageEntity_h +#endif // hifi_ImageEntityItem_h diff --git a/scripts/system/edit.js b/scripts/system/edit.js index 4241468de1..bcd39ab0a5 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -287,7 +287,7 @@ var toolBar = (function () { properties.userData = JSON.stringify({ grabbableKey: { grabbable: false } }); } - print("properties.type: " + properties.type); + print("check 1 type: " + properties.type); entityID = Entities.addEntity(properties); if (properties.type === "ParticleEffect") { From 72d8f90ec1b8950dc06bacff76ff84c114df07f7 Mon Sep 17 00:00:00 2001 From: Elisa Lupin-Jimenez Date: Thu, 11 Jan 2018 14:43:19 -0800 Subject: [PATCH 10/89] not sure why entities don't render with these changes --- libraries/entities/src/EntityItemProperties.h | 1 + libraries/entities/src/EntityTypes.cpp | 2 +- libraries/entities/src/ImageEntityItem.cpp | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/libraries/entities/src/EntityItemProperties.h b/libraries/entities/src/EntityItemProperties.h index 5bd6836336..e121332374 100644 --- a/libraries/entities/src/EntityItemProperties.h +++ b/libraries/entities/src/EntityItemProperties.h @@ -67,6 +67,7 @@ class EntityItemProperties { friend class ParticleEffectEntityItem; // TODO: consider removing this friend relationship and use public methods friend class ZoneEntityItem; // TODO: consider removing this friend relationship and use public methods friend class WebEntityItem; // TODO: consider removing this friend relationship and use public methods + friend class ImageEntityItem; friend class LineEntityItem; // TODO: consider removing this friend relationship and use public methods friend class PolyVoxEntityItem; // TODO: consider removing this friend relationship and use public methods friend class PolyLineEntityItem; // TODO: consider removing this friend relationship and use public methods diff --git a/libraries/entities/src/EntityTypes.cpp b/libraries/entities/src/EntityTypes.cpp index 2e4a0e89a9..47ec408374 100644 --- a/libraries/entities/src/EntityTypes.cpp +++ b/libraries/entities/src/EntityTypes.cpp @@ -91,7 +91,7 @@ EntityItemPointer EntityTypes::constructEntityItem(EntityType entityType, const EntityItemPointer newEntityItem = NULL; EntityTypeFactory factory = NULL; if (entityType >= 0 && entityType <= LAST) { - qCDebug(entities) << "check 4 type: " << entityType; + qCDebug(entities) << "check 4 type: " << entityType << ", name: " << properties.getName(); factory = _factories[entityType]; } if (factory) { diff --git a/libraries/entities/src/ImageEntityItem.cpp b/libraries/entities/src/ImageEntityItem.cpp index a94374f7b5..2e6432ba19 100644 --- a/libraries/entities/src/ImageEntityItem.cpp +++ b/libraries/entities/src/ImageEntityItem.cpp @@ -24,7 +24,7 @@ #include "ShapeEntityItem.h" #include "ImageEntityItem.h" -const QString ImageEntityItem::DEFAULT_IMAGE_URL = QString(""); +const QString ImageEntityItem::DEFAULT_IMAGE_URL(""); EntityItemPointer ImageEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) { EntityItemPointer entity(new ImageEntityItem(entityID), [](EntityItem* ptr) { ptr->deleteLater(); }); From 3d000d3d011396aee7ae664d8c64f7f2ffaf77d8 Mon Sep 17 00:00:00 2001 From: Elisa Lupin-Jimenez Date: Thu, 11 Jan 2018 16:11:01 -0800 Subject: [PATCH 11/89] changed packet number --- libraries/networking/src/udt/PacketHeaders.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/libraries/networking/src/udt/PacketHeaders.cpp b/libraries/networking/src/udt/PacketHeaders.cpp index c48c6bfc0b..1ceb9b8e73 100644 --- a/libraries/networking/src/udt/PacketHeaders.cpp +++ b/libraries/networking/src/udt/PacketHeaders.cpp @@ -31,7 +31,6 @@ PacketVersion versionForPacketType(PacketType packetType) { case PacketType::EntityData: case PacketType::EntityPhysics: return static_cast(EntityVersion::SoftEntities); - case PacketType::EntityQuery: return static_cast(EntityQueryPacketVersion::RemovedJurisdictions); case PacketType::AvatarIdentity: From 6f76650789ba9454e8d8634098e7519d1a3daa43 Mon Sep 17 00:00:00 2001 From: Elisa Lupin-Jimenez Date: Tue, 16 Jan 2018 12:17:39 -0800 Subject: [PATCH 12/89] updated with master --- libraries/entities/src/ImageEntityItem.cpp | 9 +++++++-- libraries/entities/src/ImageEntityItem.h | 4 ++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/libraries/entities/src/ImageEntityItem.cpp b/libraries/entities/src/ImageEntityItem.cpp index 2e6432ba19..41bd55ee85 100644 --- a/libraries/entities/src/ImageEntityItem.cpp +++ b/libraries/entities/src/ImageEntityItem.cpp @@ -38,6 +38,8 @@ ImageEntityItem::ImageEntityItem(const EntityItemID& entityItemID) : EntityItem( EntityItemProperties ImageEntityItem::getProperties(EntityPropertyFlags desiredProperties) const { EntityItemProperties properties = EntityItem::getProperties(desiredProperties); // get the properties from our base class + COPY_ENTITY_PROPERTY_TO_PROPERTIES(imageURL, getImageURL); + // Using "Quad" shape as defined in ShapeEntityItem.cpp properties.setShape("Quad"); return properties; } @@ -45,7 +47,7 @@ EntityItemProperties ImageEntityItem::getProperties(EntityPropertyFlags desiredP bool ImageEntityItem::setProperties(const EntityItemProperties& properties) { bool somethingChanged = EntityItem::setProperties(properties); // set the properties in our base class - //SET_ENTITY_PROPERTY_FROM_PROPERTIES(shape, setShape); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(imageURL, setImageURL); if (somethingChanged) { bool wantDebug = false; @@ -63,7 +65,7 @@ bool ImageEntityItem::setProperties(const EntityItemProperties& properties) { // TODO: eventually only include properties changed since the params.nodeData->getLastTimeBagEmpty() time EntityPropertyFlags ImageEntityItem::getEntityProperties(EncodeBitstreamParams& params) const { EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params); - + requestedProperties += PROP_IMAGE_URL; return requestedProperties; } @@ -78,6 +80,7 @@ void ImageEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBit bool successPropertyFits = true; // Using "Quad" shape as defined in ShapeEntityItem.cpp APPEND_ENTITY_PROPERTY(PROP_SHAPE, "Quad"); + APPEND_ENTITY_PROPERTY(PROP_IMAGE_URL, _imageURL); } int ImageEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead, @@ -88,6 +91,8 @@ int ImageEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesRead = 0; const unsigned char* dataAt = data; + READ_ENTITY_PROPERTY(PROP_IMAGE_URL, QString, setImageURL); + return bytesRead; } diff --git a/libraries/entities/src/ImageEntityItem.h b/libraries/entities/src/ImageEntityItem.h index 55e8fc7b36..229b6f190f 100644 --- a/libraries/entities/src/ImageEntityItem.h +++ b/libraries/entities/src/ImageEntityItem.h @@ -13,6 +13,7 @@ #define hifi_ImageEntityItem_h #include "EntityItem.h" +#include "ShapeEntityItem.h" class ImageEntityItem : public EntityItem { public: @@ -49,6 +50,9 @@ public: protected: QString _imageURL; + entity::Shape _shape{ entity::Shape::Quad }; + ShapeType _collisionShapeType{ ShapeType::SHAPE_TYPE_BOX }; + }; #endif // hifi_ImageEntityItem_h From c9c55af66153882bc6fec49cbfd736b2d3308bf8 Mon Sep 17 00:00:00 2001 From: Elisa Lupin-Jimenez Date: Tue, 16 Jan 2018 17:37:49 -0800 Subject: [PATCH 13/89] setting up properties page (not complete) --- libraries/entities/src/ImageEntityItem.cpp | 16 +++++++++++++--- libraries/entities/src/ImageEntityItem.h | 4 ++++ scripts/system/html/entityProperties.html | 9 +++++++++ scripts/system/html/js/entityProperties.js | 11 +++++++++++ 4 files changed, 37 insertions(+), 3 deletions(-) diff --git a/libraries/entities/src/ImageEntityItem.cpp b/libraries/entities/src/ImageEntityItem.cpp index 41bd55ee85..1a6ab1e146 100644 --- a/libraries/entities/src/ImageEntityItem.cpp +++ b/libraries/entities/src/ImageEntityItem.cpp @@ -79,7 +79,6 @@ void ImageEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBit bool successPropertyFits = true; // Using "Quad" shape as defined in ShapeEntityItem.cpp - APPEND_ENTITY_PROPERTY(PROP_SHAPE, "Quad"); APPEND_ENTITY_PROPERTY(PROP_IMAGE_URL, _imageURL); } @@ -96,7 +95,7 @@ int ImageEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, return bytesRead; } -/*void ShapeEntityItem::setUnscaledDimensions(const glm::vec3& value) { +void ImageEntityItem::setUnscaledDimensions(const glm::vec3& value) { const float MAX_FLAT_DIMENSION = 0.0001f; if (value.y > MAX_FLAT_DIMENSION) { // enforce flatness in Y @@ -106,7 +105,7 @@ int ImageEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, } else { EntityItem::setUnscaledDimensions(value); } -}*/ +} void ImageEntityItem::setImageURL(const QString& value) { withWriteLock([&] { @@ -128,3 +127,14 @@ QString ImageEntityItem::getImageURL() const { }); } +/*void ImageEntityItem::computeShapeInfo(ShapeInfo& info) { + // This will be called whenever DIRTY_SHAPE flag (set by dimension change, etc) + // is set. + + EntityItem::computeShapeInfo(info); +}*/ + +// This value specifies how the shape should be treated by physics calculations. +ShapeType ImageEntityItem::getShapeType() const { + return _collisionShapeType; +} diff --git a/libraries/entities/src/ImageEntityItem.h b/libraries/entities/src/ImageEntityItem.h index 229b6f190f..f3f4fbe6c7 100644 --- a/libraries/entities/src/ImageEntityItem.h +++ b/libraries/entities/src/ImageEntityItem.h @@ -42,11 +42,15 @@ public: EntityPropertyFlags& propertyFlags, bool overwriteLocalData, bool& somethingChanged) override; + void setUnscaledDimensions(const glm::vec3& value) override; static const QString DEFAULT_IMAGE_URL; virtual void setImageURL(const QString& value); QString getImageURL() const; + //virtual void computeShapeInfo(ShapeInfo& info) override; + virtual ShapeType getShapeType() const override; + protected: QString _imageURL; diff --git a/scripts/system/html/entityProperties.html b/scripts/system/html/entityProperties.html index b93974ee77..6e8178d408 100644 --- a/scripts/system/html/entityProperties.html +++ b/scripts/system/html/entityProperties.html @@ -714,6 +714,15 @@ +
+ + ImageM + +
+ + +
+
diff --git a/scripts/system/html/js/entityProperties.js b/scripts/system/html/js/entityProperties.js index 7008d0df66..05a1b2576b 100644 --- a/scripts/system/html/js/entityProperties.js +++ b/scripts/system/html/js/entityProperties.js @@ -133,6 +133,11 @@ function createEmitGroupNumberPropertyUpdateFunction(group, propertyName) { }; } +function createImageURLUpdateFunction(propertyName) { + return function() { + updateProperty(propertyName, this.value); + } +} function createEmitTextPropertyUpdateFunction(propertyName) { return function() { @@ -621,6 +626,8 @@ function loaded() { var elModelTextures = document.getElementById("property-model-textures"); var elModelOriginalTextures = document.getElementById("property-model-original-textures"); + var elImageURL = document.getElementById("property-image-url"); + var elWebSourceURL = document.getElementById("property-web-source-url"); var elWebDPI = document.getElementById("property-web-dpi"); @@ -985,6 +992,8 @@ function loaded() { } else if (properties.type === "Web") { elWebSourceURL.value = properties.sourceUrl; elWebDPI.value = properties.dpi; + } else if (properties.type === "Image") { + elImageURL.value = properties.imageURL; } else if (properties.type === "Text") { elTextText.value = properties.text; elTextLineHeight.value = properties.lineHeight.toFixed(4); @@ -1352,6 +1361,8 @@ function loaded() { elShape.addEventListener('change', createEmitTextPropertyUpdateFunction('shape')); + elImageURL.addEventListener('change', createImageURLUpdateFunction('imageURL')); + elWebSourceURL.addEventListener('change', createEmitTextPropertyUpdateFunction('sourceUrl')); elWebDPI.addEventListener('change', createEmitNumberPropertyUpdateFunction('dpi', 0)); From ecb53192add37a7bb70c88d2c00b6568aab0db7d Mon Sep 17 00:00:00 2001 From: Elisa Lupin-Jimenez Date: Wed, 17 Jan 2018 11:57:40 -0800 Subject: [PATCH 14/89] edit properties reflect image members --- scripts/system/html/css/edit-style.css | 49 +++++++++++++++++++++- scripts/system/html/js/entityProperties.js | 7 ++-- 2 files changed, 52 insertions(+), 4 deletions(-) diff --git a/scripts/system/html/css/edit-style.css b/scripts/system/html/css/edit-style.css index 736d42d593..58acc317bd 100644 --- a/scripts/system/html/css/edit-style.css +++ b/scripts/system/html/css/edit-style.css @@ -448,7 +448,7 @@ input[type=checkbox]:checked + label:hover { border: 1.5pt solid black; } -.shape-section, .light-section, .model-section, .web-section, .hyperlink-section, .text-section, .zone-section { +.shape-section, .light-section, .model-section, .web-section, .image-section, .hyperlink-section, .text-section, .zone-section { display: table; } @@ -564,6 +564,7 @@ hr { .text-group[collapsed="true"] ~ .text-group, .zone-group[collapsed="true"] ~ .zone-group, +.image-group[collapsed="true"] ~ .image-group, .web-group[collapsed="true"] ~ .web-group, .hyperlink-group[collapsed="true"] ~ .hyperlink-group, .spatial-group[collapsed="true"] ~ .spatial-group, @@ -1466,6 +1467,9 @@ input#reset-to-natural-dimensions { #properties-list.ShapeMenu #text, #properties-list.BoxMenu #text, #properties-list.SphereMenu #text, +#properties-list.ShapeMenu #image, +#properties-list.BoxMenu #image, +#properties-list.SphereMenu #image, #properties-list.ShapeMenu #web, #properties-list.BoxMenu #web, #properties-list.SphereMenu #web { @@ -1497,6 +1501,7 @@ input#reset-to-natural-dimensions { #properties-list.ParticleEffectMenu #shape-list, #properties-list.ParticleEffectMenu #text, #properties-list.ParticleEffectMenu #web, +#properties-list.ParticleEffectMenu #image, #properties-list.ParticleEffectMenu #zone { display: none; } @@ -1527,6 +1532,7 @@ input#reset-to-natural-dimensions { #properties-list.LightMenu #model, #properties-list.LightMenu #zone, #properties-list.LightMenu #text, +#properties-list.LightMenu #image, #properties-list.LightMenu #web { display: none; } @@ -1563,6 +1569,7 @@ input#reset-to-natural-dimensions { #properties-list.ModelMenu #light, #properties-list.ModelMenu #zone, #properties-list.ModelMenu #text, +#properties-list.ModelMenu #image, #properties-list.ModelMenu #web { display: none; } @@ -1599,6 +1606,7 @@ input#reset-to-natural-dimensions { #properties-list.ZoneMenu #light, #properties-list.ZoneMenu #model, #properties-list.ZoneMenu #text, +#properties-list.ZoneMenu #image, #properties-list.ZoneMenu #web { display: none; } @@ -1609,6 +1617,43 @@ input#reset-to-natural-dimensions { } +/* ----- Order of Menu items for Image ----- */ +#properties-list.ImageMenu #general { + order: 1; +} +#properties-list.ImageMenu #image { + order: 2; +} +#properties-list.ImageMenu #collision-info { + order: 3; +} +#properties-list.ImageMenu #physical { + order: 4; +} +#properties-list.ImageMenu #spatial { + order: 5; +} +#properties-list.ImageMenu #behavior { + order: 6; +} +#properties-list.ImageMenu #hyperlink { + order: 7; +} +/* sections to hide */ +#properties-list.ImageMenu #light, +#properties-list.ImageMenu #model, +#properties-list.ImageMenu #zone, +#properties-list.ImageMenu #web, +#properties-list.ImageMenu #text { + display: none; +} +/* items to hide */ +#properties-list.ImageMenu #shape-list, +#properties-list.ImageMenu #base-color-section { + display: none; +} + + /* ----- Order of Menu items for Web ----- */ #properties-list.WebMenu #general { order: 1; @@ -1635,6 +1680,7 @@ input#reset-to-natural-dimensions { #properties-list.WebMenu #light, #properties-list.WebMenu #model, #properties-list.WebMenu #zone, +#properties-list.WebMenu #image, #properties-list.WebMenu #text { display: none; } @@ -1672,6 +1718,7 @@ input#reset-to-natural-dimensions { #properties-list.TextMenu #light, #properties-list.TextMenu #model, #properties-list.TextMenu #zone, +#properties-list.TextMenu #image, #properties-list.TextMenu #web { display: none; } diff --git a/scripts/system/html/js/entityProperties.js b/scripts/system/html/js/entityProperties.js index 05a1b2576b..54b50de106 100644 --- a/scripts/system/html/js/entityProperties.js +++ b/scripts/system/html/js/entityProperties.js @@ -19,6 +19,7 @@ var ICON_FOR_TYPE = { ParticleEffect: "", Model: "", Web: "q", + Image: "q", // what do? Text: "l", Light: "p", Zone: "o", @@ -134,9 +135,9 @@ function createEmitGroupNumberPropertyUpdateFunction(group, propertyName) { } function createImageURLUpdateFunction(propertyName) { - return function() { + return function () { updateProperty(propertyName, this.value); - } + }; } function createEmitTextPropertyUpdateFunction(propertyName) { @@ -1735,7 +1736,7 @@ function loaded() { } // Dropdowns - // For each dropdown the following replacement is created in place of the oriringal dropdown... + // For each dropdown the following replacement is created in place of the original dropdown... // Structure created: //
//
display textcarat
From dc5f29aa58268a9abdc9b82f6a9bc41828c28832 Mon Sep 17 00:00:00 2001 From: Elisa Lupin-Jimenez Date: Wed, 17 Jan 2018 17:57:09 -0800 Subject: [PATCH 15/89] entity item properties hooked up for image --- libraries/entities/src/EntityItemProperties.cpp | 9 +++++++++ scripts/system/edit.js | 2 +- scripts/system/html/js/entityProperties.js | 3 ++- 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index fc8ca18fb6..3729c75f3d 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -350,6 +350,8 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { CHECK_PROPERTY_CHANGE(PROP_AMBIENT_LIGHT_MODE, ambientLightMode); CHECK_PROPERTY_CHANGE(PROP_SKYBOX_MODE, skyboxMode); + CHECK_PROPERTY_CHANGE(PROP_IMAGE_URL, imageURL); + CHECK_PROPERTY_CHANGE(PROP_SOURCE_URL, sourceUrl); CHECK_PROPERTY_CHANGE(PROP_VOXEL_VOLUME_SIZE, voxelVolumeSize); CHECK_PROPERTY_CHANGE(PROP_VOXEL_DATA, voxelData); @@ -785,6 +787,8 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool COPY_PROPERTY_FROM_QSCRIPTVALUE_ENUM(ambientLightMode, AmbientLightMode); COPY_PROPERTY_FROM_QSCRIPTVALUE_ENUM(skyboxMode, SkyboxMode); + COPY_PROPERTY_FROM_QSCRIPTVALUE(imageURL, QString, setImageURL); + COPY_PROPERTY_FROM_QSCRIPTVALUE(sourceUrl, QString, setSourceUrl); COPY_PROPERTY_FROM_QSCRIPTVALUE(voxelVolumeSize, glmVec3, setVoxelVolumeSize); COPY_PROPERTY_FROM_QSCRIPTVALUE(voxelData, QByteArray, setVoxelData); @@ -945,6 +949,8 @@ void EntityItemProperties::merge(const EntityItemProperties& other) { COPY_PROPERTY_IF_CHANGED(ambientLightMode); COPY_PROPERTY_IF_CHANGED(skyboxMode); + COPY_PROPERTY_IF_CHANGED(imageURL); + COPY_PROPERTY_IF_CHANGED(sourceUrl); COPY_PROPERTY_IF_CHANGED(voxelVolumeSize); COPY_PROPERTY_IF_CHANGED(voxelData); @@ -1138,6 +1144,7 @@ void EntityItemProperties::entityPropertyFlagsFromScriptValue(const QScriptValue ADD_PROPERTY_TO_MAP(PROP_VOXEL_SURFACE_STYLE, VoxelSurfaceStyle, voxelSurfaceStyle, uint16_t); ADD_PROPERTY_TO_MAP(PROP_NAME, Name, name, QString); ADD_PROPERTY_TO_MAP(PROP_SOURCE_URL, SourceUrl, sourceUrl, QString); + ADD_PROPERTY_TO_MAP(PROP_IMAGE_URL, ImageURL, imageURL, QString); ADD_PROPERTY_TO_MAP(PROP_LINE_WIDTH, LineWidth, lineWidth, float); ADD_PROPERTY_TO_MAP(PROP_LINE_POINTS, LinePoints, linePoints, QVector); ADD_PROPERTY_TO_MAP(PROP_HREF, Href, href, QString); @@ -2060,6 +2067,8 @@ void EntityItemProperties::markAllChanged() { _skybox.markAllChanged(); _haze.markAllChanged(); + _imageURLChanged = true; + _sourceUrlChanged = true; _voxelVolumeSizeChanged = true; _voxelDataChanged = true; diff --git a/scripts/system/edit.js b/scripts/system/edit.js index bcd39ab0a5..5f976e07d3 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -546,7 +546,7 @@ var toolBar = (function () { createNewEntity({ type: "Image", dimensions: DEFAULT_DIMENSIONS, - sourceUrl: "https://highfidelity.com/" + imageURL: "https://hifi-content.s3.amazonaws.com/elisalj/image_entity/dog.jpg" }); }); diff --git a/scripts/system/html/js/entityProperties.js b/scripts/system/html/js/entityProperties.js index 54b50de106..7c3af548ba 100644 --- a/scripts/system/html/js/entityProperties.js +++ b/scripts/system/html/js/entityProperties.js @@ -19,7 +19,7 @@ var ICON_FOR_TYPE = { ParticleEffect: "", Model: "", Web: "q", - Image: "q", // what do? + Image: "q", // change this when image type icon added Text: "l", Light: "p", Zone: "o", @@ -995,6 +995,7 @@ function loaded() { elWebDPI.value = properties.dpi; } else if (properties.type === "Image") { elImageURL.value = properties.imageURL; + //elImageURL.value = properties.sourceURL; } else if (properties.type === "Text") { elTextText.value = properties.text; elTextLineHeight.value = properties.lineHeight.toFixed(4); From 576d683d3bb62baef14c4ddc2f3696ca71679258 Mon Sep 17 00:00:00 2001 From: Elisa Lupin-Jimenez Date: Thu, 18 Jan 2018 14:40:33 -0800 Subject: [PATCH 16/89] initial changes to incorporate snap model --- libraries/entities/src/EntityScriptingInterface.cpp | 2 -- libraries/entities/src/EntityTree.cpp | 1 - libraries/entities/src/EntityTypes.cpp | 4 +++- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/libraries/entities/src/EntityScriptingInterface.cpp b/libraries/entities/src/EntityScriptingInterface.cpp index cab4251d97..4342f0e683 100644 --- a/libraries/entities/src/EntityScriptingInterface.cpp +++ b/libraries/entities/src/EntityScriptingInterface.cpp @@ -266,8 +266,6 @@ QUuid EntityScriptingInterface::addEntity(const EntityItemProperties& properties bool success = true; if (_entityTree) { _entityTree->withWriteLock([&] { - propertiesWithSimID.getType(); - qCDebug(entities) << "check 2 type: " << propertiesWithSimID.getType(); EntityItemPointer entity = _entityTree->addEntity(id, propertiesWithSimID); if (entity) { if (propertiesWithSimID.queryAACubeRelatedPropertyChanged()) { diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index 56c3aa216c..bf29f3bec9 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -514,7 +514,6 @@ EntityItemPointer EntityTree::addEntity(const EntityItemID& entityID, const Enti // construct the instance of the entity EntityTypes::EntityType type = props.getType(); - qCDebug(entities) << "check 3 type: " << type; result = EntityTypes::constructEntityItem(type, entityID, props); if (result) { diff --git a/libraries/entities/src/EntityTypes.cpp b/libraries/entities/src/EntityTypes.cpp index 47ec408374..6326df3cc1 100644 --- a/libraries/entities/src/EntityTypes.cpp +++ b/libraries/entities/src/EntityTypes.cpp @@ -91,7 +91,9 @@ EntityItemPointer EntityTypes::constructEntityItem(EntityType entityType, const EntityItemPointer newEntityItem = NULL; EntityTypeFactory factory = NULL; if (entityType >= 0 && entityType <= LAST) { - qCDebug(entities) << "check 4 type: " << entityType << ", name: " << properties.getName(); + if (getEntityTypeName(entityType) == "Image") { + entityType = getEntityTypeFromName("Web"); + } factory = _factories[entityType]; } if (factory) { From ceb621a52151d20bfa66dd4eb09779c45f003901 Mon Sep 17 00:00:00 2001 From: Elisa Lupin-Jimenez Date: Mon, 22 Jan 2018 11:36:58 -0800 Subject: [PATCH 17/89] reverted protocol change for images --- libraries/entities/src/EntityItemProperties.cpp | 16 ++++++++-------- libraries/entities/src/EntityItemProperties.h | 2 +- libraries/entities/src/EntityPropertyFlags.h | 4 ++-- libraries/entities/src/EntityTypes.cpp | 7 ++----- libraries/entities/src/EntityTypes.h | 2 +- libraries/entities/src/ImageEntityItem.cpp | 8 ++++++-- libraries/entities/src/ImageEntityItem.h | 4 ++++ scripts/system/edit.js | 10 +++++++++- 8 files changed, 33 insertions(+), 20 deletions(-) diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index 3729c75f3d..48ad9601ba 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -350,7 +350,7 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { CHECK_PROPERTY_CHANGE(PROP_AMBIENT_LIGHT_MODE, ambientLightMode); CHECK_PROPERTY_CHANGE(PROP_SKYBOX_MODE, skyboxMode); - CHECK_PROPERTY_CHANGE(PROP_IMAGE_URL, imageURL); + //CHECK_PROPERTY_CHANGE(PROP_IMAGE_URL, imageURL); CHECK_PROPERTY_CHANGE(PROP_SOURCE_URL, sourceUrl); CHECK_PROPERTY_CHANGE(PROP_VOXEL_VOLUME_SIZE, voxelVolumeSize); @@ -593,9 +593,9 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool } // Image only - if (_type == EntityTypes::Image) { + /*if (_type == EntityTypes::Image) { COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_IMAGE_URL, imageURL); - } + }*/ // Web only if (_type == EntityTypes::Web) { @@ -1144,7 +1144,7 @@ void EntityItemProperties::entityPropertyFlagsFromScriptValue(const QScriptValue ADD_PROPERTY_TO_MAP(PROP_VOXEL_SURFACE_STYLE, VoxelSurfaceStyle, voxelSurfaceStyle, uint16_t); ADD_PROPERTY_TO_MAP(PROP_NAME, Name, name, QString); ADD_PROPERTY_TO_MAP(PROP_SOURCE_URL, SourceUrl, sourceUrl, QString); - ADD_PROPERTY_TO_MAP(PROP_IMAGE_URL, ImageURL, imageURL, QString); + //ADD_PROPERTY_TO_MAP(PROP_IMAGE_URL, ImageURL, imageURL, QString); ADD_PROPERTY_TO_MAP(PROP_LINE_WIDTH, LineWidth, lineWidth, float); ADD_PROPERTY_TO_MAP(PROP_LINE_POINTS, LinePoints, linePoints, QVector); ADD_PROPERTY_TO_MAP(PROP_HREF, Href, href, QString); @@ -1381,9 +1381,9 @@ OctreeElement::AppendState EntityItemProperties::encodeEntityEditPacket(PacketTy APPEND_ENTITY_PROPERTY(PROP_DPI, properties.getDPI()); } - if (properties.getType() == EntityTypes::Image) { + /*if (properties.getType() == EntityTypes::Image) { APPEND_ENTITY_PROPERTY(PROP_IMAGE_URL, properties.getImageURL()); - } + }*/ if (properties.getType() == EntityTypes::Text) { APPEND_ENTITY_PROPERTY(PROP_TEXT, properties.getText()); @@ -1746,9 +1746,9 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_DPI, uint16_t, setDPI); } - if (properties.getType() == EntityTypes::Image) { + /*if (properties.getType() == EntityTypes::Image) { READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_IMAGE_URL, QString, setImageURL); - } + }*/ if (properties.getType() == EntityTypes::Text) { READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_TEXT, QString, setText); diff --git a/libraries/entities/src/EntityItemProperties.h b/libraries/entities/src/EntityItemProperties.h index e121332374..14544cd4d1 100644 --- a/libraries/entities/src/EntityItemProperties.h +++ b/libraries/entities/src/EntityItemProperties.h @@ -67,7 +67,7 @@ class EntityItemProperties { friend class ParticleEffectEntityItem; // TODO: consider removing this friend relationship and use public methods friend class ZoneEntityItem; // TODO: consider removing this friend relationship and use public methods friend class WebEntityItem; // TODO: consider removing this friend relationship and use public methods - friend class ImageEntityItem; + //friend class ImageEntityItem; friend class LineEntityItem; // TODO: consider removing this friend relationship and use public methods friend class PolyVoxEntityItem; // TODO: consider removing this friend relationship and use public methods friend class PolyLineEntityItem; // TODO: consider removing this friend relationship and use public methods diff --git a/libraries/entities/src/EntityPropertyFlags.h b/libraries/entities/src/EntityPropertyFlags.h index 4768ebed86..ec8f4e08c8 100644 --- a/libraries/entities/src/EntityPropertyFlags.h +++ b/libraries/entities/src/EntityPropertyFlags.h @@ -42,8 +42,8 @@ enum EntityPropertyList { PROP_ANIMATION_ALLOW_TRANSLATION, PROP_RELAY_PARENT_JOINTS, - // for image - PROP_IMAGE_URL, + /*// for image + PROP_IMAGE_URL,*/ // these properties are supported by the EntityItem base class PROP_REGISTRATION_POINT, diff --git a/libraries/entities/src/EntityTypes.cpp b/libraries/entities/src/EntityTypes.cpp index 6326df3cc1..6bf5a176a7 100644 --- a/libraries/entities/src/EntityTypes.cpp +++ b/libraries/entities/src/EntityTypes.cpp @@ -28,7 +28,7 @@ #include "LineEntityItem.h" #include "PolyVoxEntityItem.h" #include "PolyLineEntityItem.h" -#include "ImageEntityItem.h" +//#include "ImageEntityItem.h" #include "ShapeEntityItem.h" QMap EntityTypes::_typeToNameMap; @@ -48,7 +48,7 @@ REGISTER_ENTITY_TYPE(Zone) REGISTER_ENTITY_TYPE(Line) REGISTER_ENTITY_TYPE(PolyVox) REGISTER_ENTITY_TYPE(PolyLine) -REGISTER_ENTITY_TYPE(Image) +//REGISTER_ENTITY_TYPE(Image) REGISTER_ENTITY_TYPE(Shape) REGISTER_ENTITY_TYPE_WITH_FACTORY(Box, ShapeEntityItem::boxFactory) REGISTER_ENTITY_TYPE_WITH_FACTORY(Sphere, ShapeEntityItem::sphereFactory) @@ -91,9 +91,6 @@ EntityItemPointer EntityTypes::constructEntityItem(EntityType entityType, const EntityItemPointer newEntityItem = NULL; EntityTypeFactory factory = NULL; if (entityType >= 0 && entityType <= LAST) { - if (getEntityTypeName(entityType) == "Image") { - entityType = getEntityTypeFromName("Web"); - } factory = _factories[entityType]; } if (factory) { diff --git a/libraries/entities/src/EntityTypes.h b/libraries/entities/src/EntityTypes.h index 8d986c8090..6fe4274820 100644 --- a/libraries/entities/src/EntityTypes.h +++ b/libraries/entities/src/EntityTypes.h @@ -48,7 +48,7 @@ public: Line, PolyVox, PolyLine, - Image, + //Image, Shape, LAST = Shape } EntityType; diff --git a/libraries/entities/src/ImageEntityItem.cpp b/libraries/entities/src/ImageEntityItem.cpp index 1a6ab1e146..e30d85257e 100644 --- a/libraries/entities/src/ImageEntityItem.cpp +++ b/libraries/entities/src/ImageEntityItem.cpp @@ -9,6 +9,8 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +/* NOT IN USE + #include #include @@ -127,14 +129,16 @@ QString ImageEntityItem::getImageURL() const { }); } -/*void ImageEntityItem::computeShapeInfo(ShapeInfo& info) { +void ImageEntityItem::computeShapeInfo(ShapeInfo& info) { // This will be called whenever DIRTY_SHAPE flag (set by dimension change, etc) // is set. EntityItem::computeShapeInfo(info); -}*/ +} // This value specifies how the shape should be treated by physics calculations. ShapeType ImageEntityItem::getShapeType() const { return _collisionShapeType; } + +*/ diff --git a/libraries/entities/src/ImageEntityItem.h b/libraries/entities/src/ImageEntityItem.h index f3f4fbe6c7..276d21e6f0 100644 --- a/libraries/entities/src/ImageEntityItem.h +++ b/libraries/entities/src/ImageEntityItem.h @@ -9,6 +9,8 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +/* NOT IN USE + #ifndef hifi_ImageEntityItem_h #define hifi_ImageEntityItem_h @@ -60,3 +62,5 @@ protected: }; #endif // hifi_ImageEntityItem_h + +*/ diff --git a/scripts/system/edit.js b/scripts/system/edit.js index 5f976e07d3..1d02fed787 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -540,7 +540,7 @@ var toolBar = (function () { }); }); - // for image button + /*// for image button addButton("newImageButton", "web-01.svg", function () { print("new image message is received"); createNewEntity({ @@ -548,6 +548,14 @@ var toolBar = (function () { dimensions: DEFAULT_DIMENSIONS, imageURL: "https://hifi-content.s3.amazonaws.com/elisalj/image_entity/dog.jpg" }); + });*/ + addButton("newImageButton", "web-01.svg", function () { + createNewEntity({ + type: "Model", + dimensions: DEFAULT_DIMENSIONS, + modelURL: "http://hifi-content.s3.amazonaws.com/alan/dev/Test/snapshot.fbx", + textures: JSON.stringify({ "tex.picture": "https://hifi-content.s3.amazonaws.com/elisalj/image_entity/dog.jpg" }) + }); }); addButton("newWebButton", "web-01.svg", function () { From 308e481e63f5d19db4f8548c3a8689d60a22958f Mon Sep 17 00:00:00 2001 From: Elisa Lupin-Jimenez Date: Mon, 22 Jan 2018 17:46:12 -0800 Subject: [PATCH 18/89] switch to image property list not working --- scripts/system/edit.js | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/scripts/system/edit.js b/scripts/system/edit.js index 1d02fed787..6f48e1167a 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -252,7 +252,7 @@ var toolBar = (function () { properties.rotation = MyAvatar.orientation; // added image here - var PRE_ADJUST_ENTITY_TYPES = ["Box", "Sphere", "Shape", "Text", "Image", "Web"]; + var PRE_ADJUST_ENTITY_TYPES = ["Box", "Sphere", "Shape", "Text", "Web"]; if (PRE_ADJUST_ENTITY_TYPES.indexOf(properties.type) !== -1) { // Adjust position of entity per bounding box prior to creating it. @@ -287,7 +287,6 @@ var toolBar = (function () { properties.userData = JSON.stringify({ grabbableKey: { grabbable: false } }); } - print("check 1 type: " + properties.type); entityID = Entities.addEntity(properties); if (properties.type === "ParticleEffect") { @@ -540,20 +539,21 @@ var toolBar = (function () { }); }); - /*// for image button - addButton("newImageButton", "web-01.svg", function () { - print("new image message is received"); - createNewEntity({ - type: "Image", - dimensions: DEFAULT_DIMENSIONS, - imageURL: "https://hifi-content.s3.amazonaws.com/elisalj/image_entity/dog.jpg" - }); - });*/ addButton("newImageButton", "web-01.svg", function () { createNewEntity({ type: "Model", - dimensions: DEFAULT_DIMENSIONS, + // make constant for this later + dimensions: { + x: 4.16, + y: 0.02, + z: 2.58 + }, modelURL: "http://hifi-content.s3.amazonaws.com/alan/dev/Test/snapshot.fbx", + imageURL: "https://hifi-content.s3.amazonaws.com/elisalj/image_entity/dog.jpg", + // will this work? + /*get textures() { + return JSON.stringify({ "tex.picture": this.imageURL }); + }*/ textures: JSON.stringify({ "tex.picture": "https://hifi-content.s3.amazonaws.com/elisalj/image_entity/dog.jpg" }) }); }); From 42151b8fd47f6b32d3662d9d599096ec4d290408 Mon Sep 17 00:00:00 2001 From: Elisa Lupin-Jimenez Date: Tue, 23 Jan 2018 14:50:41 -0800 Subject: [PATCH 19/89] creating new image entity opens image property options --- .../entities/src/EntityItemProperties.cpp | 12 +- libraries/entities/src/EntityItemProperties.h | 2 +- libraries/entities/src/ImageEntityItem.cpp | 2 +- scripts/system/edit.js | 6 +- scripts/system/html/js/entityProperties.js | 123 ++++++++++++++++++ 5 files changed, 136 insertions(+), 9 deletions(-) diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index 48ad9601ba..7fcfc00ee8 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -592,8 +592,10 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_SKYBOX_MODE, skyboxMode, getSkyboxModeAsString()); } - // Image only - /*if (_type == EntityTypes::Image) { + + + /*// Image only + if (_type == EntityTypes::Image) { COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_IMAGE_URL, imageURL); }*/ @@ -787,7 +789,7 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool COPY_PROPERTY_FROM_QSCRIPTVALUE_ENUM(ambientLightMode, AmbientLightMode); COPY_PROPERTY_FROM_QSCRIPTVALUE_ENUM(skyboxMode, SkyboxMode); - COPY_PROPERTY_FROM_QSCRIPTVALUE(imageURL, QString, setImageURL); + //COPY_PROPERTY_FROM_QSCRIPTVALUE(imageURL, QString, setImageURL); COPY_PROPERTY_FROM_QSCRIPTVALUE(sourceUrl, QString, setSourceUrl); COPY_PROPERTY_FROM_QSCRIPTVALUE(voxelVolumeSize, glmVec3, setVoxelVolumeSize); @@ -949,7 +951,7 @@ void EntityItemProperties::merge(const EntityItemProperties& other) { COPY_PROPERTY_IF_CHANGED(ambientLightMode); COPY_PROPERTY_IF_CHANGED(skyboxMode); - COPY_PROPERTY_IF_CHANGED(imageURL); + //COPY_PROPERTY_IF_CHANGED(imageURL); COPY_PROPERTY_IF_CHANGED(sourceUrl); COPY_PROPERTY_IF_CHANGED(voxelVolumeSize); @@ -2067,7 +2069,7 @@ void EntityItemProperties::markAllChanged() { _skybox.markAllChanged(); _haze.markAllChanged(); - _imageURLChanged = true; + //_imageURLChanged = true; _sourceUrlChanged = true; _voxelVolumeSizeChanged = true; diff --git a/libraries/entities/src/EntityItemProperties.h b/libraries/entities/src/EntityItemProperties.h index 14544cd4d1..5ca28a12c5 100644 --- a/libraries/entities/src/EntityItemProperties.h +++ b/libraries/entities/src/EntityItemProperties.h @@ -195,7 +195,7 @@ public: DEFINE_PROPERTY_GROUP(Haze, haze, HazePropertyGroup); DEFINE_PROPERTY_GROUP(Animation, animation, AnimationPropertyGroup); DEFINE_PROPERTY_REF(PROP_SOURCE_URL, SourceUrl, sourceUrl, QString, ""); - DEFINE_PROPERTY_REF(PROP_IMAGE_URL, ImageURL, imageURL, QString, ""); + //DEFINE_PROPERTY_REF(PROP_IMAGE_URL, ImageURL, imageURL, QString, ""); DEFINE_PROPERTY(PROP_LINE_WIDTH, LineWidth, lineWidth, float, LineEntityItem::DEFAULT_LINE_WIDTH); DEFINE_PROPERTY_REF(LINE_POINTS, LinePoints, linePoints, QVector, QVector()); DEFINE_PROPERTY_REF(PROP_HREF, Href, href, QString, ""); diff --git a/libraries/entities/src/ImageEntityItem.cpp b/libraries/entities/src/ImageEntityItem.cpp index e30d85257e..9969a39ed0 100644 --- a/libraries/entities/src/ImageEntityItem.cpp +++ b/libraries/entities/src/ImageEntityItem.cpp @@ -35,7 +35,7 @@ EntityItemPointer ImageEntityItem::factory(const EntityItemID& entityID, const E } ImageEntityItem::ImageEntityItem(const EntityItemID& entityItemID) : EntityItem(entityItemID) { - _type = EntityTypes::Image; + //_type = EntityTypes::Image; } EntityItemProperties ImageEntityItem::getProperties(EntityPropertyFlags desiredProperties) const { diff --git a/scripts/system/edit.js b/scripts/system/edit.js index 6f48e1167a..a5c236a6c9 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -253,6 +253,8 @@ var toolBar = (function () { // added image here var PRE_ADJUST_ENTITY_TYPES = ["Box", "Sphere", "Shape", "Text", "Web"]; + //var PRE_ADJUST_ENTITY_TYPES = ["Box", "Sphere", "Shape", "Text", "Image", "Web"]; + if (PRE_ADJUST_ENTITY_TYPES.indexOf(properties.type) !== -1) { // Adjust position of entity per bounding box prior to creating it. @@ -539,6 +541,7 @@ var toolBar = (function () { }); }); + // for image button addButton("newImageButton", "web-01.svg", function () { createNewEntity({ type: "Model", @@ -548,8 +551,7 @@ var toolBar = (function () { y: 0.02, z: 2.58 }, - modelURL: "http://hifi-content.s3.amazonaws.com/alan/dev/Test/snapshot.fbx", - imageURL: "https://hifi-content.s3.amazonaws.com/elisalj/image_entity/dog.jpg", + modelURL: "https://hifi-content.s3.amazonaws.com/elisalj/image_entity/snapshot.fbx", // will this work? /*get textures() { return JSON.stringify({ "tex.picture": this.imageURL }); diff --git a/scripts/system/html/js/entityProperties.js b/scripts/system/html/js/entityProperties.js index 7c3af548ba..2b90f8ddfa 100644 --- a/scripts/system/html/js/entityProperties.js +++ b/scripts/system/html/js/entityProperties.js @@ -511,6 +511,10 @@ function loaded() { var elPropertiesList = document.getElementById("properties-list"); var elID = document.getElementById("property-id"); var elType = document.getElementById("property-type"); +<<<<<<< HEAD +======= + debugPrint("the type is: " + JSON.stringify(elType)); +>>>>>>> creating new image entity opens image property options var elTypeIcon = document.getElementById("type-icon"); var elName = document.getElementById("property-name"); var elLocked = document.getElementById("property-locked"); @@ -661,16 +665,28 @@ function loaded() { var elZoneKeyLightDirectionY = document.getElementById("property-zone-key-light-direction-y"); // Skybox +<<<<<<< HEAD var elZoneSkyboxModeInherit = document.getElementById("property-zone-skybox-mode-inherit"); var elZoneSkyboxModeDisabled = document.getElementById("property-zone-skybox-mode-disabled"); var elZoneSkyboxModeEnabled = document.getElementById("property-zone-skybox-mode-enabled"); +======= + var elZoneSkyboxModeInherit = document.getElementById("property-zone-skybox-mode-inherit"); + var elZoneSkyboxModeDisabled = document.getElementById("property-zone-skybox-mode-disabled"); + var elZoneSkyboxModeEnabled = document.getElementById("property-zone-skybox-mode-enabled"); +>>>>>>> creating new image entity opens image property options // Ambient light var elCopySkyboxURLToAmbientURL = document.getElementById("copy-skybox-url-to-ambient-url"); +<<<<<<< HEAD var elZoneAmbientLightModeInherit = document.getElementById("property-zone-ambient-light-mode-inherit"); var elZoneAmbientLightModeDisabled = document.getElementById("property-zone-ambient-light-mode-disabled"); var elZoneAmbientLightModeEnabled = document.getElementById("property-zone-ambient-light-mode-enabled"); +======= + var elZoneAmbientLightModeInherit = document.getElementById("property-zone-ambient-light-mode-inherit"); + var elZoneAmbientLightModeDisabled = document.getElementById("property-zone-ambient-light-mode-disabled"); + var elZoneAmbientLightModeEnabled = document.getElementById("property-zone-ambient-light-mode-enabled"); +>>>>>>> creating new image entity opens image property options var elZoneAmbientLightIntensity = document.getElementById("property-zone-key-ambient-intensity"); var elZoneAmbientLightURL = document.getElementById("property-zone-key-ambient-url"); @@ -787,7 +803,11 @@ function loaded() { } else { properties = data.selections[0].properties; +<<<<<<< HEAD +======= + debugPrint("props: " + JSON.stringify(properties)); +>>>>>>> creating new image entity opens image property options if (lastEntityID !== '"' + properties.id + '"' && lastEntityID !== null && editor !== null) { saveJSONUserData(true); } @@ -796,6 +816,14 @@ function loaded() { lastEntityID = '"' + properties.id + '"'; elID.value = properties.id; +<<<<<<< HEAD +======= + // image is not yet a separate entity type + if (properties.type === "Model" && properties.modelURL === "https://hifi-content.s3.amazonaws.com/elisalj/image_entity/snapshot.fbx") { + properties.type = "Image"; + } + +>>>>>>> creating new image entity opens image property options // Create class name for css ruleset filtering elPropertiesList.className = properties.type + 'Menu'; @@ -1024,9 +1052,15 @@ function loaded() { } else if (properties.type === "Zone") { // Key light +<<<<<<< HEAD elZoneKeyLightModeInherit.checked = (properties.keyLightMode === 'inherit'); elZoneKeyLightModeDisabled.checked = (properties.keyLightMode === 'disabled'); elZoneKeyLightModeEnabled.checked = (properties.keyLightMode === 'enabled'); +======= + elZoneKeyLightModeInherit.checked = (properties.keyLightMode === 'inherit'); + elZoneKeyLightModeDisabled.checked = (properties.keyLightMode === 'disabled'); + elZoneKeyLightModeEnabled.checked = (properties.keyLightMode === 'enabled'); +>>>>>>> creating new image entity opens image property options elZoneKeyLightColor.style.backgroundColor = "rgb(" + properties.keyLight.color.red + "," + properties.keyLight.color.green + "," + properties.keyLight.color.blue + ")"; @@ -1038,6 +1072,7 @@ function loaded() { elZoneKeyLightDirectionY.value = properties.keyLight.direction.y.toFixed(2); // Skybox +<<<<<<< HEAD elZoneSkyboxModeInherit.checked = (properties.skyboxMode === 'inherit'); elZoneSkyboxModeDisabled.checked = (properties.skyboxMode === 'disabled'); elZoneSkyboxModeEnabled.checked = (properties.skyboxMode === 'enabled'); @@ -1046,14 +1081,30 @@ function loaded() { elZoneAmbientLightModeInherit.checked = (properties.ambientLightMode === 'inherit'); elZoneAmbientLightModeDisabled.checked = (properties.ambientLightMode === 'disabled'); elZoneAmbientLightModeEnabled.checked = (properties.ambientLightMode === 'enabled'); +======= + elZoneSkyboxModeInherit.checked = (properties.skyboxMode === 'inherit'); + elZoneSkyboxModeDisabled.checked = (properties.skyboxMode === 'disabled'); + elZoneSkyboxModeEnabled.checked = (properties.skyboxMode === 'enabled'); + + // Ambient light + elZoneAmbientLightModeInherit.checked = (properties.ambientLightMode === 'inherit'); + elZoneAmbientLightModeDisabled.checked = (properties.ambientLightMode === 'disabled'); + elZoneAmbientLightModeEnabled.checked = (properties.ambientLightMode === 'enabled'); +>>>>>>> creating new image entity opens image property options elZoneAmbientLightIntensity.value = properties.ambientLight.ambientIntensity.toFixed(2); elZoneAmbientLightURL.value = properties.ambientLight.ambientURL; // Haze +<<<<<<< HEAD elZoneHazeModeInherit.checked = (properties.hazeMode === 'inherit'); elZoneHazeModeDisabled.checked = (properties.hazeMode === 'disabled'); elZoneHazeModeEnabled.checked = (properties.hazeMode === 'enabled'); +======= + elZoneHazeModeInherit.checked = (properties.hazeMode === 'inherit'); + elZoneHazeModeDisabled.checked = (properties.hazeMode === 'disabled'); + elZoneHazeModeEnabled.checked = (properties.hazeMode === 'enabled'); +>>>>>>> creating new image entity opens image property options elZoneHazeRange.value = properties.haze.hazeRange.toFixed(0); elZoneHazeColor.style.backgroundColor = "rgb(" + @@ -1319,15 +1370,24 @@ function loaded() { colorScheme: 'dark', layout: 'hex', color: '000000', +<<<<<<< HEAD submit: false, // We don't want to have a submission button +======= +>>>>>>> creating new image entity opens image property options onShow: function(colpick) { $('#property-color-control2').attr('active', 'true'); }, onHide: function(colpick) { $('#property-color-control2').attr('active', 'false'); }, +<<<<<<< HEAD onChange: function(hsb, hex, rgb, el) { $(el).css('background-color', '#' + hex); +======= + onSubmit: function(hsb, hex, rgb, el) { + $(el).css('background-color', '#' + hex); + $(el).colpickHide(); +>>>>>>> creating new image entity opens image property options emitColorPropertyUpdate('color', rgb.r, rgb.g, rgb.b); } })); @@ -1343,15 +1403,24 @@ function loaded() { colorScheme: 'dark', layout: 'hex', color: '000000', +<<<<<<< HEAD submit: false, // We don't want to have a submission button +======= +>>>>>>> creating new image entity opens image property options onShow: function(colpick) { $('#property-light-color').attr('active', 'true'); }, onHide: function(colpick) { $('#property-light-color').attr('active', 'false'); }, +<<<<<<< HEAD onChange: function(hsb, hex, rgb, el) { $(el).css('background-color', '#' + hex); +======= + onSubmit: function(hsb, hex, rgb, el) { + $(el).css('background-color', '#' + hex); + $(el).colpickHide(); +>>>>>>> creating new image entity opens image property options emitColorPropertyUpdate('color', rgb.r, rgb.g, rgb.b); } })); @@ -1400,15 +1469,24 @@ function loaded() { colorScheme: 'dark', layout: 'hex', color: '000000', +<<<<<<< HEAD submit: false, // We don't want to have a submission button +======= +>>>>>>> creating new image entity opens image property options onShow: function(colpick) { $('#property-text-text-color').attr('active', 'true'); }, onHide: function(colpick) { $('#property-text-text-color').attr('active', 'false'); }, +<<<<<<< HEAD onChange: function(hsb, hex, rgb, el) { $(el).css('background-color', '#' + hex); +======= + onSubmit: function(hsb, hex, rgb, el) { + $(el).css('background-color', '#' + hex); + $(el).colpickHide(); +>>>>>>> creating new image entity opens image property options $(el).attr('active', 'false'); emitColorPropertyUpdate('textColor', rgb.r, rgb.g, rgb.b); } @@ -1424,15 +1502,24 @@ function loaded() { colorScheme: 'dark', layout: 'hex', color: '000000', +<<<<<<< HEAD submit: false, // We don't want to have a submission button +======= +>>>>>>> creating new image entity opens image property options onShow: function(colpick) { $('#property-text-background-color').attr('active', 'true'); }, onHide: function(colpick) { $('#property-text-background-color').attr('active', 'false'); }, +<<<<<<< HEAD onChange: function(hsb, hex, rgb, el) { $(el).css('background-color', '#' + hex); +======= + onSubmit: function(hsb, hex, rgb, el) { + $(el).css('background-color', '#' + hex); + $(el).colpickHide(); +>>>>>>> creating new image entity opens image property options emitColorPropertyUpdate('backgroundColor', rgb.r, rgb.g, rgb.b); } })); @@ -1449,15 +1536,24 @@ function loaded() { colorScheme: 'dark', layout: 'hex', color: '000000', +<<<<<<< HEAD submit: false, // We don't want to have a submission button +======= +>>>>>>> creating new image entity opens image property options onShow: function(colpick) { $('#property-zone-key-light-color').attr('active', 'true'); }, onHide: function(colpick) { $('#property-zone-key-light-color').attr('active', 'false'); }, +<<<<<<< HEAD onChange: function(hsb, hex, rgb, el) { $(el).css('background-color', '#' + hex); +======= + onSubmit: function(hsb, hex, rgb, el) { + $(el).css('background-color', '#' + hex); + $(el).colpickHide(); +>>>>>>> creating new image entity opens image property options emitColorPropertyUpdate('color', rgb.r, rgb.g, rgb.b, 'keyLight'); } })); @@ -1518,15 +1614,24 @@ function loaded() { colorScheme: 'dark', layout: 'hex', color: '000000', +<<<<<<< HEAD submit: false, // We don't want to have a submission button +======= +>>>>>>> creating new image entity opens image property options onShow: function(colpick) { $('#property-zone-haze-color').attr('active', 'true'); }, onHide: function(colpick) { $('#property-zone-haze-color').attr('active', 'false'); }, +<<<<<<< HEAD onChange: function(hsb, hex, rgb, el) { $(el).css('background-color', '#' + hex); +======= + onSubmit: function(hsb, hex, rgb, el) { + $(el).css('background-color', '#' + hex); + $(el).colpickHide(); +>>>>>>> creating new image entity opens image property options emitColorPropertyUpdate('hazeColor', rgb.r, rgb.g, rgb.b, 'haze'); } })); @@ -1543,15 +1648,24 @@ function loaded() { colorScheme: 'dark', layout: 'hex', color: '000000', +<<<<<<< HEAD submit: false, // We don't want to have a submission button +======= +>>>>>>> creating new image entity opens image property options onShow: function(colpick) { $('#property-zone-haze-glare-color').attr('active', 'true'); }, onHide: function(colpick) { $('#property-zone-haze-glare-color').attr('active', 'false'); }, +<<<<<<< HEAD onChange: function(hsb, hex, rgb, el) { $(el).css('background-color', '#' + hex); +======= + onSubmit: function(hsb, hex, rgb, el) { + $(el).css('background-color', '#' + hex); + $(el).colpickHide(); +>>>>>>> creating new image entity opens image property options emitColorPropertyUpdate('hazeGlareColor', rgb.r, rgb.g, rgb.b, 'haze'); } })); @@ -1585,15 +1699,24 @@ function loaded() { colorScheme: 'dark', layout: 'hex', color: '000000', +<<<<<<< HEAD submit: false, // We don't want to have a submission button +======= +>>>>>>> creating new image entity opens image property options onShow: function(colpick) { $('#property-zone-skybox-color').attr('active', 'true'); }, onHide: function(colpick) { $('#property-zone-skybox-color').attr('active', 'false'); }, +<<<<<<< HEAD onChange: function(hsb, hex, rgb, el) { $(el).css('background-color', '#' + hex); +======= + onSubmit: function(hsb, hex, rgb, el) { + $(el).css('background-color', '#' + hex); + $(el).colpickHide(); +>>>>>>> creating new image entity opens image property options emitColorPropertyUpdate('color', rgb.r, rgb.g, rgb.b, 'skybox'); } })); From fe3bc00baa0cb1bcaaa3090e5ca3e438ba47874a Mon Sep 17 00:00:00 2001 From: Elisa Lupin-Jimenez Date: Tue, 23 Jan 2018 16:45:54 -0800 Subject: [PATCH 20/89] loading and rendering atp and http image files --- scripts/system/html/js/entityProperties.js | 131 ++------------------- 1 file changed, 7 insertions(+), 124 deletions(-) diff --git a/scripts/system/html/js/entityProperties.js b/scripts/system/html/js/entityProperties.js index 2b90f8ddfa..e7526fa2e0 100644 --- a/scripts/system/html/js/entityProperties.js +++ b/scripts/system/html/js/entityProperties.js @@ -136,7 +136,8 @@ function createEmitGroupNumberPropertyUpdateFunction(group, propertyName) { function createImageURLUpdateFunction(propertyName) { return function () { - updateProperty(propertyName, this.value); + var newTextures = JSON.stringify({ "tex.picture": this.value }); + updateProperty(propertyName, newTextures); }; } @@ -511,10 +512,6 @@ function loaded() { var elPropertiesList = document.getElementById("properties-list"); var elID = document.getElementById("property-id"); var elType = document.getElementById("property-type"); -<<<<<<< HEAD -======= - debugPrint("the type is: " + JSON.stringify(elType)); ->>>>>>> creating new image entity opens image property options var elTypeIcon = document.getElementById("type-icon"); var elName = document.getElementById("property-name"); var elLocked = document.getElementById("property-locked"); @@ -665,28 +662,16 @@ function loaded() { var elZoneKeyLightDirectionY = document.getElementById("property-zone-key-light-direction-y"); // Skybox -<<<<<<< HEAD var elZoneSkyboxModeInherit = document.getElementById("property-zone-skybox-mode-inherit"); var elZoneSkyboxModeDisabled = document.getElementById("property-zone-skybox-mode-disabled"); var elZoneSkyboxModeEnabled = document.getElementById("property-zone-skybox-mode-enabled"); -======= - var elZoneSkyboxModeInherit = document.getElementById("property-zone-skybox-mode-inherit"); - var elZoneSkyboxModeDisabled = document.getElementById("property-zone-skybox-mode-disabled"); - var elZoneSkyboxModeEnabled = document.getElementById("property-zone-skybox-mode-enabled"); ->>>>>>> creating new image entity opens image property options // Ambient light var elCopySkyboxURLToAmbientURL = document.getElementById("copy-skybox-url-to-ambient-url"); -<<<<<<< HEAD var elZoneAmbientLightModeInherit = document.getElementById("property-zone-ambient-light-mode-inherit"); var elZoneAmbientLightModeDisabled = document.getElementById("property-zone-ambient-light-mode-disabled"); var elZoneAmbientLightModeEnabled = document.getElementById("property-zone-ambient-light-mode-enabled"); -======= - var elZoneAmbientLightModeInherit = document.getElementById("property-zone-ambient-light-mode-inherit"); - var elZoneAmbientLightModeDisabled = document.getElementById("property-zone-ambient-light-mode-disabled"); - var elZoneAmbientLightModeEnabled = document.getElementById("property-zone-ambient-light-mode-enabled"); ->>>>>>> creating new image entity opens image property options var elZoneAmbientLightIntensity = document.getElementById("property-zone-key-ambient-intensity"); var elZoneAmbientLightURL = document.getElementById("property-zone-key-ambient-url"); @@ -803,11 +788,6 @@ function loaded() { } else { properties = data.selections[0].properties; -<<<<<<< HEAD - -======= - debugPrint("props: " + JSON.stringify(properties)); ->>>>>>> creating new image entity opens image property options if (lastEntityID !== '"' + properties.id + '"' && lastEntityID !== null && editor !== null) { saveJSONUserData(true); } @@ -816,14 +796,11 @@ function loaded() { lastEntityID = '"' + properties.id + '"'; elID.value = properties.id; -<<<<<<< HEAD -======= - // image is not yet a separate entity type + // HTML workaround since image is not yet a separate entity type if (properties.type === "Model" && properties.modelURL === "https://hifi-content.s3.amazonaws.com/elisalj/image_entity/snapshot.fbx") { properties.type = "Image"; } ->>>>>>> creating new image entity opens image property options // Create class name for css ruleset filtering elPropertiesList.className = properties.type + 'Menu'; @@ -1022,8 +999,9 @@ function loaded() { elWebSourceURL.value = properties.sourceUrl; elWebDPI.value = properties.dpi; } else if (properties.type === "Image") { - elImageURL.value = properties.imageURL; - //elImageURL.value = properties.sourceURL; + var imageLink = JSON.parse(properties.textures)["tex.picture"]; + debugPrint("image url is: " + imageLink); + elImageURL.value = imageLink; } else if (properties.type === "Text") { elTextText.value = properties.text; elTextLineHeight.value = properties.lineHeight.toFixed(4); @@ -1052,15 +1030,9 @@ function loaded() { } else if (properties.type === "Zone") { // Key light -<<<<<<< HEAD elZoneKeyLightModeInherit.checked = (properties.keyLightMode === 'inherit'); elZoneKeyLightModeDisabled.checked = (properties.keyLightMode === 'disabled'); elZoneKeyLightModeEnabled.checked = (properties.keyLightMode === 'enabled'); -======= - elZoneKeyLightModeInherit.checked = (properties.keyLightMode === 'inherit'); - elZoneKeyLightModeDisabled.checked = (properties.keyLightMode === 'disabled'); - elZoneKeyLightModeEnabled.checked = (properties.keyLightMode === 'enabled'); ->>>>>>> creating new image entity opens image property options elZoneKeyLightColor.style.backgroundColor = "rgb(" + properties.keyLight.color.red + "," + properties.keyLight.color.green + "," + properties.keyLight.color.blue + ")"; @@ -1072,7 +1044,6 @@ function loaded() { elZoneKeyLightDirectionY.value = properties.keyLight.direction.y.toFixed(2); // Skybox -<<<<<<< HEAD elZoneSkyboxModeInherit.checked = (properties.skyboxMode === 'inherit'); elZoneSkyboxModeDisabled.checked = (properties.skyboxMode === 'disabled'); elZoneSkyboxModeEnabled.checked = (properties.skyboxMode === 'enabled'); @@ -1081,30 +1052,14 @@ function loaded() { elZoneAmbientLightModeInherit.checked = (properties.ambientLightMode === 'inherit'); elZoneAmbientLightModeDisabled.checked = (properties.ambientLightMode === 'disabled'); elZoneAmbientLightModeEnabled.checked = (properties.ambientLightMode === 'enabled'); -======= - elZoneSkyboxModeInherit.checked = (properties.skyboxMode === 'inherit'); - elZoneSkyboxModeDisabled.checked = (properties.skyboxMode === 'disabled'); - elZoneSkyboxModeEnabled.checked = (properties.skyboxMode === 'enabled'); - - // Ambient light - elZoneAmbientLightModeInherit.checked = (properties.ambientLightMode === 'inherit'); - elZoneAmbientLightModeDisabled.checked = (properties.ambientLightMode === 'disabled'); - elZoneAmbientLightModeEnabled.checked = (properties.ambientLightMode === 'enabled'); ->>>>>>> creating new image entity opens image property options elZoneAmbientLightIntensity.value = properties.ambientLight.ambientIntensity.toFixed(2); elZoneAmbientLightURL.value = properties.ambientLight.ambientURL; // Haze -<<<<<<< HEAD elZoneHazeModeInherit.checked = (properties.hazeMode === 'inherit'); elZoneHazeModeDisabled.checked = (properties.hazeMode === 'disabled'); elZoneHazeModeEnabled.checked = (properties.hazeMode === 'enabled'); -======= - elZoneHazeModeInherit.checked = (properties.hazeMode === 'inherit'); - elZoneHazeModeDisabled.checked = (properties.hazeMode === 'disabled'); - elZoneHazeModeEnabled.checked = (properties.hazeMode === 'enabled'); ->>>>>>> creating new image entity opens image property options elZoneHazeRange.value = properties.haze.hazeRange.toFixed(0); elZoneHazeColor.style.backgroundColor = "rgb(" + @@ -1370,24 +1325,15 @@ function loaded() { colorScheme: 'dark', layout: 'hex', color: '000000', -<<<<<<< HEAD submit: false, // We don't want to have a submission button -======= ->>>>>>> creating new image entity opens image property options onShow: function(colpick) { $('#property-color-control2').attr('active', 'true'); }, onHide: function(colpick) { $('#property-color-control2').attr('active', 'false'); }, -<<<<<<< HEAD onChange: function(hsb, hex, rgb, el) { $(el).css('background-color', '#' + hex); -======= - onSubmit: function(hsb, hex, rgb, el) { - $(el).css('background-color', '#' + hex); - $(el).colpickHide(); ->>>>>>> creating new image entity opens image property options emitColorPropertyUpdate('color', rgb.r, rgb.g, rgb.b); } })); @@ -1403,24 +1349,15 @@ function loaded() { colorScheme: 'dark', layout: 'hex', color: '000000', -<<<<<<< HEAD submit: false, // We don't want to have a submission button -======= ->>>>>>> creating new image entity opens image property options onShow: function(colpick) { $('#property-light-color').attr('active', 'true'); }, onHide: function(colpick) { $('#property-light-color').attr('active', 'false'); }, -<<<<<<< HEAD onChange: function(hsb, hex, rgb, el) { $(el).css('background-color', '#' + hex); -======= - onSubmit: function(hsb, hex, rgb, el) { - $(el).css('background-color', '#' + hex); - $(el).colpickHide(); ->>>>>>> creating new image entity opens image property options emitColorPropertyUpdate('color', rgb.r, rgb.g, rgb.b); } })); @@ -1432,7 +1369,7 @@ function loaded() { elShape.addEventListener('change', createEmitTextPropertyUpdateFunction('shape')); - elImageURL.addEventListener('change', createImageURLUpdateFunction('imageURL')); + elImageURL.addEventListener('change', createImageURLUpdateFunction('textures')); elWebSourceURL.addEventListener('change', createEmitTextPropertyUpdateFunction('sourceUrl')); elWebDPI.addEventListener('change', createEmitNumberPropertyUpdateFunction('dpi', 0)); @@ -1469,24 +1406,15 @@ function loaded() { colorScheme: 'dark', layout: 'hex', color: '000000', -<<<<<<< HEAD submit: false, // We don't want to have a submission button -======= ->>>>>>> creating new image entity opens image property options onShow: function(colpick) { $('#property-text-text-color').attr('active', 'true'); }, onHide: function(colpick) { $('#property-text-text-color').attr('active', 'false'); }, -<<<<<<< HEAD onChange: function(hsb, hex, rgb, el) { $(el).css('background-color', '#' + hex); -======= - onSubmit: function(hsb, hex, rgb, el) { - $(el).css('background-color', '#' + hex); - $(el).colpickHide(); ->>>>>>> creating new image entity opens image property options $(el).attr('active', 'false'); emitColorPropertyUpdate('textColor', rgb.r, rgb.g, rgb.b); } @@ -1502,24 +1430,15 @@ function loaded() { colorScheme: 'dark', layout: 'hex', color: '000000', -<<<<<<< HEAD submit: false, // We don't want to have a submission button -======= ->>>>>>> creating new image entity opens image property options onShow: function(colpick) { $('#property-text-background-color').attr('active', 'true'); }, onHide: function(colpick) { $('#property-text-background-color').attr('active', 'false'); }, -<<<<<<< HEAD onChange: function(hsb, hex, rgb, el) { $(el).css('background-color', '#' + hex); -======= - onSubmit: function(hsb, hex, rgb, el) { - $(el).css('background-color', '#' + hex); - $(el).colpickHide(); ->>>>>>> creating new image entity opens image property options emitColorPropertyUpdate('backgroundColor', rgb.r, rgb.g, rgb.b); } })); @@ -1536,24 +1455,15 @@ function loaded() { colorScheme: 'dark', layout: 'hex', color: '000000', -<<<<<<< HEAD submit: false, // We don't want to have a submission button -======= ->>>>>>> creating new image entity opens image property options onShow: function(colpick) { $('#property-zone-key-light-color').attr('active', 'true'); }, onHide: function(colpick) { $('#property-zone-key-light-color').attr('active', 'false'); }, -<<<<<<< HEAD onChange: function(hsb, hex, rgb, el) { $(el).css('background-color', '#' + hex); -======= - onSubmit: function(hsb, hex, rgb, el) { - $(el).css('background-color', '#' + hex); - $(el).colpickHide(); ->>>>>>> creating new image entity opens image property options emitColorPropertyUpdate('color', rgb.r, rgb.g, rgb.b, 'keyLight'); } })); @@ -1614,24 +1524,15 @@ function loaded() { colorScheme: 'dark', layout: 'hex', color: '000000', -<<<<<<< HEAD submit: false, // We don't want to have a submission button -======= ->>>>>>> creating new image entity opens image property options onShow: function(colpick) { $('#property-zone-haze-color').attr('active', 'true'); }, onHide: function(colpick) { $('#property-zone-haze-color').attr('active', 'false'); }, -<<<<<<< HEAD onChange: function(hsb, hex, rgb, el) { $(el).css('background-color', '#' + hex); -======= - onSubmit: function(hsb, hex, rgb, el) { - $(el).css('background-color', '#' + hex); - $(el).colpickHide(); ->>>>>>> creating new image entity opens image property options emitColorPropertyUpdate('hazeColor', rgb.r, rgb.g, rgb.b, 'haze'); } })); @@ -1648,24 +1549,15 @@ function loaded() { colorScheme: 'dark', layout: 'hex', color: '000000', -<<<<<<< HEAD submit: false, // We don't want to have a submission button -======= ->>>>>>> creating new image entity opens image property options onShow: function(colpick) { $('#property-zone-haze-glare-color').attr('active', 'true'); }, onHide: function(colpick) { $('#property-zone-haze-glare-color').attr('active', 'false'); }, -<<<<<<< HEAD onChange: function(hsb, hex, rgb, el) { $(el).css('background-color', '#' + hex); -======= - onSubmit: function(hsb, hex, rgb, el) { - $(el).css('background-color', '#' + hex); - $(el).colpickHide(); ->>>>>>> creating new image entity opens image property options emitColorPropertyUpdate('hazeGlareColor', rgb.r, rgb.g, rgb.b, 'haze'); } })); @@ -1699,24 +1591,15 @@ function loaded() { colorScheme: 'dark', layout: 'hex', color: '000000', -<<<<<<< HEAD submit: false, // We don't want to have a submission button -======= ->>>>>>> creating new image entity opens image property options onShow: function(colpick) { $('#property-zone-skybox-color').attr('active', 'true'); }, onHide: function(colpick) { $('#property-zone-skybox-color').attr('active', 'false'); }, -<<<<<<< HEAD onChange: function(hsb, hex, rgb, el) { $(el).css('background-color', '#' + hex); -======= - onSubmit: function(hsb, hex, rgb, el) { - $(el).css('background-color', '#' + hex); - $(el).colpickHide(); ->>>>>>> creating new image entity opens image property options emitColorPropertyUpdate('color', rgb.r, rgb.g, rgb.b, 'skybox'); } })); From d390e20139efdd55dea99abe44959bbf2e203a46 Mon Sep 17 00:00:00 2001 From: Elisa Lupin-Jimenez Date: Wed, 24 Jan 2018 11:31:47 -0800 Subject: [PATCH 21/89] removed extraneous commenting and image class --- .../resources/qml/hifi/tablet/EditTabView.qml | 1 - .../entities/src/EntityItemProperties.cpp | 24 --- libraries/entities/src/EntityItemProperties.h | 2 - libraries/entities/src/EntityPropertyFlags.h | 3 - libraries/entities/src/EntityTypes.cpp | 2 - libraries/entities/src/EntityTypes.h | 1 - libraries/entities/src/ImageEntityItem.cpp | 144 ------------------ libraries/entities/src/ImageEntityItem.h | 66 -------- scripts/system/edit.js | 9 +- scripts/system/html/js/entityProperties.js | 1 - 10 files changed, 1 insertion(+), 252 deletions(-) delete mode 100644 libraries/entities/src/ImageEntityItem.cpp delete mode 100644 libraries/entities/src/ImageEntityItem.h diff --git a/interface/resources/qml/hifi/tablet/EditTabView.qml b/interface/resources/qml/hifi/tablet/EditTabView.qml index 482469d355..ea5762d00a 100644 --- a/interface/resources/qml/hifi/tablet/EditTabView.qml +++ b/interface/resources/qml/hifi/tablet/EditTabView.qml @@ -101,7 +101,6 @@ TabView { } } - // for image NewEntityButton { icon: "icons/create-icons/25-web-1-01.svg" text: "IMAGE" diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index 7fcfc00ee8..e2a5ddf8b5 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -350,8 +350,6 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { CHECK_PROPERTY_CHANGE(PROP_AMBIENT_LIGHT_MODE, ambientLightMode); CHECK_PROPERTY_CHANGE(PROP_SKYBOX_MODE, skyboxMode); - //CHECK_PROPERTY_CHANGE(PROP_IMAGE_URL, imageURL); - CHECK_PROPERTY_CHANGE(PROP_SOURCE_URL, sourceUrl); CHECK_PROPERTY_CHANGE(PROP_VOXEL_VOLUME_SIZE, voxelVolumeSize); CHECK_PROPERTY_CHANGE(PROP_VOXEL_DATA, voxelData); @@ -592,13 +590,6 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_SKYBOX_MODE, skyboxMode, getSkyboxModeAsString()); } - - - /*// Image only - if (_type == EntityTypes::Image) { - COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_IMAGE_URL, imageURL); - }*/ - // Web only if (_type == EntityTypes::Web) { COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_SOURCE_URL, sourceUrl); @@ -789,8 +780,6 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool COPY_PROPERTY_FROM_QSCRIPTVALUE_ENUM(ambientLightMode, AmbientLightMode); COPY_PROPERTY_FROM_QSCRIPTVALUE_ENUM(skyboxMode, SkyboxMode); - //COPY_PROPERTY_FROM_QSCRIPTVALUE(imageURL, QString, setImageURL); - COPY_PROPERTY_FROM_QSCRIPTVALUE(sourceUrl, QString, setSourceUrl); COPY_PROPERTY_FROM_QSCRIPTVALUE(voxelVolumeSize, glmVec3, setVoxelVolumeSize); COPY_PROPERTY_FROM_QSCRIPTVALUE(voxelData, QByteArray, setVoxelData); @@ -951,8 +940,6 @@ void EntityItemProperties::merge(const EntityItemProperties& other) { COPY_PROPERTY_IF_CHANGED(ambientLightMode); COPY_PROPERTY_IF_CHANGED(skyboxMode); - //COPY_PROPERTY_IF_CHANGED(imageURL); - COPY_PROPERTY_IF_CHANGED(sourceUrl); COPY_PROPERTY_IF_CHANGED(voxelVolumeSize); COPY_PROPERTY_IF_CHANGED(voxelData); @@ -1146,7 +1133,6 @@ void EntityItemProperties::entityPropertyFlagsFromScriptValue(const QScriptValue ADD_PROPERTY_TO_MAP(PROP_VOXEL_SURFACE_STYLE, VoxelSurfaceStyle, voxelSurfaceStyle, uint16_t); ADD_PROPERTY_TO_MAP(PROP_NAME, Name, name, QString); ADD_PROPERTY_TO_MAP(PROP_SOURCE_URL, SourceUrl, sourceUrl, QString); - //ADD_PROPERTY_TO_MAP(PROP_IMAGE_URL, ImageURL, imageURL, QString); ADD_PROPERTY_TO_MAP(PROP_LINE_WIDTH, LineWidth, lineWidth, float); ADD_PROPERTY_TO_MAP(PROP_LINE_POINTS, LinePoints, linePoints, QVector); ADD_PROPERTY_TO_MAP(PROP_HREF, Href, href, QString); @@ -1383,10 +1369,6 @@ OctreeElement::AppendState EntityItemProperties::encodeEntityEditPacket(PacketTy APPEND_ENTITY_PROPERTY(PROP_DPI, properties.getDPI()); } - /*if (properties.getType() == EntityTypes::Image) { - APPEND_ENTITY_PROPERTY(PROP_IMAGE_URL, properties.getImageURL()); - }*/ - if (properties.getType() == EntityTypes::Text) { APPEND_ENTITY_PROPERTY(PROP_TEXT, properties.getText()); APPEND_ENTITY_PROPERTY(PROP_LINE_HEIGHT, properties.getLineHeight()); @@ -1748,10 +1730,6 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_DPI, uint16_t, setDPI); } - /*if (properties.getType() == EntityTypes::Image) { - READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_IMAGE_URL, QString, setImageURL); - }*/ - if (properties.getType() == EntityTypes::Text) { READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_TEXT, QString, setText); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_LINE_HEIGHT, float, setLineHeight); @@ -2069,8 +2047,6 @@ void EntityItemProperties::markAllChanged() { _skybox.markAllChanged(); _haze.markAllChanged(); - //_imageURLChanged = true; - _sourceUrlChanged = true; _voxelVolumeSizeChanged = true; _voxelDataChanged = true; diff --git a/libraries/entities/src/EntityItemProperties.h b/libraries/entities/src/EntityItemProperties.h index 5ca28a12c5..3e0770f386 100644 --- a/libraries/entities/src/EntityItemProperties.h +++ b/libraries/entities/src/EntityItemProperties.h @@ -67,7 +67,6 @@ class EntityItemProperties { friend class ParticleEffectEntityItem; // TODO: consider removing this friend relationship and use public methods friend class ZoneEntityItem; // TODO: consider removing this friend relationship and use public methods friend class WebEntityItem; // TODO: consider removing this friend relationship and use public methods - //friend class ImageEntityItem; friend class LineEntityItem; // TODO: consider removing this friend relationship and use public methods friend class PolyVoxEntityItem; // TODO: consider removing this friend relationship and use public methods friend class PolyLineEntityItem; // TODO: consider removing this friend relationship and use public methods @@ -195,7 +194,6 @@ public: DEFINE_PROPERTY_GROUP(Haze, haze, HazePropertyGroup); DEFINE_PROPERTY_GROUP(Animation, animation, AnimationPropertyGroup); DEFINE_PROPERTY_REF(PROP_SOURCE_URL, SourceUrl, sourceUrl, QString, ""); - //DEFINE_PROPERTY_REF(PROP_IMAGE_URL, ImageURL, imageURL, QString, ""); DEFINE_PROPERTY(PROP_LINE_WIDTH, LineWidth, lineWidth, float, LineEntityItem::DEFAULT_LINE_WIDTH); DEFINE_PROPERTY_REF(LINE_POINTS, LinePoints, linePoints, QVector, QVector()); DEFINE_PROPERTY_REF(PROP_HREF, Href, href, QString, ""); diff --git a/libraries/entities/src/EntityPropertyFlags.h b/libraries/entities/src/EntityPropertyFlags.h index ec8f4e08c8..90438ab01c 100644 --- a/libraries/entities/src/EntityPropertyFlags.h +++ b/libraries/entities/src/EntityPropertyFlags.h @@ -42,9 +42,6 @@ enum EntityPropertyList { PROP_ANIMATION_ALLOW_TRANSLATION, PROP_RELAY_PARENT_JOINTS, - /*// for image - PROP_IMAGE_URL,*/ - // these properties are supported by the EntityItem base class PROP_REGISTRATION_POINT, PROP_ANGULAR_VELOCITY, diff --git a/libraries/entities/src/EntityTypes.cpp b/libraries/entities/src/EntityTypes.cpp index 6bf5a176a7..cb17c28fd7 100644 --- a/libraries/entities/src/EntityTypes.cpp +++ b/libraries/entities/src/EntityTypes.cpp @@ -28,7 +28,6 @@ #include "LineEntityItem.h" #include "PolyVoxEntityItem.h" #include "PolyLineEntityItem.h" -//#include "ImageEntityItem.h" #include "ShapeEntityItem.h" QMap EntityTypes::_typeToNameMap; @@ -48,7 +47,6 @@ REGISTER_ENTITY_TYPE(Zone) REGISTER_ENTITY_TYPE(Line) REGISTER_ENTITY_TYPE(PolyVox) REGISTER_ENTITY_TYPE(PolyLine) -//REGISTER_ENTITY_TYPE(Image) REGISTER_ENTITY_TYPE(Shape) REGISTER_ENTITY_TYPE_WITH_FACTORY(Box, ShapeEntityItem::boxFactory) REGISTER_ENTITY_TYPE_WITH_FACTORY(Sphere, ShapeEntityItem::sphereFactory) diff --git a/libraries/entities/src/EntityTypes.h b/libraries/entities/src/EntityTypes.h index 6fe4274820..316bf2b813 100644 --- a/libraries/entities/src/EntityTypes.h +++ b/libraries/entities/src/EntityTypes.h @@ -48,7 +48,6 @@ public: Line, PolyVox, PolyLine, - //Image, Shape, LAST = Shape } EntityType; diff --git a/libraries/entities/src/ImageEntityItem.cpp b/libraries/entities/src/ImageEntityItem.cpp deleted file mode 100644 index 9969a39ed0..0000000000 --- a/libraries/entities/src/ImageEntityItem.cpp +++ /dev/null @@ -1,144 +0,0 @@ -// -// ImageEntityItem.cpp -// libraries/entities/src -// -// Created by Elisa Lupin-Jimenez on 1/3/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 -// - -/* NOT IN USE - -#include - -#include -#include -#include -#include - -#include "EntitiesLogging.h" -#include "EntityItemProperties.h" -#include "EntityTree.h" -#include "EntityTreeElement.h" -#include "ResourceCache.h" -#include "ShapeEntityItem.h" -#include "ImageEntityItem.h" - -const QString ImageEntityItem::DEFAULT_IMAGE_URL(""); - -EntityItemPointer ImageEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) { - EntityItemPointer entity(new ImageEntityItem(entityID), [](EntityItem* ptr) { ptr->deleteLater(); }); - entity->setProperties(properties); - return entity; -} - -ImageEntityItem::ImageEntityItem(const EntityItemID& entityItemID) : EntityItem(entityItemID) { - //_type = EntityTypes::Image; -} - -EntityItemProperties ImageEntityItem::getProperties(EntityPropertyFlags desiredProperties) const { - EntityItemProperties properties = EntityItem::getProperties(desiredProperties); // get the properties from our base class - COPY_ENTITY_PROPERTY_TO_PROPERTIES(imageURL, getImageURL); - // Using "Quad" shape as defined in ShapeEntityItem.cpp - properties.setShape("Quad"); - return properties; -} - -bool ImageEntityItem::setProperties(const EntityItemProperties& properties) { - bool somethingChanged = EntityItem::setProperties(properties); // set the properties in our base class - - SET_ENTITY_PROPERTY_FROM_PROPERTIES(imageURL, setImageURL); - - if (somethingChanged) { - bool wantDebug = false; - if (wantDebug) { - uint64_t now = usecTimestampNow(); - int elapsed = now - getLastEdited(); - qCDebug(entities) << "ImageEntityItem::setProperties() AFTER update... edited AGO=" << elapsed << - "now=" << now << " getLastEdited()=" << getLastEdited(); - } - setLastEdited(properties.getLastEdited()); - } - return somethingChanged; -} - -// TODO: eventually only include properties changed since the params.nodeData->getLastTimeBagEmpty() time -EntityPropertyFlags ImageEntityItem::getEntityProperties(EncodeBitstreamParams& params) const { - EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params); - requestedProperties += PROP_IMAGE_URL; - return requestedProperties; -} - -void ImageEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params, - EntityTreeElementExtraEncodeDataPointer modelTreeElementExtraEncodeData, - EntityPropertyFlags& requestedProperties, - EntityPropertyFlags& propertyFlags, - EntityPropertyFlags& propertiesDidntFit, - int& propertyCount, - OctreeElement::AppendState& appendState) const { - - bool successPropertyFits = true; - // Using "Quad" shape as defined in ShapeEntityItem.cpp - APPEND_ENTITY_PROPERTY(PROP_IMAGE_URL, _imageURL); -} - -int ImageEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead, - ReadBitstreamToTreeParams& args, - EntityPropertyFlags& propertyFlags, bool overwriteLocalData, - bool& somethingChanged) { - - int bytesRead = 0; - const unsigned char* dataAt = data; - - READ_ENTITY_PROPERTY(PROP_IMAGE_URL, QString, setImageURL); - - return bytesRead; -} - -void ImageEntityItem::setUnscaledDimensions(const glm::vec3& value) { - const float MAX_FLAT_DIMENSION = 0.0001f; - if (value.y > MAX_FLAT_DIMENSION) { - // enforce flatness in Y - glm::vec3 newDimensions = value; - newDimensions.y = MAX_FLAT_DIMENSION; - EntityItem::setUnscaledDimensions(newDimensions); - } else { - EntityItem::setUnscaledDimensions(value); - } -} - -void ImageEntityItem::setImageURL(const QString& value) { - withWriteLock([&] { - if (_imageURL != value) { - auto newURL = QUrl::fromUserInput(value); - - if (newURL.isValid()) { - _imageURL = newURL.toDisplayString(); - } else { - qCDebug(entities) << "Clearing image entity source URL since" << value << "cannot be parsed to a valid URL."; - } - } - }); -} - -QString ImageEntityItem::getImageURL() const { - return resultWithReadLock([&] { - return _imageURL; - }); -} - -void ImageEntityItem::computeShapeInfo(ShapeInfo& info) { - // This will be called whenever DIRTY_SHAPE flag (set by dimension change, etc) - // is set. - - EntityItem::computeShapeInfo(info); -} - -// This value specifies how the shape should be treated by physics calculations. -ShapeType ImageEntityItem::getShapeType() const { - return _collisionShapeType; -} - -*/ diff --git a/libraries/entities/src/ImageEntityItem.h b/libraries/entities/src/ImageEntityItem.h deleted file mode 100644 index 276d21e6f0..0000000000 --- a/libraries/entities/src/ImageEntityItem.h +++ /dev/null @@ -1,66 +0,0 @@ -// -// ImageEntityItem.h -// libraries/entities/src -// -// Created by Elisa Lupin-Jimenez on 1/3/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 -// - -/* NOT IN USE - -#ifndef hifi_ImageEntityItem_h -#define hifi_ImageEntityItem_h - -#include "EntityItem.h" -#include "ShapeEntityItem.h" - -class ImageEntityItem : public EntityItem { -public: - static EntityItemPointer factory(const EntityItemID& entityID, const EntityItemProperties& properties); - - ImageEntityItem(const EntityItemID& entityItemID); - - ALLOW_INSTANTIATION // This class can be instantiated - - // methods for getting/setting all properties of an entity - virtual EntityItemProperties getProperties(EntityPropertyFlags desiredProperties = EntityPropertyFlags()) const override; - virtual bool setProperties(const EntityItemProperties& properties) override; - - EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const override; - - virtual void appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params, - EntityTreeElementExtraEncodeDataPointer entityTreeElementExtraEncodeData, - EntityPropertyFlags& requestedProperties, - EntityPropertyFlags& propertyFlags, - EntityPropertyFlags& propertiesDidntFit, - int& propertyCount, - OctreeElement::AppendState& appendState) const override; - - int readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead, - ReadBitstreamToTreeParams& args, - EntityPropertyFlags& propertyFlags, bool overwriteLocalData, - bool& somethingChanged) override; - - void setUnscaledDimensions(const glm::vec3& value) override; - - static const QString DEFAULT_IMAGE_URL; - virtual void setImageURL(const QString& value); - QString getImageURL() const; - - //virtual void computeShapeInfo(ShapeInfo& info) override; - virtual ShapeType getShapeType() const override; - -protected: - QString _imageURL; - - entity::Shape _shape{ entity::Shape::Quad }; - ShapeType _collisionShapeType{ ShapeType::SHAPE_TYPE_BOX }; - -}; - -#endif // hifi_ImageEntityItem_h - -*/ diff --git a/scripts/system/edit.js b/scripts/system/edit.js index a5c236a6c9..20425c7ea6 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -251,9 +251,7 @@ var toolBar = (function () { // Align entity with Avatar orientation. properties.rotation = MyAvatar.orientation; - // added image here var PRE_ADJUST_ENTITY_TYPES = ["Box", "Sphere", "Shape", "Text", "Web"]; - //var PRE_ADJUST_ENTITY_TYPES = ["Box", "Sphere", "Shape", "Text", "Image", "Web"]; if (PRE_ADJUST_ENTITY_TYPES.indexOf(properties.type) !== -1) { @@ -541,21 +539,16 @@ var toolBar = (function () { }); }); - // for image button addButton("newImageButton", "web-01.svg", function () { createNewEntity({ type: "Model", - // make constant for this later dimensions: { x: 4.16, y: 0.02, z: 2.58 }, modelURL: "https://hifi-content.s3.amazonaws.com/elisalj/image_entity/snapshot.fbx", - // will this work? - /*get textures() { - return JSON.stringify({ "tex.picture": this.imageURL }); - }*/ + // change to another default image textures: JSON.stringify({ "tex.picture": "https://hifi-content.s3.amazonaws.com/elisalj/image_entity/dog.jpg" }) }); }); diff --git a/scripts/system/html/js/entityProperties.js b/scripts/system/html/js/entityProperties.js index e7526fa2e0..59b54b1020 100644 --- a/scripts/system/html/js/entityProperties.js +++ b/scripts/system/html/js/entityProperties.js @@ -1000,7 +1000,6 @@ function loaded() { elWebDPI.value = properties.dpi; } else if (properties.type === "Image") { var imageLink = JSON.parse(properties.textures)["tex.picture"]; - debugPrint("image url is: " + imageLink); elImageURL.value = imageLink; } else if (properties.type === "Text") { elTextText.value = properties.text; From bd7204e6efd507be41c4a45d3ec4e21d61d44db2 Mon Sep 17 00:00:00 2001 From: Elisa Lupin-Jimenez Date: Wed, 24 Jan 2018 12:01:59 -0800 Subject: [PATCH 22/89] image entity now has grabbable and dynamic options --- scripts/system/edit.js | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/system/edit.js b/scripts/system/edit.js index 20425c7ea6..c298da38bc 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -547,6 +547,7 @@ var toolBar = (function () { y: 0.02, z: 2.58 }, + shapeType: "box", modelURL: "https://hifi-content.s3.amazonaws.com/elisalj/image_entity/snapshot.fbx", // change to another default image textures: JSON.stringify({ "tex.picture": "https://hifi-content.s3.amazonaws.com/elisalj/image_entity/dog.jpg" }) From 2ff47fa454b494ce64d2afa7e4396f0233e32ea7 Mon Sep 17 00:00:00 2001 From: Elisa Lupin-Jimenez Date: Thu, 25 Jan 2018 14:39:45 -0800 Subject: [PATCH 23/89] images auto-add to asset server, started asset browser add link --- interface/resources/qml/hifi/AssetServer.qml | 3 +- interface/src/Application.cpp | 42 ++++++++++++++++---- interface/src/Application.h | 1 + 3 files changed, 38 insertions(+), 8 deletions(-) diff --git a/interface/resources/qml/hifi/AssetServer.qml b/interface/resources/qml/hifi/AssetServer.qml index 37c3c2adab..6a4e703413 100644 --- a/interface/resources/qml/hifi/AssetServer.qml +++ b/interface/resources/qml/hifi/AssetServer.qml @@ -142,8 +142,9 @@ Windows.ScrollingWindow { }); } + // Elisa note - need to link this with specific add entity call function canAddToWorld(path) { - var supportedExtensions = [/\.fbx\b/i, /\.obj\b/i]; + var supportedExtensions = [/\.fbx\b/i, /\.obj\b/i, /\.jpg\b/i, /\.png\b/i]; if (selectedItems > 1) { return false; diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 99bd4d5758..b53ba61e61 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -335,15 +335,17 @@ static const int MAX_CONCURRENT_RESOURCE_DOWNLOADS = 16; // we will never drop below the 'min' value static const int MIN_PROCESSING_THREAD_POOL_SIZE = 1; -static const QString SNAPSHOT_EXTENSION = ".jpg"; -static const QString SVO_EXTENSION = ".svo"; +static const QString SNAPSHOT_EXTENSION = ".jpg"; +static const QString JPG_EXTENSION = ".jpg"; +static const QString PNG_EXTENSION = ".png"; +static const QString SVO_EXTENSION = ".svo"; static const QString SVO_JSON_EXTENSION = ".svo.json"; static const QString JSON_GZ_EXTENSION = ".json.gz"; static const QString JSON_EXTENSION = ".json"; -static const QString JS_EXTENSION = ".js"; -static const QString FST_EXTENSION = ".fst"; -static const QString FBX_EXTENSION = ".fbx"; -static const QString OBJ_EXTENSION = ".obj"; +static const QString JS_EXTENSION = ".js"; +static const QString FST_EXTENSION = ".fst"; +static const QString FBX_EXTENSION = ".fbx"; +static const QString OBJ_EXTENSION = ".obj"; static const QString AVA_JSON_EXTENSION = ".ava.json"; static const QString WEB_VIEW_TAG = "noDownload=true"; static const QString ZIP_EXTENSION = ".zip"; @@ -382,7 +384,9 @@ const QHash Application::_acceptedExtensi { JS_EXTENSION, &Application::askToLoadScript }, { FST_EXTENSION, &Application::askToSetAvatarUrl }, { JSON_GZ_EXTENSION, &Application::askToReplaceDomainContent }, - { ZIP_EXTENSION, &Application::importFromZIP } + { ZIP_EXTENSION, &Application::importFromZIP }, + { JPG_EXTENSION, &Application::importImage }, + { PNG_EXTENSION, &Application::importImage } }; class DeadlockWatchdogThread : public QThread { @@ -2899,6 +2903,27 @@ bool Application::importFromZIP(const QString& filePath) { return true; } +bool Application::importImage(const QString& urlString) { + qCDebug(interfaceapp) << "dragged image"; + QString filepath(urlString); + filepath.remove("file:///"); + //<> + addAssetToWorld(filepath, "", false, false); + //emit uploadRequest(urlString); + /*auto upload = DependencyManager::get()->createUpload(urlString); + QObject::connect(upload, &AssetUpload::finished, this, [=](AssetUpload* upload, const QString& hash) mutable { + if (upload->getError() != AssetUpload::NoError) { + QString errorInfo = "Could not upload model to the Asset Server."; + qWarning(interfaceapp) << "Error downloading model: " + errorInfo; + //addAssetToWorldError(filenameFromPath(urlString), errorInfo); + } else { + //addAssetToWorldSetMapping(urlString, QString("/" + filenameFromPath(urlString)), hash); + } + upload->deleteLater(); + });*/ + return true; +} + // thread-safe void Application::onPresent(quint32 frameCount) { bool expected = false; @@ -6403,6 +6428,7 @@ void Application::addAssetToWorld(QString path, QString zipFile, bool isZip, boo // Automatically upload and add asset to world as an alternative manual process initiated by showAssetServerWidget(). QString mapping; QString filename = filenameFromPath(path); + qCDebug(interfaceapp) << "Filename is: " << filename; if (isZip || isBlocks) { QString assetName = zipFile.section("/", -1).remove(QRegExp("[.]zip(.*)$")); QString assetFolder = path.section("model_repo/", -1); @@ -6425,6 +6451,8 @@ void Application::addAssetToWorld(QString path, QString zipFile, bool isZip, boo } void Application::addAssetToWorldWithNewMapping(QString filePath, QString mapping, int copy) { + qCDebug(interfaceapp) << "mapping request is: " << mapping; + qCDebug(interfaceapp) << "file is located: " << filePath; auto request = DependencyManager::get()->createGetMappingRequest(mapping); QObject::connect(request, &GetMappingRequest::finished, this, [=](GetMappingRequest* request) mutable { diff --git a/interface/src/Application.h b/interface/src/Application.h index ddb8ce11e5..0e9bf95081 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -469,6 +469,7 @@ private: bool importJSONFromURL(const QString& urlString); bool importSVOFromURL(const QString& urlString); bool importFromZIP(const QString& filePath); + bool importImage(const QString& urlString); bool nearbyEntitiesAreReadyForPhysics(); int processOctreeStats(ReceivedMessage& message, SharedNodePointer sendingNode); From 62437dcc2265886c5d0fb7349c4625d4e68d18d7 Mon Sep 17 00:00:00 2001 From: Elisa Lupin-Jimenez Date: Thu, 25 Jan 2018 15:26:26 -0800 Subject: [PATCH 24/89] drag and drop works --- interface/src/Application.cpp | 34 ++++++++++++++-------------------- 1 file changed, 14 insertions(+), 20 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index b53ba61e61..b203487547 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -2904,23 +2904,10 @@ bool Application::importFromZIP(const QString& filePath) { } bool Application::importImage(const QString& urlString) { - qCDebug(interfaceapp) << "dragged image"; + qCDebug(interfaceapp) << "An image file has been dropped in"; QString filepath(urlString); filepath.remove("file:///"); - //<> addAssetToWorld(filepath, "", false, false); - //emit uploadRequest(urlString); - /*auto upload = DependencyManager::get()->createUpload(urlString); - QObject::connect(upload, &AssetUpload::finished, this, [=](AssetUpload* upload, const QString& hash) mutable { - if (upload->getError() != AssetUpload::NoError) { - QString errorInfo = "Could not upload model to the Asset Server."; - qWarning(interfaceapp) << "Error downloading model: " + errorInfo; - //addAssetToWorldError(filenameFromPath(urlString), errorInfo); - } else { - //addAssetToWorldSetMapping(urlString, QString("/" + filenameFromPath(urlString)), hash); - } - upload->deleteLater(); - });*/ return true; } @@ -6428,7 +6415,6 @@ void Application::addAssetToWorld(QString path, QString zipFile, bool isZip, boo // Automatically upload and add asset to world as an alternative manual process initiated by showAssetServerWidget(). QString mapping; QString filename = filenameFromPath(path); - qCDebug(interfaceapp) << "Filename is: " << filename; if (isZip || isBlocks) { QString assetName = zipFile.section("/", -1).remove(QRegExp("[.]zip(.*)$")); QString assetFolder = path.section("model_repo/", -1); @@ -6451,8 +6437,6 @@ void Application::addAssetToWorld(QString path, QString zipFile, bool isZip, boo } void Application::addAssetToWorldWithNewMapping(QString filePath, QString mapping, int copy) { - qCDebug(interfaceapp) << "mapping request is: " << mapping; - qCDebug(interfaceapp) << "file is located: " << filePath; auto request = DependencyManager::get()->createGetMappingRequest(mapping); QObject::connect(request, &GetMappingRequest::finished, this, [=](GetMappingRequest* request) mutable { @@ -6519,7 +6503,8 @@ void Application::addAssetToWorldSetMapping(QString filePath, QString mapping, Q addAssetToWorldError(filenameFromPath(filePath), errorInfo); } else { // to prevent files that aren't models from being loaded into world automatically - if (filePath.endsWith(".obj") || filePath.endsWith(".fbx")) { + if (filePath.endsWith(OBJ_EXTENSION) || filePath.endsWith(FBX_EXTENSION) || + filePath.endsWith(JPG_EXTENSION) || filePath.endsWith(PNG_EXTENSION)) { addAssetToWorldAddEntity(filePath, mapping); } else { qCDebug(interfaceapp) << "Zipped contents are not supported entity files"; @@ -6536,8 +6521,17 @@ void Application::addAssetToWorldAddEntity(QString filePath, QString mapping) { EntityItemProperties properties; properties.setType(EntityTypes::Model); properties.setName(mapping.right(mapping.length() - 1)); - properties.setModelURL("atp:" + mapping); - properties.setShapeType(SHAPE_TYPE_SIMPLE_COMPOUND); + if (filePath.endsWith(PNG_EXTENSION) || filePath.endsWith(JPG_EXTENSION)) { + QJsonObject textures { + {"tex.picture", QString("atp:" + mapping) } + }; + properties.setModelURL("https://hifi-content.s3.amazonaws.com/elisalj/image_entity/snapshot.fbx"); + properties.setTextures(QJsonDocument(textures).toJson(QJsonDocument::Compact)); + properties.setShapeType(SHAPE_TYPE_BOX); + } else { + properties.setModelURL("atp:" + mapping); + properties.setShapeType(SHAPE_TYPE_SIMPLE_COMPOUND); + } properties.setCollisionless(true); // Temporarily set so that doesn't collide with avatar. properties.setVisible(false); // Temporarily set so that don't see at large unresized dimensions. glm::vec3 positionOffset = getMyAvatar()->getWorldOrientation() * (getMyAvatar()->getSensorToWorldScale() * glm::vec3(0.0f, 0.0f, -2.0f)); From 9f8e2017ceab378aec06a75c6f447317f68b1e32 Mon Sep 17 00:00:00 2001 From: Elisa Lupin-Jimenez Date: Fri, 26 Jan 2018 14:39:04 -0800 Subject: [PATCH 25/89] can add image directly from asset server --- interface/resources/qml/hifi/AssetServer.qml | 167 ++++++++++-------- .../qml/hifi/dialogs/TabletAssetServer.qml | 167 ++++++++++-------- .../entities/src/EntityScriptingInterface.cpp | 5 +- .../entities/src/EntityScriptingInterface.h | 2 +- 4 files changed, 183 insertions(+), 158 deletions(-) diff --git a/interface/resources/qml/hifi/AssetServer.qml b/interface/resources/qml/hifi/AssetServer.qml index 6a4e703413..04fceec1f3 100644 --- a/interface/resources/qml/hifi/AssetServer.qml +++ b/interface/resources/qml/hifi/AssetServer.qml @@ -182,92 +182,103 @@ Windows.ScrollingWindow { return; } - var SHAPE_TYPE_NONE = 0; - var SHAPE_TYPE_SIMPLE_HULL = 1; - var SHAPE_TYPE_SIMPLE_COMPOUND = 2; - var SHAPE_TYPE_STATIC_MESH = 3; - var SHAPE_TYPE_BOX = 4; - var SHAPE_TYPE_SPHERE = 5; + if (defaultURL.endsWith(".jpg") || defaultURL.endsWith(".png")) { + var name = assetProxyModel.data(treeView.selection.currentIndex); + var modelURL = "https://hifi-content.s3.amazonaws.com/elisalj/image_entity/snapshot.fbx"; + var textures = JSON.stringify({ "tex.picture": defaultURL}); + var shapeType = "box"; + var dynamic = false; + var position = Vec3.sum(MyAvatar.position, Vec3.multiply(2, Quat.getForward(MyAvatar.orientation))); + var gravity = Vec3.multiply(Vec3.fromPolar(Math.PI / 2, 0), 0); + Entities.addModelEntity(name, modelURL, textures, shapeType, dynamic, position, gravity); + } else { + var SHAPE_TYPE_NONE = 0; + var SHAPE_TYPE_SIMPLE_HULL = 1; + var SHAPE_TYPE_SIMPLE_COMPOUND = 2; + var SHAPE_TYPE_STATIC_MESH = 3; + var SHAPE_TYPE_BOX = 4; + var SHAPE_TYPE_SPHERE = 5; - var SHAPE_TYPES = []; - SHAPE_TYPES[SHAPE_TYPE_NONE] = "No Collision"; - SHAPE_TYPES[SHAPE_TYPE_SIMPLE_HULL] = "Basic - Whole model"; - SHAPE_TYPES[SHAPE_TYPE_SIMPLE_COMPOUND] = "Good - Sub-meshes"; - SHAPE_TYPES[SHAPE_TYPE_STATIC_MESH] = "Exact - All polygons"; - SHAPE_TYPES[SHAPE_TYPE_BOX] = "Box"; - SHAPE_TYPES[SHAPE_TYPE_SPHERE] = "Sphere"; + var SHAPE_TYPES = []; + SHAPE_TYPES[SHAPE_TYPE_NONE] = "No Collision"; + SHAPE_TYPES[SHAPE_TYPE_SIMPLE_HULL] = "Basic - Whole model"; + SHAPE_TYPES[SHAPE_TYPE_SIMPLE_COMPOUND] = "Good - Sub-meshes"; + SHAPE_TYPES[SHAPE_TYPE_STATIC_MESH] = "Exact - All polygons"; + SHAPE_TYPES[SHAPE_TYPE_BOX] = "Box"; + SHAPE_TYPES[SHAPE_TYPE_SPHERE] = "Sphere"; - var SHAPE_TYPE_DEFAULT = SHAPE_TYPE_STATIC_MESH; - var DYNAMIC_DEFAULT = false; - var prompt = desktop.customInputDialog({ - textInput: { - label: "Model URL", - text: defaultURL - }, - comboBox: { - label: "Automatic Collisions", - index: SHAPE_TYPE_DEFAULT, - items: SHAPE_TYPES - }, - checkBox: { - label: "Dynamic", - checked: DYNAMIC_DEFAULT, - disableForItems: [ - SHAPE_TYPE_STATIC_MESH - ], - checkStateOnDisable: false, - warningOnDisable: "Models with 'Exact' automatic collisions cannot be dynamic, and should not be used as floors" - } - }); - - prompt.selected.connect(function (jsonResult) { - if (jsonResult) { - var result = JSON.parse(jsonResult); - var url = result.textInput.trim(); - var shapeType; - switch (result.comboBox) { - case SHAPE_TYPE_SIMPLE_HULL: - shapeType = "simple-hull"; - break; - case SHAPE_TYPE_SIMPLE_COMPOUND: - shapeType = "simple-compound"; - break; - case SHAPE_TYPE_STATIC_MESH: - shapeType = "static-mesh"; - break; - case SHAPE_TYPE_BOX: - shapeType = "box"; - break; - case SHAPE_TYPE_SPHERE: - shapeType = "sphere"; - break; - default: - shapeType = "none"; + var SHAPE_TYPE_DEFAULT = SHAPE_TYPE_STATIC_MESH; + var DYNAMIC_DEFAULT = false; + var prompt = desktop.customInputDialog({ + textInput: { + label: "Model URL", + text: defaultURL + }, + comboBox: { + label: "Automatic Collisions", + index: SHAPE_TYPE_DEFAULT, + items: SHAPE_TYPES + }, + checkBox: { + label: "Dynamic", + checked: DYNAMIC_DEFAULT, + disableForItems: [ + SHAPE_TYPE_STATIC_MESH + ], + checkStateOnDisable: false, + warningOnDisable: "Models with 'Exact' automatic collisions cannot be dynamic, and should not be used as floors" } + }); - var dynamic = result.checkBox !== null ? result.checkBox : DYNAMIC_DEFAULT; - if (shapeType === "static-mesh" && dynamic) { - // The prompt should prevent this case - print("Error: model cannot be both static mesh and dynamic. This should never happen."); - } else if (url) { - var name = assetProxyModel.data(treeView.selection.currentIndex); - var addPosition = Vec3.sum(MyAvatar.position, Vec3.multiply(2, Quat.getForward(MyAvatar.orientation))); - var gravity; - if (dynamic) { - // Create a vector <0, -10, 0>. { x: 0, y: -10, z: 0 } won't work because Qt is dumb and this is a - // different scripting engine from QTScript. - gravity = Vec3.multiply(Vec3.fromPolar(Math.PI / 2, 0), 10); - } else { - gravity = Vec3.multiply(Vec3.fromPolar(Math.PI / 2, 0), 0); + prompt.selected.connect(function (jsonResult) { + if (jsonResult) { + var result = JSON.parse(jsonResult); + var url = result.textInput.trim(); + var shapeType; + switch (result.comboBox) { + case SHAPE_TYPE_SIMPLE_HULL: + shapeType = "simple-hull"; + break; + case SHAPE_TYPE_SIMPLE_COMPOUND: + shapeType = "simple-compound"; + break; + case SHAPE_TYPE_STATIC_MESH: + shapeType = "static-mesh"; + break; + case SHAPE_TYPE_BOX: + shapeType = "box"; + break; + case SHAPE_TYPE_SPHERE: + shapeType = "sphere"; + break; + default: + shapeType = "none"; } - print("Asset browser - adding asset " + url + " (" + name + ") to world."); + var dynamic = result.checkBox !== null ? result.checkBox : DYNAMIC_DEFAULT; + if (shapeType === "static-mesh" && dynamic) { + // The prompt should prevent this case + print("Error: model cannot be both static mesh and dynamic. This should never happen."); + } else if (url) { + var name = assetProxyModel.data(treeView.selection.currentIndex); + var addPosition = Vec3.sum(MyAvatar.position, Vec3.multiply(2, Quat.getForward(MyAvatar.orientation))); + var gravity; + if (dynamic) { + // Create a vector <0, -10, 0>. { x: 0, y: -10, z: 0 } won't work because Qt is dumb and this is a + // different scripting engine from QTScript. + gravity = Vec3.multiply(Vec3.fromPolar(Math.PI / 2, 0), 10); + } else { + gravity = Vec3.multiply(Vec3.fromPolar(Math.PI / 2, 0), 0); + } - // Entities.addEntity doesn't work from QML, so we use this. - Entities.addModelEntity(name, url, shapeType, dynamic, addPosition, gravity); + print("Asset browser - adding asset " + url + " (" + name + ") to world."); + + // Entities.addEntity doesn't work from QML, so we use this. + Entities.addModelEntity(name, url, "", shapeType, dynamic, addPosition, gravity); + } } - } - }); + }); + } } function copyURLToClipboard(index) { diff --git a/interface/resources/qml/hifi/dialogs/TabletAssetServer.qml b/interface/resources/qml/hifi/dialogs/TabletAssetServer.qml index a02496a252..ef4fb8d177 100644 --- a/interface/resources/qml/hifi/dialogs/TabletAssetServer.qml +++ b/interface/resources/qml/hifi/dialogs/TabletAssetServer.qml @@ -182,92 +182,103 @@ Rectangle { return; } - var SHAPE_TYPE_NONE = 0; - var SHAPE_TYPE_SIMPLE_HULL = 1; - var SHAPE_TYPE_SIMPLE_COMPOUND = 2; - var SHAPE_TYPE_STATIC_MESH = 3; - var SHAPE_TYPE_BOX = 4; - var SHAPE_TYPE_SPHERE = 5; + if (defaultURL.endsWith(".jpg") || defaultURL.endsWith(".png")) { + var name = assetProxyModel.data(treeView.selection.currentIndex); + var modelURL = "https://hifi-content.s3.amazonaws.com/elisalj/image_entity/snapshot.fbx"; + var textures = JSON.stringify({ "tex.picture": defaultURL}); + var shapeType = "box"; + var dynamic = false; + var position = Vec3.sum(MyAvatar.position, Vec3.multiply(2, Quat.getForward(MyAvatar.orientation))); + var gravity = Vec3.multiply(Vec3.fromPolar(Math.PI / 2, 0), 0); + Entities.addModelEntity(name, modelURL, textures, shapeType, dynamic, position, gravity); + } else { + var SHAPE_TYPE_NONE = 0; + var SHAPE_TYPE_SIMPLE_HULL = 1; + var SHAPE_TYPE_SIMPLE_COMPOUND = 2; + var SHAPE_TYPE_STATIC_MESH = 3; + var SHAPE_TYPE_BOX = 4; + var SHAPE_TYPE_SPHERE = 5; - var SHAPE_TYPES = []; - SHAPE_TYPES[SHAPE_TYPE_NONE] = "No Collision"; - SHAPE_TYPES[SHAPE_TYPE_SIMPLE_HULL] = "Basic - Whole model"; - SHAPE_TYPES[SHAPE_TYPE_SIMPLE_COMPOUND] = "Good - Sub-meshes"; - SHAPE_TYPES[SHAPE_TYPE_STATIC_MESH] = "Exact - All polygons"; - SHAPE_TYPES[SHAPE_TYPE_BOX] = "Box"; - SHAPE_TYPES[SHAPE_TYPE_SPHERE] = "Sphere"; + var SHAPE_TYPES = []; + SHAPE_TYPES[SHAPE_TYPE_NONE] = "No Collision"; + SHAPE_TYPES[SHAPE_TYPE_SIMPLE_HULL] = "Basic - Whole model"; + SHAPE_TYPES[SHAPE_TYPE_SIMPLE_COMPOUND] = "Good - Sub-meshes"; + SHAPE_TYPES[SHAPE_TYPE_STATIC_MESH] = "Exact - All polygons"; + SHAPE_TYPES[SHAPE_TYPE_BOX] = "Box"; + SHAPE_TYPES[SHAPE_TYPE_SPHERE] = "Sphere"; - var SHAPE_TYPE_DEFAULT = SHAPE_TYPE_STATIC_MESH; - var DYNAMIC_DEFAULT = false; - var prompt = tabletRoot.customInputDialog({ - textInput: { - label: "Model URL", - text: defaultURL - }, - comboBox: { - label: "Automatic Collisions", - index: SHAPE_TYPE_DEFAULT, - items: SHAPE_TYPES - }, - checkBox: { - label: "Dynamic", - checked: DYNAMIC_DEFAULT, - disableForItems: [ - SHAPE_TYPE_STATIC_MESH - ], - checkStateOnDisable: false, - warningOnDisable: "Models with 'Exact' automatic collisions cannot be dynamic, and should not be used as floors" - } - }); - - prompt.selected.connect(function (jsonResult) { - if (jsonResult) { - var result = JSON.parse(jsonResult); - var url = result.textInput.trim(); - var shapeType; - switch (result.comboBox) { - case SHAPE_TYPE_SIMPLE_HULL: - shapeType = "simple-hull"; - break; - case SHAPE_TYPE_SIMPLE_COMPOUND: - shapeType = "simple-compound"; - break; - case SHAPE_TYPE_STATIC_MESH: - shapeType = "static-mesh"; - break; - case SHAPE_TYPE_BOX: - shapeType = "box"; - break; - case SHAPE_TYPE_SPHERE: - shapeType = "sphere"; - break; - default: - shapeType = "none"; + var SHAPE_TYPE_DEFAULT = SHAPE_TYPE_STATIC_MESH; + var DYNAMIC_DEFAULT = false; + var prompt = tabletRoot.customInputDialog({ + textInput: { + label: "Model URL", + text: defaultURL + }, + comboBox: { + label: "Automatic Collisions", + index: SHAPE_TYPE_DEFAULT, + items: SHAPE_TYPES + }, + checkBox: { + label: "Dynamic", + checked: DYNAMIC_DEFAULT, + disableForItems: [ + SHAPE_TYPE_STATIC_MESH + ], + checkStateOnDisable: false, + warningOnDisable: "Models with 'Exact' automatic collisions cannot be dynamic, and should not be used as floors" } + }); - var dynamic = result.checkBox !== null ? result.checkBox : DYNAMIC_DEFAULT; - if (shapeType === "static-mesh" && dynamic) { - // The prompt should prevent this case - print("Error: model cannot be both static mesh and dynamic. This should never happen."); - } else if (url) { - var name = assetProxyModel.data(treeView.selection.currentIndex); - var addPosition = Vec3.sum(MyAvatar.position, Vec3.multiply(2, Quat.getForward(MyAvatar.orientation))); - var gravity; - if (dynamic) { - // Create a vector <0, -10, 0>. { x: 0, y: -10, z: 0 } won't work because Qt is dumb and this is a - // different scripting engine from QTScript. - gravity = Vec3.multiply(Vec3.fromPolar(Math.PI / 2, 0), 10); - } else { - gravity = Vec3.multiply(Vec3.fromPolar(Math.PI / 2, 0), 0); + prompt.selected.connect(function (jsonResult) { + if (jsonResult) { + var result = JSON.parse(jsonResult); + var url = result.textInput.trim(); + var shapeType; + switch (result.comboBox) { + case SHAPE_TYPE_SIMPLE_HULL: + shapeType = "simple-hull"; + break; + case SHAPE_TYPE_SIMPLE_COMPOUND: + shapeType = "simple-compound"; + break; + case SHAPE_TYPE_STATIC_MESH: + shapeType = "static-mesh"; + break; + case SHAPE_TYPE_BOX: + shapeType = "box"; + break; + case SHAPE_TYPE_SPHERE: + shapeType = "sphere"; + break; + default: + shapeType = "none"; } - print("Asset browser - adding asset " + url + " (" + name + ") to world."); + var dynamic = result.checkBox !== null ? result.checkBox : DYNAMIC_DEFAULT; + if (shapeType === "static-mesh" && dynamic) { + // The prompt should prevent this case + print("Error: model cannot be both static mesh and dynamic. This should never happen."); + } else if (url) { + var name = assetProxyModel.data(treeView.selection.currentIndex); + var addPosition = Vec3.sum(MyAvatar.position, Vec3.multiply(2, Quat.getForward(MyAvatar.orientation))); + var gravity; + if (dynamic) { + // Create a vector <0, -10, 0>. { x: 0, y: -10, z: 0 } won't work because Qt is dumb and this is a + // different scripting engine from QTScript. + gravity = Vec3.multiply(Vec3.fromPolar(Math.PI / 2, 0), 10); + } else { + gravity = Vec3.multiply(Vec3.fromPolar(Math.PI / 2, 0), 0); + } - // Entities.addEntity doesn't work from QML, so we use this. - Entities.addModelEntity(name, url, shapeType, dynamic, addPosition, gravity); + print("Asset browser - adding asset " + url + " (" + name + ") to world."); + + // Entities.addEntity doesn't work from QML, so we use this. + Entities.addModelEntity(name, url, "", shapeType, dynamic, addPosition, gravity); + } } - } - }); + }); + } } function copyURLToClipboard(index) { diff --git a/libraries/entities/src/EntityScriptingInterface.cpp b/libraries/entities/src/EntityScriptingInterface.cpp index 4342f0e683..06a141a95e 100644 --- a/libraries/entities/src/EntityScriptingInterface.cpp +++ b/libraries/entities/src/EntityScriptingInterface.cpp @@ -299,7 +299,7 @@ QUuid EntityScriptingInterface::addEntity(const EntityItemProperties& properties } } -QUuid EntityScriptingInterface::addModelEntity(const QString& name, const QString& modelUrl, const QString& shapeType, +QUuid EntityScriptingInterface::addModelEntity(const QString& name, const QString& modelUrl, const QString& textures, const QString& shapeType, bool dynamic, const glm::vec3& position, const glm::vec3& gravity) { _activityTracking.addedEntityCount++; @@ -311,6 +311,9 @@ QUuid EntityScriptingInterface::addModelEntity(const QString& name, const QStrin properties.setDynamic(dynamic); properties.setPosition(position); properties.setGravity(gravity); + if (!textures.isEmpty()) { + properties.setTextures(textures); + } return addEntity(properties); } diff --git a/libraries/entities/src/EntityScriptingInterface.h b/libraries/entities/src/EntityScriptingInterface.h index d1b321dbca..919d0e3489 100644 --- a/libraries/entities/src/EntityScriptingInterface.h +++ b/libraries/entities/src/EntityScriptingInterface.h @@ -158,7 +158,7 @@ public slots: /// temporary method until addEntity can be used from QJSEngine /// Deliberately not adding jsdoc, only used internally. - Q_INVOKABLE QUuid addModelEntity(const QString& name, const QString& modelUrl, const QString& shapeType, bool dynamic, + Q_INVOKABLE QUuid addModelEntity(const QString& name, const QString& modelUrl, const QString& textures, const QString& shapeType, bool dynamic, const glm::vec3& position, const glm::vec3& gravity); /**jsdoc From cc4bafb46fdcd4b599e2d24d2fd09393bec555dd Mon Sep 17 00:00:00 2001 From: Elisa Lupin-Jimenez Date: Fri, 26 Jan 2018 15:40:22 -0800 Subject: [PATCH 26/89] image entities shown as images in entity list --- interface/resources/qml/hifi/AssetServer.qml | 1 - scripts/system/html/js/entityList.js | 4 ++++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/interface/resources/qml/hifi/AssetServer.qml b/interface/resources/qml/hifi/AssetServer.qml index 04fceec1f3..b173b3826d 100644 --- a/interface/resources/qml/hifi/AssetServer.qml +++ b/interface/resources/qml/hifi/AssetServer.qml @@ -142,7 +142,6 @@ Windows.ScrollingWindow { }); } - // Elisa note - need to link this with specific add entity call function canAddToWorld(path) { var supportedExtensions = [/\.fbx\b/i, /\.obj\b/i, /\.jpg\b/i, /\.png\b/i]; diff --git a/scripts/system/html/js/entityList.js b/scripts/system/html/js/entityList.js index 7b25e66c67..dde91dc694 100644 --- a/scripts/system/html/js/entityList.js +++ b/scripts/system/html/js/entityList.js @@ -156,6 +156,10 @@ function loaded() { var urlParts = url.split('/'); var filename = urlParts[urlParts.length - 1]; + if (url === "https://hifi-content.s3.amazonaws.com/elisalj/image_entity/snapshot.fbx") { + type = "Image"; + } + if (entities[id] === undefined) { entityList.add([{ id: id, name: name, type: type, url: filename, locked: locked, visible: visible, From f71d9e4d6a4a994cb26187476ac78e86c6f5b998 Mon Sep 17 00:00:00 2001 From: Elisa Lupin-Jimenez Date: Tue, 30 Jan 2018 17:49:30 -0800 Subject: [PATCH 27/89] snapshot referenced locally, won't work on OS --- interface/resources/qml/hifi/AssetServer.qml | 2 +- .../qml/hifi/dialogs/TabletAssetServer.qml | 2 +- interface/resources/snapshot/snapshot.fbx | Bin 0 -> 51420 bytes interface/src/Application.cpp | 2 +- scripts/system/edit.js | 4 ++-- scripts/system/html/js/entityList.js | 4 +++- scripts/system/html/js/entityProperties.js | 3 ++- 7 files changed, 10 insertions(+), 7 deletions(-) create mode 100644 interface/resources/snapshot/snapshot.fbx diff --git a/interface/resources/qml/hifi/AssetServer.qml b/interface/resources/qml/hifi/AssetServer.qml index b173b3826d..42b8118115 100644 --- a/interface/resources/qml/hifi/AssetServer.qml +++ b/interface/resources/qml/hifi/AssetServer.qml @@ -183,7 +183,7 @@ Windows.ScrollingWindow { if (defaultURL.endsWith(".jpg") || defaultURL.endsWith(".png")) { var name = assetProxyModel.data(treeView.selection.currentIndex); - var modelURL = "https://hifi-content.s3.amazonaws.com/elisalj/image_entity/snapshot.fbx"; + var modelURL = "qrc:///snapshot/snapshot.fbx"; var textures = JSON.stringify({ "tex.picture": defaultURL}); var shapeType = "box"; var dynamic = false; diff --git a/interface/resources/qml/hifi/dialogs/TabletAssetServer.qml b/interface/resources/qml/hifi/dialogs/TabletAssetServer.qml index ef4fb8d177..315def6f14 100644 --- a/interface/resources/qml/hifi/dialogs/TabletAssetServer.qml +++ b/interface/resources/qml/hifi/dialogs/TabletAssetServer.qml @@ -184,7 +184,7 @@ Rectangle { if (defaultURL.endsWith(".jpg") || defaultURL.endsWith(".png")) { var name = assetProxyModel.data(treeView.selection.currentIndex); - var modelURL = "https://hifi-content.s3.amazonaws.com/elisalj/image_entity/snapshot.fbx"; + var modelURL = "qrc:///snapshot/snapshot.fbx"; var textures = JSON.stringify({ "tex.picture": defaultURL}); var shapeType = "box"; var dynamic = false; diff --git a/interface/resources/snapshot/snapshot.fbx b/interface/resources/snapshot/snapshot.fbx new file mode 100644 index 0000000000000000000000000000000000000000..641a7f82ebd0a67a9364c21867bad4a02d246048 GIT binary patch literal 51420 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

&G zn)U3&NLVlYg3k%9i=l-QG?rwY&lPFRdfN-$!ojKHtN?jwpZweFP!jW1fNRrMz*Wl=AuKoqy55AyU3eH^f^$WO?uQ|Q3)87W! zM%VQ3#OnI)jA6SHckwNm0;qmkMw6tW@TM|j;ZwBA76W$`f zes=!aTM{V(amXl#A5029(ssbCKD7PiNZSEt_|Qf<(st0xb(r3Mr0t-Y_Aq_?NZUab z=wbTyk+uU&>S3s-Z5{UDtKcKM}3o>GeMF{{KcB$g(?AYpNpS!mo7M IwBH~9Z+ICcSO5S3 literal 0 HcmV?d00001 diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index b203487547..53c1e3fad5 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -6525,7 +6525,7 @@ void Application::addAssetToWorldAddEntity(QString filePath, QString mapping) { QJsonObject textures { {"tex.picture", QString("atp:" + mapping) } }; - properties.setModelURL("https://hifi-content.s3.amazonaws.com/elisalj/image_entity/snapshot.fbx"); + properties.setModelURL("qrc:///snapshot/snapshot.fbx"); properties.setTextures(QJsonDocument(textures).toJson(QJsonDocument::Compact)); properties.setShapeType(SHAPE_TYPE_BOX); } else { diff --git a/scripts/system/edit.js b/scripts/system/edit.js index c298da38bc..ece8516567 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -548,9 +548,9 @@ var toolBar = (function () { z: 2.58 }, shapeType: "box", - modelURL: "https://hifi-content.s3.amazonaws.com/elisalj/image_entity/snapshot.fbx", + modelURL: 'qrc:///snapshot/snapshot.fbx', // change to another default image - textures: JSON.stringify({ "tex.picture": "https://hifi-content.s3.amazonaws.com/elisalj/image_entity/dog.jpg" }) + textures: JSON.stringify({ "tex.picture": 'qrc:///snapshot/img/no-image.jpg' }) }); }); diff --git a/scripts/system/html/js/entityList.js b/scripts/system/html/js/entityList.js index dde91dc694..85cd77bd28 100644 --- a/scripts/system/html/js/entityList.js +++ b/scripts/system/html/js/entityList.js @@ -156,7 +156,9 @@ function loaded() { var urlParts = url.split('/'); var filename = urlParts[urlParts.length - 1]; - if (url === "https://hifi-content.s3.amazonaws.com/elisalj/image_entity/snapshot.fbx") { + var snapURL = 'qrc:///snapshot/snapshot.fbx'; + + if (url === snapURL) { type = "Image"; } diff --git a/scripts/system/html/js/entityProperties.js b/scripts/system/html/js/entityProperties.js index 59b54b1020..acaff333e2 100644 --- a/scripts/system/html/js/entityProperties.js +++ b/scripts/system/html/js/entityProperties.js @@ -797,7 +797,8 @@ function loaded() { elID.value = properties.id; // HTML workaround since image is not yet a separate entity type - if (properties.type === "Model" && properties.modelURL === "https://hifi-content.s3.amazonaws.com/elisalj/image_entity/snapshot.fbx") { + var snapURL = 'qrc:///snapshot/snapshot.fbx'; + if (properties.type === "Model" && properties.modelURL === snapURL) { properties.type = "Image"; } From d7a847930d3f8d88cbbb505a6f6b6bafbbad8ac6 Mon Sep 17 00:00:00 2001 From: Elisa Lupin-Jimenez Date: Wed, 31 Jan 2018 11:44:22 -0800 Subject: [PATCH 28/89] added image icon --- interface/resources/fonts/hifi-glyphs.ttf | Bin 31232 -> 31500 bytes .../fonts/hifi-glyphs/fonts/hifi-glyphs.eot | Bin 0 -> 31678 bytes .../fonts/hifi-glyphs/fonts/hifi-glyphs.svg | 148 ++++++++++++++++++ .../fonts/hifi-glyphs/fonts/hifi-glyphs.ttf | Bin 0 -> 31500 bytes .../fonts/hifi-glyphs/fonts/hifi-glyphs.woff | Bin 0 -> 20032 bytes .../fonts/hifi-glyphs/icons-reference.html | 94 ++++++++++- .../resources/fonts/hifi-glyphs/styles.css | 35 ++++- .../resources/icons/create-icons/image.svg | 23 +++ .../resources/qml/hifi/tablet/EditTabView.qml | 2 +- scripts/system/edit.js | 1 - scripts/system/html/js/entityProperties.js | 2 +- 11 files changed, 298 insertions(+), 7 deletions(-) create mode 100644 interface/resources/fonts/hifi-glyphs/fonts/hifi-glyphs.eot create mode 100644 interface/resources/fonts/hifi-glyphs/fonts/hifi-glyphs.svg create mode 100644 interface/resources/fonts/hifi-glyphs/fonts/hifi-glyphs.ttf create mode 100644 interface/resources/fonts/hifi-glyphs/fonts/hifi-glyphs.woff create mode 100644 interface/resources/icons/create-icons/image.svg diff --git a/interface/resources/fonts/hifi-glyphs.ttf b/interface/resources/fonts/hifi-glyphs.ttf index 4cc5a0fe4f098a287c1df067c8bff37695f96d34..8907cf7858dd304cdad024bfa1ff4ca5cd94caf9 100644 GIT binary patch delta 724 zcmX|8O=uHg5Ph>h-=@iK`jgG3nn;_(>^4eivq_UGO~Jo*03;0QVN) zX6G`+!tO43VEF|A+uTBVcJti01h6S!VKbM_%s5l^Fo2H$;<;S5k`V@J8z2?H!Cbzy z)bq8w1E5C$1r`>kGZRPhIY1l%$d}J76|h4W0UB;CbTyOD?i_ub28u0Dt*}@u&77$F zfYNh-k#!sa-mUbPOYI{+;NZ}T_jeoG9&-)t4kc;5HcC}WC9NtCSnyeP&}HzJ@u(EYPvN^4f+E^ov|n-DW(Vg0d|0kC*nP%B`gw6E$TNDaYHwBiAl^DrG#$iK__|C zgvNqQH)8RGsp$dcRy}4sMszda|BrZC7X`baPejBPrbc<$R?GqWn-6M7gWo&g@Y| z_0ABp$wt^A$>#-+s4~gUOI;p%;PR4JZB63kHo?uwYRz#?q0T5G=(k{E5D^Sl!^PgV zXrw>VZw^L=tM`g}b*VT{>$OkCZsO=x?RV*^Ad2((%v^P|?5DNrhqBl4|CbHzFM6Dk ACjbBd delta 475 zcmX|-Pe>F27{!0z?6_>Hl;lBV(GkK38QNLUDR4XK+M&==V(DOwy1BW#IkZFUivMnc zu9t>xA`)~ow8=vt6p=S0FG+_EdkD#nVnUZTW~4s&c<=Jw_uf}Nk@;hpLx6hb2{DjN z4&5p){#k?4cOcr=)0^ZiVJLkBwEn>>@&4_bHb@G%JDRubp^+~yLD>UkV%#d&)hZz< zZ3B_ zt$}N19n^G#H0Q0N%}*Hyx#|acY~|BOiC6dFNgar1PZpdz_VZ;ZyoZ|KK{OBq3$*y0~&1KYOszK#GhJ3Ro0XZUASApy0dARxVdMs@1@+ zt-^nf_8?G1_yAr@=z@5iYMY{fmkMuXy@o=qG??fZ80f@_VPLwtqn$Bz!_ea~!*o}j oc6Zu&BxUcwX_D7Z->L4-ly+$eL51KxrwMlxFZ_S{m45ugzdev~Hvj+t diff --git a/interface/resources/fonts/hifi-glyphs/fonts/hifi-glyphs.eot b/interface/resources/fonts/hifi-glyphs/fonts/hifi-glyphs.eot new file mode 100644 index 0000000000000000000000000000000000000000..d3591e6499c6afaf63e4a2c0f144f4722c2f8d9f GIT binary patch literal 31678 zcmd?S37lLeM;^bE@X|Zv*gd13+Lv9|qDkCxGxE(%ISj%ojkqmri$jpYI&} zgTiXs=}!z-!!>XfTm#eaLbwiYf$QOFxDf!nXI;l|5Uzw9;6}IxUJS=!1mbdu}-v|0}?)-v@x=_T{s-!AC&>-1=t#FtYc+Y-8`o&b}T14*|fz z%a2`p{YxkQd=`Lx1%O@k!b@+w{^_TI1Kj#)0I*(o-7Qyr_=3F}Kpg_O?!#AKdFd6w zo)1m{z&8M(bM@6%UMlABn*bOBz}VHtPTV~F)Ib^le+K}|*S+}iOZTlib~S)p4}isE zm)?9m`~w~Vz_nDLPMKA2k>Gjnk35J zD{oUj3o^h5L4t*6<5d8#z5o3$FbH>_$r<$IDP{nGeOH}z1#E)RZ%*MipZ+G*!F>8` z(!Vm_44@2)P=$jq4{M@N_!ruN* z6^>GUp$SLfhw#_11P{U!@Bq9W?uP5(A^0WeK7A721D}OA!pGqQ@Bw%nmSHDc2Ad#+ zKZ6V4Yw$I=9o_)c*4PFZC;Q_b@4#Q?R z4h!%;sK5q8Ee>G*iaomFQQ+pR2m+UhH&6BUo!^{#`f2mb=r^H%)*F-xo&NKhu}iE01m?o;0Rm-N8wVq3@(Q&jW~G` zTnESD#c(~m#0ZQN@KU%5UIsVAEpRK`4tKzv@N&2degR$quY|kdRqz_P$MDOm;k9r- z0IJh+3yCFH*>d$m+`6#1e#6pjo3?M+dhWIzJ9o|Po!Wi&RWCei^TzJl&Ut&zIlovM zzG7tb+GFL)ShZFkpO|c|nVy+#&o`R;F516*VBHWGQfbR}T+a_u=}a~kMsYGQm@gc> z_|S!`CH;Tlvkb4mUHD11gMEvy;jiGI6bKjwmarbS58EGevd$6b?ap`H z5qFDw*nPYE8TY&1oVUZf-h0&h8-K|E?cm(tFH%>f9#5a0{_X6|+3)2p%6&BVRA_}a zg-=8?(Y4W|aU5SC|3PwI^0tA_z}pA@Y%m_YV(_;H|2e-aeieaUj_-x15_*JdabV2 z6qainu+>?_MqI*B)f-8?h>gw$Y}KZ)tV$Ttb+^;RJGSK_SEL4VTgTU}8~@Sx+O?0Q z!!+Mknw%{Chz?tIDhlo&O|$H1cYJz$?b`9_`r181@kD95G+_=&2^cKG5&RUs0tzq# zfTOErM3r*5h>g}1wwt~3lTb~GUiF%zjX1*lSH3q`sSLh+Ul4rM_xImESgGioe-C4? z@pCyJLqCVF7#ln>SS{r9h3epm!Li4JTrN042ja^DRN>wDHM|BmXmB9~IY^)YWtf0z zn1?p3gAK45w!VszX&Tt0*CcX?JI^5|=k%t5L$$@1K^vRjWL}eV_X&a~yP&f>RlE z2P;mY9E8q*JK$jGe=eUdq<*%)>N(5#JA8*bMvOVgRhnAdNR&Ssf2?qJ}YU zHoBc|r#6c8)pGuC8bE}zfk za6}GHcrLTDwqvIz(izVRYx!&PHJr;83M;>tvs~B8Z4jC<#V1m>R9cS*n0Bo=wp=_s zHaj~unA@Jq*9*nWpk_RsuGo27a4TDQ>tMAy_*S0JZO;wf2xV4WnAR`mem zB<_;PqOTdO8`1@+15EntQa{Z$hK-u1(%Y?a3C+-n!o~({Hd-%A`w_YtQCKn?B>ocv z)s!o2R|U?%P?VVr>Os9-)(-PBi?iW)IJXdZ!qusK@`OL=I>`T*;Um-I{v`Fjzs96| z#LN4>@8>=LJ}0xinnq@)oV@D}mGZhcv&OfD=ehYp`GIt8&hhKRLp9%?uLaf7I2)o> z&F8!Me93TH?=?Z-<$*&J9>Fi;FF^&SpbgM%_eTZmv&0lWo0aPA)*{K*(=72oHGl13 zbuL#d<|5v#Tw7_e9P!5VQZ|>%;d8zaKieA- zHN-}DB&fihl02S)j>ro&Nb`L%Ah=KIrI zWjXNupe(l-t!Nxt_;vg+D4Kh!b={1*?QWTdR;8?HrfXNqwU!x2H13*>m}(z28=Y1S zcO5vdwPA8`X3X_6GzCsi7stnonM{s}>ezg1_wJVCY&v}S69*4IklgfxiP2fHZr?&_ zV(qx?CNq`M-B<4}9lq-@07w{zefV*F3`SrU*1=h@w>Klt;4EvlN24%STA3NT5}Wxr z3RS(C#7R#cY+$2FrA(dLhvv1w~ zc0?;($@)H9`N3a*BnaqNS;0pFOPQ;EYwSp=)W1HZ@{@ivkqR7V#FzR?FO@3#L9u{e zT_mx;!InWF?e#=r)2=e?z%S$bAc0}n4(G!q09`X|Q>aWsmau8QyPbNs)oF*r*sWE{ znnWE9=4K;_OQ_3QMPa|OCi@0#=~lP1gi+WdOj2%VT8${qgx}cDgd^AiWSpltFYGFC zk>-rwnrCd_lc~~RQ8I~~i73n{D;bNUAU%)`YzvumtSjB1nDe|SQ2L{(A-v~1Li{}! zT<+Z~1zxukIWo3ueKz1K;E2p~+*n{bT`Kr#OIg0QZXCTTZP|!U-uJb-IE$Nd*?8s8 zv!rhbXuul$0)8AqC_)V;0kB$5F%@OPL^c~s*zUA5Ev_To?na6yy54PenvG_z-cIn3 zR(`11ohUq1$kd%;lwEik{{5E^|K&kc64~|t{lDWQd3FeeTKS>N^6Wz73bniPeZ-C5 zKI<=S^rFr22C?$)?HvTOHFab*dSUZEt?OL_dRa%uWPMYmjXNhEM5@4lV zt9PWT*Ji1Il3Kl1kCT|@B!%x>esb&b^46o5Ze3<856rB0oY2#1G>7{Z)-iFzx%;oz z4A0weU@$c(wPmlLnszgTOCR2tgf6~gd3npFN0+y5-FD>CH8XZFv~;n<&_DNjg$wKU z<;tVE{eQEbrT1ew97wxU(;HnKMatg@kb@Sq@sIJ75W^7EjLxtXcEbU<7%qnw!HsYm zywX^?zXtDt$Eck&fOWePHACX1cBLCN+a1>G(BP)g{#@UatX^xkx}8Q6hx&Q)oO*Wb zx!-Z>{%E;gYxS5r)au_Uxr}#6Z^zKP#*lIUz{EQ%Y}(KIe)c>+>v`Gp&p7X7mDY+5 zRg>!f#1S)RWIyv!tlL^CZCi94GH>>O;)o~Q^1H_NGT~N!ZE&Mr`SNq*p{K|wr1xai zIi?QhnN)Lp#`#ZF&+pZqny~Uep65{-M=p#*4Zn$h2^z961mnZ55u_G=(JlTEQ}UVDa#s3$j&08ES++_wuGG|)&XiP*J~v56Qe)nNT2L9 z8&McXA)^cPQWf#?EY`Xmqh=+fsnApA3MZ41I36iov}x1y%%u-Z8fM?ejuf7`vb1+y4jp~ZD6SYu^1>DZ&PB-bsTrhb@>mJ zPy@=gUAyYI9CK7q@K`XGOU9oUlj8PrNTw6RX)S#k^s^bW7;A}VtpTIYP2LmtyG*!oc_^n;Y23j8n#Zktg`Ia=Fk-?DAna~*4@EQPkDywbI8TMyWtXIWCFrIadA zcu&e!D_;!)EZc!?2R5cNnY2x>U-_4l_pYCwo}OMmkyi8y+tNccl6=%s2pQT|;MmCjKDt;O=P&CNa@74oGmV<6~J4>w7 zXKGy|TVjcn?*LO+uhnY-#=|&-G)^K#lQtPAOGtwtX>>PYyHl&uFr>kwk|CDpTD|i} z`*Ztar#2M0$ZWO4l1DFzJgwI$6?-$NMbZ`CtcM)CS4!@W#^}Ap9p}b9sK$dy6${NuJhY>uk$oY%-S_NT*d$M3EM)EpMW_r0`Y|FTr`qfc_Rjh7pBo;sf+0JV#GhJA*(4}vE!95y6GBY3>nr?lv8P(b5ko1 zFq{<<%WUO~TwB~KBjOyFST-&K34;*hcko*f7#(m976Gc|Q`!Z|INtBqM%T<%#sneo zgHQ}(+?}c9BVI~s*OSYndmUI@SXg{?VezKipOPH>(;ztGJN2hQymk5LrCa*S7QTOR z;lOzdi;If~&b{eXwr3BWM~D5k3W5~zIEMn{@Vodo;6vV6NUQZcqniC%>Tah-0znO< zR`C)hai@;#5~|gc_bo3UJ+QEN)1wOuClnbs@aK8)6K2Ve1Pi3U;*avGx%}vph_AYSq%O8K*uDZw4$lpXj#KZlXwd8!NmjT zE-o%EKDw~*!XQWm%Ue@HI=ywP)CqiL9lge5CrM1 zTUAyC!SZqtq=T*f^0(qA@vATlQvlUYy-UW50cD1fWMp`X3S`-A#H3qK>N-{$=g_`% zaKH<4Co*wlSvRhowC&9Y+}JyJi<7qtpGi-L4+STt@63wqgGqd&rK31=A{PY7;8od4 zyI^lV*Nfc)TO6nG#*Fk|F@3^+Q#_p|xPLdCiyy>K!0>a`C_t(a4G5|!PH_8h6nTF7 zvP?GA>e~6bjaGM$V>`~CwPBPz9A&SSDwj)Np7Q-LKANiA16KEJ*LK{q*XE*x`ry~# z&3FwS0ZqQrYPS=oP}S=F?=GrsUf$p44_x?uFYlYb?cRV-fA8=A0m8T8_ppf_@Bn(+ zS5JM?|Lkzvo}996%U-|!_qb(GuCZ*(+OP-+kHN3t5xk15r)pV6#wa1xysMJ9K?9yT z!Z3s)vyo(Fw^PG&w9AIVcqp(_mLt-#$b~M+KuS3j4mK<0d4bNq**Yu?M}xj4JV(f) zLfbA$DbiBp25Xh(nzD9{_vsJd|G;x_7bE~k!kjptjMmd6u482?2z-Cd8s86ssWZ-z zuklxJ_-m#Dzn94m0O5V`D9+#uh@waWE$l;n8?aT!G21?()g?-D;W+pLskY0s+9#NF z-r`6a7YO<MGPw6xPu5`ZQ^%USwZ(43`@F`b^?f@K~DhW~JI1Or}$=E43x27!%eS z5lGKVEsT!sXwID_(bgzgraaTCP7K66?fOElVZyQ22;q4tJ~G;E?pT^7A|c!d{|j%x zJoRnv3`MZ{woh7)W0?cyowVbm9aHz$!dvlbJOKrOZZisFLcNW?K85X8jbKoN4*lvL zCvbLaC$rlLoIM%mpLc7=(Yqa;J)2I>&T4ucFgOPe;cwvM;F65UovHp;qOiPyY*`Yf zZ8FwMXlnT59amntWBXNC#d}8f(98DNflVj)_*L6`w|@WZOy+DSaC&7qfhmi_tKl|$ zIlcs3C_tT%Bx3~_>w(1gGdwEg5)w|m0b3Qc)@yBBQ&Ys;Lm%@p8IKO1mhxj#erD0O zENg0wW!cvKy&H7*FX`ULOn?7Gxo7fUH+m+}rm4g!q|%$uD0lJnyGy2EET2*G@l&<> z+^K>kq4M}A_$bW40xZKhaK&l7YIGHrs&BwiB60IM@T)Lq;69qwdWhBOX{~ow?@uRZ z4ysNaCm)*bp;+i5T0uGI=ETZBNz0O0W3C&P=3|R}nd#inL;ktfOWT&$2f;7W_b&!P z|N85be%_m!^a9_Xn)3a?o1F6U{@zJDR4VPKg1i?^fSkCYOGFvJ; z6)aeuRWyeklYZWxob){3pPcl4&zqzK;=R-K88?{Mi{NDdxiiP35mk%?72%A-XAZ`M zaK3hZH0q;!b>dXASDy<7?7Kx`1NHSt_pI~pWf+#is1)e5wq+m-Tv|n`v?FmSH#lDz znd5Bcp_zP|NNNR@tQ+aCdEP%V*UJtyVky7GRBreo|J?h7;C>?H3r`(hOlNe!I?Lqp zr6}=j?%7fnm8Xi*2_*7dT&v8l8A*S2st}~SRFHQEC-DoO|9=|Vj=O2;l>#4m(ef;! z>glgO9rxg;@exSFB5a5A;8K7bN!k(`W09nN>p2KET5dH*qxhMaX&f1@GyK&fQXeUg zderM*h3boYZEe|1D85e~cxbxNBTcHDb0WR+KYbtTx|kiBPc;7$>Tu{GX`TB4B855J z8(8YzQ&*IP?e$U!`%YUTUzW=HyETO%?FXs6H!z8x&wsl+98Cm)^oMQfqwLXOF-5OX z_5vMj&mx|hfPMIB{B@Y@qa82mdu@qV`rxP;azA_1303aZG+zCwkvhAxL|Ey6`MY3=t*7b5TM``=WL_-F5<}opyA~2`GMx!PRgK z-+^xf54r$AvOdvWV3!hJnHiehX`uw=`GGN0& zIE0VlulIHR7HomD;X=3?uIp*7r%>&7hmeg`gptrH12L5_tewURd+>+JWY8y_oXS-< zXcgI?p;lxruHq*lX~W$FZ<^n>{j&M_Z98V?+19C>rzR)q@O!rHu(Xm=S&lHs1fANB z-0nEG-2?MAK^EGUO3B{2Iv)?>P4ky++dex#KY!Npd#9!**DtP_np(5CQL8((zC+)n zWX3#T$v4vrrF3kqxxPi~TlCH3BakvH<%3dYOyRbaJIAx*y?J{VoP{64kHS28kE#`A zOyhQiun0AUtxj9j>gAg8h7i7Ba8xq9o2sJfL&#$ts)#_O8En_O9ju}wm~_!*%o1p6 z#ImiIY)>(k(uE!9C&Q_NlerdE?%ZhqZh z7I)CGc!smFCvdOIbDL$eJm4PZ6#91o%;UT8i?ADRhKJyT@MZW8{2OL)0yp7)ya=G% zi=t?E>b1JkaS{(>s4L{xoWk-fwrh1ACnZ!ruGOPT3BxH?FSl#WHbDr@8LS(c zkl&=$*}z-1PIDuY!=Ouy(;(nM(;EpvTJDLcFdzq#NomZrn4w1Gml2u{keiu@nwkh!_l@q&`EXv=E5c zC5S;HLu@j}oWX#6DY$NOhOr+Imc&)8C<8|(kf%gO2!)(1e3l9WhUn&^LApc?EVh&J z37=k1Ab@s4#%;+758zG8VQ>{`>oiAfF=WxKgN*rVz(buztRq9$&UnbMl;Kg9Bf~&j zS;gtu4H&Maf^(jElgA^)EadY9@0b+9^1h89mH~^n8xKZfOtMKVq~kPZ;>+e|XO_=u zdR|s|m?X&1qUIxx1Sv11im_cz8ZTvxO6?W{##2H~%xv8jGmRqP1sjnGMx>&lpfJki z6v}?-X(aVG70X|SOI4g4bhv5EDeD4(^k9yY-;>>%qk(ePfpkO`#Sg>@1>j14p5bOPEj`Mmy7z-9#tdM9Z$!^>!t~jd!N*d~4w*w(=uSD1q9u zp8nqV@RpSy3MC|Q7H8jeFk+I+l^5QTzVionrtVz%M~0f8DD1j0K6LBlM{mtD7qt=s z^Zv?@l+{eWmt2%bAymW#Cf~CZladLpQTdw=#fQY9_|TPwe17A_6!oj14u|l+;in)4 zG1OohHo&rpqNpt~0%>QFe6RHsM$LAYJRU!hN+{1yrZ$w+?!Z|Wx|ZecY7eNp*38ED zUS@Ia3@%^jTH4#$84!2QOvm@GeAnXI9az55(c0bB9%Q#u>id@ zGeM@k23@gicT5us}aGKNpDODq#tI=uE>}|YIDO4DZwOV7YoE$iWmeVbOR|&poAMHXGp-`FxGV)-AVa0Mxq_n@}llz`lw1>@XYM53`xV zUf502i?BfP(&>DL|EllbPNv#R{i{7M=SCV=p7wlfkRg}+ZV;UMO%w2T`&mJ7R`8O2 z`{y@aJe5zS2edmJ_}Um0pZ^=*|M9ntN%3#R;)3}t7PlWba9@0b!7&fRHTWUC9b8DD z0??-~4TpO5G;-5DllF&qIj-xrJFe@vnBL{Mjzbr&^G3#OewlP^emVQ6opig^aUIv) zea87l6wZpxg|niK-~R$Qgzv%kn7B5IZGthf)*5kms@LnaiVpY`)_b85NlZG8CMOHU zLeLy(oC0V79P%4pMDe+iNes{I!=~ck2Zg+C zq&PJOa+k1dp;o>@BTDR&{c9ZSU8C~!PAQch8`Db4o%)%V219xT054`hbOo=jN64u!|z#n1anQvyEnQ|d%@r155q5C(AI8SSk^&ry22LQ46wsL*Ub zqfV5Aq7;6DHpiaT^kdH?)$`mwO}YPA{#DBNUk8lVE#P0^U%(K>?^jnF&~HY&MNoT> zwiJRFrm*L$HToZ^fBgU|6OM>LQR8yr5-vC&<@}NfrKEM2((*6PIDbZ-r|u##J8_B0 zFZd-B(jpGfA3ft-osp;49aX5}NjwP!vI%>YGGsMyT$lR3SFgBB?J1Mf!1PM5Ifhdj z@xSsT6H-d+F3XYH(^Ln+NBCczd2u?+m#8_~)6%i-vZRy~M>rqh|MQG`pf7F-EXZTF(Q$e|IL0g?;}0|8e_*Uj|n-_DRKOl^pz>5E>)jSG>2*T|GKWG7Ff zv5!_G#X2#boUaXJii6S6{FWUpH|u0oK<+oo%Z#o(E``mQ%t~P?={h4JKH>=6uqL0g zGM<$Uz5HagC@txb2Yh%SOrqh^(2(bsyNcP_KmVP)<6G9)Z;p^dO)(+3Q2**FORcB_9n)F*t9%(#&cB@^h?&`FwwK3mI28u)FN@*yGDSk3ehDw$4 zP;t)npK6_5tF}A4sP(`SS{Q$Mf%QQUWm&_FYE`VyZsR$d0F+~-o+v~w&K z#Y34eI%FSk4%m3oaZ^vjLdtd0p7Z*E3w2GQw9#>F%W<-S?Krld%Ll&exT)g1GS1wZ z;Ux^!Aj{@wDbBFeAe1L$qLzooY`8 zMk!X?#Ax?xM=K?gXV>s$rz=q0bVdz6v|0ewaOTW11nh-#@G;}RAfH0htc@V>e$+%i zo~8;hTqhYleXrh!`6(D4A6|Jflfg}yDMx>oFskor$6EPTHjBmVRQ8kE>?gDMc!q*m zGWVwGJ{{iOOE{lWGe&>e3w!Y~d<=Xjzy!(D{yL3bWoo*&YUA`3EfeXlThOLvt5;MX z?NEgredbxl(YQ%FnU&Que9Y15Y&NZ(D^H!!6DO(QPJ>W=Hl5_DAZV(SJcCNr zXWThOril(R%`ItOgdxE&+mO@fI}5_ z;z|4vF-WeliRz6+6Kb23>s>;ram(FD zj@*6Z;V}1dYwdWHeYv$3A3j29L)Oc4;rQAo?K|nqjMYuB37^C#A%GG9l1^Rk){|~b zQ8?9&nCPUgYl<9AHN4N6uUs}>drfX~?L&=4zVcURUafU7w>(?13uC!^#wVS|!g+_v zcK+0bOA8l)!6@8~e}%V06{Z2qB83@}5Isk9GYXsYOj z*)%b~ZQJ~s&DOLt?Bwkcixni}D}TOre0=RV<`u6uwq3Su-Q;H5-h72^U%|Lo7^j=2 z{3bNn*`L&3qt}>#M(S)6E@MI_a`CEyZZwKs%uI`BQ+ldzizc7Zj%%e- zap|0e(%ECxTG6ty*}-~};(c1D z!?3ha8!w$*5h8Cnwv$fhr*;Q5F9>jl$Zt9F&1#`{kM#E+!sl@g$4G^*>Nd`Ge>ioJ(*GJhj{{hx^d*e`y8ELkT5HQ- z2`*W=;S@2s@xo&6PeMncd#|0cZbcNP|$AaH(%4hKC8mkY=48wi~H z==zuwWP1MeC*YUyd_2!kt2SlA6e?v>&B=u98DAlBVheW%$?}M8b1CHFdJ0t>-jWj1 zb1@r?ST^VD*OL!=SW9=e=k1I^zyoj}-hnrR4{=ZCR@I`>pO>&)?>4)%nuA!6g89#M za+mlN~5{0QfahCD=a%)I#e1qhp#&+F|d|p23$+I zsmeCGU#T?a=BA6o!=*Ko!^6YFB!AxyufyB%MaCE30a}?(aQGA&XUVfEsyj`(ODK6% zsgpN>=5L}?C2lnvVFj<|{0YvVc*62Liw+Ouqw$Q)@Q~|}yV~_#mWzT^3NPXxs&xWy7f;@Ks6`@FidPYx@hf`F!D1`TVi1TO(j_J=}`F zhOY%hNE0TNsM4-KJO;mDRz;U}rA*PMy#P#ODMhtMuP;W6%v(b$j-I$_^=q5FbLJboYQIwGh!EDtjp+T;Dg2!t03Y8W& z$rDL@)e}_Yea53gpb*?rjEf+U87WW%E@NKWWh`(RM^52GT%h2ZD=q}lUNI?zQmB~c zk(q{!xxuN2DBu%iR(fWMh*ZKn=DL@q2M5Xn0~q#lBNeD90Mz297PP+aRCNTE8*nC8 zUyM9$bH*5X?F5CCp017%TyZTKDwHw^kaK3I8FMYv-iE<+W?-N^U~tVhTIv4NeLJ~cYa6JiZ&y%8Q^v57et*(8 z82Vp3f9hrRX<&@;r-%+u-W8y@NZ^l|;Dx?bQRrC>heGK(-eJ#iWq61ShBJ={M&`&P zWR);su1MUT>Izh=_@S|}mDi7r;fJcPKmIfvM>zSR58>F(C7oWnp2cAIkyNZ>`rFcvOH#+l@nv>jn_$+%#iby$)gk(_Rdd~Y;D zrDir;Sg=r{V2m@;rCSTq)Qr}mDVtmEWv01nbD<0d`dRoAc5#mO0m%_|FvOxJg-7-& z6b`YdPa@Jwi9R9GW0hn^EEj+)^q{-NG;>gryh?j@PG%CAGF4 z%aXPdG9WS|GAV5>rC@^mv0MpDYt?hl3N8hwZZ$nH!pah!< zZ^}i@Q#fc3nm*N=QKkJ%mLMi+)2fU?{D=Ow~@ z6iUD`NJg@~fC+o$1Fz)w?5yVlq(;!myC$avdWnyM~;@Heu zy=lGY_lWuvS-nZQJ4vX`&nho>Xgy6&B=>ol3W{QKzV?^gP{h#?MscV^676KiQlB== z1k$RvDimoqU!G;_v27N&nXVird#V?zzY*JKOe!=>kdu+B)0TV}rkhbLHb0zxG9L$u(6-~9IMUWt;&>^BNT_9%x1}ILL zup~D-7??f~oKa{GS0pGIUQh2a@{6Pk%`&6_(TYXtIn$rSIA%sS!)EeQuNQf5+JVLD@_MqAurH9K(#dwy@ zSY~p#V$`fY`wBiXVfCg;brF?nZl;tXPqr{C`WYGng)tUU%S zUTO%@zr!Qa;|v!g*VH^;bF$s(om>f;ddM4&qKHy*9+;(d$dnOk0<|`K15k?CWm(|C zGOWix#Ye%03>2VZVk&57heL1~9EY38m(`)5WL2)Iez1x%D3;j^qn|=c?1^tlR1njz zrgu+08=rcn(|e}BV}*H5eH9j~lqn-6mho&q^!?E9n6KS|V+VoltT$K7nxRy-tLfXa z?2nt&%X$xN?`M4zT{`w1flYvu{o#Ic@DC>2-e+etKw?WA&MT0MD29(??Kt@rHJ`h2a=(dRRVov+W) z#l^+5b}p7Yi)+D3>tD5a{^H{67KdFWG!w&%>$m;N_Qj&Blw_i~h_}rPF+IciywsOj za{5A>;`ipceP~)(ms{tRmn*kU?yD?kl%p_HzIaP{%Nxqe@ROk z(^a|*3@(E%K8EiydmTWvO-Pk0VSs0VgfnL6u1dRwuiDXU?a1bbqi@Hw-0YuCCPh2g%CEGgRV z!RXRvxvEZxd(8?OWCu3{iK8v%S0=ZP-M(FGI|-J)H=r8JI~o};9kjMybiv;iYsF%% zc%td%1N2;;9m9kX0n?Lk_pq)Hxsr~}2TD?+kPqFz*G=)()g;6C+vEH&LM&LeK0ZE*gjYpb|PP5x<2t`N7 z6jsx1(Zc4At}mgk$F%jvFzXIsQcLOyExrwt`i!V2dWqpv!oFx)DrNuq=s8AjHnSZf z7DlDynz6=sUPx`_-BjX=^o&c!Bg5$omx^0}Dil%a`&nBr zUh>K%+UgSF&)`wqk4GS5#uizFr`B84>bUC?0`LV_@)DAe@ir}``({@%xtD^yLHz$N z-sx*_5~ne1{0H?WvZ;e7$w>-dgKyz97APH)3Z-MA3k+_DJMnJ32Q<*WXRDEaO=F7o zM7uT%(y)7c&z|FZcHkz#Qp`r=TTcCC72MlwwaX`!U^9< zp~4m?RGE8>r5Wij+>!x2Ei!_qge~1nZ8Qu?6|&>CS*82|AJKQVTLGfT)6$@Dacb=^ zqqapxFx)Jh_$<^+1frPJWbrw}qhn(P>!3}xnt*HJ8hi`B8MN_UO`Y!ZRq+{x_5K!E zWE%9m#AfAcyH&%b+b_KE_CuE#R=8j=lN}t)W(J*&(a6}uz`R$$H(z-Bp+mQSVo#^L zXHU0tRqy`bp2lX=XQfllvj;Twudr$=?~ ziAhh;6HdK=wqQctF&q?;IaA-rJm#vvO7CqMI^7Kq;VpQr@&C?~{}Q#LXjoFwX-5fF zZyAoF)eC)&qVR`P5D&2w32U=C9PhIf-m=cl+Ux9Wq5pk#*^7N-=vi*sx1{4TrV^!n zt};cG%ob_Ec%U6_<%9@b%=Vw#(*OR2oM%`917|1k1XF{`VJf$Ndfhuva$BY)+bZJz zTJ;hfgm2>`P$NV(r=~bnoW{T+GiDBP_6A&!80!&OV_-tuoA?&cvM8`m-rE)NKXdL9(t!e zI2dbPw4Jb~vn&6m6%&CHL;PBm)(&v0FIxTj5{CFQ*ElApN*FhO#+tt&FVVlsYrE_@C;?RYFKZhj*W%!%N_npF1Vw>INn~kxk2vNY^69 zuJw7~=Sn2hWKF8Hv|Y=$A~qldw}lAQ;4d4V`8km9Fn72+P?>O%Ydh1OUBA{-7H8@8 zlYItLP=amv6n>)5Uqsgj{@#FS!OfzmWQd{%{~x9HC#2L;41}3sVpFG*OY-{v z{vNoCi?p72;q4ur7EWgUYj2!&0HFYHhcDtm(yF@k(;jED9f#Q+!Z>EXxNdDaol31+ zmr7~9uANS+tm>>&S(W;ug>|WPD!pzYMQIE4r54sHl>$cZMhg#uAb)yJC!Lml{v!VO z++4c*yv5-BTqC{Z1@#cf2SJH%?O>JTDj-!JMZ7Cuz&*G zfbYgvLkP3bGCNp4m%L4$Usn8Vjk)RhWWjI#=UZ8SzEOYUzt?<_P&g$IW??JrpnbId zvlMu)vHWwUO7=NM>!*H>l)CvR$K^l#d&FaA-;&$#Hi)21KE7sRcJD3MrdZV6fUPBr zXanu2($*BC)4KOQWSY4`X|`3qiHB;_waG)7fujSj`XnK8F2lFTa|I7y^OplJ+Wb&v z;L-uJM1#Yd;5&F34uAz2BP058cdGdlC%^3msW1qZpYipbAPk=M+l%=-AI`(y#P@>J zv$<#qvcW}?+I(NnAj61$>a-1nRtVd##4BER#g((7ZiQAh7mnvLW7`xHnT!{#3mCrl z${pJ-r@tpv-9nwoMdO)FWrwu0miB^mDWw0B@G7_s@56gddj???CSU{Xr`>@@uxX&w~J+dDIke&-Uob~zvGeZ8~i=q*#gHRoFhkLF^m7q5*sI!fCkBeqt~A3A|OI%)^b zB`IycMWCM3gd?jt#l#i#&B*yDDfKAlYL5IoT+T`3SWvJJF2qmZuR;c9U_I=FgSZud zwhtuo8`#Pyf%PGbl4eK-Y1f1(&0=DVPI8IYDb%1#!EyCkMbS>Ey~pb{bJ6e3dMybI zs$^{5UdPg{jb@2*PuJ{0wo0qfls?U=BHm5y0sRSW5~s;!1fUSQmLhNYAn&*L7Y%OFXJWan^B5!y~0C zX9G!ddHv>eYTeo$e%80N-)ST{DRx6j*OlZZ>!s7X$bZpa$!z=*#{03FfjkC;^PB9aK1_0MT||#+Noa#o zTB-^?L-2tTny5hW8%#J0mKG9ImgbV}3bHA|{J;$q=X;`z^tkri$Z?&Ws+1aGyi}-+ zq*AUuKV0l)CbF3m>(>p$gThvUr}K(=#!EphY#z9D?R!0!!$OJWS*n;YfSElUv5@B-lUp=-NN_ z$k)3-WPu=j9-#uucoJV@_9iKtNV-Z{PnpP2DoYV{v|DW@cn~4SOY9Gh|K^F_S@=iN+$2Ar>ij#w_L!Frv(VSu5Givyl0v@nSKw zsIANni^cJhgP5b&rl&VekCjT*=}ptAdEpkq3?;H$zBpcVFv}QaFm$;Kj^bVTFyVkX zAMZkV8t~zVAMW3~3-3bu4^L1X&5Yt*PXoeTG-0t*dtuDd4$ZU|#s;h;v?0S8n_w8s zrrAgq45-j-M5<2vU_`3^?42;OXiFQ>(&^J%U|7LrEQp6w!nPe5U)kFP166$HE*RTT zTKB%z`q&m~v?CZQ?H~&2Vg@;LrrpJVde#mY#0zhO592Z14Fdq(=bHxq{&{A_IG z$Ka)K7rqYfgCSS|pw&XHZfBSo?^wOIf%P|+?=E7j`~Ss6Pcbu_k$5*te@lpqv^OT( zsP$y%aFid?+!7yP*Q+PAu*CB-2lQH(0COBUiS=1Z0- zRA+QpsU^u#RRCc?%Xl$+0bZ6YE$`e>8;cw-Kbad8JVX5U z0Njfw@D5195bc@v%zXgJe^|n1n|2V0n!ju*J1CuZjJ$srZ5|SP{j|S(WYhBKKFny} z$t~`+{lM8gljG;uo^P$caA0O8qW?Q{4;;t)@uA+Drd)3)k0JVJ4RuXE^c1RD3{caa zjr4yjXsT;<=)ZKBZ9bAl=c&jq=9iLK=EAL`8<@-rVGABGn+Kdbf=vpM-kK4wtmFqP zI7SCOdR8tc<79Kb;6)oaE%YM1fIEzyU~H0$^wu=;R=!dh%vW%X^!dYZ13rLnh5=Xu zEm$;rq#Jiz*X$l`V#-OkGJ(DQ9qP`A*6*0beU)~rOIx{g6GGeZ*jIvFL48HaYp?Zl zxkra4hq9i1iS4odak~5EEeAZkaruTDZooI^{EJg(OZgR5$OT^svR_O`LqkzoWb3x? z+x6^R+DoUs&GQZIR2x3|5IAgx1NaEOA4)Jmds+}6(8G~??!lxN^4)E3WbKwb-Gs6v z&ChJLM=hbv_7>5|lBh9+QHT#p$MgO+i0D5lM8UWIug0$B$8F<`&rc#LN~E+8J=VKx zdu7YJYuRP6(!QKcT{o{yAFkUtf!n$dhazVsdinAUxhpy8BWWAkKnfHokW)`Zfga+Z z=%GCoJrw96K#`(Ad+Di%ptt@3E{aVtBxSGLIEetw{APw6ei#mChMe#Fe)XxGsvZ1R zyP|1VG#pcv_lp-M>-EVC#rK8U2~aHICsPF#waQkmUb$AO=hiE=1xWMTOE0{3P1CMv z&rPc9jfra$jV)EE5j3A!yi7oRV&M*6CQFHxPNH@t>{OQ5KO^?##BwUxfE0Rv$x3>2 z1GnUSMXjtV74>+f*eXsarQ)e+xhmyy$IhuJOvlJKOeo>wN3E8H$fa!$T2HtyaN z3jIe(i(JBAG58-oq!VO0(JEd|^olQ#?GNEYdV{=4-V$@vM=~Pa=Q{cbKBPmeNe?q3 zH9j#T(gW_Lns7X{*+QJMAtlU6f*&aX{}4Xr6I!IH|12z|Y)C1=QM=jv`-4I>5B?|^ zMexMzNwf~`mTL(wQngxny4h?eXP!-Xk=hFj|9)`V^WF8`dxtoVW@qQ-<_B~(eT zoSMBj*J{mOoIQ2-!?}@ADbHQ*ZV3M;=gEus1MZSCStRR;Zlf?$TJ6>wg<9)`aB93N zb`h3@6ZT`7*KRSNEI{rG%cSf@MJXuaJzBgBRWMWAY$(Fb*4oBqw~^XGbT`(3pDhB* z2k$Po@sw0P_=_%U>TGqQpx5YJZT{e&QZa|q<-DS|mrIk4-zb&R8BNvmg&7^UaI{*T zPlCnia=lc>-Nn~$EuBW8eQ>YxURjd$cXUbCrzc8EX|-INx1p%|a$76tSEU)brU+-$ z(=+&GbsqCIQBz(o)^oZbOCmaXEA_ZQKs0hB!#SdmZie$@ncT^+L^SelhGn9We`Q!9 z8iaKuq#^+ke3L285uKdMaGqQuk7rmS4f5*@%cMd6mSKf7$h}9AfDW7;$LPDGaKOpK zbfDwR?Su<7N@7O(gpwxdCcL0cvPGJYs5mWK6|_;wze`pUib=8^_|bO2`n1`7Y^Ax? zJS0GKtGU`SI!1S;bB;Ws-iHN#1YhYD%D5Bw&CX*hpR69S>OJ&JKE*ur`*E^fOf&`| zRA&_wprVKpH0UTpctM%O6sA!{4RtgygCm&59OiKpVl!<4O`O0IEjVCkLi)8&=>)EO1@Qctw!6hJ(PT-0&Q$&^J9Ans(_ycHVO=B@W%dv=weKCybAG54SAx}^ zY3=Tt%vRDPRL}7p&$&re&m1OTY7FjQ>xk{}arM18iUL2o%H01KSb^)BA*Wi}8U~Mo zu$U*mt6l z)@NofYYBx@lUal6A<4!fjm1cBmt+MtRnn?eld)i*8-p>}Q_>vO3cQd}&Xu%Twfhnn z9mR5(Z#3rLgprk-IU8nCG7EO*w6W3!NIXk0LnsVt*q@|FF zej@cu#`Uy*gS#dlNPOg5`CU4aT*u#41xaw^1b)FXnHd=@i{0W*;M$ZKZhwrpr@C~H zx<(vIj=vMgrb}5Q4FZqKl(B%RoJNu3_qiM~lMnQucb!_1(Pu#%<}6S0_+CG6(|#@- zN}j=Q6g)$8Zd*4ki@KC0auh?8MULfCuIvRv>T<~qtlh$%!=0W>WiPO)Ti6Mh=y>DX z*-abUu>B0gp%ghD)gxy5JTw{gtx+NNy*Q$_6m!Zn@z59{^)1JhlWch}aE+BD>?C0~ z30ISFtsqK=#!41qzfF`7mbWC;Ixp`VsunGv<9Z%r}@E-rHCdsI$Fq_Ju{-lE* + + +Generated by Fontastic.me + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/interface/resources/fonts/hifi-glyphs/fonts/hifi-glyphs.ttf b/interface/resources/fonts/hifi-glyphs/fonts/hifi-glyphs.ttf new file mode 100644 index 0000000000000000000000000000000000000000..8907cf7858dd304cdad024bfa1ff4ca5cd94caf9 GIT binary patch literal 31500 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 literal 0 HcmV?d00001 diff --git a/interface/resources/fonts/hifi-glyphs/fonts/hifi-glyphs.woff b/interface/resources/fonts/hifi-glyphs/fonts/hifi-glyphs.woff new file mode 100644 index 0000000000000000000000000000000000000000..152e5b4cfc7c4c4a93501e1299cd76b08f99f111 GIT binary patch literal 20032 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%0AM|Nb=x81LFNbI8I6Rv$RP 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

HiFi Glyphs

-

This font was created inHigh Fidelity

+

This font was created for use inHigh Fidelity

CSS mapping

  • @@ -520,8 +520,52 @@
  • -
    - +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    +

Character mapping

@@ -1034,6 +1078,50 @@
+
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • + + + + + + 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 73/89] 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 74/89] 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 75/89] 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 76/89] 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 79/89] 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 80/89] 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 81/89] 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 82/89] 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 84/89] 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 85/89] 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 86/89] 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 87/89] 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 88/89] 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 89/89] 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
    +
    +
    +
    +