From 17404863e7e3971f7b4e21d62c939f3f2af62c9b Mon Sep 17 00:00:00 2001 From: David Back Date: Thu, 15 Nov 2018 17:42:51 -0800 Subject: [PATCH 01/33] entity list reordering WIP --- scripts/system/html/css/edit-style.css | 4 ++ scripts/system/html/js/entityList.js | 97 ++++++++++++++++++++------ 2 files changed, 81 insertions(+), 20 deletions(-) diff --git a/scripts/system/html/css/edit-style.css b/scripts/system/html/css/edit-style.css index 3a291bf27b..a00dc4d57c 100644 --- a/scripts/system/html/css/edit-style.css +++ b/scripts/system/html/css/edit-style.css @@ -1383,6 +1383,10 @@ input[type=button]#export { cursor: col-resize; } +#entity-table .dragging { + background-color: #b3ecff; +} + #entity-table td { box-sizing: border-box; } diff --git a/scripts/system/html/js/entityList.js b/scripts/system/html/js/entityList.js index 00c50169a6..6f12dd1e7c 100644 --- a/scripts/system/html/js/entityList.js +++ b/scripts/system/html/js/entityList.js @@ -21,6 +21,8 @@ const MAX_LENGTH_RADIUS = 9; const MINIMUM_COLUMN_WIDTH = 24; const SCROLLBAR_WIDTH = 20; const RESIZER_WIDTH = 10; +const DELTA_X_MOVE_COLUMNS_THRESHOLD = 2; +const DELTA_X_COLUMN_SWAP_POSITION = 5; const COLUMNS = { type: { @@ -177,10 +179,12 @@ let isFilterInView = false; let columns = []; let columnsByID = {}; -let currentResizeEl = null; -let startResizeEvent = null; +let lastResizeEvent = null; let resizeColumnIndex = 0; -let startThClick = null; +let elTargetTh = null; +let targetColumnIndex = 0; +let lastColumnSwapPosition = -1; +let initialThEvent = null; let elEntityTable, elEntityTableHeader, @@ -319,7 +323,7 @@ function loaded() { let thID = "entity-" + columnID; let elTh = document.createElement("th"); elTh.setAttribute("id", thID); - elTh.setAttribute("data-resizable-column-id", thID); + elTh.setAttribute("columnIndex", columnIndex); if (columnData.glyph) { let elGlyph = document.createElement("span"); elGlyph.className = "glyph"; @@ -328,14 +332,10 @@ function loaded() { } else { elTh.innerText = columnData.columnHeader; } - elTh.onmousedown = function() { - startThClick = this; - }; - elTh.onmouseup = function() { - if (startThClick === this) { - setSortColumn(columnID); - } - startThClick = null; + elTh.onmousedown = function(event) { + elTargetTh = event.target; + initialThEvent = event; + targetColumnIndex = parseInt(elTargetTh.getAttribute("columnIndex")); }; let elResizer = document.createElement("span"); @@ -1030,7 +1030,7 @@ function loaded() { } function onStartResize(event) { - startResizeEvent = event; + lastResizeEvent = event; resizeColumnIndex = parseInt(this.getAttribute("columnIndex")); event.stopPropagation(); } @@ -1074,8 +1074,33 @@ function loaded() { entityList.refresh(); } - document.onmousemove = function(ev) { - if (startResizeEvent) { + function swapColumns(columnAIndex, columnBIndex) { + let columnA = columns[columnAIndex]; + let columnB = columns[columnBIndex]; + let columnATh = columns[columnAIndex].elTh; + let columnBTh = columns[columnBIndex].elTh; + let columnThParent = columnATh.parentNode; + columnThParent.removeChild(columnBTh); + columnThParent.insertBefore(columnBTh, columnATh); + columnATh.setAttribute("columnIndex", columnBIndex); + columnBTh.setAttribute("columnIndex", columnAIndex); + columnA.elResizer.setAttribute("columnIndex", columnBIndex); + columnB.elResizer.setAttribute("columnIndex", columnAIndex); + + for (let i = 0; i < visibleEntities.length; ++i) { + let elRow = visibleEntities[i].elRow; + let columnACell = elRow.childNodes[columnAIndex]; + let columnBCell = elRow.childNodes[columnBIndex]; + elRow.removeChild(columnBCell); + elRow.insertBefore(columnBCell, columnACell); + } + + columns[columnAIndex] = columnB; + columns[columnBIndex] = columnA; + } + + document.onmousemove = function(event) { + if (lastResizeEvent) { startTh = null; let column = columns[resizeColumnIndex]; @@ -1087,7 +1112,7 @@ function loaded() { } let fullWidth = elEntityTableBody.offsetWidth; - let dx = ev.clientX - startResizeEvent.clientX; + let dx = event.clientX - lastResizeEvent.clientX; let dPct = dx / fullWidth; let newColWidth = column.width + dPct; @@ -1097,14 +1122,46 @@ function loaded() { column.width += dPct; nextColumn.width -= dPct; updateColumnWidths(); - startResizeEvent = ev; + lastResizeEvent = event; + } + } else if (elTargetTh) { + let dxFromInitial = event.clientX - initialThEvent.clientX; + if (Math.abs(dxFromInitial) >= DELTA_X_MOVE_COLUMNS_THRESHOLD) { + elTargetTh.className = "dragging"; + } + if (targetColumnIndex < columns.length - 1) { + let nextColumnIndex = targetColumnIndex + 1; + let nextColumnTh = columns[nextColumnIndex].elTh; + let nextColumnStartX = nextColumnTh.getBoundingClientRect().left; + if (event.clientX >= nextColumnStartX && event.clientX - lastColumnSwapPosition >= DELTA_X_COLUMN_SWAP_POSITION) { + swapColumns(targetColumnIndex, nextColumnIndex); + targetColumnIndex = nextColumnIndex; + lastColumnSwapPosition = event.clientX; + } + } + if (targetColumnIndex >= 1) { + let prevColumnIndex = targetColumnIndex - 1; + let prevColumnTh = columns[prevColumnIndex].elTh; + let prevColumnEndX = prevColumnTh.getBoundingClientRect().right; + if (event.clientX <= prevColumnEndX && event.clientX - lastColumnSwapPosition >= DELTA_X_COLUMN_SWAP_POSITION) { + swapColumns(prevColumnIndex, targetColumnIndex); + targetColumnIndex = prevColumnIndex; + } } } } - document.onmouseup = function(ev) { - startResizeEvent = null; - ev.stopPropagation(); + document.onmouseup = function(event) { + if (elTargetTh) { + if (elTargetTh.className !== "dragging" && elTargetTh === event.target) { + let columnID = columns[targetColumnIndex].columnID; + setSortColumn(columnID); + } + elTargetTh.className = ""; + } + lastResizeEvent = null; + elTargetTh = null; + initialThEvent = null; } function setSpaceMode(spaceMode) { From a0f79f988194a74f19e82e104241572da7ff7d30 Mon Sep 17 00:00:00 2001 From: unknown Date: Fri, 16 Nov 2018 17:14:30 -0800 Subject: [PATCH 02/33] entity list reordering columns --- scripts/system/html/js/entityList.js | 68 ++++++++++++++++++---------- scripts/system/html/js/listView.js | 4 -- 2 files changed, 44 insertions(+), 28 deletions(-) diff --git a/scripts/system/html/js/entityList.js b/scripts/system/html/js/entityList.js index e1581b7877..963611f059 100644 --- a/scripts/system/html/js/entityList.js +++ b/scripts/system/html/js/entityList.js @@ -110,8 +110,8 @@ const COLUMNS = { }; const COMPARE_ASCENDING = function(a, b) { - let va = a[currentSortColumn]; - let vb = b[currentSortColumn]; + let va = a[currentSortColumnID]; + let vb = b[currentSortColumnID]; if (va < vb) { return -1; @@ -171,7 +171,7 @@ let entityList = null; // The ListView */ let entityListContextMenu = null; -let currentSortColumn = 'type'; +let currentSortColumnID = 'type'; let currentSortOrder = ASCENDING_SORT; let elSortOrders = {}; let typeFilters = []; @@ -182,6 +182,7 @@ let columnsByID = {}; let lastResizeEvent = null; let resizeColumnIndex = 0; let elTargetTh = null; +let elTargetSpan = null; let targetColumnIndex = 0; let lastColumnSwapPosition = -1; let initialThEvent = null; @@ -226,10 +227,6 @@ const PROFILE = !ENABLE_PROFILING ? PROFILE_NOOP : function(name, fn, args) { console.log("PROFILE-Web " + profileIndent + "(" + name + ") End " + delta + "ms"); }; -debugPrint = function (message) { - console.log(message); -}; - function loaded() { openEventBridge(function() { elEntityTable = document.getElementById("entity-table"); @@ -320,10 +317,11 @@ function loaded() { for (let columnID in COLUMNS) { let columnData = COLUMNS[columnID]; - let thID = "entity-" + columnID; let elTh = document.createElement("th"); + let thID = "entity-" + columnID; elTh.setAttribute("id", thID); elTh.setAttribute("columnIndex", columnIndex); + elTh.setAttribute("columnID", columnID); if (columnData.glyph) { let elGlyph = document.createElement("span"); elGlyph.className = "glyph"; @@ -333,15 +331,19 @@ function loaded() { elTh.innerText = columnData.columnHeader; } elTh.onmousedown = function(event) { - elTargetTh = event.target; + if (event.target.nodeName === 'TH') { + elTargetTh = event.target; + targetColumnIndex = parseInt(elTargetTh.getAttribute("columnIndex")); + lastColumnSwapPosition = event.clientX; + } else if (event.target.nodeName === 'SPAN') { + elTargetSpan = event.target; + } initialThEvent = event; - targetColumnIndex = parseInt(elTargetTh.getAttribute("columnIndex")); }; let elResizer = document.createElement("span"); elResizer.className = "resizer"; elResizer.innerHTML = " "; - elResizer.setAttribute("columnIndex", columnIndex); elResizer.onmousedown = onStartResize; elTh.appendChild(elResizer); @@ -699,13 +701,13 @@ function loaded() { refreshNoEntitiesMessage(); } - function setSortColumn(column) { + function setSortColumn(columnID) { PROFILE("set-sort-column", function() { - if (currentSortColumn === column) { + if (currentSortColumnID === columnID) { currentSortOrder *= -1; } else { - elSortOrders[currentSortColumn].innerHTML = ""; - currentSortColumn = column; + elSortOrders[currentSortColumnID].innerHTML = ""; + currentSortColumnID = columnID; currentSortOrder = ASCENDING_SORT; } refreshSortOrder(); @@ -714,7 +716,7 @@ function loaded() { } function refreshSortOrder() { - elSortOrders[currentSortColumn].innerHTML = currentSortOrder === ASCENDING_SORT ? ASCENDING_STRING : DESCENDING_STRING; + elSortOrders[currentSortColumnID].innerHTML = currentSortOrder === ASCENDING_SORT ? ASCENDING_STRING : DESCENDING_STRING; } function refreshEntities() { @@ -1031,7 +1033,7 @@ function loaded() { function onStartResize(event) { lastResizeEvent = event; - resizeColumnIndex = parseInt(this.getAttribute("columnIndex")); + resizeColumnIndex = parseInt(this.parentNode.getAttribute("columnIndex")); event.stopPropagation(); } @@ -1089,14 +1091,18 @@ function loaded() { for (let i = 0; i < visibleEntities.length; ++i) { let elRow = visibleEntities[i].elRow; - let columnACell = elRow.childNodes[columnAIndex]; - let columnBCell = elRow.childNodes[columnBIndex]; - elRow.removeChild(columnBCell); - elRow.insertBefore(columnBCell, columnACell); + if (elRow) { + let columnACell = elRow.childNodes[columnAIndex]; + let columnBCell = elRow.childNodes[columnBIndex]; + elRow.removeChild(columnBCell); + elRow.insertBefore(columnBCell, columnACell); + } } columns[columnAIndex] = columnB; columns[columnBIndex] = columnA; + + updateColumnWidths(); } document.onmousemove = function(event) { @@ -1143,24 +1149,38 @@ function loaded() { let prevColumnIndex = targetColumnIndex - 1; let prevColumnTh = columns[prevColumnIndex].elTh; let prevColumnEndX = prevColumnTh.getBoundingClientRect().right; - if (event.clientX <= prevColumnEndX && event.clientX - lastColumnSwapPosition >= DELTA_X_COLUMN_SWAP_POSITION) { + if (event.clientX <= prevColumnEndX && lastColumnSwapPosition - event.clientX >= DELTA_X_COLUMN_SWAP_POSITION) { swapColumns(prevColumnIndex, targetColumnIndex); - targetColumnIndex = prevColumnIndex; + targetColumnIndex = prevColumnIndex; + lastColumnSwapPosition = event.clientX; } } + } else if (elTargetSpan) { + let dxFromInitial = event.clientX - initialThEvent.clientX; + if (Math.abs(dxFromInitial) >= DELTA_X_MOVE_COLUMNS_THRESHOLD) { + elTargetTh = elTargetSpan.parentNode; + elTargetTh.className = "dragging"; + targetColumnIndex = parseInt(elTargetTh.getAttribute("columnIndex")); + lastColumnSwapPosition = event.clientX; + elTargetSpan = null; + } } } document.onmouseup = function(event) { if (elTargetTh) { if (elTargetTh.className !== "dragging" && elTargetTh === event.target) { - let columnID = columns[targetColumnIndex].columnID; + let columnID = elTargetTh.getAttribute("columnID"); setSortColumn(columnID); } elTargetTh.className = ""; + } else if (elTargetSpan) { + let columnID = elTargetSpan.parentNode.getAttribute("columnID"); + setSortColumn(columnID); } lastResizeEvent = null; elTargetTh = null; + elTargetSpan = null; initialThEvent = null; } diff --git a/scripts/system/html/js/listView.js b/scripts/system/html/js/listView.js index f775a4cb24..77e1ba99b1 100644 --- a/scripts/system/html/js/listView.js +++ b/scripts/system/html/js/listView.js @@ -9,10 +9,6 @@ const SCROLL_ROWS = 2; // number of rows used as scrolling buffer, each time we pass this number of rows we scroll const FIRST_ROW_INDEX = 2; // the first elRow element's index in the child nodes of the table body -debugPrint = function (message) { - console.log(message); -}; - function ListView(elTableBody, elTableScroll, elTableHeaderRow, createRowFunction, updateRowFunction, clearRowFunction, WINDOW_NONVARIABLE_HEIGHT) { this.elTableBody = elTableBody; From a94fec75b47cf1372797521424c7057fe5322373 Mon Sep 17 00:00:00 2001 From: David Back Date: Mon, 19 Nov 2018 17:59:42 -0800 Subject: [PATCH 03/33] prevent drag drop onto edit --- scripts/system/html/entityList.html | 1 + scripts/system/html/entityProperties.html | 1 + scripts/system/html/gridControls.html | 1 + scripts/system/html/js/entityList.js | 3 ++- scripts/system/html/js/entityProperties.js | 1 + scripts/system/html/js/gridControls.js | 1 + scripts/system/html/js/utils.js | 23 ++++++++++++++++++++++ 7 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 scripts/system/html/js/utils.js diff --git a/scripts/system/html/entityList.html b/scripts/system/html/entityList.html index 6915a45f19..f9948bc8b0 100644 --- a/scripts/system/html/entityList.html +++ b/scripts/system/html/entityList.html @@ -18,6 +18,7 @@ + diff --git a/scripts/system/html/entityProperties.html b/scripts/system/html/entityProperties.html index 01395368b0..a8bcabbfea 100644 --- a/scripts/system/html/entityProperties.html +++ b/scripts/system/html/entityProperties.html @@ -23,6 +23,7 @@ + diff --git a/scripts/system/html/gridControls.html b/scripts/system/html/gridControls.html index 4be002619a..8d6ee34bc0 100644 --- a/scripts/system/html/gridControls.html +++ b/scripts/system/html/gridControls.html @@ -16,6 +16,7 @@ + diff --git a/scripts/system/html/js/entityList.js b/scripts/system/html/js/entityList.js index 84ad59df36..eb3e9ed882 100644 --- a/scripts/system/html/js/entityList.js +++ b/scripts/system/html/js/entityList.js @@ -1286,8 +1286,9 @@ function loaded() { }); augmentSpinButtons(); + disableDragDrop(); - document.addEventListener("contextmenu", function (event) { + document.addEventListener("contextmenu", function(event) { entityListContextMenu.close(); // Disable default right-click context menu which is not visible in the HMD and makes it seem like the app has locked diff --git a/scripts/system/html/js/entityProperties.js b/scripts/system/html/js/entityProperties.js index d3e0751732..6dc563b102 100644 --- a/scripts/system/html/js/entityProperties.js +++ b/scripts/system/html/js/entityProperties.js @@ -3479,6 +3479,7 @@ function loaded() { }); augmentSpinButtons(); + disableDragDrop(); // Disable right-click context menu which is not visible in the HMD and makes it seem like the app has locked document.addEventListener("contextmenu", function(event) { diff --git a/scripts/system/html/js/gridControls.js b/scripts/system/html/js/gridControls.js index b2d5988938..c2183130e9 100644 --- a/scripts/system/html/js/gridControls.js +++ b/scripts/system/html/js/gridControls.js @@ -108,6 +108,7 @@ function loaded() { }); augmentSpinButtons(); + disableDragDrop(); EventBridge.emitWebEvent(JSON.stringify({ type: 'init' })); }); diff --git a/scripts/system/html/js/utils.js b/scripts/system/html/js/utils.js new file mode 100644 index 0000000000..fe96e8b79e --- /dev/null +++ b/scripts/system/html/js/utils.js @@ -0,0 +1,23 @@ +// +// utils.js +// +// Created by David Back on 19 Nov 2018 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +function disableDragDrop() { + document.addEventListener("drop", function(event) { + event.preventDefault(); + event.dataTransfer.effectAllowed = "none"; + event.dataTransfer.dropEffect = "none"; + }, false); + + document.addEventListener("dragover", function(event) { + event.preventDefault(); + event.dataTransfer.effectAllowed = "none"; + event.dataTransfer.dropEffect = "none"; + }, false); +} From b0aa1b2af28bf573ba27c07793f45d3c54512c25 Mon Sep 17 00:00:00 2001 From: birarda Date: Tue, 20 Nov 2018 11:54:57 -0800 Subject: [PATCH 04/33] make domain settings maps keyed case insensitively --- .../resources/web/js/base-settings.js | 8 ++-- domain-server/src/DomainServer.cpp | 38 ++++++++++++------- 2 files changed, 29 insertions(+), 17 deletions(-) diff --git a/domain-server/resources/web/js/base-settings.js b/domain-server/resources/web/js/base-settings.js index fd404aff20..bd96f636a8 100644 --- a/domain-server/resources/web/js/base-settings.js +++ b/domain-server/resources/web/js/base-settings.js @@ -364,7 +364,7 @@ function validateInputs() { if (keyVal.length === 0) { empty = true - markParentRowInvalid(input); + markParentRowInvalid(input) return; } @@ -373,11 +373,13 @@ function validateInputs() { _.each(otherKeys, function(otherKeyCell) { var keyInput = $(otherKeyCell).children('input'); + var lowerNewValue = keyVal.toLowerCase(); + if (keyInput.length) { - if ($(keyInput).val() == keyVal) { + if ($(keyInput).val().toLowerCase() == lowerNewValue) { duplicateKey = true; } - } else if ($(otherKeyCell).html() == keyVal) { + } else if ($(otherKeyCell).html().toLowerCase() == lowerNewValue) { duplicateKey = true; } diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 69f16af8b3..258038b8f1 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -3172,24 +3172,34 @@ void DomainServer::processPathQueryPacket(QSharedPointer messag const QString PATH_VIEWPOINT_KEY = "viewpoint"; const QString INDEX_PATH = "/"; - // check out paths in the _configMap to see if we have a match - auto keypath = QString(PATHS_SETTINGS_KEYPATH_FORMAT).arg(SETTINGS_PATHS_KEY).arg(pathQuery); - QVariant pathMatch = _settingsManager.valueForKeyPath(keypath); + QString responseViewpoint; - if (pathMatch.isValid() || pathQuery == INDEX_PATH) { + // check out paths in the _configMap to see if we have a match + auto pathsVariant = _settingsManager.valueForKeyPath(SETTINGS_PATHS_KEY); + + auto lowerPathQuery = pathQuery.toLower(); + + if (pathsVariant.canConvert()) { + auto pathsMap = pathsVariant.toMap(); + + // enumerate the paths and look case-insensitively for a matching one + for (auto it = pathsMap.constKeyValueBegin(); it != pathsMap.constKeyValueEnd(); ++it) { + if ((*it).first.toLower() == lowerPathQuery) { + responseViewpoint = (*it).second.toMap()[PATH_VIEWPOINT_KEY].toString().toLower(); + break; + } + } + } + + if (responseViewpoint.isEmpty() && pathQuery == INDEX_PATH) { + const QString DEFAULT_INDEX_PATH = "/0,0,0/0,0,0,1"; + responseViewpoint = DEFAULT_INDEX_PATH; + } + + if (!responseViewpoint.isEmpty()) { // we got a match, respond with the resulting viewpoint auto nodeList = DependencyManager::get(); - QString responseViewpoint; - - // if we didn't match the path BUT this is for the index path then send back our default - if (pathMatch.isValid()) { - responseViewpoint = pathMatch.toMap()[PATH_VIEWPOINT_KEY].toString(); - } else { - const QString DEFAULT_INDEX_PATH = "/0,0,0/0,0,0,1"; - responseViewpoint = DEFAULT_INDEX_PATH; - } - if (!responseViewpoint.isEmpty()) { QByteArray viewpointUTF8 = responseViewpoint.toUtf8(); From b2bd0d99de4b05f41c5f66b1f819c713437a0df0 Mon Sep 17 00:00:00 2001 From: birarda Date: Tue, 20 Nov 2018 16:07:48 -0800 Subject: [PATCH 05/33] add a warning for cloud domain ID changes --- .../resources/web/settings/js/settings.js | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/domain-server/resources/web/settings/js/settings.js b/domain-server/resources/web/settings/js/settings.js index 3888277c00..2950b8de75 100644 --- a/domain-server/resources/web/settings/js/settings.js +++ b/domain-server/resources/web/settings/js/settings.js @@ -18,9 +18,6 @@ $(document).ready(function(){ Settings.extraGroupsAtIndex = Settings.extraDomainGroupsAtIndex; Settings.afterReloadActions = function() { - // append the domain selection modal - appendDomainIDButtons(); - // call our method to setup the HF account button setupHFAccountButton(); @@ -52,6 +49,11 @@ $(document).ready(function(){ if (cloudWizardExit != undefined) { $('#cloud-domains-alert').show(); } + + $(Settings.DOMAIN_ID_SELECTOR).siblings('span').append("
Changing the domain ID for a Cloud Domain may result in an incorrect status for the domain on your Cloud Domains page."); + } else { + // append the domain selection modal + appendDomainIDButtons(); } handleAction(); @@ -59,9 +61,9 @@ $(document).ready(function(){ Settings.handlePostSettings = function(formJSON) { - if (!verifyAvatarHeights()) { - return false; - } + if (!verifyAvatarHeights()) { + return false; + } // check if we've set the basic http password if (formJSON["security"]) { From e193dcbc602597f823006a3d2704ac6e838a8c4b Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Tue, 27 Nov 2018 18:59:58 +0100 Subject: [PATCH 06/33] - add missing start/middle/finish tooltips - style updates --- .../system/assets/data/createAppTooltips.json | 64 +++++++++++++++++-- scripts/system/html/css/edit-style.css | 5 +- scripts/system/html/js/entityProperties.js | 11 +++- 3 files changed, 73 insertions(+), 7 deletions(-) diff --git a/scripts/system/assets/data/createAppTooltips.json b/scripts/system/assets/data/createAppTooltips.json index 720d4537ee..b3df0fa13b 100644 --- a/scripts/system/assets/data/createAppTooltips.json +++ b/scripts/system/assets/data/createAppTooltips.json @@ -193,22 +193,52 @@ "emitterShouldTrail": { "tooltip": "If enabled, then particles are \"left behind\" as the emitter moves, otherwise they are not." }, + "particleRadiusTriple": { + "tooltip": "The size of each particle.", + "jsPropertyName": "particleRadius" + }, "particleRadius": { "tooltip": "The size of each particle." }, + "radiusStart": { + "tooltip": "The start size of each particle." + }, + "radiusFinish": { + "tooltip": "The finish size of each particle." + }, "radiusSpread": { "tooltip": "The spread in size that each particle is given, resulting in a variety of sizes." }, + "particleColorTriple": { + "tooltip": "The color of each particle.", + "jsPropertyName": "color" + }, "particleColor": { "tooltip": "The color of each particle.", "jsPropertyName": "color" }, + "colorStart": { + "tooltip": "The start color of each particle." + }, + "colorFinish": { + "tooltip": "The finish color of each particle." + }, "colorSpread": { "tooltip": "The spread in color that each particle is given, resulting in a variety of colors." }, + "particleAlphaTriple": { + "tooltip": "The alpha of each particle.", + "jsPropertyName": "alpha" + }, "alpha": { "tooltip": "The alpha of each particle." }, + "alphaStart": { + "tooltip": "The start alpha of each particle." + }, + "alphaFinish": { + "tooltip": "The finish alpha of each particle." + }, "alphaSpread": { "tooltip": "The spread in alpha that each particle is given, resulting in a variety of alphas." }, @@ -218,20 +248,44 @@ "accelerationSpread": { "tooltip": "The spread in accelerations that each particle is given, resulting in a variety of accelerations." }, + "particleSpinTriple": { + "tooltip": "The spin of each particle.", + "jsPropertyName": "particleSpin" + }, "particleSpin": { - "tooltip": "The spin of each particle in the system." + "tooltip": "The spin of each particle." + }, + "spinStart": { + "tooltip": "The start spin of each particle." + }, + "spinFinish": { + "tooltip": "The finish spin of each particle." }, "spinSpread": { "tooltip": "The spread in spin that each particle is given, resulting in a variety of spins." }, "rotateWithEntity": { - "tooltip": "If enabled, each particle will spin relative to the roation of the entity as a whole." + "tooltip": "If enabled, each particle will spin relative to the rotation of the entity as a whole." + }, + "particlePolarTriple": { + "tooltip": "The angle range in deg at which particles are emitted. Starts in the entity's -z direction, and rotates around its y axis.", + "skipJSProperty": true }, "polarStart": { - "tooltip": "The angle in deg at which particles are emitted. Starts in the entity's -z direction, and rotates around its y axis." + "tooltip": "The start angle in deg at which particles are emitted. Starts in the entity's -z direction, and rotates around its y axis." + }, + "polarFinish": { + "tooltip": "The finish angle in deg at which particles are emitted. Starts in the entity's -z direction, and rotates around its y axis." + }, + "particleAzimuthTriple": { + "tooltip": "The angle range in deg at which particles are emitted. Starts in the entity's -x direction, and rotates around its z axis.", + "skipJSProperty": true }, "azimuthStart": { - "tooltip": "The angle in deg at which particles are emitted. Starts in the entity's -z direction, and rotates around its y axis." + "tooltip": "The start angle in deg at which particles are emitted. Starts in the entity's -x direction, and rotates around its z axis." + }, + "azimuthFinish": { + "tooltip": "The finish angle in deg at which particles are emitted. Starts in the entity's -x direction, and rotates around its z axis." }, "lightColor": { "tooltip": "The color of the light emitted.", @@ -352,7 +406,7 @@ "tooltip": "The URL of a sound to play when the entity collides with something else." }, "grab.grabbable": { - "tooltip": "If enabled, this entity will allow grabbing input and will be moveable." + "tooltip": "If enabled, this entity will allow grabbing input and will be movable." }, "grab.triggerable": { "tooltip": "If enabled, the collider on this entity is used for triggering events." diff --git a/scripts/system/html/css/edit-style.css b/scripts/system/html/css/edit-style.css index 5b5c9e057c..8c21965152 100644 --- a/scripts/system/html/css/edit-style.css +++ b/scripts/system/html/css/edit-style.css @@ -599,6 +599,7 @@ div.section[collapsed="true"], div.section[collapsed="true"] > .section-header { .triple-label { text-transform: uppercase; padding: 6px 0; + cursor: default; } .triple-item { @@ -1507,6 +1508,7 @@ input.rename-entity { } .create-app-tooltip { + z-index: 100; position: absolute; background: #6a6a6a; border: 1px solid black; @@ -1644,6 +1646,7 @@ input.number-slider { font-family: Raleway-Light; font-size: 14px; margin: 6px 0; + cursor: default; } #property-name, #property-id { @@ -1656,7 +1659,7 @@ input.number-slider { } #placeholder-property-type { - min-width: 0px; + min-width: 0; } .collapse-icon { diff --git a/scripts/system/html/js/entityProperties.js b/scripts/system/html/js/entityProperties.js index 78e3cd4dc8..60b9c04fbd 100644 --- a/scripts/system/html/js/entityProperties.js +++ b/scripts/system/html/js/entityProperties.js @@ -720,6 +720,7 @@ const GROUPS = [ { type: "triple", label: "Size", + propertyID: "particleRadiusTriple", properties: [ { label: "Start", @@ -771,6 +772,7 @@ const GROUPS = [ { type: "triple", label: "Color", + propertyID: "particleColorTriple", properties: [ { label: "Start", @@ -807,6 +809,7 @@ const GROUPS = [ { type: "triple", label: "Alpha", + propertyID: "particleAlphaTriple", properties: [ { label: "Start", @@ -883,6 +886,7 @@ const GROUPS = [ { type: "triple", label: "Alpha", + propertyID: "particleSpinTriple", properties: [ { label: "Start", @@ -947,6 +951,7 @@ const GROUPS = [ { type: "triple", label: "Horizontal Angle", + propertyID: "particlePolarTriple", properties: [ { label: "Start", @@ -975,6 +980,7 @@ const GROUPS = [ { type: "triple", label: "Vertical Angle", + propertyID: "particleAzimuthTriple", properties: [ { label: "Start", @@ -2886,7 +2892,10 @@ function loaded() { let propertyElementID = "property-" + propertyID; propertyElementID = propertyElementID.replace('.', '-'); - elWrapper.appendChild(createElementFromHTML(`
${innerPropertyData.label}
`)); + let elLabel = createElementFromHTML(`
${innerPropertyData.label}
`); + createAppTooltip.registerTooltipElement(elLabel, propertyID); + + elWrapper.appendChild(elLabel); elProperty.appendChild(elWrapper); let property = createProperty(innerPropertyData, propertyElementID, propertyName, propertyID, elWrapper.childNodes[0]); From ec19d2f119d49093d03a57d4c378d1c00ccb4eab Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Wed, 21 Nov 2018 15:31:47 -0800 Subject: [PATCH 07/33] Fix Create tooltips getting pushed down for tall rows --- scripts/system/html/js/entityProperties.js | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/scripts/system/html/js/entityProperties.js b/scripts/system/html/js/entityProperties.js index 60b9c04fbd..7f269aa7ed 100644 --- a/scripts/system/html/js/entityProperties.js +++ b/scripts/system/html/js/entityProperties.js @@ -2858,23 +2858,20 @@ function loaded() { elGroup.appendChild(elContainer); } - elLabel = document.createElement('label'); - elLabel.setAttribute("for", propertyElementID); + let labelText = propertyData.label !== undefined ? propertyData.label : ""; + let className = ''; if (propertyData.indentedLabel || propertyData.showPropertyRule !== undefined) { - let elSpan = document.createElement('span'); - elSpan.className = 'indented'; - elSpan.innerText = propertyData.label !== undefined ? propertyData.label : ""; - elLabel.appendChild(elSpan); - } else { - elLabel.innerText = propertyData.label !== undefined ? propertyData.label : ""; + className = 'indented'; } + elLabel = createElementFromHTML( + ``); elContainer.appendChild(elLabel); } else { elContainer = document.getElementById(propertyData.replaceID); } if (elLabel) { - createAppTooltip.registerTooltipElement(elLabel, propertyID); + createAppTooltip.registerTooltipElement(elLabel.childNodes[0], propertyID); } let elProperty = createElementFromHTML('
'); From 371c50b2ab90b51f479e3906656a6a8b4d52c31d Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Wed, 28 Nov 2018 11:01:15 -0800 Subject: [PATCH 08/33] Start work on Pay Out UI --- .../commerce/common/sendAsset/SendAsset.qml | 146 +++++++++++++++++- 1 file changed, 139 insertions(+), 7 deletions(-) diff --git a/interface/resources/qml/hifi/commerce/common/sendAsset/SendAsset.qml b/interface/resources/qml/hifi/commerce/common/sendAsset/SendAsset.qml index 2d0bb2d87b..2a2369795e 100644 --- a/interface/resources/qml/hifi/commerce/common/sendAsset/SendAsset.qml +++ b/interface/resources/qml/hifi/commerce/common/sendAsset/SendAsset.qml @@ -269,7 +269,7 @@ Item { RalewaySemiBold { id: sendAssetText; - text: root.assetCertID === "" ? "Send Money To:" : "Gift \"" + root.assetName + "\" To:"; + text: root.assetCertID === "" ? "Send Money To:" : "Send \"" + root.assetName + "\" To:"; // Anchors anchors.top: parent.top; anchors.topMargin: 26; @@ -370,6 +370,50 @@ Item { } } + Item { + id: authorizedScriptButton; + // Anchors + anchors.top: nearbyButton.bottom; + anchors.topMargin: 32; + anchors.horizontalCenter: parent.horizontalCenter; + height: connectionButton.height; + width: connectionButton.width; + + Image { + anchors.top: parent.top; + source: "./images/nearby.svg"; + height: 70; + width: parent.width; + fillMode: Image.PreserveAspectFit; + horizontalAlignment: Image.AlignHCenter; + verticalAlignment: Image.AlignTop; + mipmap: true; + } + + RalewaySemiBold { + text: "Authorized Script"; + // Anchors + anchors.bottom: parent.bottom; + height: 15; + width: parent.width; + // Text size + size: 18; + // Style + color: hifi.colors.baseGray; + horizontalAlignment: Text.AlignHCenter; + } + + MouseArea { + anchors.fill: parent; + onClicked: { + sendAssetStep.referrer = "authorizedScript"; + sendAssetStep.selectedRecipientNodeID = ""; + + root.nextActiveView = "sendAssetStep"; + } + } + } + HifiControlsUit.Button { id: backButton_sendAssetHome; visible: parentAppNavBarHeight === 0; @@ -860,7 +904,7 @@ Item { id: sendAssetStep; z: 996; - property string referrer; // either "connections", "nearby", or "payIn" + property string referrer; // either "connections", "nearby", "payIn", or "authorizedScript" property string selectedRecipientNodeID; property string selectedRecipientDisplayName; property string selectedRecipientUserName; @@ -872,7 +916,8 @@ Item { RalewaySemiBold { id: sendAssetText_sendAssetStep; - text: sendAssetStep.referrer === "payIn" && root.assetCertID !== "" ? "Send \"" + root.assetName + "\":" : + text: ((sendAssetStep.referrer === "payIn" || sendAssetStep.referrer === "authorizedScript") && + root.assetCertID !== "") ? "Send \"" + root.assetName + "\":" : (root.assetCertID === "" ? "Send Money To:" : "Gift \"" + root.assetName + "\" To:"); // Anchors anchors.top: parent.top; @@ -901,12 +946,13 @@ Item { RalewaySemiBold { id: sendToText_sendAssetStep; - text: (root.assetCertID === "" || sendAssetStep.referrer === "payIn") ? "Send to:" : "Gift to:"; + text: sendAssetStep.referrer === "authorizedScript" ? "Script Secret:" : + (root.assetCertID === "" || sendAssetStep.referrer === "payIn") ? "Send to:" : "Gift to:"; // Anchors anchors.top: parent.top; anchors.left: parent.left; anchors.bottom: parent.bottom; - width: 90; + width: paintedWidth; // Text size size: 18; // Style @@ -915,8 +961,10 @@ Item { } RecipientDisplay { + visible: sendAssetStep.referrer !== "authorizedScript"; anchors.top: parent.top; anchors.left: sendToText_sendAssetStep.right; + anchors.leftMargin: 16; anchors.right: changeButton.left; anchors.rightMargin: 12; height: parent.height; @@ -929,6 +977,71 @@ Item { multiLineDisplay: sendAssetStep.referrer === "nearby" || sendAssetStep.referrer === "payIn"; } + Item { + id: scriptSecretContainer; + visible: sendAssetStep.referrer === "authorizedScript"; + anchors.top: parent.top; + anchors.left: sendToText_sendAssetStep.right; + anchors.right: parent.right; + height: parent.height; + + RalewaySemiBold { + id: scriptSecretHelp; + text: "[?]"; + // Anchors + anchors.left: parent.left; + anchors.leftMargin: 8; + anchors.verticalCenter: parent.verticalCenter; + height: 30; + width: paintedWidth; + // Text size + size: 18; + // Style + color: hifi.colors.blueAccent; + MouseArea { + anchors.fill: parent; + hoverEnabled: true; + onEntered: { + parent.color = hifi.colors.blueHighlight; + } + onExited: { + parent.color = hifi.colors.blueAccent; + } + onClicked: { + lightboxPopup.titleText = "Script Secret"; + lightboxPopup.bodyText = "This alphanumeric text string will be used to ensure " + + "that only your scripts have access to the asset that you are sending. Keep it private!"; + lightboxPopup.button1text = "CLOSE"; + lightboxPopup.button1method = function() { + lightboxPopup.visible = false; + } + lightboxPopup.visible = true; + } + } + } + + HifiControlsUit.TextField { + id: scriptSecretTextField; + text: generateRandomSecret(); + colorScheme: root.assetCertID === "" ? hifi.colorSchemes.dark : hifi.colorSchemes.light; + // Anchors + anchors.verticalCenter: parent.verticalCenter; + anchors.left: scriptSecretHelp.right; + anchors.leftMargin: 16; + anchors.right: parent.right; + height: 50; + // Style + activeFocusOnPress: true; + activeFocusOnTab: true; + + validator: RegExpValidator { regExp: /^[a-zA-Z0-9]+$/ } + + onAccepted: { + optionalMessage.focus = true; + } + } + } + // "CHANGE" button HifiControlsUit.Button { id: changeButton; @@ -939,7 +1052,7 @@ Item { height: 35; width: 100; text: "CHANGE"; - visible: sendAssetStep.referrer !== "payIn"; + visible: sendAssetStep.referrer !== "payIn" && sendAssetStep.referrer !== "authorizedScript"; onClicked: { if (sendAssetStep.referrer === "connections") { root.nextActiveView = "chooseRecipientConnection"; @@ -1263,6 +1376,8 @@ Item { root.assetCertID, parseInt(amountTextField.text), optionalMessage.text); + } else if (sendAssetStep.referrer === "authorizedScript") { + console.log("ZRF HERE: SENDING TO AUTHORIZED SCRIPT"); } } } @@ -1867,6 +1982,17 @@ Item { sendAssetStep.referrer = ""; } + function generateRandomSecret() { + var randomSecret = ""; + var possibleCharacters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + + for (var i = 0; i < 10; i++) { + randomSecret += possibleCharacters.charAt(Math.floor(Math.random() * possibleCharacters.length)); + } + + return randomSecret; + } + // // Function Name: fromScript() // @@ -1908,9 +2034,15 @@ Item { sendAssetStep.referrer = "payIn"; sendAssetStep.selectedRecipientNodeID = ""; sendAssetStep.selectedRecipientDisplayName = "Determined by script:"; - sendAssetStep.selectedRecipientUserName = message.username; + sendAssetStep.selectedRecipientUserName = message.username || ""; optionalMessage.text = message.message || "No Message Provided"; + if (sendAssetStep.selectedRecipientUserName === "") { + console.log("SendAsset: Script didn't specify a recipient username!"); + sendAssetHome.visible = false; + return; + } + root.nextActiveView = "sendAssetStep"; break; case 'inspectionCertificate_resetCert': From bc39dbba8b38b80f6023f5e47dd75d4248c6cce6 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Thu, 29 Nov 2018 12:44:30 -0800 Subject: [PATCH 09/33] Finish out the feature --- .../commerce/common/sendAsset/SendAsset.qml | 297 +++++++++++++++--- .../common/sendAsset/images/clipboard.svg | 1 + interface/src/commerce/Ledger.cpp | 16 + interface/src/commerce/Ledger.h | 4 + interface/src/commerce/QmlCommerce.cpp | 16 + interface/src/commerce/QmlCommerce.h | 2 + 6 files changed, 291 insertions(+), 45 deletions(-) create mode 100644 interface/resources/qml/hifi/commerce/common/sendAsset/images/clipboard.svg diff --git a/interface/resources/qml/hifi/commerce/common/sendAsset/SendAsset.qml b/interface/resources/qml/hifi/commerce/common/sendAsset/SendAsset.qml index 2a2369795e..36113077f5 100644 --- a/interface/resources/qml/hifi/commerce/common/sendAsset/SendAsset.qml +++ b/interface/resources/qml/hifi/commerce/common/sendAsset/SendAsset.qml @@ -36,6 +36,8 @@ Item { property bool isCurrentlySendingAsset: false; property string assetName: ""; property string assetCertID: ""; + property string secret: ""; + property string authorizationID: ""; property string sendingPubliclyEffectImage; property var http; property var listModelName; @@ -108,6 +110,27 @@ Item { } } + onAuthorizeAssetTransferResult: { + if (!root.visible) { + return; + } + + root.isCurrentlySendingAsset = false; + + if (result.status === 'success') { + root.authorizationID = result.data.authorization_id; + authorizationIDText.text = root.authorizationID; + root.secret = result.data.secret; + secretText.text = root.secret + if (scriptSecretTextField.text !== root.secret) { + console.log("SendAsset: Returned secret doesn't match client-generated secret!"); + } + root.nextActiveView = 'paymentSuccess'; + } else { + root.nextActiveView = 'paymentFailure'; + } + } + onCertificateInfoResult: { if (result.status !== 'success') { console.log("Failed to get certificate info", result.data.message); @@ -408,6 +431,7 @@ Item { onClicked: { sendAssetStep.referrer = "authorizedScript"; sendAssetStep.selectedRecipientNodeID = ""; + scriptSecretTextField.text = generateRandomSecret(); root.nextActiveView = "sendAssetStep"; } @@ -1022,7 +1046,6 @@ Item { HifiControlsUit.TextField { id: scriptSecretTextField; - text: generateRandomSecret(); colorScheme: root.assetCertID === "" ? hifi.colorSchemes.dark : hifi.colorSchemes.light; // Anchors anchors.verticalCenter: parent.verticalCenter; @@ -1034,8 +1057,6 @@ Item { activeFocusOnPress: true; activeFocusOnTab: true; - validator: RegExpValidator { regExp: /^[a-zA-Z0-9]+$/ } - onAccepted: { optionalMessage.focus = true; } @@ -1377,7 +1398,10 @@ Item { parseInt(amountTextField.text), optionalMessage.text); } else if (sendAssetStep.referrer === "authorizedScript") { - console.log("ZRF HERE: SENDING TO AUTHORIZED SCRIPT"); + Commerce.authorizeAssetTransfer(scriptSecretTextField.text || "", + root.assetCertID, + parseInt(amountTextField.text) || 1, + optionalMessage.text) } } } @@ -1449,18 +1473,24 @@ Item { Rectangle { anchors.top: parent.top; - anchors.topMargin: root.assetCertID === "" || sendAssetStep.referrer === "payIn" ? 15 : 125; + anchors.topMargin: root.assetCertID === "" || sendAssetStep.referrer === "payIn" || + sendAssetStep.referrer === "authorizedScript" ? 15 : 125; anchors.left: parent.left; - anchors.leftMargin: root.assetCertID === "" || sendAssetStep.referrer === "payIn" ? 15 : 50; + anchors.leftMargin: root.assetCertID === "" || sendAssetStep.referrer === "payIn" || + sendAssetStep.referrer === "authorizedScript" ? 15 : 50; anchors.right: parent.right; - anchors.rightMargin: root.assetCertID === "" || sendAssetStep.referrer === "payIn" ? 15 : 50; + anchors.rightMargin: root.assetCertID === "" || sendAssetStep.referrer === "payIn" || + sendAssetStep.referrer === "authorizedScript" ? 15 : 50; anchors.bottom: parent.bottom; - anchors.bottomMargin: root.assetCertID === "" || sendAssetStep.referrer === "payIn" ? 15 : 125; + anchors.bottomMargin: root.assetCertID === "" || sendAssetStep.referrer === "payIn" || + sendAssetStep.referrer === "authorizedScript" ? 15 : 125; color: "#FFFFFF"; RalewaySemiBold { id: paymentSentText; - text: root.assetCertID === "" ? "Payment Sent" : (sendAssetStep.referrer === "payIn" ? "Item Sent" : "Gift Sent"); + text: root.assetCertID === "" ? (sendAssetStep.referrer === "authorizedScript" ? "Payment Authorized" : "Payment Sent") : + (sendAssetStep.referrer === "authorizedScript" ? "Item Transfer Authorized" : + (sendAssetStep.referrer === "payIn" ? "Item Sent" : "Gift Sent")); // Anchors anchors.top: parent.top; anchors.topMargin: 26; @@ -1498,6 +1528,8 @@ Item { onClicked: { if (sendAssetStep.referrer === "payIn") { sendToScript({method: "closeSendAsset"}); + } else if (sendAssetStep.referrer === "authorizedScript") { + showDidYouCopyLightbox(); } else { root.nextActiveView = "sendAssetHome"; resetSendAssetData(); @@ -1517,38 +1549,176 @@ Item { anchors.leftMargin: 20; anchors.right: parent.right; anchors.rightMargin: 20; - height: 80; + height: childrenRect.height; - RalewaySemiBold { - id: sendToText_paymentSuccess; - text: "Sent To:"; - // Anchors + Item { + id: sendToScriptContainer_paymentSuccess; + visible: sendAssetStep.referrer === "authorizedScript"; anchors.top: parent.top; anchors.left: parent.left; - anchors.bottom: parent.bottom; - width: 90; - // Text size - size: 18; - // Style - color: hifi.colors.baseGray; - verticalAlignment: Text.AlignVCenter; - } - - RecipientDisplay { - anchors.top: parent.top; - anchors.left: sendToText_paymentSuccess.right; anchors.right: parent.right; - height: parent.height; - textColor: hifi.colors.blueAccent; + height: childrenRect.height; - displayName: sendAssetStep.selectedRecipientDisplayName; - userName: sendAssetStep.selectedRecipientUserName; - profilePic: sendAssetStep.selectedRecipientProfilePic !== "" ? ((0 === sendAssetStep.selectedRecipientProfilePic.indexOf("http")) ? - sendAssetStep.selectedRecipientProfilePic : (Account.metaverseServerURL + sendAssetStep.selectedRecipientProfilePic)) : ""; - multiLineDisplay: sendAssetStep.referrer === "nearby" || sendAssetStep.referrer === "payIn"; + RalewaySemiBold { + id: authorizationIDLabel; + text: "Authorization ID:"; + // Anchors + anchors.left: parent.left; + anchors.top: authorizationIDText.top; + width: paintedWidth; + // Text size + size: 18; + // Style + color: hifi.colors.baseGray; + verticalAlignment: Text.AlignVCenter; + } + + RalewayRegular { + id: authorizationIDText; + text: root.authorizationID; + anchors.top: parent.top; + anchors.left: authorizationIDLabel.right; + anchors.leftMargin: 16; + anchors.right: authorizationIDClipboardButton.left; + anchors.rightMargin: 16; + // Text size + size: 18; + // Style + color: hifi.colors.baseGray; + horizontalAlignment: Text.AlignHCenter; + verticalAlignment: Text.AlignVCenter; + wrapMode: Text.WrapAnywhere; + } + + Image { + id: authorizationIDClipboardButton; + source: "images/clipboard.svg"; // clipboard by Bieutuong Bon from the Noun Project + fillMode: Image.PreserveAspectFit; + // Anchors + anchors.right: parent.right; + anchors.top: authorizationIDText.top; + height: 40; + width: height; + + MouseArea { + anchors.fill: parent; + onClicked: { + Window.copyToClipboard(root.authorizationID); + authorizationIDText.text = "Copied to Clipboard!\n"; + authorizationIDClipboardTimer.start(); + } + } + } + + Timer { + id: authorizationIDClipboardTimer; + interval: 2000; + repeat: false; + onTriggered: { + authorizationIDText.text = root.authorizationID; + } + } + + RalewaySemiBold { + id: secretLabel; + text: "Secret:"; + // Anchors + anchors.left: parent.left; + anchors.top: secretText.top; + width: authorizationIDLabel.width; + // Text size + size: 18; + // Style + color: hifi.colors.baseGray; + verticalAlignment: Text.AlignVCenter; + } + + RalewayRegular { + id: secretText; + text: root.secret; + anchors.top: authorizationIDText.bottom; + anchors.topMargin: 16; + anchors.left: secretLabel.right; + anchors.leftMargin: 16; + anchors.right: secretClipboardButton.left; + anchors.rightMargin: 16; + // Text size + size: 18; + // Style + color: hifi.colors.baseGray; + horizontalAlignment: Text.AlignHCenter; + verticalAlignment: Text.AlignVCenter; + wrapMode: Text.WrapAnywhere; + } + + Image { + id: secretClipboardButton; + source: "images/clipboard.svg"; // clipboard by Bieutuong Bon from the Noun Project + fillMode: Image.PreserveAspectFit; + // Anchors + anchors.right: parent.right; + anchors.top: secretText.top; + height: 40; + width: height; + + MouseArea { + anchors.fill: parent; + onClicked: { + Window.copyToClipboard(root.secret); + secretText.text = "Copied to Clipboard!\n"; + secretClipboardTimer.start(); + } + } + } + + Timer { + id: secretClipboardTimer; + interval: 2000; + repeat: false; + onTriggered: { + secretText.text = root.secret; + } + } } - } - + + Item { + id: sendToRecipientContainer_paymentSuccess; + visible: !sendToScriptContainer_paymentSuccess.visible; + anchors.top: parent.top; + anchors.left: parent.left; + anchors.right: parent.right; + height: 80; + + RalewaySemiBold { + id: sendToText_paymentSuccess; + text: "Sent To:"; + // Anchors + anchors.top: parent.top; + anchors.left: parent.left; + anchors.bottom: parent.bottom; + width: 90; + // Text size + size: 18; + // Style + color: hifi.colors.baseGray; + verticalAlignment: Text.AlignVCenter; + } + + RecipientDisplay { + anchors.top: parent.top; + anchors.left: sendToText_paymentSuccess.right; + anchors.right: parent.right; + height: parent.height; + textColor: hifi.colors.blueAccent; + + displayName: sendAssetStep.selectedRecipientDisplayName; + userName: sendAssetStep.selectedRecipientUserName; + profilePic: sendAssetStep.selectedRecipientProfilePic !== "" ? ((0 === sendAssetStep.selectedRecipientProfilePic.indexOf("http")) ? + sendAssetStep.selectedRecipientProfilePic : (Account.metaverseServerURL + sendAssetStep.selectedRecipientProfilePic)) : ""; + multiLineDisplay: sendAssetStep.referrer === "nearby" || sendAssetStep.referrer === "payIn"; + } + } + } Item { id: giftContainer_paymentSuccess; @@ -1563,7 +1733,8 @@ Item { RalewaySemiBold { id: gift_paymentSuccess; - text: sendAssetStep.referrer === "payIn" ? "Item:" : "Gift:"; + text: sendAssetStep.referrer === "payIn" || sendAssetStep.referrer === "authorizedScript" ? + "Item:" : "Gift:"; // Anchors anchors.top: parent.top; anchors.left: parent.left; @@ -1681,6 +1852,8 @@ Item { onClicked: { if (sendAssetStep.referrer === "payIn") { sendToScript({method: "closeSendAsset"}); + } else if (sendAssetStep.referrer === "authorizedScript") { + showDidYouCopyLightbox(); } else { root.nextActiveView = "sendAssetHome"; resetSendAssetData(); @@ -1714,13 +1887,17 @@ Item { Rectangle { anchors.top: parent.top; - anchors.topMargin: root.assetCertID === "" || sendAssetStep.referrer === "payIn" ? 15 : 150; + anchors.topMargin: root.assetCertID === "" || sendAssetStep.referrer === "payIn" || + sendAssetStep.referrer === "authorizedScript" ? 15 : 150; anchors.left: parent.left; - anchors.leftMargin: root.assetCertID === "" || sendAssetStep.referrer === "payIn" ? 15 : 50; + anchors.leftMargin: root.assetCertID === "" || sendAssetStep.referrer === "payIn" || + sendAssetStep.referrer === "authorizedScript" ? 15 : 50; anchors.right: parent.right; - anchors.rightMargin: root.assetCertID === "" || sendAssetStep.referrer === "payIn" ? 15 : 50; + anchors.rightMargin: root.assetCertID === "" || sendAssetStep.referrer === "payIn" || + sendAssetStep.referrer === "authorizedScript" ? 15 : 50; anchors.bottom: parent.bottom; - anchors.bottomMargin: root.assetCertID === "" || sendAssetStep.referrer === "payIn" ? 15 : 300; + anchors.bottomMargin: root.assetCertID === "" || sendAssetStep.referrer === "payIn" || + sendAssetStep.referrer === "authorizedScript" ? 15 : 300; color: "#FFFFFF"; RalewaySemiBold { @@ -1772,8 +1949,9 @@ Item { RalewaySemiBold { id: paymentFailureDetailText; - text: "The recipient you specified was unable to receive your " + - (root.assetCertID === "" ? "payment." : (sendAssetStep.referrer === "payIn" ? "item." : "gift.")); + text: sendAssetStep.referrer === "authorizedScript" ? "The server was unable to handle your request. Please try again later." : + ("The recipient you specified was unable to receive your " + + (root.assetCertID === "" ? "payment." : (sendAssetStep.referrer === "payIn" ? "item." : "gift."))); anchors.top: paymentFailureText.bottom; anchors.topMargin: 20; anchors.left: parent.left; @@ -1791,7 +1969,8 @@ Item { Item { id: sendToContainer_paymentFailure; - visible: root.assetCertID === "" || sendAssetStep.referrer === "payIn"; + visible: (root.assetCertID === "" || sendAssetStep.referrer === "payIn") && + sendAssetStep.referrer !== "authorizedScript"; anchors.top: paymentFailureDetailText.bottom; anchors.topMargin: 8; anchors.left: parent.left; @@ -1833,7 +2012,8 @@ Item { Item { id: amountContainer_paymentFailure; visible: root.assetCertID === ""; - anchors.top: sendToContainer_paymentFailure.bottom; + anchors.top: sendToContainer_paymentFailure.visible ? + sendToContainer_paymentFailure.bottom : paymentFailureDetailText.bottom; anchors.topMargin: 16; anchors.left: parent.left; anchors.leftMargin: 20; @@ -1954,6 +2134,11 @@ Item { root.assetCertID, parseInt(amountTextField.text), optionalMessage.text); + } else if (sendAssetStep.referrer === "authorizedScript") { + Commerce.authorizeAssetTransfer(scriptSecretTextField.text || "", + root.assetCertID, + parseInt(amountTextField.text) || 1, + optionalMessage.text) } } } @@ -1983,16 +2168,38 @@ Item { } function generateRandomSecret() { + var RANDOM_SECRET_LENGTH = 25; var randomSecret = ""; var possibleCharacters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; - for (var i = 0; i < 10; i++) { + for (var i = 0; i < RANDOM_SECRET_LENGTH; i++) { randomSecret += possibleCharacters.charAt(Math.floor(Math.random() * possibleCharacters.length)); } return randomSecret; } + function showDidYouCopyLightbox() { + lightboxPopup.titleText = "Close Confirmation"; + lightboxPopup.bodyText = "Did you copy your Authorization ID and your Script Secret?\n\n" + + "You won't be able to see your Authorization ID or your Script Secret once " + + "you close this window."; + lightboxPopup.button1text = "GO BACK"; + lightboxPopup.button1method = function() { + lightboxPopup.visible = false; + } + lightboxPopup.button2text = "I'M ALL SET"; + lightboxPopup.button2method = function() { + lightboxPopup.visible = false; + root.nextActiveView = "sendAssetHome"; + resetSendAssetData(); + if (root.assetName !== "") { + sendSignalToParent({method: "closeSendAsset"}); + } + } + lightboxPopup.visible = true; + } + // // Function Name: fromScript() // diff --git a/interface/resources/qml/hifi/commerce/common/sendAsset/images/clipboard.svg b/interface/resources/qml/hifi/commerce/common/sendAsset/images/clipboard.svg new file mode 100644 index 0000000000..798fdaaab1 --- /dev/null +++ b/interface/resources/qml/hifi/commerce/common/sendAsset/images/clipboard.svg @@ -0,0 +1 @@ +Created by Bieutuong Bonfrom the Noun Project \ No newline at end of file diff --git a/interface/src/commerce/Ledger.cpp b/interface/src/commerce/Ledger.cpp index b10c9647a0..fb177ddc82 100644 --- a/interface/src/commerce/Ledger.cpp +++ b/interface/src/commerce/Ledger.cpp @@ -63,6 +63,7 @@ Handler(balance) Handler(inventory) Handler(transferAssetToNode) Handler(transferAssetToUsername) +Handler(authorizeAssetTransfer) Handler(alreadyOwned) Handler(availableUpdates) Handler(updateItem) @@ -428,6 +429,7 @@ void Ledger::transferAssetToUsername(const QString& hfc_key, const QString& user transaction["username"] = username; transaction["quantity"] = amount; transaction["message"] = optionalMessage; + transaction["place_name"] = DependencyManager::get()->getPlaceName(); if (!certificateID.isEmpty()) { transaction["certificate_id"] = certificateID; } @@ -440,6 +442,20 @@ void Ledger::transferAssetToUsername(const QString& hfc_key, const QString& user } } +void Ledger::authorizeAssetTransfer(const QString& hfc_key, const QString& secret, const QString& certificateID, const int& amount, const QString& optionalMessage) { + QJsonObject transaction; + transaction["public_key"] = hfc_key; + transaction["secret"] = secret; + transaction["quantity"] = amount; + transaction["message"] = optionalMessage; + if (!certificateID.isEmpty()) { + transaction["certificate_id"] = certificateID; + } + QJsonDocument transactionDoc{ transaction }; + auto transactionString = transactionDoc.toJson(QJsonDocument::Compact); + signedSend("transaction", transactionString, hfc_key, "authorize", "authorizeAssetTransferSuccess", "authorizeAssetTransferFailure"); +} + void Ledger::alreadyOwned(const QString& marketplaceId) { auto wallet = DependencyManager::get(); QString endpoint = "already_owned"; diff --git a/interface/src/commerce/Ledger.h b/interface/src/commerce/Ledger.h index 715d6337ad..700cbe2c4b 100644 --- a/interface/src/commerce/Ledger.h +++ b/interface/src/commerce/Ledger.h @@ -36,6 +36,7 @@ public: void certificateInfo(const QString& certificateId); void transferAssetToNode(const QString& hfc_key, const QString& nodeID, const QString& certificateID, const int& amount, const QString& optionalMessage); void transferAssetToUsername(const QString& hfc_key, const QString& username, const QString& certificateID, const int& amount, const QString& optionalMessage); + void authorizeAssetTransfer(const QString& hfc_key, const QString& secret, const QString& certificateID, const int& amount, const QString& optionalMessage); void alreadyOwned(const QString& marketplaceId); void getAvailableUpdates(const QString& itemId = "", const int& pageNumber = 1, const int& itemsPerPage = 10); void updateItem(const QString& hfc_key, const QString& certificate_id); @@ -59,6 +60,7 @@ signals: void certificateInfoResult(QJsonObject result); void transferAssetToNodeResult(QJsonObject result); void transferAssetToUsernameResult(QJsonObject result); + void authorizeAssetTransferResult(QJsonObject result); void alreadyOwnedResult(QJsonObject result); void availableUpdatesResult(QJsonObject result); void updateItemResult(QJsonObject result); @@ -86,6 +88,8 @@ public slots: void transferAssetToNodeFailure(QNetworkReply* reply); void transferAssetToUsernameSuccess(QNetworkReply* reply); void transferAssetToUsernameFailure(QNetworkReply* reply); + void authorizeAssetTransferSuccess(QNetworkReply* reply); + void authorizeAssetTransferFailure(QNetworkReply* reply); void alreadyOwnedSuccess(QNetworkReply* reply); void alreadyOwnedFailure(QNetworkReply* reply); void availableUpdatesSuccess(QNetworkReply* reply); diff --git a/interface/src/commerce/QmlCommerce.cpp b/interface/src/commerce/QmlCommerce.cpp index 566f7ba324..aab053484b 100644 --- a/interface/src/commerce/QmlCommerce.cpp +++ b/interface/src/commerce/QmlCommerce.cpp @@ -38,6 +38,7 @@ QmlCommerce::QmlCommerce() { connect(ledger.data(), &Ledger::updateCertificateStatus, this, &QmlCommerce::updateCertificateStatus); connect(ledger.data(), &Ledger::transferAssetToNodeResult, this, &QmlCommerce::transferAssetToNodeResult); connect(ledger.data(), &Ledger::transferAssetToUsernameResult, this, &QmlCommerce::transferAssetToUsernameResult); + connect(ledger.data(), &Ledger::authorizeAssetTransferResult, this, &QmlCommerce::authorizeAssetTransferResult); connect(ledger.data(), &Ledger::availableUpdatesResult, this, &QmlCommerce::availableUpdatesResult); connect(ledger.data(), &Ledger::updateItemResult, this, &QmlCommerce::updateItemResult); @@ -246,6 +247,21 @@ void QmlCommerce::transferAssetToUsername(const QString& username, ledger->transferAssetToUsername(key, username, certificateID, amount, optionalMessage); } +void QmlCommerce::authorizeAssetTransfer(const QString& secret, + const QString& certificateID, + const int& amount, + const QString& optionalMessage) { + auto ledger = DependencyManager::get(); + auto wallet = DependencyManager::get(); + QStringList keys = wallet->listPublicKeys(); + if (keys.count() == 0) { + QJsonObject result{ { "status", "fail" }, { "message", "Uninitialized Wallet." } }; + return emit authorizeAssetTransferResult(result); + } + QString key = keys[0]; + ledger->authorizeAssetTransfer(key, secret, certificateID, amount, optionalMessage); +} + void QmlCommerce::replaceContentSet(const QString& itemHref, const QString& certificateID) { if (!certificateID.isEmpty()) { auto ledger = DependencyManager::get(); diff --git a/interface/src/commerce/QmlCommerce.h b/interface/src/commerce/QmlCommerce.h index c5fbdaf4a4..e22b540624 100644 --- a/interface/src/commerce/QmlCommerce.h +++ b/interface/src/commerce/QmlCommerce.h @@ -51,6 +51,7 @@ signals: void transferAssetToNodeResult(QJsonObject result); void transferAssetToUsernameResult(QJsonObject result); + void authorizeAssetTransferResult(QJsonObject result); void contentSetChanged(const QString& contentSetHref); @@ -84,6 +85,7 @@ protected: Q_INVOKABLE void transferAssetToNode(const QString& nodeID, const QString& certificateID, const int& amount, const QString& optionalMessage); Q_INVOKABLE void transferAssetToUsername(const QString& username, const QString& certificateID, const int& amount, const QString& optionalMessage); + Q_INVOKABLE void authorizeAssetTransfer(const QString& secret, const QString& certificateID, const int& amount, const QString& optionalMessage); Q_INVOKABLE void replaceContentSet(const QString& itemHref, const QString& certificateID); From c959dd7024d0a7f4ef841b6884fd945ed3ccaaab Mon Sep 17 00:00:00 2001 From: birarda Date: Mon, 3 Dec 2018 14:56:46 -0800 Subject: [PATCH 10/33] release avatar entity and traits locks sooner to avoid deadlock --- libraries/avatars/src/AvatarData.cpp | 38 ++++++++++++------- libraries/avatars/src/ClientTraitsHandler.cpp | 17 ++++++--- 2 files changed, 36 insertions(+), 19 deletions(-) diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index d9d4b57c31..d4357fac49 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -2832,35 +2832,47 @@ void AvatarData::setAvatarEntityData(const AvatarEntityMap& avatarEntityData) { qCDebug(avatars) << "discard suspect AvatarEntityData with size =" << avatarEntityData.size(); return; } + + std::vector deletedEntityIDs; + QList updatedEntityIDs; + _avatarEntitiesLock.withWriteLock([&] { if (_avatarEntityData != avatarEntityData) { + // keep track of entities that were attached to this avatar but no longer are AvatarEntityIDs previousAvatarEntityIDs = QSet::fromList(_avatarEntityData.keys()); _avatarEntityData = avatarEntityData; setAvatarEntityDataChanged(true); + deletedEntityIDs.reserve(previousAvatarEntityIDs.size()); + foreach (auto entityID, previousAvatarEntityIDs) { if (!_avatarEntityData.contains(entityID)) { _avatarEntityDetached.insert(entityID); - - if (_clientTraitsHandler) { - // we have a client traits handler, so we flag this removed entity as deleted - // so that changes are sent next frame - _clientTraitsHandler->markInstancedTraitDeleted(AvatarTraits::AvatarEntity, entityID); - } + deletedEntityIDs.push_back(entityID); } } - if (_clientTraitsHandler) { - // if we have a client traits handler, flag any updated or created entities - // so that we send changes for them next frame - foreach (auto entityID, _avatarEntityData.keys()) { - _clientTraitsHandler->markInstancedTraitUpdated(AvatarTraits::AvatarEntity, entityID); - } - } + updatedEntityIDs = _avatarEntityData.keys(); } }); + + if (_clientTraitsHandler) { + // we have a client traits handler + + // flag removed entities as deleted so that changes are sent next frame + for (auto& deletedEntityID : deletedEntityIDs) { + _clientTraitsHandler->markInstancedTraitDeleted(AvatarTraits::AvatarEntity, deletedEntityID); + } + + // flag any updated or created entities so that we send changes for them next frame + for (auto& entityID : updatedEntityIDs) { + _clientTraitsHandler->markInstancedTraitUpdated(AvatarTraits::AvatarEntity, entityID); + } + } + + } AvatarEntityIDs AvatarData::getAndClearRecentlyDetachedIDs() { diff --git a/libraries/avatars/src/ClientTraitsHandler.cpp b/libraries/avatars/src/ClientTraitsHandler.cpp index a301341a8e..cbc8e93745 100644 --- a/libraries/avatars/src/ClientTraitsHandler.cpp +++ b/libraries/avatars/src/ClientTraitsHandler.cpp @@ -66,7 +66,7 @@ void ClientTraitsHandler::resetForNewMixer() { } void ClientTraitsHandler::sendChangedTraitsToMixer() { - Lock lock(_traitLock); + std::unique_lock lock(_traitLock); if (hasChangedTraits() || _shouldPerformInitialSend) { // we have at least one changed trait to send @@ -90,6 +90,14 @@ void ClientTraitsHandler::sendChangedTraitsToMixer() { _traitStatuses.reset(); _hasChangedTraits = false; + // if this was an initial send of all traits, consider it completed + bool initialSend = _shouldPerformInitialSend; + _shouldPerformInitialSend = false; + + // we can release the lock here since we've taken a copy of statuses + // and will setup the packet using the information in the copy + lock.unlock(); + auto simpleIt = traitStatusesCopy.simpleCBegin(); while (simpleIt != traitStatusesCopy.simpleCEnd()) { // because the vector contains all trait types (for access using trait type as index) @@ -111,12 +119,12 @@ void ClientTraitsHandler::sendChangedTraitsToMixer() { auto instancedIt = traitStatusesCopy.instancedCBegin(); while (instancedIt != traitStatusesCopy.instancedCEnd()) { for (auto& instanceIDValuePair : instancedIt->instances) { - if ((_shouldPerformInitialSend && instanceIDValuePair.value != Deleted) + if ((initialSend && instanceIDValuePair.value != Deleted) || instanceIDValuePair.value == Updated) { // this is a changed trait we need to send or we haven't send out trait information yet // ask the owning avatar to pack it _owningAvatar->packTraitInstance(instancedIt->traitType, instanceIDValuePair.id, *traitsPacketList); - } else if (!_shouldPerformInitialSend && instanceIDValuePair.value == Deleted) { + } else if (!initialSend && instanceIDValuePair.value == Deleted) { // pack delete for this trait instance AvatarTraits::packInstancedTraitDelete(instancedIt->traitType, instanceIDValuePair.id, *traitsPacketList); @@ -127,9 +135,6 @@ void ClientTraitsHandler::sendChangedTraitsToMixer() { } nodeList->sendPacketList(std::move(traitsPacketList), *avatarMixer); - - // if this was an initial send of all traits, consider it completed - _shouldPerformInitialSend = false; } } From ab184c5c2b7adadeb42c4e8c390e8b6cc5e69797 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Mon, 3 Dec 2018 15:16:06 -0800 Subject: [PATCH 11/33] Authorized Script to Coupon --- .../commerce/common/sendAsset/SendAsset.qml | 126 +++++++++--------- .../common/sendAsset/images/coupon.svg | 10 ++ 2 files changed, 73 insertions(+), 63 deletions(-) create mode 100644 interface/resources/qml/hifi/commerce/common/sendAsset/images/coupon.svg diff --git a/interface/resources/qml/hifi/commerce/common/sendAsset/SendAsset.qml b/interface/resources/qml/hifi/commerce/common/sendAsset/SendAsset.qml index 36113077f5..7c2116f77b 100644 --- a/interface/resources/qml/hifi/commerce/common/sendAsset/SendAsset.qml +++ b/interface/resources/qml/hifi/commerce/common/sendAsset/SendAsset.qml @@ -36,7 +36,7 @@ Item { property bool isCurrentlySendingAsset: false; property string assetName: ""; property string assetCertID: ""; - property string secret: ""; + property string couponID: ""; property string authorizationID: ""; property string sendingPubliclyEffectImage; property var http; @@ -120,9 +120,9 @@ Item { if (result.status === 'success') { root.authorizationID = result.data.authorization_id; authorizationIDText.text = root.authorizationID; - root.secret = result.data.secret; - secretText.text = root.secret - if (scriptSecretTextField.text !== root.secret) { + root.couponID = result.data.secret; + couponIDText.text = root.couponID + if (couponIDTextField.text !== root.couponID) { console.log("SendAsset: Returned secret doesn't match client-generated secret!"); } root.nextActiveView = 'paymentSuccess'; @@ -394,7 +394,7 @@ Item { } Item { - id: authorizedScriptButton; + id: createCouponButton; // Anchors anchors.top: nearbyButton.bottom; anchors.topMargin: 32; @@ -404,7 +404,7 @@ Item { Image { anchors.top: parent.top; - source: "./images/nearby.svg"; + source: "./images/coupon.svg"; height: 70; width: parent.width; fillMode: Image.PreserveAspectFit; @@ -414,7 +414,7 @@ Item { } RalewaySemiBold { - text: "Authorized Script"; + text: "Create Coupon"; // Anchors anchors.bottom: parent.bottom; height: 15; @@ -429,9 +429,9 @@ Item { MouseArea { anchors.fill: parent; onClicked: { - sendAssetStep.referrer = "authorizedScript"; + sendAssetStep.referrer = "createCoupon"; sendAssetStep.selectedRecipientNodeID = ""; - scriptSecretTextField.text = generateRandomSecret(); + couponIDTextField.text = generateRandomCouponID(); root.nextActiveView = "sendAssetStep"; } @@ -928,7 +928,7 @@ Item { id: sendAssetStep; z: 996; - property string referrer; // either "connections", "nearby", "payIn", or "authorizedScript" + property string referrer; // either "connections", "nearby", "payIn", or "createCoupon" property string selectedRecipientNodeID; property string selectedRecipientDisplayName; property string selectedRecipientUserName; @@ -940,7 +940,7 @@ Item { RalewaySemiBold { id: sendAssetText_sendAssetStep; - text: ((sendAssetStep.referrer === "payIn" || sendAssetStep.referrer === "authorizedScript") && + text: ((sendAssetStep.referrer === "payIn" || sendAssetStep.referrer === "createCoupon") && root.assetCertID !== "") ? "Send \"" + root.assetName + "\":" : (root.assetCertID === "" ? "Send Money To:" : "Gift \"" + root.assetName + "\" To:"); // Anchors @@ -970,7 +970,7 @@ Item { RalewaySemiBold { id: sendToText_sendAssetStep; - text: sendAssetStep.referrer === "authorizedScript" ? "Script Secret:" : + text: sendAssetStep.referrer === "createCoupon" ? "Coupon ID:" : (root.assetCertID === "" || sendAssetStep.referrer === "payIn") ? "Send to:" : "Gift to:"; // Anchors anchors.top: parent.top; @@ -985,7 +985,7 @@ Item { } RecipientDisplay { - visible: sendAssetStep.referrer !== "authorizedScript"; + visible: sendAssetStep.referrer !== "createCoupon"; anchors.top: parent.top; anchors.left: sendToText_sendAssetStep.right; anchors.leftMargin: 16; @@ -1002,15 +1002,15 @@ Item { } Item { - id: scriptSecretContainer; - visible: sendAssetStep.referrer === "authorizedScript"; + id: couponIDContainer; + visible: sendAssetStep.referrer === "createCoupon"; anchors.top: parent.top; anchors.left: sendToText_sendAssetStep.right; anchors.right: parent.right; height: parent.height; RalewaySemiBold { - id: scriptSecretHelp; + id: couponIDHelp; text: "[?]"; // Anchors anchors.left: parent.left; @@ -1032,9 +1032,9 @@ Item { parent.color = hifi.colors.blueAccent; } onClicked: { - lightboxPopup.titleText = "Script Secret"; + lightboxPopup.titleText = "Coupon ID"; lightboxPopup.bodyText = "This alphanumeric text string will be used to ensure " + - "that only your scripts have access to the asset that you are sending. Keep it private!"; + "that only you can redeem the coupon for the asset that you are sending. Keep it private!"; lightboxPopup.button1text = "CLOSE"; lightboxPopup.button1method = function() { lightboxPopup.visible = false; @@ -1045,11 +1045,11 @@ Item { } HifiControlsUit.TextField { - id: scriptSecretTextField; + id: couponIDTextField; colorScheme: root.assetCertID === "" ? hifi.colorSchemes.dark : hifi.colorSchemes.light; // Anchors anchors.verticalCenter: parent.verticalCenter; - anchors.left: scriptSecretHelp.right; + anchors.left: couponIDHelp.right; anchors.leftMargin: 16; anchors.right: parent.right; height: 50; @@ -1073,7 +1073,7 @@ Item { height: 35; width: 100; text: "CHANGE"; - visible: sendAssetStep.referrer !== "payIn" && sendAssetStep.referrer !== "authorizedScript"; + visible: sendAssetStep.referrer !== "payIn" && sendAssetStep.referrer !== "createCoupon"; onClicked: { if (sendAssetStep.referrer === "connections") { root.nextActiveView = "chooseRecipientConnection"; @@ -1397,8 +1397,8 @@ Item { root.assetCertID, parseInt(amountTextField.text), optionalMessage.text); - } else if (sendAssetStep.referrer === "authorizedScript") { - Commerce.authorizeAssetTransfer(scriptSecretTextField.text || "", + } else if (sendAssetStep.referrer === "createCoupon") { + Commerce.authorizeAssetTransfer(couponIDTextField.text || "", root.assetCertID, parseInt(amountTextField.text) || 1, optionalMessage.text) @@ -1474,22 +1474,22 @@ Item { Rectangle { anchors.top: parent.top; anchors.topMargin: root.assetCertID === "" || sendAssetStep.referrer === "payIn" || - sendAssetStep.referrer === "authorizedScript" ? 15 : 125; + sendAssetStep.referrer === "createCoupon" ? 15 : 125; anchors.left: parent.left; anchors.leftMargin: root.assetCertID === "" || sendAssetStep.referrer === "payIn" || - sendAssetStep.referrer === "authorizedScript" ? 15 : 50; + sendAssetStep.referrer === "createCoupon" ? 15 : 50; anchors.right: parent.right; anchors.rightMargin: root.assetCertID === "" || sendAssetStep.referrer === "payIn" || - sendAssetStep.referrer === "authorizedScript" ? 15 : 50; + sendAssetStep.referrer === "createCoupon" ? 15 : 50; anchors.bottom: parent.bottom; anchors.bottomMargin: root.assetCertID === "" || sendAssetStep.referrer === "payIn" || - sendAssetStep.referrer === "authorizedScript" ? 15 : 125; + sendAssetStep.referrer === "createCoupon" ? 15 : 125; color: "#FFFFFF"; RalewaySemiBold { id: paymentSentText; - text: root.assetCertID === "" ? (sendAssetStep.referrer === "authorizedScript" ? "Payment Authorized" : "Payment Sent") : - (sendAssetStep.referrer === "authorizedScript" ? "Item Transfer Authorized" : + text: root.assetCertID === "" ? (sendAssetStep.referrer === "createCoupon" ? "Payment Authorized" : "Payment Sent") : + (sendAssetStep.referrer === "createCoupon" ? "Item Transfer Authorized" : (sendAssetStep.referrer === "payIn" ? "Item Sent" : "Gift Sent")); // Anchors anchors.top: parent.top; @@ -1528,7 +1528,7 @@ Item { onClicked: { if (sendAssetStep.referrer === "payIn") { sendToScript({method: "closeSendAsset"}); - } else if (sendAssetStep.referrer === "authorizedScript") { + } else if (sendAssetStep.referrer === "createCoupon") { showDidYouCopyLightbox(); } else { root.nextActiveView = "sendAssetHome"; @@ -1553,7 +1553,7 @@ Item { Item { id: sendToScriptContainer_paymentSuccess; - visible: sendAssetStep.referrer === "authorizedScript"; + visible: sendAssetStep.referrer === "createCoupon"; anchors.top: parent.top; anchors.left: parent.left; anchors.right: parent.right; @@ -1620,11 +1620,11 @@ Item { } RalewaySemiBold { - id: secretLabel; - text: "Secret:"; + id: couponIDLabel; + text: "Coupon ID:"; // Anchors anchors.left: parent.left; - anchors.top: secretText.top; + anchors.top: couponIDText.top; width: authorizationIDLabel.width; // Text size size: 18; @@ -1634,13 +1634,13 @@ Item { } RalewayRegular { - id: secretText; - text: root.secret; + id: couponIDText; + text: root.couponID; anchors.top: authorizationIDText.bottom; anchors.topMargin: 16; - anchors.left: secretLabel.right; + anchors.left: couponIDLabel.right; anchors.leftMargin: 16; - anchors.right: secretClipboardButton.left; + anchors.right: couponIDClipboardButton.left; anchors.rightMargin: 16; // Text size size: 18; @@ -1652,31 +1652,31 @@ Item { } Image { - id: secretClipboardButton; + id: couponIDClipboardButton; source: "images/clipboard.svg"; // clipboard by Bieutuong Bon from the Noun Project fillMode: Image.PreserveAspectFit; // Anchors anchors.right: parent.right; - anchors.top: secretText.top; + anchors.top: couponIDText.top; height: 40; width: height; MouseArea { anchors.fill: parent; onClicked: { - Window.copyToClipboard(root.secret); - secretText.text = "Copied to Clipboard!\n"; - secretClipboardTimer.start(); + Window.copyToClipboard(root.couponID); + couponIDText.text = "Copied to Clipboard!\n"; + couponIDClipboardTimer.start(); } } } Timer { - id: secretClipboardTimer; + id: couponIDClipboardTimer; interval: 2000; repeat: false; onTriggered: { - secretText.text = root.secret; + couponIDText.text = root.couponID; } } } @@ -1733,7 +1733,7 @@ Item { RalewaySemiBold { id: gift_paymentSuccess; - text: sendAssetStep.referrer === "payIn" || sendAssetStep.referrer === "authorizedScript" ? + text: sendAssetStep.referrer === "payIn" || sendAssetStep.referrer === "createCoupon" ? "Item:" : "Gift:"; // Anchors anchors.top: parent.top; @@ -1852,7 +1852,7 @@ Item { onClicked: { if (sendAssetStep.referrer === "payIn") { sendToScript({method: "closeSendAsset"}); - } else if (sendAssetStep.referrer === "authorizedScript") { + } else if (sendAssetStep.referrer === "createCoupon") { showDidYouCopyLightbox(); } else { root.nextActiveView = "sendAssetHome"; @@ -1888,16 +1888,16 @@ Item { Rectangle { anchors.top: parent.top; anchors.topMargin: root.assetCertID === "" || sendAssetStep.referrer === "payIn" || - sendAssetStep.referrer === "authorizedScript" ? 15 : 150; + sendAssetStep.referrer === "createCoupon" ? 15 : 150; anchors.left: parent.left; anchors.leftMargin: root.assetCertID === "" || sendAssetStep.referrer === "payIn" || - sendAssetStep.referrer === "authorizedScript" ? 15 : 50; + sendAssetStep.referrer === "createCoupon" ? 15 : 50; anchors.right: parent.right; anchors.rightMargin: root.assetCertID === "" || sendAssetStep.referrer === "payIn" || - sendAssetStep.referrer === "authorizedScript" ? 15 : 50; + sendAssetStep.referrer === "createCoupon" ? 15 : 50; anchors.bottom: parent.bottom; anchors.bottomMargin: root.assetCertID === "" || sendAssetStep.referrer === "payIn" || - sendAssetStep.referrer === "authorizedScript" ? 15 : 300; + sendAssetStep.referrer === "createCoupon" ? 15 : 300; color: "#FFFFFF"; RalewaySemiBold { @@ -1949,7 +1949,7 @@ Item { RalewaySemiBold { id: paymentFailureDetailText; - text: sendAssetStep.referrer === "authorizedScript" ? "The server was unable to handle your request. Please try again later." : + text: sendAssetStep.referrer === "createCoupon" ? "The server was unable to handle your request. Please try again later." : ("The recipient you specified was unable to receive your " + (root.assetCertID === "" ? "payment." : (sendAssetStep.referrer === "payIn" ? "item." : "gift."))); anchors.top: paymentFailureText.bottom; @@ -1970,7 +1970,7 @@ Item { Item { id: sendToContainer_paymentFailure; visible: (root.assetCertID === "" || sendAssetStep.referrer === "payIn") && - sendAssetStep.referrer !== "authorizedScript"; + sendAssetStep.referrer !== "createCoupon"; anchors.top: paymentFailureDetailText.bottom; anchors.topMargin: 8; anchors.left: parent.left; @@ -2134,8 +2134,8 @@ Item { root.assetCertID, parseInt(amountTextField.text), optionalMessage.text); - } else if (sendAssetStep.referrer === "authorizedScript") { - Commerce.authorizeAssetTransfer(scriptSecretTextField.text || "", + } else if (sendAssetStep.referrer === "createCoupon") { + Commerce.authorizeAssetTransfer(couponIDTextField.text || "", root.assetCertID, parseInt(amountTextField.text) || 1, optionalMessage.text) @@ -2167,22 +2167,22 @@ Item { sendAssetStep.referrer = ""; } - function generateRandomSecret() { - var RANDOM_SECRET_LENGTH = 25; - var randomSecret = ""; + function generateRandomCouponID() { + var RANDOM_COUPON_ID_LENGTH = 25; + var randomCouponID = ""; var possibleCharacters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; - for (var i = 0; i < RANDOM_SECRET_LENGTH; i++) { - randomSecret += possibleCharacters.charAt(Math.floor(Math.random() * possibleCharacters.length)); + for (var i = 0; i < RANDOM_COUPON_ID_LENGTH; i++) { + randomCouponID += possibleCharacters.charAt(Math.floor(Math.random() * possibleCharacters.length)); } - return randomSecret; + return randomCouponID; } function showDidYouCopyLightbox() { lightboxPopup.titleText = "Close Confirmation"; - lightboxPopup.bodyText = "Did you copy your Authorization ID and your Script Secret?\n\n" + - "You won't be able to see your Authorization ID or your Script Secret once " + + lightboxPopup.bodyText = "Did you copy your Authorization ID and your Coupon ID?\n\n" + + "You won't be able to see your Authorization ID or your Coupon ID once " + "you close this window."; lightboxPopup.button1text = "GO BACK"; lightboxPopup.button1method = function() { diff --git a/interface/resources/qml/hifi/commerce/common/sendAsset/images/coupon.svg b/interface/resources/qml/hifi/commerce/common/sendAsset/images/coupon.svg new file mode 100644 index 0000000000..2b7c052589 --- /dev/null +++ b/interface/resources/qml/hifi/commerce/common/sendAsset/images/coupon.svg @@ -0,0 +1,10 @@ + + + + + + + + + + From 9315c546d3ac9cfb611e56d12918290649fbb56c Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Mon, 3 Dec 2018 16:26:47 -0800 Subject: [PATCH 12/33] Initial version - move updateJoints() to derived class --- .../src/avatars-renderer/Avatar.cpp | 28 ++++++++++++++++++ .../src/avatars-renderer/Avatar.h | 3 ++ libraries/avatars/src/AvatarData.cpp | 29 ++----------------- 3 files changed, 33 insertions(+), 27 deletions(-) diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp index fceb146470..99eb08949b 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp @@ -1894,6 +1894,34 @@ QList Avatar::getSkeleton() { return QList(); } +void Avatar::updateJointMappings() { + { + QWriteLocker writeLock(&_jointDataLock); + _fstJointIndices.clear(); + _fstJointNames.clear(); + _jointData.clear(); + } + + //if (_skeletonModelURL.fileName().toLower().endsWith(".fst")) { + // //// + // // TODO: Should we rely upon HTTPResourceRequest for ResourceRequestObserver instead? + // // HTTPResourceRequest::doSend() covers all of the following and + // // then some. It doesn't cover the connect() call, so we may + // // want to add a HTTPResourceRequest::doSend() method that does + // // connects. + // QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); + // QNetworkRequest networkRequest = QNetworkRequest(_skeletonModelURL); + // networkRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); + // networkRequest.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT); + // DependencyManager::get()->update( + // _skeletonModelURL, -1, "AvatarData::updateJointMappings"); + // QNetworkReply* networkReply = networkAccessManager.get(networkRequest); + // // + // //// + // connect(networkReply, &QNetworkReply::finished, this, &AvatarData::setJointMappingsFromNetworkReply); + //} +} + void Avatar::addToScene(AvatarSharedPointer myHandle, const render::ScenePointer& scene) { if (scene) { auto nodelist = DependencyManager::get(); diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h index 8f70b12122..d577ab35bf 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h @@ -516,6 +516,9 @@ protected: mutable QReadWriteLock _modelJointIndicesCacheLock; mutable bool _modelJointsCached { false }; + /// Loads the joint indices, names from the FST file (if any) + virtual void updateJointMappings() override; + glm::vec3 _skeletonOffset; std::vector> _attachmentModels; std::vector _attachmentModelsTexturesLoaded; diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index d9d4b57c31..a369ea9a24 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -2209,33 +2209,8 @@ void AvatarData::sendIdentityPacket() { _identityDataChanged = false; } -void AvatarData::updateJointMappings() { - { - QWriteLocker writeLock(&_jointDataLock); - _fstJointIndices.clear(); - _fstJointNames.clear(); - _jointData.clear(); - } - - if (_skeletonModelURL.fileName().toLower().endsWith(".fst")) { - //// - // TODO: Should we rely upon HTTPResourceRequest for ResourceRequestObserver instead? - // HTTPResourceRequest::doSend() covers all of the following and - // then some. It doesn't cover the connect() call, so we may - // want to add a HTTPResourceRequest::doSend() method that does - // connects. - QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); - QNetworkRequest networkRequest = QNetworkRequest(_skeletonModelURL); - networkRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); - networkRequest.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT); - DependencyManager::get()->update( - _skeletonModelURL, -1, "AvatarData::updateJointMappings"); - QNetworkReply* networkReply = networkAccessManager.get(networkRequest); - // - //// - connect(networkReply, &QNetworkReply::finished, this, &AvatarData::setJointMappingsFromNetworkReply); - } -} +void AvatarData::updateJointMappings() +{ } static const QString JSON_ATTACHMENT_URL = QStringLiteral("modelUrl"); static const QString JSON_ATTACHMENT_JOINT_NAME = QStringLiteral("jointName"); From eb097af79617216c97806c59d3507e9ccd154d9d Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Mon, 3 Dec 2018 17:42:39 -0800 Subject: [PATCH 13/33] Remove all .fst downloads from AvatarData and its derived classes --- .../src/avatars/ScriptableAvatar.cpp | 4 -- .../src/avatars-renderer/Avatar.cpp | 28 ---------- .../src/avatars-renderer/Avatar.h | 3 - libraries/avatars/src/AvatarData.cpp | 56 ------------------- libraries/avatars/src/AvatarData.h | 9 --- 5 files changed, 100 deletions(-) diff --git a/assignment-client/src/avatars/ScriptableAvatar.cpp b/assignment-client/src/avatars/ScriptableAvatar.cpp index 51038a782f..392e9960e0 100644 --- a/assignment-client/src/avatars/ScriptableAvatar.cpp +++ b/assignment-client/src/avatars/ScriptableAvatar.cpp @@ -77,10 +77,6 @@ static AnimPose composeAnimPose(const HFMJoint& joint, const glm::quat rotation, } void ScriptableAvatar::update(float deltatime) { - if (_bind.isNull() && !_skeletonFBXURL.isEmpty()) { // AvatarData will parse the .fst, but not get the .fbx skeleton. - _bind = DependencyManager::get()->getAnimation(_skeletonFBXURL); - } - // Run animation if (_animation && _animation->isLoaded() && _animation->getFrames().size() > 0 && !_bind.isNull() && _bind->isLoaded()) { if (!_animSkeleton) { diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp index 99eb08949b..fceb146470 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp @@ -1894,34 +1894,6 @@ QList Avatar::getSkeleton() { return QList(); } -void Avatar::updateJointMappings() { - { - QWriteLocker writeLock(&_jointDataLock); - _fstJointIndices.clear(); - _fstJointNames.clear(); - _jointData.clear(); - } - - //if (_skeletonModelURL.fileName().toLower().endsWith(".fst")) { - // //// - // // TODO: Should we rely upon HTTPResourceRequest for ResourceRequestObserver instead? - // // HTTPResourceRequest::doSend() covers all of the following and - // // then some. It doesn't cover the connect() call, so we may - // // want to add a HTTPResourceRequest::doSend() method that does - // // connects. - // QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); - // QNetworkRequest networkRequest = QNetworkRequest(_skeletonModelURL); - // networkRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); - // networkRequest.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT); - // DependencyManager::get()->update( - // _skeletonModelURL, -1, "AvatarData::updateJointMappings"); - // QNetworkReply* networkReply = networkAccessManager.get(networkRequest); - // // - // //// - // connect(networkReply, &QNetworkReply::finished, this, &AvatarData::setJointMappingsFromNetworkReply); - //} -} - void Avatar::addToScene(AvatarSharedPointer myHandle, const render::ScenePointer& scene) { if (scene) { auto nodelist = DependencyManager::get(); diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h index d577ab35bf..8f70b12122 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h @@ -516,9 +516,6 @@ protected: mutable QReadWriteLock _modelJointIndicesCacheLock; mutable bool _modelJointsCached { false }; - /// Loads the joint indices, names from the FST file (if any) - virtual void updateJointMappings() override; - glm::vec3 _skeletonOffset; std::vector> _attachmentModels; std::vector _attachmentModelsTexturesLoaded; diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index a369ea9a24..420fa125e7 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -2000,8 +2000,6 @@ void AvatarData::setSkeletonModelURL(const QUrl& skeletonModelURL) { _skeletonModelURL = expanded; - updateJointMappings(); - if (_clientTraitsHandler) { _clientTraitsHandler->markTraitUpdated(AvatarTraits::SkeletonModelURL); } @@ -2097,57 +2095,6 @@ void AvatarData::detachAll(const QString& modelURL, const QString& jointName) { setAttachmentData(attachmentData); } -void AvatarData::setJointMappingsFromNetworkReply() { - - QNetworkReply* networkReply = static_cast(sender()); - - // before we process this update, make sure that the skeleton model URL hasn't changed - // since we made the FST request - if (networkReply->url() != _skeletonModelURL) { - qCDebug(avatars) << "Refusing to set joint mappings for FST URL that does not match the current URL"; - return; - } - - { - QWriteLocker writeLock(&_jointDataLock); - QByteArray line; - while (!(line = networkReply->readLine()).isEmpty()) { - line = line.trimmed(); - if (line.startsWith("filename")) { - int filenameIndex = line.indexOf('=') + 1; - if (filenameIndex > 0) { - _skeletonFBXURL = _skeletonModelURL.resolved(QString(line.mid(filenameIndex).trimmed())); - } - } - if (!line.startsWith("jointIndex")) { - continue; - } - int jointNameIndex = line.indexOf('=') + 1; - if (jointNameIndex == 0) { - continue; - } - int secondSeparatorIndex = line.indexOf('=', jointNameIndex); - if (secondSeparatorIndex == -1) { - continue; - } - QString jointName = line.mid(jointNameIndex, secondSeparatorIndex - jointNameIndex).trimmed(); - bool ok; - int jointIndex = line.mid(secondSeparatorIndex + 1).trimmed().toInt(&ok); - if (ok) { - while (_fstJointNames.size() < jointIndex + 1) { - _fstJointNames.append(QString()); - } - _fstJointNames[jointIndex] = jointName; - } - } - for (int i = 0; i < _fstJointNames.size(); i++) { - _fstJointIndices.insert(_fstJointNames.at(i), i + 1); - } - } - - networkReply->deleteLater(); -} - void AvatarData::sendAvatarDataPacket(bool sendAll) { auto nodeList = DependencyManager::get(); @@ -2209,9 +2156,6 @@ void AvatarData::sendIdentityPacket() { _identityDataChanged = false; } -void AvatarData::updateJointMappings() -{ } - static const QString JSON_ATTACHMENT_URL = QStringLiteral("modelUrl"); static const QString JSON_ATTACHMENT_JOINT_NAME = QStringLiteral("jointName"); static const QString JSON_ATTACHMENT_TRANSFORM = QStringLiteral("transform"); diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index 36c6ed6c50..c3819d0d28 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -1269,11 +1269,6 @@ public slots: */ void sendIdentityPacket(); - /**jsdoc - * @function MyAvatar.setJointMappingsFromNetworkReply - */ - void setJointMappingsFromNetworkReply(); - /**jsdoc * @function MyAvatar.setSessionUUID * @param {Uuid} sessionUUID @@ -1376,7 +1371,6 @@ protected: mutable HeadData* _headData { nullptr }; QUrl _skeletonModelURL; - QUrl _skeletonFBXURL; QVector _attachmentData; QVector _oldAttachmentData; QString _displayName; @@ -1390,9 +1384,6 @@ protected: QWeakPointer _owningAvatarMixer; - /// Loads the joint indices, names from the FST file (if any) - virtual void updateJointMappings(); - glm::vec3 _targetVelocity; SimpleMovingAverage _averageBytesReceived; From 7f2e427584c469c47e071da5a7351210a3bca7fc Mon Sep 17 00:00:00 2001 From: Anthony Thibault Date: Mon, 3 Dec 2018 18:56:45 -0800 Subject: [PATCH 14/33] Added DropAfterDelay strategy to Vive Tracker Calibration Dialog This is a hybrid of None and Drop strategies. Basically, if the puck is out of range for less then 1/2 a second we still use it, however any longer then that and we mark it as invalid. --- .../qml/hifi/tablet/OpenVrConfiguration.qml | 2 +- plugins/openvr/src/ViveControllerManager.cpp | 23 ++++++++++++++++++- plugins/openvr/src/ViveControllerManager.h | 5 +++- 3 files changed, 27 insertions(+), 3 deletions(-) diff --git a/interface/resources/qml/hifi/tablet/OpenVrConfiguration.qml b/interface/resources/qml/hifi/tablet/OpenVrConfiguration.qml index e18fdea444..4ce9fe7c84 100644 --- a/interface/resources/qml/hifi/tablet/OpenVrConfiguration.qml +++ b/interface/resources/qml/hifi/tablet/OpenVrConfiguration.qml @@ -871,7 +871,7 @@ Flickable { editable: true colorScheme: hifi.colorSchemes.dark - model: ["None", "Freeze", "Drop"] + model: ["None", "Freeze", "Drop", "DropAfterDelay"] label: "" onCurrentIndexChanged: { diff --git a/plugins/openvr/src/ViveControllerManager.cpp b/plugins/openvr/src/ViveControllerManager.cpp index af4f4da18c..ff1f10f8ed 100644 --- a/plugins/openvr/src/ViveControllerManager.cpp +++ b/plugins/openvr/src/ViveControllerManager.cpp @@ -138,6 +138,8 @@ static QString outOfRangeDataStrategyToString(ViveControllerManager::OutOfRangeD return "Freeze"; case ViveControllerManager::OutOfRangeDataStrategy::Drop: return "Drop"; + case ViveControllerManager::OutOfRangeDataStrategy::DropAfterDelay: + return "DropAfterDelay"; } } @@ -146,6 +148,8 @@ static ViveControllerManager::OutOfRangeDataStrategy stringToOutOfRangeDataStrat return ViveControllerManager::OutOfRangeDataStrategy::Drop; } else if (string == "Freeze") { return ViveControllerManager::OutOfRangeDataStrategy::Freeze; + } else if (string == "DropAfterDelay") { + return ViveControllerManager::OutOfRangeDataStrategy::DropAfterDelay; } else { return ViveControllerManager::OutOfRangeDataStrategy::None; } @@ -302,7 +306,7 @@ void ViveControllerManager::loadSettings() { if (_inputDevice) { const double DEFAULT_ARM_CIRCUMFERENCE = 0.33; const double DEFAULT_SHOULDER_WIDTH = 0.48; - const QString DEFAULT_OUT_OF_RANGE_STRATEGY = "Drop"; + const QString DEFAULT_OUT_OF_RANGE_STRATEGY = "DropAfterDelay"; _inputDevice->_armCircumference = settings.value("armCircumference", QVariant(DEFAULT_ARM_CIRCUMFERENCE)).toDouble(); _inputDevice->_shoulderWidth = settings.value("shoulderWidth", QVariant(DEFAULT_SHOULDER_WIDTH)).toDouble(); _inputDevice->_outOfRangeDataStrategy = stringToOutOfRangeDataStrategy(settings.value("outOfRangeDataStrategy", QVariant(DEFAULT_OUT_OF_RANGE_STRATEGY)).toString()); @@ -516,6 +520,7 @@ void ViveControllerManager::InputDevice::handleTrackedObject(uint32_t deviceInde _nextSimPoseData.vrPoses[deviceIndex].bPoseIsValid && poseIndex <= controller::TRACKED_OBJECT_15) { + uint64_t now = usecTimestampNow(); controller::Pose pose; switch (_outOfRangeDataStrategy) { case OutOfRangeDataStrategy::Drop: @@ -544,6 +549,22 @@ void ViveControllerManager::InputDevice::handleTrackedObject(uint32_t deviceInde _nextSimPoseData.angularVelocities[deviceIndex] = _lastSimPoseData.angularVelocities[deviceIndex]; } break; + case OutOfRangeDataStrategy::DropAfterDelay: + const uint64_t DROP_DELAY_TIME = 500 * USECS_PER_MSEC; + + // All Running_OK results are valid. + if (_nextSimPoseData.vrPoses[deviceIndex].eTrackingResult == vr::TrackingResult_Running_OK) { + pose = buildPose(_nextSimPoseData.poses[deviceIndex], _nextSimPoseData.linearVelocities[deviceIndex], _nextSimPoseData.angularVelocities[deviceIndex]); + // update the timer + _simDataRunningOkTimestampMap[deviceIndex] = now; + } else if (now - _simDataRunningOkTimestampMap[deviceIndex] < DROP_DELAY_TIME) { + // report the pose, even though pose is out-of-range + pose = buildPose(_nextSimPoseData.poses[deviceIndex], _nextSimPoseData.linearVelocities[deviceIndex], _nextSimPoseData.angularVelocities[deviceIndex]); + } else { + // this pose has been out-of-range for too long. + pose.valid = false; + } + break; } if (pose.valid) { diff --git a/plugins/openvr/src/ViveControllerManager.h b/plugins/openvr/src/ViveControllerManager.h index 647702ae77..dbd248dc53 100644 --- a/plugins/openvr/src/ViveControllerManager.h +++ b/plugins/openvr/src/ViveControllerManager.h @@ -63,7 +63,8 @@ public: enum class OutOfRangeDataStrategy { None, Freeze, - Drop + Drop, + DropAfterDelay }; private: @@ -205,6 +206,8 @@ private: bool _hmdTrackingEnabled { true }; + std::map _simDataRunningOkTimestampMap; + QString configToString(Config config); friend class ViveControllerManager; }; From f3236e0843299552dae90e535712088a36b81f3e Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Tue, 4 Dec 2018 10:45:32 -0800 Subject: [PATCH 15/33] Remove FST joint members from AvatarData --- libraries/avatars/src/AvatarData.cpp | 9 ++------- libraries/avatars/src/AvatarData.h | 11 +---------- 2 files changed, 3 insertions(+), 17 deletions(-) diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 420fa125e7..8705c35630 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -1776,16 +1776,11 @@ int AvatarData::getFauxJointIndex(const QString& name) const { int AvatarData::getJointIndex(const QString& name) const { int result = getFauxJointIndex(name); - if (result != -1) { - return result; - } - QReadLocker readLock(&_jointDataLock); - return _fstJointIndices.value(name) - 1; + return result; } QStringList AvatarData::getJointNames() const { - QReadLocker readLock(&_jointDataLock); - return _fstJointNames; + return QStringList(); } glm::quat AvatarData::getOrientationOutbound() const { diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index c3819d0d28..49fd49df58 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -1377,9 +1377,6 @@ protected: QString _sessionDisplayName { }; bool _lookAtSnappingEnabled { true }; - QHash _fstJointIndices; ///< 1-based, since zero is returned for missing keys - QStringList _fstJointNames; ///< in order of depth-first traversal - quint64 _errorLogExpiry; ///< time in future when to log an error QWeakPointer _owningAvatarMixer; @@ -1487,11 +1484,8 @@ protected: T readLockWithNamedJointIndex(const QString& name, const T& defaultValue, F f) const { int index = getFauxJointIndex(name); QReadLocker readLock(&_jointDataLock); - if (index == -1) { - index = _fstJointIndices.value(name) - 1; - } - // The first conditional is superfluous, but illsutrative + // The first conditional is superfluous, but illustrative if (index == -1 || index < _jointData.size()) { return defaultValue; } @@ -1508,9 +1502,6 @@ protected: void writeLockWithNamedJointIndex(const QString& name, F f) { int index = getFauxJointIndex(name); QWriteLocker writeLock(&_jointDataLock); - if (index == -1) { - index = _fstJointIndices.value(name) - 1; - } if (index == -1) { return; } From 0c64d1227a5c7c57eca72dec9c027b039df0564c Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Tue, 4 Dec 2018 16:41:03 -0800 Subject: [PATCH 16/33] fix vive crash --- plugins/openvr/src/OpenVrDisplayPlugin.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/openvr/src/OpenVrDisplayPlugin.cpp b/plugins/openvr/src/OpenVrDisplayPlugin.cpp index 99c861871d..62f1720814 100644 --- a/plugins/openvr/src/OpenVrDisplayPlugin.cpp +++ b/plugins/openvr/src/OpenVrDisplayPlugin.cpp @@ -485,6 +485,7 @@ bool OpenVrDisplayPlugin::internalActivate() { _submitCanvas->doneCurrent(); }); } + _submitCanvas->moveToThread(_submitThread.get()); } return Parent::internalActivate(); From 58396b845fc4a9733d9d0bb43d6bbb3d72a15336 Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Tue, 4 Dec 2018 17:32:50 -0800 Subject: [PATCH 17/33] move back to plugin thread (cherry picked from commit 98d3f6a9d1191031cda1ff040e69093ef6824f1c) --- plugins/openvr/src/OpenVrDisplayPlugin.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/openvr/src/OpenVrDisplayPlugin.cpp b/plugins/openvr/src/OpenVrDisplayPlugin.cpp index 62f1720814..11ef222172 100644 --- a/plugins/openvr/src/OpenVrDisplayPlugin.cpp +++ b/plugins/openvr/src/OpenVrDisplayPlugin.cpp @@ -319,6 +319,7 @@ public: glBindVertexArray(0); glDeleteVertexArrays(1, &_vao); _canvas->doneCurrent(); + _canvas->moveToThread(_plugin.thread()); } void update(const CompositeInfo& newCompositeInfo) { _queue.push(newCompositeInfo); } From 7723261cb9aea50c7f59b713b239d1bf857daf6e Mon Sep 17 00:00:00 2001 From: Clement Date: Mon, 3 Dec 2018 16:51:14 -0800 Subject: [PATCH 18/33] Remove deferred script loading logic --- libraries/script-engine/src/ScriptEngine.cpp | 115 ------------------- libraries/script-engine/src/ScriptEngine.h | 3 - 2 files changed, 118 deletions(-) diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index 455fd93f4b..6f98dd2864 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -2061,68 +2061,6 @@ bool ScriptEngine::hasEntityScriptDetails(const EntityItemID& entityID) const { return _entityScripts.contains(entityID); } -const static EntityItemID BAD_SCRIPT_UUID_PLACEHOLDER { "{20170224-dead-face-0000-cee000021114}" }; - -void ScriptEngine::processDeferredEntityLoads(const QString& entityScript, const EntityItemID& leaderID) { - QList retryLoads; - QMutableListIterator i(_deferredEntityLoads); - while (i.hasNext()) { - auto retry = i.next(); - if (retry.entityScript == entityScript) { - retryLoads << retry; - i.remove(); - } - } - foreach(DeferredLoadEntity retry, retryLoads) { - // check whether entity was since been deleted - - EntityScriptDetails details; - if (!getEntityScriptDetails(retry.entityID, details)) { - qCDebug(scriptengine) << "processDeferredEntityLoads -- entity details gone (entity deleted?)" - << retry.entityID; - continue; - } - - // check whether entity has since been unloaded or otherwise errored-out - if (details.status != EntityScriptStatus::PENDING) { - qCDebug(scriptengine) << "processDeferredEntityLoads -- entity status no longer PENDING; " - << retry.entityID << details.status; - continue; - } - - // propagate leader's failure reasons to the pending entity - EntityScriptDetails leaderDetails; - { - QWriteLocker locker { &_entityScriptsLock }; - leaderDetails = _entityScripts[leaderID]; - } - if (leaderDetails.status != EntityScriptStatus::RUNNING) { - qCDebug(scriptengine) << QString("... pending load of %1 cancelled (leader: %2 status: %3)") - .arg(retry.entityID.toString()).arg(leaderID.toString()).arg(leaderDetails.status); - - auto extraDetail = QString("\n(propagated from %1)").arg(leaderID.toString()); - if (leaderDetails.status == EntityScriptStatus::ERROR_LOADING_SCRIPT || - leaderDetails.status == EntityScriptStatus::ERROR_RUNNING_SCRIPT) { - // propagate same error so User doesn't have to hunt down stampede's leader - updateEntityScriptStatus(retry.entityID, leaderDetails.status, leaderDetails.errorInfo + extraDetail); - } else { - // the leader Entity somehow ended up in some other state (rapid-fire delete or unload could cause) - updateEntityScriptStatus(retry.entityID, EntityScriptStatus::ERROR_LOADING_SCRIPT, - "A previous Entity failed to load using this script URL; reload to try again." + extraDetail); - } - continue; - } - - if (_occupiedScriptURLs.contains(retry.entityScript)) { - qCWarning(scriptengine) << "--- SHOULD NOT HAPPEN -- recursive call into processDeferredEntityLoads" << retry.entityScript; - continue; - } - - // if we made it here then the leading entity was successful so proceed with normal load - loadEntityScript(retry.entityID, retry.entityScript, false); - } -} - void ScriptEngine::loadEntityScript(const EntityItemID& entityID, const QString& entityScript, bool forceRedownload) { if (QThread::currentThread() != thread()) { QMetaObject::invokeMethod(this, "loadEntityScript", @@ -2147,40 +2085,6 @@ void ScriptEngine::loadEntityScript(const EntityItemID& entityID, const QString& updateEntityScriptStatus(entityID, EntityScriptStatus::PENDING, "...pending..."); } - // This "occupied" approach allows multiple Entities to boot from the same script URL while still taking - // full advantage of cacheable require modules. This only affects Entities literally coming in back-to-back - // before the first one has time to finish loading. - if (_occupiedScriptURLs.contains(entityScript)) { - auto currentEntityID = _occupiedScriptURLs[entityScript]; - if (currentEntityID == BAD_SCRIPT_UUID_PLACEHOLDER) { - if (forceRedownload) { - // script was previously marked unusable, but we're reloading so reset it - _occupiedScriptURLs.remove(entityScript); - } else { - // since not reloading, assume that the exact same input would produce the exact same output again - // note: this state gets reset with "reload all scripts," leaving/returning to a Domain, clear cache, etc. -#ifdef DEBUG_ENTITY_STATES - qCDebug(scriptengine) << QString("loadEntityScript.cancelled entity: %1 (previous script failure)") - .arg(entityID.toString()); -#endif - updateEntityScriptStatus(entityID, EntityScriptStatus::ERROR_LOADING_SCRIPT, - "A previous Entity failed to load using this script URL; reload to try again."); - return; - } - } else { - // another entity is busy loading from this script URL so wait for them to finish -#ifdef DEBUG_ENTITY_STATES - qCDebug(scriptengine) << QString("loadEntityScript.deferring[%0] entity: %1 (waiting on %2 )") - .arg(_deferredEntityLoads.size()).arg(entityID.toString()).arg(currentEntityID.toString()); -#endif - _deferredEntityLoads.push_back({ entityID, entityScript }); - return; - } - } - - // the scriptURL slot is available; flag as in-use - _occupiedScriptURLs[entityScript] = entityID; - #ifdef DEBUG_ENTITY_STATES { EntityScriptDetails details; @@ -2223,10 +2127,6 @@ void ScriptEngine::loadEntityScript(const EntityItemID& entityID, const QString& qCDebug(scriptengine) << "loadEntityScript.contentAvailable -- aborting"; #endif } - // recheck whether us since may have been set to BAD_SCRIPT_UUID_PLACEHOLDER in entityScriptContentAvailable - if (_occupiedScriptURLs.contains(entityScript) && _occupiedScriptURLs[entityScript] == entityID) { - _occupiedScriptURLs.remove(entityScript); - } }); }, forceRedownload); } @@ -2301,13 +2201,6 @@ void ScriptEngine::entityScriptContentAvailable(const EntityItemID& entityID, co newDetails.errorInfo = errorInfo; newDetails.status = status; setEntityScriptDetails(entityID, newDetails); - -#ifdef DEBUG_ENTITY_STATES - qCDebug(scriptengine) << "entityScriptContentAvailable -- flagging as BAD_SCRIPT_UUID_PLACEHOLDER"; -#endif - // flag the original entityScript as unusuable - _occupiedScriptURLs[entityScript] = BAD_SCRIPT_UUID_PLACEHOLDER; - processDeferredEntityLoads(entityScript, entityID); }; // NETWORK / FILESYSTEM ERRORS @@ -2441,9 +2334,6 @@ void ScriptEngine::entityScriptContentAvailable(const EntityItemID& entityID, co callEntityScriptMethod(entityID, "preload"); emit entityScriptPreloadFinished(entityID); - - _occupiedScriptURLs.remove(entityScript); - processDeferredEntityLoads(entityScript, entityID); } /**jsdoc @@ -2499,10 +2389,6 @@ void ScriptEngine::unloadEntityScript(const EntityItemID& entityID, bool shouldR } stopAllTimersForEntityScript(entityID); - { - // FIXME: shouldn't have to do this here, but currently something seems to be firing unloads moments after firing initial load requests - processDeferredEntityLoads(scriptText, entityID); - } } } @@ -2532,7 +2418,6 @@ void ScriptEngine::unloadAllEntityScripts() { _entityScripts.clear(); } emit entityScriptDetailsUpdated(); - _occupiedScriptURLs.clear(); #ifdef DEBUG_ENGINE_STATE _debugDump( diff --git a/libraries/script-engine/src/ScriptEngine.h b/libraries/script-engine/src/ScriptEngine.h index fe8396bc50..8fe50aee78 100644 --- a/libraries/script-engine/src/ScriptEngine.h +++ b/libraries/script-engine/src/ScriptEngine.h @@ -747,7 +747,6 @@ protected: void updateEntityScriptStatus(const EntityItemID& entityID, const EntityScriptStatus& status, const QString& errorInfo = QString()); void setEntityScriptDetails(const EntityItemID& entityID, const EntityScriptDetails& details); void setParentURL(const QString& parentURL) { _parentURL = parentURL; } - void processDeferredEntityLoads(const QString& entityScript, const EntityItemID& leaderID); QObject* setupTimerWithInterval(const QScriptValue& function, int intervalMS, bool isSingleShot); void stopTimer(QTimer* timer); @@ -783,8 +782,6 @@ protected: QSet _includedURLs; mutable QReadWriteLock _entityScriptsLock { QReadWriteLock::Recursive }; QHash _entityScripts; - QHash _occupiedScriptURLs; - QList _deferredEntityLoads; EntityScriptContentAvailableMap _contentAvailableQueue; bool _isThreaded { false }; From 02307d2308f56f21754d7a121d635d4062d7b6cb Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Wed, 5 Dec 2018 16:34:53 -0800 Subject: [PATCH 19/33] Secret to Coupon ID --- .../qml/hifi/commerce/common/sendAsset/SendAsset.qml | 4 ++-- interface/src/commerce/Ledger.cpp | 4 ++-- interface/src/commerce/Ledger.h | 2 +- interface/src/commerce/QmlCommerce.cpp | 4 ++-- interface/src/commerce/QmlCommerce.h | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/interface/resources/qml/hifi/commerce/common/sendAsset/SendAsset.qml b/interface/resources/qml/hifi/commerce/common/sendAsset/SendAsset.qml index 7c2116f77b..3125ad1ee6 100644 --- a/interface/resources/qml/hifi/commerce/common/sendAsset/SendAsset.qml +++ b/interface/resources/qml/hifi/commerce/common/sendAsset/SendAsset.qml @@ -120,10 +120,10 @@ Item { if (result.status === 'success') { root.authorizationID = result.data.authorization_id; authorizationIDText.text = root.authorizationID; - root.couponID = result.data.secret; + root.couponID = result.data.coupon_id; couponIDText.text = root.couponID if (couponIDTextField.text !== root.couponID) { - console.log("SendAsset: Returned secret doesn't match client-generated secret!"); + console.log("SendAsset: Returned coupon ID doesn't match client-generated coupon ID!"); } root.nextActiveView = 'paymentSuccess'; } else { diff --git a/interface/src/commerce/Ledger.cpp b/interface/src/commerce/Ledger.cpp index fb177ddc82..8bb01f6389 100644 --- a/interface/src/commerce/Ledger.cpp +++ b/interface/src/commerce/Ledger.cpp @@ -442,10 +442,10 @@ void Ledger::transferAssetToUsername(const QString& hfc_key, const QString& user } } -void Ledger::authorizeAssetTransfer(const QString& hfc_key, const QString& secret, const QString& certificateID, const int& amount, const QString& optionalMessage) { +void Ledger::authorizeAssetTransfer(const QString& hfc_key, const QString& couponID, const QString& certificateID, const int& amount, const QString& optionalMessage) { QJsonObject transaction; transaction["public_key"] = hfc_key; - transaction["secret"] = secret; + transaction["coupon_id"] = couponID; transaction["quantity"] = amount; transaction["message"] = optionalMessage; if (!certificateID.isEmpty()) { diff --git a/interface/src/commerce/Ledger.h b/interface/src/commerce/Ledger.h index 700cbe2c4b..2e18f34c8d 100644 --- a/interface/src/commerce/Ledger.h +++ b/interface/src/commerce/Ledger.h @@ -36,7 +36,7 @@ public: void certificateInfo(const QString& certificateId); void transferAssetToNode(const QString& hfc_key, const QString& nodeID, const QString& certificateID, const int& amount, const QString& optionalMessage); void transferAssetToUsername(const QString& hfc_key, const QString& username, const QString& certificateID, const int& amount, const QString& optionalMessage); - void authorizeAssetTransfer(const QString& hfc_key, const QString& secret, const QString& certificateID, const int& amount, const QString& optionalMessage); + void authorizeAssetTransfer(const QString& hfc_key, const QString& couponID, const QString& certificateID, const int& amount, const QString& optionalMessage); void alreadyOwned(const QString& marketplaceId); void getAvailableUpdates(const QString& itemId = "", const int& pageNumber = 1, const int& itemsPerPage = 10); void updateItem(const QString& hfc_key, const QString& certificate_id); diff --git a/interface/src/commerce/QmlCommerce.cpp b/interface/src/commerce/QmlCommerce.cpp index aab053484b..00acd40e70 100644 --- a/interface/src/commerce/QmlCommerce.cpp +++ b/interface/src/commerce/QmlCommerce.cpp @@ -247,7 +247,7 @@ void QmlCommerce::transferAssetToUsername(const QString& username, ledger->transferAssetToUsername(key, username, certificateID, amount, optionalMessage); } -void QmlCommerce::authorizeAssetTransfer(const QString& secret, +void QmlCommerce::authorizeAssetTransfer(const QString& couponID, const QString& certificateID, const int& amount, const QString& optionalMessage) { @@ -259,7 +259,7 @@ void QmlCommerce::authorizeAssetTransfer(const QString& secret, return emit authorizeAssetTransferResult(result); } QString key = keys[0]; - ledger->authorizeAssetTransfer(key, secret, certificateID, amount, optionalMessage); + ledger->authorizeAssetTransfer(key, couponID, certificateID, amount, optionalMessage); } void QmlCommerce::replaceContentSet(const QString& itemHref, const QString& certificateID) { diff --git a/interface/src/commerce/QmlCommerce.h b/interface/src/commerce/QmlCommerce.h index e22b540624..ad21899ebf 100644 --- a/interface/src/commerce/QmlCommerce.h +++ b/interface/src/commerce/QmlCommerce.h @@ -85,7 +85,7 @@ protected: Q_INVOKABLE void transferAssetToNode(const QString& nodeID, const QString& certificateID, const int& amount, const QString& optionalMessage); Q_INVOKABLE void transferAssetToUsername(const QString& username, const QString& certificateID, const int& amount, const QString& optionalMessage); - Q_INVOKABLE void authorizeAssetTransfer(const QString& secret, const QString& certificateID, const int& amount, const QString& optionalMessage); + Q_INVOKABLE void authorizeAssetTransfer(const QString& couponID, const QString& certificateID, const int& amount, const QString& optionalMessage); Q_INVOKABLE void replaceContentSet(const QString& itemHref, const QString& certificateID); From 16144b663036cbce530f77091a4ab8217f22450f Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Wed, 5 Dec 2018 17:14:40 -0800 Subject: [PATCH 20/33] Move the previously deleted FST reader down to the ScriptableAvatar class --- .../src/avatars/ScriptableAvatar.cpp | 96 +++++++++++++++++++ .../src/avatars/ScriptableAvatar.h | 32 +++++++ 2 files changed, 128 insertions(+) diff --git a/assignment-client/src/avatars/ScriptableAvatar.cpp b/assignment-client/src/avatars/ScriptableAvatar.cpp index 392e9960e0..29a66b44fc 100644 --- a/assignment-client/src/avatars/ScriptableAvatar.cpp +++ b/assignment-client/src/avatars/ScriptableAvatar.cpp @@ -19,6 +19,9 @@ #include #include #include +#include +#include + ScriptableAvatar::ScriptableAvatar() { _clientTraitsHandler = std::unique_ptr(new ClientTraitsHandler(this)); @@ -62,11 +65,28 @@ AnimationDetails ScriptableAvatar::getAnimationDetails() { return _animationDetails; } +int ScriptableAvatar::getJointIndex(const QString& name) const { + // Faux joints: + int result = AvatarData::getJointIndex(name); + if (result != -1) { + return result; + } + QReadLocker readLock(&_jointDataLock); + return _fstJointIndices.value(name) - 1; +} + +QStringList ScriptableAvatar::getJointNames() const { + QReadLocker readLock(&_jointDataLock); + return _fstJointNames; + return QStringList(); +} + void ScriptableAvatar::setSkeletonModelURL(const QUrl& skeletonModelURL) { _bind.reset(); _animSkeleton.reset(); AvatarData::setSkeletonModelURL(skeletonModelURL); + updateJointMappings(); } static AnimPose composeAnimPose(const HFMJoint& joint, const glm::quat rotation, const glm::vec3 translation) { @@ -142,6 +162,82 @@ void ScriptableAvatar::update(float deltatime) { _clientTraitsHandler->sendChangedTraitsToMixer(); } +void ScriptableAvatar::updateJointMappings() { + { + QWriteLocker writeLock(&_jointDataLock); + _fstJointIndices.clear(); + _fstJointNames.clear(); + _jointData.clear(); + } + + if (_skeletonModelURL.fileName().toLower().endsWith(".fst")) { + //// + // TODO: Should we rely upon HTTPResourceRequest for ResourceRequestObserver instead? + // HTTPResourceRequest::doSend() covers all of the following and + // then some. It doesn't cover the connect() call, so we may + // want to add a HTTPResourceRequest::doSend() method that does + // connects. + QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); + QNetworkRequest networkRequest = QNetworkRequest(_skeletonModelURL); + networkRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); + networkRequest.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT); + DependencyManager::get()->update( + _skeletonModelURL, -1, "AvatarData::updateJointMappings"); + QNetworkReply* networkReply = networkAccessManager.get(networkRequest); + // + //// + connect(networkReply, &QNetworkReply::finished, this, &ScriptableAvatar::setJointMappingsFromNetworkReply); + } +} + +void ScriptableAvatar::setJointMappingsFromNetworkReply() { + QNetworkReply* networkReply = static_cast(sender()); + // before we process this update, make sure that the skeleton model URL hasn't changed + // since we made the FST request + if (networkReply->url() != _skeletonModelURL) { + qCDebug(avatars) << "Refusing to set joint mappings for FST URL that does not match the current URL"; + networkReply->deleteLater(); + return; + } + { + QWriteLocker writeLock(&_jointDataLock); + QByteArray line; + while (!(line = networkReply->readLine()).isEmpty()) { + line = line.trimmed(); + if (line.startsWith("filename")) { + int filenameIndex = line.indexOf('=') + 1; + if (filenameIndex > 0) { + _skeletonFBXURL = _skeletonModelURL.resolved(QString(line.mid(filenameIndex).trimmed())); + } + } + if (!line.startsWith("jointIndex")) { + continue; + } + int jointNameIndex = line.indexOf('=') + 1; + if (jointNameIndex == 0) { + continue; + } + int secondSeparatorIndex = line.indexOf('=', jointNameIndex); + if (secondSeparatorIndex == -1) { + continue; + } + QString jointName = line.mid(jointNameIndex, secondSeparatorIndex - jointNameIndex).trimmed(); + bool ok; + int jointIndex = line.mid(secondSeparatorIndex + 1).trimmed().toInt(&ok); + if (ok) { + while (_fstJointNames.size() < jointIndex + 1) { + _fstJointNames.append(QString()); + } + _fstJointNames[jointIndex] = jointName; + } + } + for (int i = 0; i < _fstJointNames.size(); i++) { + _fstJointIndices.insert(_fstJointNames.at(i), i + 1); + } + } + networkReply->deleteLater(); +} + void ScriptableAvatar::setHasProceduralBlinkFaceMovement(bool hasProceduralBlinkFaceMovement) { _headData->setHasProceduralBlinkFaceMovement(hasProceduralBlinkFaceMovement); } diff --git a/assignment-client/src/avatars/ScriptableAvatar.h b/assignment-client/src/avatars/ScriptableAvatar.h index 578bd84a8f..66b0b5ae3f 100644 --- a/assignment-client/src/avatars/ScriptableAvatar.h +++ b/assignment-client/src/avatars/ScriptableAvatar.h @@ -153,6 +153,27 @@ public: */ Q_INVOKABLE AnimationDetails getAnimationDetails(); + /**jsdoc + * Get the names of all the joints in the current avatar. + * @function MyAvatar.getJointNames + * @returns {string[]} The joint names. + * @example Report the names of all the joints in your current avatar. + * print(JSON.stringify(MyAvatar.getJointNames())); + */ + Q_INVOKABLE virtual QStringList getJointNames() const override; + + /**jsdoc + * Get the joint index for a named joint. The joint index value is the position of the joint in the array returned by + * {@link MyAvatar.getJointNames} or {@link Avatar.getJointNames}. + * @function MyAvatar.getJointIndex + * @param {string} name - The name of the joint. + * @returns {number} The index of the joint. + * @example Report the index of your avatar's left arm joint. + * print(JSON.stringify(MyAvatar.getJointIndex("LeftArm")); + */ + /// Returns the index of the joint with the specified name, or -1 if not found/unknown. + Q_INVOKABLE virtual int getJointIndex(const QString& name) const override; + virtual void setSkeletonModelURL(const QUrl& skeletonModelURL) override; virtual QByteArray toByteArrayStateful(AvatarDataDetail dataDetail, bool dropFaceTracking = false) override; @@ -167,12 +188,23 @@ public: public slots: void update(float deltatime); + /**jsdoc + * @function MyAvatar.setJointMappingsFromNetworkReply + */ + void setJointMappingsFromNetworkReply(); + private: AnimationPointer _animation; AnimationDetails _animationDetails; QStringList _maskedJoints; AnimationPointer _bind; // a sleazy way to get the skeleton, given the various library/cmake dependencies std::shared_ptr _animSkeleton; + QHash _fstJointIndices; ///< 1-based, since zero is returned for missing keys + QStringList _fstJointNames; ///< in order of depth-first traversal + QUrl _skeletonFBXURL; + + /// Loads the joint indices, names from the FST file (if any) + void updateJointMappings(); }; #endif // hifi_ScriptableAvatar_h From 3160339551dda631170d4fc7f1b82aa03b8e036d Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Tue, 4 Dec 2018 10:50:50 -0800 Subject: [PATCH 21/33] Improve Create properties for particles + materials Sizing of input fields is now flexible. Material scale field labels are no longer obscured. Material tooltips are corrected. --- .../system/assets/data/createAppTooltips.json | 2 +- scripts/system/html/css/edit-style.css | 30 +++++++++------- scripts/system/html/js/draggableNumber.js | 12 +++---- scripts/system/html/js/entityProperties.js | 35 ++++++++++--------- 4 files changed, 44 insertions(+), 35 deletions(-) diff --git a/scripts/system/assets/data/createAppTooltips.json b/scripts/system/assets/data/createAppTooltips.json index 720d4537ee..3a25402588 100644 --- a/scripts/system/assets/data/createAppTooltips.json +++ b/scripts/system/assets/data/createAppTooltips.json @@ -267,7 +267,7 @@ "jsPropertyName": "parentMaterialName" }, "selectSubmesh": { - "tooltip": "If enabled, \"Select Submesh\" property will show up, otherwise \"Material Name to Replace\" will be shown.", + "tooltip": "If enabled, \"Submesh to Replace\" property will show up, otherwise \"Material to Replace\" will be shown.", "skipJSProperty": true }, "priority": { diff --git a/scripts/system/html/css/edit-style.css b/scripts/system/html/css/edit-style.css index 415d8e567f..d7d88ce91e 100644 --- a/scripts/system/html/css/edit-style.css +++ b/scripts/system/html/css/edit-style.css @@ -598,6 +598,7 @@ div.section[collapsed="true"], div.section[collapsed="true"] > .section-header { .triple-label { text-transform: uppercase; + text-align: center; padding: 6px 0; } @@ -605,6 +606,10 @@ div.section[collapsed="true"], div.section[collapsed="true"] > .section-header { margin-right: 10px; } +.triple-item.rgb.fstuple { + display: block !important; +} + .section-header[collapsed="true"] { margin-bottom: -21px; } @@ -911,14 +916,17 @@ div.refresh input[type="button"] { clear: both; } +.draggable-number-container { + flex: 0 1 124px; +} .draggable-number { position: relative; -} -.draggable-number div { height: 28px; - width: 124px; + flex: 0 1 124px; } -.draggable-number.text { + +.draggable-number .text { + position: absolute; display: inline-block; color: #afafaf; background-color: #252525; @@ -930,11 +938,12 @@ div.refresh input[type="button"] { width: 100%; line-height: 2; box-sizing: border-box; + z-index: 1; } -.draggable-number.text:hover { +.draggable-number .text:hover { cursor: ew-resize; } -.draggable-number span { +.draggable-number .left-arrow, .draggable-number .right-arrow { position: absolute; display: inline-block; font-family: HiFi-Glyphs; @@ -944,12 +953,12 @@ div.refresh input[type="button"] { .draggable-number span:hover { cursor: default; } -.draggable-number.left-arrow { +.draggable-number .left-arrow { top: 3px; left: 0px; transform: rotate(180deg); } -.draggable-number.right-arrow { +.draggable-number .right-arrow { top: 3px; right: 0px; } @@ -1514,6 +1523,7 @@ input.rename-entity { width: 258px; min-height: 20px; padding: 5px; + z-index: 100; } .create-app-tooltip .create-app-tooltip-description { @@ -1629,10 +1639,6 @@ input.number-slider { flex-flow: column; } -.flex-column + .flex-column { - padding-left: 50px; -} - .flex-center { align-items: center; } diff --git a/scripts/system/html/js/draggableNumber.js b/scripts/system/html/js/draggableNumber.js index 951b123e67..3995b846be 100644 --- a/scripts/system/html/js/draggableNumber.js +++ b/scripts/system/html/js/draggableNumber.js @@ -156,8 +156,8 @@ DraggableNumber.prototype = { this.elDiv = document.createElement('div'); this.elDiv.className = "draggable-number"; - this.elText = document.createElement('label'); - this.elText.className = "draggable-number text"; + this.elText = document.createElement('span'); + this.elText.className = "text"; this.elText.innerText = " "; this.elText.style.visibility = "visible"; this.elText.addEventListener("mousedown", this.onMouseDown); @@ -165,15 +165,15 @@ DraggableNumber.prototype = { this.elLeftArrow = document.createElement('span'); this.elRightArrow = document.createElement('span'); - this.elLeftArrow.className = 'draggable-number left-arrow'; + this.elLeftArrow.className = 'left-arrow'; this.elLeftArrow.innerHTML = 'D'; this.elLeftArrow.addEventListener("click", this.onStepDown); - this.elRightArrow.className = 'draggable-number right-arrow'; + this.elRightArrow.className = 'right-arrow'; this.elRightArrow.innerHTML = 'D'; this.elRightArrow.addEventListener("click", this.onStepUp); this.elInput = document.createElement('input'); - this.elInput.className = "draggable-number input"; + this.elInput.className = "input"; this.elInput.setAttribute("type", "number"); if (this.min !== undefined) { this.elInput.setAttribute("min", this.min); @@ -190,8 +190,8 @@ DraggableNumber.prototype = { this.elInput.addEventListener("focus", this.showInput.bind(this)); this.elDiv.appendChild(this.elLeftArrow); + this.elDiv.appendChild(this.elText); this.elDiv.appendChild(this.elInput); this.elDiv.appendChild(this.elRightArrow); - this.elDiv.appendChild(this.elText); } }; diff --git a/scripts/system/html/js/entityProperties.js b/scripts/system/html/js/entityProperties.js index 14ed2b77e3..ff760e3a4a 100644 --- a/scripts/system/html/js/entityProperties.js +++ b/scripts/system/html/js/entityProperties.js @@ -556,22 +556,24 @@ const GROUPS = [ { id: "save", label: "Save Material Data", className: "black", onClick: saveMaterialData } ], propertyID: "materialData", }, + { + label: "Select Submesh", + type: "bool", + propertyID: "selectSubmesh", + }, { label: "Submesh to Replace", type: "number", min: 0, step: 1, propertyID: "submeshToReplace", + indentedLabel: true, }, { - label: "Material Name to Replace", + label: "Material to Replace", type: "string", propertyID: "materialNameToReplace", - }, - { - label: "Select Submesh", - type: "bool", - propertyID: "selectSubmesh", + indentedLabel: true, }, { label: "Priority", @@ -582,7 +584,7 @@ const GROUPS = [ { label: "Material Position", type: "vec2", - vec2Type: "xy", + vec2Type: "xyz", min: 0, max: 1, step: 0.1, @@ -593,11 +595,11 @@ const GROUPS = [ { label: "Material Scale", type: "vec2", - vec2Type: "wh", + vec2Type: "xyz", min: 0, step: 0.1, decimals: 4, - subLabels: [ "width", "height" ], + subLabels: [ "x", "y" ], propertyID: "materialMappingScale", }, { @@ -1858,7 +1860,7 @@ function createNumberProperty(property, elProperty) { let elementID = property.elementID; let propertyData = property.data; - elProperty.className = "draggable-number"; + elProperty.className += " draggable-number-container"; let dragStartFunction = createDragStartFunction(property); let dragEndFunction = createDragEndFunction(property); @@ -1937,7 +1939,7 @@ function createColorProperty(property, elProperty) { let elementID = property.elementID; let propertyData = property.data; - elProperty.className = "rgb fstuple"; + elProperty.className += " rgb fstuple"; let elColorPicker = document.createElement('div'); elColorPicker.className = "color-picker"; @@ -1978,6 +1980,7 @@ function createColorProperty(property, elProperty) { color: '000000', submit: false, // We don't want to have a submission button onShow: function(colpick) { + console.log("Showing"); $(colorPickerID).attr('active', 'true'); // The original color preview within the picker needs to be updated on show because // prior to the picker being shown we don't have access to the selections' starting color. @@ -2879,20 +2882,20 @@ function loaded() { for (let i = 0; i < propertyData.properties.length; ++i) { let innerPropertyData = propertyData.properties[i]; - let elWrapper = createElementFromHTML('
'); + let elWrapper = createElementFromHTML('
'); + elProperty.appendChild(elWrapper); let propertyID = innerPropertyData.propertyID; let propertyName = innerPropertyData.propertyName !== undefined ? innerPropertyData.propertyName : propertyID; let propertyElementID = "property-" + propertyID; propertyElementID = propertyElementID.replace('.', '-'); - elWrapper.appendChild(createElementFromHTML(`
${innerPropertyData.label}
`)); - elProperty.appendChild(elWrapper); - - let property = createProperty(innerPropertyData, propertyElementID, propertyName, propertyID, elWrapper.childNodes[0]); + let property = createProperty(innerPropertyData, propertyElementID, propertyName, propertyID, elWrapper); property.isParticleProperty = group.id.includes("particles"); property.elContainer = elContainer; property.spaceMode = propertySpaceMode; + + elWrapper.appendChild(createElementFromHTML(`
${innerPropertyData.label}
`)); if (property.type !== 'placeholder') { properties[propertyID] = property; From a2af50a9d0b7a82c7c993d1801b2c5ee0d797afe Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Thu, 6 Dec 2018 11:07:26 -0800 Subject: [PATCH 22/33] Show pre-auth'd transactions in Recent Activity before redemption --- .../qml/hifi/commerce/wallet/WalletHome.qml | 8 ++++++-- interface/src/commerce/Ledger.cpp | 17 +++++++++++++---- 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml b/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml index 4c8e1e6ca5..cf293a06df 100644 --- a/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml +++ b/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml @@ -270,9 +270,11 @@ Item { model: transactionHistoryModel; delegate: Item { width: parent.width; - height: (model.transaction_type === "pendingCount" && model.count !== 0) ? 40 : ((model.status === "confirmed" || model.status === "invalidated") ? transactionText.height + 30 : 0); + height: (model.transaction_type === "pendingCount" && model.count !== 0) ? 40 : + (transactionContainer.visible ? transactionText.height + 30 : 0); Item { + id: pendingCountContainer; visible: model.transaction_type === "pendingCount" && model.count !== 0; anchors.top: parent.top; anchors.left: parent.left; @@ -291,7 +293,9 @@ Item { } Item { - visible: model.transaction_type !== "pendingCount" && (model.status === "confirmed" || model.status === "invalidated"); + id: transactionContainer; + visible: model.transaction_type !== "pendingCount" && + (model.status === "confirmed" || model.status === "invalidated"); anchors.top: parent.top; anchors.left: parent.left; width: parent.width; diff --git a/interface/src/commerce/Ledger.cpp b/interface/src/commerce/Ledger.cpp index 8bb01f6389..d72d896638 100644 --- a/interface/src/commerce/Ledger.cpp +++ b/interface/src/commerce/Ledger.cpp @@ -204,6 +204,7 @@ QString transactionString(const QJsonObject& valueObject) { int sentMoney = valueObject["sent_money"].toInt(); int receivedMoney = valueObject["received_money"].toInt(); int dateInteger = valueObject["created_at"].toInt(); + QString transactionType = valueObject["transaction_type"].toString(); QString message = valueObject["message"].toString(); QDateTime createdAt(QDateTime::fromSecsSinceEpoch(dateInteger, Qt::UTC)); QString result; @@ -211,8 +212,12 @@ QString transactionString(const QJsonObject& valueObject) { if (sentCerts <= 0 && receivedCerts <= 0 && !KNOWN_USERS.contains(valueObject["sender_name"].toString())) { // this is an hfc transfer. if (sentMoney > 0) { - QString recipient = userLink(valueObject["recipient_name"].toString(), valueObject["place_name"].toString()); - result += QString("Money sent to %1").arg(recipient); + if (transactionType == "escrow") { + result += QString("Money transferred to coupon"); + } else { + QString recipient = userLink(valueObject["recipient_name"].toString(), valueObject["place_name"].toString()); + result += QString("Money sent to %1").arg(recipient); + } } else { QString sender = userLink(valueObject["sender_name"].toString(), valueObject["place_name"].toString()); result += QString("Money from %1").arg(sender); @@ -227,8 +232,12 @@ QString transactionString(const QJsonObject& valueObject) { ) { // this is a non-HFC asset transfer. if (sentCerts > 0) { - QString recipient = userLink(valueObject["recipient_name"].toString(), valueObject["place_name"].toString()); - result += QString("Gift sent to %1").arg(recipient); + if (transactionType == "escrow") { + result += QString("Item transferred to coupon"); + } else { + QString recipient = userLink(valueObject["recipient_name"].toString(), valueObject["place_name"].toString()); + result += QString("Gift sent to %1").arg(recipient); + } } else { QString sender = userLink(valueObject["sender_name"].toString(), valueObject["place_name"].toString()); result += QString("Gift from %1").arg(sender); From 8620a5a4c3649903a4f50c19684e95f6749f17bb Mon Sep 17 00:00:00 2001 From: David Back Date: Thu, 6 Dec 2018 13:07:59 -0800 Subject: [PATCH 23/33] CR changes --- scripts/system/html/js/listView.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/system/html/js/listView.js b/scripts/system/html/js/listView.js index b19ba7e0cb..eec3f833ad 100644 --- a/scripts/system/html/js/listView.js +++ b/scripts/system/html/js/listView.js @@ -242,7 +242,7 @@ ListView.prototype = { resize: function() { if (!this.elTableBody || !this.elTableScroll) { - debugPrint("ListView.resize - no valid table body or table scroll element"); + console.log("ListView.resize - no valid table body or table scroll element"); return; } this.preResizeFunction(); @@ -284,7 +284,7 @@ ListView.prototype = { initialize: function() { if (!this.elTableBody || !this.elTableScroll) { - debugPrint("ListView.initialize - no valid table body or table scroll element"); + console.log("ListView.initialize - no valid table body or table scroll element"); return; } From 6d40bc9c2f0fdccccf2b76c499469fda98916192 Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Thu, 29 Nov 2018 19:41:56 +0100 Subject: [PATCH 24/33] userData error message continues popup fix --- scripts/system/html/css/edit-style.css | 24 +++++ scripts/system/html/js/entityProperties.js | 103 +++++++++++++-------- 2 files changed, 86 insertions(+), 41 deletions(-) diff --git a/scripts/system/html/css/edit-style.css b/scripts/system/html/css/edit-style.css index 415d8e567f..641b9bb5fe 100644 --- a/scripts/system/html/css/edit-style.css +++ b/scripts/system/html/css/edit-style.css @@ -1663,3 +1663,27 @@ input.number-slider { .collapse-icon { cursor: pointer; } + +#property-userData-editor.error { + border: 2px solid red; +} + +#property-userData-editorStatus { + color: white; + background-color: red; + padding: 5px; + display: none; + cursor: pointer; +} + +#property-materialData-editor.error { + border: 2px solid red; +} + +#property-materialData-editorStatus { + color: white; + background-color: red; + padding: 5px; + display: none; + cursor: pointer; +} diff --git a/scripts/system/html/js/entityProperties.js b/scripts/system/html/js/entityProperties.js index dc304c6803..bc93a1a9b7 100644 --- a/scripts/system/html/js/entityProperties.js +++ b/scripts/system/html/js/entityProperties.js @@ -1823,7 +1823,7 @@ function createStringProperty(property, elProperty) { type="text" ${propertyData.placeholder ? 'placeholder="' + propertyData.placeholder + '"' : ''} ${propertyData.readOnly ? 'readonly' : ''}> - `) + `); elInput.addEventListener('change', createEmitTextPropertyUpdateFunction(property)); @@ -2359,31 +2359,41 @@ function saveUserData() { saveJSONUserData(true); } +function setJSONError(property, isError) { + $("#property-"+ property + "-editor").toggleClass('error', isError); + var $propertyUserDataEditorStatus = $("#property-"+ property + "-editorStatus"); + $propertyUserDataEditorStatus.css('display', isError ? 'block' : 'none'); + $propertyUserDataEditorStatus.text(isError ? 'Invalid JSON code - look for red X in your code' : ''); +} + function setUserDataFromEditor(noUpdate) { let json = null; + let errorFound = false; try { json = editor.get(); } catch (e) { - alert('Invalid JSON code - look for red X in your code ', +e); + errorFound = true; } - if (json === null) { + + setJSONError('userData', errorFound); + + if (json !== null) { return; + } + + let text = editor.getText(); + if (noUpdate) { + EventBridge.emitWebEvent( + JSON.stringify({ + id: lastEntityID, + type: "saveUserData", + properties: { + userData: text + } + }) + ); } else { - let text = editor.getText(); - if (noUpdate === true) { - EventBridge.emitWebEvent( - JSON.stringify({ - id: lastEntityID, - type: "saveUserData", - properties: { - userData: text - } - }) - ); - return; - } else { - updateProperty('userData', text, false); - } + updateProperty('userData', text, false); } } @@ -2505,9 +2515,10 @@ function hideUserDataSaved() { function showStaticUserData() { if (editor !== null) { - $('#property-userData-static').show(); - $('#property-userData-static').css('height', $('#property-userData-editor').height()); - $('#property-userData-static').text(editor.getText()); + let $propertyUserDataStatic = $('#property-userData-static'); + $propertyUserDataStatic.show(); + $propertyUserDataStatic.css('height', $('#property-userData-editor').height()); + $propertyUserDataStatic.text(editor.getText()); } } @@ -2528,6 +2539,7 @@ function getEditorJSON() { function deleteJSONEditor() { if (editor !== null) { + setJSONError('userData', false); editor.destroy(); editor = null; } @@ -2578,29 +2590,31 @@ function saveMaterialData() { function setMaterialDataFromEditor(noUpdate) { let json = null; + let errorFound = false; try { json = materialEditor.get(); } catch (e) { - alert('Invalid JSON code - look for red X in your code ', +e); + errorFound = true; } + + setJSONError('materialData', errorFound); + if (json === null) { return; + } + let text = materialEditor.getText(); + if (noUpdate) { + EventBridge.emitWebEvent( + JSON.stringify({ + id: lastEntityID, + type: "saveMaterialData", + properties: { + materialData: text + } + }) + ); } else { - let text = materialEditor.getText(); - if (noUpdate === true) { - EventBridge.emitWebEvent( - JSON.stringify({ - id: lastEntityID, - type: "saveMaterialData", - properties: { - materialData: text - } - }) - ); - return; - } else { - updateProperty('materialData', text, false); - } + updateProperty('materialData', text, false); } } @@ -2667,9 +2681,10 @@ function hideMaterialDataSaved() { function showStaticMaterialData() { if (materialEditor !== null) { - $('#property-materialData-static').show(); - $('#property-materialData-static').css('height', $('#property-materialData-editor').height()); - $('#property-materialData-static').text(materialEditor.getText()); + let $propertyMaterialDataStatic = $('#property-materialData-static'); + $propertyMaterialDataStatic.show(); + $propertyMaterialDataStatic.css('height', $('#property-materialData-editor').height()); + $propertyMaterialDataStatic.text(materialEditor.getText()); } } @@ -2955,7 +2970,7 @@ function loaded() { let elServerScriptError = document.getElementById("property-serverScripts-error"); let elServerScriptStatus = document.getElementById("property-serverScripts-status"); elServerScriptError.value = data.errorInfo; - // If we just set elServerScriptError's diplay to block or none, we still end up with + // If we just set elServerScriptError's display to block or none, we still end up with // it's parent contributing 21px bottom padding even when elServerScriptError is display:none. // So set it's parent to block or none elServerScriptError.parentElement.style.display = data.errorInfo ? "block" : "none"; @@ -3314,12 +3329,15 @@ function loaded() { elStaticUserData.setAttribute("id", userDataElementID + "-static"); let elUserDataEditor = document.createElement('div'); elUserDataEditor.setAttribute("id", userDataElementID + "-editor"); + let elUserDataEditorStatus = document.createElement('div'); + elUserDataEditorStatus.setAttribute("id", userDataElementID + "-editorStatus"); let elUserDataSaved = document.createElement('span'); elUserDataSaved.setAttribute("id", userDataElementID + "-saved"); elUserDataSaved.innerText = "Saved!"; elDiv.childNodes[JSON_EDITOR_ROW_DIV_INDEX].appendChild(elUserDataSaved); elDiv.insertBefore(elStaticUserData, elUserData); elDiv.insertBefore(elUserDataEditor, elUserData); + elDiv.insertBefore(elUserDataEditorStatus, elUserData); // Material Data let materialDataProperty = properties["materialData"]; @@ -3330,12 +3348,15 @@ function loaded() { elStaticMaterialData.setAttribute("id", materialDataElementID + "-static"); let elMaterialDataEditor = document.createElement('div'); elMaterialDataEditor.setAttribute("id", materialDataElementID + "-editor"); + let elMaterialDataEditorStatus = document.createElement('div'); + elMaterialDataEditorStatus.setAttribute("id", materialDataElementID + "-editorStatus"); let elMaterialDataSaved = document.createElement('span'); elMaterialDataSaved.setAttribute("id", materialDataElementID + "-saved"); elMaterialDataSaved.innerText = "Saved!"; elDiv.childNodes[JSON_EDITOR_ROW_DIV_INDEX].appendChild(elMaterialDataSaved); elDiv.insertBefore(elStaticMaterialData, elMaterialData); elDiv.insertBefore(elMaterialDataEditor, elMaterialData); + elDiv.insertBefore(elMaterialDataEditorStatus, elMaterialData); // Special Property Callbacks let elParentMaterialNameString = getPropertyInputElement("materialNameToReplace"); From 2771e2862e15e54b6cfa7edf696ef0e18d2f58f7 Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Thu, 29 Nov 2018 19:55:34 +0100 Subject: [PATCH 25/33] add missing material data json error clear on delete --- scripts/system/html/js/entityProperties.js | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/system/html/js/entityProperties.js b/scripts/system/html/js/entityProperties.js index bc93a1a9b7..ed03d62e70 100644 --- a/scripts/system/html/js/entityProperties.js +++ b/scripts/system/html/js/entityProperties.js @@ -2705,6 +2705,7 @@ function getMaterialEditorJSON() { function deleteJSONMaterialEditor() { if (materialEditor !== null) { + setJSONError('materialData', false); materialEditor.destroy(); materialEditor = null; } From 4f2289b347db6342006d41f98fdc89604f7525e1 Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Thu, 6 Dec 2018 23:42:12 +0100 Subject: [PATCH 26/33] CR fixes --- scripts/system/html/js/entityProperties.js | 30 +++++++++++----------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/scripts/system/html/js/entityProperties.js b/scripts/system/html/js/entityProperties.js index ed03d62e70..6f8f2ef4bc 100644 --- a/scripts/system/html/js/entityProperties.js +++ b/scripts/system/html/js/entityProperties.js @@ -1429,13 +1429,13 @@ const TEXTURE_ELEMENTS = { const JSON_EDITOR_ROW_DIV_INDEX = 2; -var elGroups = {}; -var properties = {}; -var colorPickers = {}; -var particlePropertyUpdates = {}; -var selectedEntityProperties; -var lastEntityID = null; -var createAppTooltip = new CreateAppTooltip(); +let elGroups = {}; +let properties = {}; +let colorPickers = {}; +let particlePropertyUpdates = {}; +let selectedEntityProperties; +let lastEntityID = null; +let createAppTooltip = new CreateAppTooltip(); let currentSpaceMode = PROPERTY_SPACE_MODE.LOCAL; function createElementFromHTML(htmlString) { @@ -1689,7 +1689,7 @@ function updateProperty(originalPropertyName, propertyValue, isParticleProperty) } } -var particleSyncDebounce = _.debounce(function () { +let particleSyncDebounce = _.debounce(function () { updateProperties(particlePropertyUpdates); particlePropertyUpdates = {}; }, DEBOUNCE_TIMEOUT); @@ -2361,7 +2361,7 @@ function saveUserData() { function setJSONError(property, isError) { $("#property-"+ property + "-editor").toggleClass('error', isError); - var $propertyUserDataEditorStatus = $("#property-"+ property + "-editorStatus"); + let $propertyUserDataEditorStatus = $("#property-"+ property + "-editorStatus"); $propertyUserDataEditorStatus.css('display', isError ? 'block' : 'none'); $propertyUserDataEditorStatus.text(isError ? 'Invalid JSON code - look for red X in your code' : ''); } @@ -2377,7 +2377,7 @@ function setUserDataFromEditor(noUpdate) { setJSONError('userData', errorFound); - if (json !== null) { + if (errorFound) { return; } @@ -2452,7 +2452,7 @@ function multiDataUpdater(groupName, updateKeyPair, userDataElement, defaults, r updateProperties(propertyUpdate, false); } -var editor = null; +let editor = null; function createJSONEditor() { let container = document.getElementById("property-userData-editor"); @@ -2545,7 +2545,7 @@ function deleteJSONEditor() { } } -var savedJSONTimer = null; +let savedJSONTimer = null; function saveJSONUserData(noUpdate) { setUserDataFromEditor(noUpdate); @@ -2599,7 +2599,7 @@ function setMaterialDataFromEditor(noUpdate) { setJSONError('materialData', errorFound); - if (json === null) { + if (errorFound) { return; } let text = materialEditor.getText(); @@ -2618,7 +2618,7 @@ function setMaterialDataFromEditor(noUpdate) { } } -var materialEditor = null; +let materialEditor = null; function createJSONMaterialEditor() { let container = document.getElementById("property-materialData-editor"); @@ -2711,7 +2711,7 @@ function deleteJSONMaterialEditor() { } } -var savedMaterialJSONTimer = null; +let savedMaterialJSONTimer = null; function saveJSONMaterialData(noUpdate) { setMaterialDataFromEditor(noUpdate); From 62a06fe3834fe91248c0d970daf90c2a9b648916 Mon Sep 17 00:00:00 2001 From: Clement Date: Thu, 29 Nov 2018 11:10:57 -0800 Subject: [PATCH 27/33] Update build docs --- BUILD.md | 20 ++++++++++++++++---- BUILD_WIN.md | 23 +++++------------------ 2 files changed, 21 insertions(+), 22 deletions(-) diff --git a/BUILD.md b/BUILD.md index 00b17743e9..25bbc89951 100644 --- a/BUILD.md +++ b/BUILD.md @@ -13,7 +13,7 @@ ### CMake External Project Dependencies -These dependencies need not be installed manually. They are automatically downloaded on the platforms where they are required. +These dependencies need not be installed manually. They are automatically downloaded on the platforms where they are required. - [Bullet Physics Engine](https://github.com/bulletphysics/bullet3/releases): 2.83 - [glm](https://glm.g-truc.net/0.9.8/index.html): 0.9.8 - [Oculus SDK](https://developer.oculus.com/downloads/): 1.11 (Win32) / 0.5 (Mac) @@ -22,7 +22,7 @@ These dependencies need not be installed manually. They are automatically downlo - [QuaZip](https://sourceforge.net/projects/quazip/files/quazip/): 0.7.3 - [SDL2](https://www.libsdl.org/download-2.0.php): 2.0.3 - [Intel Threading Building Blocks](https://www.threadingbuildingblocks.org/): 4.3 -- [vcpkg](https://github.com/highfidelity/vcpkg): +- [vcpkg](https://github.com/highfidelity/vcpkg): - [VHACD](https://github.com/virneo/v-hacd) - [zlib](http://www.zlib.net/): 1.28 (Win32 only) - [nvtt](https://github.com/highfidelity/nvidia-texture-tools): 2.1.1 (customized) @@ -48,6 +48,18 @@ The path it needs to be set to will depend on where and how Qt5 was installed. e export QT_CMAKE_PREFIX_PATH=/usr/local/Cellar/qt5/5.10.1/lib/cmake export QT_CMAKE_PREFIX_PATH=/usr/local/opt/qt5/lib/cmake +#### Vcpkg + +Hifi uses vcpkg to download and build dependencies. +You do not need to install vcpkg. + +Building the dependencies can be lengthy and the resulting files will be stored in your OS temp directory. +However, those files can potentially get cleaned up by the OS, so in order to avoid this and having to redo the lengthy build step, you can set the following environment variable: + +export HIFI_VCPKG_BASE=/path/to/directory + +Where /path/to/directory is the path to a directory where you wish the build files to get stored. + #### Generating build files Create a build directory in the root of your checkout and then run the CMake build from there. This will keep the rest of the directory clean. @@ -80,7 +92,7 @@ In the examples below the variable $NAME would be replaced by the name of the de ### Optional Components -#### Build Options +#### Build Options The following build options can be used when running CMake @@ -89,7 +101,7 @@ The following build options can be used when running CMake * BUILD_TESTS * BUILD_TOOLS -#### Developer Build Options +#### Developer Build Options * USE_GLES * DISABLE_UI diff --git a/BUILD_WIN.md b/BUILD_WIN.md index 073b048911..a81fca5900 100644 --- a/BUILD_WIN.md +++ b/BUILD_WIN.md @@ -19,7 +19,7 @@ If you do not wish to use the Python installation bundled with Visual Studio, yo ### Step 2. Installing CMake -Download and install the latest version of CMake 3.9. +Download and install the latest version of CMake 3.9. Download the file named win64-x64 Installer from the [CMake Website](https://cmake.org/download/). You can access the installer on this [3.9 Version page](https://cmake.org/files/v3.9/). During installation, make sure to check "Add CMake to system PATH for all users" when prompted. @@ -35,20 +35,7 @@ Go to `Control Panel > System > Advanced System Settings > Environment Variables * Set "Variable name": `QT_CMAKE_PREFIX_PATH` * Set "Variable value": `C:\Qt\5.10.1\msvc2017_64\lib\cmake` -### Step 5. Installing [vcpkg](https://github.com/Microsoft/vcpkg) - - * Clone the VCPKG [repository](https://github.com/Microsoft/vcpkg) - * Follow the instructions in the [readme](https://github.com/Microsoft/vcpkg/blob/master/README.md) to bootstrap vcpkg - * Note, you may need to do these in a _Developer Command Prompt_ - * Set an environment variable VCPKG_ROOT to the location of the cloned repository - * Close and re-open any command prompts after setting the environment variable so that they will pick up the change - -### Step 6. Installing OpenSSL via vcpkg - - * In the vcpkg directory, install the 64 bit OpenSSL package with the command `.\vcpkg install openssl:x64-windows` - * Once the build completes you should have a file `ssl.h` in `${VCPKG_ROOT}/installed/x64-windows/include/openssl` - -### Step 7. Running CMake to Generate Build Files +### Step 5. Running CMake to Generate Build Files Run Command Prompt from Start and run the following commands: ``` @@ -58,9 +45,9 @@ cd build cmake .. -G "Visual Studio 15 Win64" ``` -Where `%HIFI_DIR%` is the directory for the highfidelity repository. +Where `%HIFI_DIR%` is the directory for the highfidelity repository. -### Step 8. Making a Build +### Step 6. Making a Build Open `%HIFI_DIR%\build\hifi.sln` using Visual Studio. @@ -68,7 +55,7 @@ Change the Solution Configuration (menu ribbon under the menu bar, next to the g Run from the menu bar `Build > Build Solution`. -### Step 9. Testing Interface +### Step 7. Testing Interface Create another environment variable (see Step #4) * Set "Variable name": `_NO_DEBUG_HEAP` From 1a3708c49dca4479429f1ca98cca4f3ad4c601f9 Mon Sep 17 00:00:00 2001 From: David Back Date: Thu, 6 Dec 2018 16:14:28 -0800 Subject: [PATCH 28/33] better drag drop fix --- scripts/system/html/js/utils.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/scripts/system/html/js/utils.js b/scripts/system/html/js/utils.js index fe96e8b79e..d61b4d1762 100644 --- a/scripts/system/html/js/utils.js +++ b/scripts/system/html/js/utils.js @@ -11,13 +11,17 @@ function disableDragDrop() { document.addEventListener("drop", function(event) { event.preventDefault(); - event.dataTransfer.effectAllowed = "none"; - event.dataTransfer.dropEffect = "none"; - }, false); + }); document.addEventListener("dragover", function(event) { - event.preventDefault(); event.dataTransfer.effectAllowed = "none"; event.dataTransfer.dropEffect = "none"; + event.preventDefault(); + }); + + document.addEventListener("dragenter", function(event) { + event.dataTransfer.effectAllowed = "none"; + event.dataTransfer.dropEffect = "none"; + event.preventDefault(); }, false); } From 818339823c64a65fd21a120a9c7da38a4d4da7d5 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Thu, 6 Dec 2018 14:50:16 -0800 Subject: [PATCH 29/33] Add new number type in Create --- scripts/system/html/css/edit-style.css | 5 + scripts/system/html/js/entityProperties.js | 159 +++++++++++++-------- 2 files changed, 103 insertions(+), 61 deletions(-) diff --git a/scripts/system/html/css/edit-style.css b/scripts/system/html/css/edit-style.css index 13825119ce..5bfa5c0609 100644 --- a/scripts/system/html/css/edit-style.css +++ b/scripts/system/html/css/edit-style.css @@ -1697,3 +1697,8 @@ input.number-slider { display: none; cursor: pointer; } + +input[type=number].hide-spinner::-webkit-inner-spin-button { + -webkit-appearance: none; + visibility: hidden; +} diff --git a/scripts/system/html/js/entityProperties.js b/scripts/system/html/js/entityProperties.js index 84a1ab2a12..972c5f63be 100644 --- a/scripts/system/html/js/entityProperties.js +++ b/scripts/system/html/js/entityProperties.js @@ -75,7 +75,7 @@ const GROUPS = [ }, { label: "Parent Joint Index", - type: "number", + type: "number-draggable", propertyID: "parentJointIndex", }, { @@ -135,7 +135,7 @@ const GROUPS = [ }, { label: "Line Height", - type: "number", + type: "number-draggable", min: 0, step: 0.005, decimals: 4, @@ -183,7 +183,7 @@ const GROUPS = [ }, { label: "Light Intensity", - type: "number", + type: "number-draggable", min: 0, max: 10, step: 0.1, @@ -193,7 +193,7 @@ const GROUPS = [ }, { label: "Light Horizontal Angle", - type: "number", + type: "number-draggable", multiplier: DEGREES_TO_RADIANS, decimals: 2, unit: "deg", @@ -202,7 +202,7 @@ const GROUPS = [ }, { label: "Light Vertical Angle", - type: "number", + type: "number-draggable", multiplier: DEGREES_TO_RADIANS, decimals: 2, unit: "deg", @@ -241,7 +241,7 @@ const GROUPS = [ }, { label: "Ambient Intensity", - type: "number", + type: "number-draggable", min: 0, max: 10, step: 0.1, @@ -270,7 +270,7 @@ const GROUPS = [ }, { label: "Range", - type: "number", + type: "number-draggable", min: 5, max: 10000, step: 5, @@ -287,7 +287,7 @@ const GROUPS = [ }, { label: "Base", - type: "number", + type: "number-draggable", min: -1000, max: 1000, step: 10, @@ -298,7 +298,7 @@ const GROUPS = [ }, { label: "Ceiling", - type: "number", + type: "number-draggable", min: -1000, max: 5000, step: 10, @@ -315,7 +315,7 @@ const GROUPS = [ }, { label: "Background Blend", - type: "number", + type: "number-draggable", min: 0, max: 1, step: 0.01, @@ -337,7 +337,7 @@ const GROUPS = [ }, { label: "Glare Angle", - type: "number", + type: "number-draggable", min: 0, max: 180, step: 1, @@ -353,7 +353,7 @@ const GROUPS = [ }, { label: "Bloom Intensity", - type: "number", + type: "number-draggable", min: 0, max: 1, step: 0.01, @@ -363,7 +363,7 @@ const GROUPS = [ }, { label: "Bloom Threshold", - type: "number", + type: "number-draggable", min: 0, max: 1, step: 0.01, @@ -373,7 +373,7 @@ const GROUPS = [ }, { label: "Bloom Size", - type: "number", + type: "number-draggable", min: 0, max: 2, step: 0.01, @@ -432,22 +432,22 @@ const GROUPS = [ }, { label: "Animation Frame", - type: "number", + type: "number-draggable", propertyID: "animation.currentFrame", }, { label: "First Frame", - type: "number", + type: "number-draggable", propertyID: "animation.firstFrame", }, { label: "Last Frame", - type: "number", + type: "number-draggable", propertyID: "animation.lastFrame", }, { label: "Animation FPS", - type: "number", + type: "number-draggable", propertyID: "animation.fps", }, { @@ -486,7 +486,7 @@ const GROUPS = [ }, { label: "Source Resolution", - type: "number", + type: "number-draggable", propertyID: "dpi", }, ] @@ -503,7 +503,7 @@ const GROUPS = [ }, { label: "Intensity", - type: "number", + type: "number-draggable", min: 0, step: 0.1, decimals: 1, @@ -511,7 +511,7 @@ const GROUPS = [ }, { label: "Fall-Off Radius", - type: "number", + type: "number-draggable", min: 0, step: 0.1, decimals: 1, @@ -525,14 +525,14 @@ const GROUPS = [ }, { label: "Spotlight Exponent", - type: "number", + type: "number-draggable", step: 0.01, decimals: 2, propertyID: "exponent", }, { label: "Spotlight Cut-Off", - type: "number", + type: "number-draggable", step: 0.01, decimals: 2, propertyID: "cutoff", @@ -563,7 +563,7 @@ const GROUPS = [ }, { label: "Submesh to Replace", - type: "number", + type: "number-draggable", min: 0, step: 1, propertyID: "submeshToReplace", @@ -577,7 +577,7 @@ const GROUPS = [ }, { label: "Priority", - type: "number", + type: "number-draggable", min: 0, propertyID: "priority", }, @@ -612,7 +612,7 @@ const GROUPS = [ }, { label: "Material Rotation", - type: "number", + type: "number-draggable", step: 0.1, decimals: 2, unit: "deg", @@ -636,7 +636,7 @@ const GROUPS = [ }, { label: "Lifespan", - type: "number", + type: "number-draggable", unit: "s", min: 0.01, max: 10, @@ -646,7 +646,7 @@ const GROUPS = [ }, { label: "Max Particles", - type: "number", + type: "number-draggable", min: 1, max: 10000, step: 1, @@ -667,7 +667,7 @@ const GROUPS = [ properties: [ { label: "Emit Rate", - type: "number", + type: "number-draggable", min: 1, max: 1000, step: 1, @@ -675,7 +675,7 @@ const GROUPS = [ }, { label: "Emit Speed", - type: "number", + type: "number-draggable", min: 0, max: 5, step: 0.01, @@ -684,7 +684,7 @@ const GROUPS = [ }, { label: "Speed Spread", - type: "number", + type: "number-draggable", min: 0, max: 5, step: 0.01, @@ -703,7 +703,7 @@ const GROUPS = [ }, { label: "Emit Radius Start", - type: "number", + type: "number-draggable", min: 0, max: 1, step: 0.01, @@ -738,7 +738,7 @@ const GROUPS = [ properties: [ { label: "Start", - type: "number", + type: "number-draggable", min: 0, max: 4, step: 0.01, @@ -748,7 +748,7 @@ const GROUPS = [ }, { label: "Middle", - type: "number", + type: "number-draggable", min: 0, max: 4, step: 0.01, @@ -757,7 +757,7 @@ const GROUPS = [ }, { label: "Finish", - type: "number", + type: "number-draggable", min: 0, max: 4, step: 0.01, @@ -769,7 +769,7 @@ const GROUPS = [ }, { label: "Size Spread", - type: "number", + type: "number-draggable", min: 0, max: 4, step: 0.01, @@ -825,7 +825,7 @@ const GROUPS = [ properties: [ { label: "Start", - type: "number", + type: "number-draggable", min: 0, max: 1, step: 0.01, @@ -835,7 +835,7 @@ const GROUPS = [ }, { label: "Middle", - type: "number", + type: "number-draggable", min: 0, max: 1, step: 0.01, @@ -844,7 +844,7 @@ const GROUPS = [ }, { label: "Finish", - type: "number", + type: "number-draggable", min: 0, max: 1, step: 0.01, @@ -856,7 +856,7 @@ const GROUPS = [ }, { label: "Alpha Spread", - type: "number", + type: "number-draggable", min: 0, max: 1, step: 0.01, @@ -901,7 +901,7 @@ const GROUPS = [ properties: [ { label: "Start", - type: "number", + type: "number-draggable", min: -360, max: 360, step: 1, @@ -913,7 +913,7 @@ const GROUPS = [ }, { label: "Middle", - type: "number", + type: "number-draggable", min: -360, max: 360, step: 1, @@ -924,7 +924,7 @@ const GROUPS = [ }, { label: "Finish", - type: "number", + type: "number-draggable", min: -360, max: 360, step: 1, @@ -938,7 +938,7 @@ const GROUPS = [ }, { label: "Spin Spread", - type: "number", + type: "number-draggable", min: 0, max: 360, step: 1, @@ -965,7 +965,7 @@ const GROUPS = [ properties: [ { label: "Start", - type: "number", + type: "number-draggable", min: 0, max: 180, step: 1, @@ -976,7 +976,7 @@ const GROUPS = [ }, { label: "Finish", - type: "number", + type: "number-draggable", min: 0, max: 180, step: 1, @@ -993,7 +993,7 @@ const GROUPS = [ properties: [ { label: "Start", - type: "number", + type: "number-draggable", min: -180, max: 180, step: 1, @@ -1004,7 +1004,7 @@ const GROUPS = [ }, { label: "Finish", - type: "number", + type: "number-draggable", min: -180, max: 180, step: 1, @@ -1089,7 +1089,7 @@ const GROUPS = [ }, { label: "Scale", - type: "number", + type: "number-draggable", defaultValue: 100, unit: "%", buttons: [ { id: "rescale", label: "Rescale", className: "blue", onClick: rescaleDimensions }, @@ -1131,14 +1131,14 @@ const GROUPS = [ }, { label: "Clone Lifetime", - type: "number", + type: "number-draggable", unit: "s", propertyID: "cloneLifetime", showPropertyRule: { "cloneable": "true" }, }, { label: "Clone Limit", - type: "number", + type: "number-draggable", propertyID: "cloneLimit", showPropertyRule: { "cloneable": "true" }, }, @@ -1197,7 +1197,7 @@ const GROUPS = [ }, { label: "Lifetime", - type: "string", + type: "number", unit: "s", propertyID: "lifetime", }, @@ -1291,7 +1291,7 @@ const GROUPS = [ }, { label: "Linear Damping", - type: "number", + type: "number-draggable", min: 0, max: 1, step: 0.01, @@ -1310,7 +1310,7 @@ const GROUPS = [ }, { label: "Angular Damping", - type: "number", + type: "number-draggable", min: 0, max: 1, step: 0.01, @@ -1319,7 +1319,7 @@ const GROUPS = [ }, { label: "Bounciness", - type: "number", + type: "number-draggable", min: 0, max: 1, step: 0.01, @@ -1328,7 +1328,7 @@ const GROUPS = [ }, { label: "Friction", - type: "number", + type: "number-draggable", min: 0, max: 10, step: 0.1, @@ -1337,7 +1337,7 @@ const GROUPS = [ }, { label: "Density", - type: "number", + type: "number-draggable", min: 100, max: 10000, step: 1, @@ -1454,12 +1454,13 @@ function getPropertyInputElement(propertyID) { let property = properties[propertyID]; switch (property.data.type) { case 'string': + case 'number': case 'bool': case 'dropdown': case 'textarea': case 'texture': return property.elInput; - case 'number': + case 'number-draggable': return property.elNumber.elInput; case 'vec3': case 'vec2': @@ -1529,6 +1530,7 @@ function resetProperties() { let propertyData = property.data; switch (propertyData.type) { + case 'number': case 'string': { property.elInput.value = ""; break; @@ -1537,7 +1539,7 @@ function resetProperties() { property.elInput.checked = false; break; } - case 'number': { + case 'number-draggable': { if (propertyData.defaultValue !== undefined) { property.elNumber.setValue(propertyData.defaultValue); } else { @@ -1873,7 +1875,33 @@ function createBoolProperty(property, elProperty) { return elInput; } -function createNumberProperty(property, elProperty) { +function createNumberProperty(property, elProperty) { + let elementID = property.elementID; + let propertyData = property.data; + + elProperty.className = "text"; + + let elInput = createElementFromHTML(` + + `) + + + elInput.addEventListener('change', createEmitTextPropertyUpdateFunction(property)); + + elProperty.appendChild(elInput); + + if (propertyData.buttons !== undefined) { + addButtons(elProperty, elementID, propertyData.buttons, false); + } + + return elInput; +} + +function createNumberSpinnerProperty(property, elProperty) { let elementID = property.elementID; let propertyData = property.data; @@ -2217,7 +2245,11 @@ function createProperty(propertyData, propertyElementID, propertyName, propertyI break; } case 'number': { - property.elNumber = createNumberProperty(property, elProperty); + property.elInput = createNumberProperty(property, elProperty); + break; + } + case 'number-draggable': { + property.elNumber = createNumberSpinnerProperty(property, elProperty); break; } case 'vec3': { @@ -3098,6 +3130,7 @@ function loaded() { let isPropertyNotNumber = false; switch (propertyData.type) { case 'number': + case 'number-draggable': isPropertyNotNumber = isNaN(propertyValue) || propertyValue === null; break; case 'vec3': @@ -3130,6 +3163,10 @@ function loaded() { break; } case 'number': { + property.elInput.value = propertyValue; + break; + } + case 'number-draggable': { let multiplier = propertyData.multiplier !== undefined ? propertyData.multiplier : 1; let value = propertyValue / multiplier; if (propertyData.round !== undefined) { From c9ab5c23eab77cbb92fb717cab9671fa45cdd35d Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Thu, 6 Dec 2018 15:30:11 -0800 Subject: [PATCH 30/33] Add support for min/max/step/defaultValue on number field --- scripts/system/html/js/entityProperties.js | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/scripts/system/html/js/entityProperties.js b/scripts/system/html/js/entityProperties.js index 972c5f63be..cfead83851 100644 --- a/scripts/system/html/js/entityProperties.js +++ b/scripts/system/html/js/entityProperties.js @@ -1533,6 +1533,11 @@ function resetProperties() { case 'number': case 'string': { property.elInput.value = ""; + if (propertyData.defaultValue !== undefined) { + property.elInput.setValue(propertyData.defaultValue); + } else { + property.elInput.setValue(""); + } break; } case 'bool': { @@ -1889,6 +1894,18 @@ function createNumberProperty(property, elProperty) { ${propertyData.readOnly ? 'readonly' : ''}> `) + if (propertyData.min !== undefined) { + elInput.setAttribute("min", propertyData.min); + } + if (propertyData.max !== undefined) { + elInput.setAttribute("max", propertyData.max); + } + if (propertyData.step !== undefined) { + elInput.setAttribute("step", propertyData.step); + } + if (propertyData.defaultValue !== undefined) { + elInput.value = propertyData.defaultValue; + } elInput.addEventListener('change', createEmitTextPropertyUpdateFunction(property)); From f42c31ca36c311432cafaaa679e8dc3ae330a01f Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Thu, 6 Dec 2018 15:35:25 -0800 Subject: [PATCH 31/33] Update number property to reuse numberdraggable emit function --- scripts/system/html/js/entityProperties.js | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/scripts/system/html/js/entityProperties.js b/scripts/system/html/js/entityProperties.js index cfead83851..1717f31832 100644 --- a/scripts/system/html/js/entityProperties.js +++ b/scripts/system/html/js/entityProperties.js @@ -1532,11 +1532,10 @@ function resetProperties() { switch (propertyData.type) { case 'number': case 'string': { - property.elInput.value = ""; if (propertyData.defaultValue !== undefined) { - property.elInput.setValue(propertyData.defaultValue); + property.elInput.value = propertyData.defaultValue; } else { - property.elInput.setValue(""); + property.elInput.value = ""; } break; } @@ -1907,7 +1906,7 @@ function createNumberProperty(property, elProperty) { elInput.value = propertyData.defaultValue; } - elInput.addEventListener('change', createEmitTextPropertyUpdateFunction(property)); + elInput.addEventListener('change', createEmitNumberPropertyUpdateFunction(property)); elProperty.appendChild(elInput); @@ -1918,7 +1917,7 @@ function createNumberProperty(property, elProperty) { return elInput; } -function createNumberSpinnerProperty(property, elProperty) { +function createNumberDraggableProperty(property, elProperty) { let elementID = property.elementID; let propertyData = property.data; @@ -2266,7 +2265,7 @@ function createProperty(propertyData, propertyElementID, propertyName, propertyI break; } case 'number-draggable': { - property.elNumber = createNumberSpinnerProperty(property, elProperty); + property.elNumber = createNumberDraggableProperty(property, elProperty); break; } case 'vec3': { From bf844e047240cde91502e1e0e135d2e4bd1c45a3 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Wed, 5 Dec 2018 16:07:45 -0800 Subject: [PATCH 32/33] Add hiding of certified properties in Create --- scripts/system/html/js/entityList.js | 8 ++++-- scripts/system/html/js/entityProperties.js | 33 ++++++++++++++++------ scripts/system/libraries/entityList.js | 3 +- 3 files changed, 31 insertions(+), 13 deletions(-) diff --git a/scripts/system/html/js/entityList.js b/scripts/system/html/js/entityList.js index 9e6eb3311a..89b56c7f7b 100644 --- a/scripts/system/html/js/entityList.js +++ b/scripts/system/html/js/entityList.js @@ -23,6 +23,7 @@ const SCROLLBAR_WIDTH = 20; const RESIZER_WIDTH = 10; const DELTA_X_MOVE_COLUMNS_THRESHOLD = 2; const DELTA_X_COLUMN_SWAP_POSITION = 5; +const CERTIFIED_PLACEHOLDER = "** Certified **"; const COLUMNS = { type: { @@ -635,10 +636,11 @@ function loaded() { id: entity.id, name: entity.name, type: type, - url: filename, - fullUrl: entity.url, + url: entity.certificateID === "" ? filename : "" + CERTIFIED_PLACEHOLDER + "", + fullUrl: entity.certificateID === "" ? filename : CERTIFIED_PLACEHOLDER, locked: entity.locked, visible: entity.visible, + certificateID: entity.certificateID, verticesCount: displayIfNonZero(entity.verticesCount), texturesCount: displayIfNonZero(entity.texturesCount), texturesSize: decimalMegabytes(entity.texturesSize), @@ -876,7 +878,7 @@ function loaded() { if (column.data.glyph) { elCell.innerHTML = itemData[column.data.propertyID] ? column.data.columnHeader : null; } else { - elCell.innerText = itemData[column.data.propertyID]; + elCell.innerHTML = itemData[column.data.propertyID]; } elCell.style = "min-width:" + column.widthPx + "px;" + "max-width:" + column.widthPx + "px;"; elCell.className = createColumnClassName(column.columnID); diff --git a/scripts/system/html/js/entityProperties.js b/scripts/system/html/js/entityProperties.js index 84a1ab2a12..c39761e311 100644 --- a/scripts/system/html/js/entityProperties.js +++ b/scripts/system/html/js/entityProperties.js @@ -390,7 +390,9 @@ const GROUPS = [ { label: "Model", type: "string", + placeholder: "URL", propertyID: "modelURL", + hideIfCertified: true, }, { label: "Collision Shape", @@ -404,11 +406,13 @@ const GROUPS = [ label: "Compound Shape", type: "string", propertyID: "compoundShapeURL", + hideIfCertified: true, }, { label: "Animation", type: "string", propertyID: "animation.url", + hideIfCertified: true, }, { label: "Play Automatically", @@ -460,6 +464,7 @@ const GROUPS = [ type: "textarea", propertyID: "originalTextures", readOnly: true, + hideIfCertified: true, }, ] }, @@ -1181,6 +1186,7 @@ const GROUPS = [ buttons: [ { id: "reload", label: "F", className: "glyph", onClick: reloadScripts } ], propertyID: "script", placeholder: "URL", + hideIfCertified: true, }, { label: "Server Script", @@ -1267,6 +1273,7 @@ const GROUPS = [ placeholder: "URL", propertyID: "collisionSoundURL", showPropertyRule: { "collisionless": "false" }, + hideIfCertified: true, }, { label: "Dynamic", @@ -3084,6 +3091,15 @@ function loaded() { showGroupsForType(selectedEntityProperties.type); + if (selectedEntityProperties.locked) { + disableProperties(); + getPropertyInputElement("locked").removeAttribute('disabled'); + } else { + enableProperties(); + disableSaveUserDataButton(); + disableSaveMaterialDataButton() + } + for (let propertyID in properties) { let property = properties[propertyID]; let propertyData = property.data; @@ -3094,6 +3110,14 @@ function loaded() { if (propertyValue === undefined && !isSubProperty) { continue; } + + if (propertyData.hideIfCertified) { + let shouldHide = selectedEntityProperties.certificateID !== ""; + if (shouldHide) { + propertyValue = "** Certified **"; + } + property.elInput.disabled = shouldHide; + } let isPropertyNotNumber = false; switch (propertyData.type) { @@ -3272,15 +3296,6 @@ function loaded() { hideMaterialDataSaved(); } - if (selectedEntityProperties.locked) { - disableProperties(); - getPropertyInputElement("locked").removeAttribute('disabled'); - } else { - enableProperties(); - disableSaveUserDataButton(); - disableSaveMaterialDataButton() - } - let activeElement = document.activeElement; if (doSelectElement && typeof activeElement.select !== "undefined") { activeElement.select(); diff --git a/scripts/system/libraries/entityList.js b/scripts/system/libraries/entityList.js index eeb16fd60d..f27ab6caf2 100644 --- a/scripts/system/libraries/entityList.js +++ b/scripts/system/libraries/entityList.js @@ -164,7 +164,7 @@ EntityListTool = function(shouldUseEditTabletApp) { var cameraPosition = Camera.position; PROFILE("getMultipleProperties", function () { var multipleProperties = Entities.getMultipleEntityProperties(ids, ['name', 'type', 'locked', - 'visible', 'renderInfo', 'modelURL', 'materialURL', 'script']); + 'visible', 'renderInfo', 'modelURL', 'materialURL', 'script', 'certificateID']); for (var i = 0; i < multipleProperties.length; i++) { var properties = multipleProperties[i]; @@ -182,6 +182,7 @@ EntityListTool = function(shouldUseEditTabletApp) { url: url, locked: properties.locked, visible: properties.visible, + certificateID: properties.certificateID, verticesCount: (properties.renderInfo !== undefined ? valueIfDefined(properties.renderInfo.verticesCount) : ""), texturesCount: (properties.renderInfo !== undefined ? From 51fd742722e9919ae8b0f2d11b899577351557d5 Mon Sep 17 00:00:00 2001 From: birarda Date: Fri, 7 Dec 2018 11:17:44 -0800 Subject: [PATCH 33/33] fix for initial send of non-instanced trait --- libraries/avatars/src/ClientTraitsHandler.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/avatars/src/ClientTraitsHandler.cpp b/libraries/avatars/src/ClientTraitsHandler.cpp index cbc8e93745..3e24c1f9ad 100644 --- a/libraries/avatars/src/ClientTraitsHandler.cpp +++ b/libraries/avatars/src/ClientTraitsHandler.cpp @@ -104,7 +104,7 @@ void ClientTraitsHandler::sendChangedTraitsToMixer() { // we double check that it is a simple iterator here auto traitType = static_cast(std::distance(traitStatusesCopy.simpleCBegin(), simpleIt)); - if (_shouldPerformInitialSend || *simpleIt == Updated) { + if (initialSend || *simpleIt == Updated) { if (traitType == AvatarTraits::SkeletonModelURL) { _owningAvatar->packTrait(traitType, *traitsPacketList);