mirror of
https://github.com/overte-org/overte.git
synced 2025-08-09 15:49:24 +02:00
Merge pull request #10328 from howard-stearns/tablet-discovery-events
fix announcement suggestions
This commit is contained in:
commit
9fab66b66a
4 changed files with 137 additions and 87 deletions
|
@ -34,12 +34,13 @@ Column {
|
||||||
|
|
||||||
property string metaverseServerUrl: '';
|
property string metaverseServerUrl: '';
|
||||||
property string actions: 'snapshot';
|
property string actions: 'snapshot';
|
||||||
onActionsChanged: fillDestinations();
|
// sendToScript doesn't get wired until after everything gets created. So we have to queue fillDestinations on nextTick.
|
||||||
Component.onCompleted: fillDestinations();
|
Component.onCompleted: delay.start();
|
||||||
property string labelText: actions;
|
property string labelText: actions;
|
||||||
property string filter: '';
|
property string filter: '';
|
||||||
onFilterChanged: filterChoicesByText();
|
onFilterChanged: filterChoicesByText();
|
||||||
property var goFunction: null;
|
property var goFunction: null;
|
||||||
|
property var rpc: null;
|
||||||
|
|
||||||
HifiConstants { id: hifi }
|
HifiConstants { id: hifi }
|
||||||
ListModel { id: suggestions; }
|
ListModel { id: suggestions; }
|
||||||
|
@ -81,6 +82,20 @@ Column {
|
||||||
property var allStories: [];
|
property var allStories: [];
|
||||||
property var placeMap: ({}); // Used for making stacks.
|
property var placeMap: ({}); // Used for making stacks.
|
||||||
property int requestId: 0;
|
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
|
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.
|
// If supplied, cb1 will be run after the first page IFF it is not the last, for responsiveness.
|
||||||
var options = [
|
var options = [
|
||||||
|
@ -93,8 +108,11 @@ Column {
|
||||||
];
|
];
|
||||||
var url = metaverseBase + 'user_stories?' + options.join('&');
|
var url = metaverseBase + 'user_stories?' + options.join('&');
|
||||||
var thisRequestId = ++requestId;
|
var thisRequestId = ++requestId;
|
||||||
getRequest(url, function (error, data) {
|
rpc('request', url, function (error, data) {
|
||||||
if ((thisRequestId !== requestId) || handleError(url, error, data, cb)) {
|
if (thisRequestId !== requestId) {
|
||||||
|
error = 'stale';
|
||||||
|
}
|
||||||
|
if (handleError(url, error, data, cb)) {
|
||||||
return; // abandon stale requests
|
return; // abandon stale requests
|
||||||
}
|
}
|
||||||
allStories = allStories.concat(data.user_stories.map(makeModelData));
|
allStories = allStories.concat(data.user_stories.map(makeModelData));
|
||||||
|
@ -107,14 +125,21 @@ Column {
|
||||||
cb();
|
cb();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
property var delay: Timer { // No setTimeout or nextTick in QML.
|
||||||
|
interval: 0;
|
||||||
|
onTriggered: fillDestinations();
|
||||||
|
}
|
||||||
function fillDestinations() { // Public
|
function fillDestinations() { // Public
|
||||||
|
function report(label, error) {
|
||||||
|
console.log(label, actions, error || 'ok', allStories.length, 'filtered to', suggestions.count);
|
||||||
|
}
|
||||||
var filter = makeFilteredStoryProcessor(), counter = 0;
|
var filter = makeFilteredStoryProcessor(), counter = 0;
|
||||||
allStories = [];
|
allStories = [];
|
||||||
suggestions.clear();
|
suggestions.clear();
|
||||||
placeMap = {};
|
placeMap = {};
|
||||||
getUserStoryPage(1, function (error) {
|
getUserStoryPage(1, function (error) {
|
||||||
allStories.slice(counter).forEach(filter);
|
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;
|
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) {
|
allStories.forEach(function (story) {
|
||||||
|
@ -122,15 +147,19 @@ Column {
|
||||||
filter(story);
|
filter(story);
|
||||||
root.visible = !!suggestions.count;
|
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
|
function makeFilteredStoryProcessor() { // answer a function(storyData) that adds it to suggestions if it matches
|
||||||
var words = filter.toUpperCase().split(/\s+/).filter(identity);
|
var words = filter.toUpperCase().split(/\s+/).filter(identity);
|
||||||
function suggestable(story) {
|
function suggestable(story) {
|
||||||
if (story.action === 'snapshot') {
|
if (story.action === 'snapshot') {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return (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) {
|
function matches(story) {
|
||||||
if (!words.length) {
|
if (!words.length) {
|
||||||
|
|
|
@ -33,9 +33,27 @@ StackView {
|
||||||
property int cardWidth: 212;
|
property int cardWidth: 212;
|
||||||
property int cardHeight: 152;
|
property int cardHeight: 152;
|
||||||
property string metaverseBase: addressBarDialog.metaverseServerUrl + "/api/v1/";
|
property string metaverseBase: addressBarDialog.metaverseServerUrl + "/api/v1/";
|
||||||
|
|
||||||
property var tablet: null;
|
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) {
|
||||||
|
rpcCalls[rpcCounter] = callback;
|
||||||
|
var message = {method: method, params: parameters, id: rpcCounter++, jsonrpc: "2.0"};
|
||||||
|
sendToScript(message);
|
||||||
|
}
|
||||||
|
function fromScript(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 { id: tabletWebView; TabletWebView {} }
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
updateLocationText(false);
|
updateLocationText(false);
|
||||||
|
@ -54,7 +72,7 @@ StackView {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function resetAfterTeleport() {
|
function resetAfterTeleport() {
|
||||||
//storyCardFrame.shown = root.shown = false;
|
//storyCardFrame.shown = root.shown = false;
|
||||||
}
|
}
|
||||||
function goCard(targetString) {
|
function goCard(targetString) {
|
||||||
|
@ -266,6 +284,7 @@ StackView {
|
||||||
actions: 'announcement';
|
actions: 'announcement';
|
||||||
filter: addressLine.text;
|
filter: addressLine.text;
|
||||||
goFunction: goCard;
|
goFunction: goCard;
|
||||||
|
rpc: root.rpc;
|
||||||
}
|
}
|
||||||
Feed {
|
Feed {
|
||||||
id: places;
|
id: places;
|
||||||
|
@ -278,6 +297,7 @@ StackView {
|
||||||
actions: 'concurrency';
|
actions: 'concurrency';
|
||||||
filter: addressLine.text;
|
filter: addressLine.text;
|
||||||
goFunction: goCard;
|
goFunction: goCard;
|
||||||
|
rpc: root.rpc;
|
||||||
}
|
}
|
||||||
Feed {
|
Feed {
|
||||||
id: snapshots;
|
id: snapshots;
|
||||||
|
@ -291,6 +311,7 @@ StackView {
|
||||||
actions: 'snapshot';
|
actions: 'snapshot';
|
||||||
filter: addressLine.text;
|
filter: addressLine.text;
|
||||||
goFunction: goCard;
|
goFunction: goCard;
|
||||||
|
rpc: root.rpc;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -330,50 +351,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) {
|
function updateLocationText(enteringAddress) {
|
||||||
if (enteringAddress) {
|
if (enteringAddress) {
|
||||||
notice.text = "Go To a place, @user, path, or network address:";
|
notice.text = "Go To a place, @user, path, or network address:";
|
||||||
|
|
|
@ -723,7 +723,6 @@ function startup() {
|
||||||
activeIcon: "icons/tablet-icons/people-a.svg",
|
activeIcon: "icons/tablet-icons/people-a.svg",
|
||||||
sortOrder: 7
|
sortOrder: 7
|
||||||
});
|
});
|
||||||
tablet.fromQml.connect(fromQml);
|
|
||||||
button.clicked.connect(onTabletButtonClicked);
|
button.clicked.connect(onTabletButtonClicked);
|
||||||
tablet.screenChanged.connect(onTabletScreenChanged);
|
tablet.screenChanged.connect(onTabletScreenChanged);
|
||||||
Users.usernameFromIDReply.connect(usernameFromIDReply);
|
Users.usernameFromIDReply.connect(usernameFromIDReply);
|
||||||
|
@ -789,8 +788,23 @@ function onTabletButtonClicked() {
|
||||||
audioTimer = createAudioInterval(conserveResources ? AUDIO_LEVEL_CONSERVED_UPDATE_INTERVAL_MS : AUDIO_LEVEL_UPDATE_INTERVAL_MS);
|
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) {
|
function onTabletScreenChanged(type, url) {
|
||||||
|
wireEventBridge(shouldActivateButton);
|
||||||
// for toolbar mode: change button to active when window is first openend, false otherwise.
|
// for toolbar mode: change button to active when window is first openend, false otherwise.
|
||||||
button.editProperties({isActive: shouldActivateButton});
|
button.editProperties({isActive: shouldActivateButton});
|
||||||
shouldActivateButton = false;
|
shouldActivateButton = false;
|
||||||
|
|
|
@ -30,40 +30,6 @@
|
||||||
text: buttonName,
|
text: buttonName,
|
||||||
sortOrder: 8
|
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.
|
function request(options, callback) { // cb(error, responseOfCorrectContentType) of url. A subset of npm request.
|
||||||
var httpRequest = new XMLHttpRequest(), key;
|
var httpRequest = new XMLHttpRequest(), key;
|
||||||
// QT bug: apparently doesn't handle onload. Workaround using readyState.
|
// QT bug: apparently doesn't handle onload. Workaround using readyState.
|
||||||
|
@ -112,6 +78,70 @@
|
||||||
httpRequest.open(options.method, options.uri, true);
|
httpRequest.open(options.method, options.uri, true);
|
||||||
httpRequest.send(options.body);
|
httpRequest.send(options.body);
|
||||||
}
|
}
|
||||||
|
function fromQml(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) {
|
||||||
|
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.
|
||||||
|
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 stories = {};
|
||||||
var DEBUG = false;
|
var DEBUG = false;
|
||||||
|
|
Loading…
Reference in a new issue