From bdd38cef7ace61369711ddab842aff7db6949d6e Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Wed, 30 May 2018 17:09:13 -0700 Subject: [PATCH] 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); });