diff --git a/interface/resources/icons/tablet-icons/people-a-msg.svg b/interface/resources/icons/tablet-icons/people-a-msg.svg
new file mode 100644
index 0000000000..862ce936ce
--- /dev/null
+++ b/interface/resources/icons/tablet-icons/people-a-msg.svg
@@ -0,0 +1,83 @@
+
+
+
+
\ No newline at end of file
diff --git a/interface/resources/icons/tablet-icons/people-i-msg.svg b/interface/resources/icons/tablet-icons/people-i-msg.svg
new file mode 100644
index 0000000000..635a01be4b
--- /dev/null
+++ b/interface/resources/icons/tablet-icons/people-i-msg.svg
@@ -0,0 +1,24 @@
+
+
+
diff --git a/interface/resources/qml/hifi/Pal.qml b/interface/resources/qml/hifi/Pal.qml
index 35a0078d32..e8fc41da63 100644
--- a/interface/resources/qml/hifi/Pal.qml
+++ b/interface/resources/qml/hifi/Pal.qml
@@ -271,6 +271,8 @@ Rectangle {
connectionsUserModel.getFirstPage();
}
activeTab = "connectionsTab";
+ connectionsOnlineDot.visible = false;
+ pal.sendToScript({method: 'hideNotificationDot'});
connectionsHelpText.color = hifi.colors.blueAccent;
}
}
@@ -298,6 +300,16 @@ Rectangle {
}
}
}
+ Rectangle {
+ id: connectionsOnlineDot;
+ visible: false;
+ width: 10;
+ height: width;
+ radius: width;
+ color: "#EF3B4E"
+ anchors.left: parent.left;
+ anchors.verticalCenter: parent.verticalCenter;
+ }
// "CONNECTIONS" text
RalewaySemiBold {
id: connectionsTabSelectorText;
@@ -305,7 +317,11 @@ Rectangle {
// Text size
size: hifi.fontSizes.tabularData;
// Anchors
- anchors.fill: parent;
+ anchors.left: connectionsOnlineDot.visible ? connectionsOnlineDot.right : parent.left;
+ anchors.leftMargin: connectionsOnlineDot.visible ? 4 : 0;
+ anchors.top: parent.top;
+ anchors.bottom: parent.bottom;
+ anchors.right: parent.right;
// Style
font.capitalization: Font.AllUppercase;
color: activeTab === "connectionsTab" ? hifi.colors.blueAccent : hifi.colors.baseGray;
@@ -326,7 +342,7 @@ Rectangle {
anchors.left: connectionsTabSelectorTextContainer.left;
anchors.top: connectionsTabSelectorTextContainer.top;
anchors.topMargin: 1;
- anchors.leftMargin: connectionsTabSelectorTextMetrics.width + 42;
+ anchors.leftMargin: connectionsTabSelectorTextMetrics.width + 42 + connectionsOnlineDot.width + connectionsTabSelectorText.anchors.leftMargin;
RalewayRegular {
id: connectionsHelpText;
text: "[?]";
@@ -1267,6 +1283,9 @@ Rectangle {
case 'http.response':
http.handleHttpResponse(message);
break;
+ case 'changeConnectionsDotStatus':
+ connectionsOnlineDot.visible = message.shouldShowDot;
+ break;
default:
console.log('Unrecognized message:', JSON.stringify(message));
}
diff --git a/interface/resources/qml/hifi/commerce/wallet/Wallet.qml b/interface/resources/qml/hifi/commerce/wallet/Wallet.qml
index 65d98af234..47b9d354d0 100644
--- a/interface/resources/qml/hifi/commerce/wallet/Wallet.qml
+++ b/interface/resources/qml/hifi/commerce/wallet/Wallet.qml
@@ -408,9 +408,7 @@ Rectangle {
Connections {
onSendSignalToWallet: {
- if (msg.method === 'walletReset' || msg.method === 'passphraseReset') {
- sendToScript(msg);
- } else if (msg.method === 'walletSecurity_changeSecurityImage') {
+ if (msg.method === 'walletSecurity_changeSecurityImage') {
securityImageChange.initModel();
root.activeView = "securityImageChange";
}
diff --git a/interface/src/ui/overlays/ContextOverlayInterface.cpp b/interface/src/ui/overlays/ContextOverlayInterface.cpp
index ba9a1f9fc9..5de8bb1a2a 100644
--- a/interface/src/ui/overlays/ContextOverlayInterface.cpp
+++ b/interface/src/ui/overlays/ContextOverlayInterface.cpp
@@ -252,12 +252,6 @@ bool ContextOverlayInterface::destroyContextOverlay(const EntityItemID& entityIt
void ContextOverlayInterface::contextOverlays_mousePressOnOverlay(const OverlayID& overlayID, const PointerEvent& event) {
if (overlayID == _contextOverlayID && event.getButton() == PointerEvent::PrimaryButton) {
qCDebug(context_overlay) << "Clicked Context Overlay. Entity ID:" << _currentEntityWithContextOverlay << "Overlay ID:" << overlayID;
- Setting::Handle _settingSwitch{ "commerce", true };
- if (_settingSwitch.get()) {
- openInspectionCertificate();
- } else {
- openMarketplace();
- }
emit contextOverlayClicked(_currentEntityWithContextOverlay);
_contextOverlayJustClicked = true;
}
@@ -390,34 +384,6 @@ void ContextOverlayInterface::requestOwnershipVerification(const QUuid& entityID
}
}
-static const QString INSPECTION_CERTIFICATE_QML_PATH = "hifi/commerce/inspectionCertificate/InspectionCertificate.qml";
-void ContextOverlayInterface::openInspectionCertificate() {
- // lets open the tablet to the inspection certificate QML
- if (!_currentEntityWithContextOverlay.isNull() && _entityMarketplaceID.length() > 0) {
- setLastInspectedEntity(_currentEntityWithContextOverlay);
- auto tablet = dynamic_cast(_tabletScriptingInterface->getTablet("com.highfidelity.interface.tablet.system"));
- tablet->loadQMLSource(INSPECTION_CERTIFICATE_QML_PATH);
- _hmdScriptingInterface->openTablet();
- }
-}
-
-static const QString MARKETPLACE_BASE_URL = NetworkingConstants::METAVERSE_SERVER_URL().toString() + "/marketplace/items/";
-
-void ContextOverlayInterface::openMarketplace() {
- // lets open the tablet and go to the current item in
- // the marketplace (if the current entity has a
- // marketplaceID)
- if (!_currentEntityWithContextOverlay.isNull() && _entityMarketplaceID.length() > 0) {
- auto tablet = dynamic_cast(_tabletScriptingInterface->getTablet("com.highfidelity.interface.tablet.system"));
- // construct the url to the marketplace item
- QString url = MARKETPLACE_BASE_URL + _entityMarketplaceID;
- QString MARKETPLACES_INJECT_SCRIPT_PATH = "file:///" + qApp->applicationDirPath() + "/scripts/system/html/js/marketplacesInject.js";
- tablet->gotoWebScreen(url, MARKETPLACES_INJECT_SCRIPT_PATH);
- _hmdScriptingInterface->openTablet();
- _isInMarketplaceInspectionMode = true;
- }
-}
-
void ContextOverlayInterface::enableEntityHighlight(const EntityItemID& entityItemID) {
_selectionScriptingInterface->addToSelectedItemsList("contextOverlayHighlightList", "entity", entityItemID);
}
diff --git a/interface/src/ui/overlays/ContextOverlayInterface.h b/interface/src/ui/overlays/ContextOverlayInterface.h
index 808c3a4ee3..48b14e1a91 100644
--- a/interface/src/ui/overlays/ContextOverlayInterface.h
+++ b/interface/src/ui/overlays/ContextOverlayInterface.h
@@ -94,8 +94,6 @@ private:
bool _isInMarketplaceInspectionMode { false };
- void openInspectionCertificate();
- void openMarketplace();
void enableEntityHighlight(const EntityItemID& entityItemID);
void disableEntityHighlight(const EntityItemID& entityItemID);
diff --git a/scripts/modules/appUi.js b/scripts/modules/appUi.js
index 6d2986768a..dab377911b 100644
--- a/scripts/modules/appUi.js
+++ b/scripts/modules/appUi.js
@@ -1,5 +1,5 @@
"use strict";
-/*global Tablet, Script*/
+/* global Tablet, Script */
//
// libraries/appUi.js
//
@@ -11,6 +11,7 @@
//
function AppUi(properties) {
+ var request = Script.require('request').request;
/* 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).
@@ -31,37 +32,63 @@ function AppUi(properties) {
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
+ 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.additionalAppScreens = [];
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.
+ // Actual url may have prefix or suffix.
+ return (type === that.currentVisibleScreenType) &&
+ that.currentVisibleUrl &&
+ ((that.home.indexOf(that.currentVisibleUrl) > -1) ||
+ (that.additionalAppScreens.indexOf(that.currentVisibleUrl) > -1));
};
- 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.
+ that.setCurrentVisibleScreenMetadata = function setCurrentVisibleScreenMetadata(type, url) {
+ that.currentVisibleScreenType = type;
+ that.currentVisibleUrl = url;
+ };
+ that.open = function open(optionalUrl, optionalInject) { // How to open the app.
var url = optionalUrl || that.home;
- that.setCurrentData(url);
- if (that.isQML()) {
+ var inject = optionalInject || that.inject;
+
+ if (that.isQMLUrl(url)) {
that.tablet.loadQMLSource(url);
} else {
- that.tablet.gotoWebScreen(url, that.inject);
+ that.tablet.gotoWebScreen(url, inject);
+ }
+ };
+ // Opens some app on top of the current app (on desktop, opens new window)
+ that.openNewAppOnTop = function openNewAppOnTop(url, optionalInject) {
+ var inject = optionalInject || "";
+ if (that.isQMLUrl(url)) {
+ that.tablet.loadQMLOnTop(url);
+ } else {
+ that.tablet.loadWebScreenOnTop(url, inject);
}
};
that.close = function close() { // How to close the app.
- that.currentUrl = "";
+ that.currentVisibleUrl = "";
// 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.isQMLUrl = function isQMLUrl(url) {
+ var type = /.qml$/.test(url) ? 'QML' : 'Web';
+ return type === 'QML';
+ };
+ that.isCurrentlyOnQMLScreen = function isCurrentlyOnQMLScreen() {
+ return that.currentVisibleScreenType === 'QML';
+ };
+
+ //
+ // START Notification Handling Defaults
+ //
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({
@@ -69,16 +96,124 @@ function AppUi(properties) {
activeIcon: isWaiting ? that.activeMessagesButton : that.activeButton
});
};
- that.isQML = function isQML() { // We set type property in onClick.
- return that.type === 'QML';
+ that.notificationPollTimeout = false;
+ that.notificationPollTimeoutMs = 60000;
+ that.notificationPollEndpoint = false;
+ that.notificationPollStopPaginatingConditionMet = false;
+ that.notificationDataProcessPage = function (data) {
+ return data;
};
- that.eventSignal = function eventSignal() { // What signal to hook onMessage to.
- return that.isQML() ? that.tablet.fromQml : that.tablet.webEventReceived;
+ that.notificationPollCallback = that.ignore;
+ that.notificationPollCaresAboutSince = false;
+ that.notificationInitialCallbackMade = false;
+ that.notificationDisplayBanner = function (message) {
+ Window.displayAnnouncement(message);
+ };
+ //
+ // END Notification Handling Defaults
+ //
+
+ // Handlers
+ that.onScreenChanged = function onScreenChanged(type, url) {
+ // Set isOpen, wireEventBridge, set buttonActive as appropriate,
+ // and finally call onOpened() or onClosed() IFF defined.
+ that.setCurrentVisibleScreenMetadata(type, url);
+ if (that.checkIsOpen(type, url)) {
+ that.wireEventBridge(true);
+ if (!that.isOpen) {
+ 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?
+ that.wireEventBridge(false);
+ if (that.isOpen) {
+ that.buttonActive(false);
+ if (that.onClosed) {
+ that.onClosed();
+ }
+ that.isOpen = false;
+ }
+ }
+ console.debug(that.buttonName + " app reports: Tablet screen changed.\nNew screen type: " + type +
+ "\nNew screen URL: " + url + "\nCurrent app open status: " + that.isOpen + "\n");
};
// Overwrite with the given properties:
Object.keys(properties).forEach(function (key) { that[key] = properties[key]; });
+ //
+ // START Notification Handling
+ //
+ var METAVERSE_BASE = Account.metaverseServerURL;
+ var currentDataPageToRetrieve = 1;
+ var concatenatedServerResponse = new Array();
+ that.notificationPoll = function () {
+ if (!that.notificationPollEndpoint) {
+ return;
+ }
+
+ // User is "appearing offline"
+ if (GlobalServices.findableBy === "none") {
+ that.notificationPollTimeout = Script.setTimeout(that.notificationPoll, that.notificationPollTimeoutMs);
+ return;
+ }
+
+ var url = METAVERSE_BASE + that.notificationPollEndpoint;
+
+ if (that.notificationPollCaresAboutSince) {
+ url = url + "&since=" + (new Date().getTime());
+ }
+
+ console.debug(that.buttonName, 'polling for notifications at endpoint', url);
+
+ function requestCallback(error, response) {
+ if (error || (response.status !== 'success')) {
+ print("Error: unable to get", url, error || response.status);
+ that.notificationPollTimeout = Script.setTimeout(that.notificationPoll, that.notificationPollTimeoutMs);
+ return;
+ }
+
+ if (!that.notificationPollStopPaginatingConditionMet || that.notificationPollStopPaginatingConditionMet(response)) {
+ that.notificationPollTimeout = Script.setTimeout(that.notificationPoll, that.notificationPollTimeoutMs);
+
+ var notificationData;
+ if (concatenatedServerResponse.length) {
+ notificationData = concatenatedServerResponse;
+ } else {
+ notificationData = that.notificationDataProcessPage(response);
+ }
+ console.debug(that.buttonName, 'notification data for processing:', JSON.stringify(notificationData));
+ that.notificationPollCallback(notificationData);
+ that.notificationInitialCallbackMade = true;
+ currentDataPageToRetrieve = 1;
+ concatenatedServerResponse = new Array();
+ } else {
+ concatenatedServerResponse = concatenatedServerResponse.concat(that.notificationDataProcessPage(response));
+ currentDataPageToRetrieve++;
+ request({ uri: (url + "&page=" + currentDataPageToRetrieve) }, requestCallback);
+ }
+ }
+
+ request({ uri: url }, requestCallback);
+ };
+
+ // This won't do anything if there isn't a notification endpoint set
+ that.notificationPoll();
+
+ function availabilityChanged() {
+ if (that.notificationPollTimeout) {
+ Script.clearTimeout(that.notificationPollTimeout);
+ that.notificationPollTimeout = false;
+ }
+ that.notificationPoll();
+ }
+ //
+ // END Notification Handling
+ //
+
// Properties:
that.tablet = Tablet.getTablet(that.tabletName);
// Must be after we gather properties.
@@ -100,65 +235,58 @@ function AppUi(properties) {
}
that.button = that.tablet.addButton(buttonOptions);
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;
+ that.hasOutboundEventBridge = false;
+ that.hasInboundQmlEventBridge = false;
+ that.hasInboundHtmlEventBridge = 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.sendToHtml = function (messageObject) {
+ that.tablet.emitScriptEvent(JSON.stringify(messageObject));
+ };
+ that.fromHtml = function (messageString) {
+ var parsedMessage = JSON.parse(messageString);
+ parsedMessage.messageSrc = "HTML";
+ that.onMessage(parsedMessage);
+ };
that.sendMessage = that.ignore;
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();
+ // Sets has*EventBridge and wires onMessage to the proper event bridge as appropriate, IFF onMessage defined.
+ var isCurrentlyOnQMLScreen = that.isCurrentlyOnQMLScreen();
// Outbound (always, regardless of whether there is an inbound handler).
if (on) {
- that.sendMessage = isQml ? that.tablet.sendToQml : that.sendToHtml;
+ that.sendMessage = isCurrentlyOnQMLScreen ? that.tablet.sendToQml : that.sendToHtml;
+ that.hasOutboundEventBridge = true;
} else {
that.sendMessage = that.ignore;
+ that.hasOutboundEventBridge = false;
}
- if (!that.onMessage) { return; }
+ 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;
+ if (isCurrentlyOnQMLScreen && !that.hasInboundQmlEventBridge) {
+ console.debug(that.buttonName, 'connecting', that.tablet.fromQml);
+ that.tablet.fromQml.connect(that.onMessage);
+ that.hasInboundQmlEventBridge = true;
+ } else if (!isCurrentlyOnQMLScreen && !that.hasInboundHtmlEventBridge) {
+ console.debug(that.buttonName, 'connecting', that.tablet.webEventReceived);
+ that.tablet.webEventReceived.connect(that.fromHtml);
+ that.hasInboundHtmlEventBridge = true;
}
} else {
- if (that.hasEventBridge) {
- console.debug(that.buttonName, 'disconnecting', that.eventSignal());
- that.eventSignal().disconnect(handler);
- that.hasEventBridge = false;
+ if (that.hasInboundQmlEventBridge) {
+ console.debug(that.buttonName, 'disconnecting', that.tablet.fromQml);
+ that.tablet.fromQml.disconnect(that.onMessage);
+ that.hasInboundQmlEventBridge = false;
+ }
+ if (that.hasInboundHtmlEventBridge) {
+ console.debug(that.buttonName, 'disconnecting', that.tablet.webEventReceived);
+ that.tablet.webEventReceived.disconnect(that.fromHtml);
+ that.hasInboundHtmlEventBridge = false;
}
}
};
@@ -175,6 +303,7 @@ function AppUi(properties) {
} : that.ignore;
that.onScriptEnding = function onScriptEnding() {
// Close if necessary, clean up any remaining handlers, and remove the button.
+ GlobalServices.findableByChanged.disconnect(availabilityChanged);
if (that.isOpen) {
that.close();
}
@@ -185,10 +314,15 @@ function AppUi(properties) {
}
that.tablet.removeButton(that.button);
}
+ if (that.notificationPollTimeout) {
+ Script.clearInterval(that.notificationPollTimeout);
+ that.notificationPollTimeout = false;
+ }
};
// Set up the handlers.
that.tablet.screenChanged.connect(that.onScreenChanged);
that.button.clicked.connect(that.onClicked);
Script.scriptEnding.connect(that.onScriptEnding);
+ GlobalServices.findableByChanged.connect(availabilityChanged);
}
module.exports = AppUi;
diff --git a/scripts/system/commerce/wallet.js b/scripts/system/commerce/wallet.js
index 5939b36438..993ea30c2e 100644
--- a/scripts/system/commerce/wallet.js
+++ b/scripts/system/commerce/wallet.js
@@ -1,5 +1,5 @@
"use strict";
-/*jslint vars:true, plusplus:true, forin:true*/
+/* jslint vars:true, plusplus:true, forin:true */
/* eslint indent: ["error", 4, { "outerIIFEBody": 0 }] */
//
// wallet.js
@@ -14,629 +14,529 @@
/* global getConnectionData */
(function () { // BEGIN LOCAL_SCOPE
- Script.include("/~/system/libraries/accountUtils.js");
- Script.include("/~/system/libraries/connectionUtils.js");
+Script.include("/~/system/libraries/accountUtils.js");
+Script.include("/~/system/libraries/connectionUtils.js");
+var AppUi = Script.require('appUi');
- var MARKETPLACE_URL = Account.metaverseServerURL + "/marketplace";
+var MARKETPLACE_URL = Account.metaverseServerURL + "/marketplace";
- // BEGIN AVATAR SELECTOR LOGIC
- 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 };
+// BEGIN AVATAR SELECTOR LOGIC
+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 };
- var overlays = {}; // Keeps track of all our extended overlay data objects, keyed by target identifier.
+var overlays = {}; // Keeps track of all our extended overlay data objects, keyed by target identifier.
- function ExtendedOverlay(key, type, properties) { // A wrapper around overlays to store the key it is associated with.
- overlays[key] = this;
- this.key = key;
- this.selected = false;
- this.hovering = false;
- this.activeOverlay = Overlays.addOverlay(type, properties); // We could use different overlays for (un)selected...
+function ExtendedOverlay(key, type, properties) { // A wrapper around overlays to store the key it is associated with.
+ overlays[key] = this;
+ this.key = key;
+ this.selected = false;
+ this.hovering = false;
+ this.activeOverlay = Overlays.addOverlay(type, properties); // We could use different overlays for (un)selected...
+}
+// Instance methods:
+ExtendedOverlay.prototype.deleteOverlay = function () { // remove display and data of this overlay
+ Overlays.deleteOverlay(this.activeOverlay);
+ delete overlays[this.key];
+};
+
+ExtendedOverlay.prototype.editOverlay = function (properties) { // change display of this overlay
+ Overlays.editOverlay(this.activeOverlay, properties);
+};
+
+function color(selected, hovering) {
+ var base = hovering ? HOVER_COLOR : selected ? SELECTED_COLOR : UNSELECTED_COLOR;
+ function scale(component) {
+ var delta = 0xFF - component;
+ return component;
}
- // Instance methods:
- ExtendedOverlay.prototype.deleteOverlay = function () { // remove display and data of this overlay
- Overlays.deleteOverlay(this.activeOverlay);
- delete overlays[this.key];
- };
-
- ExtendedOverlay.prototype.editOverlay = function (properties) { // change display of this overlay
- Overlays.editOverlay(this.activeOverlay, properties);
- };
-
- function color(selected, hovering) {
- var base = hovering ? HOVER_COLOR : selected ? SELECTED_COLOR : UNSELECTED_COLOR;
- function scale(component) {
- var delta = 0xFF - component;
- return component;
- }
- return { red: scale(base.red), green: scale(base.green), blue: scale(base.blue) };
- }
- // so we don't have to traverse the overlays to get the last one
- var lastHoveringId = 0;
- ExtendedOverlay.prototype.hover = function (hovering) {
- this.hovering = hovering;
- if (this.key === lastHoveringId) {
- if (hovering) {
- return;
- }
- lastHoveringId = 0;
- }
- this.editOverlay({ color: color(this.selected, hovering) });
+ return { red: scale(base.red), green: scale(base.green), blue: scale(base.blue) };
+}
+// so we don't have to traverse the overlays to get the last one
+var lastHoveringId = 0;
+ExtendedOverlay.prototype.hover = function (hovering) {
+ this.hovering = hovering;
+ if (this.key === lastHoveringId) {
if (hovering) {
- // un-hover the last hovering overlay
- if (lastHoveringId && lastHoveringId !== this.key) {
- ExtendedOverlay.get(lastHoveringId).hover(false);
- }
- lastHoveringId = this.key;
- }
- };
- ExtendedOverlay.prototype.select = function (selected) {
- if (this.selected === selected) {
return;
}
-
- this.editOverlay({ color: color(selected, this.hovering) });
- this.selected = selected;
- };
- // Class methods:
- var selectedId = false;
- ExtendedOverlay.isSelected = function (id) {
- return selectedId === id;
- };
- ExtendedOverlay.get = function (key) { // answer the extended overlay data object associated with the given avatar identifier
- return overlays[key];
- };
- ExtendedOverlay.some = function (iterator) { // Bails early as soon as iterator returns truthy.
- var key;
- for (key in overlays) {
- if (iterator(ExtendedOverlay.get(key))) {
- return;
- }
- }
- };
- ExtendedOverlay.unHover = function () { // calls hover(false) on lastHoveringId (if any)
- if (lastHoveringId) {
+ lastHoveringId = 0;
+ }
+ this.editOverlay({ color: color(this.selected, hovering) });
+ if (hovering) {
+ // un-hover the last hovering overlay
+ if (lastHoveringId && lastHoveringId !== this.key) {
ExtendedOverlay.get(lastHoveringId).hover(false);
}
- };
+ lastHoveringId = this.key;
+ }
+};
+ExtendedOverlay.prototype.select = function (selected) {
+ if (this.selected === selected) {
+ return;
+ }
- // hit(overlay) on the one overlay intersected by pickRay, if any.
- // noHit() if no ExtendedOverlay was intersected (helps with hover)
- ExtendedOverlay.applyPickRay = function (pickRay, hit, noHit) {
- var pickedOverlay = Overlays.findRayIntersection(pickRay); // Depends on nearer coverOverlays to extend closer to us than farther ones.
- if (!pickedOverlay.intersects) {
- if (noHit) {
- return noHit();
- }
+ this.editOverlay({ color: color(selected, this.hovering) });
+ this.selected = selected;
+};
+// Class methods:
+var selectedId = false;
+ExtendedOverlay.isSelected = function (id) {
+ return selectedId === id;
+};
+ExtendedOverlay.get = function (key) { // answer the extended overlay data object associated with the given avatar identifier
+ return overlays[key];
+};
+ExtendedOverlay.some = function (iterator) { // Bails early as soon as iterator returns truthy.
+ var key;
+ for (key in overlays) {
+ if (iterator(ExtendedOverlay.get(key))) {
return;
}
- ExtendedOverlay.some(function (overlay) { // See if pickedOverlay is one of ours.
- if ((overlay.activeOverlay) === pickedOverlay.overlayID) {
- hit(overlay);
- return true;
- }
- });
- };
-
- function addAvatarNode(id) {
- return new ExtendedOverlay(id, "sphere", {
- drawInFront: true,
- solid: true,
- alpha: 0.8,
- color: color(false, false),
- ignoreRayIntersection: false
- });
}
-
- var pingPong = true;
- function updateOverlays() {
- var eye = Camera.position;
- AvatarList.getAvatarIdentifiers().forEach(function (id) {
- if (!id) {
- return; // don't update ourself, or avatars we're not interested in
- }
- var avatar = AvatarList.getAvatar(id);
- if (!avatar) {
- return; // will be deleted below if there had been an overlay.
- }
- var overlay = ExtendedOverlay.get(id);
- if (!overlay) { // For now, we're treating this as a temporary loss, as from the personal space bubble. Add it back.
- overlay = addAvatarNode(id);
- }
- var target = avatar.position;
- var distance = Vec3.distance(target, eye);
- var offset = 0.2;
- var diff = Vec3.subtract(target, eye); // get diff between target and eye (a vector pointing to the eye from avatar position)
- var headIndex = avatar.getJointIndex("Head"); // base offset on 1/2 distance from hips to head if we can
- if (headIndex > 0) {
- offset = avatar.getAbsoluteJointTranslationInObjectFrame(headIndex).y / 2;
- }
-
- // move a bit in front, towards the camera
- target = Vec3.subtract(target, Vec3.multiply(Vec3.normalize(diff), offset));
-
- // now bump it up a bit
- target.y = target.y + offset;
-
- overlay.ping = pingPong;
- overlay.editOverlay({
- color: color(ExtendedOverlay.isSelected(id), overlay.hovering),
- position: target,
- dimensions: 0.032 * distance
- });
- });
- pingPong = !pingPong;
- ExtendedOverlay.some(function (overlay) { // Remove any that weren't updated. (User is gone.)
- if (overlay.ping === pingPong) {
- overlay.deleteOverlay();
- }
- });
- }
- function removeOverlays() {
- selectedId = false;
- lastHoveringId = 0;
- ExtendedOverlay.some(function (overlay) {
- overlay.deleteOverlay();
- });
+};
+ExtendedOverlay.unHover = function () { // calls hover(false) on lastHoveringId (if any)
+ if (lastHoveringId) {
+ ExtendedOverlay.get(lastHoveringId).hover(false);
}
+};
- //
- // Clicks.
- //
- function usernameFromIDReply(id, username, machineFingerprint, isAdmin) {
- if (selectedId === id) {
- var message = {
- method: 'updateSelectedRecipientUsername',
- userName: username === "" ? "unknown username" : username
- };
- sendToQml(message);
+// hit(overlay) on the one overlay intersected by pickRay, if any.
+// noHit() if no ExtendedOverlay was intersected (helps with hover)
+ExtendedOverlay.applyPickRay = function (pickRay, hit, noHit) {
+ var pickedOverlay = Overlays.findRayIntersection(pickRay); // Depends on nearer coverOverlays to extend closer to us than farther ones.
+ if (!pickedOverlay.intersects) {
+ if (noHit) {
+ return noHit();
}
+ return;
}
- function handleClick(pickRay) {
- ExtendedOverlay.applyPickRay(pickRay, function (overlay) {
- var nextSelectedStatus = !overlay.selected;
- var avatarId = overlay.key;
- selectedId = nextSelectedStatus ? avatarId : false;
- if (nextSelectedStatus) {
- Users.requestUsernameFromID(avatarId);
- }
- var message = {
- method: 'selectRecipient',
- id: avatarId,
- isSelected: nextSelectedStatus,
- displayName: '"' + AvatarList.getAvatar(avatarId).sessionDisplayName + '"',
- userName: ''
- };
- sendToQml(message);
-
- ExtendedOverlay.some(function (overlay) {
- var id = overlay.key;
- var selected = ExtendedOverlay.isSelected(id);
- overlay.select(selected);
- });
-
+ ExtendedOverlay.some(function (overlay) { // See if pickedOverlay is one of ours.
+ if ((overlay.activeOverlay) === pickedOverlay.overlayID) {
+ hit(overlay);
return true;
- });
- }
- function handleMouseEvent(mousePressEvent) { // handleClick if we get one.
- if (!mousePressEvent.isLeftButton) {
- return;
}
- handleClick(Camera.computePickRay(mousePressEvent.x, mousePressEvent.y));
+ });
+};
+
+function addAvatarNode(id) {
+ return new ExtendedOverlay(id, "sphere", {
+ drawInFront: true,
+ solid: true,
+ alpha: 0.8,
+ color: color(false, false),
+ ignoreRayIntersection: false
+ });
+}
+
+var pingPong = true;
+function updateOverlays() {
+ var eye = Camera.position;
+ AvatarList.getAvatarIdentifiers().forEach(function (id) {
+ if (!id) {
+ return; // don't update ourself, or avatars we're not interested in
+ }
+ var avatar = AvatarList.getAvatar(id);
+ if (!avatar) {
+ return; // will be deleted below if there had been an overlay.
+ }
+ var overlay = ExtendedOverlay.get(id);
+ if (!overlay) { // For now, we're treating this as a temporary loss, as from the personal space bubble. Add it back.
+ overlay = addAvatarNode(id);
+ }
+ var target = avatar.position;
+ var distance = Vec3.distance(target, eye);
+ var offset = 0.2;
+ var diff = Vec3.subtract(target, eye); // get diff between target and eye (a vector pointing to the eye from avatar position)
+ var headIndex = avatar.getJointIndex("Head"); // base offset on 1/2 distance from hips to head if we can
+ if (headIndex > 0) {
+ offset = avatar.getAbsoluteJointTranslationInObjectFrame(headIndex).y / 2;
+ }
+
+ // move a bit in front, towards the camera
+ target = Vec3.subtract(target, Vec3.multiply(Vec3.normalize(diff), offset));
+
+ // now bump it up a bit
+ target.y = target.y + offset;
+
+ overlay.ping = pingPong;
+ overlay.editOverlay({
+ color: color(ExtendedOverlay.isSelected(id), overlay.hovering),
+ position: target,
+ dimensions: 0.032 * distance
+ });
+ });
+ pingPong = !pingPong;
+ ExtendedOverlay.some(function (overlay) { // Remove any that weren't updated. (User is gone.)
+ if (overlay.ping === pingPong) {
+ overlay.deleteOverlay();
+ }
+ });
+}
+function removeOverlays() {
+ selectedId = false;
+ lastHoveringId = 0;
+ ExtendedOverlay.some(function (overlay) {
+ overlay.deleteOverlay();
+ });
+}
+
+//
+// Clicks.
+//
+function usernameFromIDReply(id, username, machineFingerprint, isAdmin) {
+ if (selectedId === id) {
+ var message = {
+ method: 'updateSelectedRecipientUsername',
+ userName: username === "" ? "unknown username" : username
+ };
+ ui.sendMessage(message);
}
- function handleMouseMove(pickRay) { // given the pickRay, just do the hover logic
- ExtendedOverlay.applyPickRay(pickRay, function (overlay) {
- overlay.hover(true);
- }, function () {
+}
+function handleClick(pickRay) {
+ ExtendedOverlay.applyPickRay(pickRay, function (overlay) {
+ var nextSelectedStatus = !overlay.selected;
+ var avatarId = overlay.key;
+ selectedId = nextSelectedStatus ? avatarId : false;
+ if (nextSelectedStatus) {
+ Users.requestUsernameFromID(avatarId);
+ }
+ var message = {
+ method: 'selectRecipient',
+ id: avatarId,
+ isSelected: nextSelectedStatus,
+ displayName: '"' + AvatarList.getAvatar(avatarId).sessionDisplayName + '"',
+ userName: ''
+ };
+ ui.sendMessage(message);
+
+ ExtendedOverlay.some(function (overlay) {
+ var id = overlay.key;
+ var selected = ExtendedOverlay.isSelected(id);
+ overlay.select(selected);
+ });
+
+ return true;
+ });
+}
+function handleMouseEvent(mousePressEvent) { // handleClick if we get one.
+ if (!mousePressEvent.isLeftButton) {
+ return;
+ }
+ handleClick(Camera.computePickRay(mousePressEvent.x, mousePressEvent.y));
+}
+function handleMouseMove(pickRay) { // given the pickRay, just do the hover logic
+ ExtendedOverlay.applyPickRay(pickRay, function (overlay) {
+ overlay.hover(true);
+ }, function () {
+ ExtendedOverlay.unHover();
+ });
+}
+
+// handy global to keep track of which hand is the mouse (if any)
+var currentHandPressed = 0;
+var TRIGGER_CLICK_THRESHOLD = 0.85;
+var TRIGGER_PRESS_THRESHOLD = 0.05;
+
+function handleMouseMoveEvent(event) { // find out which overlay (if any) is over the mouse position
+ var pickRay;
+ if (HMD.active) {
+ if (currentHandPressed !== 0) {
+ pickRay = controllerComputePickRay(currentHandPressed);
+ } else {
+ // nothing should hover, so
ExtendedOverlay.unHover();
+ return;
+ }
+ } else {
+ pickRay = Camera.computePickRay(event.x, event.y);
+ }
+ handleMouseMove(pickRay);
+}
+function handleTriggerPressed(hand, value) {
+ // The idea is if you press one trigger, it is the one
+ // we will consider the mouse. Even if the other is pressed,
+ // we ignore it until this one is no longer pressed.
+ var isPressed = value > TRIGGER_PRESS_THRESHOLD;
+ if (currentHandPressed === 0) {
+ currentHandPressed = isPressed ? hand : 0;
+ return;
+ }
+ if (currentHandPressed === hand) {
+ currentHandPressed = isPressed ? hand : 0;
+ return;
+ }
+ // otherwise, the other hand is still triggered
+ // so do nothing.
+}
+
+// We get mouseMoveEvents from the handControllers, via handControllerPointer.
+// But we don't get mousePressEvents.
+var triggerMapping = Controller.newMapping(Script.resolvePath('') + '-click');
+var triggerPressMapping = Controller.newMapping(Script.resolvePath('') + '-press');
+function controllerComputePickRay(hand) {
+ var controllerPose = getControllerWorldLocation(hand, true);
+ if (controllerPose.valid) {
+ return { origin: controllerPose.position, direction: Quat.getUp(controllerPose.orientation) };
+ }
+}
+function makeClickHandler(hand) {
+ return function (clicked) {
+ if (clicked > TRIGGER_CLICK_THRESHOLD) {
+ var pickRay = controllerComputePickRay(hand);
+ handleClick(pickRay);
+ }
+ };
+}
+function makePressHandler(hand) {
+ return function (value) {
+ handleTriggerPressed(hand, value);
+ };
+}
+triggerMapping.from(Controller.Standard.RTClick).peek().to(makeClickHandler(Controller.Standard.RightHand));
+triggerMapping.from(Controller.Standard.LTClick).peek().to(makeClickHandler(Controller.Standard.LeftHand));
+triggerPressMapping.from(Controller.Standard.RT).peek().to(makePressHandler(Controller.Standard.RightHand));
+triggerPressMapping.from(Controller.Standard.LT).peek().to(makePressHandler(Controller.Standard.LeftHand));
+// END AVATAR SELECTOR LOGIC
+
+var sendMoneyRecipient;
+var sendMoneyParticleEffectUpdateTimer;
+var particleEffectTimestamp;
+var sendMoneyParticleEffect;
+var SEND_MONEY_PARTICLE_TIMER_UPDATE = 250;
+var SEND_MONEY_PARTICLE_EMITTING_DURATION = 3000;
+var SEND_MONEY_PARTICLE_LIFETIME_SECONDS = 8;
+var SEND_MONEY_PARTICLE_PROPERTIES = {
+ accelerationSpread: { x: 0, y: 0, z: 0 },
+ alpha: 1,
+ alphaFinish: 1,
+ alphaSpread: 0,
+ alphaStart: 1,
+ azimuthFinish: 0,
+ azimuthStart: -6,
+ color: { red: 143, green: 5, blue: 255 },
+ colorFinish: { red: 255, green: 0, blue: 204 },
+ colorSpread: { red: 0, green: 0, blue: 0 },
+ colorStart: { red: 0, green: 136, blue: 255 },
+ emitAcceleration: { x: 0, y: 0, z: 0 }, // Immediately gets updated to be accurate
+ emitDimensions: { x: 0, y: 0, z: 0 },
+ emitOrientation: { x: 0, y: 0, z: 0 },
+ emitRate: 4,
+ emitSpeed: 2.1,
+ emitterShouldTrail: true,
+ isEmitting: 1,
+ lifespan: SEND_MONEY_PARTICLE_LIFETIME_SECONDS + 1, // Immediately gets updated to be accurate
+ lifetime: SEND_MONEY_PARTICLE_LIFETIME_SECONDS + 1,
+ maxParticles: 20,
+ name: 'hfc-particles',
+ particleRadius: 0.2,
+ polarFinish: 0,
+ polarStart: 0,
+ radiusFinish: 0.05,
+ radiusSpread: 0,
+ radiusStart: 0.2,
+ speedSpread: 0,
+ textures: "http://hifi-content.s3.amazonaws.com/alan/dev/Particles/Bokeh-Particle-HFC.png",
+ type: 'ParticleEffect'
+};
+
+var MS_PER_SEC = 1000;
+function updateSendMoneyParticleEffect() {
+ var timestampNow = Date.now();
+ if ((timestampNow - particleEffectTimestamp) > (SEND_MONEY_PARTICLE_LIFETIME_SECONDS * MS_PER_SEC)) {
+ deleteSendMoneyParticleEffect();
+ return;
+ } else if ((timestampNow - particleEffectTimestamp) > SEND_MONEY_PARTICLE_EMITTING_DURATION) {
+ Entities.editEntity(sendMoneyParticleEffect, {
+ isEmitting: 0
+ });
+ } else if (sendMoneyParticleEffect) {
+ var recipientPosition = AvatarList.getAvatar(sendMoneyRecipient).position;
+ var distance = Vec3.distance(recipientPosition, MyAvatar.position);
+ var accel = Vec3.subtract(recipientPosition, MyAvatar.position);
+ accel.y -= 3.0;
+ var life = Math.sqrt(2 * distance / Vec3.length(accel));
+ Entities.editEntity(sendMoneyParticleEffect, {
+ emitAcceleration: accel,
+ lifespan: life
});
}
+}
- // handy global to keep track of which hand is the mouse (if any)
- var currentHandPressed = 0;
- var TRIGGER_CLICK_THRESHOLD = 0.85;
- var TRIGGER_PRESS_THRESHOLD = 0.05;
-
- function handleMouseMoveEvent(event) { // find out which overlay (if any) is over the mouse position
- var pickRay;
- if (HMD.active) {
- if (currentHandPressed !== 0) {
- pickRay = controllerComputePickRay(currentHandPressed);
- } else {
- // nothing should hover, so
- ExtendedOverlay.unHover();
- return;
- }
- } else {
- pickRay = Camera.computePickRay(event.x, event.y);
- }
- handleMouseMove(pickRay);
+function deleteSendMoneyParticleEffect() {
+ if (sendMoneyParticleEffectUpdateTimer) {
+ Script.clearInterval(sendMoneyParticleEffectUpdateTimer);
+ sendMoneyParticleEffectUpdateTimer = null;
}
- function handleTriggerPressed(hand, value) {
- // The idea is if you press one trigger, it is the one
- // we will consider the mouse. Even if the other is pressed,
- // we ignore it until this one is no longer pressed.
- var isPressed = value > TRIGGER_PRESS_THRESHOLD;
- if (currentHandPressed === 0) {
- currentHandPressed = isPressed ? hand : 0;
- return;
- }
- if (currentHandPressed === hand) {
- currentHandPressed = isPressed ? hand : 0;
- return;
- }
- // otherwise, the other hand is still triggered
- // so do nothing.
+ if (sendMoneyParticleEffect) {
+ sendMoneyParticleEffect = Entities.deleteEntity(sendMoneyParticleEffect);
}
+ sendMoneyRecipient = null;
+}
- // We get mouseMoveEvents from the handControllers, via handControllerPointer.
- // But we don't get mousePressEvents.
- var triggerMapping = Controller.newMapping(Script.resolvePath('') + '-click');
- var triggerPressMapping = Controller.newMapping(Script.resolvePath('') + '-press');
- function controllerComputePickRay(hand) {
- var controllerPose = getControllerWorldLocation(hand, true);
- if (controllerPose.valid) {
- return { origin: controllerPose.position, direction: Quat.getUp(controllerPose.orientation) };
- }
+function onUsernameChanged() {
+ if (Account.username !== Settings.getValue("wallet/savedUsername")) {
+ Settings.setValue("wallet/autoLogout", false);
+ Settings.setValue("wallet/savedUsername", "");
}
- function makeClickHandler(hand) {
- return function (clicked) {
- if (clicked > TRIGGER_CLICK_THRESHOLD) {
- var pickRay = controllerComputePickRay(hand);
- handleClick(pickRay);
- }
- };
- }
- function makePressHandler(hand) {
- return function (value) {
- handleTriggerPressed(hand, value);
- };
- }
- triggerMapping.from(Controller.Standard.RTClick).peek().to(makeClickHandler(Controller.Standard.RightHand));
- triggerMapping.from(Controller.Standard.LTClick).peek().to(makeClickHandler(Controller.Standard.LeftHand));
- triggerPressMapping.from(Controller.Standard.RT).peek().to(makePressHandler(Controller.Standard.RightHand));
- triggerPressMapping.from(Controller.Standard.LT).peek().to(makePressHandler(Controller.Standard.LeftHand));
- // END AVATAR SELECTOR LOGIC
+}
- // Function Name: onButtonClicked()
- //
- // Description:
- // -Fired when the app button is pressed.
- //
- // Relevant Variables:
- // -WALLET_QML_SOURCE: The path to the Wallet QML
- // -onWalletScreen: true/false depending on whether we're looking at the app.
- var WALLET_QML_SOURCE = "hifi/commerce/wallet/Wallet.qml";
- var MARKETPLACE_PURCHASES_QML_PATH = "hifi/commerce/purchases/Purchases.qml";
- var onWalletScreen = false;
- function onButtonClicked() {
- if (!tablet) {
- print("Warning in buttonClicked(): 'tablet' undefined!");
- return;
+// Function Name: fromQml()
+//
+// Description:
+// -Called when a message is received from SpectatorCamera.qml. The "message" argument is what is sent from the QML
+// in the format "{method, params}", like json-rpc. See also sendToQml().
+var MARKETPLACE_PURCHASES_QML_PATH = "hifi/commerce/purchases/Purchases.qml";
+var MARKETPLACES_INJECT_SCRIPT_URL = Script.resolvePath("../html/js/marketplacesInject.js");
+function fromQml(message) {
+ switch (message.method) {
+ case 'passphrasePopup_cancelClicked':
+ case 'needsLogIn_cancelClicked':
+ ui.close();
+ break;
+ case 'walletSetup_cancelClicked':
+ switch (message.referrer) {
+ case '': // User clicked "Wallet" app
+ case undefined:
+ case null:
+ ui.close();
+ break;
+ case 'purchases':
+ case 'marketplace cta':
+ case 'mainPage':
+ ui.open(MARKETPLACE_URL, MARKETPLACES_INJECT_SCRIPT_URL);
+ break;
+ default: // User needs to return to an individual marketplace item URL
+ ui.open(MARKETPLACE_URL + '/items/' + message.referrer, MARKETPLACES_INJECT_SCRIPT_URL);
+ break;
}
- if (onWalletScreen) {
- // for toolbar-mode: go back to home screen, this will close the window.
- tablet.gotoHomeScreen();
- } else {
- tablet.loadQMLSource(WALLET_QML_SOURCE);
- }
- }
-
- // Function Name: sendToQml()
- //
- // Description:
- // -Use this function to send a message to the QML (i.e. to change appearances). The "message" argument is what is sent to
- // the QML in the format "{method, params}", like json-rpc. See also fromQml().
- function sendToQml(message) {
- tablet.sendToQml(message);
- }
-
- var sendMoneyRecipient;
- var sendMoneyParticleEffectUpdateTimer;
- var particleEffectTimestamp;
- var sendMoneyParticleEffect;
- var SEND_MONEY_PARTICLE_TIMER_UPDATE = 250;
- var SEND_MONEY_PARTICLE_EMITTING_DURATION = 3000;
- var SEND_MONEY_PARTICLE_LIFETIME_SECONDS = 8;
- var SEND_MONEY_PARTICLE_PROPERTIES = {
- accelerationSpread: { x: 0, y: 0, z: 0 },
- alpha: 1,
- alphaFinish: 1,
- alphaSpread: 0,
- alphaStart: 1,
- azimuthFinish: 0,
- azimuthStart: -6,
- color: { red: 143, green: 5, blue: 255 },
- colorFinish: { red: 255, green: 0, blue: 204 },
- colorSpread: { red: 0, green: 0, blue: 0 },
- colorStart: { red: 0, green: 136, blue: 255 },
- emitAcceleration: { x: 0, y: 0, z: 0 }, // Immediately gets updated to be accurate
- emitDimensions: { x: 0, y: 0, z: 0 },
- emitOrientation: { x: 0, y: 0, z: 0 },
- emitRate: 4,
- emitSpeed: 2.1,
- emitterShouldTrail: true,
- isEmitting: 1,
- lifespan: SEND_MONEY_PARTICLE_LIFETIME_SECONDS + 1, // Immediately gets updated to be accurate
- lifetime: SEND_MONEY_PARTICLE_LIFETIME_SECONDS + 1,
- maxParticles: 20,
- name: 'hfc-particles',
- particleRadius: 0.2,
- polarFinish: 0,
- polarStart: 0,
- radiusFinish: 0.05,
- radiusSpread: 0,
- radiusStart: 0.2,
- speedSpread: 0,
- textures: "http://hifi-content.s3.amazonaws.com/alan/dev/Particles/Bokeh-Particle-HFC.png",
- type: 'ParticleEffect'
- };
-
- function updateSendMoneyParticleEffect() {
- var timestampNow = Date.now();
- if ((timestampNow - particleEffectTimestamp) > (SEND_MONEY_PARTICLE_LIFETIME_SECONDS * 1000)) {
- deleteSendMoneyParticleEffect();
- return;
- } else if ((timestampNow - particleEffectTimestamp) > SEND_MONEY_PARTICLE_EMITTING_DURATION) {
- Entities.editEntity(sendMoneyParticleEffect, {
- isEmitting: 0
- });
- } else if (sendMoneyParticleEffect) {
- var recipientPosition = AvatarList.getAvatar(sendMoneyRecipient).position;
- var distance = Vec3.distance(recipientPosition, MyAvatar.position);
- var accel = Vec3.subtract(recipientPosition, MyAvatar.position);
- accel.y -= 3.0;
- var life = Math.sqrt(2 * distance / Vec3.length(accel));
- Entities.editEntity(sendMoneyParticleEffect, {
- emitAcceleration: accel,
- lifespan: life
- });
- }
- }
-
- function deleteSendMoneyParticleEffect() {
- if (sendMoneyParticleEffectUpdateTimer) {
- Script.clearInterval(sendMoneyParticleEffectUpdateTimer);
- sendMoneyParticleEffectUpdateTimer = null;
- }
- if (sendMoneyParticleEffect) {
- sendMoneyParticleEffect = Entities.deleteEntity(sendMoneyParticleEffect);
- }
- sendMoneyRecipient = null;
- }
-
- function onUsernameChanged() {
- if (Account.username !== Settings.getValue("wallet/savedUsername")) {
- Settings.setValue("wallet/autoLogout", false);
- Settings.setValue("wallet/savedUsername", "");
- }
- }
-
- // Function Name: fromQml()
- //
- // Description:
- // -Called when a message is received from SpectatorCamera.qml. The "message" argument is what is sent from the QML
- // in the format "{method, params}", like json-rpc. See also sendToQml().
- var isHmdPreviewDisabled = true;
- var MARKETPLACES_INJECT_SCRIPT_URL = Script.resolvePath("../html/js/marketplacesInject.js");
-
- function fromQml(message) {
- switch (message.method) {
- case 'passphrasePopup_cancelClicked':
- case 'needsLogIn_cancelClicked':
- tablet.gotoHomeScreen();
- break;
- case 'walletSetup_cancelClicked':
- switch (message.referrer) {
- case '': // User clicked "Wallet" app
- case undefined:
- case null:
- tablet.gotoHomeScreen();
- break;
- case 'purchases':
- case 'marketplace cta':
- case 'mainPage':
- tablet.gotoWebScreen(MARKETPLACE_URL, MARKETPLACES_INJECT_SCRIPT_URL);
- break;
- default: // User needs to return to an individual marketplace item URL
- tablet.gotoWebScreen(MARKETPLACE_URL + '/items/' + message.referrer, MARKETPLACES_INJECT_SCRIPT_URL);
- break;
- }
- break;
- case 'needsLogIn_loginClicked':
- openLoginWindow();
- break;
- case 'disableHmdPreview':
- break; // do nothing here, handled in marketplaces.js
- case 'maybeEnableHmdPreview':
- break; // do nothing here, handled in marketplaces.js
- case 'passphraseReset':
- onButtonClicked();
- onButtonClicked();
- break;
- case 'walletReset':
- Settings.setValue("isFirstUseOfPurchases", true);
- onButtonClicked();
- onButtonClicked();
- break;
- case 'transactionHistory_linkClicked':
- tablet.gotoWebScreen(message.marketplaceLink, MARKETPLACES_INJECT_SCRIPT_URL);
- break;
- case 'goToPurchases_fromWalletHome':
- case 'goToPurchases':
- tablet.pushOntoStack(MARKETPLACE_PURCHASES_QML_PATH);
- break;
- case 'goToMarketplaceMainPage':
- tablet.gotoWebScreen(MARKETPLACE_URL, MARKETPLACES_INJECT_SCRIPT_URL);
- break;
- case 'goToMarketplaceItemPage':
- tablet.gotoWebScreen(MARKETPLACE_URL + '/items/' + message.itemId, MARKETPLACES_INJECT_SCRIPT_URL);
- break;
- case 'refreshConnections':
- print('Refreshing Connections...');
- getConnectionData(false);
- break;
- case 'enable_ChooseRecipientNearbyMode':
- if (!isUpdateOverlaysWired) {
- Script.update.connect(updateOverlays);
- isUpdateOverlaysWired = true;
- }
- break;
- case 'disable_ChooseRecipientNearbyMode':
- if (isUpdateOverlaysWired) {
- Script.update.disconnect(updateOverlays);
- isUpdateOverlaysWired = false;
- }
- removeOverlays();
- break;
- case 'sendAsset_sendPublicly':
- if (message.assetName === "") {
- deleteSendMoneyParticleEffect();
- sendMoneyRecipient = message.recipient;
- var amount = message.amount;
- var props = SEND_MONEY_PARTICLE_PROPERTIES;
- props.parentID = MyAvatar.sessionUUID;
- props.position = MyAvatar.position;
- props.position.y += 0.2;
- if (message.effectImage) {
- props.textures = message.effectImage;
- }
- sendMoneyParticleEffect = Entities.addEntity(props, true);
- particleEffectTimestamp = Date.now();
- updateSendMoneyParticleEffect();
- sendMoneyParticleEffectUpdateTimer = Script.setInterval(updateSendMoneyParticleEffect, SEND_MONEY_PARTICLE_TIMER_UPDATE);
- }
- break;
- case 'transactionHistory_goToBank':
- if (Account.metaverseServerURL.indexOf("staging") >= 0) {
- Window.location = "hifi://hifiqa-master-metaverse-staging"; // So that we can test in staging.
- } else {
- Window.location = "hifi://BankOfHighFidelity";
- }
- break;
- case 'wallet_availableUpdatesReceived':
- // NOP
- break;
- case 'http.request':
- // Handled elsewhere, don't log.
- break;
- default:
- print('Unrecognized message from QML:', JSON.stringify(message));
- }
- }
-
- // Function Name: wireEventBridge()
- //
- // Description:
- // -Used to connect/disconnect the script's response to the tablet's "fromQml" signal. Set the "on" argument to enable or
- // disable to event bridge.
- //
- // Relevant Variables:
- // -hasEventBridge: true/false depending on whether we've already connected the event bridge.
- var hasEventBridge = false;
- function wireEventBridge(on) {
- if (!tablet) {
- print("Warning in wireEventBridge(): 'tablet' undefined!");
- return;
- }
- if (on) {
- if (!hasEventBridge) {
- tablet.fromQml.connect(fromQml);
- hasEventBridge = true;
- }
- } else {
- if (hasEventBridge) {
- tablet.fromQml.disconnect(fromQml);
- hasEventBridge = false;
- }
- }
- }
-
- // Function Name: onTabletScreenChanged()
- //
- // Description:
- // -Called when the TabletScriptingInterface::screenChanged() signal is emitted. The "type" argument can be either the string
- // value of "Home", "Web", "Menu", "QML", or "Closed". The "url" argument is only valid for Web and QML.
- function onTabletScreenChanged(type, url) {
- onWalletScreen = (type === "QML" && url === WALLET_QML_SOURCE);
- wireEventBridge(onWalletScreen);
- // Change button to active when window is first openend, false otherwise.
- if (button) {
- button.editProperties({ isActive: onWalletScreen });
- }
-
- if (onWalletScreen) {
- if (!isWired) {
- Users.usernameFromIDReply.connect(usernameFromIDReply);
- Controller.mousePressEvent.connect(handleMouseEvent);
- Controller.mouseMoveEvent.connect(handleMouseMoveEvent);
- triggerMapping.enable();
- triggerPressMapping.enable();
- }
- isWired = true;
- } else {
- off();
- }
- }
-
- //
- // Manage the connection between the button and the window.
- //
- var button;
- var buttonName = "WALLET";
- var tablet = null;
- var walletEnabled = Settings.getValue("commerce", true);
- function startup() {
- GlobalServices.myUsernameChanged.connect(onUsernameChanged);
- if (walletEnabled) {
- tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
- button = tablet.addButton({
- text: buttonName,
- icon: "icons/tablet-icons/wallet-i.svg",
- activeIcon: "icons/tablet-icons/wallet-a.svg",
- sortOrder: 10
- });
- button.clicked.connect(onButtonClicked);
- tablet.screenChanged.connect(onTabletScreenChanged);
- }
- }
- var isWired = false;
- var isUpdateOverlaysWired = false;
- function off() {
- if (isWired) {
- Users.usernameFromIDReply.disconnect(usernameFromIDReply);
- Controller.mousePressEvent.disconnect(handleMouseEvent);
- Controller.mouseMoveEvent.disconnect(handleMouseMoveEvent);
- triggerMapping.disable();
- triggerPressMapping.disable();
-
- isWired = false;
+ break;
+ case 'needsLogIn_loginClicked':
+ openLoginWindow();
+ break;
+ case 'disableHmdPreview':
+ break; // do nothing here, handled in marketplaces.js
+ case 'maybeEnableHmdPreview':
+ break; // do nothing here, handled in marketplaces.js
+ case 'transactionHistory_linkClicked':
+ ui.open(message.marketplaceLink, MARKETPLACES_INJECT_SCRIPT_URL);
+ break;
+ case 'goToPurchases_fromWalletHome':
+ case 'goToPurchases':
+ ui.open(MARKETPLACE_PURCHASES_QML_PATH);
+ break;
+ case 'goToMarketplaceMainPage':
+ ui.open(MARKETPLACE_URL, MARKETPLACES_INJECT_SCRIPT_URL);
+ break;
+ case 'goToMarketplaceItemPage':
+ ui.open(MARKETPLACE_URL + '/items/' + message.itemId, MARKETPLACES_INJECT_SCRIPT_URL);
+ break;
+ case 'refreshConnections':
+ print('Refreshing Connections...');
+ getConnectionData(false);
+ break;
+ case 'enable_ChooseRecipientNearbyMode':
+ if (!isUpdateOverlaysWired) {
+ Script.update.connect(updateOverlays);
+ isUpdateOverlaysWired = true;
}
+ break;
+ case 'disable_ChooseRecipientNearbyMode':
if (isUpdateOverlaysWired) {
Script.update.disconnect(updateOverlays);
isUpdateOverlaysWired = false;
}
removeOverlays();
- }
- function shutdown() {
- GlobalServices.myUsernameChanged.disconnect(onUsernameChanged);
- button.clicked.disconnect(onButtonClicked);
- tablet.removeButton(button);
- deleteSendMoneyParticleEffect();
- if (tablet) {
- tablet.screenChanged.disconnect(onTabletScreenChanged);
- if (onWalletScreen) {
- tablet.gotoHomeScreen();
+ break;
+ case 'sendAsset_sendPublicly':
+ if (message.assetName === "") {
+ deleteSendMoneyParticleEffect();
+ sendMoneyRecipient = message.recipient;
+ var amount = message.amount;
+ var props = SEND_MONEY_PARTICLE_PROPERTIES;
+ props.parentID = MyAvatar.sessionUUID;
+ props.position = MyAvatar.position;
+ props.position.y += 0.2;
+ if (message.effectImage) {
+ props.textures = message.effectImage;
}
+ sendMoneyParticleEffect = Entities.addEntity(props, true);
+ particleEffectTimestamp = Date.now();
+ updateSendMoneyParticleEffect();
+ sendMoneyParticleEffectUpdateTimer = Script.setInterval(updateSendMoneyParticleEffect, SEND_MONEY_PARTICLE_TIMER_UPDATE);
}
- off();
+ break;
+ case 'transactionHistory_goToBank':
+ if (Account.metaverseServerURL.indexOf("staging") >= 0) {
+ Window.location = "hifi://hifiqa-master-metaverse-staging"; // So that we can test in staging.
+ } else {
+ Window.location = "hifi://BankOfHighFidelity";
+ }
+ break;
+ case 'wallet_availableUpdatesReceived':
+ // NOP
+ break;
+ case 'http.request':
+ // Handled elsewhere, don't log.
+ break;
+ default:
+ print('Unrecognized message from QML:', JSON.stringify(message));
}
+}
- //
- // Run the functions.
- //
- startup();
- Script.scriptEnding.connect(shutdown);
+function walletOpened() {
+ Users.usernameFromIDReply.connect(usernameFromIDReply);
+ Controller.mousePressEvent.connect(handleMouseEvent);
+ Controller.mouseMoveEvent.connect(handleMouseMoveEvent);
+ triggerMapping.enable();
+ triggerPressMapping.enable();
+}
+function walletClosed() {
+ off();
+}
+
+//
+// Manage the connection between the button and the window.
+//
+var BUTTON_NAME = "WALLET";
+var WALLET_QML_SOURCE = "hifi/commerce/wallet/Wallet.qml";
+var ui;
+function startup() {
+ ui = new AppUi({
+ buttonName: BUTTON_NAME,
+ sortOrder: 10,
+ home: WALLET_QML_SOURCE,
+ onOpened: walletOpened,
+ onClosed: walletClosed,
+ onMessage: fromQml
+ });
+ GlobalServices.myUsernameChanged.connect(onUsernameChanged);
+}
+var isUpdateOverlaysWired = false;
+function off() {
+ Users.usernameFromIDReply.disconnect(usernameFromIDReply);
+ Controller.mousePressEvent.disconnect(handleMouseEvent);
+ Controller.mouseMoveEvent.disconnect(handleMouseMoveEvent);
+ triggerMapping.disable();
+ triggerPressMapping.disable();
+
+ if (isUpdateOverlaysWired) {
+ Script.update.disconnect(updateOverlays);
+ isUpdateOverlaysWired = false;
+ }
+ removeOverlays();
+}
+function shutdown() {
+ GlobalServices.myUsernameChanged.disconnect(onUsernameChanged);
+ deleteSendMoneyParticleEffect();
+ off();
+}
+
+//
+// Run the functions.
+//
+startup();
+Script.scriptEnding.connect(shutdown);
}()); // END LOCAL_SCOPE
diff --git a/scripts/system/help.js b/scripts/system/help.js
index aaeb82721c..40bbf6dbe2 100644
--- a/scripts/system/help.js
+++ b/scripts/system/help.js
@@ -1,5 +1,5 @@
"use strict";
-
+/* eslint indent: ["error", 4, { "outerIIFEBody": 0 }] */
//
// help.js
// scripts/system/
@@ -12,50 +12,18 @@
//
/* globals Tablet, Script, HMD, Controller, Menu */
-(function() { // BEGIN LOCAL_SCOPE
-
- var HOME_BUTTON_TEXTURE = Script.resourcesPath() + "meshes/tablet-with-home-button.fbx/tablet-with-home-button.fbm/button-root.png";
- var HELP_URL = Script.resourcesPath() + "html/tabletHelp.html";
- var buttonName = "HELP";
- var onHelpScreen = false;
- var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
- var button = tablet.addButton({
- icon: "icons/tablet-icons/help-i.svg",
- activeIcon: "icons/tablet-icons/help-a.svg",
- text: buttonName,
- sortOrder: 6
+(function () { // BEGIN LOCAL_SCOPE
+var AppUi = Script.require('appUi');
+
+var HELP_URL = Script.resourcesPath() + "html/tabletHelp.html";
+var HELP_BUTTON_NAME = "HELP";
+var ui;
+function startup() {
+ ui = new AppUi({
+ buttonName: HELP_BUTTON_NAME,
+ sortOrder: 6,
+ home: HELP_URL
});
-
- var enabled = false;
- function onClicked() {
- if (onHelpScreen) {
- tablet.gotoHomeScreen();
- } else {
- if (HMD.tabletID) {
- Entities.editEntity(HMD.tabletID, {textures: JSON.stringify({"tex.close" : HOME_BUTTON_TEXTURE})});
- }
- Menu.triggerOption('Help...');
- onHelpScreen = true;
- }
- }
-
- function onScreenChanged(type, url) {
- onHelpScreen = type === "Web" && (url.indexOf(HELP_URL) === 0);
- button.editProperties({ isActive: onHelpScreen });
- }
-
- button.clicked.connect(onClicked);
- tablet.screenChanged.connect(onScreenChanged);
-
- Script.scriptEnding.connect(function () {
- if (onHelpScreen) {
- tablet.gotoHomeScreen();
- }
- button.clicked.disconnect(onClicked);
- tablet.screenChanged.disconnect(onScreenChanged);
- if (tablet) {
- tablet.removeButton(button);
- }
- });
-
+}
+startup();
}()); // END LOCAL_SCOPE
diff --git a/scripts/system/html/js/marketplacesInject.js b/scripts/system/html/js/marketplacesInject.js
index 7eece890c9..3239105254 100644
--- a/scripts/system/html/js/marketplacesInject.js
+++ b/scripts/system/html/js/marketplacesInject.js
@@ -1,3 +1,5 @@
+/* global $, window, MutationObserver */
+
//
// marketplacesInject.js
//
@@ -11,7 +13,6 @@
//
(function () {
-
// Event bridge messages.
var CLARA_IO_DOWNLOAD = "CLARA.IO DOWNLOAD";
var CLARA_IO_STATUS = "CLARA.IO STATUS";
@@ -24,7 +25,7 @@
var canWriteAssets = false;
var xmlHttpRequest = null;
- var isPreparing = false; // Explicitly track download request status.
+ var isPreparing = false; // Explicitly track download request status.
var commerceMode = false;
var userIsLoggedIn = false;
@@ -33,7 +34,6 @@
var messagesWaiting = false;
function injectCommonCode(isDirectoryPage) {
-
// Supporting styles from marketplaces.css.
// Glyph font family, size, and spacing adjusted because HiFi-Glyphs cannot be used cross-domain.
$("head").append(
@@ -74,7 +74,9 @@
(document.referrer !== "") ? window.history.back() : window.location = (marketplaceBaseURL + "/marketplace?");
});
$("#all-markets").on("click", function () {
- EventBridge.emitWebEvent(GOTO_DIRECTORY);
+ EventBridge.emitWebEvent(JSON.stringify({
+ type: GOTO_DIRECTORY
+ }));
});
}
@@ -94,11 +96,11 @@
});
}
- emitWalletSetupEvent = function() {
+ var emitWalletSetupEvent = function () {
EventBridge.emitWebEvent(JSON.stringify({
type: "WALLET_SETUP"
}));
- }
+ };
function maybeAddSetupWalletButton() {
if (!$('body').hasClass("walletsetup-injected") && userIsLoggedIn && walletNeedsSetup) {
@@ -285,7 +287,7 @@
$(this).closest('.col-xs-3').prev().attr("class", 'col-xs-6');
$(this).closest('.col-xs-3').attr("class", 'col-xs-6');
- var priceElement = $(this).find('.price')
+ var priceElement = $(this).find('.price');
priceElement.css({
"padding": "3px 5px",
"height": "40px",
@@ -355,12 +357,12 @@
function injectAddScrollbarToCategories() {
$('#categories-dropdown').on('show.bs.dropdown', function () {
$('body > div.container').css('display', 'none')
- $('#categories-dropdown > ul.dropdown-menu').css({ 'overflow': 'auto', 'height': 'calc(100vh - 110px)' })
+ $('#categories-dropdown > ul.dropdown-menu').css({ 'overflow': 'auto', 'height': 'calc(100vh - 110px)' });
});
$('#categories-dropdown').on('hide.bs.dropdown', function () {
- $('body > div.container').css('display', '')
- $('#categories-dropdown > ul.dropdown-menu').css({ 'overflow': '', 'height': '' })
+ $('body > div.container').css('display', '');
+ $('#categories-dropdown > ul.dropdown-menu').css({ 'overflow': '', 'height': '' });
});
}
@@ -382,7 +384,6 @@
mutations.forEach(function (mutation) {
injectBuyButtonOnMainPage();
});
- //observer.disconnect();
});
var config = { attributes: true, childList: true, characterData: true };
observer.observe(target, config);
@@ -451,8 +452,8 @@
"itemPage",
urlParams.get('edition'),
type);
- }
- });
+ }
+ });
maybeAddPurchasesButton();
}
}
@@ -503,127 +504,142 @@
$(".top-title .col-sm-4").append(downloadContainer);
downloadContainer.append(downloadFBX);
}
+ }
+ }
- // Automatic download to High Fidelity.
- function startAutoDownload() {
+ // Automatic download to High Fidelity.
+ function startAutoDownload() {
+ // One file request at a time.
+ if (isPreparing) {
+ console.log("WARNING: Clara.io FBX: Prepare only one download at a time");
+ return;
+ }
- // One file request at a time.
- if (isPreparing) {
- console.log("WARNING: Clara.io FBX: Prepare only one download at a time");
- return;
- }
+ // User must be able to write to Asset Server.
+ if (!canWriteAssets) {
+ console.log("ERROR: Clara.io FBX: File download cancelled because no permissions to write to Asset Server");
+ EventBridge.emitWebEvent(JSON.stringify({
+ type: WARN_USER_NO_PERMISSIONS
+ }));
+ return;
+ }
- // User must be able to write to Asset Server.
- if (!canWriteAssets) {
- console.log("ERROR: Clara.io FBX: File download cancelled because no permissions to write to Asset Server");
- EventBridge.emitWebEvent(WARN_USER_NO_PERMISSIONS);
- return;
- }
+ // User must be logged in.
+ var loginButton = $("#topnav a[href='/signup']");
+ if (loginButton.length > 0) {
+ loginButton[0].click();
+ return;
+ }
- // User must be logged in.
- var loginButton = $("#topnav a[href='/signup']");
- if (loginButton.length > 0) {
- loginButton[0].click();
- return;
- }
+ // Obtain zip file to download for requested asset.
+ // Reference: https://clara.io/learn/sdk/api/export
- // Obtain zip file to download for requested asset.
- // Reference: https://clara.io/learn/sdk/api/export
+ //var XMLHTTPREQUEST_URL = "https://clara.io/api/scenes/{uuid}/export/fbx?zip=true¢erScene=true&alignSceneGround=true&fbxUnit=Meter&fbxVersion=7&fbxEmbedTextures=true&imageFormat=WebGL";
+ // 13 Jan 2017: Specify FBX version 5 and remove some options in order to make Clara.io site more likely to
+ // be successful in generating zip files.
+ var XMLHTTPREQUEST_URL = "https://clara.io/api/scenes/{uuid}/export/fbx?fbxUnit=Meter&fbxVersion=5&fbxEmbedTextures=true&imageFormat=WebGL";
- //var XMLHTTPREQUEST_URL = "https://clara.io/api/scenes/{uuid}/export/fbx?zip=true¢erScene=true&alignSceneGround=true&fbxUnit=Meter&fbxVersion=7&fbxEmbedTextures=true&imageFormat=WebGL";
- // 13 Jan 2017: Specify FBX version 5 and remove some options in order to make Clara.io site more likely to
- // be successful in generating zip files.
- var XMLHTTPREQUEST_URL = "https://clara.io/api/scenes/{uuid}/export/fbx?fbxUnit=Meter&fbxVersion=5&fbxEmbedTextures=true&imageFormat=WebGL";
+ var uuid = location.href.match(/\/view\/([a-z0-9\-]*)/)[1];
+ var url = XMLHTTPREQUEST_URL.replace("{uuid}", uuid);
- var uuid = location.href.match(/\/view\/([a-z0-9\-]*)/)[1];
- var url = XMLHTTPREQUEST_URL.replace("{uuid}", uuid);
+ xmlHttpRequest = new XMLHttpRequest();
+ var responseTextIndex = 0;
+ var zipFileURL = "";
- xmlHttpRequest = new XMLHttpRequest();
- var responseTextIndex = 0;
- var zipFileURL = "";
+ xmlHttpRequest.onreadystatechange = function () {
+ // Messages are appended to responseText; process the new ones.
+ var message = this.responseText.slice(responseTextIndex);
+ var statusMessage = "";
- xmlHttpRequest.onreadystatechange = function () {
- // Messages are appended to responseText; process the new ones.
- var message = this.responseText.slice(responseTextIndex);
- var statusMessage = "";
+ if (isPreparing) { // Ignore messages in flight after finished/cancelled.
+ var lines = message.split(/[\n\r]+/);
- if (isPreparing) { // Ignore messages in flight after finished/cancelled.
- var lines = message.split(/[\n\r]+/);
+ for (var i = 0, length = lines.length; i < length; i++) {
+ if (lines[i].slice(0, 5) === "data:") {
+ // Parse line.
+ var data;
+ try {
+ data = JSON.parse(lines[i].slice(5));
+ }
+ catch (e) {
+ data = {};
+ }
- for (var i = 0, length = lines.length; i < length; i++) {
- if (lines[i].slice(0, 5) === "data:") {
- // Parse line.
- var data;
- try {
- data = JSON.parse(lines[i].slice(5));
- }
- catch (e) {
- data = {};
- }
+ // Extract status message.
+ if (data.hasOwnProperty("message") && data.message !== null) {
+ statusMessage = data.message;
+ console.log("Clara.io FBX: " + statusMessage);
+ }
- // Extract status message.
- if (data.hasOwnProperty("message") && data.message !== null) {
- statusMessage = data.message;
- console.log("Clara.io FBX: " + statusMessage);
- }
-
- // Extract zip file URL.
- if (data.hasOwnProperty("files") && data.files.length > 0) {
- zipFileURL = data.files[0].url;
- if (zipFileURL.slice(-4) !== ".zip") {
- console.log(JSON.stringify(data)); // Data for debugging.
- }
- }
+ // Extract zip file URL.
+ if (data.hasOwnProperty("files") && data.files.length > 0) {
+ zipFileURL = data.files[0].url;
+ if (zipFileURL.slice(-4) !== ".zip") {
+ console.log(JSON.stringify(data)); // Data for debugging.
}
}
-
- if (statusMessage !== "") {
- // Update the UI with the most recent status message.
- EventBridge.emitWebEvent(CLARA_IO_STATUS + " " + statusMessage);
- }
}
-
- responseTextIndex = this.responseText.length;
- };
-
- // Note: onprogress doesn't have computable total length so can't use it to determine % complete.
-
- xmlHttpRequest.onload = function () {
- var statusMessage = "";
-
- if (!isPreparing) {
- return;
- }
-
- isPreparing = false;
-
- var HTTP_OK = 200;
- if (this.status !== HTTP_OK) {
- statusMessage = "Zip file request terminated with " + this.status + " " + this.statusText;
- console.log("ERROR: Clara.io FBX: " + statusMessage);
- EventBridge.emitWebEvent(CLARA_IO_STATUS + " " + statusMessage);
- } else if (zipFileURL.slice(-4) !== ".zip") {
- statusMessage = "Error creating zip file for download.";
- console.log("ERROR: Clara.io FBX: " + statusMessage + ": " + zipFileURL);
- EventBridge.emitWebEvent(CLARA_IO_STATUS + " " + statusMessage);
- } else {
- EventBridge.emitWebEvent(CLARA_IO_DOWNLOAD + " " + zipFileURL);
- console.log("Clara.io FBX: File download initiated for " + zipFileURL);
- }
-
- xmlHttpRequest = null;
}
- isPreparing = true;
-
- console.log("Clara.io FBX: Request zip file for " + uuid);
- EventBridge.emitWebEvent(CLARA_IO_STATUS + " Initiating download");
-
- xmlHttpRequest.open("POST", url, true);
- xmlHttpRequest.setRequestHeader("Accept", "text/event-stream");
- xmlHttpRequest.send();
+ if (statusMessage !== "") {
+ // Update the UI with the most recent status message.
+ EventBridge.emitWebEvent(JSON.stringify({
+ type: CLARA_IO_STATUS,
+ status: statusMessage
+ }));
+ }
}
+
+ responseTextIndex = this.responseText.length;
+ };
+
+ // Note: onprogress doesn't have computable total length so can't use it to determine % complete.
+
+ xmlHttpRequest.onload = function () {
+ var statusMessage = "";
+
+ if (!isPreparing) {
+ return;
+ }
+
+ isPreparing = false;
+
+ var HTTP_OK = 200;
+ if (this.status !== HTTP_OK) {
+ statusMessage = "Zip file request terminated with " + this.status + " " + this.statusText;
+ console.log("ERROR: Clara.io FBX: " + statusMessage);
+ EventBridge.emitWebEvent(JSON.stringify({
+ type: CLARA_IO_STATUS,
+ status: statusMessage
+ }));
+ } else if (zipFileURL.slice(-4) !== ".zip") {
+ statusMessage = "Error creating zip file for download.";
+ console.log("ERROR: Clara.io FBX: " + statusMessage + ": " + zipFileURL);
+ EventBridge.emitWebEvent(JSON.stringify({
+ type: CLARA_IO_STATUS,
+ status: (statusMessage + ": " + zipFileURL)
+ }));
+ } else {
+ EventBridge.emitWebEvent(JSON.stringify({
+ type: CLARA_IO_DOWNLOAD
+ }));
+ console.log("Clara.io FBX: File download initiated for " + zipFileURL);
+ }
+
+ xmlHttpRequest = null;
}
+
+ isPreparing = true;
+
+ console.log("Clara.io FBX: Request zip file for " + uuid);
+ EventBridge.emitWebEvent(JSON.stringify({
+ type: CLARA_IO_STATUS,
+ status: "Initiating download"
+ }));
+
+ xmlHttpRequest.open("POST", url, true);
+ xmlHttpRequest.setRequestHeader("Accept", "text/event-stream");
+ xmlHttpRequest.send();
}
function injectClaraCode() {
@@ -663,7 +679,9 @@
updateClaraCodeInterval = undefined;
});
- EventBridge.emitWebEvent(QUERY_CAN_WRITE_ASSETS);
+ EventBridge.emitWebEvent(JSON.stringify({
+ type: QUERY_CAN_WRITE_ASSETS
+ }));
}
function cancelClaraDownload() {
@@ -673,7 +691,9 @@
xmlHttpRequest.abort();
xmlHttpRequest = null;
console.log("Clara.io FBX: File download cancelled");
- EventBridge.emitWebEvent(CLARA_IO_CANCELLED_DOWNLOAD);
+ EventBridge.emitWebEvent(JSON.stringify({
+ type: CLARA_IO_CANCELLED_DOWNLOAD
+ }));
}
}
@@ -708,25 +728,22 @@
function onLoad() {
EventBridge.scriptEventReceived.connect(function (message) {
- if (message.slice(0, CAN_WRITE_ASSETS.length) === CAN_WRITE_ASSETS) {
- canWriteAssets = message.slice(-4) === "true";
- } else if (message.slice(0, CLARA_IO_CANCEL_DOWNLOAD.length) === CLARA_IO_CANCEL_DOWNLOAD) {
+ message = JSON.parse(message);
+ if (message.type === CAN_WRITE_ASSETS) {
+ canWriteAssets = message.canWriteAssets;
+ } else if (message.type === CLARA_IO_CANCEL_DOWNLOAD) {
cancelClaraDownload();
- } else {
- var parsedJsonMessage = JSON.parse(message);
-
- if (parsedJsonMessage.type === "marketplaces") {
- if (parsedJsonMessage.action === "commerceSetting") {
- commerceMode = !!parsedJsonMessage.data.commerceMode;
- userIsLoggedIn = !!parsedJsonMessage.data.userIsLoggedIn;
- walletNeedsSetup = !!parsedJsonMessage.data.walletNeedsSetup;
- marketplaceBaseURL = parsedJsonMessage.data.metaverseServerURL;
- if (marketplaceBaseURL.indexOf('metaverse.') !== -1) {
- marketplaceBaseURL = marketplaceBaseURL.replace('metaverse.', '');
- }
- messagesWaiting = parsedJsonMessage.data.messagesWaiting;
- injectCode();
+ } else if (message.type === "marketplaces") {
+ if (message.action === "commerceSetting") {
+ commerceMode = !!message.data.commerceMode;
+ userIsLoggedIn = !!message.data.userIsLoggedIn;
+ walletNeedsSetup = !!message.data.walletNeedsSetup;
+ marketplaceBaseURL = message.data.metaverseServerURL;
+ if (marketplaceBaseURL.indexOf('metaverse.') !== -1) {
+ marketplaceBaseURL = marketplaceBaseURL.replace('metaverse.', '');
}
+ messagesWaiting = message.data.messagesWaiting;
+ injectCode();
}
}
});
@@ -739,6 +756,6 @@
}
// Load / unload.
- window.addEventListener("load", onLoad); // More robust to Web site issues than using $(document).ready().
- window.addEventListener("page:change", onLoad); // Triggered after Marketplace HTML is changed
+ window.addEventListener("load", onLoad); // More robust to Web site issues than using $(document).ready().
+ window.addEventListener("page:change", onLoad); // Triggered after Marketplace HTML is changed
}());
diff --git a/scripts/system/marketplaces/marketplaces.js b/scripts/system/marketplaces/marketplaces.js
index 7b4f05193f..13ad1f6b69 100644
--- a/scripts/system/marketplaces/marketplaces.js
+++ b/scripts/system/marketplaces/marketplaces.js
@@ -16,1147 +16,1082 @@
var selectionDisplay = null; // for gridTool.js to ignore
(function () { // BEGIN LOCAL_SCOPE
+var AppUi = Script.require('appUi');
+Script.include("/~/system/libraries/gridTool.js");
+Script.include("/~/system/libraries/connectionUtils.js");
- Script.include("/~/system/libraries/WebTablet.js");
- Script.include("/~/system/libraries/gridTool.js");
- Script.include("/~/system/libraries/connectionUtils.js");
+var METAVERSE_SERVER_URL = Account.metaverseServerURL;
+var MARKETPLACES_URL = Script.resolvePath("../html/marketplaces.html");
+var MARKETPLACES_INJECT_SCRIPT_URL = Script.resolvePath("../html/js/marketplacesInject.js");
+var MARKETPLACE_CHECKOUT_QML_PATH = "hifi/commerce/checkout/Checkout.qml";
+var MARKETPLACE_PURCHASES_QML_PATH = "hifi/commerce/purchases/Purchases.qml";
+var MARKETPLACE_WALLET_QML_PATH = "hifi/commerce/wallet/Wallet.qml";
+var MARKETPLACE_INSPECTIONCERTIFICATE_QML_PATH = "hifi/commerce/inspectionCertificate/InspectionCertificate.qml";
+var REZZING_SOUND = SoundCache.getSound(Script.resolvePath("../assets/sounds/rezzing.wav"));
- var METAVERSE_SERVER_URL = Account.metaverseServerURL;
- var MARKETPLACE_URL = METAVERSE_SERVER_URL + "/marketplace";
- var MARKETPLACE_URL_INITIAL = MARKETPLACE_URL + "?"; // Append "?" to signal injected script that it's the initial page.
- var MARKETPLACES_URL = Script.resolvePath("../html/marketplaces.html");
- var MARKETPLACES_INJECT_SCRIPT_URL = Script.resolvePath("../html/js/marketplacesInject.js");
- var MARKETPLACE_CHECKOUT_QML_PATH = "hifi/commerce/checkout/Checkout.qml";
- var MARKETPLACE_PURCHASES_QML_PATH = "hifi/commerce/purchases/Purchases.qml";
- var MARKETPLACE_WALLET_QML_PATH = "hifi/commerce/wallet/Wallet.qml";
- var MARKETPLACE_INSPECTIONCERTIFICATE_QML_PATH = "commerce/inspectionCertificate/InspectionCertificate.qml";
- var REZZING_SOUND = SoundCache.getSound(Script.resolvePath("../assets/sounds/rezzing.wav"));
+// Event bridge messages.
+var CLARA_IO_DOWNLOAD = "CLARA.IO DOWNLOAD";
+var CLARA_IO_STATUS = "CLARA.IO STATUS";
+var CLARA_IO_CANCEL_DOWNLOAD = "CLARA.IO CANCEL DOWNLOAD";
+var CLARA_IO_CANCELLED_DOWNLOAD = "CLARA.IO CANCELLED DOWNLOAD";
+var GOTO_DIRECTORY = "GOTO_DIRECTORY";
+var QUERY_CAN_WRITE_ASSETS = "QUERY_CAN_WRITE_ASSETS";
+var CAN_WRITE_ASSETS = "CAN_WRITE_ASSETS";
+var WARN_USER_NO_PERMISSIONS = "WARN_USER_NO_PERMISSIONS";
- var HOME_BUTTON_TEXTURE = "http://hifi-content.s3.amazonaws.com/alan/dev/tablet-with-home-button.fbx/tablet-with-home-button.fbm/button-root.png";
- // var HOME_BUTTON_TEXTURE = Script.resourcesPath() + "meshes/tablet-with-home-button.fbx/tablet-with-home-button.fbm/button-root.png";
+var CLARA_DOWNLOAD_TITLE = "Preparing Download";
+var messageBox = null;
+var isDownloadBeingCancelled = false;
- // Event bridge messages.
- var CLARA_IO_DOWNLOAD = "CLARA.IO DOWNLOAD";
- var CLARA_IO_STATUS = "CLARA.IO STATUS";
- var CLARA_IO_CANCEL_DOWNLOAD = "CLARA.IO CANCEL DOWNLOAD";
- var CLARA_IO_CANCELLED_DOWNLOAD = "CLARA.IO CANCELLED DOWNLOAD";
- var GOTO_DIRECTORY = "GOTO_DIRECTORY";
- var QUERY_CAN_WRITE_ASSETS = "QUERY_CAN_WRITE_ASSETS";
- var CAN_WRITE_ASSETS = "CAN_WRITE_ASSETS";
- var WARN_USER_NO_PERMISSIONS = "WARN_USER_NO_PERMISSIONS";
+var CANCEL_BUTTON = 4194304; // QMessageBox::Cancel
+var NO_BUTTON = 0; // QMessageBox::NoButton
- var CLARA_DOWNLOAD_TITLE = "Preparing Download";
- var messageBox = null;
- var isDownloadBeingCancelled = false;
+var NO_PERMISSIONS_ERROR_MESSAGE = "Cannot download model because you can't write to \nthe domain's Asset Server.";
- var CANCEL_BUTTON = 4194304; // QMessageBox::Cancel
- var NO_BUTTON = 0; // QMessageBox::NoButton
-
- var NO_PERMISSIONS_ERROR_MESSAGE = "Cannot download model because you can't write to \nthe domain's Asset Server.";
-
- function onMessageBoxClosed(id, button) {
- if (id === messageBox && button === CANCEL_BUTTON) {
- isDownloadBeingCancelled = true;
- messageBox = null;
- tablet.emitScriptEvent(CLARA_IO_CANCEL_DOWNLOAD);
- }
- }
-
- var onMarketplaceScreen = false;
- var onCommerceScreen = false;
-
- var debugCheckout = false;
- var debugError = false;
- function showMarketplace() {
- if (!debugCheckout) {
- UserActivityLogger.openedMarketplace();
- tablet.gotoWebScreen(MARKETPLACE_URL_INITIAL, MARKETPLACES_INJECT_SCRIPT_URL);
- } else {
- tablet.pushOntoStack(MARKETPLACE_CHECKOUT_QML_PATH);
- sendToQml({
- method: 'updateCheckoutQML', params: {
- itemId: '424611a2-73d0-4c03-9087-26a6a279257b',
- itemName: '2018-02-15 Finnegon',
- itemPrice: (debugError ? 10 : 3),
- itemHref: 'http://devmpassets.highfidelity.com/424611a2-73d0-4c03-9087-26a6a279257b-v1/finnigon.fst',
- categories: ["Miscellaneous"]
- }
- });
- }
- }
-
- function messagesWaiting(isWaiting) {
- if (marketplaceButton) {
- marketplaceButton.editProperties({
- icon: (isWaiting ? WAITING_ICON : NORMAL_ICON),
- activeIcon: (isWaiting ? WAITING_ACTIVE : NORMAL_ACTIVE)
- });
- }
- }
-
- function onCanWriteAssetsChanged() {
- var message = CAN_WRITE_ASSETS + " " + Entities.canWriteAssets();
- tablet.emitScriptEvent(message);
- }
-
-
- var tabletShouldBeVisibleInSecondaryCamera = false;
- function setTabletVisibleInSecondaryCamera(visibleInSecondaryCam) {
- if (visibleInSecondaryCam) {
- // if we're potentially showing the tablet, only do so if it was visible before
- if (!tabletShouldBeVisibleInSecondaryCamera) {
- return;
- }
- } else {
- // if we're hiding the tablet, check to see if it was visible in the first place
- tabletShouldBeVisibleInSecondaryCamera = Overlays.getProperty(HMD.tabletID, "isVisibleInSecondaryCamera");
- }
-
- Overlays.editOverlay(HMD.tabletID, { isVisibleInSecondaryCamera : visibleInSecondaryCam });
- Overlays.editOverlay(HMD.homeButtonID, { isVisibleInSecondaryCamera : visibleInSecondaryCam });
- Overlays.editOverlay(HMD.homeButtonHighlightID, { isVisibleInSecondaryCamera : visibleInSecondaryCam });
- Overlays.editOverlay(HMD.tabletScreenID, { isVisibleInSecondaryCamera : visibleInSecondaryCam });
- }
-
- function openWallet() {
- tablet.pushOntoStack(MARKETPLACE_WALLET_QML_PATH);
- }
-
- function setCertificateInfo(currentEntityWithContextOverlay, itemCertificateId) {
- wireEventBridge(true);
- var certificateId = itemCertificateId || (Entities.getEntityProperties(currentEntityWithContextOverlay, ['certificateID']).certificateID);
- sendToQml({
- method: 'inspectionCertificate_setCertificateId',
- entityId: currentEntityWithContextOverlay,
- certificateId: certificateId
+function onMessageBoxClosed(id, button) {
+ if (id === messageBox && button === CANCEL_BUTTON) {
+ isDownloadBeingCancelled = true;
+ messageBox = null;
+ ui.sendToHtml({
+ type: CLARA_IO_CANCEL_DOWNLOAD
});
}
+}
- function onUsernameChanged() {
- if (onMarketplaceScreen) {
- tablet.gotoWebScreen(MARKETPLACE_URL_INITIAL, MARKETPLACES_INJECT_SCRIPT_URL);
- }
- }
+function onCanWriteAssetsChanged() {
+ ui.sendToHtml({
+ type: CAN_WRITE_ASSETS,
+ canWriteAssets: Entities.canWriteAssets()
+ });
+}
- var userHasUpdates = false;
- function sendCommerceSettings() {
- tablet.emitScriptEvent(JSON.stringify({
- type: "marketplaces",
- action: "commerceSetting",
- data: {
- commerceMode: Settings.getValue("commerce", true),
- userIsLoggedIn: Account.loggedIn,
- walletNeedsSetup: Wallet.walletStatus === 1,
- metaverseServerURL: Account.metaverseServerURL,
- messagesWaiting: userHasUpdates
- }
- }));
- }
- // BEGIN AVATAR SELECTOR LOGIC
- 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 };
-
- var overlays = {}; // Keeps track of all our extended overlay data objects, keyed by target identifier.
-
- function ExtendedOverlay(key, type, properties) { // A wrapper around overlays to store the key it is associated with.
- overlays[key] = this;
- this.key = key;
- this.selected = false;
- this.hovering = false;
- this.activeOverlay = Overlays.addOverlay(type, properties); // We could use different overlays for (un)selected...
- }
- // Instance methods:
- ExtendedOverlay.prototype.deleteOverlay = function () { // remove display and data of this overlay
- Overlays.deleteOverlay(this.activeOverlay);
- delete overlays[this.key];
- };
-
- ExtendedOverlay.prototype.editOverlay = function (properties) { // change display of this overlay
- Overlays.editOverlay(this.activeOverlay, properties);
- };
-
- function color(selected, hovering) {
- var base = hovering ? HOVER_COLOR : selected ? SELECTED_COLOR : UNSELECTED_COLOR;
- function scale(component) {
- var delta = 0xFF - component;
- return component;
- }
- return { red: scale(base.red), green: scale(base.green), blue: scale(base.blue) };
- }
- // so we don't have to traverse the overlays to get the last one
- var lastHoveringId = 0;
- ExtendedOverlay.prototype.hover = function (hovering) {
- this.hovering = hovering;
- if (this.key === lastHoveringId) {
- if (hovering) {
- return;
- }
- lastHoveringId = 0;
- }
- this.editOverlay({ color: color(this.selected, hovering) });
- if (hovering) {
- // un-hover the last hovering overlay
- if (lastHoveringId && lastHoveringId !== this.key) {
- ExtendedOverlay.get(lastHoveringId).hover(false);
- }
- lastHoveringId = this.key;
- }
- };
- ExtendedOverlay.prototype.select = function (selected) {
- if (this.selected === selected) {
+var tabletShouldBeVisibleInSecondaryCamera = false;
+function setTabletVisibleInSecondaryCamera(visibleInSecondaryCam) {
+ if (visibleInSecondaryCam) {
+ // if we're potentially showing the tablet, only do so if it was visible before
+ if (!tabletShouldBeVisibleInSecondaryCamera) {
return;
}
+ } else {
+ // if we're hiding the tablet, check to see if it was visible in the first place
+ tabletShouldBeVisibleInSecondaryCamera = Overlays.getProperty(HMD.tabletID, "isVisibleInSecondaryCamera");
+ }
- this.editOverlay({ color: color(selected, this.hovering) });
- this.selected = selected;
- };
- // Class methods:
- var selectedId = false;
- ExtendedOverlay.isSelected = function (id) {
- return selectedId === id;
- };
- ExtendedOverlay.get = function (key) { // answer the extended overlay data object associated with the given avatar identifier
- return overlays[key];
- };
- ExtendedOverlay.some = function (iterator) { // Bails early as soon as iterator returns truthy.
- var key;
- for (key in overlays) {
- if (iterator(ExtendedOverlay.get(key))) {
- return;
- }
+ Overlays.editOverlay(HMD.tabletID, { isVisibleInSecondaryCamera : visibleInSecondaryCam });
+ Overlays.editOverlay(HMD.homeButtonID, { isVisibleInSecondaryCamera : visibleInSecondaryCam });
+ Overlays.editOverlay(HMD.homeButtonHighlightID, { isVisibleInSecondaryCamera : visibleInSecondaryCam });
+ Overlays.editOverlay(HMD.tabletScreenID, { isVisibleInSecondaryCamera : visibleInSecondaryCam });
+}
+
+function openWallet() {
+ ui.open(MARKETPLACE_WALLET_QML_PATH);
+}
+
+// Function Name: wireQmlEventBridge()
+//
+// Description:
+// -Used to connect/disconnect the script's response to the tablet's "fromQml" signal. Set the "on" argument to enable or
+// disable to event bridge.
+//
+// Relevant Variables:
+// -hasEventBridge: true/false depending on whether we've already connected the event bridge.
+var hasEventBridge = false;
+function wireQmlEventBridge(on) {
+ if (!ui.tablet) {
+ print("Warning in wireQmlEventBridge(): 'tablet' undefined!");
+ return;
+ }
+ if (on) {
+ if (!hasEventBridge) {
+ ui.tablet.fromQml.connect(onQmlMessageReceived);
+ hasEventBridge = true;
}
- };
- ExtendedOverlay.unHover = function () { // calls hover(false) on lastHoveringId (if any)
- if (lastHoveringId) {
+ } else {
+ if (hasEventBridge) {
+ ui.tablet.fromQml.disconnect(onQmlMessageReceived);
+ hasEventBridge = false;
+ }
+ }
+}
+
+var contextOverlayEntity = "";
+function openInspectionCertificateQML(currentEntityWithContextOverlay) {
+ ui.open(MARKETPLACE_INSPECTIONCERTIFICATE_QML_PATH);
+ contextOverlayEntity = currentEntityWithContextOverlay;
+}
+
+function setCertificateInfo(currentEntityWithContextOverlay, itemCertificateId) {
+ var certificateId = itemCertificateId ||
+ (Entities.getEntityProperties(currentEntityWithContextOverlay, ['certificateID']).certificateID);
+ ui.tablet.sendToQml({
+ method: 'inspectionCertificate_setCertificateId',
+ entityId: currentEntityWithContextOverlay,
+ certificateId: certificateId
+ });
+}
+
+function onUsernameChanged() {
+ if (onMarketplaceScreen) {
+ ui.open(MARKETPLACE_URL_INITIAL, MARKETPLACES_INJECT_SCRIPT_URL);
+ }
+}
+
+var userHasUpdates = false;
+function sendCommerceSettings() {
+ ui.sendToHtml({
+ type: "marketplaces",
+ action: "commerceSetting",
+ data: {
+ commerceMode: Settings.getValue("commerce", true),
+ userIsLoggedIn: Account.loggedIn,
+ walletNeedsSetup: Wallet.walletStatus === 1,
+ metaverseServerURL: Account.metaverseServerURL,
+ messagesWaiting: userHasUpdates
+ }
+ });
+}
+
+// BEGIN AVATAR SELECTOR LOGIC
+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 };
+
+var overlays = {}; // Keeps track of all our extended overlay data objects, keyed by target identifier.
+
+function ExtendedOverlay(key, type, properties) { // A wrapper around overlays to store the key it is associated with.
+ overlays[key] = this;
+ this.key = key;
+ this.selected = false;
+ this.hovering = false;
+ this.activeOverlay = Overlays.addOverlay(type, properties); // We could use different overlays for (un)selected...
+}
+// Instance methods:
+ExtendedOverlay.prototype.deleteOverlay = function () { // remove display and data of this overlay
+ Overlays.deleteOverlay(this.activeOverlay);
+ delete overlays[this.key];
+};
+
+ExtendedOverlay.prototype.editOverlay = function (properties) { // change display of this overlay
+ Overlays.editOverlay(this.activeOverlay, properties);
+};
+
+function color(selected, hovering) {
+ var base = hovering ? HOVER_COLOR : selected ? SELECTED_COLOR : UNSELECTED_COLOR;
+ function scale(component) {
+ return component;
+ }
+ return { red: scale(base.red), green: scale(base.green), blue: scale(base.blue) };
+}
+// so we don't have to traverse the overlays to get the last one
+var lastHoveringId = 0;
+ExtendedOverlay.prototype.hover = function (hovering) {
+ this.hovering = hovering;
+ if (this.key === lastHoveringId) {
+ if (hovering) {
+ return;
+ }
+ lastHoveringId = 0;
+ }
+ this.editOverlay({ color: color(this.selected, hovering) });
+ if (hovering) {
+ // un-hover the last hovering overlay
+ if (lastHoveringId && lastHoveringId !== this.key) {
ExtendedOverlay.get(lastHoveringId).hover(false);
}
- };
+ lastHoveringId = this.key;
+ }
+};
+ExtendedOverlay.prototype.select = function (selected) {
+ if (this.selected === selected) {
+ return;
+ }
- // hit(overlay) on the one overlay intersected by pickRay, if any.
- // noHit() if no ExtendedOverlay was intersected (helps with hover)
- ExtendedOverlay.applyPickRay = function (pickRay, hit, noHit) {
- var pickedOverlay = Overlays.findRayIntersection(pickRay); // Depends on nearer coverOverlays to extend closer to us than farther ones.
- if (!pickedOverlay.intersects) {
- if (noHit) {
- return noHit();
- }
+ this.editOverlay({ color: color(selected, this.hovering) });
+ this.selected = selected;
+};
+// Class methods:
+var selectedId = false;
+ExtendedOverlay.isSelected = function (id) {
+ return selectedId === id;
+};
+ExtendedOverlay.get = function (key) { // answer the extended overlay data object associated with the given avatar identifier
+ return overlays[key];
+};
+ExtendedOverlay.some = function (iterator) { // Bails early as soon as iterator returns truthy.
+ var key;
+ for (key in overlays) {
+ if (iterator(ExtendedOverlay.get(key))) {
return;
}
- ExtendedOverlay.some(function (overlay) { // See if pickedOverlay is one of ours.
- if ((overlay.activeOverlay) === pickedOverlay.overlayID) {
- hit(overlay);
- return true;
- }
- });
- };
-
- function addAvatarNode(id) {
- return new ExtendedOverlay(id, "sphere", {
- drawInFront: true,
- solid: true,
- alpha: 0.8,
- color: color(false, false),
- ignoreRayIntersection: false
- });
}
-
- var pingPong = true;
- function updateOverlays() {
- var eye = Camera.position;
- AvatarList.getAvatarIdentifiers().forEach(function (id) {
- if (!id) {
- return; // don't update ourself, or avatars we're not interested in
- }
- var avatar = AvatarList.getAvatar(id);
- if (!avatar) {
- return; // will be deleted below if there had been an overlay.
- }
- var overlay = ExtendedOverlay.get(id);
- if (!overlay) { // For now, we're treating this as a temporary loss, as from the personal space bubble. Add it back.
- overlay = addAvatarNode(id);
- }
- var target = avatar.position;
- var distance = Vec3.distance(target, eye);
- var offset = 0.2;
- var diff = Vec3.subtract(target, eye); // get diff between target and eye (a vector pointing to the eye from avatar position)
- var headIndex = avatar.getJointIndex("Head"); // base offset on 1/2 distance from hips to head if we can
- if (headIndex > 0) {
- offset = avatar.getAbsoluteJointTranslationInObjectFrame(headIndex).y / 2;
- }
-
- // move a bit in front, towards the camera
- target = Vec3.subtract(target, Vec3.multiply(Vec3.normalize(diff), offset));
-
- // now bump it up a bit
- target.y = target.y + offset;
-
- overlay.ping = pingPong;
- overlay.editOverlay({
- color: color(ExtendedOverlay.isSelected(id), overlay.hovering),
- position: target,
- dimensions: 0.032 * distance
- });
- });
- pingPong = !pingPong;
- ExtendedOverlay.some(function (overlay) { // Remove any that weren't updated. (User is gone.)
- if (overlay.ping === pingPong) {
- overlay.deleteOverlay();
- }
- });
- }
- function removeOverlays() {
- selectedId = false;
- lastHoveringId = 0;
- ExtendedOverlay.some(function (overlay) {
- overlay.deleteOverlay();
- });
+};
+ExtendedOverlay.unHover = function () { // calls hover(false) on lastHoveringId (if any)
+ if (lastHoveringId) {
+ ExtendedOverlay.get(lastHoveringId).hover(false);
}
+};
- //
- // Clicks.
- //
- function usernameFromIDReply(id, username, machineFingerprint, isAdmin) {
- if (selectedId === id) {
- var message = {
- method: 'updateSelectedRecipientUsername',
- userName: username === "" ? "unknown username" : username
- };
- sendToQml(message);
+// hit(overlay) on the one overlay intersected by pickRay, if any.
+// noHit() if no ExtendedOverlay was intersected (helps with hover)
+ExtendedOverlay.applyPickRay = function (pickRay, hit, noHit) {
+ var pickedOverlay = Overlays.findRayIntersection(pickRay); // Depends on nearer coverOverlays to extend closer to us than farther ones.
+ if (!pickedOverlay.intersects) {
+ if (noHit) {
+ return noHit();
}
+ return;
}
- function handleClick(pickRay) {
- ExtendedOverlay.applyPickRay(pickRay, function (overlay) {
- var nextSelectedStatus = !overlay.selected;
- var avatarId = overlay.key;
- selectedId = nextSelectedStatus ? avatarId : false;
- if (nextSelectedStatus) {
- Users.requestUsernameFromID(avatarId);
- }
- var message = {
- method: 'selectRecipient',
- id: avatarId,
- isSelected: nextSelectedStatus,
- displayName: '"' + AvatarList.getAvatar(avatarId).sessionDisplayName + '"',
- userName: ''
- };
- sendToQml(message);
-
- ExtendedOverlay.some(function (overlay) {
- var id = overlay.key;
- var selected = ExtendedOverlay.isSelected(id);
- overlay.select(selected);
- });
-
+ ExtendedOverlay.some(function (overlay) { // See if pickedOverlay is one of ours.
+ if ((overlay.activeOverlay) === pickedOverlay.overlayID) {
+ hit(overlay);
return true;
- });
- }
- function handleMouseEvent(mousePressEvent) { // handleClick if we get one.
- if (!mousePressEvent.isLeftButton) {
- return;
}
- handleClick(Camera.computePickRay(mousePressEvent.x, mousePressEvent.y));
+ });
+};
+
+function addAvatarNode(id) {
+ return new ExtendedOverlay(id, "sphere", {
+ drawInFront: true,
+ solid: true,
+ alpha: 0.8,
+ color: color(false, false),
+ ignoreRayIntersection: false
+ });
+}
+
+var pingPong = true;
+function updateOverlays() {
+ var eye = Camera.position;
+ AvatarList.getAvatarIdentifiers().forEach(function (id) {
+ if (!id) {
+ return; // don't update ourself, or avatars we're not interested in
+ }
+ var avatar = AvatarList.getAvatar(id);
+ if (!avatar) {
+ return; // will be deleted below if there had been an overlay.
+ }
+ var overlay = ExtendedOverlay.get(id);
+ if (!overlay) { // For now, we're treating this as a temporary loss, as from the personal space bubble. Add it back.
+ overlay = addAvatarNode(id);
+ }
+ var target = avatar.position;
+ var distance = Vec3.distance(target, eye);
+ var offset = 0.2;
+ var diff = Vec3.subtract(target, eye); // get diff between target and eye (a vector pointing to the eye from avatar position)
+ var headIndex = avatar.getJointIndex("Head"); // base offset on 1/2 distance from hips to head if we can
+ if (headIndex > 0) {
+ offset = avatar.getAbsoluteJointTranslationInObjectFrame(headIndex).y / 2;
+ }
+
+ // move a bit in front, towards the camera
+ target = Vec3.subtract(target, Vec3.multiply(Vec3.normalize(diff), offset));
+
+ // now bump it up a bit
+ target.y = target.y + offset;
+
+ overlay.ping = pingPong;
+ overlay.editOverlay({
+ color: color(ExtendedOverlay.isSelected(id), overlay.hovering),
+ position: target,
+ dimensions: 0.032 * distance
+ });
+ });
+ pingPong = !pingPong;
+ ExtendedOverlay.some(function (overlay) { // Remove any that weren't updated. (User is gone.)
+ if (overlay.ping === pingPong) {
+ overlay.deleteOverlay();
+ }
+ });
+}
+function removeOverlays() {
+ selectedId = false;
+ lastHoveringId = 0;
+ ExtendedOverlay.some(function (overlay) {
+ overlay.deleteOverlay();
+ });
+}
+
+//
+// Clicks.
+//
+function usernameFromIDReply(id, username, machineFingerprint, isAdmin) {
+ if (selectedId === id) {
+ var message = {
+ method: 'updateSelectedRecipientUsername',
+ userName: username === "" ? "unknown username" : username
+ };
+ ui.tablet.sendToQml(message);
}
- function handleMouseMove(pickRay) { // given the pickRay, just do the hover logic
- ExtendedOverlay.applyPickRay(pickRay, function (overlay) {
- overlay.hover(true);
- }, function () {
+}
+function handleClick(pickRay) {
+ ExtendedOverlay.applyPickRay(pickRay, function (overlay) {
+ var nextSelectedStatus = !overlay.selected;
+ var avatarId = overlay.key;
+ selectedId = nextSelectedStatus ? avatarId : false;
+ if (nextSelectedStatus) {
+ Users.requestUsernameFromID(avatarId);
+ }
+ var message = {
+ method: 'selectRecipient',
+ id: avatarId,
+ isSelected: nextSelectedStatus,
+ displayName: '"' + AvatarList.getAvatar(avatarId).sessionDisplayName + '"',
+ userName: ''
+ };
+ ui.tablet.sendToQml(message);
+
+ ExtendedOverlay.some(function (overlay) {
+ var id = overlay.key;
+ var selected = ExtendedOverlay.isSelected(id);
+ overlay.select(selected);
+ });
+
+ return true;
+ });
+}
+function handleMouseEvent(mousePressEvent) { // handleClick if we get one.
+ if (!mousePressEvent.isLeftButton) {
+ return;
+ }
+ handleClick(Camera.computePickRay(mousePressEvent.x, mousePressEvent.y));
+}
+function handleMouseMove(pickRay) { // given the pickRay, just do the hover logic
+ ExtendedOverlay.applyPickRay(pickRay, function (overlay) {
+ overlay.hover(true);
+ }, function () {
+ ExtendedOverlay.unHover();
+ });
+}
+
+// handy global to keep track of which hand is the mouse (if any)
+var currentHandPressed = 0;
+var TRIGGER_CLICK_THRESHOLD = 0.85;
+var TRIGGER_PRESS_THRESHOLD = 0.05;
+
+function handleMouseMoveEvent(event) { // find out which overlay (if any) is over the mouse position
+ var pickRay;
+ if (HMD.active) {
+ if (currentHandPressed !== 0) {
+ pickRay = controllerComputePickRay(currentHandPressed);
+ } else {
+ // nothing should hover, so
ExtendedOverlay.unHover();
- });
- }
-
- // handy global to keep track of which hand is the mouse (if any)
- var currentHandPressed = 0;
- var TRIGGER_CLICK_THRESHOLD = 0.85;
- var TRIGGER_PRESS_THRESHOLD = 0.05;
-
- function handleMouseMoveEvent(event) { // find out which overlay (if any) is over the mouse position
- var pickRay;
- if (HMD.active) {
- if (currentHandPressed !== 0) {
- pickRay = controllerComputePickRay(currentHandPressed);
- } else {
- // nothing should hover, so
- ExtendedOverlay.unHover();
- return;
- }
- } else {
- pickRay = Camera.computePickRay(event.x, event.y);
- }
- handleMouseMove(pickRay);
- }
- function handleTriggerPressed(hand, value) {
- // The idea is if you press one trigger, it is the one
- // we will consider the mouse. Even if the other is pressed,
- // we ignore it until this one is no longer pressed.
- var isPressed = value > TRIGGER_PRESS_THRESHOLD;
- if (currentHandPressed === 0) {
- currentHandPressed = isPressed ? hand : 0;
return;
}
- if (currentHandPressed === hand) {
- currentHandPressed = isPressed ? hand : 0;
- return;
- }
- // otherwise, the other hand is still triggered
- // so do nothing.
+ } else {
+ pickRay = Camera.computePickRay(event.x, event.y);
}
-
- // We get mouseMoveEvents from the handControllers, via handControllerPointer.
- // But we don't get mousePressEvents.
- var triggerMapping = Controller.newMapping(Script.resolvePath('') + '-click');
- var triggerPressMapping = Controller.newMapping(Script.resolvePath('') + '-press');
- function controllerComputePickRay(hand) {
- var controllerPose = getControllerWorldLocation(hand, true);
- if (controllerPose.valid) {
- return { origin: controllerPose.position, direction: Quat.getUp(controllerPose.orientation) };
- }
+ handleMouseMove(pickRay);
+}
+function handleTriggerPressed(hand, value) {
+ // The idea is if you press one trigger, it is the one
+ // we will consider the mouse. Even if the other is pressed,
+ // we ignore it until this one is no longer pressed.
+ var isPressed = value > TRIGGER_PRESS_THRESHOLD;
+ if (currentHandPressed === 0) {
+ currentHandPressed = isPressed ? hand : 0;
+ return;
}
- function makeClickHandler(hand) {
- return function (clicked) {
- if (clicked > TRIGGER_CLICK_THRESHOLD) {
- var pickRay = controllerComputePickRay(hand);
- handleClick(pickRay);
- }
- };
+ if (currentHandPressed === hand) {
+ currentHandPressed = isPressed ? hand : 0;
+ return;
}
- function makePressHandler(hand) {
- return function (value) {
- handleTriggerPressed(hand, value);
- };
+ // otherwise, the other hand is still triggered
+ // so do nothing.
+}
+
+// We get mouseMoveEvents from the handControllers, via handControllerPointer.
+// But we don't get mousePressEvents.
+var triggerMapping = Controller.newMapping(Script.resolvePath('') + '-click');
+var triggerPressMapping = Controller.newMapping(Script.resolvePath('') + '-press');
+function controllerComputePickRay(hand) {
+ var controllerPose = getControllerWorldLocation(hand, true);
+ if (controllerPose.valid) {
+ return { origin: controllerPose.position, direction: Quat.getUp(controllerPose.orientation) };
}
- triggerMapping.from(Controller.Standard.RTClick).peek().to(makeClickHandler(Controller.Standard.RightHand));
- triggerMapping.from(Controller.Standard.LTClick).peek().to(makeClickHandler(Controller.Standard.LeftHand));
- triggerPressMapping.from(Controller.Standard.RT).peek().to(makePressHandler(Controller.Standard.RightHand));
- triggerPressMapping.from(Controller.Standard.LT).peek().to(makePressHandler(Controller.Standard.LeftHand));
- // END AVATAR SELECTOR LOGIC
-
- var grid = new Grid();
- function adjustPositionPerBoundingBox(position, direction, registration, dimensions, orientation) {
- // Adjust the position such that the bounding box (registration, dimenions, and orientation) lies behind the original
- // position in the given direction.
- var CORNERS = [
- { x: 0, y: 0, z: 0 },
- { x: 0, y: 0, z: 1 },
- { x: 0, y: 1, z: 0 },
- { x: 0, y: 1, z: 1 },
- { x: 1, y: 0, z: 0 },
- { x: 1, y: 0, z: 1 },
- { x: 1, y: 1, z: 0 },
- { x: 1, y: 1, z: 1 },
- ];
-
- // Go through all corners and find least (most negative) distance in front of position.
- var distance = 0;
- for (var i = 0, length = CORNERS.length; i < length; i++) {
- var cornerVector =
- Vec3.multiplyQbyV(orientation, Vec3.multiplyVbyV(Vec3.subtract(CORNERS[i], registration), dimensions));
- var cornerDistance = Vec3.dot(cornerVector, direction);
- distance = Math.min(cornerDistance, distance);
+}
+function makeClickHandler(hand) {
+ return function (clicked) {
+ if (clicked > TRIGGER_CLICK_THRESHOLD) {
+ var pickRay = controllerComputePickRay(hand);
+ handleClick(pickRay);
}
- position = Vec3.sum(Vec3.multiply(distance, direction), position);
- return position;
- }
-
- var HALF_TREE_SCALE = 16384;
- function getPositionToCreateEntity(extra) {
- var CREATE_DISTANCE = 2;
- var position;
- var delta = extra !== undefined ? extra : 0;
- if (Camera.mode === "entity" || Camera.mode === "independent") {
- position = Vec3.sum(Camera.position, Vec3.multiply(Quat.getForward(Camera.orientation), CREATE_DISTANCE + delta));
- } else {
- position = Vec3.sum(MyAvatar.position, Vec3.multiply(Quat.getForward(MyAvatar.orientation), CREATE_DISTANCE + delta));
- position.y += 0.5;
- }
-
- if (position.x > HALF_TREE_SCALE || position.y > HALF_TREE_SCALE || position.z > HALF_TREE_SCALE) {
- return null;
- }
- return position;
- }
-
- function rezEntity(itemHref, itemType) {
- var isWearable = itemType === "wearable";
- var success = Clipboard.importEntities(itemHref);
- var wearableLocalPosition = null;
- var wearableLocalRotation = null;
- var wearableLocalDimensions = null;
- var wearableDimensions = null;
-
- if (itemType === "contentSet") {
- console.log("Item is a content set; codepath shouldn't go here.");
- return;
- }
-
- if (isWearable) {
- var wearableTransforms = Settings.getValue("io.highfidelity.avatarStore.checkOut.transforms");
- if (!wearableTransforms) {
- // TODO delete this clause
- wearableTransforms = Settings.getValue("io.highfidelity.avatarStore.checkOut.tranforms");
- }
- var certPos = itemHref.search("certificate_id="); // TODO how do I parse a URL from here?
- if (certPos >= 0) {
- certPos += 15; // length of "certificate_id="
- var certURLEncoded = itemHref.substring(certPos);
- var certB64Encoded = decodeURIComponent(certURLEncoded);
- for (var key in wearableTransforms) {
- if (wearableTransforms.hasOwnProperty(key)) {
- var certificateTransforms = wearableTransforms[key].certificateTransforms;
- if (certificateTransforms) {
- for (var certID in certificateTransforms) {
- if (certificateTransforms.hasOwnProperty(certID) &&
- certID == certB64Encoded) {
- var certificateTransform = certificateTransforms[certID];
- wearableLocalPosition = certificateTransform.localPosition;
- wearableLocalRotation = certificateTransform.localRotation;
- wearableLocalDimensions = certificateTransform.localDimensions;
- wearableDimensions = certificateTransform.dimensions;
- }
- }
- }
- }
- }
- }
- }
-
- if (success) {
- var VERY_LARGE = 10000;
- var isLargeImport = Clipboard.getClipboardContentsLargestDimension() >= VERY_LARGE;
- var position = Vec3.ZERO;
- if (!isLargeImport) {
- position = getPositionToCreateEntity(Clipboard.getClipboardContentsLargestDimension() / 2);
- }
- if (position !== null && position !== undefined) {
- var pastedEntityIDs = Clipboard.pasteEntities(position);
- if (!isLargeImport) {
- // The first entity in Clipboard gets the specified position with the rest being relative to it. Therefore, move
- // entities after they're imported so that they're all the correct distance in front of and with geometric mean
- // centered on the avatar/camera direction.
- var deltaPosition = Vec3.ZERO;
- var entityPositions = [];
- var entityParentIDs = [];
-
- var propType = Entities.getEntityProperties(pastedEntityIDs[0], ["type"]).type;
- var NO_ADJUST_ENTITY_TYPES = ["Zone", "Light", "ParticleEffect"];
- if (NO_ADJUST_ENTITY_TYPES.indexOf(propType) === -1) {
- var targetDirection;
- if (Camera.mode === "entity" || Camera.mode === "independent") {
- targetDirection = Camera.orientation;
- } else {
- targetDirection = MyAvatar.orientation;
- }
- targetDirection = Vec3.multiplyQbyV(targetDirection, Vec3.UNIT_Z);
-
- var targetPosition = getPositionToCreateEntity();
- var deltaParallel = HALF_TREE_SCALE; // Distance to move entities parallel to targetDirection.
- var deltaPerpendicular = Vec3.ZERO; // Distance to move entities perpendicular to targetDirection.
- for (var i = 0, length = pastedEntityIDs.length; i < length; i++) {
- var curLoopEntityProps = Entities.getEntityProperties(pastedEntityIDs[i], ["position", "dimensions",
- "registrationPoint", "rotation", "parentID"]);
- var adjustedPosition = adjustPositionPerBoundingBox(targetPosition, targetDirection,
- curLoopEntityProps.registrationPoint, curLoopEntityProps.dimensions, curLoopEntityProps.rotation);
- var delta = Vec3.subtract(adjustedPosition, curLoopEntityProps.position);
- var distance = Vec3.dot(delta, targetDirection);
- deltaParallel = Math.min(distance, deltaParallel);
- deltaPerpendicular = Vec3.sum(Vec3.subtract(delta, Vec3.multiply(distance, targetDirection)),
- deltaPerpendicular);
- entityPositions[i] = curLoopEntityProps.position;
- entityParentIDs[i] = curLoopEntityProps.parentID;
- }
- deltaPerpendicular = Vec3.multiply(1 / pastedEntityIDs.length, deltaPerpendicular);
- deltaPosition = Vec3.sum(Vec3.multiply(deltaParallel, targetDirection), deltaPerpendicular);
- }
-
- if (grid.getSnapToGrid()) {
- var firstEntityProps = Entities.getEntityProperties(pastedEntityIDs[0], ["position", "dimensions",
- "registrationPoint"]);
- var positionPreSnap = Vec3.sum(deltaPosition, firstEntityProps.position);
- position = grid.snapToSurface(grid.snapToGrid(positionPreSnap, false, firstEntityProps.dimensions,
- firstEntityProps.registrationPoint), firstEntityProps.dimensions, firstEntityProps.registrationPoint);
- deltaPosition = Vec3.subtract(position, firstEntityProps.position);
- }
-
- if (!Vec3.equal(deltaPosition, Vec3.ZERO)) {
- for (var editEntityIndex = 0, numEntities = pastedEntityIDs.length; editEntityIndex < numEntities; editEntityIndex++) {
- if (Uuid.isNull(entityParentIDs[editEntityIndex])) {
- Entities.editEntity(pastedEntityIDs[editEntityIndex], {
- position: Vec3.sum(deltaPosition, entityPositions[editEntityIndex])
- });
- }
- }
- }
- }
-
- if (isWearable) {
- // apply the relative offsets saved during checkout
- var offsets = {};
- if (wearableLocalPosition) {
- offsets.localPosition = wearableLocalPosition;
- }
- if (wearableLocalRotation) {
- offsets.localRotation = wearableLocalRotation;
- }
- if (wearableLocalDimensions) {
- offsets.localDimensions = wearableLocalDimensions;
- } else if (wearableDimensions) {
- offsets.dimensions = wearableDimensions;
- }
- // we currently assume a wearable is a single entity
- Entities.editEntity(pastedEntityIDs[0], offsets);
- }
-
- var rezPosition = Entities.getEntityProperties(pastedEntityIDs[0], "position").position;
-
- Audio.playSound(REZZING_SOUND, {
- volume: 1.0,
- position: rezPosition,
- localOnly: true
- });
-
- } else {
- Window.notifyEditError("Can't import entities: entities would be out of bounds.");
- }
- } else {
- Window.notifyEditError("There was an error importing the entity file.");
- }
- }
-
- var referrerURL; // Used for updating Purchases QML
- var filterText; // Used for updating Purchases QML
- function onMessage(message) {
- if (message === GOTO_DIRECTORY) {
- tablet.gotoWebScreen(MARKETPLACES_URL, MARKETPLACES_INJECT_SCRIPT_URL);
- } else if (message === QUERY_CAN_WRITE_ASSETS) {
- tablet.emitScriptEvent(CAN_WRITE_ASSETS + " " + Entities.canWriteAssets());
- } else if (message === WARN_USER_NO_PERMISSIONS) {
- Window.alert(NO_PERMISSIONS_ERROR_MESSAGE);
- } else if (message.slice(0, CLARA_IO_STATUS.length) === CLARA_IO_STATUS) {
- if (isDownloadBeingCancelled) {
- return;
- }
-
- var text = message.slice(CLARA_IO_STATUS.length);
- if (messageBox === null) {
- messageBox = Window.openMessageBox(CLARA_DOWNLOAD_TITLE, text, CANCEL_BUTTON, NO_BUTTON);
- } else {
- Window.updateMessageBox(messageBox, CLARA_DOWNLOAD_TITLE, text, CANCEL_BUTTON, NO_BUTTON);
- }
- return;
- } else if (message.slice(0, CLARA_IO_DOWNLOAD.length) === CLARA_IO_DOWNLOAD) {
- if (messageBox !== null) {
- Window.closeMessageBox(messageBox);
- messageBox = null;
- }
- return;
- } else if (message === CLARA_IO_CANCELLED_DOWNLOAD) {
- isDownloadBeingCancelled = false;
- } else {
- var parsedJsonMessage = JSON.parse(message);
- if (parsedJsonMessage.type === "CHECKOUT") {
- wireEventBridge(true);
- tablet.pushOntoStack(MARKETPLACE_CHECKOUT_QML_PATH);
- sendToQml({
- method: 'updateCheckoutQML',
- params: parsedJsonMessage
- });
- } else if (parsedJsonMessage.type === "REQUEST_SETTING") {
- sendCommerceSettings();
- } else if (parsedJsonMessage.type === "PURCHASES") {
- referrerURL = parsedJsonMessage.referrerURL;
- filterText = "";
- tablet.pushOntoStack(MARKETPLACE_PURCHASES_QML_PATH);
- } else if (parsedJsonMessage.type === "LOGIN") {
- openLoginWindow();
- } else if (parsedJsonMessage.type === "WALLET_SETUP") {
- wireEventBridge(true);
- sendToQml({
- method: 'updateWalletReferrer',
- referrer: "marketplace cta"
- });
- openWallet();
- } else if (parsedJsonMessage.type === "MY_ITEMS") {
- referrerURL = MARKETPLACE_URL_INITIAL;
- filterText = "";
- tablet.pushOntoStack(MARKETPLACE_PURCHASES_QML_PATH);
- wireEventBridge(true);
- sendToQml({
- method: 'purchases_showMyItems'
- });
- }
- }
- }
-
- function onButtonClicked() {
- if (!tablet) {
- print("Warning in buttonClicked(): 'tablet' undefined!");
- return;
- }
- if (onMarketplaceScreen || onCommerceScreen) {
- // for toolbar-mode: go back to home screen, this will close the window.
- tablet.gotoHomeScreen();
- } else {
- if (HMD.tabletID) {
- Entities.editEntity(HMD.tabletID, { textures: JSON.stringify({ "tex.close": HOME_BUTTON_TEXTURE }) });
- }
- showMarketplace();
- }
- }
-
- // Function Name: sendToQml()
- //
- // Description:
- // -Use this function to send a message to the QML (i.e. to change appearances). The "message" argument is what is sent to
- // the QML in the format "{method, params}", like json-rpc. See also fromQml().
- function sendToQml(message) {
- tablet.sendToQml(message);
- }
-
- var sendAssetRecipient;
- var sendAssetParticleEffectUpdateTimer;
- var particleEffectTimestamp;
- var sendAssetParticleEffect;
- var SEND_ASSET_PARTICLE_TIMER_UPDATE = 250;
- var SEND_ASSET_PARTICLE_EMITTING_DURATION = 3000;
- var SEND_ASSET_PARTICLE_LIFETIME_SECONDS = 8;
- var SEND_ASSET_PARTICLE_PROPERTIES = {
- accelerationSpread: { x: 0, y: 0, z: 0 },
- alpha: 1,
- alphaFinish: 1,
- alphaSpread: 0,
- alphaStart: 1,
- azimuthFinish: 0,
- azimuthStart: -6,
- color: { red: 255, green: 222, blue: 255 },
- colorFinish: { red: 255, green: 229, blue: 225 },
- colorSpread: { red: 0, green: 0, blue: 0 },
- colorStart: { red: 243, green: 255, blue: 255 },
- emitAcceleration: { x: 0, y: 0, z: 0 }, // Immediately gets updated to be accurate
- emitDimensions: { x: 0, y: 0, z: 0 },
- emitOrientation: { x: 0, y: 0, z: 0 },
- emitRate: 4,
- emitSpeed: 2.1,
- emitterShouldTrail: true,
- isEmitting: 1,
- lifespan: SEND_ASSET_PARTICLE_LIFETIME_SECONDS + 1, // Immediately gets updated to be accurate
- lifetime: SEND_ASSET_PARTICLE_LIFETIME_SECONDS + 1,
- maxParticles: 20,
- name: 'asset-particles',
- particleRadius: 0.2,
- polarFinish: 0,
- polarStart: 0,
- radiusFinish: 0.05,
- radiusSpread: 0,
- radiusStart: 0.2,
- speedSpread: 0,
- textures: "http://hifi-content.s3.amazonaws.com/alan/dev/Particles/Bokeh-Particle-HFC.png",
- type: 'ParticleEffect'
};
+}
+function makePressHandler(hand) {
+ return function (value) {
+ handleTriggerPressed(hand, value);
+ };
+}
+triggerMapping.from(Controller.Standard.RTClick).peek().to(makeClickHandler(Controller.Standard.RightHand));
+triggerMapping.from(Controller.Standard.LTClick).peek().to(makeClickHandler(Controller.Standard.LeftHand));
+triggerPressMapping.from(Controller.Standard.RT).peek().to(makePressHandler(Controller.Standard.RightHand));
+triggerPressMapping.from(Controller.Standard.LT).peek().to(makePressHandler(Controller.Standard.LeftHand));
+// END AVATAR SELECTOR LOGIC
- function updateSendAssetParticleEffect() {
- var timestampNow = Date.now();
- if ((timestampNow - particleEffectTimestamp) > (SEND_ASSET_PARTICLE_LIFETIME_SECONDS * 1000)) {
- deleteSendAssetParticleEffect();
- return;
- } else if ((timestampNow - particleEffectTimestamp) > SEND_ASSET_PARTICLE_EMITTING_DURATION) {
- Entities.editEntity(sendAssetParticleEffect, {
- isEmitting: 0
- });
- } else if (sendAssetParticleEffect) {
- var recipientPosition = AvatarList.getAvatar(sendAssetRecipient).position;
- var distance = Vec3.distance(recipientPosition, MyAvatar.position);
- var accel = Vec3.subtract(recipientPosition, MyAvatar.position);
- accel.y -= 3.0;
- var life = Math.sqrt(2 * distance / Vec3.length(accel));
- Entities.editEntity(sendAssetParticleEffect, {
- emitAcceleration: accel,
- lifespan: life
+var grid = new Grid();
+function adjustPositionPerBoundingBox(position, direction, registration, dimensions, orientation) {
+ // Adjust the position such that the bounding box (registration, dimenions, and orientation) lies behind the original
+ // position in the given direction.
+ var CORNERS = [
+ { x: 0, y: 0, z: 0 },
+ { x: 0, y: 0, z: 1 },
+ { x: 0, y: 1, z: 0 },
+ { x: 0, y: 1, z: 1 },
+ { x: 1, y: 0, z: 0 },
+ { x: 1, y: 0, z: 1 },
+ { x: 1, y: 1, z: 0 },
+ { x: 1, y: 1, z: 1 }
+ ];
+
+ // Go through all corners and find least (most negative) distance in front of position.
+ var distance = 0;
+ for (var i = 0, length = CORNERS.length; i < length; i++) {
+ var cornerVector =
+ Vec3.multiplyQbyV(orientation, Vec3.multiplyVbyV(Vec3.subtract(CORNERS[i], registration), dimensions));
+ var cornerDistance = Vec3.dot(cornerVector, direction);
+ distance = Math.min(cornerDistance, distance);
+ }
+ position = Vec3.sum(Vec3.multiply(distance, direction), position);
+ return position;
+}
+
+var HALF_TREE_SCALE = 16384;
+function getPositionToCreateEntity(extra) {
+ var CREATE_DISTANCE = 2;
+ var position;
+ var delta = extra !== undefined ? extra : 0;
+ if (Camera.mode === "entity" || Camera.mode === "independent") {
+ position = Vec3.sum(Camera.position, Vec3.multiply(Quat.getForward(Camera.orientation), CREATE_DISTANCE + delta));
+ } else {
+ position = Vec3.sum(MyAvatar.position, Vec3.multiply(Quat.getForward(MyAvatar.orientation), CREATE_DISTANCE + delta));
+ position.y += 0.5;
+ }
+
+ if (position.x > HALF_TREE_SCALE || position.y > HALF_TREE_SCALE || position.z > HALF_TREE_SCALE) {
+ return null;
+ }
+ return position;
+}
+
+function rezEntity(itemHref, itemType) {
+ var isWearable = itemType === "wearable";
+ var success = Clipboard.importEntities(itemHref);
+ var wearableLocalPosition = null;
+ var wearableLocalRotation = null;
+ var wearableLocalDimensions = null;
+ var wearableDimensions = null;
+
+ if (itemType === "contentSet") {
+ console.log("Item is a content set; codepath shouldn't go here.");
+ return;
+ }
+
+ if (isWearable) {
+ var wearableTransforms = Settings.getValue("io.highfidelity.avatarStore.checkOut.transforms");
+ if (!wearableTransforms) {
+ // TODO delete this clause
+ wearableTransforms = Settings.getValue("io.highfidelity.avatarStore.checkOut.tranforms");
+ }
+ var certPos = itemHref.search("certificate_id="); // TODO how do I parse a URL from here?
+ if (certPos >= 0) {
+ certPos += 15; // length of "certificate_id="
+ var certURLEncoded = itemHref.substring(certPos);
+ var certB64Encoded = decodeURIComponent(certURLEncoded);
+ for (var key in wearableTransforms) {
+ if (wearableTransforms.hasOwnProperty(key)) {
+ var certificateTransforms = wearableTransforms[key].certificateTransforms;
+ if (certificateTransforms) {
+ for (var certID in certificateTransforms) {
+ if (certificateTransforms.hasOwnProperty(certID) &&
+ certID == certB64Encoded) {
+ var certificateTransform = certificateTransforms[certID];
+ wearableLocalPosition = certificateTransform.localPosition;
+ wearableLocalRotation = certificateTransform.localRotation;
+ wearableLocalDimensions = certificateTransform.localDimensions;
+ wearableDimensions = certificateTransform.dimensions;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ if (success) {
+ var VERY_LARGE = 10000;
+ var isLargeImport = Clipboard.getClipboardContentsLargestDimension() >= VERY_LARGE;
+ var position = Vec3.ZERO;
+ if (!isLargeImport) {
+ position = getPositionToCreateEntity(Clipboard.getClipboardContentsLargestDimension() / 2);
+ }
+ if (position !== null && position !== undefined) {
+ var pastedEntityIDs = Clipboard.pasteEntities(position);
+ if (!isLargeImport) {
+ // The first entity in Clipboard gets the specified position with the rest being relative to it. Therefore, move
+ // entities after they're imported so that they're all the correct distance in front of and with geometric mean
+ // centered on the avatar/camera direction.
+ var deltaPosition = Vec3.ZERO;
+ var entityPositions = [];
+ var entityParentIDs = [];
+
+ var propType = Entities.getEntityProperties(pastedEntityIDs[0], ["type"]).type;
+ var NO_ADJUST_ENTITY_TYPES = ["Zone", "Light", "ParticleEffect"];
+ if (NO_ADJUST_ENTITY_TYPES.indexOf(propType) === -1) {
+ var targetDirection;
+ if (Camera.mode === "entity" || Camera.mode === "independent") {
+ targetDirection = Camera.orientation;
+ } else {
+ targetDirection = MyAvatar.orientation;
+ }
+ targetDirection = Vec3.multiplyQbyV(targetDirection, Vec3.UNIT_Z);
+
+ var targetPosition = getPositionToCreateEntity();
+ var deltaParallel = HALF_TREE_SCALE; // Distance to move entities parallel to targetDirection.
+ var deltaPerpendicular = Vec3.ZERO; // Distance to move entities perpendicular to targetDirection.
+ for (var i = 0, length = pastedEntityIDs.length; i < length; i++) {
+ var curLoopEntityProps = Entities.getEntityProperties(pastedEntityIDs[i], ["position", "dimensions",
+ "registrationPoint", "rotation", "parentID"]);
+ var adjustedPosition = adjustPositionPerBoundingBox(targetPosition, targetDirection,
+ curLoopEntityProps.registrationPoint, curLoopEntityProps.dimensions, curLoopEntityProps.rotation);
+ var delta = Vec3.subtract(adjustedPosition, curLoopEntityProps.position);
+ var distance = Vec3.dot(delta, targetDirection);
+ deltaParallel = Math.min(distance, deltaParallel);
+ deltaPerpendicular = Vec3.sum(Vec3.subtract(delta, Vec3.multiply(distance, targetDirection)),
+ deltaPerpendicular);
+ entityPositions[i] = curLoopEntityProps.position;
+ entityParentIDs[i] = curLoopEntityProps.parentID;
+ }
+ deltaPerpendicular = Vec3.multiply(1 / pastedEntityIDs.length, deltaPerpendicular);
+ deltaPosition = Vec3.sum(Vec3.multiply(deltaParallel, targetDirection), deltaPerpendicular);
+ }
+
+ if (grid.getSnapToGrid()) {
+ var firstEntityProps = Entities.getEntityProperties(pastedEntityIDs[0], ["position", "dimensions",
+ "registrationPoint"]);
+ var positionPreSnap = Vec3.sum(deltaPosition, firstEntityProps.position);
+ position = grid.snapToSurface(grid.snapToGrid(positionPreSnap, false, firstEntityProps.dimensions,
+ firstEntityProps.registrationPoint), firstEntityProps.dimensions, firstEntityProps.registrationPoint);
+ deltaPosition = Vec3.subtract(position, firstEntityProps.position);
+ }
+
+ if (!Vec3.equal(deltaPosition, Vec3.ZERO)) {
+ for (var editEntityIndex = 0, numEntities = pastedEntityIDs.length; editEntityIndex < numEntities; editEntityIndex++) {
+ if (Uuid.isNull(entityParentIDs[editEntityIndex])) {
+ Entities.editEntity(pastedEntityIDs[editEntityIndex], {
+ position: Vec3.sum(deltaPosition, entityPositions[editEntityIndex])
+ });
+ }
+ }
+ }
+ }
+
+ if (isWearable) {
+ // apply the relative offsets saved during checkout
+ var offsets = {};
+ if (wearableLocalPosition) {
+ offsets.localPosition = wearableLocalPosition;
+ }
+ if (wearableLocalRotation) {
+ offsets.localRotation = wearableLocalRotation;
+ }
+ if (wearableLocalDimensions) {
+ offsets.localDimensions = wearableLocalDimensions;
+ } else if (wearableDimensions) {
+ offsets.dimensions = wearableDimensions;
+ }
+ // we currently assume a wearable is a single entity
+ Entities.editEntity(pastedEntityIDs[0], offsets);
+ }
+
+ var rezPosition = Entities.getEntityProperties(pastedEntityIDs[0], "position").position;
+
+ Audio.playSound(REZZING_SOUND, {
+ volume: 1.0,
+ position: rezPosition,
+ localOnly: true
});
+
+ } else {
+ Window.notifyEditError("Can't import entities: entities would be out of bounds.");
}
+ } else {
+ Window.notifyEditError("There was an error importing the entity file.");
}
+}
- function deleteSendAssetParticleEffect() {
- if (sendAssetParticleEffectUpdateTimer) {
- Script.clearInterval(sendAssetParticleEffectUpdateTimer);
- sendAssetParticleEffectUpdateTimer = null;
- }
- if (sendAssetParticleEffect) {
- sendAssetParticleEffect = Entities.deleteEntity(sendAssetParticleEffect);
- }
- sendAssetRecipient = null;
- }
-
- var savedDisablePreviewOptionLocked = false;
- var savedDisablePreviewOption = Menu.isOptionChecked("Disable Preview");;
- function maybeEnableHMDPreview() {
- // Set a small timeout to prevent sensitive data from being shown during
- // UI fade
- Script.setTimeout(function () {
- setTabletVisibleInSecondaryCamera(true);
- DesktopPreviewProvider.setPreviewDisabledReason("USER");
- Menu.setIsOptionChecked("Disable Preview", savedDisablePreviewOption);
- savedDisablePreviewOptionLocked = false;
- }, 150);
- }
-
- // Function Name: fromQml()
- //
- // Description:
- // -Called when a message is received from Checkout.qml. The "message" argument is what is sent from the Checkout QML
- // in the format "{method, params}", like json-rpc.
- function fromQml(message) {
- switch (message.method) {
- case 'purchases_openWallet':
- case 'checkout_openWallet':
- case 'checkout_setUpClicked':
- openWallet();
- break;
- case 'purchases_walletNotSetUp':
- wireEventBridge(true);
- sendToQml({
- method: 'updateWalletReferrer',
- referrer: "purchases"
- });
- openWallet();
- break;
- case 'checkout_walletNotSetUp':
- wireEventBridge(true);
- sendToQml({
- method: 'updateWalletReferrer',
- referrer: message.referrer === "itemPage" ? message.itemId : message.referrer
- });
- openWallet();
- break;
- case 'checkout_cancelClicked':
- tablet.gotoWebScreen(MARKETPLACE_URL + '/items/' + message.params, MARKETPLACES_INJECT_SCRIPT_URL);
- // TODO: Make Marketplace a QML app that's a WebView wrapper so we can use the app stack.
- // I don't think this is trivial to do since we also want to inject some JS into the DOM.
- //tablet.popFromStack();
- break;
- case 'header_goToPurchases':
- case 'checkout_goToPurchases':
- referrerURL = MARKETPLACE_URL_INITIAL;
- filterText = message.filterText;
- tablet.pushOntoStack(MARKETPLACE_PURCHASES_QML_PATH);
- break;
- case 'checkout_itemLinkClicked':
- tablet.gotoWebScreen(MARKETPLACE_URL + '/items/' + message.itemId, MARKETPLACES_INJECT_SCRIPT_URL);
- break;
- case 'checkout_continueShopping':
- tablet.gotoWebScreen(MARKETPLACE_URL_INITIAL, MARKETPLACES_INJECT_SCRIPT_URL);
- //tablet.popFromStack();
- break;
- case 'purchases_itemInfoClicked':
- var itemId = message.itemId;
- if (itemId && itemId !== "") {
- tablet.gotoWebScreen(MARKETPLACE_URL + '/items/' + itemId, MARKETPLACES_INJECT_SCRIPT_URL);
- }
- break;
- case 'checkout_rezClicked':
- case 'purchases_rezClicked':
- rezEntity(message.itemHref, message.itemType);
- break;
- case 'header_marketplaceImageClicked':
- case 'purchases_backClicked':
- tablet.gotoWebScreen(message.referrerURL, MARKETPLACES_INJECT_SCRIPT_URL);
- break;
- case 'purchases_goToMarketplaceClicked':
- tablet.gotoWebScreen(MARKETPLACE_URL_INITIAL, MARKETPLACES_INJECT_SCRIPT_URL);
- break;
- case 'updateItemClicked':
- tablet.gotoWebScreen(message.upgradeUrl + "?edition=" + message.itemEdition,
- MARKETPLACES_INJECT_SCRIPT_URL);
- break;
- case 'giftAsset':
-
- break;
- case 'passphrasePopup_cancelClicked':
- case 'needsLogIn_cancelClicked':
- tablet.gotoWebScreen(MARKETPLACE_URL_INITIAL, MARKETPLACES_INJECT_SCRIPT_URL);
- break;
- case 'needsLogIn_loginClicked':
- openLoginWindow();
- break;
- case 'disableHmdPreview':
- if (!savedDisablePreviewOption) {
- savedDisablePreviewOption = Menu.isOptionChecked("Disable Preview");
- savedDisablePreviewOptionLocked = true;
- }
-
- if (!savedDisablePreviewOption) {
- DesktopPreviewProvider.setPreviewDisabledReason("SECURE_SCREEN");
- Menu.setIsOptionChecked("Disable Preview", true);
- setTabletVisibleInSecondaryCamera(false);
- }
- break;
- case 'maybeEnableHmdPreview':
- maybeEnableHMDPreview();
- break;
- case 'purchases_openGoTo':
- tablet.loadQMLSource("hifi/tablet/TabletAddressDialog.qml");
- break;
- case 'purchases_itemCertificateClicked':
- setCertificateInfo("", message.itemCertificateId);
- break;
- case 'inspectionCertificate_closeClicked':
- tablet.gotoHomeScreen();
- break;
- case 'inspectionCertificate_requestOwnershipVerification':
- ContextOverlay.requestOwnershipVerification(message.entity);
- break;
- case 'inspectionCertificate_showInMarketplaceClicked':
- tablet.gotoWebScreen(message.marketplaceUrl, MARKETPLACES_INJECT_SCRIPT_URL);
- break;
- case 'header_myItemsClicked':
- referrerURL = MARKETPLACE_URL_INITIAL;
- filterText = "";
- tablet.pushOntoStack(MARKETPLACE_PURCHASES_QML_PATH);
- wireEventBridge(true);
- sendToQml({
- method: 'purchases_showMyItems'
- });
- break;
- case 'refreshConnections':
- // Guard to prevent this code from being executed while sending money --
- // we only want to execute this while sending non-HFC gifts
- if (!onWalletScreen) {
- print('Refreshing Connections...');
- getConnectionData(false);
- }
- break;
- case 'enable_ChooseRecipientNearbyMode':
- // Guard to prevent this code from being executed while sending money --
- // we only want to execute this while sending non-HFC gifts
- if (!onWalletScreen) {
- if (!isUpdateOverlaysWired) {
- Script.update.connect(updateOverlays);
- isUpdateOverlaysWired = true;
- }
- }
- break;
- case 'disable_ChooseRecipientNearbyMode':
- // Guard to prevent this code from being executed while sending money --
- // we only want to execute this while sending non-HFC gifts
- if (!onWalletScreen) {
- if (isUpdateOverlaysWired) {
- Script.update.disconnect(updateOverlays);
- isUpdateOverlaysWired = false;
- }
- removeOverlays();
- }
- break;
- case 'wallet_availableUpdatesReceived':
- case 'purchases_availableUpdatesReceived':
- userHasUpdates = message.numUpdates > 0;
- messagesWaiting(userHasUpdates);
- break;
- case 'purchases_updateWearables':
- var currentlyWornWearables = [];
- var ATTACHMENT_SEARCH_RADIUS = 100; // meters (just in case)
-
- var nearbyEntities = Entities.findEntitiesByType('Model', MyAvatar.position, ATTACHMENT_SEARCH_RADIUS);
-
- for (var i = 0; i < nearbyEntities.length; i++) {
- var currentProperties = Entities.getEntityProperties(nearbyEntities[i], ['certificateID', 'editionNumber', 'parentID']);
- if (currentProperties.parentID === MyAvatar.sessionUUID) {
- currentlyWornWearables.push({
- entityID: nearbyEntities[i],
- entityCertID: currentProperties.certificateID,
- entityEdition: currentProperties.editionNumber
- });
- }
- }
-
- sendToQml({ method: 'updateWearables', wornWearables: currentlyWornWearables });
- break;
- case 'sendAsset_sendPublicly':
- if (message.assetName !== "") {
- deleteSendAssetParticleEffect();
- sendAssetRecipient = message.recipient;
- var amount = message.amount;
- var props = SEND_ASSET_PARTICLE_PROPERTIES;
- props.parentID = MyAvatar.sessionUUID;
- props.position = MyAvatar.position;
- props.position.y += 0.2;
- if (message.effectImage) {
- props.textures = message.effectImage;
- }
- sendAssetParticleEffect = Entities.addEntity(props, true);
- particleEffectTimestamp = Date.now();
- updateSendAssetParticleEffect();
- sendAssetParticleEffectUpdateTimer = Script.setInterval(updateSendAssetParticleEffect, SEND_ASSET_PARTICLE_TIMER_UPDATE);
- }
- break;
- case 'http.request':
- // Handled elsewhere, don't log.
- break;
- case 'goToPurchases_fromWalletHome': // HRS FIXME What's this about?
- break;
- default:
- print('Unrecognized message from Checkout.qml or Purchases.qml: ' + JSON.stringify(message));
- }
- }
-
- // Function Name: wireEventBridge()
- //
- // Description:
- // -Used to connect/disconnect the script's response to the tablet's "fromQml" signal. Set the "on" argument to enable or
- // disable to event bridge.
- //
- // Relevant Variables:
- // -hasEventBridge: true/false depending on whether we've already connected the event bridge.
- var hasEventBridge = false;
- function wireEventBridge(on) {
- if (!tablet) {
- print("Warning in wireEventBridge(): 'tablet' undefined!");
+var referrerURL; // Used for updating Purchases QML
+var filterText; // Used for updating Purchases QML
+function onWebEventReceived(message) {
+ message = JSON.parse(message);
+ if (message.type === GOTO_DIRECTORY) {
+ ui.open(MARKETPLACES_URL, MARKETPLACES_INJECT_SCRIPT_URL);
+ } else if (message.type === QUERY_CAN_WRITE_ASSETS) {
+ ui.sendToHtml(CAN_WRITE_ASSETS + " " + Entities.canWriteAssets());
+ } else if (message.type === WARN_USER_NO_PERMISSIONS) {
+ Window.alert(NO_PERMISSIONS_ERROR_MESSAGE);
+ } else if (message.type === CLARA_IO_STATUS) {
+ if (isDownloadBeingCancelled) {
return;
}
- if (on) {
- if (!hasEventBridge) {
- tablet.fromQml.connect(fromQml);
- hasEventBridge = true;
- }
+
+ var text = message.status;
+ if (messageBox === null) {
+ messageBox = Window.openMessageBox(CLARA_DOWNLOAD_TITLE, text, CANCEL_BUTTON, NO_BUTTON);
} else {
- if (hasEventBridge) {
- tablet.fromQml.disconnect(fromQml);
- hasEventBridge = false;
- }
+ Window.updateMessageBox(messageBox, CLARA_DOWNLOAD_TITLE, text, CANCEL_BUTTON, NO_BUTTON);
}
- }
-
- // Function Name: onTabletScreenChanged()
- //
- // Description:
- // -Called when the TabletScriptingInterface::screenChanged() signal is emitted. The "type" argument can be either the string
- // value of "Home", "Web", "Menu", "QML", or "Closed". The "url" argument is only valid for Web and QML.
- var onWalletScreen = false;
- var onCommerceScreen = false;
- function onTabletScreenChanged(type, url) {
- onMarketplaceScreen = type === "Web" && url.indexOf(MARKETPLACE_URL) !== -1;
- var onWalletScreenNow = url.indexOf(MARKETPLACE_WALLET_QML_PATH) !== -1;
- var onCommerceScreenNow = type === "QML" && (url.indexOf(MARKETPLACE_CHECKOUT_QML_PATH) !== -1 || url === MARKETPLACE_PURCHASES_QML_PATH
- || url.indexOf(MARKETPLACE_INSPECTIONCERTIFICATE_QML_PATH) !== -1);
-
- if ((!onWalletScreenNow && onWalletScreen) || (!onCommerceScreenNow && onCommerceScreen)) { // exiting wallet or commerce screen
- maybeEnableHMDPreview();
+ return;
+ } else if (message.type === CLARA_IO_DOWNLOAD) {
+ if (messageBox !== null) {
+ Window.closeMessageBox(messageBox);
+ messageBox = null;
}
-
- onCommerceScreen = onCommerceScreenNow;
- onWalletScreen = onWalletScreenNow;
- wireEventBridge(onMarketplaceScreen || onCommerceScreen || onWalletScreen);
-
- if (url === MARKETPLACE_PURCHASES_QML_PATH) {
- sendToQml({
- method: 'updatePurchases',
- referrerURL: referrerURL,
- filterText: filterText
- });
- }
-
- // for toolbar mode: change button to active when window is first openend, false otherwise.
- if (marketplaceButton) {
- marketplaceButton.editProperties({ isActive: (onMarketplaceScreen || onCommerceScreen) && !onWalletScreen });
- }
- if (type === "Web" && url.indexOf(MARKETPLACE_URL) !== -1) {
- ContextOverlay.isInMarketplaceInspectionMode = true;
- } else {
- ContextOverlay.isInMarketplaceInspectionMode = false;
- }
-
- if (onCommerceScreen) {
- if (!isWired) {
- Users.usernameFromIDReply.connect(usernameFromIDReply);
- Controller.mousePressEvent.connect(handleMouseEvent);
- Controller.mouseMoveEvent.connect(handleMouseMoveEvent);
- triggerMapping.enable();
- triggerPressMapping.enable();
- }
- isWired = true;
- Wallet.refreshWalletStatus();
- } else {
- off();
- sendToQml({
- method: 'inspectionCertificate_resetCert'
- });
- }
- }
-
- //
- // Manage the connection between the button and the window.
- //
- var marketplaceButton;
- var buttonName = "MARKET";
- var tablet = null;
- var NORMAL_ICON = "icons/tablet-icons/market-i.svg";
- var NORMAL_ACTIVE = "icons/tablet-icons/market-a.svg";
- var WAITING_ICON = "icons/tablet-icons/market-i-msg.svg";
- var WAITING_ACTIVE = "icons/tablet-icons/market-a-msg.svg";
- function startup() {
- tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
- marketplaceButton = tablet.addButton({
- icon: NORMAL_ICON,
- activeIcon: NORMAL_ACTIVE,
- text: buttonName,
- sortOrder: 9
+ return;
+ } else if (message.type === CLARA_IO_CANCELLED_DOWNLOAD) {
+ isDownloadBeingCancelled = false;
+ } else if (message.type === "CHECKOUT") {
+ wireQmlEventBridge(true);
+ ui.open(MARKETPLACE_CHECKOUT_QML_PATH);
+ ui.tablet.sendToQml({
+ method: 'updateCheckoutQML',
+ params: message
+ });
+ } else if (message.type === "REQUEST_SETTING") {
+ sendCommerceSettings();
+ } else if (message.type === "PURCHASES") {
+ referrerURL = message.referrerURL;
+ filterText = "";
+ ui.open(MARKETPLACE_PURCHASES_QML_PATH);
+ } else if (message.type === "LOGIN") {
+ openLoginWindow();
+ } else if (message.type === "WALLET_SETUP") {
+ wireQmlEventBridge(true);
+ ui.tablet.sendToQml({
+ method: 'updateWalletReferrer',
+ referrer: "marketplace cta"
+ });
+ openWallet();
+ } else if (message.type === "MY_ITEMS") {
+ referrerURL = MARKETPLACE_URL_INITIAL;
+ filterText = "";
+ ui.open(MARKETPLACE_PURCHASES_QML_PATH);
+ wireQmlEventBridge(true);
+ ui.tablet.sendToQml({
+ method: 'purchases_showMyItems'
});
-
- ContextOverlay.contextOverlayClicked.connect(setCertificateInfo);
- Entities.canWriteAssetsChanged.connect(onCanWriteAssetsChanged);
- GlobalServices.myUsernameChanged.connect(onUsernameChanged);
- marketplaceButton.clicked.connect(onButtonClicked);
- tablet.screenChanged.connect(onTabletScreenChanged);
- tablet.webEventReceived.connect(onMessage);
- Wallet.walletStatusChanged.connect(sendCommerceSettings);
- Window.messageBoxClosed.connect(onMessageBoxClosed);
-
- Wallet.refreshWalletStatus();
}
- var isWired = false;
- var isUpdateOverlaysWired = false;
- function off() {
- if (isWired) {
- Users.usernameFromIDReply.disconnect(usernameFromIDReply);
- Controller.mousePressEvent.disconnect(handleMouseEvent);
- Controller.mouseMoveEvent.disconnect(handleMouseMoveEvent);
- triggerMapping.disable();
- triggerPressMapping.disable();
+}
+var sendAssetRecipient;
+var sendAssetParticleEffectUpdateTimer;
+var particleEffectTimestamp;
+var sendAssetParticleEffect;
+var SEND_ASSET_PARTICLE_TIMER_UPDATE = 250;
+var SEND_ASSET_PARTICLE_EMITTING_DURATION = 3000;
+var SEND_ASSET_PARTICLE_LIFETIME_SECONDS = 8;
+var SEND_ASSET_PARTICLE_PROPERTIES = {
+ accelerationSpread: { x: 0, y: 0, z: 0 },
+ alpha: 1,
+ alphaFinish: 1,
+ alphaSpread: 0,
+ alphaStart: 1,
+ azimuthFinish: 0,
+ azimuthStart: -6,
+ color: { red: 255, green: 222, blue: 255 },
+ colorFinish: { red: 255, green: 229, blue: 225 },
+ colorSpread: { red: 0, green: 0, blue: 0 },
+ colorStart: { red: 243, green: 255, blue: 255 },
+ emitAcceleration: { x: 0, y: 0, z: 0 }, // Immediately gets updated to be accurate
+ emitDimensions: { x: 0, y: 0, z: 0 },
+ emitOrientation: { x: 0, y: 0, z: 0 },
+ emitRate: 4,
+ emitSpeed: 2.1,
+ emitterShouldTrail: true,
+ isEmitting: 1,
+ lifespan: SEND_ASSET_PARTICLE_LIFETIME_SECONDS + 1, // Immediately gets updated to be accurate
+ lifetime: SEND_ASSET_PARTICLE_LIFETIME_SECONDS + 1,
+ maxParticles: 20,
+ name: 'asset-particles',
+ particleRadius: 0.2,
+ polarFinish: 0,
+ polarStart: 0,
+ radiusFinish: 0.05,
+ radiusSpread: 0,
+ radiusStart: 0.2,
+ speedSpread: 0,
+ textures: "http://hifi-content.s3.amazonaws.com/alan/dev/Particles/Bokeh-Particle-HFC.png",
+ type: 'ParticleEffect'
+};
- isWired = false;
- }
- if (isUpdateOverlaysWired) {
- Script.update.disconnect(updateOverlays);
- isUpdateOverlaysWired = false;
- }
- removeOverlays();
- }
- function shutdown() {
- maybeEnableHMDPreview();
+function updateSendAssetParticleEffect() {
+ var timestampNow = Date.now();
+ if ((timestampNow - particleEffectTimestamp) > (SEND_ASSET_PARTICLE_LIFETIME_SECONDS * 1000)) {
deleteSendAssetParticleEffect();
+ return;
+ } else if ((timestampNow - particleEffectTimestamp) > SEND_ASSET_PARTICLE_EMITTING_DURATION) {
+ Entities.editEntity(sendAssetParticleEffect, {
+ isEmitting: 0
+ });
+ } else if (sendAssetParticleEffect) {
+ var recipientPosition = AvatarList.getAvatar(sendAssetRecipient).position;
+ var distance = Vec3.distance(recipientPosition, MyAvatar.position);
+ var accel = Vec3.subtract(recipientPosition, MyAvatar.position);
+ accel.y -= 3.0;
+ var life = Math.sqrt(2 * distance / Vec3.length(accel));
+ Entities.editEntity(sendAssetParticleEffect, {
+ emitAcceleration: accel,
+ lifespan: life
+ });
+ }
+}
- ContextOverlay.contextOverlayClicked.disconnect(setCertificateInfo);
- Entities.canWriteAssetsChanged.disconnect(onCanWriteAssetsChanged);
- GlobalServices.myUsernameChanged.disconnect(onUsernameChanged);
- marketplaceButton.clicked.disconnect(onButtonClicked);
- tablet.removeButton(marketplaceButton);
- tablet.webEventReceived.disconnect(onMessage);
- Wallet.walletStatusChanged.disconnect(sendCommerceSettings);
- Window.messageBoxClosed.disconnect(onMessageBoxClosed);
+function deleteSendAssetParticleEffect() {
+ if (sendAssetParticleEffectUpdateTimer) {
+ Script.clearInterval(sendAssetParticleEffectUpdateTimer);
+ sendAssetParticleEffectUpdateTimer = null;
+ }
+ if (sendAssetParticleEffect) {
+ sendAssetParticleEffect = Entities.deleteEntity(sendAssetParticleEffect);
+ }
+ sendAssetRecipient = null;
+}
+
+var savedDisablePreviewOption = Menu.isOptionChecked("Disable Preview");
+var UI_FADE_TIMEOUT_MS = 150;
+function maybeEnableHMDPreview() {
+ // Set a small timeout to prevent sensitive data from being shown during UI fade
+ Script.setTimeout(function () {
+ setTabletVisibleInSecondaryCamera(true);
+ DesktopPreviewProvider.setPreviewDisabledReason("USER");
+ Menu.setIsOptionChecked("Disable Preview", savedDisablePreviewOption);
+ }, UI_FADE_TIMEOUT_MS);
+}
- if (tablet) {
- tablet.screenChanged.disconnect(onTabletScreenChanged);
- if (onMarketplaceScreen) {
- tablet.gotoHomeScreen();
+var onQmlMessageReceived = function onQmlMessageReceived(message) {
+ if (message.messageSrc === "HTML") {
+ return;
+ }
+ switch (message.method) {
+ case 'purchases_openWallet':
+ case 'checkout_openWallet':
+ case 'checkout_setUpClicked':
+ openWallet();
+ break;
+ case 'purchases_walletNotSetUp':
+ wireQmlEventBridge(true);
+ ui.tablet.sendToQml({
+ method: 'updateWalletReferrer',
+ referrer: "purchases"
+ });
+ openWallet();
+ break;
+ case 'checkout_walletNotSetUp':
+ wireQmlEventBridge(true);
+ ui.tablet.sendToQml({
+ method: 'updateWalletReferrer',
+ referrer: message.referrer === "itemPage" ? message.itemId : message.referrer
+ });
+ openWallet();
+ break;
+ case 'checkout_cancelClicked':
+ ui.open(MARKETPLACE_URL + '/items/' + message.params, MARKETPLACES_INJECT_SCRIPT_URL);
+ break;
+ case 'header_goToPurchases':
+ case 'checkout_goToPurchases':
+ referrerURL = MARKETPLACE_URL_INITIAL;
+ filterText = message.filterText;
+ ui.open(MARKETPLACE_PURCHASES_QML_PATH);
+ break;
+ case 'checkout_itemLinkClicked':
+ ui.open(MARKETPLACE_URL + '/items/' + message.itemId, MARKETPLACES_INJECT_SCRIPT_URL);
+ break;
+ case 'checkout_continueShopping':
+ ui.open(MARKETPLACE_URL_INITIAL, MARKETPLACES_INJECT_SCRIPT_URL);
+ break;
+ case 'purchases_itemInfoClicked':
+ var itemId = message.itemId;
+ if (itemId && itemId !== "") {
+ ui.open(MARKETPLACE_URL + '/items/' + itemId, MARKETPLACES_INJECT_SCRIPT_URL);
+ }
+ break;
+ case 'checkout_rezClicked':
+ case 'purchases_rezClicked':
+ rezEntity(message.itemHref, message.itemType);
+ break;
+ case 'header_marketplaceImageClicked':
+ case 'purchases_backClicked':
+ ui.open(message.referrerURL, MARKETPLACES_INJECT_SCRIPT_URL);
+ break;
+ case 'purchases_goToMarketplaceClicked':
+ ui.open(MARKETPLACE_URL_INITIAL, MARKETPLACES_INJECT_SCRIPT_URL);
+ break;
+ case 'updateItemClicked':
+ ui.open(message.upgradeUrl + "?edition=" + message.itemEdition,
+ MARKETPLACES_INJECT_SCRIPT_URL);
+ break;
+ case 'giftAsset':
+
+ break;
+ case 'passphrasePopup_cancelClicked':
+ case 'needsLogIn_cancelClicked':
+ ui.open(MARKETPLACE_URL_INITIAL, MARKETPLACES_INJECT_SCRIPT_URL);
+ break;
+ case 'needsLogIn_loginClicked':
+ openLoginWindow();
+ break;
+ case 'disableHmdPreview':
+ if (!savedDisablePreviewOption) {
+ savedDisablePreviewOption = Menu.isOptionChecked("Disable Preview");
+ }
+
+ if (!savedDisablePreviewOption) {
+ DesktopPreviewProvider.setPreviewDisabledReason("SECURE_SCREEN");
+ Menu.setIsOptionChecked("Disable Preview", true);
+ setTabletVisibleInSecondaryCamera(false);
+ }
+ break;
+ case 'maybeEnableHmdPreview':
+ maybeEnableHMDPreview();
+ break;
+ case 'purchases_openGoTo':
+ ui.open("hifi/tablet/TabletAddressDialog.qml");
+ break;
+ case 'purchases_itemCertificateClicked':
+ contextOverlayEntity = "";
+ setCertificateInfo(contextOverlayEntity, message.itemCertificateId);
+ break;
+ case 'inspectionCertificate_closeClicked':
+ ui.close();
+ break;
+ case 'inspectionCertificate_requestOwnershipVerification':
+ ContextOverlay.requestOwnershipVerification(message.entity);
+ break;
+ case 'inspectionCertificate_showInMarketplaceClicked':
+ ui.open(message.marketplaceUrl, MARKETPLACES_INJECT_SCRIPT_URL);
+ break;
+ case 'header_myItemsClicked':
+ referrerURL = MARKETPLACE_URL_INITIAL;
+ filterText = "";
+ ui.open(MARKETPLACE_PURCHASES_QML_PATH);
+ wireQmlEventBridge(true);
+ ui.tablet.sendToQml({
+ method: 'purchases_showMyItems'
+ });
+ break;
+ case 'refreshConnections':
+ // Guard to prevent this code from being executed while sending money --
+ // we only want to execute this while sending non-HFC gifts
+ if (!onWalletScreen) {
+ print('Refreshing Connections...');
+ getConnectionData(false);
+ }
+ break;
+ case 'enable_ChooseRecipientNearbyMode':
+ // Guard to prevent this code from being executed while sending money --
+ // we only want to execute this while sending non-HFC gifts
+ if (!onWalletScreen) {
+ if (!isUpdateOverlaysWired) {
+ Script.update.connect(updateOverlays);
+ isUpdateOverlaysWired = true;
+ }
+ }
+ break;
+ case 'disable_ChooseRecipientNearbyMode':
+ // Guard to prevent this code from being executed while sending money --
+ // we only want to execute this while sending non-HFC gifts
+ if (!onWalletScreen) {
+ if (isUpdateOverlaysWired) {
+ Script.update.disconnect(updateOverlays);
+ isUpdateOverlaysWired = false;
+ }
+ removeOverlays();
+ }
+ break;
+ case 'wallet_availableUpdatesReceived':
+ case 'purchases_availableUpdatesReceived':
+ userHasUpdates = message.numUpdates > 0;
+ ui.messagesWaiting(userHasUpdates);
+ break;
+ case 'purchases_updateWearables':
+ var currentlyWornWearables = [];
+ var ATTACHMENT_SEARCH_RADIUS = 100; // meters (just in case)
+
+ var nearbyEntities = Entities.findEntitiesByType('Model', MyAvatar.position, ATTACHMENT_SEARCH_RADIUS);
+
+ for (var i = 0; i < nearbyEntities.length; i++) {
+ var currentProperties = Entities.getEntityProperties(
+ nearbyEntities[i], ['certificateID', 'editionNumber', 'parentID']
+ );
+ if (currentProperties.parentID === MyAvatar.sessionUUID) {
+ currentlyWornWearables.push({
+ entityID: nearbyEntities[i],
+ entityCertID: currentProperties.certificateID,
+ entityEdition: currentProperties.editionNumber
+ });
}
}
+ ui.tablet.sendToQml({ method: 'updateWearables', wornWearables: currentlyWornWearables });
+ break;
+ case 'sendAsset_sendPublicly':
+ if (message.assetName !== "") {
+ deleteSendAssetParticleEffect();
+ sendAssetRecipient = message.recipient;
+ var props = SEND_ASSET_PARTICLE_PROPERTIES;
+ props.parentID = MyAvatar.sessionUUID;
+ props.position = MyAvatar.position;
+ props.position.y += 0.2;
+ if (message.effectImage) {
+ props.textures = message.effectImage;
+ }
+ sendAssetParticleEffect = Entities.addEntity(props, true);
+ particleEffectTimestamp = Date.now();
+ updateSendAssetParticleEffect();
+ sendAssetParticleEffectUpdateTimer = Script.setInterval(updateSendAssetParticleEffect,
+ SEND_ASSET_PARTICLE_TIMER_UPDATE);
+ }
+ break;
+ case 'http.request':
+ // Handled elsewhere, don't log.
+ break;
+ case 'goToPurchases_fromWalletHome': // HRS FIXME What's this about?
+ break;
+ default:
+ print('Unrecognized message from Checkout.qml or Purchases.qml: ' + JSON.stringify(message));
+ }
+};
+
+// Function Name: onTabletScreenChanged()
+//
+// Description:
+// -Called when the TabletScriptingInterface::screenChanged() signal is emitted. The "type" argument can be either the string
+// value of "Home", "Web", "Menu", "QML", or "Closed". The "url" argument is only valid for Web and QML.
+var onMarketplaceScreen = false;
+var onWalletScreen = false;
+var onCommerceScreen = false;
+var onInspectionCertificateScreen = false;
+var onTabletScreenChanged = function onTabletScreenChanged(type, url) {
+ ui.setCurrentVisibleScreenMetadata(type, url);
+ onMarketplaceScreen = type === "Web" && url.indexOf(MARKETPLACE_URL) !== -1;
+ onInspectionCertificateScreen = type === "QML" && url.indexOf(MARKETPLACE_INSPECTIONCERTIFICATE_QML_PATH) !== -1;
+ var onWalletScreenNow = url.indexOf(MARKETPLACE_WALLET_QML_PATH) !== -1;
+ var onCommerceScreenNow = type === "QML" &&
+ (url.indexOf(MARKETPLACE_CHECKOUT_QML_PATH) !== -1 || url === MARKETPLACE_PURCHASES_QML_PATH ||
+ onInspectionCertificateScreen);
+
+ // exiting wallet or commerce screen
+ if ((!onWalletScreenNow && onWalletScreen) || (!onCommerceScreenNow && onCommerceScreen)) {
+ maybeEnableHMDPreview();
+ }
+
+ onCommerceScreen = onCommerceScreenNow;
+ onWalletScreen = onWalletScreenNow;
+ wireQmlEventBridge(onMarketplaceScreen || onCommerceScreen || onWalletScreen);
+
+ if (url === MARKETPLACE_PURCHASES_QML_PATH) {
+ ui.tablet.sendToQml({
+ method: 'updatePurchases',
+ referrerURL: referrerURL,
+ filterText: filterText
+ });
+ }
+
+ ui.isOpen = (onMarketplaceScreen || onCommerceScreen) && !onWalletScreen;
+ ui.buttonActive(ui.isOpen);
+
+ if (type === "Web" && url.indexOf(MARKETPLACE_URL) !== -1) {
+ ContextOverlay.isInMarketplaceInspectionMode = true;
+ } else {
+ ContextOverlay.isInMarketplaceInspectionMode = false;
+ }
+
+ if (onInspectionCertificateScreen) {
+ setCertificateInfo(contextOverlayEntity);
+ }
+
+ if (onCommerceScreen) {
+ if (!isWired) {
+ Users.usernameFromIDReply.connect(usernameFromIDReply);
+ Controller.mousePressEvent.connect(handleMouseEvent);
+ Controller.mouseMoveEvent.connect(handleMouseMoveEvent);
+ triggerMapping.enable();
+ triggerPressMapping.enable();
+ }
+ isWired = true;
+ Wallet.refreshWalletStatus();
+ } else {
+ ui.tablet.sendToQml({
+ method: 'inspectionCertificate_resetCert'
+ });
off();
}
+ console.debug(ui.buttonName + " app reports: Tablet screen changed.\nNew screen type: " + type +
+ "\nNew screen URL: " + url + "\nCurrent app open status: " + ui.isOpen + "\n");
+};
- //
- // Run the functions.
- //
- startup();
- Script.scriptEnding.connect(shutdown);
+//
+// Manage the connection between the button and the window.
+//
+var BUTTON_NAME = "MARKET";
+var MARKETPLACE_URL = METAVERSE_SERVER_URL + "/marketplace";
+var MARKETPLACE_URL_INITIAL = MARKETPLACE_URL + "?"; // Append "?" to signal injected script that it's the initial page.
+var ui;
+function startup() {
+ ui = new AppUi({
+ buttonName: BUTTON_NAME,
+ sortOrder: 9,
+ inject: MARKETPLACES_INJECT_SCRIPT_URL,
+ home: MARKETPLACE_URL_INITIAL,
+ onScreenChanged: onTabletScreenChanged,
+ onMessage: onQmlMessageReceived
+ });
+ ContextOverlay.contextOverlayClicked.connect(openInspectionCertificateQML);
+ Entities.canWriteAssetsChanged.connect(onCanWriteAssetsChanged);
+ GlobalServices.myUsernameChanged.connect(onUsernameChanged);
+ ui.tablet.webEventReceived.connect(onWebEventReceived);
+ Wallet.walletStatusChanged.connect(sendCommerceSettings);
+ Window.messageBoxClosed.connect(onMessageBoxClosed);
+ Wallet.refreshWalletStatus();
+}
+
+var isWired = false;
+var isUpdateOverlaysWired = false;
+function off() {
+ if (isWired) {
+ Users.usernameFromIDReply.disconnect(usernameFromIDReply);
+ Controller.mousePressEvent.disconnect(handleMouseEvent);
+ Controller.mouseMoveEvent.disconnect(handleMouseMoveEvent);
+ triggerMapping.disable();
+ triggerPressMapping.disable();
+
+ isWired = false;
+ }
+
+ if (isUpdateOverlaysWired) {
+ Script.update.disconnect(updateOverlays);
+ isUpdateOverlaysWired = false;
+ }
+ removeOverlays();
+}
+function shutdown() {
+ maybeEnableHMDPreview();
+ deleteSendAssetParticleEffect();
+
+ Window.messageBoxClosed.disconnect(onMessageBoxClosed);
+ Wallet.walletStatusChanged.disconnect(sendCommerceSettings);
+ ui.tablet.webEventReceived.disconnect(onWebEventReceived);
+ GlobalServices.myUsernameChanged.disconnect(onUsernameChanged);
+ Entities.canWriteAssetsChanged.disconnect(onCanWriteAssetsChanged);
+ ContextOverlay.contextOverlayClicked.disconnect(openInspectionCertificateQML);
+
+ off();
+}
+
+//
+// Run the functions.
+//
+startup();
+Script.scriptEnding.connect(shutdown);
}()); // END LOCAL_SCOPE
diff --git a/scripts/system/pal.js b/scripts/system/pal.js
index ebb45130e5..85898c28fb 100644
--- a/scripts/system/pal.js
+++ b/scripts/system/pal.js
@@ -1,6 +1,9 @@
"use strict";
-/*jslint vars:true, plusplus:true, forin:true*/
-/*global Tablet, Settings, Script, AvatarList, Users, Entities, MyAvatar, Camera, Overlays, Vec3, Quat, HMD, Controller, Account, UserActivityLogger, Messages, Window, XMLHttpRequest, print, location, getControllerWorldLocation*/
+/* jslint vars:true, plusplus:true, forin:true */
+/* global Tablet, Settings, Script, AvatarList, Users, Entities,
+ MyAvatar, Camera, Overlays, Vec3, Quat, HMD, Controller, Account,
+ UserActivityLogger, Messages, Window, XMLHttpRequest, print, location, getControllerWorldLocation
+*/
/* eslint indent: ["error", 4, { "outerIIFEBody": 0 }] */
//
// pal.js
@@ -20,7 +23,7 @@ var AppUi = Script.require('appUi');
var populateNearbyUserList, color, textures, removeOverlays,
controllerComputePickRay, off,
receiveMessage, avatarDisconnected, clearLocalQMLDataAndClosePAL,
- CHANNEL, getConnectionData, findableByChanged,
+ CHANNEL, getConnectionData,
avatarAdded, avatarRemoved, avatarSessionChanged; // forward references;
// hardcoding these as it appears we cannot traverse the originalTextures in overlays??? Maybe I've missed
@@ -318,6 +321,10 @@ function fromQml(message) { // messages are {method, params}, like json-rpc. See
break;
case 'http.request':
break; // Handled by request-service.
+ case 'hideNotificationDot':
+ shouldShowDot = false;
+ ui.messagesWaiting(shouldShowDot);
+ break;
default:
print('Unrecognized message from Pal.qml:', JSON.stringify(message));
}
@@ -361,8 +368,8 @@ function getProfilePicture(username, callback) { // callback(url) if successfull
});
}
var SAFETY_LIMIT = 400;
-function getAvailableConnections(domain, callback) { // callback([{usename, location}...]) if successfull. (Logs otherwise)
- var url = METAVERSE_BASE + '/api/v1/users?per_page=' + SAFETY_LIMIT + '&';
+function getAvailableConnections(domain, callback, numResultsPerPage) { // callback([{usename, location}...]) if successfull. (Logs otherwise)
+ var url = METAVERSE_BASE + '/api/v1/users?per_page=' + (numResultsPerPage || SAFETY_LIMIT) + '&';
if (domain) {
url += 'status=' + domain.slice(1, -1); // without curly braces
} else {
@@ -713,7 +720,7 @@ function tabletVisibilityChanged() {
if (!ui.tablet.tabletShown && ui.isOpen) {
ui.close();
}
- }
+}
var UPDATE_INTERVAL_MS = 100;
var updateInterval;
@@ -725,10 +732,14 @@ function createUpdateInterval() {
var previousContextOverlay = ContextOverlay.enabled;
var previousRequestsDomainListData = Users.requestsDomainListData;
-function on() {
+function palOpened() {
+ ui.sendMessage({
+ method: 'changeConnectionsDotStatus',
+ shouldShowDot: shouldShowDot
+ });
previousContextOverlay = ContextOverlay.enabled;
- previousRequestsDomainListData = Users.requestsDomainListData
+ previousRequestsDomainListData = Users.requestsDomainListData;
ContextOverlay.enabled = false;
Users.requestsDomainListData = true;
@@ -807,14 +818,98 @@ function avatarSessionChanged(avatarID) {
sendToQml({ method: 'palIsStale', params: [avatarID, 'avatarSessionChanged'] });
}
+function notificationDataProcessPage(data) {
+ return data.data.users;
+}
+
+var shouldShowDot = false;
+var storedOnlineUsersArray = [];
+function notificationPollCallback(connectionsArray) {
+ //
+ // START logic for handling online/offline user changes
+ //
+ var i, j;
+ var newlyOnlineConnectionsArray = [];
+ for (i = 0; i < connectionsArray.length; i++) {
+ var currentUser = connectionsArray[i];
+
+ if (connectionsArray[i].online) {
+ var indexOfStoredOnlineUser = -1;
+ for (j = 0; j < storedOnlineUsersArray.length; j++) {
+ if (currentUser.username === storedOnlineUsersArray[j].username) {
+ indexOfStoredOnlineUser = j;
+ break;
+ }
+ }
+ // If the user record isn't already presesnt inside `storedOnlineUsersArray`...
+ if (indexOfStoredOnlineUser < 0) {
+ storedOnlineUsersArray.push(currentUser);
+ newlyOnlineConnectionsArray.push(currentUser);
+ }
+ } else {
+ var indexOfOfflineUser = -1;
+ for (j = 0; j < storedOnlineUsersArray.length; j++) {
+ if (currentUser.username === storedOnlineUsersArray[j].username) {
+ indexOfOfflineUser = j;
+ break;
+ }
+ }
+ if (indexOfOfflineUser >= 0) {
+ storedOnlineUsersArray.splice(indexOfOfflineUser);
+ }
+ }
+ }
+ // If there's new data, the light should turn on.
+ // If the light is already on and you have connections online, the light should stay on.
+ // In all other cases, the light should turn off or stay off.
+ shouldShowDot = newlyOnlineConnectionsArray.length > 0 || (storedOnlineUsersArray.length > 0 && shouldShowDot);
+ //
+ // END logic for handling online/offline user changes
+ //
+
+ if (!ui.isOpen) {
+ ui.messagesWaiting(shouldShowDot);
+ ui.sendMessage({
+ method: 'changeConnectionsDotStatus',
+ shouldShowDot: shouldShowDot
+ });
+
+ if (newlyOnlineConnectionsArray.length > 0) {
+ var message;
+ if (!ui.notificationInitialCallbackMade) {
+ message = newlyOnlineConnectionsArray.length + " of your connections " +
+ (newlyOnlineConnectionsArray.length === 1 ? "is" : "are") + " online. Open PEOPLE to join them!";
+ ui.notificationDisplayBanner(message);
+ } else {
+ for (i = 0; i < newlyOnlineConnectionsArray.length; i++) {
+ message = newlyOnlineConnectionsArray[i].username + " is available in " +
+ newlyOnlineConnectionsArray[i].location.root.name + ". Open PEOPLE to join them!";
+ ui.notificationDisplayBanner(message);
+ }
+ }
+ }
+ }
+}
+
+function isReturnedDataEmpty(data) {
+ var usersArray = data.data.users;
+ return usersArray.length === 0;
+}
+
function startup() {
ui = new AppUi({
buttonName: "PEOPLE",
sortOrder: 7,
home: "hifi/Pal.qml",
- onOpened: on,
+ onOpened: palOpened,
onClosed: off,
- onMessage: fromQml
+ onMessage: fromQml,
+ notificationPollEndpoint: "/api/v1/users?filter=connections&per_page=10",
+ notificationPollTimeoutMs: 60000,
+ notificationDataProcessPage: notificationDataProcessPage,
+ notificationPollCallback: notificationPollCallback,
+ notificationPollStopPaginatingConditionMet: isReturnedDataEmpty,
+ notificationPollCaresAboutSince: false
});
Window.domainChanged.connect(clearLocalQMLDataAndClosePAL);
Window.domainConnectionRefused.connect(clearLocalQMLDataAndClosePAL);
diff --git a/scripts/system/snapshot.js b/scripts/system/snapshot.js
index 37270f896e..3d744b3bd2 100644
--- a/scripts/system/snapshot.js
+++ b/scripts/system/snapshot.js
@@ -7,28 +7,19 @@
// Distributed under the Apache License, Version 2.0
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
-/* globals Tablet, Script, HMD, Settings, DialogsManager, Menu, Reticle, OverlayWebWindow, Desktop, Account, MyAvatar, Snapshot */
+/* globals Tablet, Script, HMD, Settings, DialogsManager, Menu, Reticle,
+ OverlayWebWindow, Desktop, Account, MyAvatar, Snapshot */
/* eslint indent: ["error", 4, { "outerIIFEBody": 0 }] */
(function () { // BEGIN LOCAL_SCOPE
Script.include("/~/system/libraries/accountUtils.js");
+var AppUi = Script.require('appUi');
var SNAPSHOT_DELAY = 500; // 500ms
var FINISH_SOUND_DELAY = 350;
var resetOverlays;
var reticleVisible;
-var buttonName = "SNAP";
-var buttonConnected = false;
-
-var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
-var button = tablet.addButton({
- icon: "icons/tablet-icons/snap-i.svg",
- activeIcon: "icons/tablet-icons/snap-a.svg",
- text: buttonName,
- sortOrder: 5
-});
-
var snapshotOptions = {};
var imageData = [];
var storyIDsToMaybeDelete = [];
@@ -52,8 +43,6 @@ try {
print('Failed to resolve request api, error: ' + err);
}
-
-
function removeFromStoryIDsToMaybeDelete(story_id) {
storyIDsToMaybeDelete.splice(storyIDsToMaybeDelete.indexOf(story_id), 1);
print('storyIDsToMaybeDelete[] now:', JSON.stringify(storyIDsToMaybeDelete));
@@ -73,33 +62,32 @@ function onMessage(message) {
// 2. Although we currently use a single image, we would like to take snapshot, a selfie, a 360 etc. all at the
// same time, show the user all of them, and have the user deselect any that they do not want to share.
// So we'll ultimately be receiving a set of objects, perhaps with different post processing for each.
- message = JSON.parse(message);
if (message.type !== "snapshot") {
return;
}
switch (message.action) {
case 'ready': // DOM is ready and page has loaded
- tablet.emitScriptEvent(JSON.stringify({
+ ui.sendMessage({
type: "snapshot",
action: "captureSettings",
setting: Settings.getValue("alsoTakeAnimatedSnapshot", true)
- }));
+ });
if (Snapshot.getSnapshotsLocation() !== "") {
isDomainOpen(Settings.getValue("previousSnapshotDomainID"), function (canShare) {
- tablet.emitScriptEvent(JSON.stringify({
+ ui.sendMessage({
type: "snapshot",
action: "showPreviousImages",
options: snapshotOptions,
image_data: imageData,
canShare: canShare
- }));
+ });
});
} else {
- tablet.emitScriptEvent(JSON.stringify({
+ ui.sendMessage({
type: "snapshot",
action: "showSetupInstructions"
- }));
+ });
Settings.setValue("previousStillSnapPath", "");
Settings.setValue("previousStillSnapStoryID", "");
Settings.setValue("previousStillSnapBlastingDisabled", false);
@@ -124,7 +112,7 @@ function onMessage(message) {
|| (!HMD.active && Settings.getValue("desktopTabletBecomesToolbar", true))) {
Desktop.show("hifi/dialogs/GeneralPreferencesDialog.qml", "GeneralPreferencesDialog");
} else {
- tablet.loadQMLOnTop("hifi/tablet/TabletGeneralPreferences.qml");
+ ui.openNewAppOnTop("hifi/tablet/TabletGeneralPreferences.qml");
}
break;
case 'captureStillAndGif':
@@ -284,7 +272,6 @@ var POLAROID_RATE_LIMIT_MS = 1000;
var polaroidPrintingIsRateLimited = false;
function printToPolaroid(image_url) {
-
// Rate-limit printing
if (polaroidPrintingIsRateLimited) {
return;
@@ -376,19 +363,6 @@ function fillImageDataFromPrevious() {
}
}
-var SNAPSHOT_REVIEW_URL = Script.resolvePath("html/SnapshotReview.html");
-var isInSnapshotReview = false;
-function onButtonClicked() {
- if (isInSnapshotReview){
- // for toolbar-mode: go back to home screen, this will close the window.
- tablet.gotoHomeScreen();
- } else {
- fillImageDataFromPrevious();
- tablet.gotoWebScreen(SNAPSHOT_REVIEW_URL);
- HMD.openTablet();
- }
-}
-
function snapshotUploaded(isError, reply) {
if (!isError) {
var replyJson = JSON.parse(reply),
@@ -409,12 +383,12 @@ function snapshotUploaded(isError, reply) {
}
if ((isGif && !ignoreGifSnapshotData) || (!isGif && !ignoreStillSnapshotData)) {
print('SUCCESS: Snapshot uploaded! Story with audience:for_url created! ID:', storyID);
- tablet.emitScriptEvent(JSON.stringify({
+ ui.sendMessage({
type: "snapshot",
action: "snapshotUploadComplete",
story_id: storyID,
image_url: imageURL,
- }));
+ });
if (isGif) {
Settings.setValue("previousAnimatedSnapStoryID", storyID);
} else {
@@ -429,10 +403,10 @@ function snapshotUploaded(isError, reply) {
}
var href, snapshotDomainID;
function takeSnapshot() {
- tablet.emitScriptEvent(JSON.stringify({
+ ui.sendMessage({
type: "snapshot",
action: "clearPreviousImages"
- }));
+ });
Settings.setValue("previousStillSnapPath", "");
Settings.setValue("previousStillSnapStoryID", "");
Settings.setValue("previousStillSnapBlastingDisabled", false);
@@ -471,10 +445,6 @@ function takeSnapshot() {
} else {
Window.stillSnapshotTaken.connect(stillSnapshotTaken);
}
- if (buttonConnected) {
- button.clicked.disconnect(onButtonClicked);
- buttonConnected = false;
- }
// hide overlays if they are on
if (resetOverlays) {
@@ -538,10 +508,6 @@ function stillSnapshotTaken(pathStillSnapshot, notify) {
Menu.setIsOptionChecked("Show Overlays", true);
}
Window.stillSnapshotTaken.disconnect(stillSnapshotTaken);
- if (!buttonConnected) {
- button.clicked.connect(onButtonClicked);
- buttonConnected = true;
- }
// A Snapshot Review dialog might be left open indefinitely after taking the picture,
// during which time the user may have moved. So stash that info in the dialog so that
@@ -559,12 +525,12 @@ function stillSnapshotTaken(pathStillSnapshot, notify) {
isLoggedIn: isLoggedIn
};
imageData = [{ localPath: pathStillSnapshot, href: href }];
- tablet.emitScriptEvent(JSON.stringify({
+ ui.sendMessage({
type: "snapshot",
action: "addImages",
options: snapshotOptions,
image_data: imageData
- }));
+ });
});
}
@@ -572,10 +538,10 @@ function snapshotDirChanged(snapshotPath) {
Window.browseDirChanged.disconnect(snapshotDirChanged);
if (snapshotPath !== "") { // not cancelled
Snapshot.setSnapshotsLocation(snapshotPath);
- tablet.emitScriptEvent(JSON.stringify({
+ ui.sendMessage({
type: "snapshot",
action: "snapshotLocationChosen"
- }));
+ });
}
}
@@ -603,22 +569,18 @@ function processingGifStarted(pathStillSnapshot) {
isLoggedIn: isLoggedIn
};
imageData = [{ localPath: pathStillSnapshot, href: href }];
- tablet.emitScriptEvent(JSON.stringify({
+ ui.sendMessage({
type: "snapshot",
action: "addImages",
options: snapshotOptions,
image_data: imageData
- }));
+ });
});
}
function processingGifCompleted(pathAnimatedSnapshot) {
isLoggedIn = Account.isLoggedIn();
Window.processingGifCompleted.disconnect(processingGifCompleted);
- if (!buttonConnected) {
- button.clicked.connect(onButtonClicked);
- buttonConnected = true;
- }
Settings.setValue("previousAnimatedSnapPath", pathAnimatedSnapshot);
@@ -631,12 +593,12 @@ function processingGifCompleted(pathAnimatedSnapshot) {
canBlast: location.domainID === Settings.getValue("previousSnapshotDomainID"),
};
imageData = [{ localPath: pathAnimatedSnapshot, href: href }];
- tablet.emitScriptEvent(JSON.stringify({
+ ui.sendMessage({
type: "snapshot",
action: "addImages",
options: snapshotOptions,
image_data: imageData
- }));
+ });
});
}
function maybeDeleteSnapshotStories() {
@@ -655,28 +617,16 @@ function maybeDeleteSnapshotStories() {
});
storyIDsToMaybeDelete = [];
}
-function onTabletScreenChanged(type, url) {
- var wasInSnapshotReview = isInSnapshotReview;
- isInSnapshotReview = (type === "Web" && url === SNAPSHOT_REVIEW_URL);
- button.editProperties({ isActive: isInSnapshotReview });
- if (isInSnapshotReview !== wasInSnapshotReview) {
- if (isInSnapshotReview) {
- tablet.webEventReceived.connect(onMessage);
- } else {
- tablet.webEventReceived.disconnect(onMessage);
- }
- }
-}
function onUsernameChanged() {
fillImageDataFromPrevious();
isDomainOpen(Settings.getValue("previousSnapshotDomainID"), function (canShare) {
- tablet.emitScriptEvent(JSON.stringify({
+ ui.sendMessage({
type: "snapshot",
action: "showPreviousImages",
options: snapshotOptions,
image_data: imageData,
canShare: canShare
- }));
+ });
});
if (isLoggedIn) {
if (shareAfterLogin) {
@@ -705,10 +655,10 @@ function onUsernameChanged() {
function snapshotLocationSet(location) {
if (location !== "") {
- tablet.emitScriptEvent(JSON.stringify({
+ ui.sendMessage({
type: "snapshot",
action: "snapshotLocationChosen"
- }));
+ });
}
}
@@ -733,36 +683,36 @@ function processRezPermissionChange(canRez) {
action = 'setPrintButtonDisabled';
}
- tablet.emitScriptEvent(JSON.stringify({
+ ui.sendMessage({
type: "snapshot",
action : action
- }));
+ });
}
-button.clicked.connect(onButtonClicked);
-buttonConnected = true;
+function startup() {
+ ui = new AppUi({
+ buttonName: "SNAP",
+ sortOrder: 5,
+ home: Script.resolvePath("html/SnapshotReview.html"),
+ onOpened: fillImageDataFromPrevious,
+ onMessage: onMessage
+ });
-Window.snapshotShared.connect(snapshotUploaded);
-tablet.screenChanged.connect(onTabletScreenChanged);
-GlobalServices.myUsernameChanged.connect(onUsernameChanged);
-Snapshot.snapshotLocationSet.connect(snapshotLocationSet);
+ Entities.canRezChanged.connect(updatePrintPermissions);
+ Entities.canRezTmpChanged.connect(updatePrintPermissions);
+ GlobalServices.myUsernameChanged.connect(onUsernameChanged);
+ Snapshot.snapshotLocationSet.connect(snapshotLocationSet);
+ Window.snapshotShared.connect(snapshotUploaded);
+}
+startup();
-Entities.canRezChanged.connect(updatePrintPermissions);
-Entities.canRezTmpChanged.connect(updatePrintPermissions);
-
-Script.scriptEnding.connect(function () {
- if (buttonConnected) {
- button.clicked.disconnect(onButtonClicked);
- buttonConnected = false;
- }
- if (tablet) {
- tablet.removeButton(button);
- tablet.screenChanged.disconnect(onTabletScreenChanged);
- }
+function shutdown() {
Window.snapshotShared.disconnect(snapshotUploaded);
Snapshot.snapshotLocationSet.disconnect(snapshotLocationSet);
+ GlobalServices.myUsernameChanged.disconnect(onUsernameChanged);
Entities.canRezChanged.disconnect(updatePrintPermissions);
Entities.canRezTmpChanged.disconnect(updatePrintPermissions);
-});
+}
+Script.scriptEnding.connect(shutdown);
}()); // END LOCAL_SCOPE
diff --git a/scripts/system/tablet-goto.js b/scripts/system/tablet-goto.js
index 8fafac7685..804f838d04 100644
--- a/scripts/system/tablet-goto.js
+++ b/scripts/system/tablet-goto.js
@@ -1,9 +1,10 @@
"use strict";
-/*jslint vars:true, plusplus:true, forin:true*/
-/*global Window, Script, Tablet, HMD, Controller, Account, XMLHttpRequest, location, print*/
+/* jslint vars:true, plusplus:true, forin:true */
+/* eslint indent: ["error", 4, { "outerIIFEBody": 0 }] */
+/* global Window, Script, Tablet, HMD, Controller, Account, XMLHttpRequest, location, print */
//
-// goto.js
+// tablet-goto.js
// scripts/system/
//
// Created by Dante Ruiz on 8 February 2017
@@ -14,148 +15,118 @@
//
(function () { // BEGIN LOCAL_SCOPE
+var request = Script.require('request').request;
+var AppUi = Script.require('appUi');
+var DEBUG = false;
+function debug() {
+ if (!DEBUG) {
+ return;
+ }
+ print('tablet-goto.js:', [].map.call(arguments, JSON.stringify));
+}
- var request = Script.require('request').request;
- var DEBUG = false;
- function debug() {
- if (!DEBUG) {
+var stories = {}, pingPong = false;
+function expire(id) {
+ var options = {
+ uri: Account.metaverseServerURL + '/api/v1/user_stories/' + id,
+ method: 'PUT',
+ json: true,
+ body: {expire: "true"}
+ };
+ request(options, function (error, response) {
+ debug('expired story', options, 'error:', error, 'response:', response);
+ if (error || (response.status !== 'success')) {
+ print("ERROR expiring story: ", error || response.status);
+ }
+ });
+}
+var PER_PAGE_DEBUG = 10;
+var PER_PAGE_NORMAL = 100;
+function pollForAnnouncements() {
+ // We could bail now if !Account.isLoggedIn(), but what if we someday have system-wide announcments?
+ var actions = 'announcement';
+ var count = DEBUG ? PER_PAGE_DEBUG : PER_PAGE_NORMAL;
+ var options = [
+ 'now=' + new Date().toISOString(),
+ 'include_actions=' + actions,
+ 'restriction=' + (Account.isLoggedIn() ? 'open,hifi' : 'open'),
+ 'require_online=true',
+ 'protocol=' + encodeURIComponent(Window.protocolSignature()),
+ 'per_page=' + count
+ ];
+ var url = Account.metaverseServerURL + '/api/v1/user_stories?' + options.join('&');
+ request({
+ uri: url
+ }, function (error, data) {
+ debug(url, error, data);
+ if (error || (data.status !== 'success')) {
+ print("Error: unable to get", url, error || data.status);
return;
}
- print('tablet-goto.js:', [].map.call(arguments, JSON.stringify));
- }
-
- var gotoQmlSource = "hifi/tablet/TabletAddressDialog.qml";
- var buttonName = "GOTO";
- var onGotoScreen = false;
- var shouldActivateButton = false;
- function ignore() { }
-
- var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
- var NORMAL_ICON = "icons/tablet-icons/goto-i.svg";
- var NORMAL_ACTIVE = "icons/tablet-icons/goto-a.svg";
- var WAITING_ICON = "icons/tablet-icons/goto-msg.svg";
- var button = tablet.addButton({
- icon: NORMAL_ICON,
- activeIcon: NORMAL_ACTIVE,
- 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);
-
- var stories = {}, pingPong = false;
- function expire(id) {
- var options = {
- uri: Account.metaverseServerURL + '/api/v1/user_stories/' + id,
- method: 'PUT',
- json: true,
- body: {expire: "true"}
- };
- request(options, function (error, response) {
- debug('expired story', options, 'error:', error, 'response:', response);
- if (error || (response.status !== 'success')) {
- print("ERROR expiring story: ", error || response.status);
+ var didNotify = false, key;
+ pingPong = !pingPong;
+ data.user_stories.forEach(function (story) {
+ var stored = stories[story.id], storedOrNew = stored || story;
+ debug('story exists:', !!stored, storedOrNew);
+ if ((storedOrNew.username === Account.username) && (storedOrNew.place_name !== location.placename)) {
+ if (storedOrNew.audience === 'for_connections') { // Only expire if we haven't already done so.
+ expire(story.id);
+ }
+ return; // before marking
}
- });
- }
- function pollForAnnouncements() {
- // We could bail now if !Account.isLoggedIn(), but what if we someday have system-wide announcments?
- var actions = 'announcement';
- var count = DEBUG ? 10 : 100;
- var options = [
- 'now=' + new Date().toISOString(),
- 'include_actions=' + actions,
- 'restriction=' + (Account.isLoggedIn() ? 'open,hifi' : 'open'),
- 'require_online=true',
- 'protocol=' + encodeURIComponent(Window.protocolSignature()),
- 'per_page=' + count
- ];
- var url = Account.metaverseServerURL + '/api/v1/user_stories?' + options.join('&');
- request({
- uri: url
- }, function (error, data) {
- debug(url, error, data);
- if (error || (data.status !== 'success')) {
- print("Error: unable to get", url, error || data.status);
+ storedOrNew.pingPong = pingPong;
+ if (stored) { // already seen
return;
}
- var didNotify = false, key;
- pingPong = !pingPong;
- data.user_stories.forEach(function (story) {
- var stored = stories[story.id], storedOrNew = stored || story;
- debug('story exists:', !!stored, storedOrNew);
- if ((storedOrNew.username === Account.username) && (storedOrNew.place_name !== location.placename)) {
- if (storedOrNew.audience == 'for_connections') { // Only expire if we haven't already done so.
- expire(story.id);
- }
- return; // before marking
- }
- storedOrNew.pingPong = pingPong;
- if (stored) { // already seen
- return;
- }
- stories[story.id] = story;
- var message = story.username + " " + story.action_string + " in " + story.place_name + ". Open GOTO to join them.";
- Window.displayAnnouncement(message);
- didNotify = true;
- });
- for (key in stories) { // Any story we were tracking that was not marked, has expired.
- if (stories[key].pingPong !== pingPong) {
- debug('removing story', key);
- delete stories[key];
- }
- }
- if (didNotify) {
- messagesWaiting(true);
- if (HMD.isHandControllerAvailable()) {
- var STRENGTH = 1.0, DURATION_MS = 60, HAND = 2; // both hands
- Controller.triggerHapticPulse(STRENGTH, DURATION_MS, HAND);
- }
- } else if (!Object.keys(stories).length) { // If there's nothing being tracked, then any messageWaiting has expired.
- messagesWaiting(false);
- }
+ stories[story.id] = story;
+ var message = story.username + " " + story.action_string + " in " +
+ story.place_name + ". Open GOTO to join them.";
+ Window.displayAnnouncement(message);
+ didNotify = true;
});
- }
- var ANNOUNCEMENTS_POLL_TIME_MS = (DEBUG ? 10 : 60) * 1000;
- var pollTimer = Script.setInterval(pollForAnnouncements, ANNOUNCEMENTS_POLL_TIME_MS);
-
- Script.scriptEnding.connect(function () {
- Script.clearInterval(pollTimer);
- button.clicked.disconnect(onClicked);
- tablet.removeButton(button);
- tablet.screenChanged.disconnect(onScreenChanged);
+ for (key in stories) { // Any story we were tracking that was not marked, has expired.
+ if (stories[key].pingPong !== pingPong) {
+ debug('removing story', key);
+ delete stories[key];
+ }
+ }
+ if (didNotify) {
+ ui.messagesWaiting(true);
+ if (HMD.isHandControllerAvailable()) {
+ var STRENGTH = 1.0, DURATION_MS = 60, HAND = 2; // both hands
+ Controller.triggerHapticPulse(STRENGTH, DURATION_MS, HAND);
+ }
+ } else if (!Object.keys(stories).length) { // If there's nothing being tracked, then any messageWaiting has expired.
+ ui.messagesWaiting(false);
+ }
});
+}
+var MS_PER_SEC = 1000;
+var DEBUG_POLL_TIME_SEC = 10;
+var NORMAL_POLL_TIME_SEC = 60;
+var ANNOUNCEMENTS_POLL_TIME_MS = (DEBUG ? DEBUG_POLL_TIME_SEC : NORMAL_POLL_TIME_SEC) * MS_PER_SEC;
+var pollTimer = Script.setInterval(pollForAnnouncements, ANNOUNCEMENTS_POLL_TIME_MS);
+function gotoOpened() {
+ ui.messagesWaiting(false);
+}
+
+var ui;
+var GOTO_QML_SOURCE = "hifi/tablet/TabletAddressDialog.qml";
+var BUTTON_NAME = "GOTO";
+function startup() {
+ ui = new AppUi({
+ buttonName: BUTTON_NAME,
+ sortOrder: 8,
+ onOpened: gotoOpened,
+ home: GOTO_QML_SOURCE
+ });
+}
+
+function shutdown() {
+ Script.clearInterval(pollTimer);
+}
+
+startup();
+Script.scriptEnding.connect(shutdown);
}()); // END LOCAL_SCOPE