diff --git a/package.json b/package.json index ad584f83bcf0827ec6c4f2914bf9e38abc20234b..8b2851b5edcd8fc13942b1dc88882760fe7c4728 100644 --- a/package.json +++ b/package.json @@ -34,10 +34,13 @@ "jsonschema": "^1.2.2", "minijanus": "^0.5.0", "mobile-detect": "^1.4.1", + "moment": "^2.22.0", + "moment-timezone": "^0.5.14", "moving-average": "^1.0.0", "naf-janus-adapter": "https://github.com/mozilla/naf-janus-adapter#feature/disconnect", - "networked-aframe": "github:mozillareality/networked-aframe#mr-social-client/master", + "networked-aframe": "https://github.com/mozillareality/networked-aframe#mr-social-client/master", "nipplejs": "^0.6.7", + "phoenix": "^1.3.0", "query-string": "^5.0.1", "raven-js": "^3.20.1", "react": "^16.1.1", diff --git a/src/components/animated-robot-hands.js b/src/components/animated-robot-hands.js deleted file mode 100644 index 1b26402848a8cb641560d76307708c4d77d53352..0000000000000000000000000000000000000000 --- a/src/components/animated-robot-hands.js +++ /dev/null @@ -1,94 +0,0 @@ -// Global THREE, AFRAME -const POSES = { - open: "allOpen", - thumbDown: "thumbDown", - indexDown: "indexDown", - mrpDown: "mrpDown", - thumbUp: "thumbsUp", - point: "point", - fist: "allGrip", - pinch: "pinch" -}; - -// TODO: When we have analog values of index-finger triggers or middle-finger grips, -// it would be nice to animate the hands proportionally to those analog values. -AFRAME.registerComponent("animated-robot-hands", { - dependencies: ["animation-mixer"], - schema: { - leftHand: { type: "selector", default: "#player-left-controller" }, - rightHand: { type: "selector", default: "#player-right-controller" } - }, - - init: function() { - this.playAnimation = this.playAnimation.bind(this); - - this.mixer = this.el.components["animation-mixer"].mixer; - - const object3DMap = this.el.object3DMap; - const rootObj = object3DMap.mesh || object3DMap.scene; - this.clipActionObject = rootObj.parent; - - // Set hands to open pose because the bind pose is funky dues - // to the workaround for FBX2glTF animations. - this.openL = this.mixer.clipAction(POSES.open + "_L", this.clipActionObject); - this.openR = this.mixer.clipAction(POSES.open + "_R", this.clipActionObject); - this.openL.play(); - this.openR.play(); - }, - - play: function() { - this.data.leftHand.addEventListener("hand-pose", this.playAnimation); - this.data.rightHand.addEventListener("hand-pose", this.playAnimation); - }, - - pause: function() { - this.data.leftHand.removeEventListener("hand-pose", this.playAnimation); - this.data.rightHand.removeEventListener("hand-pose", this.playAnimation); - }, - - // Animate from pose to pose. - // TODO: Transition from current pose (which may be BETWEEN two other poses) - // to the target pose, rather than stopping previous actions altogether. - playAnimation: function(evt) { - const isLeft = evt.target === this.data.leftHand; - // Stop the initial animations we started when the model loaded. - if (!this.openLStopped && isLeft) { - this.openL.stop(); - this.openLStopped = true; - } else if (!this.openRStopped && !isLeft) { - this.openR.stop(); - this.openRStopped = true; - } - - const { current, previous } = evt.detail; - const mixer = this.mixer; - const suffix = isLeft ? "_L" : "_R"; - const prevPose = POSES[previous] + suffix; - const currPose = POSES[current] + suffix; - - // STOP previous actions playing for this hand. - if (this["pose" + suffix + "_to"] !== undefined) { - this["pose" + suffix + "_to"].stop(); - } - if (this["pose" + suffix + "_from"] !== undefined) { - this["pose" + suffix + "_from"].stop(); - } - - const duration = 0.065; - // console.log( - // `Animating ${isLeft ? "left" : "right"} hand from ${prevPose} to ${currPose} over ${duration} seconds.` - // ); - const from = mixer.clipAction(prevPose, this.clipActionObject); - const to = mixer.clipAction(currPose, this.clipActionObject); - from.fadeOut(duration); - to.fadeIn(duration); - to.play(); - from.play(); - // Update the mixer slightly to prevent one frame of the default pose - // from appearing. TODO: Find out why that happens - this.mixer.update(0.001); - - this["pose" + suffix + "_to"] = to; - this["pose" + suffix + "_from"] = from; - } -}); diff --git a/src/components/gltf-model-plus.js b/src/components/gltf-model-plus.js index d9ff8d8ff722388d3b0a63f4731d5541e91bde5e..41b6b50200ad273fdc5729866a9809245c3c23ff 100644 --- a/src/components/gltf-model-plus.js +++ b/src/components/gltf-model-plus.js @@ -154,8 +154,8 @@ function attachTemplate(root, { selector, templateRoot }) { } // Append all child elements - for (const child of root.children) { - el.appendChild(child); + while (root.children.length > 0) { + el.appendChild(root.children[0]); } } } diff --git a/src/components/hand-poses.js b/src/components/hand-poses.js new file mode 100644 index 0000000000000000000000000000000000000000..16d1f1479af6f4f4e17963084e6e135ecb82b942 --- /dev/null +++ b/src/components/hand-poses.js @@ -0,0 +1,75 @@ +const POSES = { + open: "allOpen", + thumbDown: "thumbDown", + indexDown: "indexDown", + mrpDown: "mrpDown", + thumbUp: "thumbsUp", + point: "point", + fist: "allGrip", + pinch: "pinch" +}; + +const NETWORK_POSES = ["allOpen", "thumbDown", "indexDown", "mrpDown", "thumbsUp", "point", "allGrip", "pinch"]; + +AFRAME.registerComponent("hand-pose", { + multiple: true, + schema: { + pose: { default: 0 } + }, + + init() { + this.animatePose = this.animatePose.bind(this); + this.mixer = this.el.components["animation-mixer"]; + const object3DMap = this.mixer.el.object3DMap; + const rootObj = object3DMap.mesh || object3DMap.scene; + this.clipActionObject = rootObj.parent; + const suffix = this.id == "left" ? "_L" : "_R"; + this.from = this.to = this.mixer.mixer.clipAction(POSES.open + suffix, this.clipActionObject); + this.from.play(); + }, + + update(oldData) { + if (oldData.pose != this.data.pose) { + this.animatePose(NETWORK_POSES[oldData.pose || 0], NETWORK_POSES[this.data.pose]); + } + }, + + animatePose(prev, curr) { + this.from.stop(); + this.to.stop(); + + const duration = 0.065; + const suffix = this.id == "left" ? "_L" : "_R"; + this.from = this.mixer.mixer.clipAction(prev + suffix, this.clipActionObject); + this.to = this.mixer.mixer.clipAction(curr + suffix, this.clipActionObject); + + this.from.fadeOut(duration); + this.to.fadeIn(duration); + this.to.play(); + this.from.play(); + + this.mixer.mixer.update(0.001); + } +}); + +AFRAME.registerComponent("hand-pose-controller", { + multiple: true, + schema: { + eventSrc: { type: "selector" } + }, + init: function() { + this.setHandPose = this.setHandPose.bind(this); + }, + + play: function() { + this.data.eventSrc.addEventListener("hand-pose", this.setHandPose); + }, + + pause: function() { + this.data.eventSrc.removeEventListener("hand-pose", this.setHandPose); + }, + + setHandPose: function(evt) { + this.el.setAttribute(`hand-pose__${this.id}`, "pose", NETWORK_POSES.indexOf(POSES[evt.detail.current])); + } +}); diff --git a/src/components/virtual-gamepad-controls.css b/src/components/virtual-gamepad-controls.css index d3e36e2fa243e0d9e35e693224cbd235420a0702..572e6169f6a29c911d0fa89e37382b10838da3c0 100644 --- a/src/components/virtual-gamepad-controls.css +++ b/src/components/virtual-gamepad-controls.css @@ -1,6 +1,6 @@ :local(.touchZone) { position: absolute; - top: 0; + height: 20vh; bottom: 0; } @@ -13,7 +13,3 @@ left: 50%; right: 0; } - -:local(.touchZone) .nipple { - margin: 5vh 5vw; -} diff --git a/src/components/virtual-gamepad-controls.js b/src/components/virtual-gamepad-controls.js index d70219bf1e6374fefc6d6daaeb9e8e10bfc27fb8..f92b7d4534f8e45e499edf6bcf1345f9e0f33374 100644 --- a/src/components/virtual-gamepad-controls.js +++ b/src/components/virtual-gamepad-controls.js @@ -16,29 +16,42 @@ AFRAME.registerComponent("virtual-gamepad-controls", { const leftStick = nipplejs.create({ zone: leftTouchZone, - mode: "static", color: "white", - position: { left: "50px", bottom: "50px" } + fadeTime: 0 }); const rightStick = nipplejs.create({ zone: rightTouchZone, - mode: "static", color: "white", - position: { right: "50px", bottom: "50px" } + fadeTime: 0 }); - this.onJoystickChanged = this.onJoystickChanged.bind(this); + this.onMoveJoystickChanged = this.onMoveJoystickChanged.bind(this); + this.onMoveJoystickEnd = this.onMoveJoystickEnd.bind(this); + this.onLookJoystickChanged = this.onLookJoystickChanged.bind(this); + this.onLookJoystickEnd = this.onLookJoystickEnd.bind(this); - rightStick.on("move end", this.onJoystickChanged); - leftStick.on("move end", this.onJoystickChanged); + leftStick.on("move", this.onMoveJoystickChanged); + leftStick.on("end", this.onMoveJoystickEnd); + + rightStick.on("move", this.onLookJoystickChanged); + rightStick.on("end", this.onLookJoystickEnd); this.leftTouchZone = leftTouchZone; this.rightTouchZone = rightTouchZone; this.leftStick = leftStick; this.rightStick = rightStick; - this.yaw = 0; + this.inVr = false; + this.moving = false; + this.rotating = false; + + this.moveEvent = { + axis: [0, 0] + }; + this.rotateYEvent = { + value: 0 + }; this.onEnterVr = this.onEnterVr.bind(this); this.onExitVr = this.onExitVr.bind(this); @@ -46,39 +59,59 @@ AFRAME.registerComponent("virtual-gamepad-controls", { this.el.sceneEl.addEventListener("exit-vr", this.onExitVr); }, - onJoystickChanged(event, joystick) { - if (event.target.id === this.leftStick.id) { - if (event.type === "move") { - const angle = joystick.angle.radian; - const force = joystick.force < 1 ? joystick.force : 1; - const x = Math.cos(angle) * force; - const z = Math.sin(angle) * force; - this.el.sceneEl.emit("move", { axis: [x, z] }); - } else { - this.el.sceneEl.emit("move", { axis: [0, 0] }); + onMoveJoystickChanged(event, joystick) { + const angle = joystick.angle.radian; + const force = joystick.force < 1 ? joystick.force : 1; + const x = Math.cos(angle) * force; + const z = Math.sin(angle) * force; + this.moving = true; + this.moveEvent.axis[0] = x; + this.moveEvent.axis[1] = z; + }, + + onMoveJoystickEnd() { + this.moving = false; + this.moveEvent.axis[0] = 0; + this.moveEvent.axis[1] = 0; + this.el.sceneEl.emit("move", this.moveEvent); + }, + + onLookJoystickChanged(event, joystick) { + // Set pitch and yaw angles on right stick move + const angle = joystick.angle.radian; + const force = joystick.force < 1 ? joystick.force : 1; + this.rotating = true; + this.rotateYEvent.value = Math.cos(angle) * force; + }, + + onLookJoystickEnd() { + this.rotating = false; + this.rotateYEvent.value = 0; + this.el.sceneEl.emit("rotateY", this.rotateYEvent); + }, + + tick() { + if (!this.inVr) { + if (this.moving) { + this.el.sceneEl.emit("move", this.moveEvent); } - } else { - if (event.type === "move") { - // Set pitch and yaw angles on right stick move - const angle = joystick.angle.radian; - const force = joystick.force < 1 ? joystick.force : 1; - this.yaw = Math.cos(angle) * force; - this.el.sceneEl.emit("rotateY", { value: this.yaw }); - } else { - this.yaw = 0; - this.el.sceneEl.emit("rotateY", { value: this.yaw }); + + if (this.rotating) { + this.el.sceneEl.emit("rotateY", this.rotateYEvent); } } }, onEnterVr() { // Hide the joystick controls + this.inVr = true; this.leftTouchZone.style.display = "none"; this.rightTouchZone.style.display = "none"; }, onExitVr() { // Show the joystick controls + this.inVr = false; this.leftTouchZone.style.display = "block"; this.rightTouchZone.style.display = "block"; }, diff --git a/src/hub.html b/src/hub.html index 0575e5b0156ddeac6b68a603c6fe53690c56c38f..0f6df33ece622b7d5f3bea8ff007628213623893 100644 --- a/src/hub.html +++ b/src/hub.html @@ -63,7 +63,7 @@ <a-entity class="model" gltf-model-plus="inflate: true"> <template data-selector=".RootScene"> - <a-entity ik-controller animation-mixer space-invader-mesh="meshSelector: .Bot_Skinned"></a-entity> + <a-entity ik-controller hand-pose__left hand-pose__right animation-mixer space-invader-mesh="meshSelector: .Bot_Skinned"></a-entity> </template> <template data-selector=".Neck"> @@ -157,7 +157,7 @@ <!-- Player Rig --> <a-entity id="player-rig" - networked="template: #remote-avatar-template; attachLocalTemplate: false;" + networked="template: #remote-avatar-template; attachTemplateToLocal: false;" spawn-controller="radius: 4;" wasd-to-analog2d character-controller="pivot: #player-camera" @@ -227,8 +227,11 @@ <template data-selector=".RootScene"> <a-entity ik-controller - animated-robot-hands animation-mixer + hand-pose__left + hand-pose__right + hand-pose-controller__left="eventSrc:#player-left-controller" + hand-pose-controller__right="eventSrc:#player-right-controller" ></a-entity> </template> @@ -275,7 +278,12 @@ ></a-entity> <!-- Environment --> - <a-entity id="environment-root" position="0 0 0" nav-mesh-helper></a-entity> + <a-entity + id="environment-root" + nav-mesh-helper + static-body="shape: none;" + class="collidable" + ></a-entity> <a-entity id="skybox" @@ -294,23 +302,6 @@ xr="ar: false" ></a-entity> - <a-cylinder - position="0 0.45 0" - material="visible: false" - height="1" radius="3.1" - segments-radial="12" - static-body - class="collidable" - ></a-cylinder> - - <a-plane - material="visible: false" - rotation="-90 0 0" - height="35" - width="35" - static-body - class="collidable" - ></a-plane> </a-scene> <div id="ui-root"></div> diff --git a/src/hub.js b/src/hub.js index 6f80de46a67fb1165190874a46324505ae77a565..89c03be300ad7bf9122ef0837cb6546f11a05ea0 100644 --- a/src/hub.js +++ b/src/hub.js @@ -1,5 +1,8 @@ import "./assets/stylesheets/hub.scss"; +import moment from "moment-timezone"; +import uuid from "uuid/v4"; import queryString from "query-string"; +import { Socket } from "phoenix"; import { patchWebGLRenderingContext } from "./utils/webgl"; patchWebGLRenderingContext(); @@ -15,7 +18,7 @@ import "aframe-rounded"; import "webrtc-adapter"; import trackpad_dpad4 from "./behaviours/trackpad-dpad4"; -import { joystick_dpad4 } from "./behaviours/joystick-dpad4"; +import joystick_dpad4 from "./behaviours/joystick-dpad4"; import { PressedMove } from "./activators/pressedmove"; import { ReverseY } from "./activators/reversey"; import "./activators/shortpress"; @@ -37,12 +40,12 @@ import "./components/water"; import "./components/skybox"; import "./components/layers"; import "./components/spawn-controller"; -import "./components/animated-robot-hands"; import "./components/hide-when-quality"; import "./components/player-info"; import "./components/debug"; import "./components/animation-mixer"; import "./components/loop-animation"; +import "./components/hand-poses"; import "./components/gltf-model-plus"; import "./components/gltf-bundle"; import "./components/hud-controller"; @@ -50,6 +53,7 @@ import "./components/hud-controller"; import ReactDOM from "react-dom"; import React from "react"; import UIRoot from "./react-components/ui-root"; +import HubChannel from "./utils/hub-channel"; import "./systems/personal-space-bubble"; import "./systems/app-mode"; @@ -106,6 +110,7 @@ AFRAME.registerInputMappings(inputConfig, true); const store = new Store(); const concurrentLoadDetector = new ConcurrentLoadDetector(); +const hubChannel = new HubChannel(store); concurrentLoadDetector.start(); @@ -113,6 +118,7 @@ concurrentLoadDetector.start(); store.update({ profile: { ...generateDefaultProfile(), ...(store.state.profile || {}) } }); async function exitScene() { + hubChannel.disconnect(); const scene = document.querySelector("a-scene"); scene.renderer.animate(null); // Stop animation loop, TODO A-Frame should do this document.body.removeChild(scene); @@ -150,7 +156,7 @@ async function enterScene(mediaStream, enterInVR, janusRoomId) { scene.setAttribute("stats", true); } - if (isMobile || qsTruthy(qs.mobile)) { + if (isMobile || qsTruthy("mobile")) { playerRig.setAttribute("virtual-gamepad-controls", {}); } @@ -186,6 +192,12 @@ async function enterScene(mediaStream, enterInVR, janusRoomId) { }); if (!qsTruthy("offline")) { + document.body.addEventListener("connected", () => { + hubChannel.sendEntryEvent().then(() => { + store.update({ lastEnteredAt: moment().toJSON() }); + }); + }); + scene.components["networked-scene"].connect(); if (mediaStream) { @@ -276,15 +288,32 @@ const onReady = async () => { return; } - const hubId = document.location.pathname.substring(1).split("/")[0]; + // Connect to reticulum over phoenix channels to get hub info. + const hubId = qs.hub_id || document.location.pathname.substring(1).split("/")[0]; console.log(`Hub ID: ${hubId}`); - const res = await fetch(`/api/v1/hubs/${hubId}`); - const data = await res.json(); - const hub = data.hubs[0]; - const defaultSpaceTopic = hub.topics[0]; - const gltfBundleUrl = defaultSpaceTopic.assets.find(a => a.asset_type === "gltf_bundle").src; - uiRoot.setState({ janusRoomId: defaultSpaceTopic.janus_room_id }); - initialEnvironmentEl.setAttribute("gltf-bundle", `src: ${gltfBundleUrl}`); + + const socketProtocol = document.location.protocol === "https:" ? "wss:" : "ws:"; + const socketPort = qs.phx_port || document.location.port; + const socketHost = qs.phx_host || document.location.hostname; + const socketUrl = `${socketProtocol}//${socketHost}${socketPort ? `:${socketPort}` : ""}/socket`; + console.log(`Phoenix Channel URL: ${socketUrl}`); + + const socket = new Socket(socketUrl, { params: { session_id: uuid() } }); + socket.connect(); + + const channel = socket.channel(`hub:${hubId}`, {}); + + channel + .join() + .receive("ok", data => { + const hub = data.hubs[0]; + const defaultSpaceTopic = hub.topics[0]; + const gltfBundleUrl = defaultSpaceTopic.assets.find(a => a.asset_type === "gltf_bundle").src; + uiRoot.setState({ janusRoomId: defaultSpaceTopic.janus_room_id }); + initialEnvironmentEl.setAttribute("gltf-bundle", `src: ${gltfBundleUrl}`); + hubChannel.setPhoenixChannel(channel); + }) + .receive("error", res => console.error(res)); }; document.addEventListener("DOMContentLoaded", onReady); diff --git a/src/network-schemas.js b/src/network-schemas.js index 54cc0b63ba40efed17baf43c0d3554b1ea2fa4bc..822951bedb782de1ba401ae20c00232f47b61412 100644 --- a/src/network-schemas.js +++ b/src/network-schemas.js @@ -3,9 +3,22 @@ function registerNetworkSchemas() { template: "#remote-avatar-template", components: [ "position", - "rotation", + { + component: "rotation", + lerp: false + }, "scale", "player-info", + { + selector: ".RootScene", + component: "hand-pose__left", + property: "pose" + }, + { + selector: ".RootScene", + component: "hand-pose__right", + property: "pose" + }, { selector: ".camera", component: "position" diff --git a/src/react-components/avatar-selector.js b/src/react-components/avatar-selector.js index d88340f04532b89d07512d38bb3f292c78e4e844..528d5b81558e37f12aea6a0182c4cc08d8971782 100644 --- a/src/react-components/avatar-selector.js +++ b/src/react-components/avatar-selector.js @@ -20,8 +20,8 @@ class AvatarSelector extends Component { const numAvatars = this.props.avatars.length; return ((currAvatarIndex + direction) % numAvatars + numAvatars) % numAvatars; }; - nextAvatarIndex = () => this.getAvatarIndex(1); - previousAvatarIndex = () => this.getAvatarIndex(-1); + nextAvatarIndex = () => this.getAvatarIndex(-1); + previousAvatarIndex = () => this.getAvatarIndex(1); emitChangeToNext = () => { const nextAvatarId = this.props.avatars[this.nextAvatarIndex()].id; @@ -38,7 +38,17 @@ class AvatarSelector extends Component { // HACK - a-animation ought to restart the animation when the `to` attribute changes, but it doesn't // so we need to force it here. const currRot = this.animation.parentNode.getAttribute("rotation"); - this.animation.setAttribute("from", `${currRot.x} ${currRot.y} ${currRot.z}`); + const currY = currRot.y; + const toRot = String.split(this.animation.attributes.to.value, " "); + const toY = toRot[1]; + const step = 360.0 / this.props.avatars.length; + const brokenlyBigRotation = Math.abs(toY - currY) > 3 * step; + let fromY = currY; + if (brokenlyBigRotation) { + // Rotation in Y wrapped around 360. Adjust the "from" to prevent a dramatic rotation + fromY = currY < toY ? currY + 360 : currY - 360; + } + this.animation.setAttribute("from", `${currRot.x} ${fromY} ${currRot.z}`); this.animation.stop(); this.animation.handleMixinUpdate(); this.animation.start(); @@ -83,7 +93,7 @@ class AvatarSelector extends Component { attribute="rotation" dur="1000" easing="ease-out" - to={`0 ${360 * this.getAvatarIndex() / this.props.avatars.length + 180} 0`} + to={`0 ${(360 * this.getAvatarIndex() / this.props.avatars.length + 180) % 360} 0`} /> {avatarEntities} </a-entity> diff --git a/src/storage/store.js b/src/storage/store.js index b9f7366abf0aa01d0cec7836bcef541bf3fe8ee8..2c79a06604a08b0e1a72e1b3fca2a2d4264d80c5 100644 --- a/src/storage/store.js +++ b/src/storage/store.js @@ -28,7 +28,8 @@ export const SCHEMA = { properties: { id: { type: "string", pattern: "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$" }, profile: { $ref: "#/definitions/profile" }, - lastUsedMicDeviceId: { type: "string" } + lastUsedMicDeviceId: { type: "string" }, + lastEnteredAt: { type: "string" } }, additionalProperties: false diff --git a/src/utils/hub-channel.js b/src/utils/hub-channel.js new file mode 100644 index 0000000000000000000000000000000000000000..13e51b21b1e1f3702432a7a04c6804949c332b88 --- /dev/null +++ b/src/utils/hub-channel.js @@ -0,0 +1,75 @@ +import moment from "moment-timezone"; + +export default class HubChannel { + constructor(store) { + this.store = store; + } + + setPhoenixChannel = channel => { + this.channel = channel; + }; + + sendEntryEvent = async () => { + if (!this.channel) { + console.warn("No phoenix channel initialized before room entry."); + return; + } + + let entryDisplayType = "Screen"; + + if (navigator.getVRDisplays) { + const vrDisplay = (await navigator.getVRDisplays()).find(d => d.isPresenting); + + if (vrDisplay) { + entryDisplayType = vrDisplay.displayName; + } + } + + // This is fairly hacky, but gets the # of initial occupants + let initialOccupantCount = 0; + + if (NAF.connection.adapter && NAF.connection.adapter.publisher) { + initialOccupantCount = NAF.connection.adapter.publisher.initialOccupants.length; + } + + const entryTimingFlags = this.getEntryTimingFlags(); + + const entryEvent = { + ...entryTimingFlags, + initialOccupantCount, + entryDisplayType, + userAgent: navigator.userAgent + }; + + this.channel.push("events:entered", entryEvent); + }; + + getEntryTimingFlags = () => { + const entryTimingFlags = { isNewDaily: true, isNewMonthly: true, isNewDayWindow: true, isNewMonthWindow: true }; + + if (!this.store.state.lastEnteredAt) { + return entryTimingFlags; + } + + const lastEntered = moment(this.store.state.lastEnteredAt); + const lastEnteredPst = moment(lastEntered).tz("America/Los_Angeles"); + const nowPst = moment().tz("America/Los_Angeles"); + const dayWindowAgo = moment().subtract(1, "day"); + const monthWindowAgo = moment().subtract(1, "month"); + + entryTimingFlags.isNewDaily = + lastEnteredPst.dayOfYear() !== nowPst.dayOfYear() || lastEnteredPst.year() !== nowPst.year(); + entryTimingFlags.isNewMonthly = + lastEnteredPst.month() !== nowPst.month() || lastEnteredPst.year() !== nowPst.year(); + entryTimingFlags.isNewDayWindow = lastEntered.isBefore(dayWindowAgo); + entryTimingFlags.isNewMonthWindow = lastEntered.isBefore(monthWindowAgo); + + return entryTimingFlags; + }; + + disconnect = () => { + if (this.channel) { + this.channel.socket.disconnect(); + } + }; +} diff --git a/yarn.lock b/yarn.lock index 9e4e6fbae46ef483424f8b4ada1dadce72744951..2f53dc8b77829473fcf0cc14e978f0eb226eaaaa 100644 --- a/yarn.lock +++ b/yarn.lock @@ -118,7 +118,7 @@ accepts@1.3.3: mime-types "~2.1.11" negotiator "0.6.1" -accepts@~1.3.4: +accepts@~1.3.4, accepts@~1.3.5: version "1.3.5" resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.5.tgz#eb777df6011723a3b14e8a72c0805c8e86746bd2" dependencies: @@ -2007,14 +2007,18 @@ colormin@^1.0.5: css-color-names "0.0.4" has "^1.0.1" -colors@*, colors@^1.1.2, colors@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/colors/-/colors-1.1.2.tgz#168a4701756b6a7f51a12ce0c97bfa28c084ed63" +colors@*: + version "1.2.1" + resolved "https://registry.yarnpkg.com/colors/-/colors-1.2.1.tgz#f4a3d302976aaf042356ba1ade3b1a2c62d9d794" colors@1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/colors/-/colors-1.0.3.tgz#0433f44d809680fdeb60ed260f1b0c262e82a40b" +colors@^1.1.2, colors@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/colors/-/colors-1.1.2.tgz#168a4701756b6a7f51a12ce0c97bfa28c084ed63" + combine-source-map@~0.7.1: version "0.7.2" resolved "https://registry.yarnpkg.com/combine-source-map/-/combine-source-map-0.7.2.tgz#0870312856b307a87cc4ac486f3a9a62aeccc09e" @@ -3072,7 +3076,42 @@ expand-tilde@^2.0.0, expand-tilde@^2.0.2: dependencies: homedir-polyfill "^1.0.1" -express@^4.10.7, express@^4.16.2: +express@^4.10.7: + version "4.16.3" + resolved "https://registry.yarnpkg.com/express/-/express-4.16.3.tgz#6af8a502350db3246ecc4becf6b5a34d22f7ed53" + dependencies: + accepts "~1.3.5" + array-flatten "1.1.1" + body-parser "1.18.2" + content-disposition "0.5.2" + content-type "~1.0.4" + cookie "0.3.1" + cookie-signature "1.0.6" + debug "2.6.9" + depd "~1.1.2" + encodeurl "~1.0.2" + escape-html "~1.0.3" + etag "~1.8.1" + finalhandler "1.1.1" + fresh "0.5.2" + merge-descriptors "1.0.1" + methods "~1.1.2" + on-finished "~2.3.0" + parseurl "~1.3.2" + path-to-regexp "0.1.7" + proxy-addr "~2.0.3" + qs "6.5.1" + range-parser "~1.2.0" + safe-buffer "5.1.1" + send "0.16.2" + serve-static "1.13.2" + setprototypeof "1.1.0" + statuses "~1.4.0" + type-is "~1.6.16" + utils-merge "1.0.1" + vary "~1.1.2" + +express@^4.16.2: version "4.16.2" resolved "https://registry.yarnpkg.com/express/-/express-4.16.2.tgz#e35c6dfe2d64b7dca0a5cd4f21781be3299e076c" dependencies: @@ -3278,6 +3317,18 @@ finalhandler@1.1.0: statuses "~1.3.1" unpipe "~1.0.0" +finalhandler@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.1.tgz#eebf4ed840079c83f4249038c9d703008301b105" + dependencies: + debug "2.6.9" + encodeurl "~1.0.2" + escape-html "~1.0.3" + on-finished "~2.3.0" + parseurl "~1.3.2" + statuses "~1.4.0" + unpipe "~1.0.0" + find-cache-dir@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-1.0.0.tgz#9288e3e9e3cc3748717d39eade17cf71fc30ee6f" @@ -3971,7 +4022,7 @@ http-deceiver@^1.2.7: version "1.2.7" resolved "https://registry.yarnpkg.com/http-deceiver/-/http-deceiver-1.2.7.tgz#fa7168944ab9a519d337cb0bec7284dc3e723d87" -http-errors@1.6.2, http-errors@~1.6.2: +http-errors@1.6.2: version "1.6.2" resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.2.tgz#0a002cc85707192a7e7946ceedc11155f60ec736" dependencies: @@ -3980,6 +4031,15 @@ http-errors@1.6.2, http-errors@~1.6.2: setprototypeof "1.0.3" statuses ">= 1.3.1 < 2" +http-errors@~1.6.2: + version "1.6.3" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.3.tgz#8b55680bb4be283a0b5bf4ea2e38580be1d9320d" + dependencies: + depd "~1.1.2" + inherits "2.0.3" + setprototypeof "1.1.0" + statuses ">= 1.4.0 < 2" + http-parser-js@>=0.4.0: version "0.4.10" resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.4.10.tgz#92c9c1374c35085f75db359ec56cc257cbb93fa4" @@ -5244,6 +5304,16 @@ module-deps@^6.0.0: through2 "^2.0.0" xtend "^4.0.0" +moment-timezone@^0.5.14: + version "0.5.14" + resolved "https://registry.yarnpkg.com/moment-timezone/-/moment-timezone-0.5.14.tgz#4eb38ff9538b80108ba467a458f3ed4268ccfcb1" + dependencies: + moment ">= 2.9.0" + +"moment@>= 2.9.0", moment@^2.22.0: + version "2.22.0" + resolved "https://registry.yarnpkg.com/moment/-/moment-2.22.0.tgz#7921ade01017dd45186e7fee5f424f0b8663a730" + move-concurrently@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/move-concurrently/-/move-concurrently-1.0.1.tgz#be2c005fda32e0b29af1f05d7c4b33214c701f92" @@ -5345,9 +5415,9 @@ neo-async@^2.5.0: version "2.5.0" resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.5.0.tgz#76b1c823130cca26acfbaccc8fbaf0a2fa33b18f" -"networked-aframe@github:mozillareality/networked-aframe#mr-social-client/master": +"networked-aframe@https://github.com/mozillareality/networked-aframe#mr-social-client/master": version "0.6.1" - resolved "https://codeload.github.com/mozillareality/networked-aframe/tar.gz/69be0e7e5f66070526c8240cb795b9e88da971a9" + resolved "https://github.com/mozillareality/networked-aframe#69be0e7e5f66070526c8240cb795b9e88da971a9" dependencies: easyrtc "1.1.0" express "^4.10.7" @@ -5950,6 +6020,10 @@ performance-now@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-0.2.0.tgz#33ef30c5c77d4ea21c5a53869d91b56d8f2555e5" +phoenix@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/phoenix/-/phoenix-1.3.0.tgz#1df2c27f986ee295e37c9983ec28ebac1d7f4a3e" + pify@^2.0.0, pify@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" @@ -6321,7 +6395,7 @@ prop-types@^15.5.4, prop-types@^15.6.0: loose-envify "^1.3.1" object-assign "^4.1.1" -proxy-addr@~2.0.2: +proxy-addr@~2.0.2, proxy-addr@~2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.3.tgz#355f262505a621646b3130a728eb647e22055341" dependencies: @@ -7089,7 +7163,7 @@ serve-static@1.13.1: parseurl "~1.3.2" send "0.16.1" -serve-static@^1.10.0, serve-static@^1.8.0: +serve-static@1.13.2, serve-static@^1.10.0, serve-static@^1.8.0: version "1.13.2" resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.13.2.tgz#095e8472fd5b46237db50ce486a43f4b86c6cec1" dependencies: @@ -7465,7 +7539,11 @@ static-extend@^0.1.1: define-property "^0.2.5" object-copy "^0.1.0" -"statuses@>= 1.3.1 < 2", statuses@~1.3.1: +"statuses@>= 1.3.1 < 2", "statuses@>= 1.4.0 < 2": + version "1.5.0" + resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" + +statuses@~1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.3.1.tgz#faf51b9eb74aaef3b3acf4ad5f61abf24cb7b93e" @@ -7897,7 +7975,7 @@ type-check@~0.3.2: dependencies: prelude-ls "~1.1.2" -type-is@~1.6.15: +type-is@~1.6.15, type-is@~1.6.16: version "1.6.16" resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.16.tgz#f89ce341541c672b25ee7ae3c73dee3b2be50194" dependencies: