diff --git a/src/AudioContext.js b/src/AudioContext.js new file mode 100644 index 0000000000000000000000000000000000000000..8d1554ff5eb63f27e5c96da5cd3122c96393f63e --- /dev/null +++ b/src/AudioContext.js @@ -0,0 +1,7 @@ +import React from "react"; +export const AudioContext = React.createContext({ + onMouseEnter: () => { + console.log("enter sound not configured"); + }, + onMouseLeave: () => {} +}); diff --git a/src/assets/sfx/237716__hydranos__beeph.mp3 b/src/assets/sfx/237716__hydranos__beeph.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..37190370c6ca0e9352cfce9b207034ecee746757 Binary files /dev/null and b/src/assets/sfx/237716__hydranos__beeph.mp3 differ diff --git a/src/assets/sfx/238270__meroleroman7__robot.wav b/src/assets/sfx/238270__meroleroman7__robot.wav new file mode 100644 index 0000000000000000000000000000000000000000..ca7207f96e584e4a918f13b9c99ba197a7280930 Binary files /dev/null and b/src/assets/sfx/238270__meroleroman7__robot.wav differ diff --git a/src/assets/sfx/431532__supersound23__popping.mp3 b/src/assets/sfx/431532__supersound23__popping.mp3 new file mode 100755 index 0000000000000000000000000000000000000000..0a5a6d0a3b59e76df4c630f9c9d2696892f0bbfe Binary files /dev/null and b/src/assets/sfx/431532__supersound23__popping.mp3 differ diff --git a/src/assets/sfx/HUBS_GOODBYE_Happy_Tech.wav b/src/assets/sfx/HUBS_GOODBYE_Happy_Tech.wav new file mode 100755 index 0000000000000000000000000000000000000000..04992a2245c1444958792d2e80d4bda264d4ef48 Binary files /dev/null and b/src/assets/sfx/HUBS_GOODBYE_Happy_Tech.wav differ diff --git a/src/assets/sfx/HUBS_WELCOME_Arrival.wav b/src/assets/sfx/HUBS_WELCOME_Arrival.wav new file mode 100755 index 0000000000000000000000000000000000000000..4e152532c4221e29565f7908a088f099bb93485c Binary files /dev/null and b/src/assets/sfx/HUBS_WELCOME_Arrival.wav differ diff --git a/src/assets/sfx/HUBS_WELCOME_Complex_Synth.wav b/src/assets/sfx/HUBS_WELCOME_Complex_Synth.wav new file mode 100755 index 0000000000000000000000000000000000000000..932f84c861a5ff876648419adaccbb5f5147025b Binary files /dev/null and b/src/assets/sfx/HUBS_WELCOME_Complex_Synth.wav differ diff --git a/src/assets/sfx/HUBS_WELCOME_Happy_Tech.wav b/src/assets/sfx/HUBS_WELCOME_Happy_Tech.wav new file mode 100755 index 0000000000000000000000000000000000000000..c0e398252d000d744f0fee5d9b59d89dbdcab41c Binary files /dev/null and b/src/assets/sfx/HUBS_WELCOME_Happy_Tech.wav differ diff --git a/src/assets/sfx/Object_Grow_New.wav b/src/assets/sfx/Object_Grow_New.wav new file mode 100755 index 0000000000000000000000000000000000000000..5513a526cfb794bd87fc7fdb5d8227020e896c93 Binary files /dev/null and b/src/assets/sfx/Object_Grow_New.wav differ diff --git a/src/assets/sfx/Object_Lock_New.wav b/src/assets/sfx/Object_Lock_New.wav new file mode 100755 index 0000000000000000000000000000000000000000..19dcf0bd0730cfda7d9b4d9a62c3f1517613874a Binary files /dev/null and b/src/assets/sfx/Object_Lock_New.wav differ diff --git a/src/assets/sfx/footfall_click.wav b/src/assets/sfx/footfall_click.wav new file mode 100644 index 0000000000000000000000000000000000000000..2d661cbdca152960a4a0831c141d574492102c98 Binary files /dev/null and b/src/assets/sfx/footfall_click.wav differ diff --git a/src/assets/sfx/snap_rotate.wav b/src/assets/sfx/snap_rotate.wav new file mode 100755 index 0000000000000000000000000000000000000000..dd9320a790d18f914aacb3ecacd03c3f0327d2e6 Binary files /dev/null and b/src/assets/sfx/snap_rotate.wav differ diff --git a/src/assets/sfx/tap_mellow.aif b/src/assets/sfx/tap_mellow.aif new file mode 100644 index 0000000000000000000000000000000000000000..3e39298e64ca5c157d1d32df3cfece94235bcc17 Binary files /dev/null and b/src/assets/sfx/tap_mellow.aif differ diff --git a/src/assets/sfx/tap_mellow.wav b/src/assets/sfx/tap_mellow.wav new file mode 100644 index 0000000000000000000000000000000000000000..a3fe32e27827a038dde848ece74f8c6914fd3ea0 Binary files /dev/null and b/src/assets/sfx/tap_mellow.wav differ diff --git a/src/assets/sfx/teleport_cancel.wav b/src/assets/sfx/teleport_cancel.wav new file mode 100755 index 0000000000000000000000000000000000000000..d19009ca8f38970196ae5541ef7613d5efe4ae05 Binary files /dev/null and b/src/assets/sfx/teleport_cancel.wav differ diff --git a/src/assets/sfx/teleport_end.wav b/src/assets/sfx/teleport_end.wav new file mode 100755 index 0000000000000000000000000000000000000000..02dd434b4d476de5f9f74f9ac034a4e94e399a19 Binary files /dev/null and b/src/assets/sfx/teleport_end.wav differ diff --git a/src/assets/sfx/teleport_start.wav b/src/assets/sfx/teleport_start.wav new file mode 100755 index 0000000000000000000000000000000000000000..702badd2838dde1ecd4eb01e8122e203e3686585 Binary files /dev/null and b/src/assets/sfx/teleport_start.wav differ diff --git a/src/avatar-selector.js b/src/avatar-selector.js index 1ded01b457cd2a0d05cdd9a0ed9535b4fdd36a45..3d5be9e49643c767fb12a61067f0fc49513457ef 100644 --- a/src/avatar-selector.js +++ b/src/avatar-selector.js @@ -16,6 +16,7 @@ import "./components/animation-mixer"; import "./components/audio-feedback"; import "./components/loop-animation"; import "./components/gamma-factor"; +import "./components/scene-sound"; import "./gltf-component-mappings"; import { avatars } from "./assets/avatars/avatars"; diff --git a/src/components/camera-tool.js b/src/components/camera-tool.js index cc6bbefb233cecc6d28980fd2d1630c5d1730952..d229c35f985fab178eede531dbfb76e7a7b377f3 100644 --- a/src/components/camera-tool.js +++ b/src/components/camera-tool.js @@ -139,6 +139,7 @@ AFRAME.registerComponent("camera-tool", { sceneEl.emit("object_spawned", { objectType: ObjectTypes.CAMERA }); }); }); + sceneEl.emit("camera_tool_took_snapshot"); this.takeSnapshotNextTick = false; } }; diff --git a/src/components/cursor-controller.js b/src/components/cursor-controller.js index 421b084c309710e9442dd3f44019fdcb0cad94d5..c796a1348fa037a0f0f7df5b812f465ee1de2d06 100644 --- a/src/components/cursor-controller.js +++ b/src/components/cursor-controller.js @@ -229,10 +229,12 @@ AFRAME.registerComponent("cursor-controller", { const targetDistanceMod = this.currentDistanceMod + delta; const moddedDistance = this.currentDistance - targetDistanceMod; if (moddedDistance > far || moddedDistance < near) { + this.el.emit("cursor-distance-change-blocked"); return false; } this.currentDistanceMod = targetDistanceMod; + this.el.emit("cursor-distance-changed"); return true; }, diff --git a/src/components/emit-state-change.js b/src/components/emit-state-change.js new file mode 100644 index 0000000000000000000000000000000000000000..b3357cd3911f26b8165235ca06e32e64d8bf0ac0 --- /dev/null +++ b/src/components/emit-state-change.js @@ -0,0 +1,33 @@ +AFRAME.registerComponent("emit-state-change", { + multiple: true, + schema: { + state: { type: "string" }, + transform: { type: "string", oneof: ["rising", "falling"] }, + event: { type: "string" } + }, + + init() { + this.stateadded = this.stateadded.bind(this); + this.stateremoved = this.stateremoved.bind(this); + }, + + stateadded(e) { + if (e.detail === this.data.state) { + this.el.emit(this.data.event); + } + }, + stateremoved(e) { + if (e.detail === this.data.state) { + this.el.emit(this.data.event); + } + }, + + update() { + if (this.data.transform === "rising") { + this.el.addEventListener("stateadded", this.stateadded); + } + if (this.data.transform === "falling") { + this.el.addEventListener("stateremoved", this.stateadded); + } + } +}); diff --git a/src/components/scene-sound.js b/src/components/scene-sound.js new file mode 100644 index 0000000000000000000000000000000000000000..430eda4c0b52b0a703158362f0d2fd75ff5f62df --- /dev/null +++ b/src/components/scene-sound.js @@ -0,0 +1,12 @@ +AFRAME.registerComponent("scene-sound", { + multiple: true, + schema: { + sound: { type: "string" }, + on: { type: "string" } + }, + + init() { + const sound = this.el.components[`${this.attrName.replace("scene-", "")}`]; + this.el.sceneEl.addEventListener(this.data.on, sound.playSound); + } +}); diff --git a/src/components/tools/networked-drawing.js b/src/components/tools/networked-drawing.js index 518de9cd82489111ce6585861df99cfd26776015..f4fe0a0de5a484bb636b165e48ad42802c0e398a 100644 --- a/src/components/tools/networked-drawing.js +++ b/src/components/tools/networked-drawing.js @@ -381,6 +381,7 @@ AFRAME.registerComponent("networked-drawing", { })(), _endLine() { + this.el.emit("stop_draw"); if (!this.drawStarted) return; if (this.networkedEl && NAF.utils.isMine(this.networkedEl)) this._pushToNetworkBuffer(null); diff --git a/src/components/tools/pen.js b/src/components/tools/pen.js index 855c94e0a1d11f5905dd20f55acdda27ebad227d..edeb6e9e748db54f7ed0b3674dc064523f6dcbd6 100644 --- a/src/components/tools/pen.js +++ b/src/components/tools/pen.js @@ -120,13 +120,14 @@ AFRAME.registerComponent("pen", { if (this.currentDrawing) { this.el.object3D.getWorldPosition(this.worldPosition); this._getNormal(this.normal, this.worldPosition, this.direction); - + this.el.emit("start_draw"); this.currentDrawing.startDraw(this.worldPosition, this.direction, this.normal, this.data.color, this.data.radius); } }, _endDraw() { if (this.currentDrawing) { + this.el.emit("stop_draw"); this.timeSinceLastDraw = 0; this.el.object3D.getWorldPosition(this.worldPosition); this._getNormal(this.normal, this.worldPosition, this.direction); @@ -151,18 +152,23 @@ AFRAME.registerComponent("pen", { switch (evt.detail) { case "activated": this._startDraw(); + this.el.emit("start_draw"); break; case "colorNext": this._changeColor(1); + this.el.emit("next_pen_color"); break; case "colorPrev": this._changeColor(-1); + this.el.emit("prev_pen_color"); break; case "radiusUp": this._changeRadius(this.data.minRadius); + this.el.emit("increase_pen_radius"); break; case "radiusDown": this._changeRadius(-this.data.minRadius); + this.el.emit("decrease_pen_radius"); break; case "grabbed": this.grabbed = true; diff --git a/src/hub.html b/src/hub.html index 4884abca46af25a088c333af4fab7ad673731ec9..0eb0a47548b3ec8b85641a4be2ca9671e1d3c1fc 100644 --- a/src/hub.html +++ b/src/hub.html @@ -62,6 +62,7 @@ <img id="spawn-pen-hover" crossorigin="anonymous" src="./assets/hud/spawn_pen-hover.png"> <img id="spawn-camera" crossorigin="anonymous" src="./assets/hud/spawn_camera.png"> <img id="spawn-camera-hover" crossorigin="anonymous" src="./assets/hud/spawn_camera-hover.png"> + <img id="water-normal-map" crossorigin="anonymous" src="./assets/waternormals.jpg"> <a-asset-item id="botdefault" response-type="arraybuffer" src="https://asset-bundles-prod.reticulum.io/bots/BotDefault_Avatar-9f71f8ff22.gltf"></a-asset-item> <a-asset-item id="botbobo" response-type="arraybuffer" src="https://asset-bundles-prod.reticulum.io/bots/BotBobo_Avatar-f9740a010b.gltf"></a-asset-item> @@ -74,11 +75,32 @@ <a-asset-item id="botrobert" response-type="arraybuffer" src="https://asset-bundles-prod.reticulum.io/bots/BotRobert_Avatar-e9554880f3.gltf"></a-asset-item> <a-asset-item id="botwoody" response-type="arraybuffer" src="https://asset-bundles-prod.reticulum.io/bots/BotWoody_Avatar-0140485a23.gltf"></a-asset-item> - <a-asset-item id="quack" src="./assets/sfx/quack.mp3" response-type="arraybuffer" preload="auto"></a-asset-item> - <a-asset-item id="specialquack" src="./assets/sfx/specialquack.mp3" response-type="arraybuffer" preload="auto"></a-asset-item> - - <img id="water-normal-map" crossorigin="anonymous" src="./assets/waternormals.jpg"> - + <a-asset-item id="quack" src="./assets/sfx/quack.mp3" response-type="arraybuffer" preload="auto"></a-asset-item> + <a-asset-item id="specialquack" src="./assets/sfx/specialquack.mp3" response-type="arraybuffer" preload="auto"></a-asset-item> + <a-asset-item id="sound_asset-teleport_start" src="./assets/sfx/tap_mellow.wav" response-type="arraybuffer" preload="auto"></a-asset-item> + <a-asset-item id="sound_asset-teleport_end" src="./assets/sfx/tap_mellow.wav" response-type="arraybuffer" preload="auto"></a-asset-item> + <a-asset-item id="sound_asset-snap_rotate" src="./assets/sfx/tap_mellow.wav" response-type="arraybuffer" preload="auto"></a-asset-item> + <a-asset-item id="sound_asset-media_loaded" src="./assets/sfx/tap_mellow.wav" response-type="arraybuffer" preload="auto"></a-asset-item> + <a-asset-item id="sound_asset-hud_mouseout" src="./assets/sfx/footfall_click.wav" response-type="arraybuffer" preload="auto"></a-asset-item> + <a-asset-item id="sound_asset-hud_mouseover" src="./assets/sfx/tap_mellow.wav" response-type="arraybuffer" preload="auto"></a-asset-item> + <a-asset-item id="sound_asset-hover" src="./assets/sfx/tap_mellow.wav" response-type="arraybuffer" preload="auto"></a-asset-item> + <a-asset-item id="sound_asset-hover_off" src="./assets/sfx/footfall_click.wav" response-type="arraybuffer" preload="auto"></a-asset-item> + <a-asset-item id="sound_asset-cursor_distance_change_blocked" src="./assets/sfx/tap_mellow.wav" response-type="arraybuffer" preload="auto"></a-asset-item> + <a-asset-item id="sound_asset-cursor_distance_changed" src="./assets/sfx/footfall_click.wav" response-type="arraybuffer" preload="auto"></a-asset-item> + <a-asset-item id="sound_asset-hud_click" src="./assets/sfx/tap_mellow.wav" response-type="arraybuffer" preload="auto"></a-asset-item> + <a-asset-item id="sound_asset-toggle_mute" src="./assets/sfx/tap_mellow.wav" response-type="arraybuffer" preload="auto"></a-asset-item> + <a-asset-item id="sound_asset-toggle_freeze" src="./assets/sfx/tap_mellow.wav" response-type="arraybuffer" preload="auto"></a-asset-item> + <a-asset-item id="sound_asset-toggle_space_bubble" src="./assets/sfx/tap_mellow.wav" response-type="arraybuffer" preload="auto"></a-asset-item> + <a-asset-item id="sound_asset-spawn_pen" src="./assets/sfx/tap_mellow.wav" response-type="arraybuffer" preload="auto"></a-asset-item> + <a-asset-item id="sound_asset-increase_pen_radius" src="./assets/sfx/tap_mellow.wav" response-type="arraybuffer" preload="auto"></a-asset-item> + <a-asset-item id="sound_asset-decrease_pen_radius" src="./assets/sfx/tap_mellow.wav" response-type="arraybuffer" preload="auto"></a-asset-item> + <a-asset-item id="sound_asset-next_pen_color" src="./assets/sfx/tap_mellow.wav" response-type="arraybuffer" preload="auto"></a-asset-item> + <a-asset-item id="sound_asset-prev_pen_color" src="./assets/sfx/footfall_click.wav" response-type="arraybuffer" preload="auto"></a-asset-item> + <a-asset-item id="sound_asset-start_draw" src="./assets/sfx/tap_mellow.wav" response-type="arraybuffer" preload="auto"></a-asset-item> + <a-asset-item id="sound_asset-stop_draw" src="./assets/sfx/footfall_click.wav" response-type="arraybuffer" preload="auto"></a-asset-item> + <a-asset-item id="sound_asset-camera_tool_took_snapshot" src="./assets/sfx/footfall_click.wav" response-type="arraybuffer" preload="auto"></a-asset-item> + + <!-- <a-asset-item id="watch-model" response-type="arraybuffer" src="./assets/hud/watch.glb"></a-asset-item> --> <!-- Templates --> <template id="video-template"> <a-entity class="video" geometry="primitive: plane;" material="side: double; shader: flat;" networked-video-player></a-entity> @@ -161,6 +183,10 @@ rotation activatable__increase-scale="buttonStartEvents: scroll_right; buttonEndEvents: horizontal_scroll_release; activatedState: scaleUp;" activatable__decrease-scale="buttonStartEvents: scroll_left; buttonEndEvents: horizontal_scroll_release; activatedState: scaleDown;" + sound__hover="src: #sound_asset-hover; on: hovered; poolSize: 1;" + sound__hoveroff ="src: #sound_asset-hover_off; on: unhovered; poolSize: 1;" + emit-state-change__hovered="state: hovered; transform: rising; event: hovered;" + emit-state-change__unhovered="state: hovered; transform: falling; event: unhovered;" > <!-- HACK: rotation component above is required for its side effect of setting YXZ order --> <a-entity class="delete-button" visible-while-frozen> @@ -185,6 +211,16 @@ activatable__increase-radius="buttonStartEvents: increase_radius, scroll_up; buttonEndEvents: thumb_up, secondary_hand_release, vertical_scroll_release; activatedState: radiusUp;" activatable__decrease-radius="buttonStartEvents: decrease_radius, scroll_down; buttonEndEvents: thumb_up, secondary_hand_release, vertical_scroll_release; activatedState: radiusDown;" scale="0.5 0.5 0.5" + sound__next_pen_color="src: #sound_asset-next_pen_color; on: next_pen_color; poolSize: 2;" + sound__prev_pen_color="src: #sound_asset-prev_pen_color; on: prev_pen_color; poolSize: 2;" + sound__start_draw="src: #sound_asset-start_draw; on: start_draw; poolSize: 2;" + sound__stop_draw="src: #sound_asset-stop_draw; on: stop_draw; poolSize: 2;" + sound__increase_pen_radius="src: #sound_asset-increase_pen_radius; on: increase_pen_radius; poolSize: 2;" + sound__decrease_pen_radius="src: #sound_asset-decrease_pen_radius; on: decrease_pen_radius; poolSize: 2;" + sound__hover="src: #sound_asset-hover; on: hovered; poolSize: 1;" + sound__hoveroff ="src: #sound_asset-hover_off; on: unhovered; poolSize: 1;" + emit-state-change__hovered="state: hovered; transform: rising; event: hovered;" + emit-state-change__unhovered="state: hovered; transform: falling; event: unhovered;" > <a-sphere id="pen" @@ -341,10 +377,45 @@ <a-rounded height="0.08" width="0.5" color="#000000" position="-0.20 0.125 0" radius="0.040" opacity="0.35" class="hud bg"></a-rounded> <a-entity id="hud-hub-entry-link" text=" value:; width:1.1; align:center;" position="0.05 0.165 0"></a-entity> <a-rounded height="0.13" width="0.59" color="#000000" position="-0.24 -0.065 0" radius="0.065" opacity="0.35" class="hud bg"></a-rounded> - <a-image icon-button="tooltip: #hud-tooltip; tooltipText: Mute Mic; activeTooltipText: Unmute Mic; image: #mute-off; hoverImage: #mute-off-hover; activeImage: #mute-on; activeHoverImage: #mute-on-hover" scale="0.1 0.1 0.1" position="-0.17 0 0.001" class="ui hud mic" material="alphaTest:0.1;"></a-image> - <a-image icon-button="tooltip: #hud-tooltip; tooltipText: Pause; activeTooltipText: Resume; image: #freeze-off; hoverImage: #freeze-off-hover; activeImage: #freeze-on; activeHoverImage: #freeze-on-hover" scale="0.2 0.2 0.2" position="0 0 0.005" class="ui hud freeze"></a-image> - <a-image icon-button="tooltip: #hud-tooltip; tooltipText: Pen; activeTooltipText: Pen; image: #spawn-pen; hoverImage: #spawn-pen-hover; activeImage: #spawn-pen; activeHoverImage: #spawn-pen-hover" scale="0.1 0.1 0.1" position="0.17 0 0.001" class="ui hud pen" material="alphaTest:0.1;"></a-image> - <a-image icon-button="tooltip: #hud-tooltip; tooltipText: Camera; activeTooltipText: Camera; image: #spawn-camera; hoverImage: #spawn-camera-hover; activeImage: #spawn-camera; activeHoverImage: #spawn-camera-hover" scale="0.1 0.1 0.1" position="0.28 0 0.001" class="ui hud cameraBtn" material="alphaTest:0.1;"></a-image> + <a-image + icon-button="tooltip: #hud-tooltip; tooltipText: Mute Mic; activeTooltipText: Unmute Mic; image: #mute-off; hoverImage: #mute-off-hover; activeImage: #mute-on; activeHoverImage: #mute-on-hover" + scale="0.1 0.1 0.1" + position="-0.17 0 0.001" + class="ui hud mic" + material="alphaTest:0.1;" + sound__hud_mouseover="src: #sound_asset-hud_mouseover; on: mouseover; poolSize: 5;" + sound__hud_mouseout="src: #sound_asset-hud_mouseout; on: mouseout; poolSize: 5;" + sound__hud_click="src: #sound_asset-hud_click; on: click; poolSize: 3;" + ></a-image> + <a-image + icon-button="tooltip: #hud-tooltip; tooltipText: Pause; activeTooltipText: Resume; image: #freeze-off; hoverImage: #freeze-off-hover; activeImage: #freeze-on; activeHoverImage: #freeze-on-hover" + scale="0.2 0.2 0.2" + position="0 0 0.005" + class="ui hud freeze" + sound__hud_mouseover="src: #sound_asset-hud_mouseover; on: mouseover; poolSize: 5;" + sound__hud_mouseout="src: #sound_asset-hud_mouseout; on: mouseout; poolSize: 5;" + sound__hud_click="src: #sound_asset-hud_click; on: click; poolSize: 3;" + ></a-image> + <a-image + icon-button="tooltip: #hud-tooltip; tooltipText: Spawn Pen; activeTooltipText: Spawn Pen; image: #spawn-pen; hoverImage: #spawn-pen-hover; activeImage: #spawn-pen; activeHoverImage: #spawn-pen-hover" + scale="0.1 0.1 0.1" + position="0.17 0 0.001" + class="ui hud pen" + material="alphaTest:0.1;" + sound__hud_mouseover="src: #sound_asset-hud_mouseover; on: mouseover; poolSize: 5;" + sound__hud_mouseout="src: #sound_asset-hud_mouseout; on: mouseout; poolSize: 5;" + sound__hud_click="src: #sound_asset-hud_click; on: click; poolSize: 3;" + ></a-image> + <a-image + icon-button="tooltip: #hud-tooltip; tooltipText: Spawn Camera; activeTooltipText: Spawn Camera; image: #spawn-camera; hoverImage: #spawn-camera-hover; activeImage: #spawn-camera; activeHoverImage: #spawn-camera-hover" + scale="0.1 0.1 0.1" + position="0.28 0 0.001" + class="ui hud cameraBtn" + material="alphaTest:0.1;" + sound__hud_mouseover="src: #sound_asset-hud_mouseover; on: mouseover; poolSize: 5;" + sound__hud_mouseout="src: #sound_asset-hud_mouseout; on: mouseout; poolSize: 5;" + sound__hud_click="src: #sound_asset-hud_click; on: click; poolSize: 3;" + ></a-image> <a-rounded visible="false" id="hud-tooltip" height="0.08" width="0.3" color="#000000" position="-0.15 -0.2 0" rotation="-20 0 0" radius="0.025" opacity="0.35" class="hud bg"> <a-entity text="value: Mute Mic; align:center;" position="0.15 0.04 0.001" ></a-entity> </a-rounded> @@ -357,6 +428,38 @@ camera personal-space-bubble="radius: 0.4;" pitch-yaw-rotator + sound__teleport_start="src: #sound_asset-teleport_start; on: nothing; poolSize: 2;" + scene-sound__teleport_start="on: cursor-teleport_down;" + sound__teleport_end="src: #sound_asset-teleport_end; on: nothing; poolSize: 2;" + scene-sound__teleport_end="on: cursor-teleport_up;" + sound__snap_rotate_left="src: #sound_asset-snap_rotate; on: nothing; poolSize: 3;" + scene-sound__snap_rotate_left="on: snap_rotate_left;" + sound__snap_rotate_right="src: #sound_asset-snap_rotate; on: nothing; poolSize: 3;" + scene-sound__snap_rotate_right="on: snap_rotate_right;" + sound__model_loaded="src: #sound_asset-media_loaded; on: nothing; poolSize: 2;" + scene-sound__model_loaded="on: model-loaded;" + sound__image_loaded="src: #sound_asset-media_loaded; on: nothing; poolSize: 2;" + scene-sound__image_loaded="on: image-loaded;" + sound__hud_action_mute="src: #sound_asset-toggle_mute; on: nothing; poolSize: 2;" + scene-sound__hud_action_mute="on: action_mute;" + sound__hud_action_freeze="src: #sound_asset-toggle_freeze; on: nothing; poolSize: 2;" + scene-sound__hud_action_freeze="on: action_freeze;" + sound__hud_action_space_bubble="src: #sound_asset-toggle_space_bubble; on: nothing; poolSize: 2;" + scene-sound__hud_action_space_bubble="on: action_space_bubble;" + sound__hud_spawn_pen="src: #sound_asset-spawn_pen; on: nothing; poolSize: 2;" + scene-sound__hud_spawn_pen="on: spawn_pen;" + sound__hud_mouseover="src: #sound_asset-hud_mouseover; on: nothing; poolSize: 1;" + scene-sound__hud_mouseover="on: play_sound-hud_mouse_enter;" + sound__hud_mouseout="src: #sound_asset-hud_mouseout; on: nothing; poolSize: 1;" + scene-sound__hud_mouseout="on: play_sound-hud_mouse_leave;" + sound__hud_click="src: #sound_asset-hud_click; on: nothing; poolSize: 1;" + scene-sound__hud_click="on: hud_click;" + sound__cursor_distance_changed="src: #sound_asset-cursor_distance_changed; on: nothing; poolSize: 1;" + scene-sound__cursor_distance_changed="on: cursor-distance-changed;" + sound__cursor_distance_change_blocked="src: #sound_asset-cursor_distance_change_blocked; on: nothing; poolSize: 1;" + scene-sound__cursor_distance_change_blocked="on: cursor-distance-change-blocked;" + sound__camera_tool_took_snapshot="src: #sound_asset-camera_tool_took_snapshot; on: nothing; poolSize: 1;" + scene-sound__camera_tool_took_snapshot="on: camera_tool_took_snapshot;" > <a-entity id="gaze-teleport" diff --git a/src/hub.js b/src/hub.js index a091cb4249135153be71241510caff22f42745ee..b7a1b3d8f7854dbb19260fdd4fd15702d5e874c9 100644 --- a/src/hub.js +++ b/src/hub.js @@ -70,6 +70,8 @@ import "./components/destroy-at-extreme-distances"; import "./components/gamma-factor"; import "./components/visible-to-owner"; import "./components/camera-tool"; +import "./components/scene-sound"; +import "./components/emit-state-change"; import ReactDOM from "react-dom"; import React from "react"; diff --git a/src/react-components/2d-hud.js b/src/react-components/2d-hud.js index 74577ff1ec0291d8bd14665fe0a7054c8a0f70e3..d40e661c9a63338ae027ff44d004fc906d86147c 100644 --- a/src/react-components/2d-hud.js +++ b/src/react-components/2d-hud.js @@ -4,28 +4,49 @@ import cx from "classnames"; import styles from "../assets/stylesheets/2d-hud.scss"; import uiStyles from "../assets/stylesheets/ui-root.scss"; +import { AudioContext } from "../AudioContext"; const TopHUD = ({ muted, frozen, onToggleMute, onToggleFreeze, onSpawnPen, onSpawnCamera }) => ( - <div className={cx(styles.container, styles.top, styles.unselectable)}> - <div className={cx(uiStyles.uiInteractive, styles.panel, styles.left)}> - <div - className={cx(styles.iconButton, styles.mute, { [styles.active]: muted })} - title={muted ? "Unmute Mic" : "Mute Mic"} - onClick={onToggleMute} - /> - </div> - <div - className={cx(uiStyles.uiInteractive, styles.iconButton, styles.large, styles.freeze, { - [styles.active]: frozen - })} - title={frozen ? "Resume" : "Pause"} - onClick={onToggleFreeze} - /> - <div className={cx(uiStyles.uiInteractive, styles.panel, styles.right)}> - <div className={cx(styles.iconButton, styles.spawn_pen)} title={"Drawing Pen"} onClick={onSpawnPen} /> - <div className={cx(styles.iconButton, styles.spawn_camera)} title={"Camera"} onClick={onSpawnCamera} /> - </div> - </div> + <AudioContext.Consumer> + {audio => ( + <div className={cx(styles.container, styles.top, styles.unselectable)}> + <div className={cx(uiStyles.uiInteractive, styles.panel, styles.left)}> + <div + className={cx(styles.iconButton, styles.mute, { [styles.active]: muted })} + title={muted ? "Unmute Mic" : "Mute Mic"} + onClick={onToggleMute} + onMouseEnter={audio.onMouseEnter} + onMouseLeave={audio.onMouseLeave} + /> + </div> + <div + className={cx(uiStyles.uiInteractive, styles.iconButton, styles.large, styles.freeze, { + [styles.active]: frozen + })} + title={frozen ? "Resume" : "Pause"} + onClick={onToggleFreeze} + onMouseEnter={audio.onMouseEnter} + onMouseLeave={audio.onMouseLeave} + /> + <div className={cx(uiStyles.uiInteractive, styles.panel, styles.right)}> + <div + className={cx(styles.iconButton, styles.spawn_pen)} + title={"Drawing Pen"} + onClick={onSpawnPen} + onMouseEnter={audio.onMouseEnter} + onMouseLeave={audio.onMouseLeave} + /> + <div + className={cx(styles.iconButton, styles.spawn_camera)} + title={"Camera"} + onClick={onSpawnCamera} + onMouseEnter={audio.onMouseEnter} + onMouseLeave={audio.onMouseLeave} + /> + </div> + </div> + )} + </AudioContext.Consumer> ); TopHUD.propTypes = { @@ -38,36 +59,47 @@ TopHUD.propTypes = { }; const BottomHUD = ({ onCreateObject, showPhotoPicker, onMediaPicked }) => ( - <div className={cx(styles.container, styles.column, styles.bottom, styles.unselectable)}> - {showPhotoPicker ? ( - <div className={cx(uiStyles.uiInteractive, styles.panel, styles.up)}> - <input - id="media-picker-input" - className={cx(styles.hide)} - type="file" - accept="image/*" - multiple - onChange={e => { - for (const file of e.target.files) { - onMediaPicked(file); - } - }} - /> - <label htmlFor="media-picker-input"> - <div className={cx(styles.iconButton, styles.mobileMediaPicker)} title={"Pick Media"} /> - </label> + <AudioContext.Consumer> + {audio => ( + <div className={cx(styles.container, styles.column, styles.bottom, styles.unselectable)}> + {showPhotoPicker ? ( + <div className={cx(uiStyles.uiInteractive, styles.panel, styles.up)}> + <input + id="media-picker-input" + className={cx(styles.hide)} + type="file" + accept="image/*" + multiple + onChange={e => { + for (const file of e.target.files) { + onMediaPicked(file); + } + }} + /> + <label htmlFor="media-picker-input"> + <div + className={cx(styles.iconButton, styles.mobileMediaPicker)} + title={"Pick Media"} + onMouseEnter={audio.onMouseEnter} + onMouseLeave={audio.onMouseLeave} + /> + </label> + </div> + ) : ( + <div /> + )} + <div> + <div + className={cx(uiStyles.uiInteractive, styles.iconButton, styles.large, styles.createObject)} + title={"Create Object"} + onClick={onCreateObject} + onMouseEnter={audio.onMouseEnter} + onMouseLeave={audio.onMouseLeave} + /> + </div> </div> - ) : ( - <div /> )} - <div> - <div - className={cx(uiStyles.uiInteractive, styles.iconButton, styles.large, styles.createObject)} - title={"Create Object"} - onClick={onCreateObject} - /> - </div> - </div> + </AudioContext.Consumer> ); BottomHUD.propTypes = { diff --git a/src/react-components/auto-exit-warning.js b/src/react-components/auto-exit-warning.js index d8691a85182c7f8bbd3ee9a7472e428df9aa2d3b..4f54111ed0290f3e15bc1112c4df5181fd3aef14 100644 --- a/src/react-components/auto-exit-warning.js +++ b/src/react-components/auto-exit-warning.js @@ -2,20 +2,31 @@ import React from "react"; import { FormattedMessage } from "react-intl"; import PropTypes from "prop-types"; +import { AudioContext } from "../AudioContext"; + const AutoExitWarning = props => ( - <div className="autoexit-panel"> - <div className="autoexit-panel__title"> - <FormattedMessage id="autoexit.title" /> - <span>{props.secondsRemaining}</span> - <FormattedMessage id="autoexit.title_units" /> - </div> - <div className="autoexit-panel__subtitle"> - <FormattedMessage id="autoexit.subtitle" /> - </div> - <div className="autoexit-panel__cancel-button" onClick={props.onCancel}> - <FormattedMessage id="autoexit.cancel" /> - </div> - </div> + <AudioContext.Consumer> + {audio => ( + <div className="autoexit-panel"> + <div className="autoexit-panel__title"> + <FormattedMessage id="autoexit.title" /> + <span>{props.secondsRemaining}</span> + <FormattedMessage id="autoexit.title_units" /> + </div> + <div className="autoexit-panel__subtitle"> + <FormattedMessage id="autoexit.subtitle" /> + </div> + <div + className="autoexit-panel__cancel-button" + onClick={props.onCancel} + onMouseEnter={audio.onMouseEnter} + onMouseLeave={audio.onMouseLeave} + > + <FormattedMessage id="autoexit.cancel" /> + </div> + </div> + )} + </AudioContext.Consumer> ); AutoExitWarning.propTypes = { diff --git a/src/react-components/avatar-selector.js b/src/react-components/avatar-selector.js index 0c024655d46b39c77bf7c5f50e87733907cbd790..314b45d74f95ecb14091c14d2f6d95cfc922ec0a 100644 --- a/src/react-components/avatar-selector.js +++ b/src/react-components/avatar-selector.js @@ -4,6 +4,7 @@ import { injectIntl } from "react-intl"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faAngleLeft } from "@fortawesome/free-solid-svg-icons/faAngleLeft"; import { faAngleRight } from "@fortawesome/free-solid-svg-icons/faAngleRight"; +import { AudioContext } from "../AudioContext"; class AvatarSelector extends Component { static propTypes = { @@ -176,12 +177,30 @@ class AvatarSelector extends Component { /> <a-entity hide-when-quality="low" light="type: ambient; color: #FFF" /> </a-scene> - <button className="avatar-selector__previous-button" onClick={this.emitChangeToPrevious}> - <FontAwesomeIcon icon={faAngleLeft} /> - </button> - <button className="avatar-selector__next-button" onClick={this.emitChangeToNext}> - <FontAwesomeIcon icon={faAngleRight} /> - </button> + <AudioContext.Consumer> + {audio => ( + <button + className="avatar-selector__previous-button" + onClick={this.emitChangeToPrevious} + onMouseEnter={audio.onMouseEnter} + onMouseLeave={audio.onMouseLeave} + > + <FontAwesomeIcon icon={faAngleLeft} /> + </button> + )} + </AudioContext.Consumer> + <AudioContext.Consumer> + {audio => ( + <button + className="avatar-selector__next-button" + onClick={this.emitChangeToNext} + onMouseEnter={audio.onMouseEnter} + onMouseLeave={audio.onMouseLeave} + > + <FontAwesomeIcon icon={faAngleRight} /> + </button> + )} + </AudioContext.Consumer> </div> ); } diff --git a/src/react-components/create-object-dialog.js b/src/react-components/create-object-dialog.js index 5ccfe2ce692a234fff6e76d8657516c1ac1d8816..46b1d95d12ff6bcaa0d7d856cb94516be8b9bc68 100644 --- a/src/react-components/create-object-dialog.js +++ b/src/react-components/create-object-dialog.js @@ -7,6 +7,7 @@ import { faTimes } from "@fortawesome/free-solid-svg-icons/faTimes"; import styles from "../assets/stylesheets/create-object-dialog.scss"; import cx from "classnames"; import DialogContainer from "./dialog-container.js"; +import { AudioContext } from "../AudioContext"; const attributionHostnames = { "giphy.com": giphyLogo, @@ -87,14 +88,32 @@ export default class CreateObjectDialog extends Component { const { onCreate, onClose, ...other } = this.props; // eslint-disable-line no-unused-vars const cancelButton = ( - <label className={cx(styles.smallButton, styles.cancelIcon)} onClick={this.reset}> - <FontAwesomeIcon icon={faTimes} /> - </label> + <AudioContext.Consumer> + {audio => ( + <label + className={cx(styles.smallButton, styles.cancelIcon)} + onClick={this.reset} + onMouseEnter={audio.onMouseEnter} + onMouseLeave={audio.onMouseLeave} + > + <FontAwesomeIcon icon={faTimes} /> + </label> + )} + </AudioContext.Consumer> ); const uploadButton = ( - <label htmlFor={fileInputId} className={cx(styles.smallButton, styles.uploadIcon)}> - <FontAwesomeIcon icon={faPaperclip} /> - </label> + <AudioContext.Consumer> + {audio => ( + <label + htmlFor={fileInputId} + className={cx(styles.smallButton, styles.uploadIcon)} + onMouseEnter={audio.onMouseEnter} + onMouseLeave={audio.onMouseLeave} + > + <FontAwesomeIcon icon={faPaperclip} /> + </label> + )} + </AudioContext.Consumer> ); const filenameLabel = <label className={cx(styles.leftSideOfInput)}>{this.state.fileName}</label>; const urlInput = ( @@ -108,7 +127,7 @@ export default class CreateObjectDialog extends Component { ); return ( - <DialogContainer title="Create Object" onClose={onClose} {...other}> + <DialogContainer title="Create Object" onClose={this.props.onClose} {...other}> <div> {isMobile ? mobileInstructions : desktopInstructions} <form onSubmit={this.onCreateClicked}> @@ -125,9 +144,17 @@ export default class CreateObjectDialog extends Component { {this.state.url || this.state.fileName ? cancelButton : uploadButton} </div> <div className={styles.buttons}> - <button className={styles.actionButton}> - <span>Create</span> - </button> + <AudioContext.Consumer> + {audio => ( + <button + className={styles.actionButton} + onMouseEnter={audio.onMouseEnter} + onMouseLeave={audio.onMouseLeave} + > + <span>Create</span> + </button> + )} + </AudioContext.Consumer> </div> {this.state.attributionImage ? ( <div> diff --git a/src/react-components/create-room-dialog.js b/src/react-components/create-room-dialog.js index 756cc9bf43736059999a1a66b2fdfe5d856a4e96..15e61023ae2cbc13b78640138db39c463936cdcb 100644 --- a/src/react-components/create-room-dialog.js +++ b/src/react-components/create-room-dialog.js @@ -1,10 +1,11 @@ import React, { Component } from "react"; import PropTypes from "prop-types"; import DialogContainer from "./dialog-container.js"; +import { AudioContext } from "../AudioContext"; const HUB_NAME_PATTERN = "^[A-Za-z0-9-'\":!@#$%^&*(),.?~ ]{4,64}$"; -export default class CreateObjectDialog extends Component { +export default class CreateRoomDialog extends Component { static propTypes = { onCustomScene: PropTypes.func, onClose: PropTypes.func @@ -46,9 +47,17 @@ export default class CreateObjectDialog extends Component { onChange={e => this.setState({ customSceneUrl: e.target.value })} /> <div className="custom-scene-form__buttons"> - <button className="custom-scene-form__action-button"> - <span>create</span> - </button> + <AudioContext.Consumer> + {audio => ( + <button + className="custom-scene-form__action-button" + onMouseEnter={audio.onMouseEnter} + onMouseLeave={audio.onMouseLeave} + > + <span>create</span> + </button> + )} + </AudioContext.Consumer> </div> </div> </form> diff --git a/src/react-components/dialog-container.js b/src/react-components/dialog-container.js index 18c37956c7408150d59d2385e251817ad9747cb7..f57aa92ccdcf02edd0bf19168e3ef7c64c698edc 100644 --- a/src/react-components/dialog-container.js +++ b/src/react-components/dialog-container.js @@ -1,5 +1,6 @@ import React, { Component } from "react"; import PropTypes from "prop-types"; +import { AudioContext } from "../AudioContext"; export default class DialogContainer extends Component { static propTypes = { @@ -37,20 +38,29 @@ export default class DialogContainer extends Component { render() { return ( <div className="dialog-overlay"> - <div className="dialog" onClick={this.onContainerClicked}> - <div className="dialog__box"> - <div className="dialog__box__contents"> - {this.props.onClose && ( - <button className="dialog__box__contents__close" onClick={this.props.onClose}> - <span>×</span> - </button> - )} - <div className="dialog__box__contents__title">{this.props.title}</div> - <div className="dialog__box__contents__body">{this.props.children}</div> - <div className="dialog__box__contents__button-container" /> + <AudioContext.Consumer> + {audio => ( + <div className="dialog" onClick={this.onContainerClicked}> + <div className="dialog__box"> + <div className="dialog__box__contents"> + {this.props.onClose && ( + <button + className="dialog__box__contents__close" + onClick={this.props.onClose} + onMouseEnter={audio.onMouseEnter} + onMouseLeave={audio.onMouseLeave} + > + <span>×</span> + </button> + )} + <div className="dialog__box__contents__title">{this.props.title}</div> + <div className="dialog__box__contents__body">{this.props.children}</div> + <div className="dialog__box__contents__button-container" /> + </div> + </div> </div> - </div> - </div> + )} + </AudioContext.Consumer> </div> ); } diff --git a/src/react-components/entry-buttons.js b/src/react-components/entry-buttons.js index 9e64b3ab4c419b6728e1351e7080629f1b6a97ea..469572e40dcdedc7429fca48deaa1325475726b5 100644 --- a/src/react-components/entry-buttons.js +++ b/src/react-components/entry-buttons.js @@ -1,6 +1,7 @@ import React from "react"; import { FormattedMessage } from "react-intl"; import PropTypes from "prop-types"; +import { AudioContext } from "../AudioContext"; import MobileScreenEntryImg from "../assets/images/mobile_screen_entry.svg"; import DesktopScreenEntryImg from "../assets/images/desktop_screen_entry.svg"; @@ -11,24 +12,33 @@ import DeviceEntryImg from "../assets/images/device_entry.svg"; import styles from "../assets/stylesheets/entry.scss"; const EntryButton = props => ( - <button className={styles.entryButton} onClick={props.onClick}> - <img src={props.iconSrc} className={styles.icon} /> - <div className={styles.label}> - <div className={styles.contents}> - <span> - <FormattedMessage id={props.prefixMessageId} /> - </span> - <span className={styles.bolded}> - <FormattedMessage id={props.mediumMessageId} /> - </span> - {props.subtitle && ( - <div className={styles.subtitle}> - <FormattedMessage id={props.subtitle} /> + <AudioContext.Consumer> + {audio => ( + <button + className={styles.entryButton} + onClick={props.onClick} + onMouseEnter={audio.onMouseEnter} + onMouseLeave={audio.onMouseLeave} + > + <img src={props.iconSrc} className={styles.icon} /> + <div className={styles.label}> + <div className={styles.contents}> + <span> + <FormattedMessage id={props.prefixMessageId} /> + </span> + <span className={styles.bolded}> + <FormattedMessage id={props.mediumMessageId} /> + </span> + {props.subtitle && ( + <div className={styles.subtitle}> + <FormattedMessage id={props.subtitle} /> + </div> + )} </div> - )} - </div> - </div> - </button> + </div> + </button> + )} + </AudioContext.Consumer> ); EntryButton.propTypes = { diff --git a/src/react-components/help-dialog.js b/src/react-components/help-dialog.js index 7e1fb4ba46801f921159a853acc41b5737a64331..ba6da9a9711a3bb944976b011483d898567ac135 100644 --- a/src/react-components/help-dialog.js +++ b/src/react-components/help-dialog.js @@ -1,6 +1,7 @@ import React, { Component } from "react"; import { FormattedMessage } from "react-intl"; import DialogContainer from "./dialog-container.js"; +import { AudioContext } from "../AudioContext"; export default class HelpDialog extends Component { render() { @@ -21,17 +22,39 @@ export default class HelpDialog extends Component { <p> The <b>Pause/Resume Toggle</b> pauses all other avatars and lets you block others or remove objects. </p> - <p className="dialog__box__contents__links"> - <a target="_blank" rel="noopener noreferrer" href="https://github.com/mozilla/hubs/blob/master/TERMS.md"> - <FormattedMessage id="profile.terms_of_use" /> - </a> - <a target="_blank" rel="noopener noreferrer" href="https://github.com/mozilla/hubs/blob/master/PRIVACY.md"> - <FormattedMessage id="profile.privacy_notice" /> - </a> - <a target="_blank" rel="noopener noreferrer" href="/?report"> - <FormattedMessage id="help.report_issue" /> - </a> - </p> + <AudioContext.Consumer> + {audio => ( + <p className="dialog__box__contents__links"> + <a + target="_blank" + rel="noopener noreferrer" + href="https://github.com/mozilla/hubs/blob/master/TERMS.md" + onMouseEnter={audio.onMouseEnter} + onMouseLeave={audio.onMouseLeave} + > + <FormattedMessage id="profile.terms_of_use" /> + </a> + <a + target="_blank" + rel="noopener noreferrer" + href="https://github.com/mozilla/hubs/blob/master/PRIVACY.md" + onMouseEnter={audio.onMouseEnter} + onMouseLeave={audio.onMouseLeave} + > + <FormattedMessage id="profile.privacy_notice" /> + </a> + <a + target="_blank" + rel="noopener noreferrer" + href="/?report" + onMouseEnter={audio.onMouseEnter} + onMouseLeave={audio.onMouseLeave} + > + <FormattedMessage id="help.report_issue" /> + </a> + </p> + )} + </AudioContext.Consumer> </div> </DialogContainer> ); diff --git a/src/react-components/home-root.js b/src/react-components/home-root.js index b7b36ecc74829b3b3a7e7bda7901ebfe073e7e63..49ccd30da1da604e2aac5cb1298271275cefc009 100644 --- a/src/react-components/home-root.js +++ b/src/react-components/home-root.js @@ -20,6 +20,7 @@ import ReportDialog from "./report-dialog.js"; import SlackDialog from "./slack-dialog.js"; import UpdatesDialog from "./updates-dialog.js"; import DialogContainer from "./dialog-container.js"; +import { AudioContext } from "../AudioContext"; addLocaleData([...en]); @@ -180,119 +181,146 @@ class HomeRoot extends Component { }); return ( - <IntlProvider locale={lang} messages={messages}> - <div className={styles.home}> - <div className={mainContentClassNames}> - <div className={styles.headerContent}> - <div className={styles.titleAndNav}> - <div className={styles.links}> - <a - href="https://blog.mozvr.com/introducing-hubs-a-new-way-to-get-together-online/" - rel="noreferrer noopener" - > - <FormattedMessage id="home.about_link" /> - </a> - <a href="https://github.com/mozilla/hubs" rel="noreferrer noopener"> - <FormattedMessage id="home.source_link" /> - </a> - </div> - </div> - <div className={styles.ident} /> - </div> - <div className={styles.heroContent}> - <div className={styles.attribution}> - Medieval Fantasy Book by{" "} - <a - target="_blank" - rel="noreferrer noopener" - href="https://sketchfab.com/models/06d5a80a04fc4c5ab552759e9a97d91a?utm_campaign=06d5a80a04fc4c5ab552759e9a97d91a&utm_medium=embed&utm_source=oembed" - > - Pixel - </a> - </div> - <div className={styles.container}> - <img className={styles.logo} src={hubLogo} /> - <div className={styles.title}> - <FormattedMessage id="home.hero_title" /> + <AudioContext.Consumer> + {audio => ( + <IntlProvider locale={lang} messages={messages}> + <div className={styles.home}> + <div className={mainContentClassNames}> + <div className={styles.headerContent}> + <div className={styles.titleAndNav} onClick={() => (document.location = "/")}> + <div className={styles.hubs} onMouseEnter={audio.onMouseEnter} onMouseLeave={audio.onMouseLeave}> + hubs + </div> + <div className={styles.preview} onMouseEnter={audio.onMouseEnter} onMouseLeave={audio.onMouseLeave}> + preview + </div> + <div className={styles.links}> + <a + href="https://blog.mozvr.com/introducing-hubs-a-new-way-to-get-together-online/" + rel="noreferrer noopener" + onMouseEnter={audio.onMouseEnter} + onMouseLeave={audio.onMouseLeave} + > + <FormattedMessage id="home.about_link" /> + </a> + <a + href="https://github.com/mozilla/hubs" + rel="noreferrer noopener" + onMouseEnter={audio.onMouseEnter} + onMouseLeave={audio.onMouseLeave} + > + <FormattedMessage id="home.source_link" /> + </a> + </div> + </div> + <div className={styles.ident} /> </div> - {this.state.environments.length === 0 && ( - <div className="loader-wrap"> - <div className="loader"> - <div className="loader-center" /> + <div className={styles.heroContent}> + <div className={styles.attribution}> + Medieval Fantasy Book by{" "} + <a + target="_blank" + rel="noreferrer noopener" + href="https://sketchfab.com/models/06d5a80a04fc4c5ab552759e9a97d91a?utm_campaign=06d5a80a04fc4c5ab552759e9a97d91a&utm_medium=embed&utm_source=oembed" + > + Pixel + </a> + </div> + <div className={styles.container}> + <img className={styles.logo} src={hubLogo} /> + <div className={styles.title}> + <FormattedMessage id="home.hero_title" /> </div> + {this.state.environments.length === 0 && ( + <div className="loader-wrap"> + <div className="loader"> + <div className="loader-center" /> + </div> + </div> + )} </div> - )} - </div> - <div className={styles.create}> - <HubCreatePanel - initialEnvironment={this.props.initialEnvironment} - environments={this.state.environments} - /> - </div> - {this.state.environments.length > 1 && ( - <div className={styles.joinButton}> - <a href="/link"> - <FormattedMessage id="home.join_room" /> - </a> + <div className={styles.create}> + <HubCreatePanel + initialEnvironment={this.props.initialEnvironment} + environments={this.state.environments} + /> + </div> + {this.state.environments.length > 1 && ( + <div className={styles.joinButton}> + <a href="/link" onMouseEnter={audio.onMouseEnter} onMouseLeave={audio.onMouseLeave}> + <FormattedMessage id="home.join_room" /> + </a> + </div> + )} </div> - )} - </div> - <div className={styles.footerContent}> - <div className={styles.links}> - <div className={styles.top}> - <a - className={styles.link} - rel="noopener noreferrer" - href="#" - onClick={this.onDialogLinkClicked(this.showSlackDialog.bind(this))} - > - <FormattedMessage id="home.join_us" /> - </a> - <a - className={styles.link} - rel="noopener noreferrer" - href="#" - onClick={this.onDialogLinkClicked(this.showUpdatesDialog.bind(this))} - > - <FormattedMessage id="home.get_updates" /> - </a> - <a - className={styles.link} - rel="noopener noreferrer" - href="#" - onClick={this.onDialogLinkClicked(this.showReportDialog.bind(this))} - > - <FormattedMessage id="home.report_issue" /> - </a> - <a - className={styles.link} - target="_blank" - rel="noopener noreferrer" - href="https://github.com/mozilla/hubs/blob/master/TERMS.md" - > - <FormattedMessage id="home.terms_of_use" /> - </a> - <a - className={styles.link} - target="_blank" - rel="noopener noreferrer" - href="https://github.com/mozilla/hubs/blob/master/PRIVACY.md" - > - <FormattedMessage id="home.privacy_notice" /> - </a> + <div className={styles.footerContent}> + <div className={styles.links}> + <div className={styles.top}> + <a + className={styles.link} + rel="noopener noreferrer" + href="#" + onClick={this.onDialogLinkClicked(this.showSlackDialog.bind(this))} + onMouseEnter={audio.onMouseEnter} + onMouseLeave={audio.onMouseLeave} + > + <FormattedMessage id="home.join_us" /> + </a> + <a + className={styles.link} + rel="noopener noreferrer" + href="#" + onClick={this.onDialogLinkClicked(this.showUpdatesDialog.bind(this))} + onMouseEnter={audio.onMouseEnter} + onMouseLeave={audio.onMouseLeave} + > + <FormattedMessage id="home.get_updates" /> + </a> + <a + className={styles.link} + rel="noopener noreferrer" + href="#" + onClick={this.onDialogLinkClicked(this.showReportDialog.bind(this))} + onMouseEnter={audio.onMouseEnter} + onMouseLeave={audio.onMouseLeave} + > + <FormattedMessage id="home.report_issue" /> + </a> + <a + className={styles.link} + target="_blank" + rel="noopener noreferrer" + href="https://github.com/mozilla/hubs/blob/master/TERMS.md" + onMouseEnter={audio.onMouseEnter} + onMouseLeave={audio.onMouseLeave} + > + <FormattedMessage id="home.terms_of_use" /> + </a> + <a + className={styles.link} + target="_blank" + rel="noopener noreferrer" + href="https://github.com/mozilla/hubs/blob/master/PRIVACY.md" + onMouseEnter={audio.onMouseEnter} + onMouseLeave={audio.onMouseLeave} + > + <FormattedMessage id="home.privacy_notice" /> + </a> - <img className={styles.mozLogo} src={mozLogo} /> + <img className={styles.mozLogo} src={mozLogo} /> + </div> + </div> </div> </div> + <video playsInline muted loop autoPlay className={styles.backgroundVideo} id="background-video"> + <source src={homeVideoWebM} type="video/webm" /> + <source src={homeVideoMp4} type="video/mp4" /> + </video> + {this.state.dialog} </div> - </div> - <video playsInline muted loop autoPlay className={styles.backgroundVideo} id="background-video"> - <source src={homeVideoWebM} type="video/webm" /> - <source src={homeVideoMp4} type="video/mp4" /> - </video> - {this.state.dialog} - </div> - </IntlProvider> + </IntlProvider> + )} + </AudioContext.Consumer> ); } } diff --git a/src/react-components/hub-create-panel.js b/src/react-components/hub-create-panel.js index 8a9ca35e28edb3d99c3685e483e3f031c755c557..4df0fa184e0aa781ef06e4dbcf24031b3de6d7f0 100644 --- a/src/react-components/hub-create-panel.js +++ b/src/react-components/hub-create-panel.js @@ -8,6 +8,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { resolveURL, extractUrlBase } from "../utils/resolveURL"; import { getReticulumFetchUrl } from "../utils/phoenix-utils"; import CreateRoomDialog from "./create-room-dialog.js"; +import { AudioContext } from "../AudioContext"; import default_scene_preview_thumbnail from "../assets/images/default_thumbnail.png"; import styles from "../assets/stylesheets/hub-create.scss"; @@ -173,75 +174,118 @@ class HubCreatePanel extends Component { const environmentThumbnail = this._getEnvironmentThumbnail(this.state.environmentIndex); return ( - <div> - <form onSubmit={this.createHub}> - <div className={styles.createPanel}> - <div className={styles.form}> - <div className={styles.environment}> - <div className={styles.picker}> - <img className={styles.image} srcSet={environmentThumbnail.srcset} /> - <div className={styles.labels}> - <div className={styles.header}> - <span className={styles.title}>{environmentTitle}</span> - {environmentAuthor && - environmentAuthor.name && - (environmentAuthor.url ? ( - <a href={environmentAuthor.url} rel="noopener noreferrer" className={styles.author}> - <FormattedMessage id="home.environment_author_by" /> - <span>{environmentAuthor.name}</span> - </a> - ) : ( - <span className={styles.author}> - <FormattedMessage id="home.environment_author_by" /> - <span>{environmentAuthor.name}</span> - </span> - ))} - {environmentAuthor && - environmentAuthor.organization && - (environmentAuthor.organization.url ? ( - <a href={environmentAuthor.organization.url} rel="noopener noreferrer" className={styles.org}> - <span>{environmentAuthor.organization.name}</span> - </a> - ) : ( - <span className={styles.org}> - <span>{environmentAuthor.organization.name}</span> - </span> - ))} - </div> - <div className={styles.footer}> - <button onClick={this.showCustomSceneDialog} className={styles.customButton}> - <FormattedMessage id="home.room_create_options" /> - </button> + <AudioContext.Consumer> + {audio => ( + <div> + <form onSubmit={this.createHub}> + <div className={styles.createPanel}> + <div className={styles.form}> + <div className={styles.environment}> + <div className={styles.picker}> + <img className={styles.image} srcSet={environmentThumbnail.srcset} /> + <div className={styles.labels}> + <div className={styles.header}> + <span className={styles.title}>{environmentTitle}</span> + {environmentAuthor && + environmentAuthor.name && + (environmentAuthor.url ? ( + <a + href={environmentAuthor.url} + rel="noopener noreferrer" + className={styles.author} + onMouseEnter={audio.onMouseEnter} + onMouseLeave={audio.onMouseLeave} + > + <FormattedMessage id="home.environment_author_by" /> + <span>{environmentAuthor.name}</span> + </a> + ) : ( + <span className={styles.author}> + <FormattedMessage id="home.environment_author_by" /> + <span>{environmentAuthor.name}</span> + </span> + ))} + {environmentAuthor && + environmentAuthor.organization && + (environmentAuthor.organization.url ? ( + <a + href={environmentAuthor.organization.url} + rel="noopener noreferrer" + className={styles.org} + onMouseEnter={audio.onMouseEnter} + onMouseLeave={audio.onMouseLeave} + > + <span>{environmentAuthor.organization.name}</span> + </a> + ) : ( + <span className={styles.org}> + <span>{environmentAuthor.organization.name}</span> + </span> + ))} + </div> + <div className={styles.footer}> + <button + onClick={this.showCustomSceneDialog} + onMouseEnter={audio.onMouseEnter} + onMouseLeave={audio.onMouseLeave} + className={styles.customButton} + > + <FormattedMessage id="home.room_create_options" /> + </button> + </div> + </div> + <div className={styles.controls}> + <button + className={styles.prev} + type="button" + tabIndex="1" + onClick={this.setToPreviousEnvironment} + onMouseEnter={audio.onMouseEnter} + onMouseLeave={audio.onMouseLeave} + > + <FontAwesomeIcon icon={faAngleLeft} /> + </button> + + <button + className={styles.next} + type="button" + tabIndex="2" + onClick={this.setToNextEnvironment} + onMouseEnter={audio.onMouseEnter} + onMouseLeave={audio.onMouseLeave} + > + <FontAwesomeIcon icon={faAngleRight} /> + </button> + </div> </div> </div> - <div className={styles.controls}> - <button className={styles.prev} type="button" tabIndex="1" onClick={this.setToPreviousEnvironment}> - <FontAwesomeIcon icon={faAngleLeft} /> - </button> - - <button className={styles.next} type="button" tabIndex="2" onClick={this.setToNextEnvironment}> - <FontAwesomeIcon icon={faAngleRight} /> + <div className={styles.container}> + <button + type="submit" + tabIndex="5" + className={styles.submitButton} + onMouseEnter={audio.onMouseEnter} + onMouseLeave={audio.onMouseLeave} + > + <FormattedMessage id="home.room_create_button" /> </button> </div> </div> </div> - <div className={styles.container}> - <button type="submit" tabIndex="5" className={styles.submitButton}> - <FormattedMessage id="home.room_create_button" /> - </button> - </div> - </div> + </form> + {this.state.showCustomSceneDialog && ( + <CreateRoomDialog + onClose={() => this.setState({ showCustomSceneDialog: false })} + onCustomScene={(name, url) => { + this.setState({ showCustomSceneDialog: false, name: name, customSceneUrl: url }, () => + this.createHub() + ); + }} + /> + )} </div> - </form> - {this.state.showCustomSceneDialog && ( - <CreateRoomDialog - onClose={() => this.setState({ showCustomSceneDialog: false })} - onCustomScene={(name, url) => { - this.setState({ showCustomSceneDialog: false, name: name, customSceneUrl: url }, () => this.createHub()); - }} - /> )} - </div> + </AudioContext.Consumer> ); } } diff --git a/src/react-components/invite-dialog.js b/src/react-components/invite-dialog.js index 5a9e4446a6486dfb5fe049bb913885b76a469756..238775205e35b9a166ba4a66b114be039fb0ade7 100644 --- a/src/react-components/invite-dialog.js +++ b/src/react-components/invite-dialog.js @@ -4,6 +4,7 @@ import copy from "copy-to-clipboard"; import classNames from "classnames"; import { FormattedMessage } from "react-intl"; +import { AudioContext } from "../AudioContext"; import styles from "../assets/stylesheets/invite-dialog.scss"; function pad(num, size) { @@ -45,43 +46,72 @@ export default class InviteDialog extends Component { const shareLink = `hub.link/${entryCodeString}`; return ( - <div className={styles.dialog}> - <div className={styles.attachPoint} /> - <div className={styles.close} onClick={() => this.props.onClose()}> - <span>×</span> - </div> - <div> - <FormattedMessage id="invite.enter_via" /> - <a href="https://hub.link" target="_blank" className={styles.hubLinkLink} rel="noopener noreferrer"> - hub.link - </a> - <FormattedMessage id="invite.and_enter_code" /> - </div> - <div className={styles.code}> - {entryCodeString.split("").map((d, i) => ( - <div className={classNames({ [styles.digit]: true, [styles[`digit_${i}`]]: true })} key={`link_code_${i}`}> - {d} + <AudioContext.Consumer> + {audio => ( + <div className={styles.dialog}> + <div className={styles.attachPoint} /> + <div + className={styles.close} + onClick={() => this.props.onClose()} + onMouseEnter={audio.onMouseEnter} + onMouseLeave={audio.onMouseLeave} + > + <span>×</span> </div> - ))} - </div> - <div> - <FormattedMessage id="invite.or_visit" /> - </div> - <div className={styles.domain}> - <input type="text" readOnly onFocus={e => e.target.select()} value={shareLink} /> - </div> - <div className={styles.buttons}> - <button className={styles.linkButton} onClick={this.copyClicked.bind(this, "https://" + shareLink)}> - <span>{this.state.copyButtonActive ? "copied!" : "copy"}</span> - </button> - {this.props.allowShare && - navigator.share && ( - <button className={styles.linkButton} onClick={this.shareClicked.bind(this, "https://" + shareLink)}> - <span>{this.state.shareButtonActive ? "sharing..." : "share"}</span> + <div> + <FormattedMessage id="invite.enter_via" /> + <a + href="https://hub.link" + target="_blank" + className={styles.hubLinkLink} + rel="noopener noreferrer" + onMouseEnter={audio.onMouseEnter} + onMouseLeave={audio.onMouseLeave} + > + hub.link + </a> + <FormattedMessage id="invite.and_enter_code" /> + </div> + <div className={styles.code}> + {entryCodeString.split("").map((d, i) => ( + <div + className={classNames({ [styles.digit]: true, [styles[`digit_${i}`]]: true })} + key={`link_code_${i}`} + > + {d} + </div> + ))} + </div> + <div> + <FormattedMessage id="invite.or_visit" /> + </div> + <div className={styles.domain}> + <input type="text" readOnly onFocus={e => e.target.select()} value={shareLink} /> + </div> + <div className={styles.buttons}> + <button + className={styles.linkButton} + onClick={this.copyClicked.bind(this, "https://" + shareLink)} + onMouseEnter={audio.onMouseEnter} + onMouseLeave={audio.onMouseLeave} + > + <span>{this.state.copyButtonActive ? "copied!" : "copy"}</span> </button> - )} - </div> - </div> + {this.props.allowShare && + navigator.share && ( + <button + className={styles.linkButton} + onClick={this.shareClicked.bind(this, "https://" + shareLink)} + onMouseEnter={audio.onMouseEnter} + onMouseLeave={audio.onMouseLeave} + > + <span>{this.state.shareButtonActive ? "sharing..." : "share"}</span> + </button> + )} + </div> + </div> + )} + </AudioContext.Consumer> ); } } diff --git a/src/react-components/invite-team-dialog.js b/src/react-components/invite-team-dialog.js index a2b3bcd50c40331487642e0d103b402d464bd5f9..65ccc55caa679b691b8cf14d24e122324f6ce512 100644 --- a/src/react-components/invite-team-dialog.js +++ b/src/react-components/invite-team-dialog.js @@ -1,6 +1,7 @@ import React, { Component } from "react"; import DialogContainer from "./dialog-container.js"; import PropTypes from "prop-types"; +import { AudioContext } from "../AudioContext"; export default class InviteTeamDialog extends Component { static propTypes = { @@ -30,9 +31,18 @@ export default class InviteTeamDialog extends Component { </div> <div className="invite-team-form"> <div className="invite-team-form__buttons"> - <button className="invite-team-form__action-button" onClick={this.inviteClicked}> - <span>{this.state.inviteButtonText}</span> - </button> + <AudioContext.Consumer> + {audio => ( + <button + className="invite-team-form__action-button" + onClick={this.inviteClicked} + onMouseEnter={audio.onMouseEnter} + onMouseLeave={audio.onMouseLeave} + > + <span>{this.state.inviteButtonText}</span> + </button> + )} + </AudioContext.Consumer> </div> </div> </div> diff --git a/src/react-components/link-dialog.js b/src/react-components/link-dialog.js index d3f45051d33736e65ecde5805eee3bb465959e07..7461ad08618719efddd0b9ea1b77b0d5f6bd2e28 100644 --- a/src/react-components/link-dialog.js +++ b/src/react-components/link-dialog.js @@ -3,6 +3,7 @@ import PropTypes from "prop-types"; import classNames from "classnames"; import { FormattedMessage } from "react-intl"; import LinkDialogHeader from "../assets/images/link_dialog_header.svg"; +import { AudioContext } from "../AudioContext"; import styles from "../assets/stylesheets/link-dialog.scss"; @@ -16,56 +17,77 @@ export default class LinkDialog extends Component { const { linkCode } = this.props; return ( - <div className={styles.dialog}> - <div className={styles.close} onClick={() => this.props.onClose()}> - <span>×</span> - </div> - <div> - {!linkCode && ( + <AudioContext.Consumer> + {audio => ( + <div className={styles.dialog}> + <div + className={styles.close} + onClick={() => this.props.onClose()} + onMouseEnter={audio.onMouseEnter} + onMouseLeave={audio.onMouseLeave} + > + <span>×</span> + </div> <div> - <div className={classNames("loading-panel", styles.codeLoadingPanel)}> - <div className="loader-wrap"> - <div className="loader"> - <div className="loader-center" /> + {!linkCode && ( + <div> + <div className={classNames("loading-panel", styles.codeLoadingPanel)}> + <div className="loader-wrap"> + <div className="loader"> + <div className="loader-center" /> + </div> + </div> </div> </div> - </div> - </div> - )} - {linkCode && ( - <div className={styles.contents}> - <img className={styles.imageHeader} src={LinkDialogHeader} /> - <div className={styles.header}> - <FormattedMessage id="link.connect_headset" /> - </div> - <div> - <FormattedMessage id="link.in_your_browser" /> - </div> - <a href="https://hub.link" className={styles.domain} target="_blank" rel="noopener noreferrer"> - hub.link - </a> - <div> - <FormattedMessage id="link.enter_code" /> - </div> + )} {linkCode && ( - <div className={styles.code}> - {linkCode.split("").map((d, i) => ( - <span className={styles.digit} key={`link_code_${i}`}> - {d} - </span> - ))} + <div className={styles.contents}> + <img className={styles.imageHeader} src={LinkDialogHeader} /> + <div className={styles.header}> + <FormattedMessage id="link.connect_headset" /> + </div> + <div> + <FormattedMessage id="link.in_your_browser" /> + </div> + <a + href="https://hub.link" + className={styles.domain} + target="_blank" + rel="noopener noreferrer" + onMouseEnter={audio.onMouseEnter} + onMouseLeave={audio.onMouseLeave} + > + hub.link + </a> + <div> + <FormattedMessage id="link.enter_code" /> + </div> + {linkCode && ( + <div className={styles.code}> + {linkCode.split("").map((d, i) => ( + <span className={styles.digit} key={`link_code_${i}`}> + {d} + </span> + ))} + </div> + )} + <div className={styles.keepOpen}> + <FormattedMessage id="link.do_not_close" /> + </div> + <button + className={styles.closeButton} + onClick={() => this.props.onClose()} + onMouseEnter={audio.onMouseEnter} + onMouseLeave={audio.onMouseLeave} + > + <FormattedMessage id="link.cancel" /> + </button> </div> )} - <div className={styles.keepOpen}> - <FormattedMessage id="link.do_not_close" /> - </div> - <button className={styles.closeButton} onClick={() => this.props.onClose()}> - <FormattedMessage id="link.cancel" /> - </button> </div> - )} - </div> - </div> + </div> + )} + </AudioContext.Consumer> ); } } diff --git a/src/react-components/link-root.js b/src/react-components/link-root.js index 5253de0130b73f10d7e71ada313fa01057e0aaf1..2d53455412b5e40c048ecc849c248dd2c08d2e4a 100644 --- a/src/react-components/link-root.js +++ b/src/react-components/link-root.js @@ -8,6 +8,7 @@ import classNames from "classnames"; import styles from "../assets/stylesheets/link.scss"; import { disableiOSZoom } from "../utils/disable-ios-zoom"; import HeadsetIcon from "../assets/images/generic_vr_entry.svg"; +import { AudioContext } from "../AudioContext"; const MAX_DIGITS = 6; const MAX_LETTERS = 4; @@ -135,127 +136,155 @@ class LinkRoot extends Component { return ( <IntlProvider locale={lang} messages={messages}> - <div className={styles.link}> - <div className={styles.linkContents}> - <div className={styles.logo}> - <img src="../assets/images/hub-preview-light-no-shadow.png" /> - </div> - {this.state.entered.length === this.maxAllowedChars() && ( - <div className={classNames("loading-panel", styles.codeLoadingPanel)}> - <div className="loader-wrap"> - <div className="loader"> - <div className="loader-center" /> - </div> + <AudioContext.Consumer> + {audio => ( + <div className={styles.link}> + <div className={styles.linkContents}> + <div className={styles.logo}> + <img src="../assets/images/hub-preview-light-no-shadow.png" /> </div> - </div> - )} - - <div className={styles.enteredContents}> - <div className={styles.header}> - <FormattedMessage - id={ - this.state.failedAtLeastOnce - ? "link.try_again" - : "link.link_page_header_" + (!this.state.isAlphaMode ? "entry" : "headset") - } - /> - </div> + {this.state.entered.length === this.maxAllowedChars() && ( + <div className={classNames("loading-panel", styles.codeLoadingPanel)}> + <div className="loader-wrap"> + <div className="loader"> + <div className="loader-center" /> + </div> + </div> + </div> + )} - <div className={styles.entered}> - <input - className={styles.charInput} - type={this.state.isAlphaMode ? "text" : "tel"} - pattern="[0-9A-I]*" - value={this.state.entered} - onChange={ev => { - if (!this.state.isAlphaMode && ev.target.value.match(/[a-z]/i)) { - this.setState({ isAlphaMode: true }); - } - - this.setState({ entered: ev.target.value.toUpperCase() }); - }} - /> - </div> + <div className={styles.enteredContents}> + <div className={styles.header}> + <FormattedMessage + id={ + this.state.failedAtLeastOnce + ? "link.try_again" + : "link.link_page_header_" + (!this.state.isAlphaMode ? "entry" : "headset") + } + /> + </div> - <div className={styles.enteredFooter}> - {!this.state.isAlphaMode && ( - <img onClick={() => this.toggleMode()} src={HeadsetIcon} className={styles.headsetIcon} /> - )} - {!this.state.isAlphaMode && ( - <span> - <a href="#" onClick={() => this.toggleMode()}> - <FormattedMessage id="link.linking_a_headset" /> - </a> - </span> - )} - </div> - </div> + <div className={styles.entered}> + <input + className={styles.charInput} + type={this.state.isAlphaMode ? "text" : "tel"} + pattern="[0-9A-I]*" + value={this.state.entered} + onChange={ev => { + if (!this.state.isAlphaMode && ev.target.value.match(/[a-z]/i)) { + this.setState({ isAlphaMode: true }); + } + + this.setState({ entered: ev.target.value.toUpperCase() }); + }} + /> + </div> - <div className={styles.keypad}> - {(this.state.isAlphaMode - ? ["A", "B", "C", "D", "E", "F", "G", "H", "I"] - : [1, 2, 3, 4, 5, 6, 7, 8, 9] - ).map((d, i) => ( - <button - disabled={this.state.entered.length === this.maxAllowedChars()} - key={`char_${i}`} - className={styles.keypadButton} - onClick={() => { - if (!hasTouchEvents) this.addToEntry(d); - }} - onTouchStart={() => this.addToEntry(d)} - > - {d} - </button> - ))} - <button - className={classNames(styles.keypadButton, styles.keypadToggleMode)} - onTouchStart={() => this.toggleMode()} - onClick={() => { - if (!hasTouchEvents) this.toggleMode(); - }} - > - {this.state.isAlphaMode ? "123" : "ABC"} - </button> - {!this.state.isAlphaMode && ( - <button - disabled={this.state.entered.length === this.maxAllowedChars()} - className={classNames(styles.keypadButton, styles.keypadZeroButton)} - onTouchStart={() => this.addToEntry(0)} - onClick={() => { - if (!hasTouchEvents) this.addToEntry(0); - }} - > - 0 - </button> - )} - <button - disabled={this.state.entered.length === 0 || this.state.entered.length === this.maxAllowedChars()} - className={classNames(styles.keypadButton, styles.keypadBackspace)} - onTouchStart={() => this.removeChar()} - onClick={() => { - if (!hasTouchEvents) this.removeChar(); - }} - > - ⌫ - </button> - </div> + <div className={styles.enteredFooter}> + {!this.state.isAlphaMode && ( + <img onClick={() => this.toggleMode()} src={HeadsetIcon} className={styles.headsetIcon} /> + )} + {!this.state.isAlphaMode && ( + <span> + <a + href="#" + onClick={() => this.toggleMode()} + onMouseEnter={audio.onMouseEnter} + onMouseLeave={audio.onMouseLeave} + > + <FormattedMessage id="link.linking_a_headset" /> + </a> + </span> + )} + </div> + </div> + + <div className={styles.keypad}> + {(this.state.isAlphaMode + ? ["A", "B", "C", "D", "E", "F", "G", "H", "I"] + : [1, 2, 3, 4, 5, 6, 7, 8, 9] + ).map((d, i) => ( + <button + disabled={this.state.entered.length === this.maxAllowedChars()} + key={`char_${i}`} + className={styles.keypadButton} + onClick={() => { + if (!hasTouchEvents) this.addToEntry(d); + }} + onTouchStart={() => this.addToEntry(d)} + onMouseEnter={audio.onMouseEnter} + onMouseLeave={audio.onMouseLeave} + > + {d} + </button> + ))} + <button + className={classNames(styles.keypadButton, styles.keypadToggleMode)} + onTouchStart={() => this.toggleMode()} + onClick={() => { + if (!hasTouchEvents) this.toggleMode(); + }} + onMouseEnter={audio.onMouseEnter} + onMouseLeave={audio.onMouseLeave} + > + {this.state.isAlphaMode ? "123" : "ABC"} + </button> + {!this.state.isAlphaMode && ( + <button + disabled={this.state.entered.length === this.maxAllowedChars()} + className={classNames(styles.keypadButton, styles.keypadZeroButton)} + onTouchStart={() => this.addToEntry(0)} + onClick={() => { + if (!hasTouchEvents) this.addToEntry(0); + }} + onMouseEnter={audio.onMouseEnter} + onMouseLeave={audio.onMouseLeave} + > + 0 + </button> + )} + <button + disabled={this.state.entered.length === 0 || this.state.entered.length === this.maxAllowedChars()} + className={classNames(styles.keypadButton, styles.keypadBackspace)} + onTouchStart={() => this.removeChar()} + onClick={() => { + if (!hasTouchEvents) this.removeChar(); + }} + onMouseEnter={audio.onMouseEnter} + onMouseLeave={audio.onMouseLeave} + > + ⌫ + </button> + </div> - <div className={styles.footer}> - <div - className={styles.linkHeadsetFooterLink} - style={{ visibility: this.state.isAlphaMode ? "hidden" : "visible" }} - > - <img onClick={() => this.toggleMode()} src={HeadsetIcon} className={styles.headsetIcon} /> - <span> - <a href="#" onClick={() => this.toggleMode()}> - <FormattedMessage id="link.linking_a_headset" /> - </a> - </span> + <div className={styles.footer}> + <div + className={styles.linkHeadsetFooterLink} + style={{ visibility: this.state.isAlphaMode ? "hidden" : "visible" }} + > + <img + onClick={() => this.toggleMode()} + src={HeadsetIcon} + className={styles.headsetIcon} + onMouseEnter={audio.onMouseEnter} + onMouseLeave={audio.onMouseLeave} + /> + <span> + <a + href="#" + onClick={() => this.toggleMode()} + onMouseEnter={audio.onMouseEnter} + onMouseLeave={audio.onMouseLeave} + > + <FormattedMessage id="link.linking_a_headset" /> + </a> + </span> + </div> + </div> </div> </div> - </div> - </div> + )} + </AudioContext.Consumer> </IntlProvider> ); } diff --git a/src/react-components/profile-entry-panel.js b/src/react-components/profile-entry-panel.js index 307ae73b0dac97d5b50908d5773f28bc2953aec1..9dff6f08d3b95fecb860bf1d212b7dd905315b3b 100644 --- a/src/react-components/profile-entry-panel.js +++ b/src/react-components/profile-entry-panel.js @@ -5,6 +5,7 @@ import { SCHEMA } from "../storage/store"; import styles from "../assets/stylesheets/profile.scss"; import classNames from "classnames"; import hubLogo from "../assets/images/hub-preview-white.png"; +import { AudioContext } from "../AudioContext"; class ProfileEntryPanel extends Component { static propTypes = { @@ -75,55 +76,73 @@ class ProfileEntryPanel extends Component { const { formatMessage } = this.props.intl; return ( - <div className={styles.profileEntry}> - <form onSubmit={this.saveStateAndFinish} className={styles.form}> - <div className={classNames([styles.box, styles.darkened])}> - <label htmlFor="#profile-entry-display-name" className={styles.title}> - <FormattedMessage id="profile.header" /> - </label> - <input - id="profile-entry-display-name" - className={styles.formFieldText} - value={this.state.displayName} - onFocus={e => e.target.select()} - onChange={e => this.setState({ displayName: e.target.value })} - required - spellCheck="false" - pattern={SCHEMA.definitions.profile.properties.displayName.pattern} - title={formatMessage({ id: "profile.display_name.validation_warning" })} - ref={inp => (this.nameInput = inp)} - /> - <div className={styles.avatarSelectorContainer}> - <div className="loading-panel"> - <div className="loader-wrap"> - <div className="loader"> - <div className="loader-center" /> + <AudioContext.Consumer> + {audio => ( + <div className={styles.profileEntry}> + <form onSubmit={this.saveStateAndFinish} className={styles.form}> + <div className={classNames([styles.box, styles.darkened])}> + <label htmlFor="#profile-entry-display-name" className={styles.title}> + <FormattedMessage id="profile.header" /> + </label> + <input + id="profile-entry-display-name" + className={styles.formFieldText} + value={this.state.displayName} + onFocus={e => e.target.select()} + onChange={e => this.setState({ displayName: e.target.value })} + required + spellCheck="false" + pattern={SCHEMA.definitions.profile.properties.displayName.pattern} + title={formatMessage({ id: "profile.display_name.validation_warning" })} + ref={inp => (this.nameInput = inp)} + /> + <div className={styles.avatarSelectorContainer}> + <div className="loading-panel"> + <div className="loader-wrap"> + <div className="loader"> + <div className="loader-center" /> + </div> + </div> </div> + <iframe + className={styles.avatarSelector} + src={`/avatar-selector.html#avatar_id=${this.state.avatarId}`} + ref={ifr => (this.avatarSelector = ifr)} + /> + </div> + <input + className={styles.formSubmit} + type="submit" + value={formatMessage({ id: "profile.save" })} + onMouseEnter={audio.onMouseEnter} + onMouseLeave={audio.onMouseLeave} + /> + <div className={styles.links}> + <a + target="_blank" + rel="noopener noreferrer" + href="https://github.com/mozilla/hubs/blob/master/TERMS.md" + onMouseEnter={audio.onMouseEnter} + onMouseLeave={audio.onMouseLeave} + > + <FormattedMessage id="profile.terms_of_use" /> + </a> + <a + target="_blank" + rel="noopener noreferrer" + href="https://github.com/mozilla/hubs/blob/master/PRIVACY.md" + onMouseEnter={audio.onMouseEnter} + onMouseLeave={audio.onMouseLeave} + > + <FormattedMessage id="profile.privacy_notice" /> + </a> </div> </div> - <iframe - className={styles.avatarSelector} - src={`/avatar-selector.html#avatar_id=${this.state.avatarId}`} - ref={ifr => (this.avatarSelector = ifr)} - /> - </div> - <input className={styles.formSubmit} type="submit" value={formatMessage({ id: "profile.save" })} /> - <div className={styles.links}> - <a target="_blank" rel="noopener noreferrer" href="https://github.com/mozilla/hubs/blob/master/TERMS.md"> - <FormattedMessage id="profile.terms_of_use" /> - </a> - <a - target="_blank" - rel="noopener noreferrer" - href="https://github.com/mozilla/hubs/blob/master/PRIVACY.md" - > - <FormattedMessage id="profile.privacy_notice" /> - </a> - </div> + </form> + <img className={styles.logo} src={hubLogo} /> </div> - </form> - <img className={styles.logo} src={hubLogo} /> - </div> + )} + </AudioContext.Consumer> ); } } diff --git a/src/react-components/profile-info-header.js b/src/react-components/profile-info-header.js new file mode 100644 index 0000000000000000000000000000000000000000..2e6de88966a6c751fc5b2941d1a8ef95ed2b5296 --- /dev/null +++ b/src/react-components/profile-info-header.js @@ -0,0 +1,49 @@ +import React from "react"; +import PropTypes from "prop-types"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { faQuestion } from "@fortawesome/free-solid-svg-icons/faQuestion"; +import { AudioContext } from "../AudioContext"; + +export const ProfileInfoHeader = props => ( + <AudioContext.Consumer> + {audio => ( + <div className="profile-info-header"> + <div className="profile-info-header__menu-buttons"> + <button + className="profile-info-header__menu-buttons__menu-button" + onClick={props.onClickHelp} + onMouseEnter={audio.onMouseEnter} + onMouseLeave={audio.onMouseLeave} + > + <i className="profile-info-header__menu-buttons__menu-button__icon"> + <FontAwesomeIcon icon={faQuestion} /> + </i> + </button> + </div> + <div className="profile-info-header__profile_display_name"> + <img + src="../assets/images/account.svg" + onClick={props.onClickName} + className="profile-info-header__icon" + onMouseEnter={audio.onMouseEnter} + onMouseLeave={audio.onMouseLeave} + /> + <div + onClick={props.onClickName} + title={props.name} + onMouseEnter={audio.onMouseEnter} + onMouseLeave={audio.onMouseLeave} + > + {props.name} + </div> + </div> + </div> + )} + </AudioContext.Consumer> +); + +ProfileInfoHeader.propTypes = { + onClickName: PropTypes.func, + onClickHelp: PropTypes.func, + name: PropTypes.string +}; diff --git a/src/react-components/report-dialog.js b/src/react-components/report-dialog.js index 36072eed617957090fc2942a5f17047b3ea533ba..12649646cf06865f46980b3d728dba01882822d6 100644 --- a/src/react-components/report-dialog.js +++ b/src/react-components/report-dialog.js @@ -1,31 +1,59 @@ import React, { Component } from "react"; import DialogContainer from "./dialog-container.js"; +import { AudioContext } from "../AudioContext"; export default class ReportDialog extends Component { render() { return ( - <DialogContainer title="Report an Issue" {...this.props}> - <span> - <p>Need to report a problem?</p> - <p> - You can file a{" "} - <a href="https://github.com/mozilla/hubs/issues" target="_blank" rel="noopener noreferrer"> - GitHub Issue - </a>{" "} - or e-mail us for support at <a href="mailto:hubs@mozilla.com">hubs@mozilla.com</a>. - </p> - <p> - You can also find us in{" "} - <a href="https://webvr.slack.com/messages/social" target="_blank" rel="noopener noreferrer"> - #social - </a>{" "} - on the{" "} - <a href="https://webvr-slack.herokuapp.com/" target="_blank" rel="noopener noreferrer"> - WebVR Slack - </a>. - </p> - </span> - </DialogContainer> + <AudioContext.Consumer> + {audio => ( + <DialogContainer title="Report an Issue" {...this.props}> + <span> + <p>Need to report a problem?</p> + <p> + You can file a{" "} + <a + href="https://github.com/mozilla/hubs/issues" + target="_blank" + rel="noopener noreferrer" + onMouseEnter={audio.onMouseEnter} + onMouseLeave={audio.onMouseLeave} + > + GitHub Issue + </a>{" "} + or e-mail us for support at{" "} + <a href="mailto:hubs@mozilla.com" onMouseEnter={audio.onMouseEnter} onMouseLeave={audio.onMouseLeave}> + hubs@mozilla.com + </a> + . + </p> + <p> + You can also find us in{" "} + <a + href="https://webvr.slack.com/messages/social" + target="_blank" + rel="noopener noreferrer" + onMouseEnter={audio.onMouseEnter} + onMouseLeave={audio.onMouseLeave} + > + #social + </a>{" "} + on the{" "} + <a + href="https://webvr-slack.herokuapp.com/" + target="_blank" + rel="noopener noreferrer" + onMouseEnter={audio.onMouseEnter} + onMouseLeave={audio.onMouseLeave} + > + WebVR Slack + </a> + . + </p> + </span> + </DialogContainer> + )} + </AudioContext.Consumer> ); } } diff --git a/src/react-components/safari-dialog.js b/src/react-components/safari-dialog.js index de96bfbf4545e6496e97a3e0dab81593adad7519..60fd609efab23ff56fa643d0ba42faf86b9bf36d 100644 --- a/src/react-components/safari-dialog.js +++ b/src/react-components/safari-dialog.js @@ -1,6 +1,7 @@ import React, { Component } from "react"; import copy from "copy-to-clipboard"; import DialogContainer from "./dialog-container.js"; +import { AudioContext } from "../AudioContext"; export default class SafariDialog extends Component { state = { @@ -27,9 +28,18 @@ export default class SafariDialog extends Component { className="invite-form__link_field" /> <div className="invite-form__buttons"> - <button className="invite-form__action-button" onClick={onCopyClicked}> - <span>{this.state.copyLinkButtonText}</span> - </button> + <AudioContext.Consumer> + {audio => ( + <button + className="invite-form__action-button" + onClick={onCopyClicked} + onMouseEnter={audio.onMouseEnter} + onMouseLeave={audio.onMouseLeave} + > + <span>{this.state.copyLinkButtonText}</span> + </button> + )} + </AudioContext.Consumer> </div> </div> </div> diff --git a/src/react-components/scene-ui.js b/src/react-components/scene-ui.js index ec04e095e135bac8bb85beff8c27c7cf2b6b3ae9..65af8699649ec67523b36fe160157f8b11065e5a 100644 --- a/src/react-components/scene-ui.js +++ b/src/react-components/scene-ui.js @@ -7,6 +7,7 @@ import styles from "../assets/stylesheets/scene-ui.scss"; import hubLogo from "../assets/images/hub-preview-white.png"; import { getReticulumFetchUrl } from "../utils/phoenix-utils"; import { generateHubName } from "../utils/name-generation"; +import { AudioContext } from "../AudioContext"; import { lang, messages } from "../utils/i18n"; @@ -68,34 +69,43 @@ class SceneUI extends Component { render() { return ( <IntlProvider locale={lang} messages={messages}> - <div className={styles.ui}> - <div - className={classNames({ - [styles.screenshot]: true, - [styles.screenshotHidden]: this.props.sceneLoaded - })} - > - {this.state.showScreenshot && <img src={this.props.sceneScreenshotURL} />} - </div> - <div className={styles.whiteOverlay} /> - <div className={styles.grid}> - <div className={styles.mainPanel}> - <a href="/" className={styles.logo}> - <img src={hubLogo} /> - </a> - <div className={styles.logoTagline}> - <FormattedMessage id="scene.logo_tagline" /> + <AudioContext.Consumer> + {audio => ( + <div className={styles.ui}> + <div + className={classNames({ + [styles.screenshot]: true, + [styles.screenshotHidden]: this.props.sceneLoaded + })} + > + {this.state.showScreenshot && <img src={this.props.sceneScreenshotURL} />} + </div> + <div className={styles.whiteOverlay} /> + <div className={styles.grid}> + <div className={styles.mainPanel}> + <a + href="/" + className={styles.logo} + onMouseEnter={audio.onMouseEnter} + onMouseLeave={audio.onMouseLeave} + > + <img src={hubLogo} /> + </a> + <div className={styles.logoTagline}> + <FormattedMessage id="scene.logo_tagline" /> + </div> + <button onClick={this.createRoom} onMouseEnter={audio.onMouseEnter} onMouseLeave={audio.onMouseLeave}> + <FormattedMessage id="scene.create_button" /> + </button> + </div> + </div> + <div className={styles.info}> + <div className={styles.name}>{this.props.sceneName}</div> + <div className={styles.attribution}>{this.props.sceneAttribution}</div> </div> - <button onClick={this.createRoom}> - <FormattedMessage id="scene.create_button" /> - </button> </div> - </div> - <div className={styles.info}> - <div className={styles.name}>{this.props.sceneName}</div> - <div className={styles.attribution}>{this.props.sceneAttribution}</div> - </div> - </div> + )} + </AudioContext.Consumer> </IntlProvider> ); } diff --git a/src/react-components/slack-dialog.js b/src/react-components/slack-dialog.js index 5a024855ba2a206ebcc4a8a80e92e1460daa4b7a..8b70d8504ce9eeedd1e8b197b14f10557e10053d 100644 --- a/src/react-components/slack-dialog.js +++ b/src/react-components/slack-dialog.js @@ -1,32 +1,56 @@ import React, { Component } from "react"; import DialogContainer from "./dialog-container.js"; +import { AudioContext } from "../AudioContext"; export default class SlackDialog extends Component { render() { return ( <DialogContainer title="Get in Touch" {...this.props}> - <span> - <p>Want to join the conversation?</p> - <p> - Join us on the{" "} - <a href="https://webvr-slack.herokuapp.com/" target="_blank" rel="noopener noreferrer"> - WebVR Slack - </a>{" "} - in the{" "} - <a href="https://webvr.slack.com/messages/social" target="_blank" rel="noopener noreferrer"> - #social - </a>{" "} - channel.<br /> - VR meetups every Friday at noon PDT! - </p> - <p> - Or, tweet at{" "} - <a href="https://twitter.com/mozillareality" target="_blank" rel="noopener noreferrer"> - @mozillareality - </a>{" "} - on Twitter. - </p> - </span> + <AudioContext.Consumer> + {audio => ( + <span> + <p>Want to join the conversation?</p> + <p> + Join us on the{" "} + <a + href="https://webvr-slack.herokuapp.com/" + target="_blank" + rel="noopener noreferrer" + onMouseEnter={audio.onMouseEnter} + onMouseLeave={audio.onMouseLeave} + > + WebVR Slack + </a>{" "} + in the{" "} + <a + href="https://webvr.slack.com/messages/social" + target="_blank" + rel="noopener noreferrer" + onMouseEnter={audio.onMouseEnter} + onMouseLeave={audio.onMouseLeave} + > + #social + </a>{" "} + channel. + <br /> + VR meetups every Friday at noon PDT! + </p> + <p> + Or, tweet at{" "} + <a + href="https://twitter.com/mozillareality" + target="_blank" + rel="noopener noreferrer" + onMouseEnter={audio.onMouseEnter} + onMouseLeave={audio.onMouseLeave} + > + @mozillareality + </a>{" "} + on Twitter. + </p> + </span> + )} + </AudioContext.Consumer> </DialogContainer> ); } diff --git a/src/react-components/ui-root.js b/src/react-components/ui-root.js index 73c2a1e871da4c1f4185daf1a85efaaaac80eb62..0fec42f87ca6accec6cc7cf07fab958ca6344eb2 100644 --- a/src/react-components/ui-root.js +++ b/src/react-components/ui-root.js @@ -9,6 +9,7 @@ import MovingAverage from "moving-average"; import screenfull from "screenfull"; import styles from "../assets/stylesheets/ui-root.scss"; import entryStyles from "../assets/stylesheets/entry.scss"; +import { AudioContext } from "../AudioContext"; import { lang, messages } from "../utils/i18n"; import AutoExitWarning from "./auto-exit-warning"; @@ -133,6 +134,17 @@ class UIRoot extends Component { this.props.scene.addEventListener("stateadded", this.onAframeStateChanged); this.props.scene.addEventListener("stateremoved", this.onAframeStateChanged); this.props.scene.addEventListener("exit", this.exit); + const scene = this.props.scene; + this.setState({ + audioContext: { + onMouseEnter: () => { + scene.emit("play_sound-hud_mouse_enter"); + }, + onMouseLeave: () => { + // scene.emit("play_sound-hud_mouse_leave"); + } + } + }); } componentWillUnmount() { @@ -532,25 +544,35 @@ class UIRoot extends Component { this.setState({ dialog: null }); }; - showHelpDialog = () => { - this.setState({ dialog: <HelpDialog onClose={this.closeDialog} /> }); - }; + showHelpDialog() { + this.setState({ + dialog: <HelpDialog onClose={this.closeDialog} /> + }); + } - showSafariDialog = () => { - this.setState({ dialog: <SafariDialog onClose={this.closeDialog} /> }); - }; + showSafariDialog() { + this.setState({ + dialog: <SafariDialog onClose={this.closeDialog} /> + }); + } - showInviteTeamDialog = () => { - this.setState({ dialog: <InviteTeamDialog hubChannel={this.props.hubChannel} onClose={this.closeDialog} /> }); - }; + showInviteTeamDialog() { + this.setState({ + dialog: <InviteTeamDialog hubChannel={this.props.hubChannel} onClose={this.closeDialog} /> + }); + } - showCreateObjectDialog = () => { - this.setState({ dialog: <CreateObjectDialog onCreate={this.createObject} onClose={this.closeDialog} /> }); - }; + showCreateObjectDialog() { + this.setState({ + dialog: <CreateObjectDialog onCreate={this.createObject} onClose={this.closeDialog} /> + }); + } - showWebVRRecommendDialog = () => { - this.setState({ dialog: <WebVRRecommendDialog onClose={this.closeDialog} /> }); - }; + showWebVRRecommendDialog() { + this.setState({ + dialog: <WebVRRecommendDialog onClose={this.closeDialog} /> + }); + } onMiniInviteClicked = () => { const link = "https://hub.link/" + this.props.hubEntryCode; @@ -572,35 +594,69 @@ class UIRoot extends Component { if (this.props.roomUnavailableReason === "closed") { // TODO i18n, due to links and markup subtitle = ( - <div> - Sorry, this room is no longer available. - <p /> - A room may be closed if we receive reports that it violates our{" "} - <a target="_blank" rel="noreferrer noopener" href="https://github.com/mozilla/hubs/blob/master/TERMS.md"> - Terms of Use - </a>. - <br /> - If you have questions, contact us at <a href="mailto:hubs@mozilla.com">hubs@mozilla.com</a>. - <p /> - If you'd like to run your own server, hubs's source code is available on{" "} - <a href="https://github.com/mozilla/hubs">GitHub</a>. - </div> + <AudioContext.Consumer> + {audio => ( + <div> + Sorry, this room is no longer available. + <p />A room may be closed if we receive reports that it violates our{" "} + <a + target="_blank" + rel="noreferrer noopener" + href="https://github.com/mozilla/hubs/blob/master/TERMS.md" + onMouseEnter={audio.onMouseEnter} + onMouseLeave={audio.onMouseLeave} + > + Terms of Use + </a> + .<br /> + If you have questions, contact us at{" "} + <a href="mailto:hubs@mozilla.com" onMouseEnter={audio.onMouseEnter} onMouseLeave={audio.onMouseLeave}> + hubs@mozilla.com + </a> + .<p /> + If you'd like to run your own server, hubs's source code is available on{" "} + <a + href="https://github.com/mozilla/hubs" + onMouseEnter={audio.onMouseEnter} + onMouseLeave={audio.onMouseLeave} + > + GitHub + </a> + . + </div> + )} + </AudioContext.Consumer> ); } else if (this.props.platformUnsupportedReason === "no_data_channels") { // TODO i18n, due to links and markup subtitle = ( - <div> - Your browser does not support{" "} - <a - href="https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/createDataChannel#Browser_compatibility" - rel="noreferrer noopener" - > - WebRTC Data Channels - </a>, which is required to use Hubs.<br />If you"d like to use Hubs with Oculus or SteamVR, you can{" "} - <a href="https://www.mozilla.org/firefox" rel="noreferrer noopener"> - Download Firefox - </a>. - </div> + <AudioContext.Consumer> + {audio => ( + <div> + Your browser does not support{" "} + <a + href="https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/createDataChannel#Browser_compatibility" + rel="noreferrer noopener" + onMouseEnter={audio.onMouseEnter} + onMouseLeave={audio.onMouseLeave} + > + WebRTC Data Channels + </a> + , which is required to use Hubs. + <br /> + If you"d like to use Hubs with Oculus or SteamVR, you can{" "} + <a + href="https://www.mozilla.org/firefox" + rel="noreferrer noopener" + onMouseEnter={audio.onMouseEnter} + onMouseLeave={audio.onMouseLeave} + > + Download Firefox + </a> + . + </div> + )} + </AudioContext.Consumer> ); } else { const reason = this.props.roomUnavailableReason || this.props.platformUnsupportedReason; @@ -610,9 +666,17 @@ class UIRoot extends Component { <FormattedMessage id={exitSubtitleId} /> <p /> {this.props.roomUnavailableReason && ( - <div> - You can also <a href="/">create a new room</a>. - </div> + <AudioContext.Consumer> + {audio => ( + <div> + You can also{" "} + <a href="/" onMouseEnter={audio.onMouseEnter} onMouseLeave={audio.onMouseLeave}> + create a new room + </a> + . + </div> + )} + </AudioContext.Consumer> )} </div> ); @@ -656,25 +720,38 @@ class UIRoot extends Component { renderEntryStartPanel = () => { return ( - <div className={entryStyles.entryPanel}> - <div className={entryStyles.title}>{this.props.hubName}</div> + <AudioContext.Consumer> + {audio => ( + <div className={entryStyles.entryPanel}> + <div className={entryStyles.title}>{this.props.hubName}</div> + + <div className={entryStyles.center}> + <div + onClick={() => this.setState({ showProfileEntry: true })} + className={entryStyles.profileName} + onMouseEnter={audio.onMouseEnter} + onMouseLeave={audio.onMouseLeave} + > + <img src="../assets/images/account.svg" className={entryStyles.profileIcon} /> + <div title={this.props.store.state.profile.displayName}> + {this.props.store.state.profile.displayName} + </div> + </div> + </div> - <div className={entryStyles.center}> - <div onClick={() => this.setState({ showProfileEntry: true })} className={entryStyles.profileName}> - <img src="../assets/images/account.svg" className={entryStyles.profileIcon} /> - <div title={this.props.store.state.profile.displayName}>{this.props.store.state.profile.displayName}</div> + <div className={entryStyles.buttonContainer}> + <button + className={classNames([entryStyles.actionButton, entryStyles.wideButton])} + onClick={() => this.handleStartEntry()} + onMouseEnter={audio.onMouseEnter} + onMouseLeave={audio.onMouseLeave} + > + <FormattedMessage id="entry.enter-room" /> + </button> + </div> </div> - </div> - - <div className={entryStyles.buttonContainer}> - <button - className={classNames([entryStyles.actionButton, entryStyles.wideButton])} - onClick={() => this.handleStartEntry()} - > - <FormattedMessage id="entry.enter-room" /> - </button> - </div> - </div> + )} + </AudioContext.Consumer> ); }; @@ -707,9 +784,18 @@ class UIRoot extends Component { )} <DeviceEntryButton onClick={() => this.attemptLink()} isInHMD={this.props.availableVREntryTypes.isInHMD} /> {this.props.availableVREntryTypes.cardboard !== VR_DEVICE_AVAILABILITY.no && ( - <div className={entryStyles.secondary} onClick={this.enterVR}> - <FormattedMessage id="entry.cardboard" /> - </div> + <AudioContext.Consumer> + {audio => ( + <div + className={entryStyles.secondary} + onClick={this.enterVR} + onMouseEnter={audio.onMouseEnter} + onMouseLeave={audio.onMouseLeave} + > + <FormattedMessage id="entry.cardboard" /> + </div> + )} + </AudioContext.Consumer> )} {screenSharingCheckbox} </div> @@ -733,36 +819,55 @@ class UIRoot extends Component { renderMicPanel = () => { return ( - <div className="mic-grant-panel"> - <div className="mic-grant-panel__grant-container"> - <div className="mic-grant-panel__title"> - <FormattedMessage - id={this.state.entryStep == ENTRY_STEPS.mic_grant ? "audio.grant-title" : "audio.granted-title"} - /> - </div> - <div className="mic-grant-panel__subtitle"> - <FormattedMessage - id={this.state.entryStep == ENTRY_STEPS.mic_grant ? "audio.grant-subtitle" : "audio.granted-subtitle"} - /> - </div> - <div className="mic-grant-panel__button-container"> - {this.state.entryStep == ENTRY_STEPS.mic_grant ? ( - <button className="mic-grant-panel__button" onClick={this.onMicGrantButton}> - <img src="../assets/images/mic_denied.png" srcSet="../assets/images/mic_denied@2x.png 2x" /> - </button> - ) : ( - <button className="mic-grant-panel__button" onClick={this.onMicGrantButton}> - <img src="../assets/images/mic_granted.png" srcSet="../assets/images/mic_granted@2x.png 2x" /> - </button> - )} + <AudioContext.Consumer> + {audio => ( + <div className="mic-grant-panel"> + <div className="mic-grant-panel__grant-container"> + <div className="mic-grant-panel__title"> + <FormattedMessage + id={this.state.entryStep == ENTRY_STEPS.mic_grant ? "audio.grant-title" : "audio.granted-title"} + /> + </div> + <div className="mic-grant-panel__subtitle"> + <FormattedMessage + id={this.state.entryStep == ENTRY_STEPS.mic_grant ? "audio.grant-subtitle" : "audio.granted-subtitle"} + /> + </div> + <div className="mic-grant-panel__button-container"> + {this.state.entryStep == ENTRY_STEPS.mic_grant ? ( + <button + className="mic-grant-panel__button" + onClick={this.onMicGrantButton} + onMouseEnter={audio.onMouseEnter} + onMouseLeave={audio.onMouseLeave} + > + <img src="../assets/images/mic_denied.png" srcSet="../assets/images/mic_denied@2x.png 2x" /> + </button> + ) : ( + <button + className="mic-grant-panel__button" + onClick={this.onMicGrantButton} + onMouseEnter={audio.onMouseEnter} + onMouseLeave={audio.onMouseLeave} + > + <img src="../assets/images/mic_granted.png" srcSet="../assets/images/mic_granted@2x.png 2x" /> + </button> + )} + </div> + <div className="mic-grant-panel__next-container"> + <button + className={classNames("mic-grant-panel__next")} + onClick={this.onMicGrantButton} + onMouseEnter={audio.onMouseEnter} + onMouseLeave={audio.onMouseLeave} + > + <FormattedMessage id="audio.granted-next" /> + </button> + </div> + </div> </div> - </div> - <div className="mic-grant-panel__next-container"> - <button className={classNames("mic-grant-panel__next")} onClick={this.onMicGrantButton}> - <FormattedMessage id="audio.granted-next" /> - </button> - </div> - </div> + )} + </AudioContext.Consumer> ); }; @@ -774,104 +879,121 @@ class UIRoot extends Component { const speakerClip = { clip: `rect(${this.state.tonePlaying ? 0 : maxLevelHeight}px, 111px, 111px, 0px)` }; const subtitleId = AFRAME.utils.device.isMobile() ? "audio.subtitle-mobile" : "audio.subtitle-desktop"; return ( - <div className="audio-setup-panel"> - <div> - <div className="audio-setup-panel__title"> - <FormattedMessage id="audio.title" /> - </div> - <div className="audio-setup-panel__subtitle"> - {(AFRAME.utils.device.isMobile() || this.state.enterInVR) && <FormattedMessage id={subtitleId} />} - </div> - <div className="audio-setup-panel__levels"> - <div className="audio-setup-panel__levels__icon"> - <img - src="../assets/images/level_background.png" - srcSet="../assets/images/level_background@2x.png 2x" - className="audio-setup-panel__levels__icon-part" - /> - <img - src="../assets/images/level_fill.png" - srcSet="../assets/images/level_fill@2x.png 2x" - className="audio-setup-panel__levels__icon-part" - style={micClip} - /> - {this.state.audioTrack ? ( - <img - src="../assets/images/mic_level.png" - srcSet="../assets/images/mic_level@2x.png 2x" - className="audio-setup-panel__levels__icon-part" - /> - ) : ( - <img - src="../assets/images/mic_denied.png" - srcSet="../assets/images/mic_denied@2x.png 2x" - className="audio-setup-panel__levels__icon-part" - /> + <AudioContext.Consumer> + {audio => ( + <div className="audio-setup-panel"> + <div> + <div className="audio-setup-panel__title"> + <FormattedMessage id="audio.title" /> + </div> + <div className="audio-setup-panel__subtitle"> + {(AFRAME.utils.device.isMobile() || this.state.enterInVR) && <FormattedMessage id={subtitleId} />} + </div> + <div className="audio-setup-panel__levels"> + <div className="audio-setup-panel__levels__icon"> + <img + src="../assets/images/level_background.png" + srcSet="../assets/images/level_background@2x.png 2x" + className="audio-setup-panel__levels__icon-part" + /> + <img + src="../assets/images/level_fill.png" + srcSet="../assets/images/level_fill@2x.png 2x" + className="audio-setup-panel__levels__icon-part" + style={micClip} + /> + {this.state.audioTrack ? ( + <img + src="../assets/images/mic_level.png" + srcSet="../assets/images/mic_level@2x.png 2x" + className="audio-setup-panel__levels__icon-part" + /> + ) : ( + <img + src="../assets/images/mic_denied.png" + srcSet="../assets/images/mic_denied@2x.png 2x" + className="audio-setup-panel__levels__icon-part" + /> + )} + </div> + <div + className="audio-setup-panel__levels__icon" + onClick={this.playTestTone} + onMouseEnter={audio.onMouseEnter} + onMouseLeave={audio.onMouseLeave} + > + <img + src="../assets/images/level_background.png" + srcSet="../assets/images/level_background@2x.png 2x" + className="audio-setup-panel__levels__icon-part" + /> + <img + src="../assets/images/level_fill.png" + srcSet="../assets/images/level_fill@2x.png 2x" + className="audio-setup-panel__levels__icon-part" + style={speakerClip} + /> + <img + src="../assets/images/speaker_level.png" + srcSet="../assets/images/speaker_level@2x.png 2x" + className="audio-setup-panel__levels__icon-part" + /> + </div> + </div> + {this.state.audioTrack && ( + <div className="audio-setup-panel__device-chooser"> + <select + className="audio-setup-panel__device-chooser__dropdown" + value={this.selectedMicDeviceId()} + onChange={this.micDeviceChanged} + onMouseEnter={audio.onMouseEnter} + onMouseLeave={audio.onMouseLeave} + > + {this.state.micDevices.map(d => ( + <option key={d.deviceId} value={d.deviceId}> + + {d.label} + </option> + ))} + </select> + <img + className="audio-setup-panel__device-chooser__mic-icon" + src="../assets/images/mic_small.png" + srcSet="../assets/images/mic_small@2x.png 2x" + /> + <img + className="audio-setup-panel__device-chooser__dropdown-arrow" + src="../assets/images/dropdown_arrow.png" + srcSet="../assets/images/dropdown_arrow@2x.png 2x" + /> + </div> + )} + {this.shouldShowHmdMicWarning() && ( + <div className="audio-setup-panel__hmd-mic-warning"> + <img + src="../assets/images/warning_icon.png" + srcSet="../assets/images/warning_icon@2x.png 2x" + className="audio-setup-panel__hmd-mic-warning__icon" + /> + <span className="audio-setup-panel__hmd-mic-warning__label"> + <FormattedMessage id="audio.hmd-mic-warning" /> + </span> + </div> )} </div> - <div className="audio-setup-panel__levels__icon" onClick={this.playTestTone}> - <img - src="../assets/images/level_background.png" - srcSet="../assets/images/level_background@2x.png 2x" - className="audio-setup-panel__levels__icon-part" - /> - <img - src="../assets/images/level_fill.png" - srcSet="../assets/images/level_fill@2x.png 2x" - className="audio-setup-panel__levels__icon-part" - style={speakerClip} - /> - <img - src="../assets/images/speaker_level.png" - srcSet="../assets/images/speaker_level@2x.png 2x" - className="audio-setup-panel__levels__icon-part" - /> - </div> - </div> - {this.state.audioTrack && ( - <div className="audio-setup-panel__device-chooser"> - <select - className="audio-setup-panel__device-chooser__dropdown" - value={this.selectedMicDeviceId()} - onChange={this.micDeviceChanged} + <div className="audio-setup-panel__enter-button-container"> + <button + className="audio-setup-panel__enter-button" + onClick={this.onAudioReadyButton} + onMouseEnter={audio.onMouseEnter} + onMouseLeave={audio.onMouseLeave} > - {this.state.micDevices.map(d => ( - <option key={d.deviceId} value={d.deviceId}> - {d.label} - </option> - ))} - </select> - <img - className="audio-setup-panel__device-chooser__mic-icon" - src="../assets/images/mic_small.png" - srcSet="../assets/images/mic_small@2x.png 2x" - /> - <img - className="audio-setup-panel__device-chooser__dropdown-arrow" - src="../assets/images/dropdown_arrow.png" - srcSet="../assets/images/dropdown_arrow@2x.png 2x" - /> - </div> - )} - {this.shouldShowHmdMicWarning() && ( - <div className="audio-setup-panel__hmd-mic-warning"> - <img - src="../assets/images/warning_icon.png" - srcSet="../assets/images/warning_icon@2x.png 2x" - className="audio-setup-panel__hmd-mic-warning__icon" - /> - <span className="audio-setup-panel__hmd-mic-warning__label"> - <FormattedMessage id="audio.hmd-mic-warning" /> - </span> + <FormattedMessage id="audio.enter-now" /> + </button> </div> - )} - </div> - <div className="audio-setup-panel__enter-button-container"> - <button className="audio-setup-panel__enter-button" onClick={this.onAudioReadyButton}> - <FormattedMessage id="audio.enter-now" /> - </button> - </div> - </div> + </div> + )} + </AudioContext.Consumer> ); }; @@ -897,14 +1019,23 @@ class UIRoot extends Component { if (this.state.entryPanelCollapsed && !this.isWaitingForAutoExit()) { dialogContents = ( - <div className={entryStyles.entryDialog}> - <div> </div> - <button onClick={() => this.setState({ entryPanelCollapsed: false })} className={entryStyles.expand}> - <i> - <FontAwesomeIcon icon={faChevronUp} /> - </i> - </button> - </div> + <AudioContext.Consumer> + {audio => ( + <div className={entryStyles.entryDialog}> + <div> </div> + <button + onClick={() => this.setState({ entryPanelCollapsed: false })} + className={entryStyles.expand} + onMouseEnter={audio.onMouseEnter} + onMouseLeave={audio.onMouseLeave} + > + <i> + <FontAwesomeIcon icon={faChevronUp} /> + </i> + </button> + </div> + )} + </AudioContext.Consumer> ); } else { dialogContents = this.isWaitingForAutoExit() ? ( @@ -915,11 +1046,20 @@ class UIRoot extends Component { ) : ( <div className={entryStyles.entryDialog}> {!this.state.entryPanelCollapsed && ( - <button onClick={() => this.setState({ entryPanelCollapsed: true })} className={entryStyles.collapse}> - <i> - <FontAwesomeIcon icon={faChevronDown} /> - </i> - </button> + <AudioContext.Consumer> + {audio => ( + <button + onClick={() => this.setState({ entryPanelCollapsed: true })} + className={entryStyles.collapse} + onMouseEnter={audio.onMouseEnter} + onMouseLeave={audio.onMouseLeave} + > + <i> + <FontAwesomeIcon icon={faChevronDown} /> + </i> + </button> + )} + </AudioContext.Consumer> )} {startPanel} {devicePanel} @@ -940,111 +1080,143 @@ class UIRoot extends Component { return ( <IntlProvider locale={lang} messages={messages}> - <div className={styles.ui}> - {this.state.dialog} - - {this.state.showProfileEntry && ( - <ProfileEntryPanel finished={this.onProfileFinished} store={this.props.store} /> - )} - - {(!entryFinished || this.isWaitingForAutoExit()) && ( - <div className={styles.uiDialog}> - <div className={dialogBoxContentsClassNames}>{dialogContents}</div> - </div> - )} + <AudioContext.Provider value={this.state.audioContext}> + <AudioContext.Consumer> + {audio => ( + <div className={styles.ui}> + {this.state.dialog} + + {this.state.showProfileEntry && ( + <ProfileEntryPanel finished={this.onProfileFinished} store={this.props.store} /> + )} + + {(!entryFinished || this.isWaitingForAutoExit()) && ( + <div className={styles.uiDialog}> + <div className={dialogBoxContentsClassNames}>{dialogContents}</div> + </div> + )} + + <div + className={classNames({ + [styles.inviteContainer]: true, + [styles.inviteContainerBelowHud]: entryFinished, + [styles.inviteContainerInverted]: this.state.showInviteDialog + })} + > + {!showVREntryButton && ( + <button + className={classNames({ + [styles.hideSmallScreens]: this.props.occupantCount > 1 && entryFinished + })} + onClick={() => this.toggleInviteDialog()} + onMouseEnter={audio.onMouseEnter} + onMouseLeave={audio.onMouseLeave} + > + <FormattedMessage id="entry.invite-others-nag" /> + </button> + )} + {!showVREntryButton && + this.props.occupantCount > 1 && + entryFinished && ( + <button + onClick={this.onMiniInviteClicked} + className={styles.inviteMiniButton} + onMouseEnter={audio.onMouseEnter} + onMouseLeave={audio.onMouseLeave} + > + <span> + {this.state.miniInviteActivated + ? navigator.share + ? "sharing..." + : "copied!" + : "hub.link/" + this.props.hubEntryCode} + </span> + </button> + )} + {showVREntryButton && ( + <button + onClick={() => this.props.scene.enterVR()} + onMouseEnter={audio.onMouseEnter} + onMouseLeave={audio.onMouseLeave} + > + <FormattedMessage id="entry.return-to-vr" /> + </button> + )} + {this.state.showInviteDialog && ( + <InviteDialog + allowShare={!this.props.availableVREntryTypes.isInHMD} + entryCode={this.props.hubEntryCode} + onClose={() => this.setState({ showInviteDialog: false })} + /> + )} + </div> - <div - className={classNames({ - [styles.inviteContainer]: true, - [styles.inviteContainerBelowHud]: entryFinished, - [styles.inviteContainerInverted]: this.state.showInviteDialog - })} - > - {!showVREntryButton && ( - <button - className={classNames({ [styles.hideSmallScreens]: this.props.occupantCount > 1 && entryFinished })} - onClick={() => this.toggleInviteDialog()} - > - <FormattedMessage id="entry.invite-others-nag" /> - </button> - )} - {!showVREntryButton && - this.props.occupantCount > 1 && - entryFinished && ( - <button onClick={this.onMiniInviteClicked} className={styles.inviteMiniButton}> - <span> - {this.state.miniInviteActivated - ? navigator.share - ? "sharing..." - : "copied!" - : "hub.link/" + this.props.hubEntryCode} - </span> + {this.state.showLinkDialog && ( + <LinkDialog + linkCode={this.state.linkCode} + onClose={() => { + this.state.linkCodeCancel(); + this.setState({ showLinkDialog: false, linkCode: null, linkCodeCancel: null }); + }} + /> + )} + + <button + onClick={() => this.showHelpDialog()} + className={styles.helpIcon} + onMouseEnter={audio.onMouseEnter} + onMouseLeave={audio.onMouseLeave} + > + <i> + <FontAwesomeIcon icon={faQuestion} /> + </i> </button> - )} - {showVREntryButton && ( - <button onClick={() => this.props.scene.enterVR()}> - <FormattedMessage id="entry.return-to-vr" /> - </button> - )} - {this.state.showInviteDialog && ( - <InviteDialog - allowShare={!this.props.availableVREntryTypes.isInHMD} - entryCode={this.props.hubEntryCode} - onClose={() => this.setState({ showInviteDialog: false })} - /> - )} - </div> - - {this.state.showLinkDialog && ( - <LinkDialog - linkCode={this.state.linkCode} - onClose={() => { - this.state.linkCodeCancel(); - this.setState({ showLinkDialog: false, linkCode: null, linkCodeCancel: null }); - }} - /> - )} - <button onClick={() => this.showHelpDialog()} className={styles.helpIcon}> - <i> - <FontAwesomeIcon icon={faQuestion} /> - </i> - </button> - - <div className={styles.presenceInfo}> - <FontAwesomeIcon icon={faUsers} /> - <span className={styles.occupantCount}>{this.props.occupantCount || "-"}</span> - </div> - - {this.state.entryStep === ENTRY_STEPS.finished ? ( - <div> - <TwoDHUD.TopHUD - muted={this.state.muted} - frozen={this.state.frozen} - spacebubble={this.state.spacebubble} - onToggleMute={this.toggleMute} - onToggleFreeze={this.toggleFreeze} - onToggleSpaceBubble={this.toggleSpaceBubble} - onSpawnPen={this.spawnPen} - onSpawnCamera={() => this.props.scene.emit("action_spawn_camera")} - /> - {this.props.isSupportAvailable && ( - <div className={styles.nagCornerButton}> - <button onClick={() => this.showInviteTeamDialog()}> - <FormattedMessage id="entry.invite-team-nag" /> - </button> + <div + className={styles.presenceInfo} + onMouseEnter={audio.onMouseEnter} + onMouseLeave={audio.onMouseLeave} + > + <FontAwesomeIcon icon={faUsers} /> + <span className={styles.occupantCount}>{this.props.occupantCount || "-"}</span> </div> - )} - {!this.isWaitingForAutoExit() && ( - <TwoDHUD.BottomHUD - onCreateObject={() => this.showCreateObjectDialog()} - showPhotoPicker={AFRAME.utils.device.isMobile()} - onMediaPicked={this.createObject} - /> - )} - </div> - ) : null} - </div> + + {this.state.entryStep === ENTRY_STEPS.finished ? ( + <div> + <TwoDHUD.TopHUD + muted={this.state.muted} + frozen={this.state.frozen} + spacebubble={this.state.spacebubble} + onToggleMute={this.toggleMute} + onToggleFreeze={this.toggleFreeze} + onToggleSpaceBubble={this.toggleSpaceBubble} + onSpawnPen={this.spawnPen} + onSpawnCamera={() => this.props.scene.emit("action_spawn_camera")} + /> + {this.props.isSupportAvailable && ( + <div className={styles.nagCornerButton}> + <button + onClick={() => this.showInviteTeamDialog()} + onMouseEnter={audio.onMouseEnter} + onMouseLeave={audio.onMouseLeave} + > + <FormattedMessage id="entry.invite-team-nag" /> + </button> + </div> + )} + {!this.isWaitingForAutoExit() && ( + <TwoDHUD.BottomHUD + onCreateObject={() => this.showCreateObjectDialog()} + showPhotoPicker={AFRAME.utils.device.isMobile()} + onMediaPicked={this.createObject} + /> + )} + </div> + ) : null} + </div> + )} + </AudioContext.Consumer> + </AudioContext.Provider> </IntlProvider> ); } diff --git a/src/react-components/updates-dialog.js b/src/react-components/updates-dialog.js index 71d732c2404a366a7bb5e858ba0609bc6d241473..2437d6f229b85dc73133989e2aa018232f7d671e 100644 --- a/src/react-components/updates-dialog.js +++ b/src/react-components/updates-dialog.js @@ -3,6 +3,7 @@ import { FormattedMessage } from "react-intl"; import PropTypes from "prop-types"; import formurlencoded from "form-urlencoded"; import DialogContainer from "./dialog-container.js"; +import { AudioContext } from "../AudioContext"; export default class UpdatesDialog extends Component { static propTypes = { @@ -40,37 +41,57 @@ export default class UpdatesDialog extends Component { return ( <DialogContainer {...other}> - <span> - <p>Sign up to get updates about new features in Hubs.</p> - <form onSubmit={signUpForMailingList}> - <div className="mailing-list-form"> - <input - type="email" - value={this.state.mailingListEmail} - onChange={e => this.setState({ mailingListEmail: e.target.value })} - className="mailing-list-form__email_field" - required - placeholder="Your email here" - /> - <label className="mailing-list-form__privacy"> - <input - className="mailing-list-form__privacy_checkbox" - type="checkbox" - required - value={this.state.mailingListPrivacy} - onChange={e => this.setState({ mailingListPrivacy: e.target.checked })} - /> - <span className="mailing-list-form__privacy_label"> - <FormattedMessage id="mailing_list.privacy_label" />{" "} - <a target="_blank" rel="noopener noreferrer" href="https://www.mozilla.org/en-US/privacy/"> - <FormattedMessage id="mailing_list.privacy_link" /> - </a> - </span> - </label> - <input className="mailing-list-form__submit" type="submit" value="Sign Up Now" /> - </div> - </form> - </span> + <AudioContext.Consumer> + {audio => ( + <span> + <p>Sign up to get updates about new features in Hubs.</p> + <form onSubmit={signUpForMailingList}> + <div className="mailing-list-form"> + <input + type="email" + value={this.state.mailingListEmail} + onChange={e => this.setState({ mailingListEmail: e.target.value })} + className="mailing-list-form__email_field" + required + placeholder="Your email here" + onMouseEnter={audio.onMouseEnter} + onMouseLeave={audio.onMouseLeave} + /> + <label className="mailing-list-form__privacy"> + <input + className="mailing-list-form__privacy_checkbox" + type="checkbox" + required + value={this.state.mailingListPrivacy} + onChange={e => this.setState({ mailingListPrivacy: e.target.checked })} + onMouseEnter={audio.onMouseEnter} + onMouseLeave={audio.onMouseLeave} + /> + <span className="mailing-list-form__privacy_label"> + <FormattedMessage id="mailing_list.privacy_label" />{" "} + <a + target="_blank" + rel="noopener noreferrer" + href="https://www.mozilla.org/en-US/privacy/" + onMouseEnter={audio.onMouseEnter} + onMouseLeave={audio.onMouseLeave} + > + <FormattedMessage id="mailing_list.privacy_link" /> + </a> + </span> + </label> + <input + className="mailing-list-form__submit" + type="submit" + value="Sign Up Now" + onMouseEnter={audio.onMouseEnter} + onMouseLeave={audio.onMouseLeave} + /> + </div> + </form> + </span> + )} + </AudioContext.Consumer> </DialogContainer> ); } diff --git a/src/react-components/webvr-recommend-dialog.js b/src/react-components/webvr-recommend-dialog.js index 264245b273f69e3ba6c17a893362d3f0f715c2a7..edfd4af5d64de6fc658de59336e7c1e42e05b182 100644 --- a/src/react-components/webvr-recommend-dialog.js +++ b/src/react-components/webvr-recommend-dialog.js @@ -1,22 +1,39 @@ import React, { Component } from "react"; import DialogContainer from "./dialog-container.js"; +import { AudioContext } from "../AudioContext"; export default class WebVRRecommendDialog extends Component { render() { return ( <DialogContainer title="Enter in VR" {...this.props}> - <div style={{ display: "flex", flexDirection: "column", alignItems: "center" }}> - <p>To enter Hubs with Oculus or SteamVR, you can use Firefox.</p> - <a className="info-dialog--action-button" href="https://www.mozilla.org/firefox"> - Download Firefox - </a> - <p style={{ fontSize: "0.8em" }}> - For a full list of browsers with experimental VR support, visit{" "} - <a href="https://webvr.rocks" target="_blank" rel="noopener noreferrer"> - WebVR Rocks - </a>. - </p> - </div> + <AudioContext.Consumer> + {audio => ( + <div style={{ display: "flex", flexDirection: "column", alignItems: "center" }}> + <p>To enter Hubs with Oculus or SteamVR, you can use Firefox.</p> + <a + className="info-dialog--action-button" + href="https://www.mozilla.org/firefox" + onMouseEnter={audio.onMouseEnter} + onMouseLeave={audio.onMouseLeave} + > + Download Firefox + </a> + <p style={{ fontSize: "0.8em" }}> + For a full list of browsers with experimental VR support, visit{" "} + <a + href="https://webvr.rocks" + target="_blank" + rel="noopener noreferrer" + onMouseEnter={audio.onMouseEnter} + onMouseLeave={audio.onMouseLeave} + > + WebVR Rocks + </a> + . + </p> + </div> + )} + </AudioContext.Consumer> </DialogContainer> ); }