From f9debf13886f451eea64173f36b3c2dc17741971 Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Fri, 28 Apr 2017 16:53:08 -0700 Subject: [PATCH 1/2] checkpoint --- interface/resources/qml/hifi/Feed.qml | 46 ++++++-- .../qml/hifi/tablet/TabletAddressDialog.qml | 73 +++++-------- scripts/system/pal.js | 16 ++- scripts/system/tablet-goto.js | 103 ++++++++++++------ 4 files changed, 150 insertions(+), 88 deletions(-) diff --git a/interface/resources/qml/hifi/Feed.qml b/interface/resources/qml/hifi/Feed.qml index b03144644a..ee0db29cc7 100644 --- a/interface/resources/qml/hifi/Feed.qml +++ b/interface/resources/qml/hifi/Feed.qml @@ -34,12 +34,12 @@ Column { property string metaverseServerUrl: ''; property string actions: 'snapshot'; - onActionsChanged: fillDestinations(); Component.onCompleted: fillDestinations(); property string labelText: actions; property string filter: ''; onFilterChanged: filterChoicesByText(); property var goFunction: null; + property var rpc: null; HifiConstants { id: hifi } ListModel { id: suggestions; } @@ -81,10 +81,24 @@ Column { 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(), + //'now=' + new Date().toISOString(), 'include_actions=' + actions, 'restriction=' + (Account.isLoggedIn() ? 'open,hifi' : 'open'), 'require_online=true', @@ -93,8 +107,17 @@ Column { ]; var url = metaverseBase + 'user_stories?' + options.join('&'); var thisRequestId = ++requestId; - getRequest(url, function (error, data) { - if ((thisRequestId !== requestId) || handleError(url, error, data, cb)) { + rpc('request', { + uri: url + }, function (error, data) { + console.log('fixme response', url, JSON.stringify(error), JSON.stringify(data)); + data.total_pages = 1; // fixme remove after testing + if (thisRequestId !== requestId) { + error = 'stale'; + } + //console.log('fixme', actions, pageNumber, thisRequestId, requestId, url, error) + //console.log('fixme data', actions, pageNumber, JSON.stringify(data)); + if (handleError(url, error, data, cb)) { return; // abandon stale requests } allStories = allStories.concat(data.user_stories.map(makeModelData)); @@ -108,21 +131,28 @@ Column { }); } function fillDestinations() { // Public + 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); - console.log('user stories query', actions, error || 'ok', allStories.length, 'filtered to', suggestions.count); + 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. + }/*, 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 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); @@ -130,7 +160,7 @@ Column { if (story.action === 'snapshot') { return true; } - return (story.place_name !== AddressManager.placename); // Not our entry, but do show other entry points to current domain. + return true; // fixme restore (story.place_name !== AddressManager.placename); // Not our entry, but do show other entry points to current domain. } function matches(story) { if (!words.length) { diff --git a/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml b/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml index 9689583649..0893c51517 100644 --- a/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml +++ b/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml @@ -33,9 +33,33 @@ StackView { property int cardWidth: 212; property int cardHeight: 152; 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; + signal sendToScript(var message); + function rpc(method, parameters, callback) { + console.log('fixme rpc', method); + sendToScript('foo'); + console.log('fixme sent to script'); + /*rpcCalls[rpcCounter] = callback; + var message = {method: method, params: parameters, id: rpcCounter++, jsonrpc: "2.0"}; + console.log('fixme sending rpc', JSON.stringify(message)); + sendToScript(message); + console.log('fixme sent rpc', message.id);*/ + } + function fromScript(message) { + console.log('fixme got message from script:', JSON.stringify(message)); + var callback = rpcCalls[message.id]; + if (!callback) { + console.log('No callback for message fromScript', JSON.stringify(message)); + return; + } + delete rpcCalls[message.id]; + callback(message.error, message.result); + } + Component { id: tabletWebView; TabletWebView {} } Component.onCompleted: { updateLocationText(false); @@ -266,6 +290,7 @@ StackView { actions: 'announcement'; filter: addressLine.text; goFunction: goCard; + rpc: root.rpc; } Feed { id: places; @@ -278,6 +303,7 @@ StackView { actions: 'concurrency'; filter: addressLine.text; goFunction: goCard; + rpc: root.rpc; } Feed { id: snapshots; @@ -291,6 +317,7 @@ StackView { actions: 'snapshot'; filter: addressLine.text; goFunction: goCard; + rpc: root.rpc; } } } @@ -330,50 +357,6 @@ StackView { } - function getRequest(url, cb) { // cb(error, responseOfCorrectContentType) of url. General for 'get' text/html/json, but without redirects. - // TODO: make available to other .qml. - var request = new XMLHttpRequest(); - // QT bug: apparently doesn't handle onload. Workaround using readyState. - request.onreadystatechange = function () { - var READY_STATE_DONE = 4; - var HTTP_OK = 200; - if (request.readyState >= READY_STATE_DONE) { - var error = (request.status !== HTTP_OK) && request.status.toString() + ':' + request.statusText, - response = !error && request.responseText, - contentType = !error && request.getResponseHeader('content-type'); - if (!error && contentType.indexOf('application/json') === 0) { - try { - response = JSON.parse(response); - } catch (e) { - error = e; - } - } - cb(error, response); - } - }; - request.open("GET", url, true); - request.send(); - } - - function identity(x) { - return x; - } - - 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 updateLocationText(enteringAddress) { if (enteringAddress) { notice.text = "Go To a place, @user, path, or network address:"; diff --git a/scripts/system/pal.js b/scripts/system/pal.js index ae64065216..4a6b8d4142 100644 --- a/scripts/system/pal.js +++ b/scripts/system/pal.js @@ -723,7 +723,6 @@ function startup() { activeIcon: "icons/tablet-icons/people-a.svg", sortOrder: 7 }); - tablet.fromQml.connect(fromQml); button.clicked.connect(onTabletButtonClicked); tablet.screenChanged.connect(onTabletScreenChanged); Users.usernameFromIDReply.connect(usernameFromIDReply); @@ -789,8 +788,23 @@ function onTabletButtonClicked() { audioTimer = createAudioInterval(conserveResources ? AUDIO_LEVEL_CONSERVED_UPDATE_INTERVAL_MS : AUDIO_LEVEL_UPDATE_INTERVAL_MS); } } +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 onTabletScreenChanged(type, url) { + wireEventBridge(shouldActivateButton); // for toolbar mode: change button to active when window is first openend, false otherwise. button.editProperties({isActive: shouldActivateButton}); shouldActivateButton = false; diff --git a/scripts/system/tablet-goto.js b/scripts/system/tablet-goto.js index 8ba19d18a8..c32f5d9e50 100644 --- a/scripts/system/tablet-goto.js +++ b/scripts/system/tablet-goto.js @@ -30,40 +30,6 @@ text: buttonName, sortOrder: 8 }); - function messagesWaiting(isWaiting) { - button.editProperties({ - icon: isWaiting ? WAITING_ICON : NORMAL_ICON - // No need for a different activeIcon, because we issue messagesWaiting(false) when the button goes active anyway. - }); - } - - function onClicked() { - if (onGotoScreen) { - // for toolbar-mode: go back to home screen, this will close the window. - tablet.gotoHomeScreen(); - } else { - shouldActivateButton = true; - tablet.loadQMLSource(gotoQmlSource); - onGotoScreen = true; - } - } - - function onScreenChanged(type, url) { - ignore(type); - if (url === gotoQmlSource) { - onGotoScreen = true; - shouldActivateButton = true; - button.editProperties({isActive: shouldActivateButton}); - messagesWaiting(false); - } else { - shouldActivateButton = false; - onGotoScreen = false; - button.editProperties({isActive: shouldActivateButton}); - } - } - button.clicked.connect(onClicked); - tablet.screenChanged.connect(onScreenChanged); - function request(options, callback) { // cb(error, responseOfCorrectContentType) of url. A subset of npm request. var httpRequest = new XMLHttpRequest(), key; // QT bug: apparently doesn't handle onload. Workaround using readyState. @@ -112,6 +78,74 @@ httpRequest.open(options.method, options.uri, true); httpRequest.send(options.body); } + function fromQmlXX(message) { + print('fixme got fromQml', JSON.stringify(message)); + /*var response = {id: message.id, jsonrpc: "2.0"}; + switch (message.method) { + case 'request': + request(message.params, function (error, 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 + // No need for a different activeIcon, because we issue messagesWaiting(false) when the button goes active anyway. + }); + } + var hasEventBridge = false; + function wireEventBridge(on) { + print('fixme wireEventBridge', on, hasEventBridge); + if (on) { + if (!hasEventBridge) { + tablet.fromQml.connect(fromQmlXX); + print('fixme wired', tablet); + hasEventBridge = true; + } + } else { + if (hasEventBridge) { + tablet.fromQml.disconnect(fromQmlXX); + hasEventBridge = false; + } + } + } + wireEventBridge(true); + + function onClicked() { + if (onGotoScreen) { + // for toolbar-mode: go back to home screen, this will close the window. + tablet.gotoHomeScreen(); + } else { + shouldActivateButton = true; + tablet.loadQMLSource(gotoQmlSource); + onGotoScreen = true; + } + } + + function onScreenChanged(type, url) { + ignore(type); + if (url === gotoQmlSource) { + onGotoScreen = true; + shouldActivateButton = true; + button.editProperties({isActive: shouldActivateButton}); + wireEventBridge(true); + messagesWaiting(false); + } else { + shouldActivateButton = false; + onGotoScreen = false; + button.editProperties({isActive: shouldActivateButton}); + wireEventBridge(false); + } + } + button.clicked.connect(onClicked); + tablet.screenChanged.connect(onScreenChanged); var stories = {}; var DEBUG = false; @@ -135,6 +169,7 @@ return; } var didNotify = false; + print('fixme poll', url, JSON.stringify(data.user_stories)); data.user_stories.forEach(function (story) { if (stories[story.id]) { // already seen return; From b6ae0a5bde0705ab6e99ee8fbe696d7be4e5fc54 Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Fri, 28 Apr 2017 20:06:13 -0700 Subject: [PATCH 2/2] add nextTick, and cleanup --- interface/resources/qml/hifi/Feed.qml | 23 +++++++++---------- .../qml/hifi/tablet/TabletAddressDialog.qml | 10 ++------ scripts/system/tablet-goto.js | 15 ++++-------- 3 files changed, 18 insertions(+), 30 deletions(-) diff --git a/interface/resources/qml/hifi/Feed.qml b/interface/resources/qml/hifi/Feed.qml index ee0db29cc7..fd3472b7be 100644 --- a/interface/resources/qml/hifi/Feed.qml +++ b/interface/resources/qml/hifi/Feed.qml @@ -34,7 +34,8 @@ Column { property string metaverseServerUrl: ''; property string actions: 'snapshot'; - Component.onCompleted: fillDestinations(); + // sendToScript doesn't get wired until after everything gets created. So we have to queue fillDestinations on nextTick. + Component.onCompleted: delay.start(); property string labelText: actions; property string filter: ''; onFilterChanged: filterChoicesByText(); @@ -98,7 +99,7 @@ Column { 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(), + 'now=' + new Date().toISOString(), 'include_actions=' + actions, 'restriction=' + (Account.isLoggedIn() ? 'open,hifi' : 'open'), 'require_online=true', @@ -107,16 +108,10 @@ Column { ]; var url = metaverseBase + 'user_stories?' + options.join('&'); var thisRequestId = ++requestId; - rpc('request', { - uri: url - }, function (error, data) { - console.log('fixme response', url, JSON.stringify(error), JSON.stringify(data)); - data.total_pages = 1; // fixme remove after testing + rpc('request', url, function (error, data) { if (thisRequestId !== requestId) { error = 'stale'; } - //console.log('fixme', actions, pageNumber, thisRequestId, requestId, url, error) - //console.log('fixme data', actions, pageNumber, JSON.stringify(data)); if (handleError(url, error, data, cb)) { return; // abandon stale requests } @@ -130,6 +125,10 @@ Column { cb(); }); } + property var delay: Timer { // No setTimeout or nextTick in QML. + interval: 0; + onTriggered: fillDestinations(); + } function fillDestinations() { // Public function report(label, error) { console.log(label, actions, error || 'ok', allStories.length, 'filtered to', suggestions.count); @@ -142,14 +141,14 @@ Column { 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. + }, 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 identity(x) { return x; @@ -160,7 +159,7 @@ Column { if (story.action === 'snapshot') { return true; } - return true; // fixme restore (story.place_name !== AddressManager.placename); // Not our entry, but do show other entry points to current domain. + return story.place_name !== AddressManager.placename; // Not our entry, but do show other entry points to current domain. } function matches(story) { if (!words.length) { diff --git a/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml b/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml index 0893c51517..b7c0d24b24 100644 --- a/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml +++ b/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml @@ -40,17 +40,11 @@ StackView { property var rpcCounter: 0; signal sendToScript(var message); function rpc(method, parameters, callback) { - console.log('fixme rpc', method); - sendToScript('foo'); - console.log('fixme sent to script'); - /*rpcCalls[rpcCounter] = callback; + rpcCalls[rpcCounter] = callback; var message = {method: method, params: parameters, id: rpcCounter++, jsonrpc: "2.0"}; - console.log('fixme sending rpc', JSON.stringify(message)); sendToScript(message); - console.log('fixme sent rpc', message.id);*/ } function fromScript(message) { - console.log('fixme got message from script:', JSON.stringify(message)); var callback = rpcCalls[message.id]; if (!callback) { console.log('No callback for message fromScript', JSON.stringify(message)); @@ -78,7 +72,7 @@ StackView { } - function resetAfterTeleport() { + function resetAfterTeleport() { //storyCardFrame.shown = root.shown = false; } function goCard(targetString) { diff --git a/scripts/system/tablet-goto.js b/scripts/system/tablet-goto.js index c32f5d9e50..fec7a6de90 100644 --- a/scripts/system/tablet-goto.js +++ b/scripts/system/tablet-goto.js @@ -78,9 +78,8 @@ httpRequest.open(options.method, options.uri, true); httpRequest.send(options.body); } - function fromQmlXX(message) { - print('fixme got fromQml', JSON.stringify(message)); - /*var response = {id: message.id, jsonrpc: "2.0"}; + function fromQml(message) { + var response = {id: message.id, jsonrpc: "2.0"}; switch (message.method) { case 'request': request(message.params, function (error, data) { @@ -92,7 +91,7 @@ default: response.error = {message: 'Unrecognized message', data: message}; } - tablet.sendToQml(response);*/ + tablet.sendToQml(response); } function messagesWaiting(isWaiting) { button.editProperties({ @@ -102,21 +101,18 @@ } var hasEventBridge = false; function wireEventBridge(on) { - print('fixme wireEventBridge', on, hasEventBridge); if (on) { if (!hasEventBridge) { - tablet.fromQml.connect(fromQmlXX); - print('fixme wired', tablet); + tablet.fromQml.connect(fromQml); hasEventBridge = true; } } else { if (hasEventBridge) { - tablet.fromQml.disconnect(fromQmlXX); + tablet.fromQml.disconnect(fromQml); hasEventBridge = false; } } } - wireEventBridge(true); function onClicked() { if (onGotoScreen) { @@ -169,7 +165,6 @@ return; } var didNotify = false; - print('fixme poll', url, JSON.stringify(data.user_stories)); data.user_stories.forEach(function (story) { if (stories[story.id]) { // already seen return;