From 46061a2a15bf49c2da631480e5189e28eff2902a Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Mon, 16 Jul 2018 11:54:05 -0700 Subject: [PATCH 1/5] initial with pal --- scripts/modules/appUi.js | 161 +++++++++++++++++++++++++++++++++++++++ scripts/system/pal.js | 101 ++++++++++-------------- 2 files changed, 201 insertions(+), 61 deletions(-) create mode 100644 scripts/modules/appUi.js diff --git a/scripts/modules/appUi.js b/scripts/modules/appUi.js new file mode 100644 index 0000000000..636affb94a --- /dev/null +++ b/scripts/modules/appUi.js @@ -0,0 +1,161 @@ +// +// 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 in #3, if any. + (and if converting an existing app, remove code that [un]wires that message handling.) + x. 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) && (tabletUrl.indexOf(that.home) >= 0); // Actual url may have prefix or suffix. + } + that.toOpen = function toOpen() { // How to open the app. + if (that.isQML()) { + that.tablet.loadQMLSource(that.home); + } else { + that.tablet.gotoWebScreen(that.home, that.inject); + } + }; + that.toClose = function toClose() { // How to close the app. + // 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. + print('hrs fixme onScreenChanged', type, url, that.isOpen); + if (that.checkIsOpen(type, url)) { + if (!that.isOpen) { + that.isOpen = true; + that.wireEventBridge(true); + that.buttonActive(true); + if (that.onOpened) { + that.onOpened(); + } + } + + } else { // Not us. Should we do something for type Home, Menu, and particularly Closed (meaning tablet hidden? + if (that.isOpen) { + that.isOpen = false; + that.wireEventBridge(false); + that.buttonActive(false); + if (that.onClosed) { + that.onClosed(); + } + } + } + }; + that.hasEventBridge = false; + that.wireEventBridge = function wireEventBridge(on) { + // Sets hasEventBridge and wires onMessage to eventSignal as appropriate, IFF onMessage defined. + print('hrs fixme wireEventBridge', on, that.hasEventBridge); + if (!that.onMessage) { return; } + if (on) { + if (!that.hasEventBridge) { + print('hrs fixme connecting', that.eventSignal()); + that.eventSignal().connect(that.onMessage); + that.hasEventBridge = true; + } + } else { + if (that.hasEventBridge) { + print('hrs fixme connecting', that.eventSignal()); + that.eventSignal().disconnect(that.onMessage); + 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 toOpen() or toClose(), and reset type based on current home property. + if (that.isOpen) { + that.toClose(); + } else { + that.type = /.qml$/.test(that.home) ? 'QML' : 'Web' + that.toOpen(); + } + } : that.ignore; + that.onScriptEnding = function onScriptEnding() { + // Close if necessary, clean up any remaining handlers, and remove the button. + if (that.isOpen) { + that.toClose(); + } + 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; diff --git a/scripts/system/pal.js b/scripts/system/pal.js index e967ee6469..cc12b7798f 100644 --- a/scripts/system/pal.js +++ b/scripts/system/pal.js @@ -15,9 +15,10 @@ (function() { // BEGIN LOCAL_SCOPE var request = Script.require('request').request; + var AppUi = Script.require('appUi'); var populateNearbyUserList, color, textures, removeOverlays, - controllerComputePickRay, onTabletButtonClicked, onTabletScreenChanged, + controllerComputePickRay, off, receiveMessage, avatarDisconnected, clearLocalQMLDataAndClosePAL, createAudioInterval, tablet, CHANNEL, getConnectionData, findableByChanged, avatarAdded, avatarRemoved, avatarSessionChanged; // forward references; @@ -678,20 +679,7 @@ function tabletVisibilityChanged() { 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; +/*var hasEventBridge = false; function wireEventBridge(on) { if (on) { if (!hasEventBridge) { @@ -704,38 +692,31 @@ function wireEventBridge(on) { hasEventBridge = false; } } -} - -function onTabletScreenChanged(type, url) { +}*/ +function captureState() { 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}); - - if (onPalScreen) { - isWired = true; - - ContextOverlay.enabled = false; - Users.requestsDomainListData = true; - - audioTimer = createAudioInterval(AUDIO_LEVEL_UPDATE_INTERVAL_MS); - - tablet.tabletShownChanged.connect(tabletVisibilityChanged); - Script.update.connect(updateOverlays); - Controller.mousePressEvent.connect(handleMouseEvent); - Controller.mouseMoveEvent.connect(handleMouseMoveEvent); - Users.usernameFromIDReply.connect(usernameFromIDReply); - triggerMapping.enable(); - triggerPressMapping.enable(); - populateNearbyUserList(); - } else { - off(); - if (wasOnPalScreen) { - ContextOverlay.enabled = true; - } - } + onPalScreen = ui.isOpen; + //wireEventBridge(onPalScreen); } +function on() { + captureState(); + isWired = true; + + ContextOverlay.enabled = false; + Users.requestsDomainListData = true; + + audioTimer = createAudioInterval(AUDIO_LEVEL_UPDATE_INTERVAL_MS); + + tablet.tabletShownChanged.connect(tabletVisibilityChanged); + Script.update.connect(updateOverlays); + Controller.mousePressEvent.connect(handleMouseEvent); + Controller.mouseMoveEvent.connect(handleMouseMoveEvent); + Users.usernameFromIDReply.connect(usernameFromIDReply); + triggerMapping.enable(); + triggerPressMapping.enable(); + populateNearbyUserList(); +} +var button, ui, tablet; // // Message from other scripts, such as edit.js @@ -749,7 +730,7 @@ function receiveMessage(channel, messageString, senderID) { switch (message.method) { case 'select': if (!onPalScreen) { - tablet.loadQMLSource(PAL_QML_SOURCE); + tablet.loadQMLSource(ui.home); Script.setTimeout(function () { sendToQml(message); }, 1000); } else { sendToQml(message); // Accepts objects, not just strings. @@ -847,20 +828,17 @@ 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); + tablet = ui.tablet; + button = ui.button; Window.domainChanged.connect(clearLocalQMLDataAndClosePAL); Window.domainConnectionRefused.connect(clearLocalQMLDataAndClosePAL); Messages.subscribe(CHANNEL); @@ -877,6 +855,7 @@ var isWired = false; var audioTimer; var AUDIO_LEVEL_UPDATE_INTERVAL_MS = 100; // 10hz for now (change this and change the AVERAGING_RATIO too) function off() { + captureState(); if (isWired) { Script.update.disconnect(updateOverlays); Controller.mousePressEvent.disconnect(handleMouseEvent); @@ -896,15 +875,15 @@ function off() { } removeOverlays(); + if (wasOnPalScreen) { + ContextOverlay.enabled = true; + } } 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); From ffa5259319dedee88ce9bd0050eb55a5802d2b2e Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Mon, 16 Jul 2018 13:06:12 -0700 Subject: [PATCH 2/5] cleanup references to button, tablet --- scripts/modules/appUi.js | 31 ++++++++++--------- scripts/system/pal.js | 65 ++++++++++------------------------------ 2 files changed, 32 insertions(+), 64 deletions(-) diff --git a/scripts/modules/appUi.js b/scripts/modules/appUi.js index 636affb94a..6e6c82bdc1 100644 --- a/scripts/modules/appUi.js +++ b/scripts/modules/appUi.js @@ -13,13 +13,16 @@ function AppUi(properties) { 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, + (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 in #3, if any. - (and if converting an existing app, remove code that [un]wires that message handling.) + (And if converting an existing app, remove screenChanged.[dis]connect.) + 5. Define onMessage and sendMessage in #3, if any. + (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.) x. lint! */ @@ -36,14 +39,14 @@ function AppUi(properties) { that.checkIsOpen = function checkIsOpen(type, tabletUrl) { // Are we active? Value used to set isOpen. return (type === that.type) && (tabletUrl.indexOf(that.home) >= 0); // Actual url may have prefix or suffix. } - that.toOpen = function toOpen() { // How to open the app. + that.open = function open() { // How to open the app. if (that.isQML()) { that.tablet.loadQMLSource(that.home); } else { that.tablet.gotoWebScreen(that.home, that.inject); } }; - that.toClose = function toClose() { // How to close the app. + that.close = function close() { // How to close the app. // for toolbar-mode: go back to home screen, this will close the window. that.tablet.gotoHomeScreen(); }; @@ -87,7 +90,7 @@ function AppUi(properties) { that.onScreenChanged = function onScreenChanged(type, url) { // Set isOpen, wireEventBridge, set buttonActive as appropriate, // and finally call onOpened() or onClosed() IFF defined. - print('hrs fixme onScreenChanged', type, url, that.isOpen); + console.debug(that.buttonName, 'onScreenChanged', type, url, that.isOpen); if (that.checkIsOpen(type, url)) { if (!that.isOpen) { that.isOpen = true; @@ -112,17 +115,17 @@ function AppUi(properties) { that.hasEventBridge = false; that.wireEventBridge = function wireEventBridge(on) { // Sets hasEventBridge and wires onMessage to eventSignal as appropriate, IFF onMessage defined. - print('hrs fixme wireEventBridge', on, that.hasEventBridge); + console.debug(that.buttonName, 'wireEventBridge', on, that.hasEventBridge); if (!that.onMessage) { return; } if (on) { if (!that.hasEventBridge) { - print('hrs fixme connecting', that.eventSignal()); + console.debug(that.buttonName, 'connecting', that.eventSignal()); that.eventSignal().connect(that.onMessage); that.hasEventBridge = true; } } else { if (that.hasEventBridge) { - print('hrs fixme connecting', that.eventSignal()); + console.debug(that.buttonName, 'connecting', that.eventSignal()); that.eventSignal().disconnect(that.onMessage); that.hasEventBridge = false; } @@ -132,18 +135,18 @@ function AppUi(properties) { // To facilitate incremental development, only wire onClicked to do something when "home" is defined in properties. that.onClicked = that.home ? function onClicked() { - // Call toOpen() or toClose(), and reset type based on current home property. + // Call open() or close(), and reset type based on current home property. if (that.isOpen) { - that.toClose(); + that.close(); } else { that.type = /.qml$/.test(that.home) ? 'QML' : 'Web' - that.toOpen(); + that.open(); } } : that.ignore; that.onScriptEnding = function onScriptEnding() { // Close if necessary, clean up any remaining handlers, and remove the button. if (that.isOpen) { - that.toClose(); + that.close(); } that.tablet.screenChanged.disconnect(that.onScreenChanged); if (that.button) { diff --git a/scripts/system/pal.js b/scripts/system/pal.js index cc12b7798f..b2560d48c3 100644 --- a/scripts/system/pal.js +++ b/scripts/system/pal.js @@ -325,7 +325,7 @@ function fromQml(message) { // messages are {method, params}, like json-rpc. See } function sendToQml(message) { - tablet.sendToQml(message); + ui.tablet.sendToQml(message); } function updateUser(data) { print('PAL update:', JSON.stringify(data)); @@ -670,44 +670,24 @@ 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(); + if (!ui.tablet.tabletShown && ui.isOpen) { + ui.close(); } } -var wasOnPalScreen = false; -var onPalScreen = false; -/*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 captureState() { - wasOnPalScreen = onPalScreen; - onPalScreen = ui.isOpen; - //wireEventBridge(onPalScreen); -} function on() { - captureState(); - isWired = true; ContextOverlay.enabled = false; Users.requestsDomainListData = true; audioTimer = createAudioInterval(AUDIO_LEVEL_UPDATE_INTERVAL_MS); - tablet.tabletShownChanged.connect(tabletVisibilityChanged); + ui.tablet.tabletShownChanged.connect(tabletVisibilityChanged); Script.update.connect(updateOverlays); Controller.mousePressEvent.connect(handleMouseEvent); Controller.mouseMoveEvent.connect(handleMouseMoveEvent); @@ -716,7 +696,6 @@ function on() { triggerPressMapping.enable(); populateNearbyUserList(); } -var button, ui, tablet; // // Message from other scripts, such as edit.js @@ -729,8 +708,8 @@ function receiveMessage(channel, messageString, senderID) { var message = JSON.parse(messageString); switch (message.method) { case 'select': - if (!onPalScreen) { - tablet.loadQMLSource(ui.home); + if (!ui.isOpen) { + ui.open(); Script.setTimeout(function () { sendToQml(message); }, 1000); } else { sendToQml(message); // Accepts objects, not just strings. @@ -810,9 +789,8 @@ function avatarDisconnected(nodeID) { function clearLocalQMLDataAndClosePAL() { sendToQml({ method: 'clearLocalQMLData' }); - if (onPalScreen) { - ContextOverlay.enabled = true; - tablet.gotoHomeScreen(); + if (ui.isOpen) { + ui.close(); } } @@ -838,7 +816,6 @@ function startup() { onMessage: fromQml }); tablet = ui.tablet; - button = ui.button; Window.domainChanged.connect(clearLocalQMLDataAndClosePAL); Window.domainConnectionRefused.connect(clearLocalQMLDataAndClosePAL); Messages.subscribe(CHANNEL); @@ -850,40 +827,28 @@ function startup() { } startup(); - -var isWired = false; var audioTimer; var AUDIO_LEVEL_UPDATE_INTERVAL_MS = 100; // 10hz for now (change this and change the AVERAGING_RATIO too) function off() { - captureState(); - if (isWired) { + if (ui.isOpen) { // i.e., only when connected Script.update.disconnect(updateOverlays); 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; - if (audioTimer) { Script.clearInterval(audioTimer); } } removeOverlays(); - if (wasOnPalScreen) { - ContextOverlay.enabled = true; - } + ContextOverlay.enabled = true; } function shutdown() { - if (onPalScreen) { - tablet.gotoHomeScreen(); - } Window.domainChanged.disconnect(clearLocalQMLDataAndClosePAL); Window.domainConnectionRefused.disconnect(clearLocalQMLDataAndClosePAL); Messages.subscribe(CHANNEL); From f1fed377636bb3c1ba16947f5ab72cff64e182cd Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Mon, 16 Jul 2018 15:58:01 -0700 Subject: [PATCH 3/5] cleanup --- scripts/modules/appUi.js | 66 +++++++++++++++++++++++++--------------- scripts/system/pal.js | 13 +++++--- 2 files changed, 49 insertions(+), 30 deletions(-) diff --git a/scripts/modules/appUi.js b/scripts/modules/appUi.js index 6e6c82bdc1..f2b88dc4ea 100644 --- a/scripts/modules/appUi.js +++ b/scripts/modules/appUi.js @@ -14,22 +14,22 @@ function AppUi(properties) { 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).) + 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. (And if converting an existing app, remove code that [un]wires that message handling such as - fromQml/sendToQml or webEventReceived/emitScriptEvent.) + 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.) - x. lint! + 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 + var base = that[name] || (that.buttonPrefix + suffix); + that[name] = (base.indexOf('/') >= 0) ? base : (that.graphicsDirectory + base); //poor man's merge } // Defaults: @@ -37,34 +37,34 @@ function AppUi(properties) { 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) && (tabletUrl.indexOf(that.home) >= 0); // Actual url may have prefix or suffix. + return (type === that.type) && (tabletUrl.indexOf(that.home) >= 0); // Actual url may have prefix or suffix. } that.open = function open() { // How to open the app. - if (that.isQML()) { - that.tablet.loadQMLSource(that.home); - } else { - that.tablet.gotoWebScreen(that.home, that.inject); - } + if (that.isQML()) { + that.tablet.loadQMLSource(that.home); + } else { + that.tablet.gotoWebScreen(that.home, that.inject); + } }; that.close = function close() { // How to close the app. - // for toolbar-mode: go back to home screen, this will close the window. - that.tablet.gotoHomeScreen(); + // 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.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. + // 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'; + return that.type === 'QML'; } that.eventSignal = function eventSignal() { // What signal to hook onMessage to. - return that.isQML() ? that.tablet.fromQml : that.tablet.webEventReceived; + return that.isQML() ? that.tablet.fromQml : that.tablet.webEventReceived; }; // Overwrite with the given properties: @@ -88,45 +88,61 @@ function AppUi(properties) { // Handlers that.onScreenChanged = function onScreenChanged(type, url) { - // Set isOpen, wireEventBridge, set buttonActive as appropriate, - // and finally call onOpened() or onClosed() IFF defined. + // 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.isOpen = true; 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.isOpen = false; 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 isQml = that.isQML(); console.debug(that.buttonName, 'wireEventBridge', on, that.hasEventBridge); + // 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 + var handler = isQml ? that.onMessage : that.fromHtml; if (on) { if (!that.hasEventBridge) { console.debug(that.buttonName, 'connecting', that.eventSignal()); - that.eventSignal().connect(that.onMessage); + that.eventSignal().connect(handler); that.hasEventBridge = true; } } else { if (that.hasEventBridge) { - console.debug(that.buttonName, 'connecting', that.eventSignal()); - that.eventSignal().disconnect(that.onMessage); + console.debug(that.buttonName, 'disconnecting', that.eventSignal()); + that.eventSignal().disconnect(handler); that.hasEventBridge = false; } } diff --git a/scripts/system/pal.js b/scripts/system/pal.js index b2560d48c3..fe891087e4 100644 --- a/scripts/system/pal.js +++ b/scripts/system/pal.js @@ -20,7 +20,7 @@ var populateNearbyUserList, color, textures, removeOverlays, controllerComputePickRay, off, receiveMessage, avatarDisconnected, clearLocalQMLDataAndClosePAL, - createAudioInterval, tablet, CHANNEL, getConnectionData, findableByChanged, + createAudioInterval, CHANNEL, getConnectionData, findableByChanged, avatarAdded, avatarRemoved, avatarSessionChanged; // forward references; // hardcoding these as it appears we cannot traverse the originalTextures in overlays??? Maybe I've missed @@ -325,7 +325,7 @@ function fromQml(message) { // messages are {method, params}, like json-rpc. See } function sendToQml(message) { - ui.tablet.sendToQml(message); + ui.sendMessage(message); } function updateUser(data) { print('PAL update:', JSON.stringify(data)); @@ -680,8 +680,12 @@ function tabletVisibilityChanged() { } } +var previousContextOverlay = ContextOverlay.enabled; +var previousRequestsDomainListData = Users.requestsDomainListData; function on() { + previousContextOverlay = ContextOverlay.enabled; + previousRequestsDomainListData = Users.requestsDomainListData ContextOverlay.enabled = false; Users.requestsDomainListData = true; @@ -815,7 +819,6 @@ function startup() { onClosed: off, onMessage: fromQml }); - tablet = ui.tablet; Window.domainChanged.connect(clearLocalQMLDataAndClosePAL); Window.domainConnectionRefused.connect(clearLocalQMLDataAndClosePAL); Messages.subscribe(CHANNEL); @@ -838,14 +841,14 @@ function off() { Users.usernameFromIDReply.disconnect(usernameFromIDReply); triggerMapping.disable(); triggerPressMapping.disable(); - Users.requestsDomainListData = false; if (audioTimer) { Script.clearInterval(audioTimer); } } removeOverlays(); - ContextOverlay.enabled = true; + ContextOverlay.enabled = previousContextOverlay; + Users.requestsDomainListData = previousRequestsDomainListData; } function shutdown() { From 2c92b02b95a0b221e26d74f3235b9a6e2c386241 Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Mon, 16 Jul 2018 16:28:44 -0700 Subject: [PATCH 4/5] lint --- scripts/modules/appUi.js | 107 ++++++++++++++++++++------------------- scripts/system/pal.js | 34 ++++++------- 2 files changed, 71 insertions(+), 70 deletions(-) diff --git a/scripts/modules/appUi.js b/scripts/modules/appUi.js index f2b88dc4ea..7b6169e623 100644 --- a/scripts/modules/appUi.js +++ b/scripts/modules/appUi.js @@ -1,3 +1,5 @@ +"use strict"; +/*global Tablet, Script*/ // // libraries/appUi.js // @@ -25,7 +27,6 @@ function AppUi(properties) { and use isOpen, open(), and close() as needed.) 7. lint! */ - var that = this; function defaultButton(name, suffix) { var base = that[name] || (that.buttonPrefix + suffix); @@ -38,7 +39,7 @@ function AppUi(properties) { 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) && (tabletUrl.indexOf(that.home) >= 0); // Actual url may have prefix or suffix. - } + }; that.open = function open() { // How to open the app. if (that.isQML()) { that.tablet.loadQMLSource(that.home); @@ -62,7 +63,7 @@ function AppUi(properties) { }; 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; }; @@ -90,58 +91,58 @@ function AppUi(properties) { 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); + 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; - } + 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; - } + 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.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 isQml = that.isQML(); - console.debug(that.buttonName, 'wireEventBridge', on, that.hasEventBridge); - // 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; - } + // 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(); + console.debug(that.buttonName, 'wireEventBridge', on, that.hasEventBridge); + // 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; } + if (!that.onMessage) { return; } - // Inbound - var handler = isQml ? that.onMessage : that.fromHtml; + // Inbound + handler = isQml ? that.onMessage : that.fromHtml; if (on) { if (!that.hasEventBridge) { - console.debug(that.buttonName, 'connecting', that.eventSignal()); + 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()); + console.debug(that.buttonName, 'disconnecting', that.eventSignal()); that.eventSignal().disconnect(handler); that.hasEventBridge = false; } @@ -150,31 +151,31 @@ function AppUi(properties) { 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. + ? function onClicked() { + // Call open() or close(), and reset type based on current home property. if (that.isOpen) { - that.close(); + that.close(); } else { - that.type = /.qml$/.test(that.home) ? 'QML' : 'Web' - that.open(); + that.type = /.qml$/.test(that.home) ? 'QML' : 'Web'; + that.open(); } - } : that.ignore; + } : 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) { + // 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.button.clicked.disconnect(that.onClicked); + } that.tablet.removeButton(that.button); - } + } }; // Set up the handlers. - that.tablet.screenChanged.connect(that.onScreenChanged); + that.tablet.screenChanged.connect(that.onScreenChanged); that.button.clicked.connect(that.onClicked); Script.scriptEnding.connect(that.onScriptEnding); -}; +} module.exports = AppUi; diff --git a/scripts/system/pal.js b/scripts/system/pal.js index fe891087e4..03dfada7ec 100644 --- a/scripts/system/pal.js +++ b/scripts/system/pal.js @@ -12,10 +12,10 @@ // 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 AppUi = Script.require('appUi'); +var request = Script.require('request').request; +var AppUi = Script.require('appUi'); var populateNearbyUserList, color, textures, removeOverlays, controllerComputePickRay, off, @@ -41,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"); @@ -222,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; @@ -282,7 +283,7 @@ function fromQml(message) { // messages are {method, params}, like json-rpc. See } getConnectionData(false); }); - break + break; case 'removeFriend': friendUserName = message.params; @@ -297,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."); @@ -308,17 +309,17 @@ 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)); } @@ -335,7 +336,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({ @@ -363,7 +363,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 { @@ -374,7 +374,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) { From a904182cc2ca7cac9b6a3feb3c92e4136872c427 Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Wed, 18 Jul 2018 14:03:26 -0700 Subject: [PATCH 5/5] cr feedback --- scripts/modules/appUi.js | 20 +++++++++++++------- scripts/system/pal.js | 3 +-- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/scripts/modules/appUi.js b/scripts/modules/appUi.js index 7b6169e623..db81af3755 100644 --- a/scripts/modules/appUi.js +++ b/scripts/modules/appUi.js @@ -20,7 +20,8 @@ function AppUi(properties) { 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. + 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, @@ -38,16 +39,23 @@ function AppUi(properties) { 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) && (tabletUrl.indexOf(that.home) >= 0); // Actual url may have prefix or suffix. + return (type === that.type) && that.currentUrl && (tabletUrl.indexOf(that.currentUrl) >= 0); // Actual url may have prefix or suffix. }; - that.open = function open() { // How to open the app. + 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(that.home); + that.tablet.loadQMLSource(url); } else { - that.tablet.gotoWebScreen(that.home, that.inject); + 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(); }; @@ -122,7 +130,6 @@ function AppUi(properties) { // 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(); - console.debug(that.buttonName, 'wireEventBridge', on, that.hasEventBridge); // Outbound (always, regardless of whether there is an inbound handler). if (on) { that.sendMessage = isQml ? that.tablet.sendToQml : that.sendToHtml; @@ -156,7 +163,6 @@ function AppUi(properties) { if (that.isOpen) { that.close(); } else { - that.type = /.qml$/.test(that.home) ? 'QML' : 'Web'; that.open(); } } : that.ignore; diff --git a/scripts/system/pal.js b/scripts/system/pal.js index b6b11837e1..9485b8b49a 100644 --- a/scripts/system/pal.js +++ b/scripts/system/pal.js @@ -315,8 +315,7 @@ function fromQml(message) { // messages are {method, params}, like json-rpc. See return; } getConnectionData(friendUserName); - } - ); + }); break; case 'http.request': break; // Handled by request-service.