mirror of
https://github.com/JulianGro/overte.git
synced 2025-04-06 14:43:29 +02:00
737 lines
25 KiB
JavaScript
737 lines
25 KiB
JavaScript
"use strict";
|
|
/* jslint vars:true, plusplus:true, forin:true */
|
|
/* eslint indent: ["error", 4, { "outerIIFEBody": 0 }] */
|
|
//
|
|
// wallet.js
|
|
//
|
|
// Created by Zach Fox on 2017-08-17
|
|
// Copyright 2017 High Fidelity, Inc
|
|
//
|
|
// Distributed under the Apache License, Version 2.0
|
|
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
|
//
|
|
|
|
/* global getConnectionData getControllerWorldLocation openLoginWindow WalletScriptingInterface */
|
|
|
|
(function () { // BEGIN LOCAL_SCOPE
|
|
Script.include("/~/system/libraries/accountUtils.js");
|
|
Script.include("/~/system/libraries/connectionUtils.js");
|
|
var AppUi = Script.require('appUi');
|
|
|
|
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 };
|
|
|
|
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;
|
|
}
|
|
|
|
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) {
|
|
ExtendedOverlay.get(lastHoveringId).hover(false);
|
|
}
|
|
};
|
|
|
|
// 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) {
|
|
// Depends on nearer coverOverlays to extend closer to us than farther ones.
|
|
var pickedOverlay = Overlays.findRayIntersection(pickRay);
|
|
if (!pickedOverlay.intersects) {
|
|
if (noHit) {
|
|
return noHit();
|
|
}
|
|
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;
|
|
var OVERLAY_SCALE = 0.032;
|
|
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;
|
|
// get diff between target and eye (a vector pointing to the eye from avatar position)
|
|
var diff = Vec3.subtract(target, eye);
|
|
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: OVERLAY_SCALE * 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 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: Script.getExternalPath(Script.ExternalPaths.HF_Content, "/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
|
|
});
|
|
}
|
|
}
|
|
|
|
function deleteSendMoneyParticleEffect() {
|
|
if (sendMoneyParticleEffectUpdateTimer) {
|
|
Script.clearInterval(sendMoneyParticleEffectUpdateTimer);
|
|
sendMoneyParticleEffectUpdateTimer = null;
|
|
}
|
|
if (sendMoneyParticleEffect) {
|
|
sendMoneyParticleEffect = Entities.deleteEntity(sendMoneyParticleEffect);
|
|
}
|
|
sendMoneyRecipient = null;
|
|
}
|
|
|
|
function onUsernameChanged() {
|
|
if (ui.checkIsOpen()) {
|
|
ui.open(WALLET_QML_SOURCE);
|
|
}
|
|
}
|
|
|
|
var MARKETPLACE_QML_PATH = "hifi/commerce/marketplace/Marketplace.qml";
|
|
function openMarketplace(optionalItem) {
|
|
ui.open(MARKETPLACE_QML_PATH);
|
|
|
|
if (optionalItem) {
|
|
ui.tablet.sendToQml({
|
|
method: 'updateMarketplaceQMLItem',
|
|
params: { itemId: optionalItem }
|
|
});
|
|
}
|
|
}
|
|
|
|
function setCertificateInfo(itemCertificateId) {
|
|
ui.tablet.sendToQml({
|
|
method: 'inspectionCertificate_setCertificateId',
|
|
entityId: "",
|
|
certificateId: itemCertificateId
|
|
});
|
|
}
|
|
|
|
// 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().
|
|
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':
|
|
openMarketplace();
|
|
break;
|
|
default:
|
|
openMarketplace();
|
|
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 'transactionHistory_linkClicked':
|
|
openMarketplace(message.itemId);
|
|
break;
|
|
case 'goToMarketplaceMainPage':
|
|
openMarketplace();
|
|
break;
|
|
case 'goToMarketplaceItemPage':
|
|
openMarketplace(message.itemId);
|
|
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':
|
|
deleteSendMoneyParticleEffect();
|
|
sendMoneyRecipient = message.recipient;
|
|
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 '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 'purchases_walletNotSetUp':
|
|
ui.tablet.sendToQml({
|
|
method: 'updateWalletReferrer',
|
|
referrer: "purchases"
|
|
});
|
|
break;
|
|
case 'purchases_openGoTo':
|
|
ui.open("hifi/tablet/TabletAddressDialog.qml");
|
|
break;
|
|
case 'purchases_itemInfoClicked':
|
|
var itemId = message.itemId;
|
|
if (itemId && itemId !== "") {
|
|
openMarketplace(itemId);
|
|
}
|
|
break;
|
|
case 'purchases_itemCertificateClicked':
|
|
setCertificateInfo(message.itemCertificateId);
|
|
break;
|
|
case 'clearShouldShowDotHistory':
|
|
shouldShowDotHistory = false;
|
|
ui.messagesWaiting(shouldShowDotUpdates || shouldShowDotHistory);
|
|
break;
|
|
case 'clearShouldShowDotUpdates':
|
|
shouldShowDotUpdates = false;
|
|
ui.messagesWaiting(shouldShowDotUpdates || shouldShowDotHistory);
|
|
break;
|
|
case 'http.request':
|
|
// Handled elsewhere, don't log.
|
|
break;
|
|
case 'closeSendAsset':
|
|
ui.close();
|
|
break;
|
|
default:
|
|
print('wallet.js: Unrecognized message from QML');
|
|
}
|
|
}
|
|
|
|
var isWired = false;
|
|
function walletOpened() {
|
|
Users.usernameFromIDReply.connect(usernameFromIDReply);
|
|
Controller.mousePressEvent.connect(handleMouseEvent);
|
|
Controller.mouseMoveEvent.connect(handleMouseMoveEvent);
|
|
triggerMapping.enable();
|
|
triggerPressMapping.enable();
|
|
isWired = true;
|
|
|
|
if (shouldShowDotHistory) {
|
|
ui.sendMessage({
|
|
method: 'updateRecentActivityMessageLight',
|
|
messagesWaiting: shouldShowDotHistory
|
|
});
|
|
}
|
|
}
|
|
|
|
function walletClosed() {
|
|
off();
|
|
}
|
|
|
|
function notificationDataProcessPageUpdates(data) {
|
|
return data.data.updates;
|
|
}
|
|
|
|
function notificationDataProcessPageHistory(data) {
|
|
return data.data.history;
|
|
}
|
|
|
|
var shouldShowDotUpdates = false;
|
|
function notificationPollCallbackUpdates(updatesArray) {
|
|
shouldShowDotUpdates = updatesArray.length > 0;
|
|
ui.messagesWaiting(shouldShowDotUpdates || shouldShowDotHistory);
|
|
|
|
if (updatesArray.length > 0) {
|
|
var message;
|
|
if (!ui.notificationInitialCallbackMade[0]) {
|
|
message = updatesArray.length + " of your purchased items " +
|
|
(updatesArray.length === 1 ? "has an update " : "have updates ") +
|
|
"available. Open INVENTORY to update.";
|
|
ui.notificationDisplayBanner(message);
|
|
|
|
ui.notificationPollCaresAboutSince[0] = true;
|
|
} else {
|
|
for (var i = 0; i < updatesArray.length; i++) {
|
|
message = "Update available for \"" +
|
|
updatesArray[i].base_item_title + "\"." +
|
|
"Open INVENTORY to update.";
|
|
ui.notificationDisplayBanner(message);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
var shouldShowDotHistory = false;
|
|
function notificationPollCallbackHistory(historyArray) {
|
|
if (!ui.isOpen) {
|
|
var notificationCount = historyArray.length;
|
|
shouldShowDotHistory = shouldShowDotHistory || notificationCount > 0;
|
|
ui.messagesWaiting(shouldShowDotUpdates || shouldShowDotHistory);
|
|
|
|
if (notificationCount > 0) {
|
|
var message;
|
|
if (!ui.notificationInitialCallbackMade[1]) {
|
|
message = "You have " + notificationCount + " unread recent " +
|
|
"transaction" + (notificationCount === 1 ? "" : "s") + ". Open INVENTORY to see all activity.";
|
|
ui.notificationDisplayBanner(message);
|
|
} else {
|
|
for (var i = 0; i < notificationCount; i++) {
|
|
var historyMessage = historyArray[i].message;
|
|
var sanitizedHistoryMessage = historyMessage.replace(/<\/?[^>]+(>|$)/g, "");
|
|
message = '"' + sanitizedHistoryMessage + '" ' +
|
|
"Open INVENTORY to see all activity.";
|
|
ui.notificationDisplayBanner(message);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function isReturnedDataEmptyUpdates(data) {
|
|
var updatesArray = data.data.updates;
|
|
return updatesArray.length === 0;
|
|
}
|
|
|
|
function isReturnedDataEmptyHistory(data) {
|
|
var historyArray = data.data.history;
|
|
return historyArray.length === 0;
|
|
}
|
|
|
|
var DEVELOPER_MENU = "Developer";
|
|
var MARKETPLACE_ITEM_TESTER_LABEL = "Marketplace Item Tester";
|
|
var MARKETPLACE_ITEM_TESTER_QML_SOURCE = "hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml";
|
|
function installMarketplaceItemTester() {
|
|
if (!Menu.menuExists(DEVELOPER_MENU)) {
|
|
Menu.addMenu(DEVELOPER_MENU);
|
|
}
|
|
if (!Menu.menuItemExists(DEVELOPER_MENU, MARKETPLACE_ITEM_TESTER_LABEL)) {
|
|
Menu.addMenuItem({
|
|
menuName: DEVELOPER_MENU,
|
|
menuItemName: MARKETPLACE_ITEM_TESTER_LABEL,
|
|
isCheckable: false
|
|
});
|
|
}
|
|
|
|
Menu.menuItemEvent.connect(function (menuItem) {
|
|
if (menuItem === MARKETPLACE_ITEM_TESTER_LABEL) {
|
|
ui.open(MARKETPLACE_ITEM_TESTER_QML_SOURCE);
|
|
}
|
|
});
|
|
}
|
|
|
|
function uninstallMarketplaceItemTester() {
|
|
if (Menu.menuExists(DEVELOPER_MENU) &&
|
|
Menu.menuItemExists(DEVELOPER_MENU, MARKETPLACE_ITEM_TESTER_LABEL)
|
|
) {
|
|
Menu.removeMenuItem(DEVELOPER_MENU, MARKETPLACE_ITEM_TESTER_LABEL);
|
|
}
|
|
}
|
|
|
|
var BUTTON_NAME = "INVENTORY";
|
|
var WALLET_QML_SOURCE = "hifi/commerce/wallet/Wallet.qml";
|
|
var SENDASSET_QML_SOURCE = "hifi/commerce/common/sendAsset/SendAsset.qml";
|
|
var NOTIFICATION_POLL_TIMEOUT = 300000;
|
|
var ui;
|
|
function startup() {
|
|
var notificationPollEndpointArray = ["/api/v1/commerce/available_updates?per_page=10"];
|
|
var notificationPollTimeoutMsArray = [NOTIFICATION_POLL_TIMEOUT];
|
|
var notificationDataProcessPageArray = [notificationDataProcessPageUpdates];
|
|
var notificationPollCallbackArray = [notificationPollCallbackUpdates];
|
|
var notificationPollStopPaginatingConditionMetArray = [isReturnedDataEmptyUpdates];
|
|
var notificationPollCaresAboutSinceArray = [false];
|
|
|
|
if (!WalletScriptingInterface.limitedCommerce) {
|
|
notificationPollEndpointArray[1] = "/api/v1/commerce/history?per_page=10";
|
|
notificationPollTimeoutMsArray[1] = NOTIFICATION_POLL_TIMEOUT;
|
|
notificationDataProcessPageArray[1] = notificationDataProcessPageHistory;
|
|
notificationPollCallbackArray[1] = notificationPollCallbackHistory;
|
|
notificationPollStopPaginatingConditionMetArray[1] = isReturnedDataEmptyHistory;
|
|
notificationPollCaresAboutSinceArray[1] = true;
|
|
}
|
|
|
|
ui = new AppUi({
|
|
buttonName: BUTTON_NAME,
|
|
sortOrder: 10,
|
|
home: WALLET_QML_SOURCE,
|
|
additionalAppScreens: SENDASSET_QML_SOURCE,
|
|
onOpened: walletOpened,
|
|
onClosed: walletClosed,
|
|
onMessage: fromQml,
|
|
notificationPollEndpoint: notificationPollEndpointArray,
|
|
notificationPollTimeoutMs: notificationPollTimeoutMsArray,
|
|
notificationDataProcessPage: notificationDataProcessPageArray,
|
|
notificationPollCallback: notificationPollCallbackArray,
|
|
notificationPollStopPaginatingConditionMet: notificationPollStopPaginatingConditionMetArray,
|
|
notificationPollCaresAboutSince: notificationPollCaresAboutSinceArray
|
|
});
|
|
GlobalServices.myUsernameChanged.connect(onUsernameChanged);
|
|
installMarketplaceItemTester();
|
|
}
|
|
|
|
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() {
|
|
GlobalServices.myUsernameChanged.disconnect(onUsernameChanged);
|
|
deleteSendMoneyParticleEffect();
|
|
uninstallMarketplaceItemTester();
|
|
off();
|
|
}
|
|
|
|
//
|
|
// Run the functions.
|
|
//
|
|
startup();
|
|
Script.scriptEnding.connect(shutdown);
|
|
}()); // END LOCAL_SCOPE
|