mirror of
https://github.com/lubosz/overte.git
synced 2025-04-23 19:34:02 +02:00
Merge pull request #13624 from howard-stearns/app-refactor4-merge
appUi module, and use in People app
This commit is contained in:
commit
2378cdfdc6
2 changed files with 243 additions and 108 deletions
187
scripts/modules/appUi.js
Normal file
187
scripts/modules/appUi.js
Normal file
|
@ -0,0 +1,187 @@
|
|||
"use strict";
|
||||
/*global Tablet, Script*/
|
||||
//
|
||||
// libraries/appUi.js
|
||||
//
|
||||
// Created by Howard Stearns on 3/20/18.
|
||||
// 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 AppUi(properties) {
|
||||
/* Example development order:
|
||||
1. var AppUi = Script.require('appUi');
|
||||
2. Put appname-i.svg, appname-a.svg in graphicsDirectory (where non-default graphicsDirectory can be added in #3).
|
||||
3. ui = new AppUi({buttonName: "APPNAME", home: "qml-or-html-path"});
|
||||
(And if converting an existing app,
|
||||
define var tablet = ui.tablet, button = ui.button; as needed.
|
||||
remove button.clicked.[dis]connect and tablet.remove(button).)
|
||||
4. Define onOpened and onClosed behavior in #3, if any.
|
||||
(And if converting an existing app, remove screenChanged.[dis]connect.)
|
||||
5. Define onMessage and sendMessage in #3, if any. onMessage is wired/unwired on open/close. If you
|
||||
want a handler to be "always on", connect it yourself at script startup.
|
||||
(And if converting an existing app, remove code that [un]wires that message handling such as
|
||||
fromQml/sendToQml or webEventReceived/emitScriptEvent.)
|
||||
6. (If converting an existing app, cleanup stuff that is no longer necessary, like references to button, tablet,
|
||||
and use isOpen, open(), and close() as needed.)
|
||||
7. lint!
|
||||
*/
|
||||
var that = this;
|
||||
function defaultButton(name, suffix) {
|
||||
var base = that[name] || (that.buttonPrefix + suffix);
|
||||
that[name] = (base.indexOf('/') >= 0) ? base : (that.graphicsDirectory + base); //poor man's merge
|
||||
}
|
||||
|
||||
// Defaults:
|
||||
that.tabletName = "com.highfidelity.interface.tablet.system";
|
||||
that.inject = "";
|
||||
that.graphicsDirectory = "icons/tablet-icons/"; // Where to look for button svgs. See below.
|
||||
that.checkIsOpen = function checkIsOpen(type, tabletUrl) { // Are we active? Value used to set isOpen.
|
||||
return (type === that.type) && that.currentUrl && (tabletUrl.indexOf(that.currentUrl) >= 0); // Actual url may have prefix or suffix.
|
||||
};
|
||||
that.setCurrentData = function setCurrentData(url) {
|
||||
that.currentUrl = url;
|
||||
that.type = /.qml$/.test(url) ? 'QML' : 'Web';
|
||||
}
|
||||
that.open = function open(optionalUrl) { // How to open the app.
|
||||
var url = optionalUrl || that.home;
|
||||
that.setCurrentData(url);
|
||||
if (that.isQML()) {
|
||||
that.tablet.loadQMLSource(url);
|
||||
} else {
|
||||
that.tablet.gotoWebScreen(url, that.inject);
|
||||
}
|
||||
};
|
||||
that.close = function close() { // How to close the app.
|
||||
that.currentUrl = "";
|
||||
// for toolbar-mode: go back to home screen, this will close the window.
|
||||
that.tablet.gotoHomeScreen();
|
||||
};
|
||||
that.buttonActive = function buttonActive(isActive) { // How to make the button active (white).
|
||||
that.button.editProperties({isActive: isActive});
|
||||
};
|
||||
that.messagesWaiting = function messagesWaiting(isWaiting) { // How to indicate a message light on button.
|
||||
// Note that waitingButton doesn't have to exist unless someone explicitly calls this with isWaiting true.
|
||||
that.button.editProperties({
|
||||
icon: isWaiting ? that.normalMessagesButton : that.normalButton,
|
||||
activeIcon: isWaiting ? that.activeMessagesButton : that.activeButton
|
||||
});
|
||||
};
|
||||
that.isQML = function isQML() { // We set type property in onClick.
|
||||
return that.type === 'QML';
|
||||
};
|
||||
that.eventSignal = function eventSignal() { // What signal to hook onMessage to.
|
||||
return that.isQML() ? that.tablet.fromQml : that.tablet.webEventReceived;
|
||||
};
|
||||
|
||||
// Overwrite with the given properties:
|
||||
Object.keys(properties).forEach(function (key) { that[key] = properties[key]; });
|
||||
|
||||
// Properties:
|
||||
that.tablet = Tablet.getTablet(that.tabletName);
|
||||
// Must be after we gather properties.
|
||||
that.buttonPrefix = that.buttonPrefix || that.buttonName.toLowerCase() + "-";
|
||||
defaultButton('normalButton', 'i.svg');
|
||||
defaultButton('activeButton', 'a.svg');
|
||||
defaultButton('normalMessagesButton', 'i-msg.svg');
|
||||
defaultButton('activeMessagesButton', 'a-msg.svg');
|
||||
that.button = that.tablet.addButton({
|
||||
icon: that.normalButton,
|
||||
activeIcon: that.activeButton,
|
||||
text: that.buttonName,
|
||||
sortOrder: that.sortOrder
|
||||
});
|
||||
that.ignore = function ignore() { };
|
||||
|
||||
// Handlers
|
||||
that.onScreenChanged = function onScreenChanged(type, url) {
|
||||
// Set isOpen, wireEventBridge, set buttonActive as appropriate,
|
||||
// and finally call onOpened() or onClosed() IFF defined.
|
||||
console.debug(that.buttonName, 'onScreenChanged', type, url, that.isOpen);
|
||||
if (that.checkIsOpen(type, url)) {
|
||||
if (!that.isOpen) {
|
||||
that.wireEventBridge(true);
|
||||
that.buttonActive(true);
|
||||
if (that.onOpened) {
|
||||
that.onOpened();
|
||||
}
|
||||
that.isOpen = true;
|
||||
}
|
||||
|
||||
} else { // Not us. Should we do something for type Home, Menu, and particularly Closed (meaning tablet hidden?
|
||||
if (that.isOpen) {
|
||||
that.wireEventBridge(false);
|
||||
that.buttonActive(false);
|
||||
if (that.onClosed) {
|
||||
that.onClosed();
|
||||
}
|
||||
that.isOpen = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
that.hasEventBridge = false;
|
||||
// HTML event bridge uses strings, not objects. Here we abstract over that.
|
||||
// (Although injected javascript still has to use JSON.stringify/JSON.parse.)
|
||||
that.sendToHtml = function (messageObject) { that.tablet.emitScriptEvent(JSON.stringify(messageObject)); };
|
||||
that.fromHtml = function (messageString) { that.onMessage(JSON.parse(messageString)); };
|
||||
that.wireEventBridge = function wireEventBridge(on) {
|
||||
// Uniquivocally sets that.sendMessage(messageObject) to do the right thing.
|
||||
// Sets hasEventBridge and wires onMessage to eventSignal as appropriate, IFF onMessage defined.
|
||||
var handler, isQml = that.isQML();
|
||||
// Outbound (always, regardless of whether there is an inbound handler).
|
||||
if (on) {
|
||||
that.sendMessage = isQml ? that.tablet.sendToQml : that.sendToHtml;
|
||||
} else {
|
||||
that.sendMessage = that.ignore;
|
||||
}
|
||||
|
||||
if (!that.onMessage) { return; }
|
||||
|
||||
// Inbound
|
||||
handler = isQml ? that.onMessage : that.fromHtml;
|
||||
if (on) {
|
||||
if (!that.hasEventBridge) {
|
||||
console.debug(that.buttonName, 'connecting', that.eventSignal());
|
||||
that.eventSignal().connect(handler);
|
||||
that.hasEventBridge = true;
|
||||
}
|
||||
} else {
|
||||
if (that.hasEventBridge) {
|
||||
console.debug(that.buttonName, 'disconnecting', that.eventSignal());
|
||||
that.eventSignal().disconnect(handler);
|
||||
that.hasEventBridge = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
that.isOpen = false;
|
||||
// To facilitate incremental development, only wire onClicked to do something when "home" is defined in properties.
|
||||
that.onClicked = that.home
|
||||
? function onClicked() {
|
||||
// Call open() or close(), and reset type based on current home property.
|
||||
if (that.isOpen) {
|
||||
that.close();
|
||||
} else {
|
||||
that.open();
|
||||
}
|
||||
} : that.ignore;
|
||||
that.onScriptEnding = function onScriptEnding() {
|
||||
// Close if necessary, clean up any remaining handlers, and remove the button.
|
||||
if (that.isOpen) {
|
||||
that.close();
|
||||
}
|
||||
that.tablet.screenChanged.disconnect(that.onScreenChanged);
|
||||
if (that.button) {
|
||||
if (that.onClicked) {
|
||||
that.button.clicked.disconnect(that.onClicked);
|
||||
}
|
||||
that.tablet.removeButton(that.button);
|
||||
}
|
||||
};
|
||||
// Set up the handlers.
|
||||
that.tablet.screenChanged.connect(that.onScreenChanged);
|
||||
that.button.clicked.connect(that.onClicked);
|
||||
Script.scriptEnding.connect(that.onScriptEnding);
|
||||
}
|
||||
module.exports = AppUi;
|
|
@ -12,14 +12,15 @@
|
|||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
(function() { // BEGIN LOCAL_SCOPE
|
||||
(function () { // BEGIN LOCAL_SCOPE
|
||||
|
||||
var request = Script.require('request').request;
|
||||
var request = Script.require('request').request;
|
||||
var AppUi = Script.require('appUi');
|
||||
|
||||
var populateNearbyUserList, color, textures, removeOverlays,
|
||||
controllerComputePickRay, onTabletButtonClicked, onTabletScreenChanged,
|
||||
controllerComputePickRay, off,
|
||||
receiveMessage, avatarDisconnected, clearLocalQMLDataAndClosePAL,
|
||||
tablet, CHANNEL, getConnectionData, findableByChanged,
|
||||
CHANNEL, getConnectionData, findableByChanged,
|
||||
avatarAdded, avatarRemoved, avatarSessionChanged; // forward references;
|
||||
|
||||
// hardcoding these as it appears we cannot traverse the originalTextures in overlays??? Maybe I've missed
|
||||
|
@ -40,6 +41,7 @@ var HOVER_TEXTURES = {
|
|||
var UNSELECTED_COLOR = { red: 0x1F, green: 0xC6, blue: 0xA6};
|
||||
var SELECTED_COLOR = {red: 0xF3, green: 0x91, blue: 0x29};
|
||||
var HOVER_COLOR = {red: 0xD0, green: 0xD0, blue: 0xD0}; // almost white for now
|
||||
var METAVERSE_BASE = Account.metaverseServerURL;
|
||||
|
||||
Script.include("/~/system/libraries/controllers.js");
|
||||
|
||||
|
@ -221,7 +223,7 @@ function convertDbToLinear(decibels) {
|
|||
return Math.pow(2, decibels / 10.0);
|
||||
}
|
||||
function fromQml(message) { // messages are {method, params}, like json-rpc. See also sendToQml.
|
||||
var data;
|
||||
var data, connectionUserName, friendUserName;
|
||||
switch (message.method) {
|
||||
case 'selected':
|
||||
selectedIds = message.params;
|
||||
|
@ -281,7 +283,7 @@ function fromQml(message) { // messages are {method, params}, like json-rpc. See
|
|||
}
|
||||
getConnectionData(false);
|
||||
});
|
||||
break
|
||||
break;
|
||||
|
||||
case 'removeFriend':
|
||||
friendUserName = message.params;
|
||||
|
@ -296,7 +298,7 @@ function fromQml(message) { // messages are {method, params}, like json-rpc. See
|
|||
}
|
||||
getConnectionData(friendUserName);
|
||||
});
|
||||
break
|
||||
break;
|
||||
case 'addFriend':
|
||||
friendUserName = message.params;
|
||||
print("Adding " + friendUserName + " to friends.");
|
||||
|
@ -307,24 +309,23 @@ function fromQml(message) { // messages are {method, params}, like json-rpc. See
|
|||
body: {
|
||||
username: friendUserName,
|
||||
}
|
||||
}, function (error, response) {
|
||||
if (error || (response.status !== 'success')) {
|
||||
print("Error: unable to friend " + friendUserName, error || response.status);
|
||||
return;
|
||||
}
|
||||
getConnectionData(friendUserName);
|
||||
}, function (error, response) {
|
||||
if (error || (response.status !== 'success')) {
|
||||
print("Error: unable to friend " + friendUserName, error || response.status);
|
||||
return;
|
||||
}
|
||||
);
|
||||
getConnectionData(friendUserName);
|
||||
});
|
||||
break;
|
||||
case 'http.request':
|
||||
break; // Handled by request-service.
|
||||
break; // Handled by request-service.
|
||||
default:
|
||||
print('Unrecognized message from Pal.qml:', JSON.stringify(message));
|
||||
}
|
||||
}
|
||||
|
||||
function sendToQml(message) {
|
||||
tablet.sendToQml(message);
|
||||
ui.sendMessage(message);
|
||||
}
|
||||
function updateUser(data) {
|
||||
print('PAL update:', JSON.stringify(data));
|
||||
|
@ -334,7 +335,6 @@ function updateUser(data) {
|
|||
// User management services
|
||||
//
|
||||
// These are prototype versions that will be changed when the back end changes.
|
||||
var METAVERSE_BASE = Account.metaverseServerURL;
|
||||
|
||||
function requestJSON(url, callback) { // callback(data) if successfull. Logs otherwise.
|
||||
request({
|
||||
|
@ -362,7 +362,7 @@ function getProfilePicture(username, callback) { // callback(url) if successfull
|
|||
});
|
||||
}
|
||||
function getAvailableConnections(domain, callback) { // callback([{usename, location}...]) if successfull. (Logs otherwise)
|
||||
url = METAVERSE_BASE + '/api/v1/users?per_page=400&'
|
||||
var url = METAVERSE_BASE + '/api/v1/users?per_page=400&';
|
||||
if (domain) {
|
||||
url += 'status=' + domain.slice(1, -1); // without curly braces
|
||||
} else {
|
||||
|
@ -373,7 +373,7 @@ function getAvailableConnections(domain, callback) { // callback([{usename, loca
|
|||
});
|
||||
}
|
||||
function getInfoAboutUser(specificUsername, callback) {
|
||||
url = METAVERSE_BASE + '/api/v1/users?filter=connections'
|
||||
var url = METAVERSE_BASE + '/api/v1/users?filter=connections';
|
||||
requestJSON(url, function (connectionsData) {
|
||||
for (user in connectionsData.users) {
|
||||
if (connectionsData.users[user].username === specificUsername) {
|
||||
|
@ -705,77 +705,41 @@ triggerMapping.from(Controller.Standard.LTClick).peek().to(makeClickHandler(Cont
|
|||
triggerPressMapping.from(Controller.Standard.RT).peek().to(makePressHandler(Controller.Standard.RightHand));
|
||||
triggerPressMapping.from(Controller.Standard.LT).peek().to(makePressHandler(Controller.Standard.LeftHand));
|
||||
|
||||
var ui;
|
||||
// Most apps can have people toggle the tablet closed and open again, and the app should remain "open" even while
|
||||
// the tablet is not shown. However, for the pal, we explicitly close the app and return the tablet to it's
|
||||
// home screen (so that the avatar highlighting goes away).
|
||||
function tabletVisibilityChanged() {
|
||||
if (!tablet.tabletShown && onPalScreen) {
|
||||
ContextOverlay.enabled = true;
|
||||
tablet.gotoHomeScreen();
|
||||
}
|
||||
}
|
||||
|
||||
var wasOnPalScreen = false;
|
||||
var onPalScreen = false;
|
||||
var PAL_QML_SOURCE = "hifi/Pal.qml";
|
||||
function onTabletButtonClicked() {
|
||||
if (!tablet) {
|
||||
print("Warning in onTabletButtonClicked(): 'tablet' undefined!");
|
||||
return;
|
||||
}
|
||||
if (onPalScreen) {
|
||||
// In Toolbar Mode, `gotoHomeScreen` will close the app window.
|
||||
tablet.gotoHomeScreen();
|
||||
} else {
|
||||
tablet.loadQMLSource(PAL_QML_SOURCE);
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
if (!ui.tablet.tabletShown && ui.isOpen) {
|
||||
ui.close();
|
||||
}
|
||||
}
|
||||
|
||||
var UPDATE_INTERVAL_MS = 100;
|
||||
var updateInterval;
|
||||
function createUpdateInterval() {
|
||||
return Script.setInterval(function () {
|
||||
updateOverlays();
|
||||
}, UPDATE_INTERVAL_MS);
|
||||
}
|
||||
|
||||
function onTabletScreenChanged(type, url) {
|
||||
wasOnPalScreen = onPalScreen;
|
||||
onPalScreen = (type === "QML" && url === PAL_QML_SOURCE);
|
||||
wireEventBridge(onPalScreen);
|
||||
// for toolbar mode: change button to active when window is first openend, false otherwise.
|
||||
button.editProperties({isActive: onPalScreen});
|
||||
var previousContextOverlay = ContextOverlay.enabled;
|
||||
var previousRequestsDomainListData = Users.requestsDomainListData;
|
||||
function on() {
|
||||
|
||||
if (onPalScreen) {
|
||||
isWired = true;
|
||||
previousContextOverlay = ContextOverlay.enabled;
|
||||
previousRequestsDomainListData = Users.requestsDomainListData
|
||||
ContextOverlay.enabled = false;
|
||||
Users.requestsDomainListData = true;
|
||||
|
||||
ContextOverlay.enabled = false;
|
||||
Users.requestsDomainListData = true;
|
||||
|
||||
tablet.tabletShownChanged.connect(tabletVisibilityChanged);
|
||||
updateInterval = createUpdateInterval();
|
||||
Controller.mousePressEvent.connect(handleMouseEvent);
|
||||
Controller.mouseMoveEvent.connect(handleMouseMoveEvent);
|
||||
Users.usernameFromIDReply.connect(usernameFromIDReply);
|
||||
triggerMapping.enable();
|
||||
triggerPressMapping.enable();
|
||||
populateNearbyUserList();
|
||||
} else {
|
||||
off();
|
||||
if (wasOnPalScreen) {
|
||||
ContextOverlay.enabled = true;
|
||||
}
|
||||
}
|
||||
ui.tablet.tabletShownChanged.connect(tabletVisibilityChanged);
|
||||
updateInterval = createUpdateInterval();
|
||||
Controller.mousePressEvent.connect(handleMouseEvent);
|
||||
Controller.mouseMoveEvent.connect(handleMouseMoveEvent);
|
||||
Users.usernameFromIDReply.connect(usernameFromIDReply);
|
||||
triggerMapping.enable();
|
||||
triggerPressMapping.enable();
|
||||
populateNearbyUserList();
|
||||
}
|
||||
|
||||
//
|
||||
|
@ -789,8 +753,8 @@ function receiveMessage(channel, messageString, senderID) {
|
|||
var message = JSON.parse(messageString);
|
||||
switch (message.method) {
|
||||
case 'select':
|
||||
if (!onPalScreen) {
|
||||
tablet.loadQMLSource(PAL_QML_SOURCE);
|
||||
if (!ui.isOpen) {
|
||||
ui.open();
|
||||
Script.setTimeout(function () { sendToQml(message); }, 1000);
|
||||
} else {
|
||||
sendToQml(message); // Accepts objects, not just strings.
|
||||
|
@ -826,9 +790,8 @@ function avatarDisconnected(nodeID) {
|
|||
|
||||
function clearLocalQMLDataAndClosePAL() {
|
||||
sendToQml({ method: 'clearLocalQMLData' });
|
||||
if (onPalScreen) {
|
||||
ContextOverlay.enabled = true;
|
||||
tablet.gotoHomeScreen();
|
||||
if (ui.isOpen) {
|
||||
ui.close();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -844,20 +807,15 @@ function avatarSessionChanged(avatarID) {
|
|||
sendToQml({ method: 'palIsStale', params: [avatarID, 'avatarSessionChanged'] });
|
||||
}
|
||||
|
||||
|
||||
var button;
|
||||
var buttonName = "PEOPLE";
|
||||
var tablet = null;
|
||||
function startup() {
|
||||
tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
|
||||
button = tablet.addButton({
|
||||
text: buttonName,
|
||||
icon: "icons/tablet-icons/people-i.svg",
|
||||
activeIcon: "icons/tablet-icons/people-a.svg",
|
||||
sortOrder: 7
|
||||
ui = new AppUi({
|
||||
buttonName: "PEOPLE",
|
||||
sortOrder: 7,
|
||||
home: "hifi/Pal.qml",
|
||||
onOpened: on,
|
||||
onClosed: off,
|
||||
onMessage: fromQml
|
||||
});
|
||||
button.clicked.connect(onTabletButtonClicked);
|
||||
tablet.screenChanged.connect(onTabletScreenChanged);
|
||||
Window.domainChanged.connect(clearLocalQMLDataAndClosePAL);
|
||||
Window.domainConnectionRefused.connect(clearLocalQMLDataAndClosePAL);
|
||||
Messages.subscribe(CHANNEL);
|
||||
|
@ -869,35 +827,25 @@ function startup() {
|
|||
}
|
||||
startup();
|
||||
|
||||
|
||||
var isWired = false;
|
||||
function off() {
|
||||
if (isWired) {
|
||||
if (ui.isOpen) { // i.e., only when connected
|
||||
if (updateInterval) {
|
||||
Script.clearInterval(updateInterval);
|
||||
}
|
||||
Controller.mousePressEvent.disconnect(handleMouseEvent);
|
||||
Controller.mouseMoveEvent.disconnect(handleMouseMoveEvent);
|
||||
tablet.tabletShownChanged.disconnect(tabletVisibilityChanged);
|
||||
ui.tablet.tabletShownChanged.disconnect(tabletVisibilityChanged);
|
||||
Users.usernameFromIDReply.disconnect(usernameFromIDReply);
|
||||
ContextOverlay.enabled = true
|
||||
triggerMapping.disable();
|
||||
triggerPressMapping.disable();
|
||||
Users.requestsDomainListData = false;
|
||||
|
||||
isWired = false;
|
||||
}
|
||||
|
||||
removeOverlays();
|
||||
ContextOverlay.enabled = previousContextOverlay;
|
||||
Users.requestsDomainListData = previousRequestsDomainListData;
|
||||
}
|
||||
|
||||
function shutdown() {
|
||||
if (onPalScreen) {
|
||||
tablet.gotoHomeScreen();
|
||||
}
|
||||
button.clicked.disconnect(onTabletButtonClicked);
|
||||
tablet.removeButton(button);
|
||||
tablet.screenChanged.disconnect(onTabletScreenChanged);
|
||||
Window.domainChanged.disconnect(clearLocalQMLDataAndClosePAL);
|
||||
Window.domainConnectionRefused.disconnect(clearLocalQMLDataAndClosePAL);
|
||||
Messages.subscribe(CHANNEL);
|
||||
|
|
Loading…
Reference in a new issue