From 5cdcf1c53e6b158d3619a71903b4476a9d20164a Mon Sep 17 00:00:00 2001 From: Oren Hurvitz Date: Tue, 17 Apr 2018 11:53:37 +0300 Subject: [PATCH 01/63] Eliminated spurious "resolved_item" warnings in CMake Previously, building the project produced many warnings with this message: "resolved_item == resolved_embedded_item - not copying..." These warnings are distracting as they make it difficult to see actual warnings. This commit changes the warnings to status messages. This fix was copied from: https://github.com/jherico/OculusMinimalExample/blob/master/cmake/templates/FixupBundlePostBuild.cmake.in --- cmake/templates/FixupBundlePostBuild.cmake.in | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/cmake/templates/FixupBundlePostBuild.cmake.in b/cmake/templates/FixupBundlePostBuild.cmake.in index 57379bb48b..4ebb0ea1a6 100644 --- a/cmake/templates/FixupBundlePostBuild.cmake.in +++ b/cmake/templates/FixupBundlePostBuild.cmake.in @@ -11,6 +11,36 @@ include(BundleUtilities) +# replace copy_resolved_item_into_bundle +# +# The official version of copy_resolved_item_into_bundle will print out a "warning:" when +# the resolved item matches the resolved embedded item. This not not really an issue that +# should rise to the level of a "warning" so we replace this message with a "status:" +# +# Source: https://github.com/jherico/OculusMinimalExample/blob/master/cmake/templates/FixupBundlePostBuild.cmake.in +# +function(copy_resolved_item_into_bundle resolved_item resolved_embedded_item) + if (WIN32) + # ignore case on Windows + string(TOLOWER "${resolved_item}" resolved_item_compare) + string(TOLOWER "${resolved_embedded_item}" resolved_embedded_item_compare) + else() + set(resolved_item_compare "${resolved_item}") + set(resolved_embedded_item_compare "${resolved_embedded_item}") + endif() + + if ("${resolved_item_compare}" STREQUAL "${resolved_embedded_item_compare}") + # this is our only change from the original version + message(STATUS "status: resolved_item == resolved_embedded_item - not copying...") + else() + #message(STATUS "copying COMMAND ${CMAKE_COMMAND} -E copy ${resolved_item} ${resolved_embedded_item}") + execute_process(COMMAND ${CMAKE_COMMAND} -E copy "${resolved_item}" "${resolved_embedded_item}") + if(UNIX AND NOT APPLE) + file(RPATH_REMOVE FILE "${resolved_embedded_item}") + endif() + endif() +endfunction() + function(gp_resolved_file_type_override resolved_file type_var) if( file MATCHES ".*VCRUNTIME140.*" ) set(type "system" PARENT_SCOPE) From 50eaf3e166bbe76cac76214788be2c4120cd1e9b Mon Sep 17 00:00:00 2001 From: Oren Hurvitz Date: Tue, 17 Apr 2018 12:14:45 +0300 Subject: [PATCH 02/63] Fixed CMakeLists.txt for 'oven' to work in Linux --- tools/oven/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/oven/CMakeLists.txt b/tools/oven/CMakeLists.txt index 71bb997303..1b77a2585f 100644 --- a/tools/oven/CMakeLists.txt +++ b/tools/oven/CMakeLists.txt @@ -11,7 +11,7 @@ if (WIN32) elseif (UNIX AND NOT APPLE) find_package(Threads REQUIRED) if(THREADS_HAVE_PTHREAD_ARG) - target_compile_options(PUBLIC oven "-pthread") + target_compile_options(oven PUBLIC "-pthread") endif() elseif (APPLE) # Fix up the rpath so macdeployqt works From cd06067030ad1d7ab791361df35aa41fd456bf11 Mon Sep 17 00:00:00 2001 From: Oren Hurvitz Date: Wed, 18 Apr 2018 10:18:29 +0300 Subject: [PATCH 03/63] Fixed based on PR review --- cmake/templates/FixupBundlePostBuild.cmake.in | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/cmake/templates/FixupBundlePostBuild.cmake.in b/cmake/templates/FixupBundlePostBuild.cmake.in index 4ebb0ea1a6..bb96fe49f3 100644 --- a/cmake/templates/FixupBundlePostBuild.cmake.in +++ b/cmake/templates/FixupBundlePostBuild.cmake.in @@ -33,10 +33,9 @@ function(copy_resolved_item_into_bundle resolved_item resolved_embedded_item) # this is our only change from the original version message(STATUS "status: resolved_item == resolved_embedded_item - not copying...") else() - #message(STATUS "copying COMMAND ${CMAKE_COMMAND} -E copy ${resolved_item} ${resolved_embedded_item}") execute_process(COMMAND ${CMAKE_COMMAND} -E copy "${resolved_item}" "${resolved_embedded_item}") - if(UNIX AND NOT APPLE) - file(RPATH_REMOVE FILE "${resolved_embedded_item}") + if (UNIX AND NOT APPLE) + file(RPATH_REMOVE FILE "${resolved_embedded_item}") endif() endif() endfunction() From d8257dcfd3ca497898e8a198aa15816616ca985a Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Mon, 7 May 2018 11:45:17 -0700 Subject: [PATCH 04/63] Add 010 fbx template --- tools/010-templates/fbx.bt | 102 +++++++++++++++++++++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100644 tools/010-templates/fbx.bt diff --git a/tools/010-templates/fbx.bt b/tools/010-templates/fbx.bt new file mode 100644 index 0000000000..dcb620066e --- /dev/null +++ b/tools/010-templates/fbx.bt @@ -0,0 +1,102 @@ +// +// fbx.bt +// tools/010-templates +// +// Created by Ryan Huffman +// Copyright 2018 High Fidelity, Inc. +// +// FBX file template +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +local char use64BitAddresses = 1; + +struct Header { + char prefix[23]; + int32 version; +}; + +struct Property { + char type; + if (type == 'Y') { + int16 value; + } else if (type == 'C') { + char value; + } else if (type == 'I') { + int32 value; + } else if (type == 'F') { + float value; + } else if (type == 'D') { + double value; + } else if (type == 'L') { + int64 value; + } else if (type == 'S' || type == 'R') { + uint32 size; + char value[size]; + } else { + uint32 length; + uint32 encoding; + uint32 compressedLength; + if (encoding == 1) { + char compressedData[compressedLength]; + } else if (type == 'f') { + float values[this.length]; + } else if (type == 'd') { + double values[this.length]; + } else if (type == 'l') { + int64 values[this.length]; + } else if (type == 'i') { + int32 values[this.length]; + } else if (type == 'b') { + char values[this.length]; + } else { + Printf("%c", type); + Assert(false, "Error, unknown property type"); + } + } +}; + +struct Node; + +string nodeName(Node& node) { + if (!exists(node.name)) { + return "Node ----- "; + } + local string s; + SPrintf(s, "Node (%s) ", node.name); + return s; +} + +struct Node { + if (use64BitAddresses) { + int64 endOffset; + uint64 propertyCount; + uint64 propertyListLength; + } else { + int32 endOffset; + uint32 propertyCount; + uint32 propertyListLength; + } + uchar nameLength; + char name[this.nameLength]; + Property properties[this.propertyCount]; + while (FTell() < endOffset) { + Node children; + } +}; + +struct File { + Header header; + use64BitAddresses = header.version >= 7500; + local int i = 0; + Node node; + local string name = node.name; + while (name != "") { + Node node; + i++; + name = exists(node[i].name) ? node[i].name : ""; + } + +} file; From b9fb9875a7462f3262237c8ead48feac565324a5 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Mon, 7 May 2018 11:48:50 -0700 Subject: [PATCH 05/63] Add 010 ktx template --- tools/010-templates/ktx.bt | 52 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 tools/010-templates/ktx.bt diff --git a/tools/010-templates/ktx.bt b/tools/010-templates/ktx.bt new file mode 100644 index 0000000000..9690dbb391 --- /dev/null +++ b/tools/010-templates/ktx.bt @@ -0,0 +1,52 @@ +// +// ktx.bt +// tools/010-templates +// +// Created by Ryan Huffman +// Copyright 2018 High Fidelity, Inc. +// +// KTX file template +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +struct Header { + char identifier[12]; + uint32 endianness; + uint32 glType; + uint32 glTypeSize; + uint32 glFormat; + uint32 glInternalFormat; + uint32 glBaseInternalFormat; + uint32 pixelWidth; + uint32 pixelHeight; + uint32 pixelDepth; + uint32 numberOfArrayElements; + uint32 numberOfFaces; + uint32 numberOfMipmapLevels; + uint32 bytesOfKeyValueData; +}; + +struct KV { + uint32 byteSize; + local uint32 keyLength = ReadStringLength(FTell()); + char key[keyLength]; + char value[byteSize - keyLength] ; + char padding[3 - ((byteSize + 3) % 4)]; +}; + +string kvName(KV& kv) { + local string s; + SPrintf(s, "KeyValue (%s) ", kv.key); + return s; +} + +struct File { + Header header; + local uint32 endOfKV = FTell() + header.bytesOfKeyValueData; + while (FTell() < endOfKV) { + KV keyValue ; + } + char imageData[FileSize() - FTell()]; +} file; From 894cd3ed76b3d870e391a406f3173d9f8f833b3d Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Mon, 7 May 2018 11:52:29 -0700 Subject: [PATCH 06/63] Add 010-templates/README.md --- tools/010-templates/README.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 tools/010-templates/README.md diff --git a/tools/010-templates/README.md b/tools/010-templates/README.md new file mode 100644 index 0000000000..df3ce6d0e5 --- /dev/null +++ b/tools/010-templates/README.md @@ -0,0 +1 @@ +This directory contains [010 editor](https://www.sweetscape.com/010editor/) templates for parsing and inspecting different file types. From ccab2dd641b0a27b9ad29aa889af97e2154fa6ac Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Tue, 15 May 2018 11:48:33 -0700 Subject: [PATCH 07/63] Starting the work --- .../qml/hifi/commerce/wallet/WalletHome.qml | 109 ++-------- .../qml/hifi/models/PSFListModel.qml | 188 ++++++++++++++++++ 2 files changed, 207 insertions(+), 90 deletions(-) create mode 100644 interface/resources/qml/hifi/models/PSFListModel.qml diff --git a/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml b/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml index 7a14ee060f..afc205fd51 100644 --- a/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml +++ b/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml @@ -18,27 +18,24 @@ import QtQuick.Controls 2.2 import "../../../styles-uit" import "../../../controls-uit" as HifiControlsUit import "../../../controls" as HifiControls +import "../../../models" as HifiModels Item { HifiConstants { id: hifi; } id: root; - property bool initialHistoryReceived: false; - property bool historyRequestPending: true; - property bool noMoreHistoryData: false; + property bool initialResultReceived: false; property int pendingCount: 0; - property int currentHistoryPage: 1; - property var pagesAlreadyAdded: new Array(); onVisibleChanged: { if (visible) { transactionHistoryModel.clear(); Commerce.balance(); - initialHistoryReceived = false; - root.currentHistoryPage = 1; - root.noMoreHistoryData = false; - root.historyRequestPending = true; - Commerce.history(root.currentHistoryPage); + transactionHistoryModel.initialResultReceived = false; + transactionHistoryModel.nextPageToRetrieve = 1; + transactionHistoryModel.noMoreDataToRetrieve = false; + transactionHistoryModel.requestPending = true; + Commerce.history(transactionHistoryModel.nextPageToRetrieve); Commerce.getAvailableUpdates(); } else { refreshTimer.stop(); @@ -53,84 +50,16 @@ Item { } onHistoryResult : { - root.initialHistoryReceived = true; - root.historyRequestPending = false; + + transactionHistoryModel.processResult(result.status, result.data.history); - if (result.status === 'success') { - var currentPage = parseInt(result.current_page); - - if (result.data.history.length === 0) { - root.noMoreHistoryData = true; - console.log("No more data to retrieve from Commerce.history() endpoint.") - } else if (root.currentHistoryPage === 1) { - var sameItemCount = 0; - tempTransactionHistoryModel.clear(); - - tempTransactionHistoryModel.append(result.data.history); - - for (var i = 0; i < tempTransactionHistoryModel.count; i++) { - if (!transactionHistoryModel.get(i)) { - sameItemCount = -1; - break; - } else if (tempTransactionHistoryModel.get(i).transaction_type === transactionHistoryModel.get(i).transaction_type && - tempTransactionHistoryModel.get(i).text === transactionHistoryModel.get(i).text) { - sameItemCount++; - } - } - - if (sameItemCount !== tempTransactionHistoryModel.count) { - transactionHistoryModel.clear(); - for (var i = 0; i < tempTransactionHistoryModel.count; i++) { - transactionHistoryModel.append(tempTransactionHistoryModel.get(i)); - } - calculatePendingAndInvalidated(); - } - } else { - if (root.pagesAlreadyAdded.indexOf(currentPage) !== -1) { - console.log("Page " + currentPage + " of history has already been added to the list."); - } else { - // First, add the history result to a temporary model - tempTransactionHistoryModel.clear(); - tempTransactionHistoryModel.append(result.data.history); - - // Make a note that we've already added this page to the model... - root.pagesAlreadyAdded.push(currentPage); - - var insertionIndex = 0; - // If there's nothing in the model right now, we don't need to modify insertionIndex. - if (transactionHistoryModel.count !== 0) { - var currentIteratorPage; - // Search through the whole transactionHistoryModel and look for the insertion point. - // The insertion point is found when the result page from the server is less than - // the page that the current item came from, OR when we've reached the end of the whole model. - for (var i = 0; i < transactionHistoryModel.count; i++) { - currentIteratorPage = transactionHistoryModel.get(i).resultIsFromPage; - - if (currentPage < currentIteratorPage) { - insertionIndex = i; - break; - } else if (i === transactionHistoryModel.count - 1) { - insertionIndex = i + 1; - break; - } - } - } - - // Go through the results we just got back from the server, setting the "resultIsFromPage" - // property of those results and adding them to the main model. - for (var i = 0; i < tempTransactionHistoryModel.count; i++) { - tempTransactionHistoryModel.setProperty(i, "resultIsFromPage", currentPage); - transactionHistoryModel.insert(i + insertionIndex, tempTransactionHistoryModel.get(i)) - } - - calculatePendingAndInvalidated(); - } - } + if (!transactionHistoryModel.noMoreDataToRetrieve) { + calculatePendingAndInvalidated(); } // Only auto-refresh if the user hasn't scrolled // and there is more data to grab - if (transactionHistory.atYBeginning && !root.noMoreHistoryData) { + if (transactionHistory.atYBeginning && !transactionHistoryModel.noMoreDataToRetrieve) { refreshTimer.start(); } } @@ -235,7 +164,7 @@ Item { onTriggered: { if (transactionHistory.atYBeginning) { console.log("Refreshing 1st Page of Recent Activity..."); - root.historyRequestPending = true; + transactionHistoryModel.requestPending = true; Commerce.balance(); Commerce.history(1); } @@ -302,7 +231,7 @@ Item { ListModel { id: tempTransactionHistoryModel; } - ListModel { + HifiModels.PSFListModel { id: transactionHistoryModel; } Item { @@ -313,7 +242,7 @@ Item { anchors.right: parent.right; Item { - visible: transactionHistoryModel.count === 0 && root.initialHistoryReceived; + visible: transactionHistoryModel.count === 0 && transactionHistoryModel.initialResultReceived; anchors.centerIn: parent; width: parent.width - 12; height: parent.height; @@ -462,11 +391,11 @@ Item { onAtYEndChanged: { if (transactionHistory.atYEnd) { console.log("User scrolled to the bottom of 'Recent Activity'."); - if (!root.historyRequestPending && !root.noMoreHistoryData) { + if (!transactionHistoryModel.requestPending && !transactionHistoryModel.noMoreDataToRetrieve) { // Grab next page of results and append to model - root.historyRequestPending = true; - Commerce.history(++root.currentHistoryPage); - console.log("Fetching Page " + root.currentHistoryPage + " of Recent Activity..."); + transactionHistoryModel.requestPending = true; + Commerce.history(++transactionHistoryModel.nextPageToRetrieve); + console.log("Fetching Page " + transactionHistoryModel.nextPageToRetrieve + " of Recent Activity..."); } } } diff --git a/interface/resources/qml/hifi/models/PSFListModel.qml b/interface/resources/qml/hifi/models/PSFListModel.qml new file mode 100644 index 0000000000..cbc28b208d --- /dev/null +++ b/interface/resources/qml/hifi/models/PSFListModel.qml @@ -0,0 +1,188 @@ +// +// PSFListModel.qml +// qml/hifi/commerce/common +// +// PSFListModel +// "PSF" stands for: +// - Paged +// - Sortable +// - Filterable +// +// Created by Zach Fox on 2018-05-15 +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import QtQuick 2.7 + +ListModel { + id: root; + property string sortColumnName: ""; + property bool isSortingDescending: true; + property bool valuesAreNumerical: false; + + property bool initialResultReceived: false; + property bool requestPending: false; + property bool noMoreDataToRetrieve: false; + property int nextPageToRetrieve: 1; + property var pagesAlreadyAdded: new Array(); + + ListModel { + id: tempModel; + } + + function processResult(status, retrievedResult) { + root.initialResultReceived = true; + root.requestPending = false; + + if (status === 'success') { + var currentPage = parseInt(result.current_page); + + if (retrievedResult.length === 0) { + root.noMoreDataToRetrieve = true; + console.log("No more data to retrieve from backend endpoint.") + } else if (root.nextPageToRetrieve === 1) { + var sameItemCount = 0; + + tempModel.clear(); + tempModel.append(retrievedResult); + + for (var i = 0; i < tempModel.count; i++) { + if (!root.get(i)) { + sameItemCount = -1; + break; + } + // Gotta think of another way to determine if the data we just got is the same + // as the data that we already have in the model. + /* else if (tempModel.get(i).transaction_type === root.get(i).transaction_type && + tempModel.get(i).text === root.get(i).text) { + sameItemCount++; + }*/ + } + + if (sameItemCount !== tempModel.count) { + root.clear(); + for (var i = 0; i < tempModel.count; i++) { + root.append(tempModel.get(i)); + } + } + } else { + if (root.pagesAlreadyAdded.indexOf(currentPage) !== -1) { + console.log("Page " + currentPage + " of paginated data has already been added to the list."); + } else { + // First, add the result to a temporary model + tempModel.clear(); + tempModel.append(retrievedResult); + + // Make a note that we've already added this page to the model... + root.pagesAlreadyAdded.push(currentPage); + + var insertionIndex = 0; + // If there's nothing in the model right now, we don't need to modify insertionIndex. + if (root.count !== 0) { + var currentIteratorPage; + // Search through the whole model and look for the insertion point. + // The insertion point is found when the result page from the server is less than + // the page that the current item came from, OR when we've reached the end of the whole model. + for (var i = 0; i < root.count; i++) { + currentIteratorPage = root.get(i).resultIsFromPage; + + if (currentPage < currentIteratorPage) { + insertionIndex = i; + break; + } else if (i === root.count - 1) { + insertionIndex = i + 1; + break; + } + } + } + + // Go through the results we just got back from the server, setting the "resultIsFromPage" + // property of those results and adding them to the main model. + // NOTE that this wouldn't be necessary if we did this step on the server. + for (var i = 0; i < tempModel.count; i++) { + tempModel.setProperty(i, "resultIsFromPage", currentPage); + root.insert(i + insertionIndex, tempModel.get(i)) + } + } + } + } + } + + function swap(a, b) { + if (a < b) { + move(a, b, 1); + move(b - 1, a, 1); + } else if (a > b) { + move(b, a, 1); + move(a - 1, b, 1); + } + } + + function partition(begin, end, pivot) { + if (valuesAreNumerical) { + var piv = get(pivot)[sortColumnName]; + swap(pivot, end - 1); + var store = begin; + var i; + + for (i = begin; i < end - 1; ++i) { + var currentElement = get(i)[sortColumnName]; + if (isSortingDescending) { + if (currentElement > piv) { + swap(store, i); + ++store; + } + } else { + if (currentElement < piv) { + swap(store, i); + ++store; + } + } + } + swap(end - 1, store); + + return store; + } else { + var piv = get(pivot)[sortColumnName].toLowerCase(); + swap(pivot, end - 1); + var store = begin; + var i; + + for (i = begin; i < end - 1; ++i) { + var currentElement = get(i)[sortColumnName].toLowerCase(); + if (isSortingDescending) { + if (currentElement > piv) { + swap(store, i); + ++store; + } + } else { + if (currentElement < piv) { + swap(store, i); + ++store; + } + } + } + swap(end - 1, store); + + return store; + } + } + + function qsort(begin, end) { + if (end - 1 > begin) { + var pivot = begin + Math.floor(Math.random() * (end - begin)); + + pivot = partition(begin, end, pivot); + + qsort(begin, pivot); + qsort(pivot + 1, end); + } + } + + function quickSort() { + qsort(0, count) + } +} \ No newline at end of file From 154d70866f5261075c83323a322e34d912a52e2d Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Wed, 16 May 2018 12:17:17 -0700 Subject: [PATCH 08/63] It's working --- .../qml/hifi/commerce/wallet/WalletHome.qml | 52 +++---- .../qml/hifi/models/PSFListModel.qml | 147 +++++++++++++++--- interface/src/commerce/Ledger.cpp | 4 +- interface/src/commerce/Ledger.h | 2 +- interface/src/commerce/QmlCommerce.cpp | 4 +- interface/src/commerce/QmlCommerce.h | 2 +- 6 files changed, 158 insertions(+), 53 deletions(-) diff --git a/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml b/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml index afc205fd51..ac3afd3cf2 100644 --- a/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml +++ b/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml @@ -18,7 +18,7 @@ import QtQuick.Controls 2.2 import "../../../styles-uit" import "../../../controls-uit" as HifiControlsUit import "../../../controls" as HifiControls -import "../../../models" as HifiModels +import "../../models" as HifiModels Item { HifiConstants { id: hifi; } @@ -32,10 +32,10 @@ Item { transactionHistoryModel.clear(); Commerce.balance(); transactionHistoryModel.initialResultReceived = false; - transactionHistoryModel.nextPageToRetrieve = 1; + transactionHistoryModel.currentPageToRetrieve = 1; transactionHistoryModel.noMoreDataToRetrieve = false; transactionHistoryModel.requestPending = true; - Commerce.history(transactionHistoryModel.nextPageToRetrieve); + transactionHistoryModel.getPage(); Commerce.getAvailableUpdates(); } else { refreshTimer.stop(); @@ -50,18 +50,7 @@ Item { } onHistoryResult : { - - transactionHistoryModel.processResult(result.status, result.data.history); - - if (!transactionHistoryModel.noMoreDataToRetrieve) { - calculatePendingAndInvalidated(); - } - - // Only auto-refresh if the user hasn't scrolled - // and there is more data to grab - if (transactionHistory.atYBeginning && !transactionHistoryModel.noMoreDataToRetrieve) { - refreshTimer.start(); - } + transactionHistoryModel.pageRetrieved(result); } onAvailableUpdatesResult: { @@ -166,7 +155,8 @@ Item { console.log("Refreshing 1st Page of Recent Activity..."); transactionHistoryModel.requestPending = true; Commerce.balance(); - Commerce.history(1); + transactionHistoryModel.currentPageToRetrieve = 1; + transactionHistoryModel.getPage(); } } } @@ -228,11 +218,26 @@ Item { } } - ListModel { - id: tempTransactionHistoryModel; - } HifiModels.PSFListModel { id: transactionHistoryModel; + + itemsPerPage: 100; + getPage: function() { + Commerce.history(transactionHistoryModel.currentPageToRetrieve, transactionHistoryModel.itemsPerPage); + } + pageRetrieved: function(result) { + transactionHistoryModel.processResult(result.status, result.data.history); + + if (!transactionHistoryModel.noMoreDataToRetrieve) { + calculatePendingAndInvalidated(); + } + + // Only auto-refresh if the user hasn't scrolled + // and there is more data to grab + if (transactionHistory.atYBeginning && !transactionHistoryModel.noMoreDataToRetrieve) { + refreshTimer.start(); + } + } } Item { anchors.top: recentActivityText.bottom; @@ -311,7 +316,7 @@ Item { height: parent.height; visible: transactionHistoryModel.count !== 0; clip: true; - model: transactionHistoryModel; + model: transactionHistoryModel.model; delegate: Item { width: parent.width; height: (model.transaction_type === "pendingCount" && root.pendingCount !== 0) ? 40 : ((model.status === "confirmed" || model.status === "invalidated") ? transactionText.height + 30 : 0); @@ -391,12 +396,7 @@ Item { onAtYEndChanged: { if (transactionHistory.atYEnd) { console.log("User scrolled to the bottom of 'Recent Activity'."); - if (!transactionHistoryModel.requestPending && !transactionHistoryModel.noMoreDataToRetrieve) { - // Grab next page of results and append to model - transactionHistoryModel.requestPending = true; - Commerce.history(++transactionHistoryModel.nextPageToRetrieve); - console.log("Fetching Page " + transactionHistoryModel.nextPageToRetrieve + " of Recent Activity..."); - } + transactionHistoryModel.getNextPage(); } } } diff --git a/interface/resources/qml/hifi/models/PSFListModel.qml b/interface/resources/qml/hifi/models/PSFListModel.qml index cbc28b208d..ee639a4f71 100644 --- a/interface/resources/qml/hifi/models/PSFListModel.qml +++ b/interface/resources/qml/hifi/models/PSFListModel.qml @@ -17,58 +17,156 @@ import QtQuick 2.7 -ListModel { +Item { id: root; - property string sortColumnName: ""; - property bool isSortingDescending: true; - property bool valuesAreNumerical: false; + + // Used when printing debug statements + property string listModelName: ""; + + // Holds the value of the page that'll be retrieved the next time `getPage()` is called + property int currentPageToRetrieve: 1; + + // If defined, the endpoint that `getPage()` will hit (as long as there isn't a custom `getPage()` + // that does something fancy) + property string endpoint; + // If defined, the sort key used when calling the above endpoint. + // (as long as there isn't a custom `getPage()` that does something fancy) + property string sortKey; + // If defined, the search filter used when calling the above endpoint. + // (as long as there isn't a custom `getPage()` that does something fancy) + property string searchFilter; + // If defined, the tags filter used when calling the above endpoint. + // (as long as there isn't a custom `getPage()` that does something fancy) + property string tagsFilter; + // The number of items that'll be retrieved per page when calling `getPage()` + // (as long as there isn't a custom `getPage()` that does something fancy) + property int itemsPerPage: 100; + + // This function, by default, will retrieve data from the above-defined `endpoint` with the + // sort and filter data as set above. It can be custom-defined by this item's Parent. + property var getPage: function() { + // Put code here that calls the `endpoint` with the proper `sortKey`, `searchFilter`, and `tagsFilter`. + // Whatever code goes here should define the below `pageRetrieved()` as + // the callback for after the page is asynchronously retrieved. + + // The parent of this Item can also define custom `getPage()` and `pageRetrieved()` functions. + // See `WalletHome.qml` as an example of a file that does this. `WalletHome.qml` must use that method because + // it hits an endpoint that must be authenticated via the Wallet. + console.log("default getPage()"); + } + // This function, by default, will handle the data retrieved using `getPage()` above. + // It can be custom-defined by this item's Parent. + property var pageRetrieved: function() { + console.log("default pageRetrieved()"); + } + + // This function, by default, will get the _next_ page of data according to `getPage()` if there + // isn't a pending request and if there's more data to retrieve. + // It can be custom-defined by this item's Parent. + property var getNextPage: function() { + if (!root.requestPending && !root.noMoreDataToRetrieve) { + root.requestPending = true; + root.currentPageToRetrieve++; + root.getPage(); + console.log("Fetching Page " + root.currentPageToRetrieve + " of " + root.listModelName + "..."); + } + } + + // Resets both internal `ListModel`s and resets the page to retrieve to "1". + function resetModel() { + pagesAlreadyAdded = new Array(); + tempModel.clear(); + finalModel.clear(); + root.currentPageToRetrieve = 1; + } + + onEndpointChanged: { + resetModel(); + root.getPage(); + } + + onSortKeyChanged: { + resetModel(); + root.getPage(); + } + + onSearchFilterChanged: { + resetModel(); + root.getPage(); + } + + onTagsFilterChanged: { + resetModel(); + root.getPage(); + } property bool initialResultReceived: false; property bool requestPending: false; property bool noMoreDataToRetrieve: false; - property int nextPageToRetrieve: 1; property var pagesAlreadyAdded: new Array(); + + // Redefining members and methods so that the parent of this Item + // can use PSFListModel as they would a regular ListModel + property alias model: finalModel; + property alias count: finalModel.count; + function clear() { finalModel.clear(); } + function get(index) { return finalModel.get(index); } + // Used while processing page data and sorting ListModel { id: tempModel; } + // This is the model that the parent of this Item will actually see + ListModel { + id: finalModel; + } + function processResult(status, retrievedResult) { root.initialResultReceived = true; root.requestPending = false; if (status === 'success') { - var currentPage = parseInt(result.current_page); + var currentPage = parseInt(retrievedResult.current_page); if (retrievedResult.length === 0) { root.noMoreDataToRetrieve = true; console.log("No more data to retrieve from backend endpoint.") - } else if (root.nextPageToRetrieve === 1) { + } + /* + See FIXME below... + + else if (root.currentPageToRetrieve === 1) { var sameItemCount = 0; tempModel.clear(); tempModel.append(retrievedResult); for (var i = 0; i < tempModel.count; i++) { - if (!root.get(i)) { + if (!finalModel.get(i)) { sameItemCount = -1; break; } - // Gotta think of another way to determine if the data we just got is the same + // Gotta think of a generic way to determine if the data we just got is the same // as the data that we already have in the model. - /* else if (tempModel.get(i).transaction_type === root.get(i).transaction_type && - tempModel.get(i).text === root.get(i).text) { + else if (tempModel.get(i).transaction_type === finalModel.get(i).transaction_type && + tempModel.get(i).text === finalModel.get(i).text) { sameItemCount++; - }*/ + } } if (sameItemCount !== tempModel.count) { - root.clear(); + finalModel.clear(); for (var i = 0; i < tempModel.count; i++) { - root.append(tempModel.get(i)); + finalModel.append(tempModel.get(i)); } } - } else { + } + */ + else { + // FIXME! Reconsider this logic, because it means that auto-refreshing the first page of results + // (like we do in WalletHome for Recent Activity) _won't_ catch brand new data elements! + // See the commented code above for how I did this for WalletHome specifically. if (root.pagesAlreadyAdded.indexOf(currentPage) !== -1) { console.log("Page " + currentPage + " of paginated data has already been added to the list."); } else { @@ -81,18 +179,18 @@ ListModel { var insertionIndex = 0; // If there's nothing in the model right now, we don't need to modify insertionIndex. - if (root.count !== 0) { + if (finalModel.count !== 0) { var currentIteratorPage; // Search through the whole model and look for the insertion point. // The insertion point is found when the result page from the server is less than // the page that the current item came from, OR when we've reached the end of the whole model. - for (var i = 0; i < root.count; i++) { - currentIteratorPage = root.get(i).resultIsFromPage; + for (var i = 0; i < finalModel.count; i++) { + currentIteratorPage = finalModel.get(i).resultIsFromPage; if (currentPage < currentIteratorPage) { insertionIndex = i; break; - } else if (i === root.count - 1) { + } else if (i === finalModel.count - 1) { insertionIndex = i + 1; break; } @@ -101,16 +199,23 @@ ListModel { // Go through the results we just got back from the server, setting the "resultIsFromPage" // property of those results and adding them to the main model. - // NOTE that this wouldn't be necessary if we did this step on the server. + // NOTE that this wouldn't be necessary if we did this step (or a similar step) on the server. for (var i = 0; i < tempModel.count; i++) { tempModel.setProperty(i, "resultIsFromPage", currentPage); - root.insert(i + insertionIndex, tempModel.get(i)) + finalModel.insert(i + insertionIndex, tempModel.get(i)) } } } } } + // Used when sorting model data on the CLIENT + // Right now, there is no sorting done on the client for + // any users of PSFListModel, but that could very easily change. + property string sortColumnName: ""; + property bool isSortingDescending: true; + property bool valuesAreNumerical: false; + function swap(a, b) { if (a < b) { move(a, b, 1); diff --git a/interface/src/commerce/Ledger.cpp b/interface/src/commerce/Ledger.cpp index f791ea25bc..0abdba1214 100644 --- a/interface/src/commerce/Ledger.cpp +++ b/interface/src/commerce/Ledger.cpp @@ -260,9 +260,9 @@ void Ledger::historyFailure(QNetworkReply& reply) { failResponse("history", reply); } -void Ledger::history(const QStringList& keys, const int& pageNumber) { +void Ledger::history(const QStringList& keys, const int& pageNumber, const int& itemsPerPage) { QJsonObject params; - params["per_page"] = 100; + params["per_page"] = itemsPerPage; params["page"] = pageNumber; keysQuery("history", "historySuccess", "historyFailure", params); } diff --git a/interface/src/commerce/Ledger.h b/interface/src/commerce/Ledger.h index abc97bfe72..1365e39b21 100644 --- a/interface/src/commerce/Ledger.h +++ b/interface/src/commerce/Ledger.h @@ -29,7 +29,7 @@ public: bool receiveAt(const QString& hfc_key, const QString& signing_key); void balance(const QStringList& keys); void inventory(const QStringList& keys); - void history(const QStringList& keys, const int& pageNumber); + void history(const QStringList& keys, const int& pageNumber, const int& itemsPerPage); void account(); void updateLocation(const QString& asset_id, const QString& location, const bool& alsoUpdateSiblings = false, const bool controlledFailure = false); void certificateInfo(const QString& certificateId); diff --git a/interface/src/commerce/QmlCommerce.cpp b/interface/src/commerce/QmlCommerce.cpp index 722f29ba2f..9a5b0519a0 100644 --- a/interface/src/commerce/QmlCommerce.cpp +++ b/interface/src/commerce/QmlCommerce.cpp @@ -114,12 +114,12 @@ void QmlCommerce::inventory() { } } -void QmlCommerce::history(const int& pageNumber) { +void QmlCommerce::history(const int& pageNumber, const int& itemsPerPage) { auto ledger = DependencyManager::get(); auto wallet = DependencyManager::get(); QStringList cachedPublicKeys = wallet->listPublicKeys(); if (!cachedPublicKeys.isEmpty()) { - ledger->history(cachedPublicKeys, pageNumber); + ledger->history(cachedPublicKeys, pageNumber, itemsPerPage); } } diff --git a/interface/src/commerce/QmlCommerce.h b/interface/src/commerce/QmlCommerce.h index 27e97fe7db..5f33ab094c 100644 --- a/interface/src/commerce/QmlCommerce.h +++ b/interface/src/commerce/QmlCommerce.h @@ -74,7 +74,7 @@ protected: Q_INVOKABLE void buy(const QString& assetId, int cost, const bool controlledFailure = false); Q_INVOKABLE void balance(); Q_INVOKABLE void inventory(); - Q_INVOKABLE void history(const int& pageNumber); + Q_INVOKABLE void history(const int& pageNumber, const int& itemsPerPage = 100); Q_INVOKABLE void generateKeyPair(); Q_INVOKABLE void account(); From 0021af871457278b845116d13f047089d51e6a6d Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Wed, 16 May 2018 12:28:52 -0700 Subject: [PATCH 09/63] Merge in in-progress changes from old commerce_paginationAndFiltering branch --- .../qml/hifi/commerce/checkout/Checkout.qml | 23 ++----------------- .../qml/hifi/commerce/purchases/Purchases.qml | 21 ++++------------- interface/src/commerce/Ledger.cpp | 9 ++++++-- interface/src/commerce/Ledger.h | 2 +- interface/src/commerce/QmlCommerce.cpp | 4 ++-- interface/src/commerce/QmlCommerce.h | 2 +- scripts/system/html/js/marketplacesInject.js | 12 ++++++---- 7 files changed, 25 insertions(+), 48 deletions(-) diff --git a/interface/resources/qml/hifi/commerce/checkout/Checkout.qml b/interface/resources/qml/hifi/commerce/checkout/Checkout.qml index f25282c738..16c1b55930 100644 --- a/interface/resources/qml/hifi/commerce/checkout/Checkout.qml +++ b/interface/resources/qml/hifi/commerce/checkout/Checkout.qml @@ -42,7 +42,7 @@ Rectangle { property bool alreadyOwned: false; property int itemPrice: -1; property bool isCertified; - property string itemType; + property string itemType: "unknown"; property var itemTypesArray: ["entity", "wearable", "contentSet", "app", "avatar", "unknown"]; property var itemTypesText: ["entity", "wearable", "content set", "app", "avatar", "item"]; property var buttonTextNormal: ["REZ", "WEAR", "REPLACE CONTENT SET", "INSTALL", "WEAR", "REZ"]; @@ -98,9 +98,6 @@ Rectangle { } else { root.certificateId = result.data.certificate_id; root.itemHref = result.data.download_url; - if (result.data.categories.indexOf("Wearables") > -1) { - root.itemType = "wearable"; - } root.activeView = "checkoutSuccess"; UserActivityLogger.commercePurchaseSuccess(root.itemId, root.itemAuthor, root.itemPrice, !root.alreadyOwned); } @@ -170,9 +167,6 @@ Rectangle { root.activeView = "checkoutFailure"; } else { root.itemHref = result.data.download_url; - if (result.data.categories.indexOf("Wearables") > -1) { - root.itemType = "wearable"; - } root.activeView = "checkoutSuccess"; } } @@ -186,20 +180,6 @@ Rectangle { itemPreviewImage.source = "https://hifi-metaverse.s3-us-west-1.amazonaws.com/marketplace/previews/" + itemId + "/thumbnail/hifi-mp-" + itemId + ".jpg"; } - onItemHrefChanged: { - if (root.itemHref.indexOf(".fst") > -1) { - root.itemType = "avatar"; - } else if (root.itemHref.indexOf('.json.gz') > -1 || root.itemHref.indexOf('.content.zip') > -1) { - root.itemType = "contentSet"; - } else if (root.itemHref.indexOf('.app.json') > -1) { - root.itemType = "app"; - } else if (root.itemHref.indexOf('.json') > -1) { - root.itemType = "entity"; // "wearable" type handled later - } else { - root.itemType = "unknown"; - } - } - onItemTypeChanged: { if (root.itemType === "entity" || root.itemType === "wearable" || root.itemType === "contentSet" || root.itemType === "avatar" || root.itemType === "app") { @@ -1102,6 +1082,7 @@ Rectangle { root.referrer = message.params.referrer; root.itemAuthor = message.params.itemAuthor; root.itemEdition = message.params.itemEdition || -1; + root.itemType = message.params.itemType || "unknown"; refreshBuyUI(); break; default: diff --git a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml index 8fe1ebe6c9..e32b31c1ea 100644 --- a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml +++ b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml @@ -614,7 +614,7 @@ Rectangle { wornEntityID: model.wornEntityID; upgradeUrl: model.upgrade_url; upgradeTitle: model.upgrade_title; - itemType: model.itemType; + itemType: model.item_type; isShowingMyItems: root.isShowingMyItems; valid: model.valid; anchors.topMargin: 10; @@ -996,7 +996,8 @@ Rectangle { for (var i = 0; i < purchasesModel.count; i++) { if (purchasesModel.get(i).title.toLowerCase().indexOf(filterBar.text.toLowerCase()) !== -1) { - if (purchasesModel.get(i).status !== "confirmed" && !root.isShowingMyItems) { + if (purchasesModel.get(i).status !== "confirmed" && !root.isShowingMyItems && + purchasesModel.get(i).edition_number !== "0") { tempPurchasesModel.insert(0, purchasesModel.get(i)); } else if ((root.isShowingMyItems && purchasesModel.get(i).edition_number === "0") || (!root.isShowingMyItems && purchasesModel.get(i).edition_number !== "0")) { @@ -1010,27 +1011,13 @@ Rectangle { for (var i = 0; i < tempPurchasesModel.count; i++) { currentRootFileUrl = tempPurchasesModel.get(i).root_file_url; currentCategories = tempPurchasesModel.get(i).categories; + currentItemType = tempPurchasesModel.get(i).item_type; - if (currentRootFileUrl.indexOf(".fst") > -1) { - currentItemType = "avatar"; - } else if (currentCategories.indexOf("Wearables") > -1) { - currentItemType = "wearable"; - } else if (currentRootFileUrl.endsWith('.json.gz') || currentRootFileUrl.endsWith('.content.zip')) { - currentItemType = "contentSet"; - } else if (currentRootFileUrl.endsWith('.app.json')) { - currentItemType = "app"; - } else if (currentRootFileUrl.endsWith('.json')) { - currentItemType = "entity"; - } else { - currentItemType = "unknown"; - } if (filterBar.primaryFilter_displayName !== "" && ((filterBar.primaryFilter_displayName === "Updatable" && tempPurchasesModel.get(i).upgrade_url === "") || (filterBar.primaryFilter_displayName !== "Updatable" && filterBar.primaryFilter_filterName.toLowerCase() !== currentItemType.toLowerCase()))) { tempPurchasesModel.remove(i); i--; - } else { - tempPurchasesModel.setProperty(i, 'itemType', currentItemType); } } diff --git a/interface/src/commerce/Ledger.cpp b/interface/src/commerce/Ledger.cpp index 0abdba1214..5bd8c77d04 100644 --- a/interface/src/commerce/Ledger.cpp +++ b/interface/src/commerce/Ledger.cpp @@ -134,8 +134,13 @@ void Ledger::balance(const QStringList& keys) { keysQuery("balance", "balanceSuccess", "balanceFailure"); } -void Ledger::inventory(const QStringList& keys) { - keysQuery("inventory", "inventorySuccess", "inventoryFailure"); +void Ledger::inventory(const QString& editionFilter, const QString& typeFilter, const int& page, const int& perPage) { + QJsonObject params; + params["edition_filter"] = editionFilter; + params["type_filter"] = typeFilter; + params["page"] = page; + params["per_page"] = perPage; + keysQuery("inventory", "inventorySuccess", "inventoryFailure", params); } QString hfcString(const QJsonValue& sentValue, const QJsonValue& receivedValue) { diff --git a/interface/src/commerce/Ledger.h b/interface/src/commerce/Ledger.h index 1365e39b21..9733658357 100644 --- a/interface/src/commerce/Ledger.h +++ b/interface/src/commerce/Ledger.h @@ -28,7 +28,7 @@ public: void buy(const QString& hfc_key, int cost, const QString& asset_id, const QString& inventory_key, const bool controlled_failure = false); bool receiveAt(const QString& hfc_key, const QString& signing_key); void balance(const QStringList& keys); - void inventory(const QStringList& keys); + void inventory(const QString& editionFilter, const QString& typeFilter, const int& page, const int& perPage); void history(const QStringList& keys, const int& pageNumber, const int& itemsPerPage); void account(); void updateLocation(const QString& asset_id, const QString& location, const bool& alsoUpdateSiblings = false, const bool controlledFailure = false); diff --git a/interface/src/commerce/QmlCommerce.cpp b/interface/src/commerce/QmlCommerce.cpp index 9a5b0519a0..dba8cd03c7 100644 --- a/interface/src/commerce/QmlCommerce.cpp +++ b/interface/src/commerce/QmlCommerce.cpp @@ -105,12 +105,12 @@ void QmlCommerce::balance() { } } -void QmlCommerce::inventory() { +void QmlCommerce::inventory(const QString& editionFilter, const QString& typeFilter, const int& page, const int& perPage) { auto ledger = DependencyManager::get(); auto wallet = DependencyManager::get(); QStringList cachedPublicKeys = wallet->listPublicKeys(); if (!cachedPublicKeys.isEmpty()) { - ledger->inventory(cachedPublicKeys); + ledger->inventory(editionFilter, typeFilter, page, perPage); } } diff --git a/interface/src/commerce/QmlCommerce.h b/interface/src/commerce/QmlCommerce.h index 5f33ab094c..3a08b4a19b 100644 --- a/interface/src/commerce/QmlCommerce.h +++ b/interface/src/commerce/QmlCommerce.h @@ -73,7 +73,7 @@ protected: Q_INVOKABLE void buy(const QString& assetId, int cost, const bool controlledFailure = false); Q_INVOKABLE void balance(); - Q_INVOKABLE void inventory(); + Q_INVOKABLE void inventory(const QString& editionFilter = QString(), const QString& typeFilter = QString(), const int& page = 1, const int& perPage = 20); Q_INVOKABLE void history(const int& pageNumber, const int& itemsPerPage = 100); Q_INVOKABLE void generateKeyPair(); Q_INVOKABLE void account(); diff --git a/scripts/system/html/js/marketplacesInject.js b/scripts/system/html/js/marketplacesInject.js index 864c7d92b4..799a974fd6 100644 --- a/scripts/system/html/js/marketplacesInject.js +++ b/scripts/system/html/js/marketplacesInject.js @@ -250,7 +250,7 @@ }); } - function buyButtonClicked(id, name, author, price, href, referrer, edition) { + function buyButtonClicked(id, name, author, price, href, referrer, edition, type) { EventBridge.emitWebEvent(JSON.stringify({ type: "CHECKOUT", itemId: id, @@ -259,7 +259,8 @@ itemHref: href, referrer: referrer, itemAuthor: author, - itemEdition: edition + itemEdition: edition, + itemType: type.trim() })); } @@ -328,7 +329,8 @@ $(this).closest('.grid-item').find('.item-cost').text(), $(this).attr('data-href'), "mainPage", - -1); + -1, + $(this).closest('.grid-item').find('.item-type').text()); }); } @@ -419,6 +421,7 @@ } var cost = $('.item-cost').text(); + var type = $('.item-type').text(); var isUpdating = window.location.href.indexOf('edition=') > -1; var urlParams = new URLSearchParams(window.location.search); if (isUpdating) { @@ -438,7 +441,8 @@ cost, href, "itemPage", - urlParams.get('edition')); + urlParams.get('edition'), + type); } }); maybeAddPurchasesButton(); From 97bb0147348ae9bf8e0e18d8887095955c44945f Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Wed, 16 May 2018 13:20:23 -0700 Subject: [PATCH 10/63] Almost working for Purchases! --- .../qml/hifi/commerce/purchases/Purchases.qml | 254 +++++++----------- .../qml/hifi/commerce/wallet/WalletHome.qml | 4 +- .../qml/hifi/models/PSFListModel.qml | 8 + 3 files changed, 106 insertions(+), 160 deletions(-) diff --git a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml index e32b31c1ea..9f5dec7ef3 100644 --- a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml +++ b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml @@ -16,6 +16,7 @@ import QtQuick 2.5 import "../../../styles-uit" import "../../../controls-uit" as HifiControlsUit import "../../../controls" as HifiControls +import "../../models" as HifiModels import "../wallet" as HifiWallet import "../common" as HifiCommerceCommon import "../inspectionCertificate" as HifiInspectionCertificate @@ -34,7 +35,6 @@ Rectangle { property bool punctuationMode: false; property bool isShowingMyItems: false; property bool isDebuggingFirstUseTutorial: false; - property int pendingItemCount: 0; property string installedApps; property bool keyboardRaised: false; property int numUpdatesAvailable: 0; @@ -64,7 +64,10 @@ Rectangle { } else if (!Settings.getValue("isFirstUseOfPurchases", true) && root.activeView === "initialize") { root.activeView = "purchasesMain"; root.installedApps = Commerce.getInstalledApps(); - Commerce.inventory(); + purchasesModel.initialResultReceived = false; + purchasesModel.currentPageToRetrieve = 1; + purchasesModel.noMoreDataToRetrieve = false; + purchasesModel.getPage(); Commerce.getAvailableUpdates(); } } else { @@ -81,39 +84,7 @@ Rectangle { } onInventoryResult: { - purchasesReceived = true; - - if (result.status !== 'success') { - console.log("Failed to get purchases", result.message); - } else if (!purchasesContentsList.dragging) { // Don't modify the view if the user's scrolling - var inventoryResult = processInventoryResult(result.data.assets); - - var currentIndex = purchasesContentsList.currentIndex === -1 ? 0 : purchasesContentsList.currentIndex; - purchasesModel.clear(); - purchasesModel.append(inventoryResult); - - root.pendingItemCount = 0; - for (var i = 0; i < purchasesModel.count; i++) { - if (purchasesModel.get(i).status === "pending") { - root.pendingItemCount++; - } - } - - if (previousPurchasesModel.count !== 0) { - checkIfAnyItemStatusChanged(); - } else { - // Fill statusChanged default value - // Not doing this results in the default being true... - for (var i = 0; i < purchasesModel.count; i++) { - purchasesModel.setProperty(i, "statusChanged", false); - } - } - previousPurchasesModel.append(inventoryResult); - - buildFilteredPurchasesModel(); - - purchasesContentsList.positionViewAtIndex(currentIndex, ListView.Beginning); - } + purchasesModel.pageRetrieved(result); } onAvailableUpdatesResult: { @@ -134,6 +105,11 @@ Rectangle { } } + onIsShowingMyItemsChanged: { + purchasesModel.resetModel(); + + } + Timer { id: notSetUpTimer; interval: 200; @@ -184,7 +160,10 @@ Rectangle { onSendSignalToParent: { if (msg.method === 'sendAssetHome_back' || msg.method === 'closeSendAsset') { root.activeView = "purchasesMain"; - Commerce.inventory(); + purchasesModel.initialResultReceived = false; + purchasesModel.currentPageToRetrieve = 1; + purchasesModel.noMoreDataToRetrieve = false; + purchasesModel.getPage(); Commerce.getAvailableUpdates(); } else { sendToScript(msg); @@ -451,7 +430,10 @@ Rectangle { Settings.setValue("isFirstUseOfPurchases", false); root.activeView = "purchasesMain"; root.installedApps = Commerce.getInstalledApps(); - Commerce.inventory(); + purchasesModel.initialResultReceived = false; + purchasesModel.currentPageToRetrieve = 1; + purchasesModel.noMoreDataToRetrieve = false; + purchasesModel.getPage(); Commerce.getAvailableUpdates(); break; } @@ -548,14 +530,12 @@ Rectangle { } onPrimaryFilter_displayNameChanged: { - buildFilteredPurchasesModel(); - purchasesContentsList.positionViewAtIndex(0, ListView.Beginning) + purchasesModel.tagsFilter = filterBar.primaryFilter_filterName.toLowerCase(); filterBar.previousPrimaryFilter = filterBar.primaryFilter_displayName; } onTextChanged: { - buildFilteredPurchasesModel(); - purchasesContentsList.positionViewAtIndex(0, ListView.Beginning) + purchasesModel.searchFilter = filterBar.text; filterBar.previousText = filterBar.text; } } @@ -574,24 +554,58 @@ Rectangle { anchors.topMargin: 16; } - ListModel { + HifiModels.PSFListModel { id: purchasesModel; - } - ListModel { - id: previousPurchasesModel; - } - HifiCommerceCommon.SortableListModel { - id: tempPurchasesModel; - } - HifiCommerceCommon.SortableListModel { - id: filteredPurchasesModel; + + itemsPerPage: 3; + getPage: function() { + Commerce.inventory( + root.isShowingMyItems ? "proofs" : "purchased", + filterBar.primaryFilter_filterName.toLowerCase(), + purchasesModel.currentPageToRetrieve, + purchasesModel.itemsPerPage + ); + } + pageRetrieved: function(result) { + purchasesReceived = true; + + var processedInventory = processInventoryResult(result.data.assets); + + if (purchasesModel.processResult(result.status, processedInventory)) { + var currentId; + for (var i = 0; i < purchasesModel.count; i++) { + currentId = purchasesModel.get(i).id; + purchasesModel.setProperty(i, 'cardBackVisible', false); + purchasesModel.setProperty(i, 'isInstalled', ((root.installedApps).indexOf(currentId) > -1)); + purchasesModel.setProperty(i, 'wornEntityID', ''); + } + + // Client-side filter of "Updatable" items + // FIXME - this MUST be serverside (what if we don't have the + // page containing an updatable item on the client?) + if (filterBar.primaryFilter_displayName === "Updatable") { + for (var i = 0; i < purchasesModel.count; i++) { + if (purchasesModel.get(i).upgrade_url === "") { + purchasesModel.remove(i); + i--; + } + } + } + + sendToScript({ method: 'purchases_updateWearables' }); + // FIXME: This ALSO *MUST* be serverside (what if we don't have + // all instances of the item on the client yet?) + //populateDisplayedItemCounts(); + sortByDate(); + } + } } ListView { id: purchasesContentsList; - visible: (root.isShowingMyItems && filteredPurchasesModel.count !== 0) || (!root.isShowingMyItems && filteredPurchasesModel.count !== 0); + visible: purchasesModel.count !== 0; clip: true; - model: filteredPurchasesModel; + model: purchasesModel.model; snapMode: ListView.SnapToItem; // Anchors anchors.top: separator.bottom; @@ -608,9 +622,9 @@ Rectangle { itemEdition: model.edition_number; numberSold: model.number_sold; limitedRun: model.limited_run; - displayedItemCount: model.displayedItemCount; - cardBackVisible: model.cardBackVisible; - isInstalled: model.isInstalled; + displayedItemCount: model.displayedItemCount || 0; + cardBackVisible: model.cardBackVisible || false; + isInstalled: model.isInstalled || false; wornEntityID: model.wornEntityID; upgradeUrl: model.upgrade_url; upgradeTitle: model.upgrade_title; @@ -706,11 +720,11 @@ Rectangle { } else if (msg.method === "setFilterText") { filterBar.text = msg.filterText; } else if (msg.method === "flipCard") { - for (var i = 0; i < filteredPurchasesModel.count; i++) { + for (var i = 0; i < purchasesModel.count; i++) { if (i !== index || msg.closeAll) { - filteredPurchasesModel.setProperty(i, "cardBackVisible", false); + purchasesModel.setProperty(i, "cardBackVisible", false); } else { - filteredPurchasesModel.setProperty(i, "cardBackVisible", true); + purchasesModel.setProperty(i, "cardBackVisible", true); } } } else if (msg.method === "updateItemClicked") { @@ -761,7 +775,7 @@ Rectangle { lightboxPopup.button2text = "CONFIRM"; lightboxPopup.button2method = function() { Entities.deleteEntity(msg.wornEntityID); - filteredPurchasesModel.setProperty(index, 'wornEntityID', ''); + purchasesModel.setProperty(index, 'wornEntityID', ''); root.activeView = "giftAsset"; lightboxPopup.visible = false; }; @@ -773,6 +787,14 @@ Rectangle { } } } + + + onAtYEndChanged: { + if (purchasesContentsList.atYEnd) { + console.log("User scrolled to the bottom of 'Purchases'."); + purchasesModel.getNextPage(); + } + } } Rectangle { @@ -968,8 +990,8 @@ Rectangle { function populateDisplayedItemCounts() { var itemCountDictionary = {}; var currentItemId; - for (var i = 0; i < filteredPurchasesModel.count; i++) { - currentItemId = filteredPurchasesModel.get(i).id; + for (var i = 0; i < purchasesModel.count; i++) { + currentItemId = purchasesModel.get(i).id; if (itemCountDictionary[currentItemId] === undefined) { itemCountDictionary[currentItemId] = 1; } else { @@ -977,109 +999,25 @@ Rectangle { } } - for (var i = 0; i < filteredPurchasesModel.count; i++) { - filteredPurchasesModel.setProperty(i, "displayedItemCount", itemCountDictionary[filteredPurchasesModel.get(i).id]); + for (var i = 0; i < purchasesModel.count; i++) { + purchasesModel.setProperty(i, "displayedItemCount", itemCountDictionary[purchasesModel.get(i).id]); } } function sortByDate() { - filteredPurchasesModel.sortColumnName = "purchase_date"; - filteredPurchasesModel.isSortingDescending = true; - filteredPurchasesModel.valuesAreNumerical = true; - filteredPurchasesModel.quickSort(); - } - - function buildFilteredPurchasesModel() { - var sameItemCount = 0; - - tempPurchasesModel.clear(); - - for (var i = 0; i < purchasesModel.count; i++) { - if (purchasesModel.get(i).title.toLowerCase().indexOf(filterBar.text.toLowerCase()) !== -1) { - if (purchasesModel.get(i).status !== "confirmed" && !root.isShowingMyItems && - purchasesModel.get(i).edition_number !== "0") { - tempPurchasesModel.insert(0, purchasesModel.get(i)); - } else if ((root.isShowingMyItems && purchasesModel.get(i).edition_number === "0") || - (!root.isShowingMyItems && purchasesModel.get(i).edition_number !== "0")) { - tempPurchasesModel.append(purchasesModel.get(i)); - } - } - } - - // primaryFilter filtering and adding of itemType property to model - var currentItemType, currentRootFileUrl, currentCategories; - for (var i = 0; i < tempPurchasesModel.count; i++) { - currentRootFileUrl = tempPurchasesModel.get(i).root_file_url; - currentCategories = tempPurchasesModel.get(i).categories; - currentItemType = tempPurchasesModel.get(i).item_type; - - if (filterBar.primaryFilter_displayName !== "" && - ((filterBar.primaryFilter_displayName === "Updatable" && tempPurchasesModel.get(i).upgrade_url === "") || - (filterBar.primaryFilter_displayName !== "Updatable" && filterBar.primaryFilter_filterName.toLowerCase() !== currentItemType.toLowerCase()))) { - tempPurchasesModel.remove(i); - i--; - } - } - - for (var i = 0; i < tempPurchasesModel.count; i++) { - if (!filteredPurchasesModel.get(i)) { - sameItemCount = -1; - break; - } else if (tempPurchasesModel.get(i).itemId === filteredPurchasesModel.get(i).itemId && - tempPurchasesModel.get(i).edition_number === filteredPurchasesModel.get(i).edition_number && - tempPurchasesModel.get(i).status === filteredPurchasesModel.get(i).status) { - sameItemCount++; - } - } - - if (sameItemCount !== tempPurchasesModel.count || - filterBar.text !== filterBar.previousText || - filterBar.primaryFilter !== filterBar.previousPrimaryFilter) { - filteredPurchasesModel.clear(); - var currentId; - for (var i = 0; i < tempPurchasesModel.count; i++) { - currentId = tempPurchasesModel.get(i).id; - filteredPurchasesModel.append(tempPurchasesModel.get(i)); - filteredPurchasesModel.setProperty(i, 'cardBackVisible', false); - filteredPurchasesModel.setProperty(i, 'isInstalled', ((root.installedApps).indexOf(currentId) > -1)); - filteredPurchasesModel.setProperty(i, 'wornEntityID', ''); - } - - sendToScript({ method: 'purchases_updateWearables' }); - populateDisplayedItemCounts(); - sortByDate(); - } - } - - function checkIfAnyItemStatusChanged() { - var currentPurchasesModelId, currentPurchasesModelEdition, currentPurchasesModelStatus; - var previousPurchasesModelStatus; - for (var i = 0; i < purchasesModel.count; i++) { - currentPurchasesModelId = purchasesModel.get(i).id; - currentPurchasesModelEdition = purchasesModel.get(i).edition_number; - currentPurchasesModelStatus = purchasesModel.get(i).status; - - for (var j = 0; j < previousPurchasesModel.count; j++) { - previousPurchasesModelStatus = previousPurchasesModel.get(j).status; - if (currentPurchasesModelId === previousPurchasesModel.get(j).id && - currentPurchasesModelEdition === previousPurchasesModel.get(j).edition_number && - currentPurchasesModelStatus !== previousPurchasesModelStatus) { - - purchasesModel.setProperty(i, "statusChanged", true); - } else { - purchasesModel.setProperty(i, "statusChanged", false); - } - } - } + purchasesModel.sortColumnName = "purchase_date"; + purchasesModel.isSortingDescending = true; + purchasesModel.valuesAreNumerical = true; + purchasesModel.quickSort(); } function updateCurrentlyWornWearables(wearables) { - for (var i = 0; i < filteredPurchasesModel.count; i++) { + for (var i = 0; i < purchasesModel.count; i++) { for (var j = 0; j < wearables.length; j++) { - if (filteredPurchasesModel.get(i).itemType === "wearable" && - wearables[j].entityCertID === filteredPurchasesModel.get(i).certificate_id && - wearables[j].entityEdition.toString() === filteredPurchasesModel.get(i).edition_number) { - filteredPurchasesModel.setProperty(i, 'wornEntityID', wearables[j].entityID); + if (purchasesModel.get(i).itemType === "wearable" && + wearables[j].entityCertID === purchasesModel.get(i).certificate_id && + wearables[j].entityEdition.toString() === purchasesModel.get(i).edition_number) { + purchasesModel.setProperty(i, 'wornEntityID', wearables[j].entityID); break; } } @@ -1136,7 +1074,7 @@ Rectangle { switch (message.method) { case 'updatePurchases': referrerURL = message.referrerURL || ""; - titleBarContainer.referrerURL = message.referrerURL; + titleBarContainer.referrerURL = message.referrerURL || ""; filterBar.text = message.filterText ? message.filterText : ""; break; case 'inspectionCertificate_setCertificateId': diff --git a/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml b/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml index ac3afd3cf2..b41d04f718 100644 --- a/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml +++ b/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml @@ -34,7 +34,6 @@ Item { transactionHistoryModel.initialResultReceived = false; transactionHistoryModel.currentPageToRetrieve = 1; transactionHistoryModel.noMoreDataToRetrieve = false; - transactionHistoryModel.requestPending = true; transactionHistoryModel.getPage(); Commerce.getAvailableUpdates(); } else { @@ -153,7 +152,6 @@ Item { onTriggered: { if (transactionHistory.atYBeginning) { console.log("Refreshing 1st Page of Recent Activity..."); - transactionHistoryModel.requestPending = true; Commerce.balance(); transactionHistoryModel.currentPageToRetrieve = 1; transactionHistoryModel.getPage(); @@ -221,8 +219,10 @@ Item { HifiModels.PSFListModel { id: transactionHistoryModel; + listModelName: "transaction history"; itemsPerPage: 100; getPage: function() { + transactionHistoryModel.requestPending = true; Commerce.history(transactionHistoryModel.currentPageToRetrieve, transactionHistoryModel.itemsPerPage); } pageRetrieved: function(result) { diff --git a/interface/resources/qml/hifi/models/PSFListModel.qml b/interface/resources/qml/hifi/models/PSFListModel.qml index ee639a4f71..41134ddfdb 100644 --- a/interface/resources/qml/hifi/models/PSFListModel.qml +++ b/interface/resources/qml/hifi/models/PSFListModel.qml @@ -111,6 +111,9 @@ Item { property alias count: finalModel.count; function clear() { finalModel.clear(); } function get(index) { return finalModel.get(index); } + function remove(index) { return finalModel.remove(index); } + function setProperty(index, prop, value) { return finalModel.setProperty(index, prop, value); } + function move(from, to, n) { return finalModel.move(from, to, n); } // Used while processing page data and sorting ListModel { @@ -206,7 +209,12 @@ Item { } } } + return true; + } else { + console.log("Failed to get page result for " + root.listModelName); } + + return false; } // Used when sorting model data on the CLIENT From bc590f556accecdf3faf66ee1d1a7e2d5cd981d3 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Wed, 16 May 2018 13:32:53 -0700 Subject: [PATCH 11/63] Get first page --- .../qml/hifi/commerce/purchases/Purchases.qml | 20 +++++++------------ .../qml/hifi/commerce/wallet/WalletHome.qml | 5 +---- .../qml/hifi/models/PSFListModel.qml | 17 ++++++++++++---- 3 files changed, 21 insertions(+), 21 deletions(-) diff --git a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml index 9f5dec7ef3..93400349a2 100644 --- a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml +++ b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml @@ -64,10 +64,7 @@ Rectangle { } else if (!Settings.getValue("isFirstUseOfPurchases", true) && root.activeView === "initialize") { root.activeView = "purchasesMain"; root.installedApps = Commerce.getInstalledApps(); - purchasesModel.initialResultReceived = false; - purchasesModel.currentPageToRetrieve = 1; - purchasesModel.noMoreDataToRetrieve = false; - purchasesModel.getPage(); + purchasesModel.getFirstPage(); Commerce.getAvailableUpdates(); } } else { @@ -160,10 +157,7 @@ Rectangle { onSendSignalToParent: { if (msg.method === 'sendAssetHome_back' || msg.method === 'closeSendAsset') { root.activeView = "purchasesMain"; - purchasesModel.initialResultReceived = false; - purchasesModel.currentPageToRetrieve = 1; - purchasesModel.noMoreDataToRetrieve = false; - purchasesModel.getPage(); + purchasesModel.getFirstPage(); Commerce.getAvailableUpdates(); } else { sendToScript(msg); @@ -430,10 +424,7 @@ Rectangle { Settings.setValue("isFirstUseOfPurchases", false); root.activeView = "purchasesMain"; root.installedApps = Commerce.getInstalledApps(); - purchasesModel.initialResultReceived = false; - purchasesModel.currentPageToRetrieve = 1; - purchasesModel.noMoreDataToRetrieve = false; - purchasesModel.getPage(); + purchasesModel.getFirstPage(); Commerce.getAvailableUpdates(); break; } @@ -596,7 +587,10 @@ Rectangle { // FIXME: This ALSO *MUST* be serverside (what if we don't have // all instances of the item on the client yet?) //populateDisplayedItemCounts(); - sortByDate(); + + // FIXME: Sorting by date should be done serverside (we should always get + // the most recent purchases on the 1st page) + //sortByDate(); } } } diff --git a/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml b/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml index b41d04f718..76a963f63f 100644 --- a/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml +++ b/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml @@ -31,10 +31,7 @@ Item { if (visible) { transactionHistoryModel.clear(); Commerce.balance(); - transactionHistoryModel.initialResultReceived = false; - transactionHistoryModel.currentPageToRetrieve = 1; - transactionHistoryModel.noMoreDataToRetrieve = false; - transactionHistoryModel.getPage(); + transactionHistoryModel.getFirstPage(); Commerce.getAvailableUpdates(); } else { refreshTimer.stop(); diff --git a/interface/resources/qml/hifi/models/PSFListModel.qml b/interface/resources/qml/hifi/models/PSFListModel.qml index 41134ddfdb..f3b14fedd7 100644 --- a/interface/resources/qml/hifi/models/PSFListModel.qml +++ b/interface/resources/qml/hifi/models/PSFListModel.qml @@ -71,6 +71,15 @@ Item { console.log("Fetching Page " + root.currentPageToRetrieve + " of " + root.listModelName + "..."); } } + + // A helper function used to get the first page from the server. + // It can be custom-defined by this item's Parent. + property var getFirstPage: function() { + root.initialResultReceived = false; + root.currentPageToRetrieve = 1; + root.noMoreDataToRetrieve = false; + root.getPage(); + } // Resets both internal `ListModel`s and resets the page to retrieve to "1". function resetModel() { @@ -82,22 +91,22 @@ Item { onEndpointChanged: { resetModel(); - root.getPage(); + root.getFirstPage(); } onSortKeyChanged: { resetModel(); - root.getPage(); + root.getFirstPage(); } onSearchFilterChanged: { resetModel(); - root.getPage(); + root.getFirstPage(); } onTagsFilterChanged: { resetModel(); - root.getPage(); + root.getFirstPage(); } property bool initialResultReceived: false; From 8e1c7531dc7ada6a1b63995315103c2afb7cdeef Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Wed, 23 May 2018 10:42:14 -0700 Subject: [PATCH 12/63] Add option to oven.exe to disable texture baking --- libraries/baking/src/TextureBaker.cpp | 27 ++++++++++++++++++++------- tools/oven/src/OvenCLIApplication.cpp | 15 ++++++++++++++- 2 files changed, 34 insertions(+), 8 deletions(-) diff --git a/libraries/baking/src/TextureBaker.cpp b/libraries/baking/src/TextureBaker.cpp index b6957a2712..2b50f6be97 100644 --- a/libraries/baking/src/TextureBaker.cpp +++ b/libraries/baking/src/TextureBaker.cpp @@ -157,18 +157,19 @@ void TextureBaker::processTexture() { return; } - const char* name = khronos::gl::texture::toString(memKTX->_header.getGLInternaFormat()); - if (name == nullptr) { - handleError("Could not determine internal format for compressed KTX: " + _textureURL.toString()); - return; - } // attempt to write the baked texture to the destination file path - { + if (memKTX->_header.isCompressed()) { + const char* name = khronos::gl::texture::toString(memKTX->_header.getGLInternaFormat()); + if (name == nullptr) { + handleError("Could not determine internal format for compressed KTX: " + _textureURL.toString()); + return; + } + const char* data = reinterpret_cast(memKTX->_storage->data()); const size_t length = memKTX->_storage->size(); - auto fileName = _baseFilename + BAKED_TEXTURE_BCN_SUFFIX; + auto fileName = _baseFilename + "_" + name + ".ktx"; auto filePath = _outputDirectory.absoluteFilePath(fileName); QFile bakedTextureFile { filePath }; if (!bakedTextureFile.open(QIODevice::WriteOnly) || bakedTextureFile.write(data, length) == -1) { @@ -177,6 +178,18 @@ void TextureBaker::processTexture() { } _outputFiles.push_back(filePath); meta.availableTextureTypes[memKTX->_header.getGLInternaFormat()] = _metaTexturePathPrefix + fileName; + } else { + const char* data = reinterpret_cast(memKTX->_storage->data()); + const size_t length = memKTX->_storage->size(); + + auto fileName = _baseFilename + ".ktx"; + auto filePath = _outputDirectory.absoluteFilePath(fileName); + QFile ktxTextureFile { filePath }; + if (!ktxTextureFile.open(QIODevice::WriteOnly) || ktxTextureFile.write(data, length) == -1) { + handleError("Could not write ktx texture for " + _textureURL.toString()); + return; + } + _outputFiles.push_back(filePath); } diff --git a/tools/oven/src/OvenCLIApplication.cpp b/tools/oven/src/OvenCLIApplication.cpp index ab3178db01..6f87359134 100644 --- a/tools/oven/src/OvenCLIApplication.cpp +++ b/tools/oven/src/OvenCLIApplication.cpp @@ -14,11 +14,14 @@ #include #include +#include + #include "BakerCLI.h" static const QString CLI_INPUT_PARAMETER = "i"; static const QString CLI_OUTPUT_PARAMETER = "o"; static const QString CLI_TYPE_PARAMETER = "t"; +static const QString CLI_DISABLE_TEXTURE_COMPRESSION_PARAMETER = "disable-texture-compression"; OvenCLIApplication::OvenCLIApplication(int argc, char* argv[]) : QCoreApplication(argc, argv) @@ -29,7 +32,8 @@ OvenCLIApplication::OvenCLIApplication(int argc, char* argv[]) : parser.addOptions({ { CLI_INPUT_PARAMETER, "Path to file that you would like to bake.", "input" }, { CLI_OUTPUT_PARAMETER, "Path to folder that will be used as output.", "output" }, - { CLI_TYPE_PARAMETER, "Type of asset.", "type" } + { CLI_TYPE_PARAMETER, "Type of asset.", "type" }, + { CLI_DISABLE_TEXTURE_COMPRESSION_PARAMETER, "Disable texture compression." } }); parser.addHelpOption(); @@ -40,6 +44,15 @@ OvenCLIApplication::OvenCLIApplication(int argc, char* argv[]) : QUrl inputUrl(QDir::fromNativeSeparators(parser.value(CLI_INPUT_PARAMETER))); QUrl outputUrl(QDir::fromNativeSeparators(parser.value(CLI_OUTPUT_PARAMETER))); QString type = parser.isSet(CLI_TYPE_PARAMETER) ? parser.value(CLI_TYPE_PARAMETER) : QString::null; + + if (parser.isSet(CLI_DISABLE_TEXTURE_COMPRESSION_PARAMETER)) { + qDebug() << "Disabling texture compression"; + image::setColorTexturesCompressionEnabled(false); + image::setGrayscaleTexturesCompressionEnabled(false); + image::setNormalTexturesCompressionEnabled(false); + image::setCubeTexturesCompressionEnabled(false); + } + QMetaObject::invokeMethod(cli, "bakeFile", Qt::QueuedConnection, Q_ARG(QUrl, inputUrl), Q_ARG(QString, outputUrl.toString()), Q_ARG(QString, type)); } else { From 384851c2893bd57f1819dee3f1bf6101d281b41b Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Wed, 23 May 2018 10:50:51 -0700 Subject: [PATCH 13/63] Add option to oven cli to select texture usage type --- tools/oven/src/BakerCLI.cpp | 42 ++++++++++++++++++++++++++++--------- 1 file changed, 32 insertions(+), 10 deletions(-) diff --git a/tools/oven/src/BakerCLI.cpp b/tools/oven/src/BakerCLI.cpp index a7b8401269..0db70f6fe4 100644 --- a/tools/oven/src/BakerCLI.cpp +++ b/tools/oven/src/BakerCLI.cpp @@ -16,6 +16,8 @@ #include #include +#include + #include "OvenCLIApplication.h" #include "ModelBakingLoggingCategory.h" #include "FBXBaker.h" @@ -38,17 +40,15 @@ void BakerCLI::bakeFile(QUrl inputUrl, const QString& outputPath, const QString& static const QString MODEL_EXTENSION { "fbx" }; static const QString SCRIPT_EXTENSION { "js" }; - QString extension = type; - - if (extension.isNull()) { - auto url = inputUrl.toDisplayString(); - extension = url.mid(url.lastIndexOf('.')); - } - // check what kind of baker we should be creating - bool isFBX = extension == MODEL_EXTENSION; - bool isScript = extension == SCRIPT_EXTENSION; + bool isFBX = type == MODEL_EXTENSION; + bool isScript = type == SCRIPT_EXTENSION; + // If the type doesn't match the above, we assume we have a texture, and the type specified is the + // texture usage type (albedo, cubemap, normals, etc.) + auto url = inputUrl.toDisplayString(); + auto idx = url.lastIndexOf('.'); + auto extension = idx >= 0 ? url.mid(idx + 1).toLower() : ""; bool isSupportedImage = QImageReader::supportedImageFormats().contains(extension.toLatin1()); _outputPath = outputPath; @@ -65,7 +65,29 @@ void BakerCLI::bakeFile(QUrl inputUrl, const QString& outputPath, const QString& _baker = std::unique_ptr { new JSBaker(inputUrl, outputPath) }; _baker->moveToThread(Oven::instance().getNextWorkerThread()); } else if (isSupportedImage) { - _baker = std::unique_ptr { new TextureBaker(inputUrl, image::TextureUsage::CUBE_TEXTURE, outputPath) }; + static const std::unordered_map STRING_TO_TEXTURE_USAGE_TYPE_MAP { + { "default", image::TextureUsage::DEFAULT_TEXTURE }, + { "strict", image::TextureUsage::STRICT_TEXTURE }, + { "albedo", image::TextureUsage::ALBEDO_TEXTURE }, + { "normal", image::TextureUsage::NORMAL_TEXTURE }, + { "bump", image::TextureUsage::BUMP_TEXTURE }, + { "specular", image::TextureUsage::SPECULAR_TEXTURE }, + { "metallic", image::TextureUsage::METALLIC_TEXTURE }, + { "roughness", image::TextureUsage::ROUGHNESS_TEXTURE }, + { "gloss", image::TextureUsage::GLOSS_TEXTURE }, + { "emissive", image::TextureUsage::EMISSIVE_TEXTURE }, + { "cube", image::TextureUsage::CUBE_TEXTURE }, + { "occlusion", image::TextureUsage::OCCLUSION_TEXTURE }, + { "scattering", image::TextureUsage::SCATTERING_TEXTURE }, + { "lightmap", image::TextureUsage::LIGHTMAP_TEXTURE }, + }; + + auto it = STRING_TO_TEXTURE_USAGE_TYPE_MAP.find(type); + if (it == STRING_TO_TEXTURE_USAGE_TYPE_MAP.end()) { + qCDebug(model_baking) << "Unknown texture usage type:" << type; + QCoreApplication::exit(OVEN_STATUS_CODE_FAIL); + } + _baker = std::unique_ptr { new TextureBaker(inputUrl, it->second, outputPath) }; _baker->moveToThread(Oven::instance().getNextWorkerThread()); } else { qCDebug(model_baking) << "Failed to determine baker type for file" << inputUrl; From 59ca2f99775047523f26aa320f453c456f63af63 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Wed, 23 May 2018 10:51:12 -0700 Subject: [PATCH 14/63] Fix ResourceManager not properly shutting down thread --- libraries/networking/src/ResourceManager.cpp | 5 +++++ libraries/networking/src/ResourceManager.h | 1 + 2 files changed, 6 insertions(+) diff --git a/libraries/networking/src/ResourceManager.cpp b/libraries/networking/src/ResourceManager.cpp index d06b74b724..6df15129a2 100644 --- a/libraries/networking/src/ResourceManager.cpp +++ b/libraries/networking/src/ResourceManager.cpp @@ -38,6 +38,11 @@ ResourceManager::ResourceManager(bool atpSupportEnabled) : _atpSupportEnabled(at _thread.start(); } +ResourceManager::~ResourceManager() { + _thread.terminate(); + _thread.wait(); +} + void ResourceManager::setUrlPrefixOverride(const QString& prefix, const QString& replacement) { QMutexLocker locker(&_prefixMapLock); if (replacement.isEmpty()) { diff --git a/libraries/networking/src/ResourceManager.h b/libraries/networking/src/ResourceManager.h index 9fc636f5fe..a79222d2d8 100644 --- a/libraries/networking/src/ResourceManager.h +++ b/libraries/networking/src/ResourceManager.h @@ -28,6 +28,7 @@ class ResourceManager: public QObject, public Dependency { public: ResourceManager(bool atpSupportEnabled = true); + ~ResourceManager(); void setUrlPrefixOverride(const QString& prefix, const QString& replacement); QString normalizeURL(const QString& urlString); From 235971f8bfd72b83cbefedb691d32b6028d571c5 Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Thu, 24 May 2018 15:23:45 -0700 Subject: [PATCH 15/63] checkpoint --- .../common/sendAsset/ConnectionItem.qml | 2 +- .../commerce/common/sendAsset/SendAsset.qml | 31 +-- .../qml/hifi/commerce/wallet/Wallet.qml | 21 ++ .../qml/hifi/commerce/wallet/WalletHome.qml | 63 ++--- .../qml/hifi/models/PSFListModel.qml | 243 ++++++------------ scripts/system/commerce/wallet.js | 3 + scripts/system/marketplaces/marketplaces.js | 3 + 7 files changed, 144 insertions(+), 222 deletions(-) diff --git a/interface/resources/qml/hifi/commerce/common/sendAsset/ConnectionItem.qml b/interface/resources/qml/hifi/commerce/common/sendAsset/ConnectionItem.qml index 66a9f9a822..41eacd68d5 100644 --- a/interface/resources/qml/hifi/commerce/common/sendAsset/ConnectionItem.qml +++ b/interface/resources/qml/hifi/commerce/common/sendAsset/ConnectionItem.qml @@ -44,7 +44,7 @@ Item { Item { id: avatarImage; - visible: profileUrl !== "" && userName !== ""; + visible: profilePicUrl !== "" && userName !== ""; // Size anchors.verticalCenter: parent.verticalCenter; anchors.left: parent.left; diff --git a/interface/resources/qml/hifi/commerce/common/sendAsset/SendAsset.qml b/interface/resources/qml/hifi/commerce/common/sendAsset/SendAsset.qml index c7c72e5f7c..403dde0713 100644 --- a/interface/resources/qml/hifi/commerce/common/sendAsset/SendAsset.qml +++ b/interface/resources/qml/hifi/commerce/common/sendAsset/SendAsset.qml @@ -19,6 +19,7 @@ import "../../../../styles-uit" import "../../../../controls-uit" as HifiControlsUit import "../../../../controls" as HifiControls import "../" as HifiCommerceCommon +import "../../../models" as HifiModels Item { HifiConstants { id: hifi; } @@ -118,9 +119,7 @@ Item { if (root.currentActiveView === 'chooseRecipientConnection') { // Refresh connections model - connectionsLoading.visible = false; - connectionsLoading.visible = true; - sendSignalToParent({method: 'refreshConnections'}); + connectionsModel.getFirstPage(); } else if (root.currentActiveView === 'sendAssetHome') { Commerce.balance(); } else if (root.currentActiveView === 'chooseRecipientNearby') { @@ -392,11 +391,15 @@ Item { hoverEnabled: true; } - ListModel { + HifiModels.PSFListModel { id: connectionsModel; - } - ListModel { - id: filteredConnectionsModel; + http: root.parent; // Misuse of "root" in this file! + endpoint: "/api/v1/users?per_page=400&filter=connections"; // FIXME per_page + processPage: function (data) { + console.log("HRS FIXME processPage", JSON.stringify(data)); + return data.users; + //buildFilteredConnectionsModel(); + }; } Rectangle { @@ -495,6 +498,7 @@ Item { AnimatedImage { id: connectionsLoading; + visible: !connectionsModel.retrievedAtLeastOnePage; source: "../../../../../icons/profilePicLoading.gif" width: 120; height: width; @@ -515,14 +519,14 @@ Item { } visible: !connectionsLoading.visible; clip: true; - model: filteredConnectionsModel; + model: connectionsModel.model; snapMode: ListView.SnapToItem; // Anchors anchors.fill: parent; delegate: ConnectionItem { isSelected: connectionsList.currentIndex === index; - userName: model.userName; - profilePicUrl: model.profileUrl; + userName: model.username; + profilePicUrl: model.images.thumbnail; anchors.topMargin: 6; anchors.bottomMargin: 6; @@ -1806,13 +1810,6 @@ Item { // FUNCTION DEFINITIONS START // - function updateConnections(connections) { - connectionsModel.clear(); - connectionsModel.append(connections); - buildFilteredConnectionsModel(); - connectionsLoading.visible = false; - } - function buildFilteredConnectionsModel() { filteredConnectionsModel.clear(); for (var i = 0; i < connectionsModel.count; i++) { diff --git a/interface/resources/qml/hifi/commerce/wallet/Wallet.qml b/interface/resources/qml/hifi/commerce/wallet/Wallet.qml index 86700f702e..781420f2b2 100644 --- a/interface/resources/qml/hifi/commerce/wallet/Wallet.qml +++ b/interface/resources/qml/hifi/commerce/wallet/Wallet.qml @@ -768,11 +768,32 @@ Rectangle { case 'updateSelectedRecipientUsername': sendMoney.fromScript(message); break; + case 'http.response': + handleHttpResponse(message); + break; default: console.log('Unrecognized message from wallet.js:', JSON.stringify(message)); } } signal sendToScript(var message); + property var httpCalls: ({}); + property var httpCounter: 0; + function request(options, callback) { + console.debug('HRS FIXME Wallet request', JSON.stringify(options)); + httpCalls[httpCounter] = callback; + var message = {method: 'http.request', params: options, id: httpCounter++, jsonrpc: "2.0"}; + sendToScript(message); + } + function handleHttpResponse(message) { + var callback = httpCalls[message.id]; // FIXME: as different top level tablet apps gets loaded, the id repeats. We should drop old app callbacks without warning. + if (!callback) { + console.warn('No callback for', JSON.stringify(message)); + return; + } + delete httpCalls[message.id]; + console.log('HRS FIXME QML handling of', JSON.stringify(message)); + callback(message.error, message.response); + } // generateUUID() taken from: // https://stackoverflow.com/a/8809472 diff --git a/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml b/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml index 76a963f63f..efd51bfdf3 100644 --- a/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml +++ b/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml @@ -24,12 +24,9 @@ Item { HifiConstants { id: hifi; } id: root; - property bool initialResultReceived: false; - property int pendingCount: 0; onVisibleChanged: { if (visible) { - transactionHistoryModel.clear(); Commerce.balance(); transactionHistoryModel.getFirstPage(); Commerce.getAvailableUpdates(); @@ -46,7 +43,7 @@ Item { } onHistoryResult : { - transactionHistoryModel.pageRetrieved(result); + transactionHistoryModel.handlePage(null, result); } onAvailableUpdatesResult: { @@ -61,7 +58,7 @@ Item { Connections { target: GlobalServices onMyUsernameChanged: { - transactionHistoryModel.clear(); + transactionHistoryModel.resetModel(); usernameText.text = Account.username; } } @@ -150,8 +147,7 @@ Item { if (transactionHistory.atYBeginning) { console.log("Refreshing 1st Page of Recent Activity..."); Commerce.balance(); - transactionHistoryModel.currentPageToRetrieve = 1; - transactionHistoryModel.getPage(); + transactionHistoryModel.getFirstPage(); } } } @@ -218,22 +214,34 @@ Item { listModelName: "transaction history"; itemsPerPage: 100; - getPage: function() { - transactionHistoryModel.requestPending = true; + getPage: function () { + console.log('HRS FIXME WalletHome getPage', transactionHistoryModel.currentPageToRetrieve, transactionHistoryModel.itemsPerPage); Commerce.history(transactionHistoryModel.currentPageToRetrieve, transactionHistoryModel.itemsPerPage); } - pageRetrieved: function(result) { - transactionHistoryModel.processResult(result.status, result.data.history); - - if (!transactionHistoryModel.noMoreDataToRetrieve) { - calculatePendingAndInvalidated(); + processPage: function (data) { + console.log('HRS FIXME WalletHome processPage', JSON.stringify(data)); + var result, pending; + if (transactionHistoryModel.currentPageToRetrieve == 1) { + pending = {transaction_type: "pendingCount", count: 0}; + result = [pending]; + } else { + pending = transactionHistoryModel.get(0); + result = []; } + data.history.forEach(function (item) { + if (item.status === 'pending') { + pending.count++; + } else { + result = result.concat(item); + } + }); // Only auto-refresh if the user hasn't scrolled // and there is more data to grab - if (transactionHistory.atYBeginning && !transactionHistoryModel.noMoreDataToRetrieve) { + if (transactionHistory.atYBeginning && data.history.length && transactionHistoryModel.currentPageToRetrieve >= 0) { refreshTimer.start(); } + return result; } } Item { @@ -243,8 +251,8 @@ Item { anchors.left: parent.left; anchors.right: parent.right; - Item { - visible: transactionHistoryModel.count === 0 && transactionHistoryModel.initialResultReceived; + Item { // On empty history. We don't want to flash and then replace, so don't show until we know we're zero. + visible: transactionHistoryModel.count === 0 && transactionHistoryModel.currentPageToRetrieve < 0; anchors.centerIn: parent; width: parent.width - 12; height: parent.height; @@ -316,10 +324,10 @@ Item { model: transactionHistoryModel.model; delegate: Item { width: parent.width; - height: (model.transaction_type === "pendingCount" && root.pendingCount !== 0) ? 40 : ((model.status === "confirmed" || model.status === "invalidated") ? transactionText.height + 30 : 0); + height: (model.transaction_type === "pendingCount" && model.count !== 0) ? 40 : ((model.status === "confirmed" || model.status === "invalidated") ? transactionText.height + 30 : 0); Item { - visible: model.transaction_type === "pendingCount" && root.pendingCount !== 0; + visible: model.transaction_type === "pendingCount" && model.count !== 0; anchors.top: parent.top; anchors.left: parent.left; width: parent.width; @@ -328,7 +336,7 @@ Item { AnonymousProRegular { id: pendingCountText; anchors.fill: parent; - text: root.pendingCount + ' Transaction' + (root.pendingCount > 1 ? 's' : '') + ' Pending'; + text: model.count + ' Transaction' + (model.count > 1 ? 's' : '') + ' Pending'; size: 18; color: hifi.colors.blueAccent; verticalAlignment: Text.AlignVCenter; @@ -432,21 +440,6 @@ Item { return year + '-' + month + '-' + day + '
' + drawnHour + ':' + min + amOrPm; } - - function calculatePendingAndInvalidated(startingPendingCount) { - var pendingCount = startingPendingCount ? startingPendingCount : 0; - for (var i = 0; i < transactionHistoryModel.count; i++) { - if (transactionHistoryModel.get(i).status === "pending") { - pendingCount++; - } - } - - root.pendingCount = pendingCount; - if (pendingCount > 0) { - transactionHistoryModel.insert(0, {"transaction_type": "pendingCount"}); - } - } - // // Function Name: fromScript() // diff --git a/interface/resources/qml/hifi/models/PSFListModel.qml b/interface/resources/qml/hifi/models/PSFListModel.qml index f3b14fedd7..bf67d30232 100644 --- a/interface/resources/qml/hifi/models/PSFListModel.qml +++ b/interface/resources/qml/hifi/models/PSFListModel.qml @@ -18,102 +18,96 @@ import QtQuick 2.7 Item { - id: root; // Used when printing debug statements - property string listModelName: ""; + property string listModelName: endpoint; - // Holds the value of the page that'll be retrieved the next time `getPage()` is called - property int currentPageToRetrieve: 1; - - // If defined, the endpoint that `getPage()` will hit (as long as there isn't a custom `getPage()` - // that does something fancy) + // Parameters. Even if you override getPage, below, please set these for clarity and consistency, when applicable. + // E.g., your getPage function could refer to this sortKey, etc. property string endpoint; - // If defined, the sort key used when calling the above endpoint. - // (as long as there isn't a custom `getPage()` that does something fancy) property string sortKey; - // If defined, the search filter used when calling the above endpoint. - // (as long as there isn't a custom `getPage()` that does something fancy) property string searchFilter; - // If defined, the tags filter used when calling the above endpoint. - // (as long as there isn't a custom `getPage()` that does something fancy) property string tagsFilter; - // The number of items that'll be retrieved per page when calling `getPage()` - // (as long as there isn't a custom `getPage()` that does something fancy) + onEndpointChanged: getFirstPage(); + onSortKeyChanged: getFirstPage(); + onSearchFilterChanged: getFirstPage(); + onTagsFilterChanged: getFirstPage(); property int itemsPerPage: 100; - // This function, by default, will retrieve data from the above-defined `endpoint` with the - // sort and filter data as set above. It can be custom-defined by this item's Parent. - property var getPage: function() { - // Put code here that calls the `endpoint` with the proper `sortKey`, `searchFilter`, and `tagsFilter`. - // Whatever code goes here should define the below `pageRetrieved()` as - // the callback for after the page is asynchronously retrieved. - - // The parent of this Item can also define custom `getPage()` and `pageRetrieved()` functions. - // See `WalletHome.qml` as an example of a file that does this. `WalletHome.qml` must use that method because - // it hits an endpoint that must be authenticated via the Wallet. - console.log("default getPage()"); - } - // This function, by default, will handle the data retrieved using `getPage()` above. - // It can be custom-defined by this item's Parent. - property var pageRetrieved: function() { - console.log("default pageRetrieved()"); - } - - // This function, by default, will get the _next_ page of data according to `getPage()` if there - // isn't a pending request and if there's more data to retrieve. - // It can be custom-defined by this item's Parent. - property var getNextPage: function() { - if (!root.requestPending && !root.noMoreDataToRetrieve) { - root.requestPending = true; - root.currentPageToRetrieve++; - root.getPage(); - console.log("Fetching Page " + root.currentPageToRetrieve + " of " + root.listModelName + "..."); - } - } - - // A helper function used to get the first page from the server. - // It can be custom-defined by this item's Parent. - property var getFirstPage: function() { - root.initialResultReceived = false; - root.currentPageToRetrieve = 1; - root.noMoreDataToRetrieve = false; - root.getPage(); - } - + // State. + property int currentPageToRetrieve: 0; // 0 = before first page. -1 = we have them all. Otherwise 1-based page number. + property bool retrievedAtLeastOnePage: false; // Resets both internal `ListModel`s and resets the page to retrieve to "1". function resetModel() { - pagesAlreadyAdded = new Array(); tempModel.clear(); finalModel.clear(); - root.currentPageToRetrieve = 1; + currentPageToRetrieve = 1; + retrievedAtLeastOnePage = false } - onEndpointChanged: { + // Processing one page. + + // Override to return one property of data, and/or to transform the elements. Must return an array of model elements. + property var processPage: function (data) { return data; } + + // Check consistency and call processPage. + function handlePage(error, response) { + console.log("HRS FIXME got", endpoint, error, JSON.stringify(response)); + function fail(message) { + console.warn("Warning", listModelName, JSON.stringify(message)); + current_page_to_retrieve = -1; + requestPending = false; + } + if (error || (response.status !== 'success')) { + return fail(error || response.status); + } + if (!requestPending) { + return fail("No request in flight."); + } + requestPending = false; + if (response.current_page && response.current_page !== currentPageToRetrieve) { // Not all endpoints specify this property. + return fail("Mismatched page, expected:" + currentPageToRetrieve); + } + finalModel.append(processPage(response.data || response)); // FIXME keep index steady, and apply any post sort/filter + retrievedAtLeastOnePage = true; + } + + // Override either http or getPage. + property var http: null; // An Item that has a request function. + property var getPage: function () { // Any override MUST call handlePage(), above, even if results empty. + if (!http) { return console.warn("Neither http nor getPage was set in", listModelName); } + var url = /^\//.test(endpoint) ? (Account.metaverseServerURL + endpoint) : endpoint; + // FIXME: handle sort and search parameters, and per_page and page parameters + console.log("HRS FIXME requesting", url); + http.request({uri: url}, handlePage); + } + + // Start the show by retrieving data according to `getPage()`. + // It can be custom-defined by this item's Parent. + property var getFirstPage: function () { resetModel(); - root.getFirstPage(); + requestPending = true; + getPage(); } - - onSortKeyChanged: { - resetModel(); - root.getFirstPage(); - } - - onSearchFilterChanged: { - resetModel(); - root.getFirstPage(); - } - - onTagsFilterChanged: { - resetModel(); - root.getFirstPage(); - } - - property bool initialResultReceived: false; - property bool requestPending: false; - property bool noMoreDataToRetrieve: false; - property var pagesAlreadyAdded: new Array(); + property bool requestPending: false; // For de-bouncing getNextPage. + // This function, will get the _next_ page of data according to `getPage()`. + // It can be custom-defined by this item's Parent. Typical usage: + // ListView { + // id: theList + // model: thisPSFListModelId + // onAtYEndChanged: if (theList.atYEnd) { thisPSFListModelId.getNextPage(); } + // ...} + property var getNextPage: function () { + if (requestPending || currentPageToRetrieve < 0) { + return; + } + console.log("HRS FIXME Fetching Page " + currentPageToRetrieve + " of " + listModelName + "..."); + currentPageToRetrieve++; + requestPending = true; + getPage(); + } + // Redefining members and methods so that the parent of this Item // can use PSFListModel as they would a regular ListModel property alias model: finalModel; @@ -123,6 +117,8 @@ Item { function remove(index) { return finalModel.remove(index); } function setProperty(index, prop, value) { return finalModel.setProperty(index, prop, value); } function move(from, to, n) { return finalModel.move(from, to, n); } + function insert(index, newElement) { finalModel.insert(index, newElement); } + function append(newElements) { finalModel.append(newElements); } // Used while processing page data and sorting ListModel { @@ -134,97 +130,6 @@ Item { id: finalModel; } - function processResult(status, retrievedResult) { - root.initialResultReceived = true; - root.requestPending = false; - - if (status === 'success') { - var currentPage = parseInt(retrievedResult.current_page); - - if (retrievedResult.length === 0) { - root.noMoreDataToRetrieve = true; - console.log("No more data to retrieve from backend endpoint.") - } - /* - See FIXME below... - - else if (root.currentPageToRetrieve === 1) { - var sameItemCount = 0; - - tempModel.clear(); - tempModel.append(retrievedResult); - - for (var i = 0; i < tempModel.count; i++) { - if (!finalModel.get(i)) { - sameItemCount = -1; - break; - } - // Gotta think of a generic way to determine if the data we just got is the same - // as the data that we already have in the model. - else if (tempModel.get(i).transaction_type === finalModel.get(i).transaction_type && - tempModel.get(i).text === finalModel.get(i).text) { - sameItemCount++; - } - } - - if (sameItemCount !== tempModel.count) { - finalModel.clear(); - for (var i = 0; i < tempModel.count; i++) { - finalModel.append(tempModel.get(i)); - } - } - } - */ - else { - // FIXME! Reconsider this logic, because it means that auto-refreshing the first page of results - // (like we do in WalletHome for Recent Activity) _won't_ catch brand new data elements! - // See the commented code above for how I did this for WalletHome specifically. - if (root.pagesAlreadyAdded.indexOf(currentPage) !== -1) { - console.log("Page " + currentPage + " of paginated data has already been added to the list."); - } else { - // First, add the result to a temporary model - tempModel.clear(); - tempModel.append(retrievedResult); - - // Make a note that we've already added this page to the model... - root.pagesAlreadyAdded.push(currentPage); - - var insertionIndex = 0; - // If there's nothing in the model right now, we don't need to modify insertionIndex. - if (finalModel.count !== 0) { - var currentIteratorPage; - // Search through the whole model and look for the insertion point. - // The insertion point is found when the result page from the server is less than - // the page that the current item came from, OR when we've reached the end of the whole model. - for (var i = 0; i < finalModel.count; i++) { - currentIteratorPage = finalModel.get(i).resultIsFromPage; - - if (currentPage < currentIteratorPage) { - insertionIndex = i; - break; - } else if (i === finalModel.count - 1) { - insertionIndex = i + 1; - break; - } - } - } - - // Go through the results we just got back from the server, setting the "resultIsFromPage" - // property of those results and adding them to the main model. - // NOTE that this wouldn't be necessary if we did this step (or a similar step) on the server. - for (var i = 0; i < tempModel.count; i++) { - tempModel.setProperty(i, "resultIsFromPage", currentPage); - finalModel.insert(i + insertionIndex, tempModel.get(i)) - } - } - } - return true; - } else { - console.log("Failed to get page result for " + root.listModelName); - } - - return false; - } // Used when sorting model data on the CLIENT // Right now, there is no sorting done on the client for diff --git a/scripts/system/commerce/wallet.js b/scripts/system/commerce/wallet.js index aea752c565..ac25269e41 100644 --- a/scripts/system/commerce/wallet.js +++ b/scripts/system/commerce/wallet.js @@ -511,6 +511,9 @@ case 'wallet_availableUpdatesReceived': // NOP break; + case 'http.request': + // Handled elsewhere, don't log. + break; default: print('Unrecognized message from QML:', JSON.stringify(message)); } diff --git a/scripts/system/marketplaces/marketplaces.js b/scripts/system/marketplaces/marketplaces.js index dc4d5aa844..ea8278a459 100644 --- a/scripts/system/marketplaces/marketplaces.js +++ b/scripts/system/marketplaces/marketplaces.js @@ -988,6 +988,9 @@ var selectionDisplay = null; // for gridTool.js to ignore sendAssetParticleEffectUpdateTimer = Script.setInterval(updateSendAssetParticleEffect, SEND_ASSET_PARTICLE_TIMER_UPDATE); } break; + case 'http.request': + // Handled elsewhere, don't log. + break; default: print('Unrecognized message from Checkout.qml or Purchases.qml: ' + JSON.stringify(message)); } From caa01aa25edf84fa764a31acfcf9f0f9fbfc82fe Mon Sep 17 00:00:00 2001 From: Clement Date: Tue, 22 May 2018 18:38:39 -0700 Subject: [PATCH 16/63] Don't mark Asset-less DS's backups corrupted If the DS doesn't choose to run an Asset Server, add an empty mappings file to the archive so they do not show as corrupted --- domain-server/src/AssetsBackupHandler.cpp | 10 ++++++---- domain-server/src/AssetsBackupHandler.h | 3 ++- domain-server/src/DomainServer.cpp | 22 ++++++++++++---------- domain-server/src/DomainServer.h | 2 ++ 4 files changed, 22 insertions(+), 15 deletions(-) diff --git a/domain-server/src/AssetsBackupHandler.cpp b/domain-server/src/AssetsBackupHandler.cpp index 2369b01690..6bcabc0bf1 100644 --- a/domain-server/src/AssetsBackupHandler.cpp +++ b/domain-server/src/AssetsBackupHandler.cpp @@ -34,8 +34,9 @@ static const chrono::minutes MAX_REFRESH_TIME { 5 }; Q_DECLARE_LOGGING_CATEGORY(asset_backup) Q_LOGGING_CATEGORY(asset_backup, "hifi.asset-backup"); -AssetsBackupHandler::AssetsBackupHandler(const QString& backupDirectory) : - _assetsDirectory(backupDirectory + ASSETS_DIR) +AssetsBackupHandler::AssetsBackupHandler(const QString& backupDirectory, bool assetServerEnabled) : + _assetsDirectory(backupDirectory + ASSETS_DIR), + _assetServerEnabled(assetServerEnabled) { // Make sure the asset directory exists. QDir(_assetsDirectory).mkpath("."); @@ -53,6 +54,7 @@ void AssetsBackupHandler::setupRefreshTimer() { auto nodeList = DependencyManager::get(); QObject::connect(nodeList.data(), &LimitedNodeList::nodeActivated, this, [this](SharedNodePointer node) { if (node->getType() == NodeType::AssetServer) { + assert(_assetServerEnabled); // run immediately for the first time. _mappingsRefreshTimer.start(0); } @@ -233,12 +235,12 @@ void AssetsBackupHandler::createBackup(const QString& backupName, QuaZip& zip) { return; } - if (_lastMappingsRefresh.time_since_epoch().count() == 0) { + if (_assetServerEnabled && _lastMappingsRefresh.time_since_epoch().count() == 0) { qCWarning(asset_backup) << "Current mappings not yet loaded."; return; } - if ((p_high_resolution_clock::now() - _lastMappingsRefresh) > MAX_REFRESH_TIME) { + if (_assetServerEnabled && (p_high_resolution_clock::now() - _lastMappingsRefresh) > MAX_REFRESH_TIME) { qCWarning(asset_backup) << "Backing up asset mappings that might be stale."; } diff --git a/domain-server/src/AssetsBackupHandler.h b/domain-server/src/AssetsBackupHandler.h index 82d684c2c3..427dc6831a 100644 --- a/domain-server/src/AssetsBackupHandler.h +++ b/domain-server/src/AssetsBackupHandler.h @@ -30,7 +30,7 @@ class AssetsBackupHandler : public QObject, public BackupHandlerInterface { Q_OBJECT public: - AssetsBackupHandler(const QString& backupDirectory); + AssetsBackupHandler(const QString& backupDirectory, bool assetServerEnabled); std::pair isAvailable(const QString& backupName) override; std::pair getRecoveryStatus() override; @@ -65,6 +65,7 @@ private: void updateMappings(); QString _assetsDirectory; + bool _assetServerEnabled { false }; QTimer _mappingsRefreshTimer; p_high_resolution_clock::time_point _lastMappingsRefresh; diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 4e65df495c..45a911d097 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -308,7 +308,7 @@ DomainServer::DomainServer(int argc, char* argv[]) : connect(_contentManager.get(), &DomainContentBackupManager::started, _contentManager.get(), [this](){ _contentManager->addBackupHandler(BackupHandlerPointer(new EntitiesBackupHandler(getEntitiesFilePath(), getEntitiesReplacementFilePath()))); - _contentManager->addBackupHandler(BackupHandlerPointer(new AssetsBackupHandler(getContentBackupDir()))); + _contentManager->addBackupHandler(BackupHandlerPointer(new AssetsBackupHandler(getContentBackupDir(), isAssetServerEnabled()))); _contentManager->addBackupHandler(BackupHandlerPointer(new ContentSettingsBackupHandler(_settingsManager))); }); @@ -990,15 +990,11 @@ void DomainServer::populateDefaultStaticAssignmentsExcludingTypes(const QSet(static_cast(defaultedType) + 1)) { if (!excludedTypes.contains(defaultedType) && defaultedType != Assignment::AgentType) { - if (defaultedType == Assignment::AssetServerType) { - // Make sure the asset-server is enabled before adding it here. - // Initially we do not assign it by default so we can test it in HF domains first - static const QString ASSET_SERVER_ENABLED_KEYPATH = "asset_server.enabled"; - - if (!_settingsManager.valueOrDefaultValueForKeyPath(ASSET_SERVER_ENABLED_KEYPATH).toBool()) { - // skip to the next iteration if asset-server isn't enabled - continue; - } + // Make sure the asset-server is enabled before adding it here. + // Initially we do not assign it by default so we can test it in HF domains first + if (defaultedType == Assignment::AssetServerType && !isAssetServerEnabled()) { + // skip to the next iteraion if asset-server isn't enabled + continue; } // type has not been set from a command line or config file config, use the default @@ -2945,6 +2941,12 @@ bool DomainServer::shouldReplicateNode(const Node& node) { } }; + +bool DomainServer::isAssetServerEnabled() { + static const QString ASSET_SERVER_ENABLED_KEYPATH = "asset_server.enabled"; + return _settingsManager.valueOrDefaultValueForKeyPath(ASSET_SERVER_ENABLED_KEYPATH).toBool(); +} + void DomainServer::nodeAdded(SharedNodePointer node) { // we don't use updateNodeWithData, so add the DomainServerNodeData to the node here node->setLinkedData(std::unique_ptr { new DomainServerNodeData() }); diff --git a/domain-server/src/DomainServer.h b/domain-server/src/DomainServer.h index 01adbd99a9..d128ae068c 100644 --- a/domain-server/src/DomainServer.h +++ b/domain-server/src/DomainServer.h @@ -72,6 +72,8 @@ public: static const QString REPLACEMENT_FILE_EXTENSION; + bool isAssetServerEnabled(); + public slots: /// Called by NodeList to inform us a node has been added void nodeAdded(SharedNodePointer node); From d30d84f1bdccc2ef8717443e2b7c0e72eb7e8c1a Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Fri, 25 May 2018 15:30:27 -0700 Subject: [PATCH 17/63] checkpoint that has two ways to do filtering --- .../commerce/common/sendAsset/SendAsset.qml | 22 +++----- .../qml/hifi/models/PSFListModel.qml | 55 ++++++++++++++----- 2 files changed, 49 insertions(+), 28 deletions(-) diff --git a/interface/resources/qml/hifi/commerce/common/sendAsset/SendAsset.qml b/interface/resources/qml/hifi/commerce/common/sendAsset/SendAsset.qml index 403dde0713..208cf2f49e 100644 --- a/interface/resources/qml/hifi/commerce/common/sendAsset/SendAsset.qml +++ b/interface/resources/qml/hifi/commerce/common/sendAsset/SendAsset.qml @@ -394,12 +394,16 @@ Item { HifiModels.PSFListModel { id: connectionsModel; http: root.parent; // Misuse of "root" in this file! - endpoint: "/api/v1/users?per_page=400&filter=connections"; // FIXME per_page + endpoint: "/api/v1/users?filter=connections"; + itemsPerPage: 8; processPage: function (data) { console.log("HRS FIXME processPage", JSON.stringify(data)); return data.users; - //buildFilteredConnectionsModel(); }; + searchFilter: filterBar.text; + searchItemTest: function (text, item) { + return item.username.toLowerCase().indexOf(text.toLowerCase()) !== -1; + }; //HRS FIXME remove when endpoint works. } Rectangle { @@ -475,10 +479,6 @@ Item { anchors.fill: parent; centerPlaceholderGlyph: hifi.glyphs.search; - onTextChanged: { - buildFilteredConnectionsModel(); - } - onAccepted: { focus = false; } @@ -520,6 +520,7 @@ Item { visible: !connectionsLoading.visible; clip: true; model: connectionsModel.model; + onAtYEndChanged: if (connectionsList.atYEnd) { connectionsModel.getNextPage(); } snapMode: ListView.SnapToItem; // Anchors anchors.fill: parent; @@ -1810,15 +1811,6 @@ Item { // FUNCTION DEFINITIONS START // - function buildFilteredConnectionsModel() { - filteredConnectionsModel.clear(); - for (var i = 0; i < connectionsModel.count; i++) { - if (connectionsModel.get(i).userName.toLowerCase().indexOf(filterBar.text.toLowerCase()) !== -1) { - filteredConnectionsModel.append(connectionsModel.get(i)); - } - } - } - function resetSendAssetData() { amountTextField.focus = false; optionalMessage.focus = false; diff --git a/interface/resources/qml/hifi/models/PSFListModel.qml b/interface/resources/qml/hifi/models/PSFListModel.qml index bf67d30232..ece229bea8 100644 --- a/interface/resources/qml/hifi/models/PSFListModel.qml +++ b/interface/resources/qml/hifi/models/PSFListModel.qml @@ -26,23 +26,43 @@ Item { // E.g., your getPage function could refer to this sortKey, etc. property string endpoint; property string sortKey; - property string searchFilter; + property string searchFilter: ""; property string tagsFilter; onEndpointChanged: getFirstPage(); onSortKeyChanged: getFirstPage(); - onSearchFilterChanged: getFirstPage(); + onSearchFilterChanged: { + if (searchItemTest) { + var filteredCopy = copyOfItems.filter(function (item) { + return searchItemTest(searchFilter, item); + }); + finalModel.clear(); + finalModel.append(filteredCopy); + /*for (var index = 0; index < finalModel.count; index++) { + if (!searchItemTest(searchFilter, finalModel.get(index))) { + finalModel.remove(index); + index--; // Don't skip over anything now that the indices have shifted. + } + }*/ + } else { // TODO: fancy timer against fast typing. + getFirstPage(); + } + } onTagsFilterChanged: getFirstPage(); property int itemsPerPage: 100; + // If the endpoint doesn't do search, tags, sort, these functions can be supplied to do it here. + property var searchItemTest: nil; + property var copyOfItems: []; + // State. property int currentPageToRetrieve: 0; // 0 = before first page. -1 = we have them all. Otherwise 1-based page number. property bool retrievedAtLeastOnePage: false; // Resets both internal `ListModel`s and resets the page to retrieve to "1". function resetModel() { - tempModel.clear(); finalModel.clear(); currentPageToRetrieve = 1; - retrievedAtLeastOnePage = false + retrievedAtLeastOnePage = false; + copyOfItems = []; } // Processing one page. @@ -52,6 +72,7 @@ Item { // Check consistency and call processPage. function handlePage(error, response) { + var processed; console.log("HRS FIXME got", endpoint, error, JSON.stringify(response)); function fail(message) { console.warn("Warning", listModelName, JSON.stringify(message)); @@ -68,7 +89,16 @@ Item { if (response.current_page && response.current_page !== currentPageToRetrieve) { // Not all endpoints specify this property. return fail("Mismatched page, expected:" + currentPageToRetrieve); } - finalModel.append(processPage(response.data || response)); // FIXME keep index steady, and apply any post sort/filter + processed = processPage(response.data || response); + if (searchItemTest) { + copyOfItems = copyOfItems.concat(processed); + if (searchFilter) { + processed = processed.filter(function (item) { + return searchItemTest(searchFilter, item); + }); + } + } + finalModel.append(processed); // FIXME keep index steady, and apply any post sort/filter retrievedAtLeastOnePage = true; } @@ -77,7 +107,13 @@ Item { property var getPage: function () { // Any override MUST call handlePage(), above, even if results empty. if (!http) { return console.warn("Neither http nor getPage was set in", listModelName); } var url = /^\//.test(endpoint) ? (Account.metaverseServerURL + endpoint) : endpoint; - // FIXME: handle sort and search parameters, and per_page and page parameters + var parameters = [ + // FIXME: handle sort, search, tag parameters + 'per_page=' + itemsPerPage, + 'page=' + currentPageToRetrieve + ]; + var parametersSeparator = /\?/.test(url) ? '&' : '?'; + url = url + parametersSeparator + parameters.join('&'); console.log("HRS FIXME requesting", url); http.request({uri: url}, handlePage); } @@ -120,17 +156,10 @@ Item { function insert(index, newElement) { finalModel.insert(index, newElement); } function append(newElements) { finalModel.append(newElements); } - // Used while processing page data and sorting - ListModel { - id: tempModel; - } - - // This is the model that the parent of this Item will actually see ListModel { id: finalModel; } - // Used when sorting model data on the CLIENT // Right now, there is no sorting done on the client for // any users of PSFListModel, but that could very easily change. From 17be460adfba228a7bc6aa2ff9d111133befe9b2 Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Fri, 25 May 2018 16:37:06 -0700 Subject: [PATCH 18/63] checkpoint --- .../commerce/common/sendAsset/SendAsset.qml | 2 +- .../qml/hifi/commerce/wallet/WalletHome.qml | 2 +- .../qml/hifi/models/PSFListModel.qml | 33 ++++++------ scripts/system/request-service.js | 50 +++++++++++++++++++ 4 files changed, 70 insertions(+), 17 deletions(-) create mode 100644 scripts/system/request-service.js diff --git a/interface/resources/qml/hifi/commerce/common/sendAsset/SendAsset.qml b/interface/resources/qml/hifi/commerce/common/sendAsset/SendAsset.qml index 208cf2f49e..24753e7b6a 100644 --- a/interface/resources/qml/hifi/commerce/common/sendAsset/SendAsset.qml +++ b/interface/resources/qml/hifi/commerce/common/sendAsset/SendAsset.qml @@ -558,7 +558,7 @@ Item { // "Make a Connection" instructions Rectangle { id: connectionInstructions; - visible: connectionsModel.count === 0 && !connectionsLoading.visible; + visible: connectionsModel.count === 0 && !connectionsModel.searchFilter && !connectionsLoading.visible; anchors.fill: parent; color: "white"; diff --git a/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml b/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml index efd51bfdf3..b23f6ec16c 100644 --- a/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml +++ b/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml @@ -147,7 +147,7 @@ Item { if (transactionHistory.atYBeginning) { console.log("Refreshing 1st Page of Recent Activity..."); Commerce.balance(); - transactionHistoryModel.getFirstPage(); + transactionHistoryModel.getFirstPage("delayedClear"); } } } diff --git a/interface/resources/qml/hifi/models/PSFListModel.qml b/interface/resources/qml/hifi/models/PSFListModel.qml index ece229bea8..4dc96857af 100644 --- a/interface/resources/qml/hifi/models/PSFListModel.qml +++ b/interface/resources/qml/hifi/models/PSFListModel.qml @@ -32,17 +32,9 @@ Item { onSortKeyChanged: getFirstPage(); onSearchFilterChanged: { if (searchItemTest) { - var filteredCopy = copyOfItems.filter(function (item) { - return searchItemTest(searchFilter, item); - }); + var filteredCopy = applySearchItemTest(copyOfItems); finalModel.clear(); finalModel.append(filteredCopy); - /*for (var index = 0; index < finalModel.count; index++) { - if (!searchItemTest(searchFilter, finalModel.get(index))) { - finalModel.remove(index); - index--; // Don't skip over anything now that the indices have shifted. - } - }*/ } else { // TODO: fancy timer against fast typing. getFirstPage(); } @@ -57,9 +49,11 @@ Item { // State. property int currentPageToRetrieve: 0; // 0 = before first page. -1 = we have them all. Otherwise 1-based page number. property bool retrievedAtLeastOnePage: false; - // Resets both internal `ListModel`s and resets the page to retrieve to "1". + // We normally clear on reset. But if we want to "refresh", we can delay clearing the model until we get a result. + // Not normally set directly, but rather by giving a truthy argument to getFirstPage(true); + property bool delayedClear: false; function resetModel() { - finalModel.clear(); + if (!delayedClear) { finalModel.clear(); } currentPageToRetrieve = 1; retrievedAtLeastOnePage = false; copyOfItems = []; @@ -78,6 +72,7 @@ Item { console.warn("Warning", listModelName, JSON.stringify(message)); current_page_to_retrieve = -1; requestPending = false; + delayedClear = false; } if (error || (response.status !== 'success')) { return fail(error || response.status); @@ -93,14 +88,21 @@ Item { if (searchItemTest) { copyOfItems = copyOfItems.concat(processed); if (searchFilter) { - processed = processed.filter(function (item) { - return searchItemTest(searchFilter, item); - }); + processed = applySearchItemTest(processed); } } + if (delayedClear) { + finalModel.clear(); + delayedClear = false; + } finalModel.append(processed); // FIXME keep index steady, and apply any post sort/filter retrievedAtLeastOnePage = true; } + function applySearchItemTest(items) { + return items.filter(function (item) { + return searchItemTest(searchFilter, item); + }); + } // Override either http or getPage. property var http: null; // An Item that has a request function. @@ -120,7 +122,8 @@ Item { // Start the show by retrieving data according to `getPage()`. // It can be custom-defined by this item's Parent. - property var getFirstPage: function () { + property var getFirstPage: function (delayClear) { + delayedClear = !!delayClear; resetModel(); requestPending = true; getPage(); diff --git a/scripts/system/request-service.js b/scripts/system/request-service.js new file mode 100644 index 0000000000..84e80489fa --- /dev/null +++ b/scripts/system/request-service.js @@ -0,0 +1,50 @@ +"use strict"; +// +// request-service.js +// +// Created by Howard Stearns on May 22, 2018 +// Copyright 2018 High Fidelity, Inc +// +// Distributed under the Apache License, Version 2.0 +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +(function() { // BEGIN LOCAL_SCOPE + + // QML has its own XMLHttpRequest, but: + // - npm request is easier to use. + // - It is not easy to hack QML's XMLHttpRequest to use our MetaverseServer, and to supply the user's auth when contacting it. + // a. Our custom XMLHttpRequestClass object only works with QScriptEngine, not QML's javascript. + // b. We have hacked profiles that intercept requests to our MetavserseServer (providing the correct auth), but those + // only work in QML WebEngineView. Setting up communication between ordinary QML and a hiddent WebEngineView is + // tantamount to the following anyway, and would still have to duplicate the code from request.js. + + // So, this script does two things: + // 1. Allows any root .qml to signal sendToScript({id: aString, method: 'http.request', params: byNameOptions}) + // We will then asynchonously call fromScript({id: theSameString, method: 'http.response', error: errorOrFalsey, response: body}) + // on that root object. + // 2. If the uri used (computed from byNameOptions, see request.js) begins with '/', we will: + // a. Prepend Account.metaverseServerUR. + // b. Use the appropriate auth. + + var request = Script.require('request').request; + var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); + function fromQml(message) { // messages are {id, method, params}, like json-rpc. See also sendToQml. + switch (message.method) { + case 'http.request': + request(message.params, function (error, response) { + console.log('HRS FIXME request-service got', JSON.stringify(error), JSON.stringify(response)); + tablet.sendToQml({ + id: message.id, + method: 'http.response', + error: error, // Alas, this isn't always a JSON-RPC conforming error object. + response: response, + jsonrpc: '2.0' + }); + }); + break; + } + } + tablet.fromQml.connect(fromQml); + Script.scriptEnding.connect(function () { tablet.fromQml.disconnect(fromQml); }); +}()); // END LOCAL_SCOPE From bdd38cef7ace61369711ddab842aff7db6949d6e Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Wed, 30 May 2018 17:09:13 -0700 Subject: [PATCH 19/63] checkpoint --- .../resources/qml/hifi/RootHttpRequest.qml | 39 ++++++++++++++++++ .../commerce/common/sendAsset/SendAsset.qml | 5 ++- .../qml/hifi/commerce/purchases/Purchases.qml | 40 +++++++++++++++---- .../qml/hifi/commerce/wallet/Wallet.qml | 29 +++++--------- .../qml/hifi/commerce/wallet/WalletHome.qml | 13 +++--- .../qml/hifi/models/PSFListModel.qml | 27 ++++++++----- scripts/system/request-service.js | 30 +++++++------- 7 files changed, 124 insertions(+), 59 deletions(-) create mode 100644 interface/resources/qml/hifi/RootHttpRequest.qml diff --git a/interface/resources/qml/hifi/RootHttpRequest.qml b/interface/resources/qml/hifi/RootHttpRequest.qml new file mode 100644 index 0000000000..0355626996 --- /dev/null +++ b/interface/resources/qml/hifi/RootHttpRequest.qml @@ -0,0 +1,39 @@ +// +// RootHttpRequest.qml +// qml/hifi +// +// Create an item of this in the ROOT qml to be able to make http requests. +// Used by PSFListModel.qml +// +// Created by Howard Stearns on 5/29/2018 +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import QtQuick 2.5 + +Item { + property var httpCalls: ({}); + property var httpCounter: 0; + // Public function for initiating an http request. + // REQUIRES parent to be root to have sendToScript! + function request(options, callback) { + console.debug('HttpRequest', JSON.stringify(options)); + httpCalls[httpCounter] = callback; + var message = {method: 'http.request', params: options, id: httpCounter++, jsonrpc: "2.0"}; + parent.sendToScript(message); + } + // REQUIRES that parent/root handle http.response message.method in fromScript, by calling this function. + function handleHttpResponse(message) { + var callback = httpCalls[message.id]; // FIXME: as different top level tablet apps gets loaded, the id repeats. We should drop old app callbacks without warning. + if (!callback) { + console.warn('No callback for', JSON.stringify(message)); + return; + } + delete httpCalls[message.id]; + console.debug('HttpRequest response', JSON.stringify(message)); + callback(message.error, message.response); + } +} diff --git a/interface/resources/qml/hifi/commerce/common/sendAsset/SendAsset.qml b/interface/resources/qml/hifi/commerce/common/sendAsset/SendAsset.qml index 24753e7b6a..13fe748ec7 100644 --- a/interface/resources/qml/hifi/commerce/common/sendAsset/SendAsset.qml +++ b/interface/resources/qml/hifi/commerce/common/sendAsset/SendAsset.qml @@ -37,6 +37,8 @@ Item { property string assetName: ""; property string assetCertID: ""; property string sendingPubliclyEffectImage; + property var http; + property var listModelName; // This object is always used in a popup or full-screen Wallet section. // This MouseArea is used to prevent a user from being @@ -393,7 +395,8 @@ Item { HifiModels.PSFListModel { id: connectionsModel; - http: root.parent; // Misuse of "root" in this file! + http: root.http; + listModelName: root.listModelName; endpoint: "/api/v1/users?filter=connections"; itemsPerPage: 8; processPage: function (data) { diff --git a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml index 93400349a2..795bb2306a 100644 --- a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml +++ b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml @@ -21,6 +21,7 @@ import "../wallet" as HifiWallet import "../common" as HifiCommerceCommon import "../inspectionCertificate" as HifiInspectionCertificate import "../common/sendAsset" as HifiSendAsset +import "../.." as HifiCommon // references XXX from root context @@ -81,7 +82,7 @@ Rectangle { } onInventoryResult: { - purchasesModel.pageRetrieved(result); + purchasesModel.handlePage(result.status !== "success" && result.message, result); } onAvailableUpdatesResult: { @@ -145,8 +146,14 @@ Rectangle { } } + HifiCommon.RootHttpRequest { + id: http; + } + HifiSendAsset.SendAsset { id: sendAsset; + http: http; + listModelName: "Gift Connections"; z: 998; visible: root.activeView === "giftAsset"; anchors.fill: parent; @@ -547,9 +554,10 @@ Rectangle { HifiModels.PSFListModel { id: purchasesModel; + itemsPerPage: 6; - itemsPerPage: 3; - getPage: function() { + getPage: function () { + console.log('HRS FIXME Purchases getPage', root.isShowingMyItems, filterBar.primaryFilter_filterName, purchasesModel.currentPageToRetrieve, purchasesModel.itemsPerPage); Commerce.inventory( root.isShowingMyItems ? "proofs" : "purchased", filterBar.primaryFilter_filterName.toLowerCase(), @@ -557,10 +565,24 @@ Rectangle { purchasesModel.itemsPerPage ); } - pageRetrieved: function(result) { - purchasesReceived = true; + processPage: function(data) { + purchasesReceived = true; // HRS FIXME? + data.assets.forEach(function (item) { + if (item.status.length > 1) { console.warn("Unrecognized inventory status", item); } + item.status = item.status[0]; + item.categories = item.categories.join(';'); + item.cardBackVisible = false; + item.isInstalled = root.installedApps.indexOf(item.id) > -1; + item.wornEntityID = ''; + // HRS FIXME updateable + }); + // HRS FIXME purchaess_updateWearables + // HRS FIXME populateDisplayedItemCounts + // HRS FIXME sortByDate + return data.assets; - var processedInventory = processInventoryResult(result.data.assets); + /* + var processedInventory = processInventoryResult(data.assets); if (purchasesModel.processResult(result.status, processedInventory)) { var currentId; @@ -592,6 +614,7 @@ Rectangle { // the most recent purchases on the 1st page) //sortByDate(); } + */ } } @@ -970,7 +993,7 @@ Rectangle { // FUNCTION DEFINITIONS START // - function processInventoryResult(inventory) { + function processInventoryResult(inventory) { // HRS FIXME remove for (var i = 0; i < inventory.length; i++) { if (inventory[i].status.length > 1) { console.log("WARNING: Inventory result index " + i + " has a status of length >1!") @@ -1087,6 +1110,9 @@ Rectangle { case 'updateWearables': updateCurrentlyWornWearables(message.wornWearables); break; + case 'http.response': + http.handleHttpResponse(message); + break; default: console.log('Unrecognized message from marketplaces.js:', JSON.stringify(message)); } diff --git a/interface/resources/qml/hifi/commerce/wallet/Wallet.qml b/interface/resources/qml/hifi/commerce/wallet/Wallet.qml index 781420f2b2..1e11cbc058 100644 --- a/interface/resources/qml/hifi/commerce/wallet/Wallet.qml +++ b/interface/resources/qml/hifi/commerce/wallet/Wallet.qml @@ -19,6 +19,7 @@ import "../../../controls-uit" as HifiControlsUit import "../../../controls" as HifiControls import "../common" as HifiCommerceCommon import "../common/sendAsset" +import "../.." as HifiCommon Rectangle { HifiConstants { id: hifi; } @@ -343,8 +344,14 @@ Rectangle { } } + HifiCommon.RootHttpRequest { + id: http; + } + SendAsset { id: sendMoney; + http: http; + listModelName: "Send Money Connections"; z: 997; visible: root.activeView === "sendMoney"; anchors.fill: parent; @@ -762,38 +769,22 @@ Rectangle { // NOP break; case 'updateConnections': + console.log('Wallet.qml updateConnections');// HRS FIXME sendMoney.updateConnections(message.connections); break; case 'selectRecipient': case 'updateSelectedRecipientUsername': + console.log('Wallet.qml updateSelectedRecipientUsername'); // HRS FIXME sendMoney.fromScript(message); break; case 'http.response': - handleHttpResponse(message); + http.handleHttpResponse(message); break; default: console.log('Unrecognized message from wallet.js:', JSON.stringify(message)); } } signal sendToScript(var message); - property var httpCalls: ({}); - property var httpCounter: 0; - function request(options, callback) { - console.debug('HRS FIXME Wallet request', JSON.stringify(options)); - httpCalls[httpCounter] = callback; - var message = {method: 'http.request', params: options, id: httpCounter++, jsonrpc: "2.0"}; - sendToScript(message); - } - function handleHttpResponse(message) { - var callback = httpCalls[message.id]; // FIXME: as different top level tablet apps gets loaded, the id repeats. We should drop old app callbacks without warning. - if (!callback) { - console.warn('No callback for', JSON.stringify(message)); - return; - } - delete httpCalls[message.id]; - console.log('HRS FIXME QML handling of', JSON.stringify(message)); - callback(message.error, message.response); - } // generateUUID() taken from: // https://stackoverflow.com/a/8809472 diff --git a/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml b/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml index b23f6ec16c..6d41da1e6e 100644 --- a/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml +++ b/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml @@ -211,16 +211,15 @@ Item { HifiModels.PSFListModel { id: transactionHistoryModel; - - listModelName: "transaction history"; - itemsPerPage: 100; + listModelName: "transaction history"; // For debugging. Alternatively, we could specify endpoint for that purpose, even though it's not used directly. + itemsPerPage: 6; getPage: function () { - console.log('HRS FIXME WalletHome getPage', transactionHistoryModel.currentPageToRetrieve, transactionHistoryModel.itemsPerPage); + console.debug('WalletHome getPage', transactionHistoryModel.currentPageToRetrieve); Commerce.history(transactionHistoryModel.currentPageToRetrieve, transactionHistoryModel.itemsPerPage); } processPage: function (data) { - console.log('HRS FIXME WalletHome processPage', JSON.stringify(data)); - var result, pending; + console.debug('WalletHome processPage', JSON.stringify(data)); + var result, pending; // Set up or get the accumulator for pending. if (transactionHistoryModel.currentPageToRetrieve == 1) { pending = {transaction_type: "pendingCount", count: 0}; result = [pending]; @@ -228,6 +227,8 @@ Item { pending = transactionHistoryModel.get(0); result = []; } + + // Either add to pending, or to result. data.history.forEach(function (item) { if (item.status === 'pending') { pending.count++; diff --git a/interface/resources/qml/hifi/models/PSFListModel.qml b/interface/resources/qml/hifi/models/PSFListModel.qml index 4dc96857af..daafec4e62 100644 --- a/interface/resources/qml/hifi/models/PSFListModel.qml +++ b/interface/resources/qml/hifi/models/PSFListModel.qml @@ -18,7 +18,7 @@ import QtQuick 2.7 Item { - + id: root; // Used when printing debug statements property string listModelName: endpoint; @@ -28,9 +28,14 @@ Item { property string sortKey; property string searchFilter: ""; property string tagsFilter; - onEndpointChanged: getFirstPage(); - onSortKeyChanged: getFirstPage(); + + // QML fires the following changed handlers even when first instantiating the Item. So we need a guard against firing them too early. + property bool initialized: false; + Component.onCompleted: initialized = true; + onEndpointChanged: if (initialized) { getFirstPage(); } + onSortKeyChanged: if (initialized) { getFirstPage(); } onSearchFilterChanged: { + if (!initialized) { return; } if (searchItemTest) { var filteredCopy = applySearchItemTest(copyOfItems); finalModel.clear(); @@ -39,11 +44,11 @@ Item { getFirstPage(); } } - onTagsFilterChanged: getFirstPage(); + onTagsFilterChanged: if (initialized) { getFirstPage(); } property int itemsPerPage: 100; // If the endpoint doesn't do search, tags, sort, these functions can be supplied to do it here. - property var searchItemTest: nil; + property var searchItemTest: null; property var copyOfItems: []; // State. @@ -67,10 +72,10 @@ Item { // Check consistency and call processPage. function handlePage(error, response) { var processed; - console.log("HRS FIXME got", endpoint, error, JSON.stringify(response)); + console.debug('handlePage', listModelName, error, JSON.stringify(response)); function fail(message) { console.warn("Warning", listModelName, JSON.stringify(message)); - current_page_to_retrieve = -1; + currentPageToRetrieve = -1; requestPending = false; delayedClear = false; } @@ -105,9 +110,9 @@ Item { } // Override either http or getPage. - property var http: null; // An Item that has a request function. + property var http; // An Item that has a request function. property var getPage: function () { // Any override MUST call handlePage(), above, even if results empty. - if (!http) { return console.warn("Neither http nor getPage was set in", listModelName); } + if (!http) { return console.warn("Neither http nor getPage was set for", listModelName); } var url = /^\//.test(endpoint) ? (Account.metaverseServerURL + endpoint) : endpoint; var parameters = [ // FIXME: handle sort, search, tag parameters @@ -116,7 +121,7 @@ Item { ]; var parametersSeparator = /\?/.test(url) ? '&' : '?'; url = url + parametersSeparator + parameters.join('&'); - console.log("HRS FIXME requesting", url); + console.debug('getPage', listModelName); http.request({uri: url}, handlePage); } @@ -141,8 +146,8 @@ Item { if (requestPending || currentPageToRetrieve < 0) { return; } - console.log("HRS FIXME Fetching Page " + currentPageToRetrieve + " of " + listModelName + "..."); currentPageToRetrieve++; + console.debug("getNextPage", listModelName, currentPageToRetrieve); requestPending = true; getPage(); } diff --git a/scripts/system/request-service.js b/scripts/system/request-service.js index 84e80489fa..3c3b9ccc04 100644 --- a/scripts/system/request-service.js +++ b/scripts/system/request-service.js @@ -22,7 +22,8 @@ // So, this script does two things: // 1. Allows any root .qml to signal sendToScript({id: aString, method: 'http.request', params: byNameOptions}) // We will then asynchonously call fromScript({id: theSameString, method: 'http.response', error: errorOrFalsey, response: body}) - // on that root object. + // on that root object. + // RootHttpRequest.qml does this. // 2. If the uri used (computed from byNameOptions, see request.js) begins with '/', we will: // a. Prepend Account.metaverseServerUR. // b. Use the appropriate auth. @@ -30,20 +31,19 @@ var request = Script.require('request').request; var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); function fromQml(message) { // messages are {id, method, params}, like json-rpc. See also sendToQml. - switch (message.method) { - case 'http.request': - request(message.params, function (error, response) { - console.log('HRS FIXME request-service got', JSON.stringify(error), JSON.stringify(response)); - tablet.sendToQml({ - id: message.id, - method: 'http.response', - error: error, // Alas, this isn't always a JSON-RPC conforming error object. - response: response, - jsonrpc: '2.0' - }); - }); - break; - } + switch (message.method) { + case 'http.request': + request(message.params, function (error, response) { + tablet.sendToQml({ + id: message.id, + method: 'http.response', + error: error, // Alas, this isn't always a JSON-RPC conforming error object. + response: response, + jsonrpc: '2.0' + }); + }); + break; + } } tablet.fromQml.connect(fromQml); Script.scriptEnding.connect(function () { tablet.fromQml.disconnect(fromQml); }); From 7598c7aaf3d99ed574164713aef2bf178630d6de Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Thu, 31 May 2018 13:52:06 -0700 Subject: [PATCH 20/63] Remove tablet message-box when required Desktop request was being honoured but not HMD. --- interface/src/ui/DialogsManager.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/interface/src/ui/DialogsManager.cpp b/interface/src/ui/DialogsManager.cpp index d01e7d6671..51c6987875 100644 --- a/interface/src/ui/DialogsManager.cpp +++ b/interface/src/ui/DialogsManager.cpp @@ -97,6 +97,9 @@ void DialogsManager::setDomainConnectionFailureVisibility(bool visible) { if (!hmd->getShouldShowTablet()) { hmd->openTablet(); } + } else { + tablet->gotoHomeScreen(); + hmd->closeTablet(); } } } From e8e12eef8f9d2597c4e57886f838fa610ffcd591 Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Thu, 31 May 2018 14:42:52 -0700 Subject: [PATCH 21/63] checkpoint --- .../commerce/common/sendAsset/SendAsset.qml | 5 ++-- .../qml/hifi/commerce/wallet/Wallet.qml | 3 +++ .../qml/hifi/commerce/wallet/WalletHome.qml | 23 ++----------------- .../qml/hifi/models/PSFListModel.qml | 21 +++++++++++++++-- scripts/system/marketplaces/marketplaces.js | 2 ++ 5 files changed, 29 insertions(+), 25 deletions(-) diff --git a/interface/resources/qml/hifi/commerce/common/sendAsset/SendAsset.qml b/interface/resources/qml/hifi/commerce/common/sendAsset/SendAsset.qml index 13fe748ec7..716758a3fe 100644 --- a/interface/resources/qml/hifi/commerce/common/sendAsset/SendAsset.qml +++ b/interface/resources/qml/hifi/commerce/common/sendAsset/SendAsset.qml @@ -399,8 +399,9 @@ Item { listModelName: root.listModelName; endpoint: "/api/v1/users?filter=connections"; itemsPerPage: 8; + listView: connectionsList; processPage: function (data) { - console.log("HRS FIXME processPage", JSON.stringify(data)); + console.log("processPage", connectionsModel.listModelName, JSON.stringify(data)); return data.users; }; searchFilter: filterBar.text; @@ -523,7 +524,7 @@ Item { visible: !connectionsLoading.visible; clip: true; model: connectionsModel.model; - onAtYEndChanged: if (connectionsList.atYEnd) { connectionsModel.getNextPage(); } + onAtYEndChanged: if (connectionsList.atYEnd /*&& !connectionsList.atYBeginning*/) { connectionsModel.getNextPage(); } snapMode: ListView.SnapToItem; // Anchors anchors.fill: parent; diff --git a/interface/resources/qml/hifi/commerce/wallet/Wallet.qml b/interface/resources/qml/hifi/commerce/wallet/Wallet.qml index 1e11cbc058..b75141f8dd 100644 --- a/interface/resources/qml/hifi/commerce/wallet/Wallet.qml +++ b/interface/resources/qml/hifi/commerce/wallet/Wallet.qml @@ -780,6 +780,9 @@ Rectangle { case 'http.response': http.handleHttpResponse(message); break; + case 'palIsStale': + case 'avatarDisconnected': // HRS FIXME. What are these about? + break; default: console.log('Unrecognized message from wallet.js:', JSON.stringify(message)); } diff --git a/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml b/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml index 6d41da1e6e..1c0debb12b 100644 --- a/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml +++ b/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml @@ -214,11 +214,11 @@ Item { listModelName: "transaction history"; // For debugging. Alternatively, we could specify endpoint for that purpose, even though it's not used directly. itemsPerPage: 6; getPage: function () { - console.debug('WalletHome getPage', transactionHistoryModel.currentPageToRetrieve); + console.debug('getPage', transactionHistoryModel.listModelName, transactionHistoryModel.currentPageToRetrieve); Commerce.history(transactionHistoryModel.currentPageToRetrieve, transactionHistoryModel.itemsPerPage); } processPage: function (data) { - console.debug('WalletHome processPage', JSON.stringify(data)); + console.debug('processPage', transactionHistoryModel.listModelName, JSON.stringify(data)); var result, pending; // Set up or get the accumulator for pending. if (transactionHistoryModel.currentPageToRetrieve == 1) { pending = {transaction_type: "pendingCount", count: 0}; @@ -441,25 +441,6 @@ Item { return year + '-' + month + '-' + day + '
' + drawnHour + ':' + min + amOrPm; } - // - // Function Name: fromScript() - // - // Relevant Variables: - // None - // - // Arguments: - // message: The message sent from the JavaScript. - // Messages are in format "{method, params}", like json-rpc. - // - // Description: - // Called when a message is received from a script. - // - function fromScript(message) { - switch (message.method) { - default: - console.log('Unrecognized message from wallet.js:', JSON.stringify(message)); - } - } signal sendSignalToWallet(var msg); // diff --git a/interface/resources/qml/hifi/models/PSFListModel.qml b/interface/resources/qml/hifi/models/PSFListModel.qml index daafec4e62..d9e31cbfa1 100644 --- a/interface/resources/qml/hifi/models/PSFListModel.qml +++ b/interface/resources/qml/hifi/models/PSFListModel.qml @@ -40,6 +40,7 @@ Item { var filteredCopy = applySearchItemTest(copyOfItems); finalModel.clear(); finalModel.append(filteredCopy); + debugView('after searchFilterChanged'); } else { // TODO: fancy timer against fast typing. getFirstPage(); } @@ -69,6 +70,7 @@ Item { // Override to return one property of data, and/or to transform the elements. Must return an array of model elements. property var processPage: function (data) { return data; } + property var listView; // Optional. For debugging. // Check consistency and call processPage. function handlePage(error, response) { var processed; @@ -100,14 +102,28 @@ Item { finalModel.clear(); delayedClear = false; } - finalModel.append(processed); // FIXME keep index steady, and apply any post sort/filter + finalModel.append(processed); // FIXME keep index steady, and apply any post sort retrievedAtLeastOnePage = true; + if (response.total_pages && (response.total_pages === currentPageToRetrieve)) { + currentPageToRetrieve = -1; + } + debugView('after handlePage'); + if (searchItemTest && searchFilter && listView && listView.atYEnd && (currentPageToRetrieve >= 0)) { + getNextPage(); // too fancy?? + } } function applySearchItemTest(items) { return items.filter(function (item) { return searchItemTest(searchFilter, item); }); } + function debugView(label) { + if (!listView) { return; } + console.debug(label, listModelName, 'perPage:', itemsPerPage, 'count:', listView.count, + 'index:', listView.currentIndex, 'section:', listView.currentSection, + 'atYBeginning:', listView.atYBeginning, 'atYEnd:', listView.atYEnd, + 'y:', listView.y, 'contentY:', listView.contentY); + } // Override either http or getPage. property var http; // An Item that has a request function. @@ -121,7 +137,7 @@ Item { ]; var parametersSeparator = /\?/.test(url) ? '&' : '?'; url = url + parametersSeparator + parameters.join('&'); - console.debug('getPage', listModelName); + console.debug('getPage', listModelName, currentPageToRetrieve); http.request({uri: url}, handlePage); } @@ -131,6 +147,7 @@ Item { delayedClear = !!delayClear; resetModel(); requestPending = true; + console.debug("getFirstPage", listModelName, currentPageToRetrieve); getPage(); } diff --git a/scripts/system/marketplaces/marketplaces.js b/scripts/system/marketplaces/marketplaces.js index ea8278a459..208e64fd5e 100644 --- a/scripts/system/marketplaces/marketplaces.js +++ b/scripts/system/marketplaces/marketplaces.js @@ -991,6 +991,8 @@ var selectionDisplay = null; // for gridTool.js to ignore case 'http.request': // Handled elsewhere, don't log. break; + case 'goToPurchases_fromWalletHome': // HRS FIXME What's this about? + break; default: print('Unrecognized message from Checkout.qml or Purchases.qml: ' + JSON.stringify(message)); } From 7d1d7f7bcd56b9d8a3aa54af38205f4eaa6e5c68 Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Thu, 31 May 2018 18:31:53 -0700 Subject: [PATCH 22/63] Closing the open tablet dialog - try harder --- interface/resources/qml/hifi/tablet/TabletRoot.qml | 12 ++++++++++++ interface/src/ui/DialogsManager.cpp | 3 ++- libraries/ui/src/ui/TabletScriptingInterface.cpp | 14 ++++++++++++++ libraries/ui/src/ui/TabletScriptingInterface.h | 6 ++++++ 4 files changed, 34 insertions(+), 1 deletion(-) diff --git a/interface/resources/qml/hifi/tablet/TabletRoot.qml b/interface/resources/qml/hifi/tablet/TabletRoot.qml index 15db5d8f88..fa268ad6ee 100644 --- a/interface/resources/qml/hifi/tablet/TabletRoot.qml +++ b/interface/resources/qml/hifi/tablet/TabletRoot.qml @@ -65,6 +65,18 @@ Item { return false; } + function closeDialog() { + if (openMessage != null) { + openMessage.destroy(); + openMessage = null; + } + + if (openModal != null) { + openModal.destroy(); + openModal = null; + } + } + function isUrlLoaded(url) { if (currentApp >= 0) { var currentAppUrl = tabletApps.get(currentApp).appUrl; diff --git a/interface/src/ui/DialogsManager.cpp b/interface/src/ui/DialogsManager.cpp index 51c6987875..95a96e3388 100644 --- a/interface/src/ui/DialogsManager.cpp +++ b/interface/src/ui/DialogsManager.cpp @@ -97,7 +97,8 @@ void DialogsManager::setDomainConnectionFailureVisibility(bool visible) { if (!hmd->getShouldShowTablet()) { hmd->openTablet(); } - } else { + } else if (tablet->isPathLoaded(url)) { + tablet->closeDialog(); tablet->gotoHomeScreen(); hmd->closeTablet(); } diff --git a/libraries/ui/src/ui/TabletScriptingInterface.cpp b/libraries/ui/src/ui/TabletScriptingInterface.cpp index 9070d87a3c..062acd2d99 100644 --- a/libraries/ui/src/ui/TabletScriptingInterface.cpp +++ b/libraries/ui/src/ui/TabletScriptingInterface.cpp @@ -431,6 +431,20 @@ bool TabletProxy::isMessageDialogOpen() { return result.toBool(); } +void TabletProxy::closeDialog() { + if (QThread::currentThread() != thread()) { + bool result = false; + QMetaObject::invokeMethod(this, "isMessageDialogOpen"); + return; + } + + if (!_qmlTabletRoot) { + return; + } + + QMetaObject::invokeMethod(_qmlTabletRoot, "closeDialog"); +} + void TabletProxy::emitWebEvent(const QVariant& msg) { if (QThread::currentThread() != thread()) { QMetaObject::invokeMethod(this, "emitWebEvent", Q_ARG(QVariant, msg)); diff --git a/libraries/ui/src/ui/TabletScriptingInterface.h b/libraries/ui/src/ui/TabletScriptingInterface.h index 43d889f1d1..1ab29ca3fd 100644 --- a/libraries/ui/src/ui/TabletScriptingInterface.h +++ b/libraries/ui/src/ui/TabletScriptingInterface.h @@ -308,6 +308,12 @@ public: */ Q_INVOKABLE bool isMessageDialogOpen(); + /**jsdoc + * Close any open dialogs. + * @function TabletProxy#closeDialog + */ + Q_INVOKABLE void closeDialog(); + /**jsdoc * Creates a new button, adds it to this and returns it. * @function TabletProxy#addButton From c277cc7574762e9666320cade0038a57dc146dc7 Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Thu, 31 May 2018 18:39:25 -0700 Subject: [PATCH 23/63] Fix minor error --- libraries/ui/src/ui/TabletScriptingInterface.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/ui/src/ui/TabletScriptingInterface.cpp b/libraries/ui/src/ui/TabletScriptingInterface.cpp index 062acd2d99..e03dc7ac63 100644 --- a/libraries/ui/src/ui/TabletScriptingInterface.cpp +++ b/libraries/ui/src/ui/TabletScriptingInterface.cpp @@ -434,7 +434,7 @@ bool TabletProxy::isMessageDialogOpen() { void TabletProxy::closeDialog() { if (QThread::currentThread() != thread()) { bool result = false; - QMetaObject::invokeMethod(this, "isMessageDialogOpen"); + QMetaObject::invokeMethod(this, "closeDialog"); return; } From 2d9a5c62c161ccb54bee0e311af7ae785111a553 Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Fri, 1 Jun 2018 10:02:41 -0700 Subject: [PATCH 24/63] Clean-up for compiler warnings --- libraries/ui/src/ui/TabletScriptingInterface.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/libraries/ui/src/ui/TabletScriptingInterface.cpp b/libraries/ui/src/ui/TabletScriptingInterface.cpp index e03dc7ac63..2c52e669a0 100644 --- a/libraries/ui/src/ui/TabletScriptingInterface.cpp +++ b/libraries/ui/src/ui/TabletScriptingInterface.cpp @@ -433,7 +433,6 @@ bool TabletProxy::isMessageDialogOpen() { void TabletProxy::closeDialog() { if (QThread::currentThread() != thread()) { - bool result = false; QMetaObject::invokeMethod(this, "closeDialog"); return; } From f58e1ebdd973f1e6c98eb253640a74e2d9acfd57 Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Fri, 1 Jun 2018 10:41:08 -0700 Subject: [PATCH 25/63] Only take down tablet if it was brought up --- interface/src/ui/DialogsManager.cpp | 6 +++++- interface/src/ui/DialogsManager.h | 1 + 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/interface/src/ui/DialogsManager.cpp b/interface/src/ui/DialogsManager.cpp index 95a96e3388..83601a2797 100644 --- a/interface/src/ui/DialogsManager.cpp +++ b/interface/src/ui/DialogsManager.cpp @@ -93,6 +93,7 @@ void DialogsManager::setDomainConnectionFailureVisibility(bool visible) { static const QUrl url("dialogs/TabletConnectionFailureDialog.qml"); auto hmd = DependencyManager::get(); if (visible) { + _dialogCreatedWhileShown = tablet->property("tabletShown").toBool(); tablet->initialScreen(url); if (!hmd->getShouldShowTablet()) { hmd->openTablet(); @@ -100,7 +101,10 @@ void DialogsManager::setDomainConnectionFailureVisibility(bool visible) { } else if (tablet->isPathLoaded(url)) { tablet->closeDialog(); tablet->gotoHomeScreen(); - hmd->closeTablet(); + if (!_dialogCreatedWhileShown) { + hmd->closeTablet(); + } + _dialogCreatedWhileShown = false; } } } diff --git a/interface/src/ui/DialogsManager.h b/interface/src/ui/DialogsManager.h index f17ac39a7e..0633dec573 100644 --- a/interface/src/ui/DialogsManager.h +++ b/interface/src/ui/DialogsManager.h @@ -80,6 +80,7 @@ private: QPointer _octreeStatsDialog; QPointer _testingDialog; QPointer _domainConnectionDialog; + bool _dialogCreatedWhileShown { false }; bool _addressBarVisible { false }; }; From 29b09d64e68292595952b8d664e79f5f3049ba79 Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Fri, 1 Jun 2018 13:33:45 -0700 Subject: [PATCH 26/63] checkpoint with PAL working. --- interface/resources/qml/hifi/Pal.qml | 113 +++++----------- .../commerce/common/sendAsset/SendAsset.qml | 1 - .../qml/hifi/models/PSFListModel.qml | 122 ++++++------------ scripts/system/pal.js | 2 + 4 files changed, 74 insertions(+), 164 deletions(-) diff --git a/interface/resources/qml/hifi/Pal.qml b/interface/resources/qml/hifi/Pal.qml index d779b4ba42..8a067c0733 100644 --- a/interface/resources/qml/hifi/Pal.qml +++ b/interface/resources/qml/hifi/Pal.qml @@ -18,6 +18,7 @@ import Qt.labs.settings 1.0 import "../styles-uit" import "../controls-uit" as HifiControlsUit import "../controls" as HifiControls +import "models" as HifiModels // references HMD, Users, UserActivityLogger from root context @@ -44,6 +45,28 @@ Rectangle { property bool punctuationMode: false; HifiConstants { id: hifi; } + RootHttpRequest { id: http; } + HifiModels.PSFListModel { + id: connectionsUserModel; + http: http; + endpoint: "/api/v1/users?filter=connections"; + localSort: true; + property var sortColumn: connectionsTable.getColumn(connectionsTable.sortIndicatorColumn); + sortProperty: sortColumn ? sortColumn.role : "userName"; + sortAscending: connectionsTable.sortIndicatorOrder === Qt.AscendingOrder; + itemsPerPage: 9; + listView: connectionsTable; + processPage: function (data) { + return data.users.map(function (user) { + return { + userName: user.username, + connection: user.connection, + profileUrl: user.images.thumbnail, + placeName: (user.location.root || user.location.domain || {}).name || '' + }; + }); + }; + } // The letterbox used for popup messages LetterboxMessage { @@ -106,16 +129,6 @@ Rectangle { }); return sessionIDs; } - function getSelectedConnectionsUserNames() { - var userNames = []; - connectionsTable.selection.forEach(function (userIndex) { - var datum = connectionsUserModelData[userIndex]; - if (datum) { - userNames.push(datum.userName); - } - }); - return userNames; - } function refreshNearbyWithFilter() { // We should just be able to set settings.filtered to inViewCheckbox.checked, but see #3249, so send to .js for saving. var userIds = getSelectedNearbySessionIDs(); @@ -232,9 +245,7 @@ Rectangle { anchors.fill: parent; onClicked: { if (activeTab != "connectionsTab") { - connectionsLoading.visible = false; - connectionsLoading.visible = true; - pal.sendToScript({method: 'refreshConnections'}); + connectionsUserModel.getFirstPage(); } activeTab = "connectionsTab"; connectionsHelpText.color = hifi.colors.blueAccent; @@ -258,11 +269,7 @@ Rectangle { id: reloadConnections; width: reloadConnections.height; glyph: hifi.glyphs.reload; - onClicked: { - connectionsLoading.visible = false; - connectionsLoading.visible = true; - pal.sendToScript({method: 'refreshConnections'}); - } + onClicked: connectionsUserModel.getFirstPage('delayRefresh'); } } // "CONNECTIONS" text @@ -702,7 +709,7 @@ Rectangle { anchors.top: parent.top; anchors.topMargin: 185; anchors.horizontalCenter: parent.horizontalCenter; - visible: true; + visible: !connectionsUserModel.retrievedAtLeastOnePage; onVisibleChanged: { if (visible) { connectionsTimeoutTimer.start(); @@ -747,14 +754,6 @@ Rectangle { headerVisible: true; sortIndicatorColumn: settings.connectionsSortIndicatorColumn; sortIndicatorOrder: settings.connectionsSortIndicatorOrder; - onSortIndicatorColumnChanged: { - settings.connectionsSortIndicatorColumn = sortIndicatorColumn; - sortConnectionsModel(); - } - onSortIndicatorOrderChanged: { - settings.connectionsSortIndicatorOrder = sortIndicatorOrder; - sortConnectionsModel(); - } TableViewColumn { id: connectionsUserNameHeader; @@ -779,8 +778,10 @@ Rectangle { resizable: false; } - model: ListModel { - id: connectionsUserModel; + model: connectionsUserModel.model; + Connections { + target: connectionsTable.flickableItem; + onAtYEndChanged: if (connectionsTable.flickableItem.atYEnd) { connectionsUserModel.getNextPage(); } } // This Rectangle refers to each Row in the connectionsTable. @@ -1130,16 +1131,6 @@ Rectangle { sortModel(); reloadNearby.color = 0; break; - case 'connections': - var data = message.params; - if (pal.debug) { - console.log('Got connection data: ', JSON.stringify(data)); - } - connectionsUserModelData = data; - sortConnectionsModel(); - connectionsLoading.visible = false; - connectionsRefreshProblemText.visible = false; - break; case 'select': var sessionIds = message.params[0]; var selected = message.params[1]; @@ -1239,6 +1230,11 @@ Rectangle { reloadNearby.color = 2; } break; + case 'inspectionCertificate_resetCert': // HRS FIXME what's this about? + break; + case 'http.response': + http.handleHttpResponse(message); + break; default: console.log('Unrecognized message:', JSON.stringify(message)); } @@ -1287,45 +1283,6 @@ Rectangle { nearbyTable.positionViewAtRow(newSelectedIndexes[0], ListView.Beginning); } } - function sortConnectionsModel() { - var column = connectionsTable.getColumn(connectionsTable.sortIndicatorColumn); - var sortProperty = column ? column.role : "userName"; - var before = (connectionsTable.sortIndicatorOrder === Qt.AscendingOrder) ? -1 : 1; - var after = -1 * before; - // get selection(s) before sorting - var selectedIDs = getSelectedConnectionsUserNames(); - connectionsUserModelData.sort(function (a, b) { - var aValue = a[sortProperty].toString().toLowerCase(), bValue = b[sortProperty].toString().toLowerCase(); - if (!aValue && !bValue) { - return 0; - } else if (!aValue) { - return after; - } else if (!bValue) { - return before; - } - switch (true) { - case (aValue < bValue): return before; - case (aValue > bValue): return after; - default: return 0; - } - }); - connectionsTable.selection.clear(); - - connectionsUserModel.clear(); - var userIndex = 0; - var newSelectedIndexes = []; - connectionsUserModelData.forEach(function (datum) { - datum.userIndex = userIndex++; - connectionsUserModel.append(datum); - if (selectedIDs.indexOf(datum.sessionId) != -1) { - newSelectedIndexes.push(datum.userIndex); - } - }); - if (newSelectedIndexes.length > 0) { - connectionsTable.selection.select(newSelectedIndexes); - connectionsTable.positionViewAtRow(newSelectedIndexes[0], ListView.Beginning); - } - } signal sendToScript(var message); function noticeSelection() { var userIds = []; diff --git a/interface/resources/qml/hifi/commerce/common/sendAsset/SendAsset.qml b/interface/resources/qml/hifi/commerce/common/sendAsset/SendAsset.qml index 716758a3fe..21d803b1ab 100644 --- a/interface/resources/qml/hifi/commerce/common/sendAsset/SendAsset.qml +++ b/interface/resources/qml/hifi/commerce/common/sendAsset/SendAsset.qml @@ -401,7 +401,6 @@ Item { itemsPerPage: 8; listView: connectionsList; processPage: function (data) { - console.log("processPage", connectionsModel.listModelName, JSON.stringify(data)); return data.users; }; searchFilter: filterBar.text; diff --git a/interface/resources/qml/hifi/models/PSFListModel.qml b/interface/resources/qml/hifi/models/PSFListModel.qml index d9e31cbfa1..9858d76d4a 100644 --- a/interface/resources/qml/hifi/models/PSFListModel.qml +++ b/interface/resources/qml/hifi/models/PSFListModel.qml @@ -25,15 +25,17 @@ Item { // Parameters. Even if you override getPage, below, please set these for clarity and consistency, when applicable. // E.g., your getPage function could refer to this sortKey, etc. property string endpoint; - property string sortKey; + property string sortProperty; // Currently only handles sorting on one column, which fits with current needs and tables. + property bool sortAscending; + property string sortKey: !sortProperty ? '' : (sortProperty + "," + (sortAscending ? "asc" : "desc")); property string searchFilter: ""; property string tagsFilter; // QML fires the following changed handlers even when first instantiating the Item. So we need a guard against firing them too early. property bool initialized: false; Component.onCompleted: initialized = true; - onEndpointChanged: if (initialized) { getFirstPage(); } - onSortKeyChanged: if (initialized) { getFirstPage(); } + onEndpointChanged: if (initialized) { getFirstPage('delayClear'); } + onSortKeyChanged: if (initialized) { getFirstPage('delayClear'); } onSearchFilterChanged: { if (!initialized) { return; } if (searchItemTest) { @@ -42,14 +44,15 @@ Item { finalModel.append(filteredCopy); debugView('after searchFilterChanged'); } else { // TODO: fancy timer against fast typing. - getFirstPage(); + getFirstPage('delayClear'); } } - onTagsFilterChanged: if (initialized) { getFirstPage(); } + onTagsFilterChanged: if (initialized) { getFirstPage('delayClear'); } property int itemsPerPage: 100; // If the endpoint doesn't do search, tags, sort, these functions can be supplied to do it here. property var searchItemTest: null; + property bool localSort: false; property var copyOfItems: []; // State. @@ -92,21 +95,29 @@ Item { return fail("Mismatched page, expected:" + currentPageToRetrieve); } processed = processPage(response.data || response); + if (response.total_pages && (response.total_pages === currentPageToRetrieve)) { + currentPageToRetrieve = -1; + } if (searchItemTest) { copyOfItems = copyOfItems.concat(processed); if (searchFilter) { processed = applySearchItemTest(processed); } } + if (localSort) { + copyOfItems = copyOfItems.concat(processed); + if (sortProperty) { + sortCopy(sortProperty, sortAscending); + processed = copyOfItems; + delayedClear = true; // see next conditional + } + } if (delayedClear) { finalModel.clear(); delayedClear = false; } finalModel.append(processed); // FIXME keep index steady, and apply any post sort retrievedAtLeastOnePage = true; - if (response.total_pages && (response.total_pages === currentPageToRetrieve)) { - currentPageToRetrieve = -1; - } debugView('after handlePage'); if (searchItemTest && searchFilter && listView && listView.atYEnd && (currentPageToRetrieve >= 0)) { getNextPage(); // too fancy?? @@ -185,85 +196,26 @@ Item { id: finalModel; } - // Used when sorting model data on the CLIENT - // Right now, there is no sorting done on the client for - // any users of PSFListModel, but that could very easily change. - property string sortColumnName: ""; - property bool isSortingDescending: true; - property bool valuesAreNumerical: false; + function sortCopy(sortProperty, isAscending) { + console.debug('client sort', listModelName, sortProperty, isAscending, copyOfItems.length, 'items'); + var before = isAscending ? -1 : 1; + var after = -1 * before; - function swap(a, b) { - if (a < b) { - move(a, b, 1); - move(b - 1, a, 1); - } else if (a > b) { - move(b, a, 1); - move(a - 1, b, 1); - } - } - - function partition(begin, end, pivot) { - if (valuesAreNumerical) { - var piv = get(pivot)[sortColumnName]; - swap(pivot, end - 1); - var store = begin; - var i; - - for (i = begin; i < end - 1; ++i) { - var currentElement = get(i)[sortColumnName]; - if (isSortingDescending) { - if (currentElement > piv) { - swap(store, i); - ++store; - } - } else { - if (currentElement < piv) { - swap(store, i); - ++store; - } - } + copyOfItems.sort(function (a, b) { + var aValue = a[sortProperty].toString().toLowerCase(), + bValue = b[sortProperty].toString().toLowerCase(); + if (!aValue && !bValue) { + return 0; + } else if (!aValue) { + return after; + } else if (!bValue) { + return before; } - swap(end - 1, store); - - return store; - } else { - var piv = get(pivot)[sortColumnName].toLowerCase(); - swap(pivot, end - 1); - var store = begin; - var i; - - for (i = begin; i < end - 1; ++i) { - var currentElement = get(i)[sortColumnName].toLowerCase(); - if (isSortingDescending) { - if (currentElement > piv) { - swap(store, i); - ++store; - } - } else { - if (currentElement < piv) { - swap(store, i); - ++store; - } - } + switch (true) { + case (aValue < bValue): return before; + case (aValue > bValue): return after; + default: return 0; } - swap(end - 1, store); - - return store; - } - } - - function qsort(begin, end) { - if (end - 1 > begin) { - var pivot = begin + Math.floor(Math.random() * (end - begin)); - - pivot = partition(begin, end, pivot); - - qsort(begin, pivot); - qsort(pivot + 1, end); - } - } - - function quickSort() { - qsort(0, count) + }); } } \ No newline at end of file diff --git a/scripts/system/pal.js b/scripts/system/pal.js index c70c2729f5..b122a5170a 100644 --- a/scripts/system/pal.js +++ b/scripts/system/pal.js @@ -317,6 +317,8 @@ function fromQml(message) { // messages are {method, params}, like json-rpc. See } ); break; + case 'http.request': + break; // Handled elsewhere. default: print('Unrecognized message from Pal.qml:', JSON.stringify(message)); } From 254abfa04a6ed73c2a3fcc2ee39d2bd6c269b791 Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Mon, 4 Jun 2018 12:59:08 -0700 Subject: [PATCH 27/63] add request-services to default scripts. --- scripts/defaultScripts.js | 1 + scripts/system/request-service.js | 4 +--- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/scripts/defaultScripts.js b/scripts/defaultScripts.js index 59a51830be..6ea9f4cb81 100644 --- a/scripts/defaultScripts.js +++ b/scripts/defaultScripts.js @@ -12,6 +12,7 @@ // var DEFAULT_SCRIPTS_COMBINED = [ + "system/request-service.js", "system/progress.js", "system/away.js", "system/audio.js", diff --git a/scripts/system/request-service.js b/scripts/system/request-service.js index 3c3b9ccc04..b57f2d4cd7 100644 --- a/scripts/system/request-service.js +++ b/scripts/system/request-service.js @@ -24,9 +24,7 @@ // We will then asynchonously call fromScript({id: theSameString, method: 'http.response', error: errorOrFalsey, response: body}) // on that root object. // RootHttpRequest.qml does this. - // 2. If the uri used (computed from byNameOptions, see request.js) begins with '/', we will: - // a. Prepend Account.metaverseServerUR. - // b. Use the appropriate auth. + // 2. If the uri used (computed from byNameOptions, see request.js) is to our metaverse, we will use the appropriate auth. var request = Script.require('request').request; var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); From e40fc20cb67bf4fd3259b030b5c0b794d81655fc Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Mon, 4 Jun 2018 13:00:08 -0700 Subject: [PATCH 28/63] fix some bugs that had been introduced by various out-of-team prs. --- interface/resources/qml/controls-uit/TextField.qml | 6 +++--- interface/resources/qml/hifi/Pal.qml | 1 - 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/interface/resources/qml/controls-uit/TextField.qml b/interface/resources/qml/controls-uit/TextField.qml index 6743d08275..917068ac01 100644 --- a/interface/resources/qml/controls-uit/TextField.qml +++ b/interface/resources/qml/controls-uit/TextField.qml @@ -165,11 +165,11 @@ TextField { anchors.left: parent.left Binding on anchors.right { - when: parent.right - value: parent.right + when: textField.right + value: textField.right } Binding on wrapMode { - when: parent.right + when: textField.right value: Text.WordWrap } diff --git a/interface/resources/qml/hifi/Pal.qml b/interface/resources/qml/hifi/Pal.qml index 8a067c0733..9a818ef4db 100644 --- a/interface/resources/qml/hifi/Pal.qml +++ b/interface/resources/qml/hifi/Pal.qml @@ -479,7 +479,6 @@ Rectangle { visible: !isCheckBox && !isButton && !isAvgAudio; uuid: model ? model.sessionId : ""; selected: styleData.selected; - isReplicated: model.isReplicated; isAdmin: model && model.admin; isPresent: model && model.isPresent; // Size From 5a1b56d5730094ea889748bfc4f42dfecb997620 Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Mon, 4 Jun 2018 13:11:06 -0700 Subject: [PATCH 29/63] checkpoint with goto feeds working (but without filtering) --- interface/resources/qml/hifi/Feed.qml | 52 ++++++++++++++----- .../qml/hifi/models/PSFListModel.qml | 7 ++- .../qml/hifi/tablet/TabletAddressDialog.qml | 40 +++----------- 3 files changed, 54 insertions(+), 45 deletions(-) diff --git a/interface/resources/qml/hifi/Feed.qml b/interface/resources/qml/hifi/Feed.qml index 3f3a47a297..98721ba2e0 100644 --- a/interface/resources/qml/hifi/Feed.qml +++ b/interface/resources/qml/hifi/Feed.qml @@ -16,10 +16,11 @@ import QtQuick 2.5 import QtGraphicalEffects 1.0 import "toolbars" import "../styles-uit" +import "models" as HifiModels Column { id: root; - visible: false; + visible: !!suggestions.count; property int cardWidth: 212; property int cardHeight: 152; @@ -32,18 +33,37 @@ Column { property int stackedCardShadowHeight: 4; property int labelSize: 20; - property string metaverseServerUrl: ''; + property string metaverseServerUrl: ''; // FIXME loose this? property string protocol: ''; property string actions: 'snapshot'; // sendToScript doesn't get wired until after everything gets created. So we have to queue fillDestinations on nextTick. property string labelText: actions; property string filter: ''; - onFilterChanged: filterChoicesByText(); + // FIXME onFilterChanged: filterChoicesByText(); property var goFunction: null; - property var rpc: null; + property var http: null; HifiConstants { id: hifi } - ListModel { id: suggestions; } + //FIXME ListModel { id: suggestions; } + Component.onCompleted: suggestions.getFirstPage(); + HifiModels.PSFListModel { + id: suggestions; + http: root.http; + property var options: [ + 'include_actions=' + actions, + 'restriction=' + (Account.isLoggedIn() ? 'open,hifi' : 'open'), + 'require_online=true', + 'protocol=' + Window.protocolSignature() + ]; + endpoint: '/api/v1/user_stories?' + options.join('&'); + itemsPerPage: 3; + processPage: function (data) { + console.log('FIXME processPage', suggestions.listModelName, JSON.stringify(data)); + return data.user_stories.map(makeModelData); + }; + listModelName: actions; + listView: scroll; + } function resolveUrl(url) { return (url.indexOf('/') === 0) ? (metaverseServerUrl + url) : url; @@ -60,11 +80,11 @@ Column { data.details.connections = 4; data.action = 'announcement'; } - return { + var fixme = { place_name: name, username: data.username || "", path: data.path || "", - created_at: data.created_at || "", + created_at: data.created_at || data.updated_at || "", // FIXME why aren't we getting created_at? action: data.action || "", thumbnail_url: resolveUrl(thumbnail_url), image_url: resolveUrl(data.details && data.details.image_url), @@ -77,8 +97,11 @@ Column { drillDownToPlace: false, searchText: [name].concat(tags, description || []).join(' ').toUpperCase() - } + }; + console.log('fixme makeModelData', JSON.stringify(fixme)); + return fixme; } + /* FIXME property var allStories: []; property var placeMap: ({}); // Used for making stacks. property int requestId: 0; @@ -108,7 +131,7 @@ Column { ]; var url = metaverseBase + 'user_stories?' + options.join('&'); var thisRequestId = ++requestId; - rpc('request', url, function (error, data) { + http.request(url, function (error, data) { if (thisRequestId !== requestId) { error = 'stale'; } @@ -126,8 +149,9 @@ Column { }); } function fillDestinations() { // Public - console.debug('Feed::fillDestinations()') - + console.debug('Feed::fillDestinations()'); + //suggestions.getFirstPage(); + } function report(label, error) { console.log(label, actions, error || 'ok', allStories.length, 'filtered to', suggestions.count); } @@ -193,6 +217,7 @@ Column { allStories.forEach(makeFilteredStoryProcessor()); root.visible = !!suggestions.count; } + */ RalewayBold { id: label; @@ -202,12 +227,13 @@ Column { } ListView { id: scroll; - model: suggestions; + model: suggestions.model; orientation: ListView.Horizontal; highlightFollowsCurrentItem: false highlightMoveDuration: -1; highlightMoveVelocity: -1; currentIndex: -1; + onAtXEndChanged: { console.log('FIXME onAtXEndChanged', actions, scroll.atXEnd, scroll.atXBeginning); if (scroll.atXEnd && !scroll.atXBeginning) { suggestions.getNextPage(); } } spacing: 12; width: parent.width; @@ -239,6 +265,7 @@ Column { unhoverThunk: function () { hovered = false } } } + /* WTF is this? NumberAnimation { id: anim; target: scroll; @@ -256,4 +283,5 @@ Column { scroll.currentIndex = index; anim.running = true; } + */ } diff --git a/interface/resources/qml/hifi/models/PSFListModel.qml b/interface/resources/qml/hifi/models/PSFListModel.qml index 9858d76d4a..6eff1cc073 100644 --- a/interface/resources/qml/hifi/models/PSFListModel.qml +++ b/interface/resources/qml/hifi/models/PSFListModel.qml @@ -63,7 +63,7 @@ Item { property bool delayedClear: false; function resetModel() { if (!delayedClear) { finalModel.clear(); } - currentPageToRetrieve = 1; + currentPageToRetrieve = 1; console.log('fixme resetModel set currentPageToRetrieve to 1', listModelName); retrievedAtLeastOnePage = false; copyOfItems = []; } @@ -80,6 +80,7 @@ Item { console.debug('handlePage', listModelName, error, JSON.stringify(response)); function fail(message) { console.warn("Warning", listModelName, JSON.stringify(message)); + console.log('FIXME fail setting currentPageToRetrieve to -1', listModelName); currentPageToRetrieve = -1; requestPending = false; delayedClear = false; @@ -96,6 +97,7 @@ Item { } processed = processPage(response.data || response); if (response.total_pages && (response.total_pages === currentPageToRetrieve)) { + console.log('fixme hanglePage set currentPageToRetrieve to -1', listModelName, 'response.total_pages:', response.total_pages, 'old currentPageToRetrieve:', currentPageToRetrieve); currentPageToRetrieve = -1; } if (searchItemTest) { @@ -122,6 +124,7 @@ Item { if (searchItemTest && searchFilter && listView && listView.atYEnd && (currentPageToRetrieve >= 0)) { getNextPage(); // too fancy?? } + if (listView) { console.debug('handlePage completed', listModelName, 'model:', model.count, 'view:', listView.count); } } function applySearchItemTest(items) { return items.filter(function (item) { @@ -140,6 +143,7 @@ Item { property var http; // An Item that has a request function. property var getPage: function () { // Any override MUST call handlePage(), above, even if results empty. if (!http) { return console.warn("Neither http nor getPage was set for", listModelName); } + // If it is a path starting with slash, add the metaverseServer domain. var url = /^\//.test(endpoint) ? (Account.metaverseServerURL + endpoint) : endpoint; var parameters = [ // FIXME: handle sort, search, tag parameters @@ -171,6 +175,7 @@ Item { // onAtYEndChanged: if (theList.atYEnd) { thisPSFListModelId.getNextPage(); } // ...} property var getNextPage: function () { + console.log('fixme getNextPage', listModelName, requestPending, currentPageToRetrieve); if (requestPending || currentPageToRetrieve < 0) { return; } diff --git a/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml b/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml index dc67494e27..104756967e 100644 --- a/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml +++ b/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml @@ -37,38 +37,14 @@ StackView { property string metaverseBase: addressBarDialog.metaverseServerUrl + "/api/v1/"; property var tablet: null; - // This version only implements rpc(method, parameters, callback(error, result)) calls initiated from here, not initiated from .js, nor "notifications". - property var rpcCalls: ({}); - property var rpcCounter: 0; + RootHttpRequest { id: http; } signal sendToScript(var message); - function rpc(method, parameters, callback) { - console.debug('TabletAddressDialog: rpc: method = ', method, 'parameters = ', parameters, 'callback = ', callback) - - rpcCalls[rpcCounter] = callback; - var message = {method: method, params: parameters, id: rpcCounter++, jsonrpc: "2.0"}; - sendToScript(message); - } function fromScript(message) { - if (message.method === 'refreshFeeds') { - var feeds = [happeningNow, places, snapshots]; - console.debug('TabletAddressDialog::fromScript: refreshFeeds', 'feeds = ', feeds); - - feeds.forEach(function(feed) { - feed.protocol = encodeURIComponent(message.protocolSignature); - Qt.callLater(feed.fillDestinations); - }); - - return; + switch (message.method) { + case 'http.response': + http.handleHttpResponse(message); + break; } - - var callback = rpcCalls[message.id]; - if (!callback) { - // FIXME: We often recieve very long messages here, the logging of which is drastically slowing down the main thread - //console.log('No callback for message fromScript', JSON.stringify(message)); - return; - } - delete rpcCalls[message.id]; - callback(message.error, message.result); } Component { id: tabletWebView; TabletWebView {} } @@ -351,7 +327,7 @@ StackView { actions: 'announcement'; filter: addressLine.text; goFunction: goCard; - rpc: root.rpc; + http: http; } Feed { id: places; @@ -364,7 +340,7 @@ StackView { actions: 'concurrency'; filter: addressLine.text; goFunction: goCard; - rpc: root.rpc; + http: http; } Feed { id: snapshots; @@ -378,7 +354,7 @@ StackView { actions: 'snapshot'; filter: addressLine.text; goFunction: goCard; - rpc: root.rpc; + http: http; } } } From 5257a254cb95498d290ff34b41854ff022043b46 Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Mon, 4 Jun 2018 13:12:41 -0700 Subject: [PATCH 30/63] more for the previous --- scripts/system/pal.js | 2 +- scripts/system/tablet-goto.js | 37 ----------------------------------- 2 files changed, 1 insertion(+), 38 deletions(-) diff --git a/scripts/system/pal.js b/scripts/system/pal.js index b122a5170a..41774629e7 100644 --- a/scripts/system/pal.js +++ b/scripts/system/pal.js @@ -318,7 +318,7 @@ function fromQml(message) { // messages are {method, params}, like json-rpc. See ); break; case 'http.request': - break; // Handled elsewhere. + break; // Handled by request-service. default: print('Unrecognized message from Pal.qml:', JSON.stringify(message)); } diff --git a/scripts/system/tablet-goto.js b/scripts/system/tablet-goto.js index 9cd8420a88..46ddeb2bab 100644 --- a/scripts/system/tablet-goto.js +++ b/scripts/system/tablet-goto.js @@ -41,24 +41,6 @@ sortOrder: 8 }); - function fromQml(message) { - console.debug('tablet-goto::fromQml: message = ', JSON.stringify(message)); - - var response = {id: message.id, jsonrpc: "2.0"}; - switch (message.method) { - case 'request': - request(message.params, function (error, data) { - debug('rpc', request, 'error:', error, 'data:', data); - response.error = error; - response.result = data; - tablet.sendToQml(response); - }); - return; - default: - response.error = {message: 'Unrecognized message', data: message}; - } - tablet.sendToQml(response); - } function messagesWaiting(isWaiting) { button.editProperties({ icon: isWaiting ? WAITING_ICON : NORMAL_ICON @@ -66,21 +48,6 @@ }); } - var hasEventBridge = false; - function wireEventBridge(on) { - if (on) { - if (!hasEventBridge) { - tablet.fromQml.connect(fromQml); - hasEventBridge = true; - } - } else { - if (hasEventBridge) { - tablet.fromQml.disconnect(fromQml); - hasEventBridge = false; - } - } - } - function onClicked() { if (onGotoScreen) { // for toolbar-mode: go back to home screen, this will close the window. @@ -98,15 +65,11 @@ onGotoScreen = true; shouldActivateButton = true; button.editProperties({isActive: shouldActivateButton}); - wireEventBridge(true); messagesWaiting(false); - tablet.sendToQml({ method: 'refreshFeeds', protocolSignature: Window.protocolSignature() }) - } else { shouldActivateButton = false; onGotoScreen = false; button.editProperties({isActive: shouldActivateButton}); - wireEventBridge(false); } } button.clicked.connect(onClicked); From f72f8c762b3731b137f21784d6e9e5dd6300e7fd Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Mon, 4 Jun 2018 16:52:20 -0700 Subject: [PATCH 31/63] goto client-side filtering (preliminary) --- interface/resources/qml/hifi/Feed.qml | 15 ++++++++++----- .../resources/qml/hifi/models/PSFListModel.qml | 2 +- .../qml/hifi/tablet/TabletAddressDialog.qml | 4 ---- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/interface/resources/qml/hifi/Feed.qml b/interface/resources/qml/hifi/Feed.qml index 98721ba2e0..fcfb61b1ca 100644 --- a/interface/resources/qml/hifi/Feed.qml +++ b/interface/resources/qml/hifi/Feed.qml @@ -33,7 +33,6 @@ Column { property int stackedCardShadowHeight: 4; property int labelSize: 20; - property string metaverseServerUrl: ''; // FIXME loose this? property string protocol: ''; property string actions: 'snapshot'; // sendToScript doesn't get wired until after everything gets created. So we have to queue fillDestinations on nextTick. @@ -63,10 +62,16 @@ Column { }; listModelName: actions; listView: scroll; + searchFilter: filter.toUpperCase().split(/\s+/).filter(identity).join(' '); + searchItemTest: function (text, item) { + return searchFilter.split().every(function (word) { + return item.searchText.indexOf(word) >= 0; + }); + }; //HRS FIXME remove when endpoint works. } function resolveUrl(url) { - return (url.indexOf('/') === 0) ? (metaverseServerUrl + url) : url; + return (url.indexOf('/') === 0) ? (Account.metaverseServerURL + url) : url; } function makeModelData(data) { // create a new obj from data // ListModel elements will only ever have those properties that are defined by the first obj that is added. @@ -101,6 +106,9 @@ Column { console.log('fixme makeModelData', JSON.stringify(fixme)); return fixme; } + function identity(x) { + return x; + } /* FIXME property var allStories: []; property var placeMap: ({}); // Used for making stacks. @@ -172,9 +180,6 @@ Column { report('user stories'); }); } - function identity(x) { - return x; - } function makeFilteredStoryProcessor() { // answer a function(storyData) that adds it to suggestions if it matches var words = filter.toUpperCase().split(/\s+/).filter(identity); function suggestable(story) { diff --git a/interface/resources/qml/hifi/models/PSFListModel.qml b/interface/resources/qml/hifi/models/PSFListModel.qml index 6eff1cc073..124e08b6cd 100644 --- a/interface/resources/qml/hifi/models/PSFListModel.qml +++ b/interface/resources/qml/hifi/models/PSFListModel.qml @@ -101,10 +101,10 @@ Item { currentPageToRetrieve = -1; } if (searchItemTest) { - copyOfItems = copyOfItems.concat(processed); if (searchFilter) { processed = applySearchItemTest(processed); } + copyOfItems = copyOfItems.concat(processed); } if (localSort) { copyOfItems = copyOfItems.concat(processed); diff --git a/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml b/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml index 104756967e..08f86770e6 100644 --- a/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml +++ b/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml @@ -34,7 +34,6 @@ StackView { height: parent !== null ? parent.height : undefined property int cardWidth: 212; property int cardHeight: 152; - property string metaverseBase: addressBarDialog.metaverseServerUrl + "/api/v1/"; property var tablet: null; RootHttpRequest { id: http; } @@ -322,7 +321,6 @@ StackView { width: parent.width; cardWidth: 312 + (2 * 4); cardHeight: 163 + (2 * 4); - metaverseServerUrl: addressBarDialog.metaverseServerUrl; labelText: 'HAPPENING NOW'; actions: 'announcement'; filter: addressLine.text; @@ -335,7 +333,6 @@ StackView { cardWidth: 210; cardHeight: 110 + messageHeight; messageHeight: 44; - metaverseServerUrl: addressBarDialog.metaverseServerUrl; labelText: 'PLACES'; actions: 'concurrency'; filter: addressLine.text; @@ -349,7 +346,6 @@ StackView { cardHeight: 75 + messageHeight + 4; messageHeight: 32; textPadding: 6; - metaverseServerUrl: addressBarDialog.metaverseServerUrl; labelText: 'RECENT SNAPS'; actions: 'snapshot'; filter: addressLine.text; From 6aa551b35c64ea3ace2a39f93af4321e1d2d9b6e Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Tue, 5 Jun 2018 17:04:29 -0700 Subject: [PATCH 32/63] Don't getNextPage when at beginning (even if that is also the end). --- .../resources/qml/hifi/commerce/common/sendAsset/SendAsset.qml | 2 +- interface/resources/qml/hifi/commerce/wallet/WalletHome.qml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/interface/resources/qml/hifi/commerce/common/sendAsset/SendAsset.qml b/interface/resources/qml/hifi/commerce/common/sendAsset/SendAsset.qml index 21d803b1ab..a416030711 100644 --- a/interface/resources/qml/hifi/commerce/common/sendAsset/SendAsset.qml +++ b/interface/resources/qml/hifi/commerce/common/sendAsset/SendAsset.qml @@ -523,7 +523,7 @@ Item { visible: !connectionsLoading.visible; clip: true; model: connectionsModel.model; - onAtYEndChanged: if (connectionsList.atYEnd /*&& !connectionsList.atYBeginning*/) { connectionsModel.getNextPage(); } + onAtYEndChanged: if (connectionsList.atYEnd && !connectionsList.atYBeginning) { connectionsModel.getNextPage(); } snapMode: ListView.SnapToItem; // Anchors anchors.fill: parent; diff --git a/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml b/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml index 1c0debb12b..047dcd70d1 100644 --- a/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml +++ b/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml @@ -239,7 +239,7 @@ Item { // Only auto-refresh if the user hasn't scrolled // and there is more data to grab - if (transactionHistory.atYBeginning && data.history.length && transactionHistoryModel.currentPageToRetrieve >= 0) { + if (transactionHistory.atYBeginning && data.history.length) { refreshTimer.start(); } return result; From 335aeaeb3842c9a4d3b71ea0a0b37396fda0d6ea Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Tue, 5 Jun 2018 17:06:08 -0700 Subject: [PATCH 33/63] purchases cleanup, wearables, and edition numbers when multiples seen. --- .../qml/hifi/commerce/purchases/Purchases.qml | 79 ++++++------------- 1 file changed, 24 insertions(+), 55 deletions(-) diff --git a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml index 795bb2306a..3ef4d84754 100644 --- a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml +++ b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml @@ -39,8 +39,16 @@ Rectangle { property string installedApps; property bool keyboardRaised: false; property int numUpdatesAvailable: 0; + property var itemCountDictionary: ({}); // Style color: hifi.colors.white; + function getPurchases() { + root.activeView = "purchasesMain"; + root.itemCountDictionary = {}; + root.installedApps = Commerce.getInstalledApps(); + purchasesModel.getFirstPage(); + Commerce.getAvailableUpdates(); + } Connections { target: Commerce; @@ -63,10 +71,7 @@ Rectangle { if ((Settings.getValue("isFirstUseOfPurchases", true) || root.isDebuggingFirstUseTutorial) && root.activeView !== "firstUseTutorial") { root.activeView = "firstUseTutorial"; } else if (!Settings.getValue("isFirstUseOfPurchases", true) && root.activeView === "initialize") { - root.activeView = "purchasesMain"; - root.installedApps = Commerce.getInstalledApps(); - purchasesModel.getFirstPage(); - Commerce.getAvailableUpdates(); + getPurchases(); } } else { console.log("ERROR in Purchases.qml: Unknown wallet status: " + walletStatus); @@ -104,8 +109,7 @@ Rectangle { } onIsShowingMyItemsChanged: { - purchasesModel.resetModel(); - + getPurchases(); } Timer { @@ -163,9 +167,7 @@ Rectangle { Connections { onSendSignalToParent: { if (msg.method === 'sendAssetHome_back' || msg.method === 'closeSendAsset') { - root.activeView = "purchasesMain"; - purchasesModel.getFirstPage(); - Commerce.getAvailableUpdates(); + getPurchases(); } else { sendToScript(msg); } @@ -429,10 +431,7 @@ Rectangle { case 'tutorial_skipClicked': case 'tutorial_finished': Settings.setValue("isFirstUseOfPurchases", false); - root.activeView = "purchasesMain"; - root.installedApps = Commerce.getInstalledApps(); - purchasesModel.getFirstPage(); - Commerce.getAvailableUpdates(); + getPurchases(); break; } } @@ -508,7 +507,7 @@ Rectangle { }, { "displayName": "Content Set", - "filterName": "contentSet" + "filterName": "content_set" }, { "displayName": "Entity", @@ -555,9 +554,9 @@ Rectangle { HifiModels.PSFListModel { id: purchasesModel; itemsPerPage: 6; - + listModelName: 'purchases'; getPage: function () { - console.log('HRS FIXME Purchases getPage', root.isShowingMyItems, filterBar.primaryFilter_filterName, purchasesModel.currentPageToRetrieve, purchasesModel.itemsPerPage); + console.debug('getPage', purchasesModel.listModelName, root.isShowingMyItems, filterBar.primaryFilter_filterName, purchasesModel.currentPageToRetrieve, purchasesModel.itemsPerPage); Commerce.inventory( root.isShowingMyItems ? "proofs" : "purchased", filterBar.primaryFilter_filterName.toLowerCase(), @@ -574,11 +573,14 @@ Rectangle { item.cardBackVisible = false; item.isInstalled = root.installedApps.indexOf(item.id) > -1; item.wornEntityID = ''; + item.displayedItemCount = itemCountDictionary[item.id] = (itemCountDictionary[item.id] || 0) + 1; // HRS FIXME updateable }); - // HRS FIXME purchaess_updateWearables - // HRS FIXME populateDisplayedItemCounts - // HRS FIXME sortByDate + sendToScript({ method: 'purchases_updateWearables' }); + for (var i = 0; i < purchasesModel.count; i++) { // Update all the previous counts with possibly new values. + purchasesModel.setProperty(i, "displayedItemCount", itemCountDictionary[purchasesModel.get(i).id]) + } + return data.assets; /* @@ -604,15 +606,6 @@ Rectangle { } } } - - sendToScript({ method: 'purchases_updateWearables' }); - // FIXME: This ALSO *MUST* be serverside (what if we don't have - // all instances of the item on the client yet?) - //populateDisplayedItemCounts(); - - // FIXME: Sorting by date should be done serverside (we should always get - // the most recent purchases on the 1st page) - //sortByDate(); } */ } @@ -807,7 +800,7 @@ Rectangle { onAtYEndChanged: { - if (purchasesContentsList.atYEnd) { + if (purchasesContentsList.atYEnd && !purchasesContentsList.atYBeginning) { console.log("User scrolled to the bottom of 'Purchases'."); purchasesModel.getNextPage(); } @@ -992,7 +985,7 @@ Rectangle { // // FUNCTION DEFINITIONS START // - + /* fixme remove function processInventoryResult(inventory) { // HRS FIXME remove for (var i = 0; i < inventory.length; i++) { if (inventory[i].status.length > 1) { @@ -1002,31 +995,7 @@ Rectangle { inventory[i].categories = inventory[i].categories.join(';'); } return inventory; - } - - function populateDisplayedItemCounts() { - var itemCountDictionary = {}; - var currentItemId; - for (var i = 0; i < purchasesModel.count; i++) { - currentItemId = purchasesModel.get(i).id; - if (itemCountDictionary[currentItemId] === undefined) { - itemCountDictionary[currentItemId] = 1; - } else { - itemCountDictionary[currentItemId]++; - } - } - - for (var i = 0; i < purchasesModel.count; i++) { - purchasesModel.setProperty(i, "displayedItemCount", itemCountDictionary[purchasesModel.get(i).id]); - } - } - - function sortByDate() { - purchasesModel.sortColumnName = "purchase_date"; - purchasesModel.isSortingDescending = true; - purchasesModel.valuesAreNumerical = true; - purchasesModel.quickSort(); - } + } */ function updateCurrentlyWornWearables(wearables) { for (var i = 0; i < purchasesModel.count; i++) { From 8185d1b5f5d604f2013885d937ccc36e9f18a1f3 Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Wed, 6 Jun 2018 15:24:54 -0700 Subject: [PATCH 34/63] cleanup and consistently don't getNextPage at beginning --- interface/resources/qml/hifi/Feed.qml | 8 ++------ interface/resources/qml/hifi/Pal.qml | 6 +++++- .../resources/qml/hifi/commerce/wallet/WalletHome.qml | 2 +- interface/resources/qml/hifi/models/PSFListModel.qml | 6 ++---- 4 files changed, 10 insertions(+), 12 deletions(-) diff --git a/interface/resources/qml/hifi/Feed.qml b/interface/resources/qml/hifi/Feed.qml index fcfb61b1ca..1d28f18f9d 100644 --- a/interface/resources/qml/hifi/Feed.qml +++ b/interface/resources/qml/hifi/Feed.qml @@ -43,7 +43,6 @@ Column { property var http: null; HifiConstants { id: hifi } - //FIXME ListModel { id: suggestions; } Component.onCompleted: suggestions.getFirstPage(); HifiModels.PSFListModel { id: suggestions; @@ -57,7 +56,6 @@ Column { endpoint: '/api/v1/user_stories?' + options.join('&'); itemsPerPage: 3; processPage: function (data) { - console.log('FIXME processPage', suggestions.listModelName, JSON.stringify(data)); return data.user_stories.map(makeModelData); }; listModelName: actions; @@ -85,7 +83,7 @@ Column { data.details.connections = 4; data.action = 'announcement'; } - var fixme = { + return { place_name: name, username: data.username || "", path: data.path || "", @@ -103,8 +101,6 @@ Column { searchText: [name].concat(tags, description || []).join(' ').toUpperCase() }; - console.log('fixme makeModelData', JSON.stringify(fixme)); - return fixme; } function identity(x) { return x; @@ -238,7 +234,7 @@ Column { highlightMoveDuration: -1; highlightMoveVelocity: -1; currentIndex: -1; - onAtXEndChanged: { console.log('FIXME onAtXEndChanged', actions, scroll.atXEnd, scroll.atXBeginning); if (scroll.atXEnd && !scroll.atXBeginning) { suggestions.getNextPage(); } } + onAtXEndChanged: { if (scroll.atXEnd && !scroll.atXBeginning) { suggestions.getNextPage(); } } spacing: 12; width: parent.width; diff --git a/interface/resources/qml/hifi/Pal.qml b/interface/resources/qml/hifi/Pal.qml index 9a818ef4db..fbe34b2ebc 100644 --- a/interface/resources/qml/hifi/Pal.qml +++ b/interface/resources/qml/hifi/Pal.qml @@ -780,7 +780,11 @@ Rectangle { model: connectionsUserModel.model; Connections { target: connectionsTable.flickableItem; - onAtYEndChanged: if (connectionsTable.flickableItem.atYEnd) { connectionsUserModel.getNextPage(); } + onAtYEndChanged: { + if (connectionsTable.flickableItem.atYEnd && !connectionsTable.flickableItem.atYBeginning) { + connectionsUserModel.getNextPage(); + } + } } // This Rectangle refers to each Row in the connectionsTable. diff --git a/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml b/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml index 047dcd70d1..9076f10ebc 100644 --- a/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml +++ b/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml @@ -400,7 +400,7 @@ Item { } } onAtYEndChanged: { - if (transactionHistory.atYEnd) { + if (transactionHistory.atYEnd && !transactionHistory.atYBeginning) { console.log("User scrolled to the bottom of 'Recent Activity'."); transactionHistoryModel.getNextPage(); } diff --git a/interface/resources/qml/hifi/models/PSFListModel.qml b/interface/resources/qml/hifi/models/PSFListModel.qml index 124e08b6cd..5cca73af92 100644 --- a/interface/resources/qml/hifi/models/PSFListModel.qml +++ b/interface/resources/qml/hifi/models/PSFListModel.qml @@ -79,8 +79,7 @@ Item { var processed; console.debug('handlePage', listModelName, error, JSON.stringify(response)); function fail(message) { - console.warn("Warning", listModelName, JSON.stringify(message)); - console.log('FIXME fail setting currentPageToRetrieve to -1', listModelName); + console.warn("Warning page fail", listModelName, JSON.stringify(message)); currentPageToRetrieve = -1; requestPending = false; delayedClear = false; @@ -97,7 +96,6 @@ Item { } processed = processPage(response.data || response); if (response.total_pages && (response.total_pages === currentPageToRetrieve)) { - console.log('fixme hanglePage set currentPageToRetrieve to -1', listModelName, 'response.total_pages:', response.total_pages, 'old currentPageToRetrieve:', currentPageToRetrieve); currentPageToRetrieve = -1; } if (searchItemTest) { @@ -172,7 +170,7 @@ Item { // ListView { // id: theList // model: thisPSFListModelId - // onAtYEndChanged: if (theList.atYEnd) { thisPSFListModelId.getNextPage(); } + // onAtYEndChanged: if (theList.atYEnd && !theList.atYBeginning) { thisPSFListModelId.getNextPage(); } // ...} property var getNextPage: function () { console.log('fixme getNextPage', listModelName, requestPending, currentPageToRetrieve); From 048872c79ad94952a587092a2095a73db174885a Mon Sep 17 00:00:00 2001 From: David Back Date: Wed, 6 Jun 2018 17:32:52 -0700 Subject: [PATCH 35/63] use messageEquip flag to fix Hifi-Hand-Grab --- .../controllerModules/equipEntity.js | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/scripts/system/controllers/controllerModules/equipEntity.js b/scripts/system/controllers/controllerModules/equipEntity.js index 08b88fe74d..d87f5dc94a 100644 --- a/scripts/system/controllers/controllerModules/equipEntity.js +++ b/scripts/system/controllers/controllerModules/equipEntity.js @@ -274,6 +274,7 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa this.equipedWithSecondary = false; this.handHasBeenRightsideUp = false; this.mouseEquip = false; + this.messageEquip = false; this.parameters = makeDispatcherModuleParameters( 300, @@ -283,11 +284,10 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa var equipHotspotBuddy = new EquipHotspotBuddy(); - this.setMessageGrabData = function(entityProperties, mouseEquip) { + this.setMessageGrabData = function(entityProperties) { if (entityProperties) { this.messageGrabEntity = true; this.grabEntityProps = entityProperties; - this.mouseEquip = mouseEquip; } }; @@ -585,6 +585,7 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa this.messageGrabEntity = false; this.grabEntityProps = null; this.mouseEquip = false; + this.messageEquip = false; }; this.updateInputs = function (controllerData) { @@ -661,7 +662,7 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa var timestamp = Date.now(); this.updateInputs(controllerData); - if (!this.mouseEquip && !this.isTargetIDValid(controllerData)) { + if (!this.mouseEquip && !this.messageEquip && !this.isTargetIDValid(controllerData)) { this.endEquipEntity(); return makeRunningValues(false, [], []); } @@ -762,9 +763,8 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa var equipModule = (data.hand === "left") ? leftEquipEntity : rightEquipEntity; var entityProperties = Entities.getEntityProperties(data.entityID, DISPATCHER_PROPERTIES); entityProperties.id = data.entityID; - var mouseEquip = false; - equipModule.setMessageGrabData(entityProperties, mouseEquip); - + equipModule.messageEquip = true; + equipModule.setMessageGrabData(entityProperties); } catch (e) { print("WARNING: equipEntity.js -- error parsing Hifi-Hand-Grab message: " + message); } @@ -812,15 +812,16 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa var distanceToLeftHand = Vec3.distance(entityProperties.position, leftHandPosition); var leftHandAvailable = leftEquipEntity.targetEntityID === null; var rightHandAvailable = rightEquipEntity.targetEntityID === null; - var mouseEquip = true; if (rightHandAvailable && (distanceToRightHand < distanceToLeftHand || !leftHandAvailable)) { // clear any existing grab actions on the entity now (their later removal could affect bootstrapping flags) clearGrabActions(entityID); - rightEquipEntity.setMessageGrabData(entityProperties, mouseEquip); + rightEquipEntity.mouseEquip = true; + rightEquipEntity.setMessageGrabData(entityProperties); } else if (leftHandAvailable && (distanceToLeftHand < distanceToRightHand || !rightHandAvailable)) { // clear any existing grab actions on the entity now (their later removal could affect bootstrapping flags) clearGrabActions(entityID); - leftEquipEntity.setMessageGrabData(entityProperties, mouseEquip); + leftEquipEntity.mouseEquip = true; + leftEquipEntity.setMessageGrabData(entityProperties); } } } From 0ee55a09b6dfa3a035162b88b3808e85b8a27fb4 Mon Sep 17 00:00:00 2001 From: David Back Date: Wed, 6 Jun 2018 17:35:25 -0700 Subject: [PATCH 36/63] fix tabs --- .../controllers/controllerModules/equipEntity.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/scripts/system/controllers/controllerModules/equipEntity.js b/scripts/system/controllers/controllerModules/equipEntity.js index d87f5dc94a..4e1cf66531 100644 --- a/scripts/system/controllers/controllerModules/equipEntity.js +++ b/scripts/system/controllers/controllerModules/equipEntity.js @@ -274,7 +274,7 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa this.equipedWithSecondary = false; this.handHasBeenRightsideUp = false; this.mouseEquip = false; - this.messageEquip = false; + this.messageEquip = false; this.parameters = makeDispatcherModuleParameters( 300, @@ -585,7 +585,7 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa this.messageGrabEntity = false; this.grabEntityProps = null; this.mouseEquip = false; - this.messageEquip = false; + this.messageEquip = false; }; this.updateInputs = function (controllerData) { @@ -763,7 +763,7 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa var equipModule = (data.hand === "left") ? leftEquipEntity : rightEquipEntity; var entityProperties = Entities.getEntityProperties(data.entityID, DISPATCHER_PROPERTIES); entityProperties.id = data.entityID; - equipModule.messageEquip = true; + equipModule.messageEquip = true; equipModule.setMessageGrabData(entityProperties); } catch (e) { print("WARNING: equipEntity.js -- error parsing Hifi-Hand-Grab message: " + message); @@ -815,12 +815,12 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa if (rightHandAvailable && (distanceToRightHand < distanceToLeftHand || !leftHandAvailable)) { // clear any existing grab actions on the entity now (their later removal could affect bootstrapping flags) clearGrabActions(entityID); - rightEquipEntity.mouseEquip = true; + rightEquipEntity.mouseEquip = true; rightEquipEntity.setMessageGrabData(entityProperties); } else if (leftHandAvailable && (distanceToLeftHand < distanceToRightHand || !rightHandAvailable)) { // clear any existing grab actions on the entity now (their later removal could affect bootstrapping flags) clearGrabActions(entityID); - leftEquipEntity.mouseEquip = true; + leftEquipEntity.mouseEquip = true; leftEquipEntity.setMessageGrabData(entityProperties); } } From 8f5de7b16a3b8b000b76f5ad8ef79b351a20cb68 Mon Sep 17 00:00:00 2001 From: David Back Date: Wed, 6 Jun 2018 18:05:26 -0700 Subject: [PATCH 37/63] use messageGrabEntity flag instead --- .../controllers/controllerModules/equipEntity.js | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/scripts/system/controllers/controllerModules/equipEntity.js b/scripts/system/controllers/controllerModules/equipEntity.js index 4e1cf66531..91c8d89daf 100644 --- a/scripts/system/controllers/controllerModules/equipEntity.js +++ b/scripts/system/controllers/controllerModules/equipEntity.js @@ -273,8 +273,6 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa this.shouldSendStart = false; this.equipedWithSecondary = false; this.handHasBeenRightsideUp = false; - this.mouseEquip = false; - this.messageEquip = false; this.parameters = makeDispatcherModuleParameters( 300, @@ -584,8 +582,6 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa this.targetEntityID = null; this.messageGrabEntity = false; this.grabEntityProps = null; - this.mouseEquip = false; - this.messageEquip = false; }; this.updateInputs = function (controllerData) { @@ -631,14 +627,12 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa // if the potentialHotspot is cloneable, clone it and return it // if the potentialHotspot os not cloneable and locked return null - if (potentialEquipHotspot && (((this.triggerSmoothedSqueezed() || this.secondarySmoothedSqueezed()) && !this.waitForTriggerRelease) || this.messageGrabEntity)) { this.grabbedHotspot = potentialEquipHotspot; this.targetEntityID = this.grabbedHotspot.entityID; this.startEquipEntity(controllerData); - this.messageGrabEntity = false; this.equipedWithSecondary = this.secondarySmoothedSqueezed(); return makeRunningValues(true, [potentialEquipHotspot.entityID], []); } else { @@ -662,7 +656,7 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa var timestamp = Date.now(); this.updateInputs(controllerData); - if (!this.mouseEquip && !this.messageEquip && !this.isTargetIDValid(controllerData)) { + if (!this.messageGrabEntity && !this.isTargetIDValid(controllerData)) { this.endEquipEntity(); return makeRunningValues(false, [], []); } @@ -763,7 +757,6 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa var equipModule = (data.hand === "left") ? leftEquipEntity : rightEquipEntity; var entityProperties = Entities.getEntityProperties(data.entityID, DISPATCHER_PROPERTIES); entityProperties.id = data.entityID; - equipModule.messageEquip = true; equipModule.setMessageGrabData(entityProperties); } catch (e) { print("WARNING: equipEntity.js -- error parsing Hifi-Hand-Grab message: " + message); @@ -815,12 +808,10 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa if (rightHandAvailable && (distanceToRightHand < distanceToLeftHand || !leftHandAvailable)) { // clear any existing grab actions on the entity now (their later removal could affect bootstrapping flags) clearGrabActions(entityID); - rightEquipEntity.mouseEquip = true; rightEquipEntity.setMessageGrabData(entityProperties); } else if (leftHandAvailable && (distanceToLeftHand < distanceToRightHand || !rightHandAvailable)) { // clear any existing grab actions on the entity now (their later removal could affect bootstrapping flags) clearGrabActions(entityID); - leftEquipEntity.mouseEquip = true; leftEquipEntity.setMessageGrabData(entityProperties); } } From 664884c9a85bb7474af690c6d516816513f0e768 Mon Sep 17 00:00:00 2001 From: Cristian Duarte Date: Thu, 7 Jun 2018 16:41:41 -0300 Subject: [PATCH 38/63] Use third person camera mode when switching to My View from Radar mode. This makes it consistent with the default third person camera in My View. --- scripts/system/+android/radar.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/system/+android/radar.js b/scripts/system/+android/radar.js index 5d93ed4db1..1cbe721ad0 100644 --- a/scripts/system/+android/radar.js +++ b/scripts/system/+android/radar.js @@ -1119,7 +1119,7 @@ function startRadar() { function endRadar() { printd("-- endRadar"); - Camera.mode = "first person"; + Camera.mode = "third person"; radar = false; Controller.setVPadEnabled(true); From 8b085051161890cde2e30b611e783d365d6ade2e Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Thu, 7 Jun 2018 16:14:18 -0700 Subject: [PATCH 39/63] using filter/sort endpoint parameters --- interface/resources/qml/hifi/Feed.qml | 4 ++-- interface/resources/qml/hifi/Pal.qml | 13 +++++++++++-- .../hifi/commerce/common/sendAsset/SendAsset.qml | 4 ++-- .../resources/qml/hifi/models/PSFListModel.qml | 10 ++++++++-- 4 files changed, 23 insertions(+), 8 deletions(-) diff --git a/interface/resources/qml/hifi/Feed.qml b/interface/resources/qml/hifi/Feed.qml index 1d28f18f9d..4aa07f5d99 100644 --- a/interface/resources/qml/hifi/Feed.qml +++ b/interface/resources/qml/hifi/Feed.qml @@ -61,11 +61,11 @@ Column { listModelName: actions; listView: scroll; searchFilter: filter.toUpperCase().split(/\s+/).filter(identity).join(' '); - searchItemTest: function (text, item) { + /* FIXME searchItemTest: function (text, item) { return searchFilter.split().every(function (word) { return item.searchText.indexOf(word) >= 0; }); - }; //HRS FIXME remove when endpoint works. + };*/ //HRS FIXME remove when endpoint works. } function resolveUrl(url) { diff --git a/interface/resources/qml/hifi/Pal.qml b/interface/resources/qml/hifi/Pal.qml index fbe34b2ebc..a1cc156fdd 100644 --- a/interface/resources/qml/hifi/Pal.qml +++ b/interface/resources/qml/hifi/Pal.qml @@ -50,9 +50,18 @@ Rectangle { id: connectionsUserModel; http: http; endpoint: "/api/v1/users?filter=connections"; - localSort: true; + //FIXME localSort: true; property var sortColumn: connectionsTable.getColumn(connectionsTable.sortIndicatorColumn); - sortProperty: sortColumn ? sortColumn.role : "userName"; + sortProperty: switch (sortColumn && sortColumn.role) { + case 'placeName': + 'location'; + break; + case 'connection': + 'is_friend'; + break; + default: + 'username'; + } sortAscending: connectionsTable.sortIndicatorOrder === Qt.AscendingOrder; itemsPerPage: 9; listView: connectionsTable; diff --git a/interface/resources/qml/hifi/commerce/common/sendAsset/SendAsset.qml b/interface/resources/qml/hifi/commerce/common/sendAsset/SendAsset.qml index a416030711..b3fd661088 100644 --- a/interface/resources/qml/hifi/commerce/common/sendAsset/SendAsset.qml +++ b/interface/resources/qml/hifi/commerce/common/sendAsset/SendAsset.qml @@ -404,9 +404,9 @@ Item { return data.users; }; searchFilter: filterBar.text; - searchItemTest: function (text, item) { + /* FIXME searchItemTest: function (text, item) { return item.username.toLowerCase().indexOf(text.toLowerCase()) !== -1; - }; //HRS FIXME remove when endpoint works. + };*/ //HRS FIXME remove when endpoint works. } Rectangle { diff --git a/interface/resources/qml/hifi/models/PSFListModel.qml b/interface/resources/qml/hifi/models/PSFListModel.qml index 5cca73af92..b00d3684cf 100644 --- a/interface/resources/qml/hifi/models/PSFListModel.qml +++ b/interface/resources/qml/hifi/models/PSFListModel.qml @@ -27,7 +27,7 @@ Item { property string endpoint; property string sortProperty; // Currently only handles sorting on one column, which fits with current needs and tables. property bool sortAscending; - property string sortKey: !sortProperty ? '' : (sortProperty + "," + (sortAscending ? "asc" : "desc")); + property string sortKey: !sortProperty ? '' : (sortProperty + "," + (sortAscending ? "ASC" : "DESC")); property string searchFilter: ""; property string tagsFilter; @@ -144,10 +144,16 @@ Item { // If it is a path starting with slash, add the metaverseServer domain. var url = /^\//.test(endpoint) ? (Account.metaverseServerURL + endpoint) : endpoint; var parameters = [ - // FIXME: handle sort, search, tag parameters + // FIXME: handle sort, tag parameters 'per_page=' + itemsPerPage, 'page=' + currentPageToRetrieve ]; + if (!searchItemTest && searchFilter) { + parameters.splice(parameters.length, 0, 'search=' + searchFilter); + } + if (!localSort && sortKey) { + parameters.splice(parameters.length, 0, 'sort=' + sortKey); + } var parametersSeparator = /\?/.test(url) ? '&' : '?'; url = url + parametersSeparator + parameters.join('&'); console.debug('getPage', listModelName, currentPageToRetrieve); From c7007c089f5fd4b5b55c7298f46b9111a48ca36b Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Thu, 7 Jun 2018 16:35:22 -0700 Subject: [PATCH 40/63] fix tablet not disappearing --- interface/src/ui/overlays/ModelOverlay.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/interface/src/ui/overlays/ModelOverlay.cpp b/interface/src/ui/overlays/ModelOverlay.cpp index fbb5aae84c..c8056c11c8 100644 --- a/interface/src/ui/overlays/ModelOverlay.cpp +++ b/interface/src/ui/overlays/ModelOverlay.cpp @@ -100,24 +100,32 @@ void ModelOverlay::update(float deltatime) { processMaterials(); emit DependencyManager::get()->modelAddedToScene(getID(), NestableType::Overlay, _model); } + bool metaDirty = false; if (_visibleDirty) { _visibleDirty = false; // don't show overlays in mirrors or spectator-cam unless _isVisibleInSecondaryCamera is true uint8_t modelRenderTagMask = (_isVisibleInSecondaryCamera ? render::hifi::TAG_ALL_VIEWS : render::hifi::TAG_MAIN_VIEW); _model->setTagMask(modelRenderTagMask, scene); _model->setVisibleInScene(getVisible(), scene); + metaDirty = true; } if (_drawInFrontDirty) { _drawInFrontDirty = false; _model->setLayeredInFront(getDrawInFront(), scene); + metaDirty = true; } if (_drawInHUDDirty) { _drawInHUDDirty = false; _model->setLayeredInHUD(getDrawHUDLayer(), scene); + metaDirty = true; } if (_groupCulledDirty) { _groupCulledDirty = false; - _model->setGroupCulled(_isGroupCulled); + _model->setGroupCulled(_isGroupCulled, scene); + metaDirty = true; + } + if (metaDirty) { + transaction.updateItem(getRenderItemID(), [](Overlay& data) {}); } scene->enqueueTransaction(transaction); From 348d5fc359cfdc3b44a400004c45dd0d0e986ecc Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Thu, 7 Jun 2018 17:01:48 -0700 Subject: [PATCH 41/63] inventory text filter --- interface/resources/qml/hifi/commerce/purchases/Purchases.qml | 1 + interface/src/commerce/Ledger.cpp | 3 ++- interface/src/commerce/Ledger.h | 2 +- interface/src/commerce/QmlCommerce.cpp | 4 ++-- interface/src/commerce/QmlCommerce.h | 2 +- 5 files changed, 7 insertions(+), 5 deletions(-) diff --git a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml index 3ef4d84754..f7982717e7 100644 --- a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml +++ b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml @@ -560,6 +560,7 @@ Rectangle { Commerce.inventory( root.isShowingMyItems ? "proofs" : "purchased", filterBar.primaryFilter_filterName.toLowerCase(), + filterBar.text, purchasesModel.currentPageToRetrieve, purchasesModel.itemsPerPage ); diff --git a/interface/src/commerce/Ledger.cpp b/interface/src/commerce/Ledger.cpp index 5bd8c77d04..69698e82a6 100644 --- a/interface/src/commerce/Ledger.cpp +++ b/interface/src/commerce/Ledger.cpp @@ -134,10 +134,11 @@ void Ledger::balance(const QStringList& keys) { keysQuery("balance", "balanceSuccess", "balanceFailure"); } -void Ledger::inventory(const QString& editionFilter, const QString& typeFilter, const int& page, const int& perPage) { +void Ledger::inventory(const QString& editionFilter, const QString& typeFilter, const QString& titleFilter, const int& page, const int& perPage) { QJsonObject params; params["edition_filter"] = editionFilter; params["type_filter"] = typeFilter; + params["title_filter"] = titleFilter; params["page"] = page; params["per_page"] = perPage; keysQuery("inventory", "inventorySuccess", "inventoryFailure", params); diff --git a/interface/src/commerce/Ledger.h b/interface/src/commerce/Ledger.h index 9733658357..8a8fd2630a 100644 --- a/interface/src/commerce/Ledger.h +++ b/interface/src/commerce/Ledger.h @@ -28,7 +28,7 @@ public: void buy(const QString& hfc_key, int cost, const QString& asset_id, const QString& inventory_key, const bool controlled_failure = false); bool receiveAt(const QString& hfc_key, const QString& signing_key); void balance(const QStringList& keys); - void inventory(const QString& editionFilter, const QString& typeFilter, const int& page, const int& perPage); + void inventory(const QString& editionFilter, const QString& typeFilter, const QString& titleFilter, const int& page, const int& perPage); void history(const QStringList& keys, const int& pageNumber, const int& itemsPerPage); void account(); void updateLocation(const QString& asset_id, const QString& location, const bool& alsoUpdateSiblings = false, const bool controlledFailure = false); diff --git a/interface/src/commerce/QmlCommerce.cpp b/interface/src/commerce/QmlCommerce.cpp index dba8cd03c7..834de2150d 100644 --- a/interface/src/commerce/QmlCommerce.cpp +++ b/interface/src/commerce/QmlCommerce.cpp @@ -105,12 +105,12 @@ void QmlCommerce::balance() { } } -void QmlCommerce::inventory(const QString& editionFilter, const QString& typeFilter, const int& page, const int& perPage) { +void QmlCommerce::inventory(const QString& editionFilter, const QString& typeFilter, const QString& titleFilter, const int& page, const int& perPage) { auto ledger = DependencyManager::get(); auto wallet = DependencyManager::get(); QStringList cachedPublicKeys = wallet->listPublicKeys(); if (!cachedPublicKeys.isEmpty()) { - ledger->inventory(editionFilter, typeFilter, page, perPage); + ledger->inventory(editionFilter, typeFilter, titleFilter, page, perPage); } } diff --git a/interface/src/commerce/QmlCommerce.h b/interface/src/commerce/QmlCommerce.h index 3a08b4a19b..a0c6916799 100644 --- a/interface/src/commerce/QmlCommerce.h +++ b/interface/src/commerce/QmlCommerce.h @@ -73,7 +73,7 @@ protected: Q_INVOKABLE void buy(const QString& assetId, int cost, const bool controlledFailure = false); Q_INVOKABLE void balance(); - Q_INVOKABLE void inventory(const QString& editionFilter = QString(), const QString& typeFilter = QString(), const int& page = 1, const int& perPage = 20); + Q_INVOKABLE void inventory(const QString& editionFilter = QString(), const QString& typeFilter = QString(), const QString& titleFilter = QString(), const int& page = 1, const int& perPage = 20); Q_INVOKABLE void history(const int& pageNumber, const int& itemsPerPage = 100); Q_INVOKABLE void generateKeyPair(); Q_INVOKABLE void account(); From 595a0d873367f26b1750e9d95e3d1f0f1c60f641 Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Thu, 7 Jun 2018 19:24:05 -0700 Subject: [PATCH 42/63] restore lost code --- interface/resources/qml/hifi/Pal.qml | 1 + 1 file changed, 1 insertion(+) diff --git a/interface/resources/qml/hifi/Pal.qml b/interface/resources/qml/hifi/Pal.qml index a1cc156fdd..bac7533c9f 100644 --- a/interface/resources/qml/hifi/Pal.qml +++ b/interface/resources/qml/hifi/Pal.qml @@ -488,6 +488,7 @@ Rectangle { visible: !isCheckBox && !isButton && !isAvgAudio; uuid: model ? model.sessionId : ""; selected: styleData.selected; + isReplicated: model && model.isReplicated; isAdmin: model && model.admin; isPresent: model && model.isPresent; // Size From 8e1e93abb016a3d26441b5248074fd6f5008efa1 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Fri, 8 Jun 2018 15:04:01 +1200 Subject: [PATCH 43/63] Don't save user location if not connected to domain --- libraries/networking/src/AddressManager.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/libraries/networking/src/AddressManager.cpp b/libraries/networking/src/AddressManager.cpp index 977cabb57a..317be194b8 100644 --- a/libraries/networking/src/AddressManager.cpp +++ b/libraries/networking/src/AddressManager.cpp @@ -157,7 +157,11 @@ void AddressManager::storeCurrentAddress() { // be loaded over http(s) // url.scheme() == URL_SCHEME_HTTP || // url.scheme() == URL_SCHEME_HTTPS || - currentAddressHandle.set(url); + if (isConnected()) { + currentAddressHandle.set(url); + } else { + qCWarning(networking) << "Ignoring attempt to save current address because not connected to domain:" << url; + } } else { qCWarning(networking) << "Ignoring attempt to save current address with an invalid url:" << url; } From 64e466ee7dc6db85c4068bb5844fd5d37d11102d Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Thu, 7 Jun 2018 21:10:40 -0700 Subject: [PATCH 44/63] snapshot stacks --- interface/resources/qml/hifi/Feed.qml | 23 +++++++++++-------- .../qml/hifi/commerce/purchases/Purchases.qml | 2 +- .../qml/hifi/models/PSFListModel.qml | 3 ++- 3 files changed, 17 insertions(+), 11 deletions(-) diff --git a/interface/resources/qml/hifi/Feed.qml b/interface/resources/qml/hifi/Feed.qml index 4aa07f5d99..cd50eb14d8 100644 --- a/interface/resources/qml/hifi/Feed.qml +++ b/interface/resources/qml/hifi/Feed.qml @@ -41,6 +41,7 @@ Column { // FIXME onFilterChanged: filterChoicesByText(); property var goFunction: null; property var http: null; + property var itemCountDictionary: ({}); HifiConstants { id: hifi } Component.onCompleted: suggestions.getFirstPage(); @@ -55,12 +56,20 @@ Column { ]; endpoint: '/api/v1/user_stories?' + options.join('&'); itemsPerPage: 3; + getFirstPage: function (delayRefresh) { + root.itemCountDictionary = {}; + suggestions.getFirstPageInternal(delayRefresh); + }; processPage: function (data) { - return data.user_stories.map(makeModelData); + var adding = data.user_stories.map(makeModelData); + for (var i = 0; i < suggestions.count; i++) { // Update all the previous counts with possibly new values. + suggestions.setProperty(i, "drillDownToPlace", itemCountDictionary[suggestions.get(i).place_name] > 0); + } + return adding; }; listModelName: actions; listView: scroll; - searchFilter: filter.toUpperCase().split(/\s+/).filter(identity).join(' '); + searchFilter: filter; // FIXME .toUpperCase().split(/\s+/).filter(identity).join(' '); /* FIXME searchItemTest: function (text, item) { return searchFilter.split().every(function (word) { return item.searchText.indexOf(word) >= 0; @@ -78,11 +87,7 @@ Column { tags = data.tags || [data.action, data.username], description = data.description || "", thumbnail_url = data.thumbnail_url || ""; - if (actions === 'concurrency,snapshot') { - // A temporary hack for simulating announcements. We won't use this in production, but if requested, we'll use this data like announcements. - data.details.connections = 4; - data.action = 'announcement'; - } + itemCountDictionary[name] = (itemCountDictionary[name] || 0) + 1; return { place_name: name, username: data.username || "", @@ -97,9 +102,9 @@ Column { tags: tags, description: description, online_users: data.details.connections || data.details.concurrency || 0, - drillDownToPlace: false, + drillDownToPlace: false - searchText: [name].concat(tags, description || []).join(' ').toUpperCase() + //searchText: [name].concat(tags, description || []).join(' ').toUpperCase() // FIXME remove }; } function identity(x) { diff --git a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml index f7982717e7..accdc9984b 100644 --- a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml +++ b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml @@ -42,7 +42,7 @@ Rectangle { property var itemCountDictionary: ({}); // Style color: hifi.colors.white; - function getPurchases() { + function getPurchases() { // FIXME: use the new purchasesModel.getFirstPage root.activeView = "purchasesMain"; root.itemCountDictionary = {}; root.installedApps = Commerce.getInstalledApps(); diff --git a/interface/resources/qml/hifi/models/PSFListModel.qml b/interface/resources/qml/hifi/models/PSFListModel.qml index b00d3684cf..d17689ab98 100644 --- a/interface/resources/qml/hifi/models/PSFListModel.qml +++ b/interface/resources/qml/hifi/models/PSFListModel.qml @@ -162,7 +162,8 @@ Item { // Start the show by retrieving data according to `getPage()`. // It can be custom-defined by this item's Parent. - property var getFirstPage: function (delayClear) { + property var getFirstPage: function (delayClear) { getFirstPageInternal(delayClear); } + function getFirstPageInternal(delayClear) { delayedClear = !!delayClear; resetModel(); requestPending = true; From 21ed081eae4ae6b5e7c0f999c2eb616965ba655c Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Fri, 8 Jun 2018 09:52:11 -0700 Subject: [PATCH 45/63] proper name for updated type filter of inventory endpoint --- interface/resources/qml/hifi/commerce/purchases/Purchases.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml index accdc9984b..41ad978e33 100644 --- a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml +++ b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml @@ -519,7 +519,7 @@ Rectangle { }, { "displayName": "Updatable", - "filterName": "updatable" + "filterName": "updated" } ] filterBar.primaryFilterChoices.clear(); From d4a19e7bc9f340bf3d88c28f0fc8146ef90230dc Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Fri, 8 Jun 2018 10:52:14 -0700 Subject: [PATCH 46/63] Fix MS15721: Delete app.json if installed script isn't running --- interface/src/commerce/QmlCommerce.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/interface/src/commerce/QmlCommerce.cpp b/interface/src/commerce/QmlCommerce.cpp index 722f29ba2f..3c1e8109c9 100644 --- a/interface/src/commerce/QmlCommerce.cpp +++ b/interface/src/commerce/QmlCommerce.cpp @@ -227,10 +227,13 @@ QString QmlCommerce::getInstalledApps() { QString scriptURL = appFileJsonObject["scriptURL"].toString(); // If the script .app.json is on the user's local disk but the associated script isn't running - // for some reason, start that script again. + // for some reason (i.e. the user stopped it from Running Scripts), + // delete the .app.json from the user's local disk. if (!runningScripts.contains(scriptURL)) { - if ((DependencyManager::get()->loadScript(scriptURL.trimmed())).isNull()) { - qCDebug(commerce) << "Couldn't start script while checking installed apps."; + if (!appFile.remove()) { + qCWarning(commerce) + << "Couldn't delete local .app.json file (app's script isn't running). App filename is:" + << appFileName; } } } else { From a7d75b121fe5f0fdd944144daf422a64f207c632 Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Fri, 8 Jun 2018 11:20:41 -0700 Subject: [PATCH 47/63] fix avatarHash crash --- interface/src/avatar/AvatarManager.cpp | 8 +++++--- libraries/avatars/src/AvatarHashMap.cpp | 1 + libraries/avatars/src/AvatarHashMap.h | 4 +--- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index 2e9c9fdecd..4d133706e6 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -468,13 +468,14 @@ void AvatarManager::updateAvatarRenderStatus(bool shouldRenderAvatars) { _shouldRender = shouldRenderAvatars; const render::ScenePointer& scene = qApp->getMain3DScene(); render::Transaction transaction; + auto avatarHashCopy = getHashCopy(); if (_shouldRender) { - for (auto avatarData : _avatarHash) { + for (auto avatarData : avatarHashCopy) { auto avatar = std::static_pointer_cast(avatarData); avatar->addToScene(avatar, scene, transaction); } } else { - for (auto avatarData : _avatarHash) { + for (auto avatarData : avatarHashCopy) { auto avatar = std::static_pointer_cast(avatarData); avatar->removeFromScene(avatar, scene, transaction); } @@ -514,7 +515,8 @@ RayToAvatarIntersectionResult AvatarManager::findRayIntersectionVector(const Pic glm::vec3 normDirection = glm::normalize(ray.direction); - for (auto avatarData : _avatarHash) { + auto avatarHashCopy = getHashCopy(); + for (auto avatarData : avatarHashCopy) { auto avatar = std::static_pointer_cast(avatarData); if ((avatarsToInclude.size() > 0 && !avatarsToInclude.contains(avatar->getID())) || (avatarsToDiscard.size() > 0 && avatarsToDiscard.contains(avatar->getID()))) { diff --git a/libraries/avatars/src/AvatarHashMap.cpp b/libraries/avatars/src/AvatarHashMap.cpp index 829c98a418..974ae92432 100644 --- a/libraries/avatars/src/AvatarHashMap.cpp +++ b/libraries/avatars/src/AvatarHashMap.cpp @@ -82,6 +82,7 @@ AvatarSharedPointer AvatarHashMap::addAvatar(const QUuid& sessionUUID, const QWe avatar->setSessionUUID(sessionUUID); avatar->setOwningAvatarMixer(mixerWeakPointer); + // addAvatar is only called from newOrExistingAvatar, which already locks _hashLock _avatarHash.insert(sessionUUID, avatar); emit avatarAddedEvent(sessionUUID); diff --git a/libraries/avatars/src/AvatarHashMap.h b/libraries/avatars/src/AvatarHashMap.h index 6747025de0..ef6f7845eb 100644 --- a/libraries/avatars/src/AvatarHashMap.h +++ b/libraries/avatars/src/AvatarHashMap.h @@ -46,7 +46,7 @@ class AvatarHashMap : public QObject, public Dependency { public: AvatarHash getHashCopy() { QReadLocker lock(&_hashLock); return _avatarHash; } const AvatarHash getHashCopy() const { QReadLocker lock(&_hashLock); return _avatarHash; } - int size() { return _avatarHash.size(); } + int size() { QReadLocker lock(&_hashLock); return _avatarHash.size(); } // Currently, your own avatar will be included as the null avatar id. @@ -152,8 +152,6 @@ protected: virtual void handleRemovedAvatar(const AvatarSharedPointer& removedAvatar, KillAvatarReason removalReason = KillAvatarReason::NoReason); AvatarHash _avatarHash; - // "Case-based safety": Most access to the _avatarHash is on the same thread. Write access is protected by a write-lock. - // If you read from a different thread, you must read-lock the _hashLock. (Scripted write access is not supported). mutable QReadWriteLock _hashLock; private: From 9f0864944cf48443dadde129cb7edd0c13c42579 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Fri, 8 Jun 2018 11:37:02 -0700 Subject: [PATCH 48/63] Fix Friending in Connections tab - READ FIXME --- interface/resources/qml/hifi/Pal.qml | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/interface/resources/qml/hifi/Pal.qml b/interface/resources/qml/hifi/Pal.qml index bac7533c9f..e457831a88 100644 --- a/interface/resources/qml/hifi/Pal.qml +++ b/interface/resources/qml/hifi/Pal.qml @@ -38,7 +38,6 @@ Rectangle { property var myData: ({profileUrl: "", displayName: "", userName: "", audioLevel: 0.0, avgAudioLevel: 0.0, admin: true, placeName: "", connection: "", isPresent: true}); // valid dummy until set property var ignored: ({}); // Keep a local list of ignored avatars & their data. Necessary because HashMap is slow to respond after ignoring. property var nearbyUserModelData: []; // This simple list is essentially a mirror of the nearbyUserModel listModel without all the extra complexities. - property var connectionsUserModelData: []; // This simple list is essentially a mirror of the connectionsUserModel listModel without all the extra complexities. property bool iAmAdmin: false; property var activeTab: "nearbyTab"; property bool currentlyEditingDisplayName: false @@ -873,12 +872,17 @@ Rectangle { checked: model && (model.connection === "friend"); boxSize: 24; onClicked: { - var newValue = model.connection !== "friend"; - connectionsUserModel.setProperty(model.userIndex, styleData.role, (newValue ? "friend" : "connection")); - connectionsUserModelData[model.userIndex][styleData.role] = newValue; // Defensive programming - pal.sendToScript({method: newValue ? 'addFriend' : 'removeFriend', params: model.userName}); + // HRS FIXME: NOTE FROM ZACH: With the line below uncommented, clicking any "FRIEND" checkbox + // in the table will result in the 0th table index FRIEND checkbox to become CHECKED. + // This is because there IS NO "model.userIndex" defined per entry in the connectionsUserModel. + // You could do one of two things here: + // 1. Programatically add a "userIndex" to each entry in the model as you fill it in (then this would work) + // 2. Not care about the model being accurate until its next refresh (at which point the "connection" + // property value will be correct, since the server will give the model the correct value) + //connectionsUserModel.setProperty(model.userIndex, styleData.role, (checked ? "friend" : "connection")); + pal.sendToScript({method: checked ? 'addFriend' : 'removeFriend', params: model.userName}); - UserActivityLogger["palAction"](newValue ? styleData.role : "un-" + styleData.role, model.sessionId); + UserActivityLogger["palAction"](checked ? styleData.role : "un-" + styleData.role, model.sessionId); } } } From 3ade31bdf603600ea567c5e973959a3c3f501927 Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Fri, 8 Jun 2018 12:57:40 -0700 Subject: [PATCH 49/63] always show edition --- interface/resources/qml/hifi/Pal.qml | 8 -------- .../resources/qml/hifi/commerce/purchases/Purchases.qml | 9 +-------- 2 files changed, 1 insertion(+), 16 deletions(-) diff --git a/interface/resources/qml/hifi/Pal.qml b/interface/resources/qml/hifi/Pal.qml index e457831a88..f5c47e5042 100644 --- a/interface/resources/qml/hifi/Pal.qml +++ b/interface/resources/qml/hifi/Pal.qml @@ -872,14 +872,6 @@ Rectangle { checked: model && (model.connection === "friend"); boxSize: 24; onClicked: { - // HRS FIXME: NOTE FROM ZACH: With the line below uncommented, clicking any "FRIEND" checkbox - // in the table will result in the 0th table index FRIEND checkbox to become CHECKED. - // This is because there IS NO "model.userIndex" defined per entry in the connectionsUserModel. - // You could do one of two things here: - // 1. Programatically add a "userIndex" to each entry in the model as you fill it in (then this would work) - // 2. Not care about the model being accurate until its next refresh (at which point the "connection" - // property value will be correct, since the server will give the model the correct value) - //connectionsUserModel.setProperty(model.userIndex, styleData.role, (checked ? "friend" : "connection")); pal.sendToScript({method: checked ? 'addFriend' : 'removeFriend', params: model.userName}); UserActivityLogger["palAction"](checked ? styleData.role : "un-" + styleData.role, model.sessionId); diff --git a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml index 41ad978e33..886dbbf5d8 100644 --- a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml +++ b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml @@ -39,12 +39,10 @@ Rectangle { property string installedApps; property bool keyboardRaised: false; property int numUpdatesAvailable: 0; - property var itemCountDictionary: ({}); // Style color: hifi.colors.white; function getPurchases() { // FIXME: use the new purchasesModel.getFirstPage root.activeView = "purchasesMain"; - root.itemCountDictionary = {}; root.installedApps = Commerce.getInstalledApps(); purchasesModel.getFirstPage(); Commerce.getAvailableUpdates(); @@ -574,13 +572,8 @@ Rectangle { item.cardBackVisible = false; item.isInstalled = root.installedApps.indexOf(item.id) > -1; item.wornEntityID = ''; - item.displayedItemCount = itemCountDictionary[item.id] = (itemCountDictionary[item.id] || 0) + 1; - // HRS FIXME updateable }); sendToScript({ method: 'purchases_updateWearables' }); - for (var i = 0; i < purchasesModel.count; i++) { // Update all the previous counts with possibly new values. - purchasesModel.setProperty(i, "displayedItemCount", itemCountDictionary[purchasesModel.get(i).id]) - } return data.assets; @@ -633,7 +626,7 @@ Rectangle { itemEdition: model.edition_number; numberSold: model.number_sold; limitedRun: model.limited_run; - displayedItemCount: model.displayedItemCount || 0; + displayedItemCount: 999// For now (and maybe longer), we're going to display all the edition numbers. cardBackVisible: model.cardBackVisible || false; isInstalled: model.isInstalled || false; wornEntityID: model.wornEntityID; From 812c28ef484c1453832389ab131bed004044d10b Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Fri, 8 Jun 2018 14:15:42 -0700 Subject: [PATCH 50/63] Fix MS13761: Change the move conditions under which overlays hide --- interface/src/ui/OverlayConductor.cpp | 53 +++++++-------------------- interface/src/ui/OverlayConductor.h | 11 ++---- 2 files changed, 17 insertions(+), 47 deletions(-) diff --git a/interface/src/ui/OverlayConductor.cpp b/interface/src/ui/OverlayConductor.cpp index e7e3c91d13..924a5d6179 100644 --- a/interface/src/ui/OverlayConductor.cpp +++ b/interface/src/ui/OverlayConductor.cpp @@ -18,7 +18,6 @@ #include "InterfaceLogging.h" OverlayConductor::OverlayConductor() { - } OverlayConductor::~OverlayConductor() { @@ -33,8 +32,8 @@ bool OverlayConductor::headOutsideOverlay() const { glm::vec3 uiPos = uiTransform.getTranslation(); glm::vec3 uiForward = uiTransform.getRotation() * glm::vec3(0.0f, 0.0f, -1.0f); - const float MAX_COMPOSITOR_DISTANCE = 0.99f; // If you're 1m from center of ui sphere, you're at the surface. - const float MAX_COMPOSITOR_ANGLE = 180.0f; // rotation check is effectively disabled + const float MAX_COMPOSITOR_DISTANCE = 0.99f; // If you're 1m from center of ui sphere, you're at the surface. + const float MAX_COMPOSITOR_ANGLE = 180.0f; // rotation check is effectively disabled if (glm::distance(uiPos, hmdPos) > MAX_COMPOSITOR_DISTANCE || glm::dot(uiForward, hmdForward) < cosf(glm::radians(MAX_COMPOSITOR_ANGLE))) { return true; @@ -43,10 +42,9 @@ bool OverlayConductor::headOutsideOverlay() const { } bool OverlayConductor::updateAvatarIsAtRest() { - auto myAvatar = DependencyManager::get()->getMyAvatar(); - const quint64 REST_ENABLE_TIME_USECS = 1000 * 1000; // 1 s + const quint64 REST_ENABLE_TIME_USECS = 1000 * 1000; // 1 s const quint64 REST_DISABLE_TIME_USECS = 200 * 1000; // 200 ms const float AT_REST_THRESHOLD = 0.01f; @@ -69,31 +67,6 @@ bool OverlayConductor::updateAvatarIsAtRest() { return _currentAtRest; } -bool OverlayConductor::updateAvatarHasDriveInput() { - auto myAvatar = DependencyManager::get()->getMyAvatar(); - - const quint64 DRIVE_ENABLE_TIME_USECS = 200 * 1000; // 200 ms - const quint64 DRIVE_DISABLE_TIME_USECS = 1000 * 1000; // 1 s - - bool desiredDriving = myAvatar->hasDriveInput(); - if (desiredDriving != _desiredDriving) { - // start timer - _desiredDrivingTimer = usecTimestampNow() + (desiredDriving ? DRIVE_ENABLE_TIME_USECS : DRIVE_DISABLE_TIME_USECS); - } - - _desiredDriving = desiredDriving; - - if (_desiredDrivingTimer != 0 && usecTimestampNow() > _desiredDrivingTimer) { - // timer expired - // change state! - _currentDriving = _desiredDriving; - // disable timer - _desiredDrivingTimer = 0; - } - - return _currentDriving; -} - void OverlayConductor::centerUI() { // place the overlay at the current hmd position in sensor space auto camMat = cancelOutRollAndPitch(qApp->getHMDSensorPose()); @@ -115,20 +88,21 @@ void OverlayConductor::update(float dt) { _hmdMode = false; } - bool prevDriving = _currentDriving; - bool isDriving = updateAvatarHasDriveInput(); - bool drivingChanged = prevDriving != isDriving; bool isAtRest = updateAvatarIsAtRest(); + bool prevMoving = _currentMoving; + bool isMoving = !isAtRest; + bool movingChanged = prevMoving != isMoving; + bool shouldRecenter = false; - if (_flags & SuppressedByDrive) { - if (!isDriving) { - _flags &= ~SuppressedByDrive; - shouldRecenter = true; + if (_flags & SuppressedByMove) { + if (!isMoving) { + _flags &= ~SuppressedByMove; + shouldRecenter = true; } } else { - if (myAvatar->getClearOverlayWhenMoving() && drivingChanged && isDriving) { - _flags |= SuppressedByDrive; + if (myAvatar->getClearOverlayWhenMoving() && movingChanged && isMoving) { + _flags |= SuppressedByMove; } } @@ -143,7 +117,6 @@ void OverlayConductor::update(float dt) { } } - bool targetVisible = Menu::getInstance()->isOptionChecked(MenuOption::Overlays) && (0 == (_flags & SuppressMask)); if (targetVisible != currentVisible) { offscreenUi->setPinned(!targetVisible); diff --git a/interface/src/ui/OverlayConductor.h b/interface/src/ui/OverlayConductor.h index cdd596a7bc..509292b775 100644 --- a/interface/src/ui/OverlayConductor.h +++ b/interface/src/ui/OverlayConductor.h @@ -27,18 +27,15 @@ private: bool updateAvatarIsAtRest(); enum SupressionFlags { - SuppressedByDrive = 0x01, + SuppressedByMove = 0x01, SuppressedByHead = 0x02, SuppressMask = 0x03, }; - uint8_t _flags { SuppressedByDrive }; + uint8_t _flags { SuppressedByMove }; bool _hmdMode { false }; - - // used by updateAvatarHasDriveInput - uint64_t _desiredDrivingTimer { 0 }; - bool _desiredDriving { false }; - bool _currentDriving { false }; + + bool _currentMoving { false }; // used by updateAvatarIsAtRest uint64_t _desiredAtRestTimer { 0 }; From 36a0adf553190e560e5235429f2bb48196f02e8e Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Fri, 8 Jun 2018 14:24:24 -0700 Subject: [PATCH 51/63] strip snapshot stacking code - we'll do it on server or not at all --- interface/resources/qml/hifi/Feed.qml | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/interface/resources/qml/hifi/Feed.qml b/interface/resources/qml/hifi/Feed.qml index cd50eb14d8..0b3dea9e1f 100644 --- a/interface/resources/qml/hifi/Feed.qml +++ b/interface/resources/qml/hifi/Feed.qml @@ -41,7 +41,6 @@ Column { // FIXME onFilterChanged: filterChoicesByText(); property var goFunction: null; property var http: null; - property var itemCountDictionary: ({}); HifiConstants { id: hifi } Component.onCompleted: suggestions.getFirstPage(); @@ -56,16 +55,8 @@ Column { ]; endpoint: '/api/v1/user_stories?' + options.join('&'); itemsPerPage: 3; - getFirstPage: function (delayRefresh) { - root.itemCountDictionary = {}; - suggestions.getFirstPageInternal(delayRefresh); - }; processPage: function (data) { - var adding = data.user_stories.map(makeModelData); - for (var i = 0; i < suggestions.count; i++) { // Update all the previous counts with possibly new values. - suggestions.setProperty(i, "drillDownToPlace", itemCountDictionary[suggestions.get(i).place_name] > 0); - } - return adding; + return data.user_stories.map(makeModelData); }; listModelName: actions; listView: scroll; @@ -87,7 +78,6 @@ Column { tags = data.tags || [data.action, data.username], description = data.description || "", thumbnail_url = data.thumbnail_url || ""; - itemCountDictionary[name] = (itemCountDictionary[name] || 0) + 1; return { place_name: name, username: data.username || "", From d9daa3495a1b526164dd620e54a69bcb689099f1 Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Fri, 8 Jun 2018 15:36:10 -0700 Subject: [PATCH 52/63] remove comments / dead code --- interface/resources/qml/hifi/Feed.qml | 129 +----------------- interface/resources/qml/hifi/Pal.qml | 1 - .../commerce/common/sendAsset/SendAsset.qml | 3 - .../qml/hifi/commerce/purchases/Purchases.qml | 39 +----- .../qml/hifi/commerce/wallet/Wallet.qml | 2 - .../qml/hifi/models/PSFListModel.qml | 7 +- 6 files changed, 5 insertions(+), 176 deletions(-) diff --git a/interface/resources/qml/hifi/Feed.qml b/interface/resources/qml/hifi/Feed.qml index 0b3dea9e1f..0b33bac657 100644 --- a/interface/resources/qml/hifi/Feed.qml +++ b/interface/resources/qml/hifi/Feed.qml @@ -38,7 +38,6 @@ Column { // sendToScript doesn't get wired until after everything gets created. So we have to queue fillDestinations on nextTick. property string labelText: actions; property string filter: ''; - // FIXME onFilterChanged: filterChoicesByText(); property var goFunction: null; property var http: null; @@ -51,7 +50,7 @@ Column { 'include_actions=' + actions, 'restriction=' + (Account.isLoggedIn() ? 'open,hifi' : 'open'), 'require_online=true', - 'protocol=' + Window.protocolSignature() + 'protocol=' + encodeURIComponent(Window.protocolSignature()) ]; endpoint: '/api/v1/user_stories?' + options.join('&'); itemsPerPage: 3; @@ -60,12 +59,7 @@ Column { }; listModelName: actions; listView: scroll; - searchFilter: filter; // FIXME .toUpperCase().split(/\s+/).filter(identity).join(' '); - /* FIXME searchItemTest: function (text, item) { - return searchFilter.split().every(function (word) { - return item.searchText.indexOf(word) >= 0; - }); - };*/ //HRS FIXME remove when endpoint works. + searchFilter: filter; } function resolveUrl(url) { @@ -93,127 +87,8 @@ Column { description: description, online_users: data.details.connections || data.details.concurrency || 0, drillDownToPlace: false - - //searchText: [name].concat(tags, description || []).join(' ').toUpperCase() // FIXME remove }; } - function identity(x) { - return x; - } - /* FIXME - property var allStories: []; - property var placeMap: ({}); // Used for making stacks. - property int requestId: 0; - function handleError(url, error, data, cb) { // cb(error) and answer truthy if needed, else falsey - if (!error && (data.status === 'success')) { - return; - } - if (!error) { // Create a message from the data - error = data.status + ': ' + data.error; - } - if (typeof(error) === 'string') { // Make a proper Error object - error = new Error(error); - } - error.message += ' in ' + url; // Include the url. - cb(error); - return true; - } - function getUserStoryPage(pageNumber, cb, cb1) { // cb(error) after all pages of domain data have been added to model - // If supplied, cb1 will be run after the first page IFF it is not the last, for responsiveness. - var options = [ - 'now=' + new Date().toISOString(), - 'include_actions=' + actions, - 'restriction=' + (Account.isLoggedIn() ? 'open,hifi' : 'open'), - 'require_online=true', - 'protocol=' + protocol, - 'page=' + pageNumber - ]; - var url = metaverseBase + 'user_stories?' + options.join('&'); - var thisRequestId = ++requestId; - http.request(url, function (error, data) { - if (thisRequestId !== requestId) { - error = 'stale'; - } - if (handleError(url, error, data, cb)) { - return; // abandon stale requests - } - allStories = allStories.concat(data.user_stories.map(makeModelData)); - if ((data.current_page < data.total_pages) && (data.current_page <= 10)) { // just 10 pages = 100 stories for now - if ((pageNumber === 1) && cb1) { - cb1(); - } - return getUserStoryPage(pageNumber + 1, cb); - } - cb(); - }); - } - function fillDestinations() { // Public - console.debug('Feed::fillDestinations()'); - //suggestions.getFirstPage(); - } - function report(label, error) { - console.log(label, actions, error || 'ok', allStories.length, 'filtered to', suggestions.count); - } - var filter = makeFilteredStoryProcessor(), counter = 0; - allStories = []; - suggestions.clear(); - placeMap = {}; - getUserStoryPage(1, function (error) { - allStories.slice(counter).forEach(filter); - report('user stories update', error); - root.visible = !!suggestions.count; - }, function () { // If there's more than a page, put what we have in the model right away, keeping track of how many are processed. - allStories.forEach(function (story) { - counter++; - filter(story); - root.visible = !!suggestions.count; - }); - report('user stories'); - }); - } - function makeFilteredStoryProcessor() { // answer a function(storyData) that adds it to suggestions if it matches - var words = filter.toUpperCase().split(/\s+/).filter(identity); - function suggestable(story) { - // We could filter out places we don't want to suggest, such as those where (story.place_name === AddressManager.placename) or (story.username === Account.username). - return true; - } - function matches(story) { - if (!words.length) { - return suggestable(story); - } - return words.every(function (word) { - return story.searchText.indexOf(word) >= 0; - }); - } - function addToSuggestions(place) { - var collapse = ((actions === 'concurrency,snapshot') && (place.action !== 'concurrency')) || (place.action === 'announcement'); - if (collapse) { - var existing = placeMap[place.place_name]; - if (existing) { - existing.drillDownToPlace = true; - return; - } - } - suggestions.append(place); - if (collapse) { - placeMap[place.place_name] = suggestions.get(suggestions.count - 1); - } else if (place.action === 'concurrency') { - suggestions.get(suggestions.count - 1).drillDownToPlace = true; // Don't change raw place object (in allStories). - } - } - return function (story) { - if (matches(story)) { - addToSuggestions(story); - } - }; - } - function filterChoicesByText() { - suggestions.clear(); - placeMap = {}; - allStories.forEach(makeFilteredStoryProcessor()); - root.visible = !!suggestions.count; - } - */ RalewayBold { id: label; diff --git a/interface/resources/qml/hifi/Pal.qml b/interface/resources/qml/hifi/Pal.qml index f5c47e5042..0735c03a77 100644 --- a/interface/resources/qml/hifi/Pal.qml +++ b/interface/resources/qml/hifi/Pal.qml @@ -49,7 +49,6 @@ Rectangle { id: connectionsUserModel; http: http; endpoint: "/api/v1/users?filter=connections"; - //FIXME localSort: true; property var sortColumn: connectionsTable.getColumn(connectionsTable.sortIndicatorColumn); sortProperty: switch (sortColumn && sortColumn.role) { case 'placeName': diff --git a/interface/resources/qml/hifi/commerce/common/sendAsset/SendAsset.qml b/interface/resources/qml/hifi/commerce/common/sendAsset/SendAsset.qml index b3fd661088..26c0a0aefa 100644 --- a/interface/resources/qml/hifi/commerce/common/sendAsset/SendAsset.qml +++ b/interface/resources/qml/hifi/commerce/common/sendAsset/SendAsset.qml @@ -404,9 +404,6 @@ Item { return data.users; }; searchFilter: filterBar.text; - /* FIXME searchItemTest: function (text, item) { - return item.username.toLowerCase().indexOf(text.toLowerCase()) !== -1; - };*/ //HRS FIXME remove when endpoint works. } Rectangle { diff --git a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml index 886dbbf5d8..3b37edd51b 100644 --- a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml +++ b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml @@ -41,7 +41,7 @@ Rectangle { property int numUpdatesAvailable: 0; // Style color: hifi.colors.white; - function getPurchases() { // FIXME: use the new purchasesModel.getFirstPage + function getPurchases() { root.activeView = "purchasesMain"; root.installedApps = Commerce.getInstalledApps(); purchasesModel.getFirstPage(); @@ -576,32 +576,6 @@ Rectangle { sendToScript({ method: 'purchases_updateWearables' }); return data.assets; - - /* - var processedInventory = processInventoryResult(data.assets); - - if (purchasesModel.processResult(result.status, processedInventory)) { - var currentId; - for (var i = 0; i < purchasesModel.count; i++) { - currentId = purchasesModel.get(i).id; - purchasesModel.setProperty(i, 'cardBackVisible', false); - purchasesModel.setProperty(i, 'isInstalled', ((root.installedApps).indexOf(currentId) > -1)); - purchasesModel.setProperty(i, 'wornEntityID', ''); - } - - // Client-side filter of "Updatable" items - // FIXME - this MUST be serverside (what if we don't have the - // page containing an updatable item on the client?) - if (filterBar.primaryFilter_displayName === "Updatable") { - for (var i = 0; i < purchasesModel.count; i++) { - if (purchasesModel.get(i).upgrade_url === "") { - purchasesModel.remove(i); - i--; - } - } - } - } - */ } } @@ -979,17 +953,6 @@ Rectangle { // // FUNCTION DEFINITIONS START // - /* fixme remove - function processInventoryResult(inventory) { // HRS FIXME remove - for (var i = 0; i < inventory.length; i++) { - if (inventory[i].status.length > 1) { - console.log("WARNING: Inventory result index " + i + " has a status of length >1!") - } - inventory[i].status = inventory[i].status[0]; - inventory[i].categories = inventory[i].categories.join(';'); - } - return inventory; - } */ function updateCurrentlyWornWearables(wearables) { for (var i = 0; i < purchasesModel.count; i++) { diff --git a/interface/resources/qml/hifi/commerce/wallet/Wallet.qml b/interface/resources/qml/hifi/commerce/wallet/Wallet.qml index b75141f8dd..819b71a4d0 100644 --- a/interface/resources/qml/hifi/commerce/wallet/Wallet.qml +++ b/interface/resources/qml/hifi/commerce/wallet/Wallet.qml @@ -769,12 +769,10 @@ Rectangle { // NOP break; case 'updateConnections': - console.log('Wallet.qml updateConnections');// HRS FIXME sendMoney.updateConnections(message.connections); break; case 'selectRecipient': case 'updateSelectedRecipientUsername': - console.log('Wallet.qml updateSelectedRecipientUsername'); // HRS FIXME sendMoney.fromScript(message); break; case 'http.response': diff --git a/interface/resources/qml/hifi/models/PSFListModel.qml b/interface/resources/qml/hifi/models/PSFListModel.qml index d17689ab98..28ac89c4f7 100644 --- a/interface/resources/qml/hifi/models/PSFListModel.qml +++ b/interface/resources/qml/hifi/models/PSFListModel.qml @@ -63,7 +63,7 @@ Item { property bool delayedClear: false; function resetModel() { if (!delayedClear) { finalModel.clear(); } - currentPageToRetrieve = 1; console.log('fixme resetModel set currentPageToRetrieve to 1', listModelName); + currentPageToRetrieve = 1; retrievedAtLeastOnePage = false; copyOfItems = []; } @@ -144,7 +144,6 @@ Item { // If it is a path starting with slash, add the metaverseServer domain. var url = /^\//.test(endpoint) ? (Account.metaverseServerURL + endpoint) : endpoint; var parameters = [ - // FIXME: handle sort, tag parameters 'per_page=' + itemsPerPage, 'page=' + currentPageToRetrieve ]; @@ -162,8 +161,7 @@ Item { // Start the show by retrieving data according to `getPage()`. // It can be custom-defined by this item's Parent. - property var getFirstPage: function (delayClear) { getFirstPageInternal(delayClear); } - function getFirstPageInternal(delayClear) { + property var getFirstPage: function (delayClear) { delayedClear = !!delayClear; resetModel(); requestPending = true; @@ -180,7 +178,6 @@ Item { // onAtYEndChanged: if (theList.atYEnd && !theList.atYBeginning) { thisPSFListModelId.getNextPage(); } // ...} property var getNextPage: function () { - console.log('fixme getNextPage', listModelName, requestPending, currentPageToRetrieve); if (requestPending || currentPageToRetrieve < 0) { return; } From f0d486a6a5ddd0676e58d797551abc02944e3b45 Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Fri, 8 Jun 2018 15:48:39 -0700 Subject: [PATCH 53/63] remove the unused client-side stuff from model --- .../qml/hifi/models/PSFListModel.qml | 67 ++----------------- 1 file changed, 5 insertions(+), 62 deletions(-) diff --git a/interface/resources/qml/hifi/models/PSFListModel.qml b/interface/resources/qml/hifi/models/PSFListModel.qml index 28ac89c4f7..68acb09b28 100644 --- a/interface/resources/qml/hifi/models/PSFListModel.qml +++ b/interface/resources/qml/hifi/models/PSFListModel.qml @@ -38,23 +38,11 @@ Item { onSortKeyChanged: if (initialized) { getFirstPage('delayClear'); } onSearchFilterChanged: { if (!initialized) { return; } - if (searchItemTest) { - var filteredCopy = applySearchItemTest(copyOfItems); - finalModel.clear(); - finalModel.append(filteredCopy); - debugView('after searchFilterChanged'); - } else { // TODO: fancy timer against fast typing. - getFirstPage('delayClear'); - } + getFirstPage('delayClear'); } onTagsFilterChanged: if (initialized) { getFirstPage('delayClear'); } property int itemsPerPage: 100; - // If the endpoint doesn't do search, tags, sort, these functions can be supplied to do it here. - property var searchItemTest: null; - property bool localSort: false; - property var copyOfItems: []; - // State. property int currentPageToRetrieve: 0; // 0 = before first page. -1 = we have them all. Otherwise 1-based page number. property bool retrievedAtLeastOnePage: false; @@ -65,7 +53,6 @@ Item { if (!delayedClear) { finalModel.clear(); } currentPageToRetrieve = 1; retrievedAtLeastOnePage = false; - copyOfItems = []; } // Processing one page. @@ -98,20 +85,7 @@ Item { if (response.total_pages && (response.total_pages === currentPageToRetrieve)) { currentPageToRetrieve = -1; } - if (searchItemTest) { - if (searchFilter) { - processed = applySearchItemTest(processed); - } - copyOfItems = copyOfItems.concat(processed); - } - if (localSort) { - copyOfItems = copyOfItems.concat(processed); - if (sortProperty) { - sortCopy(sortProperty, sortAscending); - processed = copyOfItems; - delayedClear = true; // see next conditional - } - } + if (delayedClear) { finalModel.clear(); delayedClear = false; @@ -119,15 +93,6 @@ Item { finalModel.append(processed); // FIXME keep index steady, and apply any post sort retrievedAtLeastOnePage = true; debugView('after handlePage'); - if (searchItemTest && searchFilter && listView && listView.atYEnd && (currentPageToRetrieve >= 0)) { - getNextPage(); // too fancy?? - } - if (listView) { console.debug('handlePage completed', listModelName, 'model:', model.count, 'view:', listView.count); } - } - function applySearchItemTest(items) { - return items.filter(function (item) { - return searchItemTest(searchFilter, item); - }); } function debugView(label) { if (!listView) { return; } @@ -147,12 +112,13 @@ Item { 'per_page=' + itemsPerPage, 'page=' + currentPageToRetrieve ]; - if (!searchItemTest && searchFilter) { + if (searchFilter) { parameters.splice(parameters.length, 0, 'search=' + searchFilter); } - if (!localSort && sortKey) { + if (sortKey) { parameters.splice(parameters.length, 0, 'sort=' + sortKey); } + var parametersSeparator = /\?/.test(url) ? '&' : '?'; url = url + parametersSeparator + parameters.join('&'); console.debug('getPage', listModelName, currentPageToRetrieve); @@ -202,27 +168,4 @@ Item { ListModel { id: finalModel; } - - function sortCopy(sortProperty, isAscending) { - console.debug('client sort', listModelName, sortProperty, isAscending, copyOfItems.length, 'items'); - var before = isAscending ? -1 : 1; - var after = -1 * before; - - copyOfItems.sort(function (a, b) { - var aValue = a[sortProperty].toString().toLowerCase(), - bValue = b[sortProperty].toString().toLowerCase(); - if (!aValue && !bValue) { - return 0; - } else if (!aValue) { - return after; - } else if (!bValue) { - return before; - } - switch (true) { - case (aValue < bValue): return before; - case (aValue > bValue): return after; - default: return 0; - } - }); - } } \ No newline at end of file From dc46c2077d2e8e0abdcd2a3a4e956fd0cd5b58df Mon Sep 17 00:00:00 2001 From: David Rowe Date: Sat, 9 Jun 2018 11:01:18 +1200 Subject: [PATCH 54/63] Fix function name --- .../controllerModules/farActionGrabEntity.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/scripts/system/controllers/controllerModules/farActionGrabEntity.js b/scripts/system/controllers/controllerModules/farActionGrabEntity.js index 66cd197abd..d79ffeb5ac 100644 --- a/scripts/system/controllers/controllerModules/farActionGrabEntity.js +++ b/scripts/system/controllers/controllerModules/farActionGrabEntity.js @@ -282,7 +282,7 @@ Script.include("/~/system/libraries/Xform.js"); this.previousRoomControllerPosition = roomControllerPosition; }; - this.endNearGrabAction = function () { + this.endFarGrabAction = function () { ensureDynamic(this.grabbedThingID); this.distanceHolding = false; this.distanceRotating = false; @@ -402,7 +402,7 @@ Script.include("/~/system/libraries/Xform.js"); this.run = function (controllerData) { if (controllerData.triggerValues[this.hand] < TRIGGER_OFF_VALUE || this.notPointingAtEntity(controllerData) || this.targetIsNull()) { - this.endNearGrabAction(); + this.endFarGrabAction(); Selection.removeFromSelectedItemsList(DISPATCHER_HOVERING_LIST, "entity", this.highlightedEntity); this.highlightedEntity = null; @@ -434,7 +434,7 @@ Script.include("/~/system/libraries/Xform.js"); // stop the far-grab so the near-grab or equip can take over. for (var k = 0; k < nearGrabReadiness.length; k++) { if (nearGrabReadiness[k].active && nearGrabReadiness[k].targets[0] === this.grabbedThingID) { - this.endNearGrabAction(); + this.endFarGrabAction(); return makeRunningValues(false, [], []); } } @@ -445,7 +445,7 @@ Script.include("/~/system/libraries/Xform.js"); // where it could near-grab something, stop searching. for (var j = 0; j < nearGrabReadiness.length; j++) { if (nearGrabReadiness[j].active) { - this.endNearGrabAction(); + this.endFarGrabAction(); return makeRunningValues(false, [], []); } } @@ -577,7 +577,7 @@ Script.include("/~/system/libraries/Xform.js"); var disableModule = getEnabledModuleByName(moduleName); if (disableModule) { if (disableModule.disableModules) { - this.endNearGrabAction(); + this.endFarGrabAction(); Selection.removeFromSelectedItemsList(DISPATCHER_HOVERING_LIST, "entity", this.highlightedEntity); this.highlightedEntity = null; From 54b96cd6029544e4201647f49a420602be6c416b Mon Sep 17 00:00:00 2001 From: David Rowe Date: Sat, 9 Jun 2018 11:32:10 +1200 Subject: [PATCH 55/63] Fix incomplete transition from far-grabbing entity to grabbing tablet --- .../controllers/controllerModules/farActionGrabEntity.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/scripts/system/controllers/controllerModules/farActionGrabEntity.js b/scripts/system/controllers/controllerModules/farActionGrabEntity.js index d79ffeb5ac..e4563fda14 100644 --- a/scripts/system/controllers/controllerModules/farActionGrabEntity.js +++ b/scripts/system/controllers/controllerModules/farActionGrabEntity.js @@ -430,10 +430,11 @@ Script.include("/~/system/libraries/Xform.js"); } if (this.actionID) { - // if we are doing a distance grab and the object gets close enough to the controller, + // if we are doing a distance grab and the object or tablet gets close enough to the controller, // stop the far-grab so the near-grab or equip can take over. for (var k = 0; k < nearGrabReadiness.length; k++) { - if (nearGrabReadiness[k].active && nearGrabReadiness[k].targets[0] === this.grabbedThingID) { + if (nearGrabReadiness[k].active && (nearGrabReadiness[k].targets[0] === this.grabbedThingID + || HMD.tabletID && nearGrabReadiness[k].targets[0] === HMD.tabletID)) { this.endFarGrabAction(); return makeRunningValues(false, [], []); } From d68415c47cc3b2a86fb69dee1392dfe9a89b1647 Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Fri, 8 Jun 2018 16:47:06 -0700 Subject: [PATCH 56/63] code feedback --- interface/resources/qml/hifi/Feed.qml | 21 +------------------ interface/resources/qml/hifi/Pal.qml | 7 +++++-- .../commerce/common/sendAsset/SendAsset.qml | 2 +- .../qml/hifi/commerce/purchases/Purchases.qml | 4 ++-- .../qml/hifi/commerce/wallet/Wallet.qml | 3 ++- .../qml/hifi/commerce/wallet/WalletHome.qml | 3 ++- .../qml/hifi/models/PSFListModel.qml | 2 +- 7 files changed, 14 insertions(+), 28 deletions(-) diff --git a/interface/resources/qml/hifi/Feed.qml b/interface/resources/qml/hifi/Feed.qml index 0b33bac657..d7d469364c 100644 --- a/interface/resources/qml/hifi/Feed.qml +++ b/interface/resources/qml/hifi/Feed.qml @@ -16,7 +16,7 @@ import QtQuick 2.5 import QtGraphicalEffects 1.0 import "toolbars" import "../styles-uit" -import "models" as HifiModels +import "qrc:////qml//hifi//models" as HifiModels // Absolute path so the same code works everywhere. Column { id: root; @@ -136,23 +136,4 @@ Column { unhoverThunk: function () { hovered = false } } } - /* WTF is this? - NumberAnimation { - id: anim; - target: scroll; - property: "contentX"; - duration: 250; - } - function scrollToIndex(index) { - anim.running = false; - var pos = scroll.contentX; - var destPos; - scroll.positionViewAtIndex(index, ListView.Contain); - destPos = scroll.contentX; - anim.from = pos; - anim.to = destPos; - scroll.currentIndex = index; - anim.running = true; - } - */ } diff --git a/interface/resources/qml/hifi/Pal.qml b/interface/resources/qml/hifi/Pal.qml index 0735c03a77..d9625a68c2 100644 --- a/interface/resources/qml/hifi/Pal.qml +++ b/interface/resources/qml/hifi/Pal.qml @@ -18,7 +18,7 @@ import Qt.labs.settings 1.0 import "../styles-uit" import "../controls-uit" as HifiControlsUit import "../controls" as HifiControls -import "models" as HifiModels +import "qrc:////qml//hifi//models" as HifiModels // Absolute path so the same code works everywhere. // references HMD, Users, UserActivityLogger from root context @@ -1238,7 +1238,10 @@ Rectangle { reloadNearby.color = 2; } break; - case 'inspectionCertificate_resetCert': // HRS FIXME what's this about? + case 'inspectionCertificate_resetCert': + // marketplaces.js sends out a signal to QML with that method when the tablet screen changes and it's not changed to a commerce-related screen. + // We want it to only be handled by the InspectionCertificate.qml, but there's not an easy way of doing that. + // As a part of a "cleanup inspectionCertificate_resetCert" ticket, we'll have to figure out less logspammy way of doing what has to be done. break; case 'http.response': http.handleHttpResponse(message); diff --git a/interface/resources/qml/hifi/commerce/common/sendAsset/SendAsset.qml b/interface/resources/qml/hifi/commerce/common/sendAsset/SendAsset.qml index 26c0a0aefa..1353ceab72 100644 --- a/interface/resources/qml/hifi/commerce/common/sendAsset/SendAsset.qml +++ b/interface/resources/qml/hifi/commerce/common/sendAsset/SendAsset.qml @@ -19,7 +19,7 @@ import "../../../../styles-uit" import "../../../../controls-uit" as HifiControlsUit import "../../../../controls" as HifiControls import "../" as HifiCommerceCommon -import "../../../models" as HifiModels +import "qrc:////qml//hifi//models" as HifiModels // Absolute path so the same code works everywhere. Item { HifiConstants { id: hifi; } diff --git a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml index 3b37edd51b..ec2798680e 100644 --- a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml +++ b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml @@ -16,7 +16,7 @@ import QtQuick 2.5 import "../../../styles-uit" import "../../../controls-uit" as HifiControlsUit import "../../../controls" as HifiControls -import "../../models" as HifiModels +import "qrc:////qml//hifi//models" as HifiModels // Absolute path so the same code works everywhere. import "../wallet" as HifiWallet import "../common" as HifiCommerceCommon import "../inspectionCertificate" as HifiInspectionCertificate @@ -600,7 +600,7 @@ Rectangle { itemEdition: model.edition_number; numberSold: model.number_sold; limitedRun: model.limited_run; - displayedItemCount: 999// For now (and maybe longer), we're going to display all the edition numbers. + displayedItemCount: 999; // For now (and maybe longer), we're going to display all the edition numbers. cardBackVisible: model.cardBackVisible || false; isInstalled: model.isInstalled || false; wornEntityID: model.wornEntityID; diff --git a/interface/resources/qml/hifi/commerce/wallet/Wallet.qml b/interface/resources/qml/hifi/commerce/wallet/Wallet.qml index 819b71a4d0..603d7fb676 100644 --- a/interface/resources/qml/hifi/commerce/wallet/Wallet.qml +++ b/interface/resources/qml/hifi/commerce/wallet/Wallet.qml @@ -779,7 +779,8 @@ Rectangle { http.handleHttpResponse(message); break; case 'palIsStale': - case 'avatarDisconnected': // HRS FIXME. What are these about? + case 'avatarDisconnected': + // Because we don't have "channels" for sending messages to a specific QML object, the messages are broadcast to all QML Items. If an Item of yours happens to be visible when some script sends a message with a method you don't expect, you'll get "Unrecognized message..." logs. break; default: console.log('Unrecognized message from wallet.js:', JSON.stringify(message)); diff --git a/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml b/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml index 9076f10ebc..ed032dd055 100644 --- a/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml +++ b/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml @@ -18,7 +18,7 @@ import QtQuick.Controls 2.2 import "../../../styles-uit" import "../../../controls-uit" as HifiControlsUit import "../../../controls" as HifiControls -import "../../models" as HifiModels +import "qrc:////qml//hifi//models" as HifiModels // Absolute path so the same code works everywhere. Item { HifiConstants { id: hifi; } @@ -229,6 +229,7 @@ Item { } // Either add to pending, or to result. + // Note that you only see a page of pending stuff until you scroll... data.history.forEach(function (item) { if (item.status === 'pending') { pending.count++; diff --git a/interface/resources/qml/hifi/models/PSFListModel.qml b/interface/resources/qml/hifi/models/PSFListModel.qml index 68acb09b28..95e9e1b79e 100644 --- a/interface/resources/qml/hifi/models/PSFListModel.qml +++ b/interface/resources/qml/hifi/models/PSFListModel.qml @@ -55,7 +55,7 @@ Item { retrievedAtLeastOnePage = false; } - // Processing one page. + // Page processing. // Override to return one property of data, and/or to transform the elements. Must return an array of model elements. property var processPage: function (data) { return data; } From 421edbfa1e57140fba71b2409a746646e2beb2d0 Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Sat, 9 Jun 2018 16:03:45 -0700 Subject: [PATCH 57/63] PSFListModel isa ListModel instead of hasa --- interface/resources/qml/hifi/Feed.qml | 2 +- interface/resources/qml/hifi/Pal.qml | 2 +- .../commerce/common/sendAsset/SendAsset.qml | 2 +- .../qml/hifi/commerce/purchases/Purchases.qml | 2 +- .../qml/hifi/commerce/wallet/WalletHome.qml | 2 +- .../qml/hifi/models/PSFListModel.qml | 26 ++++--------------- 6 files changed, 10 insertions(+), 26 deletions(-) diff --git a/interface/resources/qml/hifi/Feed.qml b/interface/resources/qml/hifi/Feed.qml index d7d469364c..d9e93c2fa7 100644 --- a/interface/resources/qml/hifi/Feed.qml +++ b/interface/resources/qml/hifi/Feed.qml @@ -98,7 +98,7 @@ Column { } ListView { id: scroll; - model: suggestions.model; + model: suggestions; orientation: ListView.Horizontal; highlightFollowsCurrentItem: false highlightMoveDuration: -1; diff --git a/interface/resources/qml/hifi/Pal.qml b/interface/resources/qml/hifi/Pal.qml index d9625a68c2..8dcb76442b 100644 --- a/interface/resources/qml/hifi/Pal.qml +++ b/interface/resources/qml/hifi/Pal.qml @@ -785,7 +785,7 @@ Rectangle { resizable: false; } - model: connectionsUserModel.model; + model: connectionsUserModel; Connections { target: connectionsTable.flickableItem; onAtYEndChanged: { diff --git a/interface/resources/qml/hifi/commerce/common/sendAsset/SendAsset.qml b/interface/resources/qml/hifi/commerce/common/sendAsset/SendAsset.qml index 1353ceab72..3e4bae4780 100644 --- a/interface/resources/qml/hifi/commerce/common/sendAsset/SendAsset.qml +++ b/interface/resources/qml/hifi/commerce/common/sendAsset/SendAsset.qml @@ -519,7 +519,7 @@ Item { } visible: !connectionsLoading.visible; clip: true; - model: connectionsModel.model; + model: connectionsModel; onAtYEndChanged: if (connectionsList.atYEnd && !connectionsList.atYBeginning) { connectionsModel.getNextPage(); } snapMode: ListView.SnapToItem; // Anchors diff --git a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml index ec2798680e..c792b88c1e 100644 --- a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml +++ b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml @@ -583,7 +583,7 @@ Rectangle { id: purchasesContentsList; visible: purchasesModel.count !== 0; clip: true; - model: purchasesModel.model; + model: purchasesModel; snapMode: ListView.SnapToItem; // Anchors anchors.top: separator.bottom; diff --git a/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml b/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml index ed032dd055..4cf6db7889 100644 --- a/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml +++ b/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml @@ -323,7 +323,7 @@ Item { height: parent.height; visible: transactionHistoryModel.count !== 0; clip: true; - model: transactionHistoryModel.model; + 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); diff --git a/interface/resources/qml/hifi/models/PSFListModel.qml b/interface/resources/qml/hifi/models/PSFListModel.qml index 95e9e1b79e..8d1674e1de 100644 --- a/interface/resources/qml/hifi/models/PSFListModel.qml +++ b/interface/resources/qml/hifi/models/PSFListModel.qml @@ -17,7 +17,7 @@ import QtQuick 2.7 -Item { +ListModel { id: root; // Used when printing debug statements property string listModelName: endpoint; @@ -50,7 +50,7 @@ Item { // Not normally set directly, but rather by giving a truthy argument to getFirstPage(true); property bool delayedClear: false; function resetModel() { - if (!delayedClear) { finalModel.clear(); } + if (!delayedClear) { root.clear(); } currentPageToRetrieve = 1; retrievedAtLeastOnePage = false; } @@ -87,12 +87,12 @@ Item { } if (delayedClear) { - finalModel.clear(); + root.clear(); delayedClear = false; } - finalModel.append(processed); // FIXME keep index steady, and apply any post sort + root.append(processed); // FIXME keep index steady, and apply any post sort retrievedAtLeastOnePage = true; - debugView('after handlePage'); + console.debug(listModelName, 'after handlePage count', root.count); } function debugView(label) { if (!listView) { return; } @@ -152,20 +152,4 @@ Item { requestPending = true; getPage(); } - - // Redefining members and methods so that the parent of this Item - // can use PSFListModel as they would a regular ListModel - property alias model: finalModel; - property alias count: finalModel.count; - function clear() { finalModel.clear(); } - function get(index) { return finalModel.get(index); } - function remove(index) { return finalModel.remove(index); } - function setProperty(index, prop, value) { return finalModel.setProperty(index, prop, value); } - function move(from, to, n) { return finalModel.move(from, to, n); } - function insert(index, newElement) { finalModel.insert(index, newElement); } - function append(newElements) { finalModel.append(newElements); } - - ListModel { - id: finalModel; - } } \ No newline at end of file From 020d62230201acd2f44076344a434ca219f30a84 Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Sat, 9 Jun 2018 17:34:49 -0700 Subject: [PATCH 58/63] don't depend on matching handlers with overlapping requests --- .../qml/hifi/commerce/purchases/Purchases.qml | 4 +-- .../qml/hifi/models/PSFListModel.qml | 27 ++++++++++++++----- 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml index c792b88c1e..0d2acf4ec3 100644 --- a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml +++ b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml @@ -525,7 +525,7 @@ Rectangle { } onPrimaryFilter_displayNameChanged: { - purchasesModel.tagsFilter = filterBar.primaryFilter_filterName.toLowerCase(); + purchasesModel.tagsFilter = filterBar.primaryFilter_filterName; filterBar.previousPrimaryFilter = filterBar.primaryFilter_displayName; } @@ -557,7 +557,7 @@ Rectangle { console.debug('getPage', purchasesModel.listModelName, root.isShowingMyItems, filterBar.primaryFilter_filterName, purchasesModel.currentPageToRetrieve, purchasesModel.itemsPerPage); Commerce.inventory( root.isShowingMyItems ? "proofs" : "purchased", - filterBar.primaryFilter_filterName.toLowerCase(), + filterBar.primaryFilter_filterName, filterBar.text, purchasesModel.currentPageToRetrieve, purchasesModel.itemsPerPage diff --git a/interface/resources/qml/hifi/models/PSFListModel.qml b/interface/resources/qml/hifi/models/PSFListModel.qml index 8d1674e1de..1bfa2f6ae0 100644 --- a/interface/resources/qml/hifi/models/PSFListModel.qml +++ b/interface/resources/qml/hifi/models/PSFListModel.qml @@ -36,11 +36,9 @@ ListModel { Component.onCompleted: initialized = true; onEndpointChanged: if (initialized) { getFirstPage('delayClear'); } onSortKeyChanged: if (initialized) { getFirstPage('delayClear'); } - onSearchFilterChanged: { - if (!initialized) { return; } - getFirstPage('delayClear'); - } + onSearchFilterChanged: if (initialized) { getFirstPage('delayClear'); } onTagsFilterChanged: if (initialized) { getFirstPage('delayClear'); } + property int itemsPerPage: 100; // State. @@ -64,7 +62,7 @@ ListModel { // Check consistency and call processPage. function handlePage(error, response) { var processed; - console.debug('handlePage', listModelName, error, JSON.stringify(response)); + console.debug('handlePage', listModelName, additionalFirstPageRequested, error, JSON.stringify(response)); function fail(message) { console.warn("Warning page fail", listModelName, JSON.stringify(message)); currentPageToRetrieve = -1; @@ -92,7 +90,17 @@ ListModel { } root.append(processed); // FIXME keep index steady, and apply any post sort retrievedAtLeastOnePage = true; - console.debug(listModelName, 'after handlePage count', root.count); + // Suppose two properties change at once, and both of their change handlers request a new first page. + // (An example is when the a filter box gets cleared with text in it, so that the search and tags are both reset.) + // Or suppose someone just types new search text quicker than the server response. + // In these cases, we would have multiple requests in flight, and signal based responses aren't generally very good + // at matching up the right handler with the right message. Rather than require all the APIs to carefully handle such, + // and also to cut down on useless requests, we take care of that case here. + if (additionalFirstPageRequested) { + console.debug('deferred getFirstPage', listModelName); + additionalFirstPageRequested = false; + getFirstPage('delayedClear'); + } } function debugView(label) { if (!listView) { return; } @@ -128,13 +136,18 @@ ListModel { // Start the show by retrieving data according to `getPage()`. // It can be custom-defined by this item's Parent. property var getFirstPage: function (delayClear) { + if (requestPending) { + console.debug('deferring getFirstPage', listModelName); + additionalFirstPageRequested = true; + return; + } delayedClear = !!delayClear; resetModel(); requestPending = true; console.debug("getFirstPage", listModelName, currentPageToRetrieve); getPage(); } - + property bool additionalFirstPageRequested: false; property bool requestPending: false; // For de-bouncing getNextPage. // This function, will get the _next_ page of data according to `getPage()`. // It can be custom-defined by this item's Parent. Typical usage: From 380137a660fa24534127df900885b31c1aa199be Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Sun, 10 Jun 2018 12:15:00 -0700 Subject: [PATCH 59/63] Fix particle entity shader --- libraries/entities-renderer/src/textured_particle.slv | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/entities-renderer/src/textured_particle.slv b/libraries/entities-renderer/src/textured_particle.slv index cab76227c4..1d4261b1cc 100644 --- a/libraries/entities-renderer/src/textured_particle.slv +++ b/libraries/entities-renderer/src/textured_particle.slv @@ -37,8 +37,8 @@ layout(std140) uniform particleBuffer { ParticleUniforms particle; }; -in vec3 inPosition; -in vec2 inColor; // This is actual Lifetime + Seed +layout(location=0) in vec3 inPosition; +layout(location=2) in vec2 inColor; // This is actual Lifetime + Seed out vec4 varColor; out vec2 varTexcoord; From c26b03f48086c495007c06d13dac95561ea7969b Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Mon, 11 Jun 2018 10:40:13 -0700 Subject: [PATCH 60/63] Cleanup --- interface/src/ui/OverlayConductor.cpp | 4 +--- interface/src/ui/OverlayConductor.h | 2 -- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/interface/src/ui/OverlayConductor.cpp b/interface/src/ui/OverlayConductor.cpp index 924a5d6179..d131bb3467 100644 --- a/interface/src/ui/OverlayConductor.cpp +++ b/interface/src/ui/OverlayConductor.cpp @@ -89,9 +89,7 @@ void OverlayConductor::update(float dt) { } bool isAtRest = updateAvatarIsAtRest(); - bool prevMoving = _currentMoving; bool isMoving = !isAtRest; - bool movingChanged = prevMoving != isMoving; bool shouldRecenter = false; @@ -101,7 +99,7 @@ void OverlayConductor::update(float dt) { shouldRecenter = true; } } else { - if (myAvatar->getClearOverlayWhenMoving() && movingChanged && isMoving) { + if (myAvatar->getClearOverlayWhenMoving() && isMoving) { _flags |= SuppressedByMove; } } diff --git a/interface/src/ui/OverlayConductor.h b/interface/src/ui/OverlayConductor.h index 509292b775..ef7ce216d2 100644 --- a/interface/src/ui/OverlayConductor.h +++ b/interface/src/ui/OverlayConductor.h @@ -34,8 +34,6 @@ private: uint8_t _flags { SuppressedByMove }; bool _hmdMode { false }; - - bool _currentMoving { false }; // used by updateAvatarIsAtRest uint64_t _desiredAtRestTimer { 0 }; From 56bd7e3d37da2c6750057a8fb49778937cc3eba5 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Mon, 11 Jun 2018 10:58:01 -0700 Subject: [PATCH 61/63] Remove dead function declaration from header --- interface/src/ui/OverlayConductor.h | 1 - 1 file changed, 1 deletion(-) diff --git a/interface/src/ui/OverlayConductor.h b/interface/src/ui/OverlayConductor.h index ef7ce216d2..cf69c32fc5 100644 --- a/interface/src/ui/OverlayConductor.h +++ b/interface/src/ui/OverlayConductor.h @@ -23,7 +23,6 @@ public: private: bool headOutsideOverlay() const; - bool updateAvatarHasDriveInput(); bool updateAvatarIsAtRest(); enum SupressionFlags { From 75bb6e52114345e2132ea1b95ce76eab4c6f910a Mon Sep 17 00:00:00 2001 From: Clement Date: Fri, 25 May 2018 13:53:39 -0700 Subject: [PATCH 62/63] Store sandbox log in appdata local --- server-console/src/main.js | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/server-console/src/main.js b/server-console/src/main.js index 8a92fc8a5d..7d65af7655 100644 --- a/server-console/src/main.js +++ b/server-console/src/main.js @@ -75,10 +75,14 @@ function getBuildInfo() { } const buildInfo = getBuildInfo(); -function getRootHifiDataDirectory() { +function getRootHifiDataDirectory(local) { var organization = buildInfo.organization; if (osType == 'Windows_NT') { - return path.resolve(osHomeDir(), 'AppData/Roaming', organization); + if (local) { + return path.resolve(osHomeDir(), 'AppData/Local', organization); + } else { + return path.resolve(osHomeDir(), 'AppData/Roaming', organization); + } } else if (osType == 'Darwin') { return path.resolve(osHomeDir(), 'Library/Application Support', organization); } else { @@ -94,8 +98,8 @@ function getAssignmentClientResourcesDirectory() { return path.join(getRootHifiDataDirectory(), '/assignment-client'); } -function getApplicationDataDirectory() { - return path.join(getRootHifiDataDirectory(), '/Server Console'); +function getApplicationDataDirectory(local) { + return path.join(getRootHifiDataDirectory(local), '/Server Console'); } // Update lock filepath @@ -104,7 +108,7 @@ const UPDATER_LOCK_FULL_PATH = getRootHifiDataDirectory() + "/" + UPDATER_LOCK_F // Configure log global.log = require('electron-log'); -const logFile = getApplicationDataDirectory() + '/log.txt'; +const logFile = getApplicationDataDirectory(true) + '/log.txt'; fs.ensureFileSync(logFile); // Ensure file exists log.transports.file.maxSize = 5 * 1024 * 1024; log.transports.file.file = logFile; @@ -221,7 +225,8 @@ function deleteOldFiles(directoryPath, maxAgeInSeconds, filenameRegex) { } } -var logPath = path.join(getApplicationDataDirectory(), '/logs'); +var oldLogPath = path.join(getApplicationDataDirectory(), '/logs'); +var logPath = path.join(getApplicationDataDirectory(true), '/logs'); log.debug("Log directory:", logPath); log.debug("Data directory:", getRootHifiDataDirectory()); From ffac96d0a09b446d027bf5ff3e8a05561decb582 Mon Sep 17 00:00:00 2001 From: Clement Date: Tue, 29 May 2018 13:48:23 -0700 Subject: [PATCH 63/63] Migrate old logs --- server-console/src/main.js | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/server-console/src/main.js b/server-console/src/main.js index 7d65af7655..b9f128d776 100644 --- a/server-console/src/main.js +++ b/server-console/src/main.js @@ -228,6 +228,17 @@ function deleteOldFiles(directoryPath, maxAgeInSeconds, filenameRegex) { var oldLogPath = path.join(getApplicationDataDirectory(), '/logs'); var logPath = path.join(getApplicationDataDirectory(true), '/logs'); +if (oldLogPath != logPath) { + console.log("Migrating old logs from " + oldLogPath + " to " + logPath); + fs.copy(oldLogPath, logPath, err => { + if (err) { + console.error(err); + } else { + console.log('success!'); + } + }) +} + log.debug("Log directory:", logPath); log.debug("Data directory:", getRootHifiDataDirectory());