diff --git a/src/assets/sfx/A_bendUp.wav b/src/assets/sfx/A_bendUp.wav new file mode 100644 index 0000000000000000000000000000000000000000..e7f05b19b081c1545ced8a6c688269ec4097caf1 Binary files /dev/null and b/src/assets/sfx/A_bendUp.wav differ diff --git a/src/assets/sfx/Chiptone_Settings/Readme.txt b/src/assets/sfx/Chiptone_Settings/Readme.txt new file mode 100644 index 0000000000000000000000000000000000000000..324526e4d687fd9f1f0c6643eb6ad11396f40642 --- /dev/null +++ b/src/assets/sfx/Chiptone_Settings/Readme.txt @@ -0,0 +1,8 @@ +The files in this folder were saved using Chiptone by SFB Games +http://sfbgames.com/chiptone/ +---------------------------------------------------------------------- + +- The sound names are not always a 1-to-1 match with the events they are tied to, but it should be relatively easy to figure out how they're being used in Hubs. + +- Generally, we try to export the sounds a 44100Hz, 16bit, and close to Chiptone's full volume. +- Until we have a 'settings' page in Hubs, individual sound volumes will have to be adjusted to taste. diff --git a/src/assets/sfx/Chiptone_Settings/settings_AbendUp.cpt b/src/assets/sfx/Chiptone_Settings/settings_AbendUp.cpt new file mode 100644 index 0000000000000000000000000000000000000000..df2e2de68f58d683b739d260908466cfa7ab1574 Binary files /dev/null and b/src/assets/sfx/Chiptone_Settings/settings_AbendUp.cpt differ diff --git a/src/assets/sfx/Chiptone_Settings/settings_Eb_blip.cpt b/src/assets/sfx/Chiptone_Settings/settings_Eb_blip.cpt new file mode 100644 index 0000000000000000000000000000000000000000..9111b3ea68b2dff6052529295ee66c02a0e5908c Binary files /dev/null and b/src/assets/sfx/Chiptone_Settings/settings_Eb_blip.cpt differ diff --git a/src/assets/sfx/Chiptone_Settings/settings_GtritoneUp.cpt b/src/assets/sfx/Chiptone_Settings/settings_GtritoneUp.cpt new file mode 100644 index 0000000000000000000000000000000000000000..af9b22fe620510cb0748c3fbd3ac57fd74ac2d79 Binary files /dev/null and b/src/assets/sfx/Chiptone_Settings/settings_GtritoneUp.cpt differ diff --git a/src/assets/sfx/Chiptone_Settings/settings_Mute.cpt b/src/assets/sfx/Chiptone_Settings/settings_Mute.cpt new file mode 100644 index 0000000000000000000000000000000000000000..697b6590b95df4b0e1c34094c9561c51ab2f0e3a Binary files /dev/null and b/src/assets/sfx/Chiptone_Settings/settings_Mute.cpt differ diff --git a/src/assets/sfx/Chiptone_Settings/settings_Pause.cpt b/src/assets/sfx/Chiptone_Settings/settings_Pause.cpt new file mode 100644 index 0000000000000000000000000000000000000000..43920b140625a402943a7806fc8d2fb6e7801173 Binary files /dev/null and b/src/assets/sfx/Chiptone_Settings/settings_Pause.cpt differ diff --git a/src/assets/sfx/Chiptone_Settings/settings_Pen.cpt b/src/assets/sfx/Chiptone_Settings/settings_Pen.cpt new file mode 100644 index 0000000000000000000000000000000000000000..07aedf3e08159d4f192c5f29104dda4648559f58 Binary files /dev/null and b/src/assets/sfx/Chiptone_Settings/settings_Pen.cpt differ diff --git a/src/assets/sfx/Chiptone_Settings/settings_PenDraw1.cpt b/src/assets/sfx/Chiptone_Settings/settings_PenDraw1.cpt new file mode 100644 index 0000000000000000000000000000000000000000..6ce45654d8d1684bcfbb41f516159b2490311ed2 Binary files /dev/null and b/src/assets/sfx/Chiptone_Settings/settings_PenDraw1.cpt differ diff --git a/src/assets/sfx/Chiptone_Settings/settings_PicSnapHey.cpt b/src/assets/sfx/Chiptone_Settings/settings_PicSnapHey.cpt new file mode 100644 index 0000000000000000000000000000000000000000..aa39278281205f1c0b9539be80cf89c1296e42e4 Binary files /dev/null and b/src/assets/sfx/Chiptone_Settings/settings_PicSnapHey.cpt differ diff --git a/src/assets/sfx/Chiptone_Settings/settings_TeleportEnd.cpt b/src/assets/sfx/Chiptone_Settings/settings_TeleportEnd.cpt new file mode 100644 index 0000000000000000000000000000000000000000..8a27ce5ecd321c3378baede4e4d311a5b2adeb3d Binary files /dev/null and b/src/assets/sfx/Chiptone_Settings/settings_TeleportEnd.cpt differ diff --git a/src/assets/sfx/Chiptone_Settings/settings_TeleportEnd2.cpt b/src/assets/sfx/Chiptone_Settings/settings_TeleportEnd2.cpt new file mode 100644 index 0000000000000000000000000000000000000000..8238d139c9698c5d7643453eede33d9962962ace Binary files /dev/null and b/src/assets/sfx/Chiptone_Settings/settings_TeleportEnd2.cpt differ diff --git a/src/assets/sfx/Chiptone_Settings/settings_TeleportStart.cpt b/src/assets/sfx/Chiptone_Settings/settings_TeleportStart.cpt new file mode 100644 index 0000000000000000000000000000000000000000..44fc3510add652c4f5fc7691bfa42f4d3c218ee1 Binary files /dev/null and b/src/assets/sfx/Chiptone_Settings/settings_TeleportStart.cpt differ diff --git a/src/assets/sfx/Chiptone_Settings/settings_TeleportStart2.cpt b/src/assets/sfx/Chiptone_Settings/settings_TeleportStart2.cpt new file mode 100644 index 0000000000000000000000000000000000000000..b94094846c15297786528bd547180ce7faee47bf Binary files /dev/null and b/src/assets/sfx/Chiptone_Settings/settings_TeleportStart2.cpt differ diff --git a/src/assets/sfx/Chiptone_Settings/settings_message1.cpt b/src/assets/sfx/Chiptone_Settings/settings_message1.cpt new file mode 100644 index 0000000000000000000000000000000000000000..a2298abc4ad50d7bfd2e890537861f50d933fc93 Binary files /dev/null and b/src/assets/sfx/Chiptone_Settings/settings_message1.cpt differ diff --git a/src/assets/sfx/Chiptone_Settings/settings_quickturn.cpt b/src/assets/sfx/Chiptone_Settings/settings_quickturn.cpt new file mode 100644 index 0000000000000000000000000000000000000000..2f08506f9c11975bf683ad88cbe01185c5331149 Binary files /dev/null and b/src/assets/sfx/Chiptone_Settings/settings_quickturn.cpt differ diff --git a/src/assets/sfx/Chiptone_Settings/settings_slowdown.cpt b/src/assets/sfx/Chiptone_Settings/settings_slowdown.cpt new file mode 100644 index 0000000000000000000000000000000000000000..a913008366a75fb246be51a03217c3210ee3f141 Binary files /dev/null and b/src/assets/sfx/Chiptone_Settings/settings_slowdown.cpt differ diff --git a/src/assets/sfx/Chiptone_Settings/settings_speedUp.cpt b/src/assets/sfx/Chiptone_Settings/settings_speedUp.cpt new file mode 100644 index 0000000000000000000000000000000000000000..54781d01f3b0a16e9b08c6cf97d586737247089e Binary files /dev/null and b/src/assets/sfx/Chiptone_Settings/settings_speedUp.cpt differ diff --git a/src/assets/sfx/Chiptone_Settings/settings_whoosh.cpt b/src/assets/sfx/Chiptone_Settings/settings_whoosh.cpt new file mode 100644 index 0000000000000000000000000000000000000000..59561835d4412a3b0722b514135c4c21d6abe4a7 Binary files /dev/null and b/src/assets/sfx/Chiptone_Settings/settings_whoosh.cpt differ diff --git a/src/assets/sfx/D_teleportEnd.wav b/src/assets/sfx/D_teleportEnd.wav new file mode 100644 index 0000000000000000000000000000000000000000..a94168da94778d6fb3eb69250510066389085687 Binary files /dev/null and b/src/assets/sfx/D_teleportEnd.wav differ diff --git a/src/assets/sfx/D_teleportStart.wav b/src/assets/sfx/D_teleportStart.wav new file mode 100644 index 0000000000000000000000000000000000000000..a6b0e24cb0bd8642cbd6afda928da3845a6a3dab Binary files /dev/null and b/src/assets/sfx/D_teleportStart.wav differ diff --git a/src/assets/sfx/Eb_blip.wav b/src/assets/sfx/Eb_blip.wav new file mode 100644 index 0000000000000000000000000000000000000000..7428237df05a6655ba8a01a88878d43045967794 Binary files /dev/null and b/src/assets/sfx/Eb_blip.wav differ diff --git a/src/assets/sfx/Fs_Mute.wav b/src/assets/sfx/Fs_Mute.wav new file mode 100644 index 0000000000000000000000000000000000000000..b7bf200385d345e2b7534976db37b3f2f0b347bd Binary files /dev/null and b/src/assets/sfx/Fs_Mute.wav differ diff --git a/src/assets/sfx/Fs_UnMute.wav b/src/assets/sfx/Fs_UnMute.wav new file mode 100644 index 0000000000000000000000000000000000000000..4bc442e4cb4287ab2fd3f644e8b6f88cbbb538a4 Binary files /dev/null and b/src/assets/sfx/Fs_UnMute.wav differ diff --git a/src/assets/sfx/G_tritoneUp.wav b/src/assets/sfx/G_tritoneUp.wav new file mode 100644 index 0000000000000000000000000000000000000000..b3489053b13befcd99e4e3a0e6e13c01f6e6e08f Binary files /dev/null and b/src/assets/sfx/G_tritoneUp.wav differ diff --git a/src/assets/sfx/Pause.wav b/src/assets/sfx/Pause.wav new file mode 100644 index 0000000000000000000000000000000000000000..1dcf5bcb8cbb7be9791c81437566b3634ec5fcb0 Binary files /dev/null and b/src/assets/sfx/Pause.wav differ diff --git a/src/assets/sfx/PenDraw1.wav b/src/assets/sfx/PenDraw1.wav new file mode 100644 index 0000000000000000000000000000000000000000..f8f418a9686d169cfd685d35d500620c9748d2ed Binary files /dev/null and b/src/assets/sfx/PenDraw1.wav differ diff --git a/src/assets/sfx/PenSpawn.wav b/src/assets/sfx/PenSpawn.wav new file mode 100644 index 0000000000000000000000000000000000000000..19d5ee41866b3bf9c804d0bea0c8cba2c877334a Binary files /dev/null and b/src/assets/sfx/PenSpawn.wav differ diff --git a/src/assets/sfx/PicSnapHey.wav b/src/assets/sfx/PicSnapHey.wav new file mode 100644 index 0000000000000000000000000000000000000000..5dcb767213c9e528c8ee601e97ecc6870fe530cb Binary files /dev/null and b/src/assets/sfx/PicSnapHey.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/message1.wav b/src/assets/sfx/message1.wav new file mode 100644 index 0000000000000000000000000000000000000000..ade42cae3e29e19378a02d78b492953e93c9e5d2 Binary files /dev/null and b/src/assets/sfx/message1.wav differ diff --git a/src/assets/sfx/quickTurn.wav b/src/assets/sfx/quickTurn.wav new file mode 100644 index 0000000000000000000000000000000000000000..130a9b9740cfac3ecbca923e8a98ed72f06561c8 Binary files /dev/null and b/src/assets/sfx/quickTurn.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.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/whoosh1.wav b/src/assets/sfx/whoosh1.wav new file mode 100644 index 0000000000000000000000000000000000000000..70360742313f25633e2d7b04f239e7b55d5c9da3 Binary files /dev/null and b/src/assets/sfx/whoosh1.wav differ diff --git a/src/assets/sfx/woody_click.wav b/src/assets/sfx/woody_click.wav new file mode 100644 index 0000000000000000000000000000000000000000..a67691024a6721096db4500b63b29fc58ed5430c Binary files /dev/null and b/src/assets/sfx/woody_click.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 9882eb88f481126ce63b9269f7a3dda44cbbfaab..2972d7fcd63b876aeaf22db191d998edb3183351 100644 --- a/src/components/camera-tool.js +++ b/src/components/camera-tool.js @@ -169,6 +169,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/emit-state-change.js b/src/components/emit-state-change.js new file mode 100644 index 0000000000000000000000000000000000000000..c436293d703cfda73c2b3f14102b8abfcdbd8fad --- /dev/null +++ b/src/components/emit-state-change.js @@ -0,0 +1,36 @@ +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() { + this.el.removeEventListener("stateadded", this.stateadded); + this.el.removeEventListener("stateremoved", this.stateremoved); + + if (this.data.transform === "rising") { + this.el.addEventListener("stateadded", this.stateadded); + } + if (this.data.transform === "falling") { + this.el.addEventListener("stateremoved", this.stateremoved); + } + } +}); diff --git a/src/components/freeze-controller.js b/src/components/freeze-controller.js index db434dec0f6bf42ec7bd5ff1bb0496de24d2ba65..bde74c975b2591220aac8e3f373defa05249cf4b 100644 --- a/src/components/freeze-controller.js +++ b/src/components/freeze-controller.js @@ -38,8 +38,10 @@ AFRAME.registerComponent("freeze-controller", { window.APP.store.update({ activity: { hasFoundFreeze: true } }); NAF.connection.adapter.toggleFreeze(); if (NAF.connection.adapter.frozen) { + this.el.emit("play_freeze_sound"); this.el.addState("frozen"); } else { + this.el.emit("play_thaw_sound"); this.el.removeState("frozen"); } } diff --git a/src/components/scene-sound.js b/src/components/scene-sound.js new file mode 100644 index 0000000000000000000000000000000000000000..249da3ebf130cca91a0ca6a544a3c33e9a3fc4b7 --- /dev/null +++ b/src/components/scene-sound.js @@ -0,0 +1,15 @@ +// As temporary measure to avoid having to customize the `sound` component that currently lives in `aframe`, +// we say that a `scene-sound` will have an associated `sound` component that is triggered when the given +// event is fired on the scene. +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..8ff854b02e7bc38bf4725540937b1044a1edd0b9 100644 --- a/src/components/tools/networked-drawing.js +++ b/src/components/tools/networked-drawing.js @@ -105,7 +105,7 @@ AFRAME.registerComponent("networked-drawing", { this.scene.remove(this.drawing); }, - tick() { + tick(t) { const connected = NAF.connection.isConnected() && this.networkedEl; const isMine = connected && NAF.utils.isMine(this.networkedEl); @@ -144,7 +144,7 @@ AFRAME.registerComponent("networked-drawing", { } } - this._deleteLines(); + this._deleteExpiredLines(t); }, _broadcastDrawing: (() => { @@ -193,12 +193,11 @@ AFRAME.registerComponent("networked-drawing", { }; })(), - _deleteLines() { + _deleteExpiredLines(time) { const length = this.networkBufferHistory.length; if (length > 0) { - const now = Date.now(); - const time = this.networkBufferHistory[0].time; - if (length > this.data.maxLines || time + this.data.maxDrawTimeout <= now) { + const drawTime = this.networkBufferHistory[0].time; + if (length > this.data.maxLines || drawTime + this.data.maxDrawTimeout <= time) { const datum = this.networkBufferHistory[0]; if (length > 1) { datum.idxLength += 2 - (this.segments % 2); @@ -381,6 +380,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); @@ -388,7 +388,7 @@ AFRAME.registerComponent("networked-drawing", { const datum = { networkBufferCount: this.networkBufferCount, idxLength: this.vertexCount - 1, - time: Date.now() + time: this.el.sceneEl.clock.elapsedTime * 1000 }; this.networkBufferHistory.push(datum); this.vertexCount = 0; diff --git a/src/components/tools/pen.js b/src/components/tools/pen.js index f09e63101f4366440a06ba4d60481999edaba6b8..ed6583aa0f8ccc13b7246e40845e82ef4a045998 100644 --- a/src/components/tools/pen.js +++ b/src/components/tools/pen.js @@ -160,13 +160,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); diff --git a/src/hub.html b/src/hub.html index b935796c02caec4cfeda0a3a7f712ca0b47f0990..eb014178ae3d76838335191c5e867f369aafcfc9 100644 --- a/src/hub.html +++ b/src/hub.html @@ -66,10 +66,31 @@ <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/D_teleportStart.wav" response-type="arraybuffer" preload="auto"></a-asset-item> + <a-asset-item id="sound_asset-teleport_end" src="./assets/sfx/D_teleportEnd.wav" response-type="arraybuffer" preload="auto"></a-asset-item> + <a-asset-item id="sound_asset-snap_rotate" src="./assets/sfx/quickTurn.wav" response-type="arraybuffer" preload="auto"></a-asset-item> + <a-asset-item id="sound_asset-media_loaded" src="./assets/sfx/A_bendUp.wav" response-type="arraybuffer" preload="auto"></a-asset-item> + <a-asset-item id="sound_asset-hud_hover_start" src="./assets/sfx/Eb_blip.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="DISABLED_sound_asset-hover_off" src="./assets/sfx/tap_mellow.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/tap_mellow.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/Fs_Mute.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-freeze" src="./assets/sfx/tap_mellow.wav" response-type="arraybuffer" preload="auto"></a-asset-item> + <a-asset-item id="sound_asset-thaw" 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/PenSpawn.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/tap_mellow.wav" response-type="arraybuffer" preload="auto"></a-asset-item> + <a-asset-item id="sound_asset-start_draw" src="./assets/sfx/PenDraw1.wav" response-type="arraybuffer" preload="auto"></a-asset-item> + <a-asset-item id="sound_asset-stop_draw" src="./assets/sfx/tap_mellow.wav" response-type="arraybuffer" preload="auto"></a-asset-item> + <a-asset-item id="sound_asset-camera_tool_took_snapshot" src="./assets/sfx/PicSnapHey.wav" response-type="arraybuffer" preload="auto"></a-asset-item> <!-- Templates --> <template id="video-template"> @@ -153,6 +174,10 @@ destroy-at-extreme-distances set-yxz-order pinnable + 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-entity class="ui interactable-ui" stop-event-propagation__grab-start="event: grab-start" stop-event-propagation__grab-end="event: grab-end"> <a-entity class="freeze-menu" visible-while-frozen="withinDistance: 3;"> @@ -174,6 +199,16 @@ sticky-object="autoLockOnRelease: true; autoLockOnLoad: true;" hoverable 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" @@ -347,16 +382,47 @@ vr-mode-toggle-playing__hud-controller > <a-entity in-world-hud="haptic:#player-right-controller;raycaster:#player-right-controller;" rotation="30 0 0"> - <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;" hoverable></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" hoverable></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 penhud" material="alphaTest:0.1;" hoverable></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;" hoverable></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> + <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;" + hoverable + sound__hud_hover_start="src: #sound_asset-hud_hover_start; on: mouseover; poolSize: 5;" + ></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" + hoverable + sound__hud_hover_start="src: #sound_asset-hud_hover_start; on: mouseover; poolSize: 5;" + ></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 penhud" + material="alphaTest:0.1;" + hoverable + sound__hud_hover_start="src: #sound_asset-hud_hover_start; on: mouseover; poolSize: 5;" + ></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;" + hoverable + sound__hud_hover_start="src: #sound_asset-hud_hover_start; on: mouseover; poolSize: 5;" + ></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> </a-entity> </a-entity> @@ -368,6 +434,44 @@ rotation pitch-yaw-rotator set-yxz-order + sound__teleport_start="positional: false; src: #sound_asset-teleport_start; on: nothing; poolSize: 2;" + scene-sound__teleport_start="on: right-teleport_down;" + sound__teleport_start2="positional: false; src: #sound_asset-teleport_start; on: nothing; poolSize: 2;" + scene-sound__teleport_start2="on: left-teleport_down;" + sound__teleport_end="positional: false; src: #sound_asset-teleport_end; on: nothing; poolSize: 2;" + scene-sound__teleport_end="on: right-teleport_up;" + sound__teleport_end2="positional: false; src: #sound_asset-teleport_end; on: nothing; poolSize: 2;" + scene-sound__teleport_end2="on: left-teleport_up;" + sound__snap_rotate_left="positional: false; src: #sound_asset-snap_rotate; on: nothing; poolSize: 3;" + scene-sound__snap_rotate_left="on: snap_rotate_left;" + sound__snap_rotate_right="positional: false; src: #sound_asset-snap_rotate; on: nothing; poolSize: 3;" + scene-sound__snap_rotate_right="on: snap_rotate_right;" + sound__model_loaded="positional: false; src: #sound_asset-media_loaded; on: nothing; poolSize: 2;" + scene-sound__model_loaded="on: model-loaded;" + sound__image_loaded="positional: false; src: #sound_asset-media_loaded; on: nothing; poolSize: 2;" + scene-sound__image_loaded="on: image-loaded;" + sound__hud_action_mute="positional: false; src: #sound_asset-toggle_mute; on: nothing; poolSize: 2;" + scene-sound__hud_action_mute="on: action_mute;" + sound__hud_action_freeze="positional: false; src: #sound_asset-toggle_freeze; on: nothing; poolSize: 2;" + scene-sound__hud_action_freeze="on: action_freeze;" + sound__action_freeze="positional: false; src: #sound_asset-freeze; on: nothing; poolSize: 2;" + scene-sound__action_freeze="on: play_freeze_sound;" + sound__action_thaw="positional: false; src: #sound_asset-thaw; on: nothing; poolSize: 2;" + scene-sound__action_thaw="on: play_thaw_sound;" + sound__hud_action_space_bubble="positional: false; src: #sound_asset-toggle_space_bubble; on: nothing; poolSize: 2;" + scene-sound__hud_action_space_bubble="on: action_space_bubble;" + sound__hud_spawn_pen="positional: false; src: #sound_asset-spawn_pen; on: nothing; poolSize: 2;" + scene-sound__hud_spawn_pen="on: spawn_pen;" + sound__hud_hover_start="positional: false; src: #sound_asset-hud_hover_start; on: nothing; poolSize: 5;" + scene-sound__hud_hover_start="on: play_sound-hud_hover_start;" + sound__hud_click="positional: false; src: #sound_asset-hud_click; on: nothing; poolSize: 1;" + scene-sound__hud_click="on: hud_click;" + sound__cursor_distance_changed="positional: false; src: #sound_asset-cursor_distance_changed; on: nothing; poolSize: 1;" + scene-sound__cursor_distance_changed="on: cursor-distance-changed;" + sound__cursor_distance_change_blocked="positional: false; 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="positional: false; 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 81a821cc3706a80bd1d36480a66e316ce744f088..3205ce594fb5ef653afa346f8b85c9887edf74ee 100644 --- a/src/hub.js +++ b/src/hub.js @@ -63,6 +63,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 "./components/action-to-event"; import "./components/stop-event-propagation"; diff --git a/src/react-components/2d-hud.js b/src/react-components/2d-hud.js index 74577ff1ec0291d8bd14665fe0a7054c8a0f70e3..62f597fa7859b5c9b4affd6b21ac8c2596fc71ea 100644 --- a/src/react-components/2d-hud.js +++ b/src/react-components/2d-hud.js @@ -4,26 +4,35 @@ import cx from "classnames"; import styles from "../assets/stylesheets/2d-hud.scss"; import uiStyles from "../assets/stylesheets/ui-root.scss"; +import { WithHoverSound } from "./wrap-with-audio"; 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)}> + <WithHoverSound> + <div + className={cx(styles.iconButton, styles.mute, { [styles.active]: muted })} + title={muted ? "Unmute Mic" : "Mute Mic"} + onClick={onToggleMute} + /> + </WithHoverSound> + </div> + <WithHoverSound> <div - className={cx(styles.iconButton, styles.mute, { [styles.active]: muted })} - title={muted ? "Unmute Mic" : "Mute Mic"} - onClick={onToggleMute} + className={cx(uiStyles.uiInteractive, styles.iconButton, styles.large, styles.freeze, { + [styles.active]: frozen + })} + title={frozen ? "Resume" : "Pause"} + onClick={onToggleFreeze} /> - </div> - <div - className={cx(uiStyles.uiInteractive, styles.iconButton, styles.large, styles.freeze, { - [styles.active]: frozen - })} - title={frozen ? "Resume" : "Pause"} - onClick={onToggleFreeze} - /> + </WithHoverSound> <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} /> + <WithHoverSound> + <div className={cx(styles.iconButton, styles.spawn_pen)} title={"Drawing Pen"} onClick={onSpawnPen} /> + </WithHoverSound> + <WithHoverSound> + <div className={cx(styles.iconButton, styles.spawn_camera)} title={"Camera"} onClick={onSpawnCamera} /> + </WithHoverSound> </div> </div> ); @@ -61,11 +70,13 @@ const BottomHUD = ({ onCreateObject, showPhotoPicker, onMediaPicked }) => ( <div /> )} <div> - <div - className={cx(uiStyles.uiInteractive, styles.iconButton, styles.large, styles.createObject)} - title={"Create Object"} - onClick={onCreateObject} - /> + <WithHoverSound> + <div + className={cx(uiStyles.uiInteractive, styles.iconButton, styles.large, styles.createObject)} + title={"Create Object"} + onClick={onCreateObject} + /> + </WithHoverSound> </div> </div> ); diff --git a/src/react-components/auto-exit-warning.js b/src/react-components/auto-exit-warning.js index d8691a85182c7f8bbd3ee9a7472e428df9aa2d3b..ede4d6e5290034768eed7eb1c0707a9d47e4a72b 100644 --- a/src/react-components/auto-exit-warning.js +++ b/src/react-components/auto-exit-warning.js @@ -2,6 +2,8 @@ import React from "react"; import { FormattedMessage } from "react-intl"; import PropTypes from "prop-types"; +import { WithHoverSound } from "./wrap-with-audio"; + const AutoExitWarning = props => ( <div className="autoexit-panel"> <div className="autoexit-panel__title"> @@ -12,9 +14,11 @@ const AutoExitWarning = props => ( <div className="autoexit-panel__subtitle"> <FormattedMessage id="autoexit.subtitle" /> </div> - <div className="autoexit-panel__cancel-button" onClick={props.onCancel}> - <FormattedMessage id="autoexit.cancel" /> - </div> + <WithHoverSound> + <div className="autoexit-panel__cancel-button" onClick={props.onCancel}> + <FormattedMessage id="autoexit.cancel" /> + </div> + </WithHoverSound> </div> ); diff --git a/src/react-components/avatar-selector.js b/src/react-components/avatar-selector.js index 0c024655d46b39c77bf7c5f50e87733907cbd790..6ffbe380d160ae375b4171585e7422cd09c8809d 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 { WithHoverSound } from "./wrap-with-audio"; class AvatarSelector extends Component { static propTypes = { @@ -176,12 +177,16 @@ 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> + <WithHoverSound> + <button className="avatar-selector__previous-button" onClick={this.emitChangeToPrevious}> + <FontAwesomeIcon icon={faAngleLeft} /> + </button> + </WithHoverSound> + <WithHoverSound> + <button className="avatar-selector__next-button" onClick={this.emitChangeToNext}> + <FontAwesomeIcon icon={faAngleRight} /> + </button> + </WithHoverSound> </div> ); } diff --git a/src/react-components/create-object-dialog.js b/src/react-components/create-object-dialog.js index 6fa624612dcf4eadb658fa7f74f069983a9aeb53..1951aefbdfe6476fbe9124bcac2d6401f40a6828 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 { WithHoverSound } from "./wrap-with-audio"; const attributionHostnames = { "giphy.com": giphyLogo, @@ -108,14 +109,18 @@ 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> + <WithHoverSound> + <label className={cx(styles.smallButton, styles.cancelIcon)} onClick={this.reset}> + <FontAwesomeIcon icon={faTimes} /> + </label> + </WithHoverSound> ); const uploadButton = ( - <label htmlFor={fileInputId} className={cx(styles.smallButton, styles.uploadIcon)}> - <FontAwesomeIcon icon={faPaperclip} /> - </label> + <WithHoverSound> + <label htmlFor={fileInputId} className={cx(styles.smallButton, styles.uploadIcon)}> + <FontAwesomeIcon icon={faPaperclip} /> + </label> + </WithHoverSound> ); const filenameLabel = <label className={cx(styles.leftSideOfInput)}>{this.state.fileName}</label>; const urlInput = ( @@ -129,7 +134,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}> @@ -146,9 +151,11 @@ 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> + <WithHoverSound> + <button className={styles.actionButton}> + <span>Create</span> + </button> + </WithHoverSound> </div> {this.state.attributionImage ? ( <div> diff --git a/src/react-components/create-room-dialog.js b/src/react-components/create-room-dialog.js index a42a2f36be7fea6bdfef0d74c6120ce59e24df01..b2f1cba898b956e1bbc35feda14638d1f00a669b 100644 --- a/src/react-components/create-room-dialog.js +++ b/src/react-components/create-room-dialog.js @@ -1,6 +1,7 @@ import React, { Component } from "react"; import PropTypes from "prop-types"; import DialogContainer from "./dialog-container.js"; +import { WithHoverSound } from "./wrap-with-audio"; const HUB_NAME_PATTERN = "^[A-Za-z0-9-'\":!@#$%^&*(),.?~ ]{4,64}$"; @@ -54,9 +55,11 @@ export default class CreateRoomDialog extends Component { /> )} <div className="custom-scene-form__buttons"> - <button className="custom-scene-form__action-button"> - <span>Create Room</span> - </button> + <WithHoverSound> + <button className="custom-scene-form__action-button"> + <span>Create Room</span> + </button> + </WithHoverSound> </div> </div> </form> diff --git a/src/react-components/dialog-container.js b/src/react-components/dialog-container.js index 18c37956c7408150d59d2385e251817ad9747cb7..a38923e7a71d57b4bb66aa23c855381af362d3ff 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 { WithHoverSound } from "./wrap-with-audio"; export default class DialogContainer extends Component { static propTypes = { @@ -41,9 +42,11 @@ export default class DialogContainer extends Component { <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> + <WithHoverSound> + <button className="dialog__box__contents__close" onClick={this.props.onClose}> + <span>×</span> + </button> + </WithHoverSound> )} <div className="dialog__box__contents__title">{this.props.title}</div> <div className="dialog__box__contents__body">{this.props.children}</div> diff --git a/src/react-components/entry-buttons.js b/src/react-components/entry-buttons.js index 9e64b3ab4c419b6728e1351e7080629f1b6a97ea..3b4d034d60693c6a2a99538581cae8499b666a69 100644 --- a/src/react-components/entry-buttons.js +++ b/src/react-components/entry-buttons.js @@ -9,27 +9,32 @@ import GearVREntryImg from "../assets/images/gearvr_entry.svg"; import DaydreamEntryImg from "../assets/images/daydream_entry.svg"; 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} /> +import { WithHoverSound } from "./wrap-with-audio"; + +const EntryButton = props => { + return ( + <WithHoverSound> + <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} /> + </div> + )} </div> - )} - </div> - </div> - </button> -); + </div> + </button> + </WithHoverSound> + ); +}; EntryButton.propTypes = { onClick: PropTypes.func, diff --git a/src/react-components/help-dialog.js b/src/react-components/help-dialog.js index abb04a1c9e107323f9993d0e160b352c39753fa2..0e20c0aa98499e87f8e9fa4e52858d610d183f7f 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 { WithHoverSound } from "./wrap-with-audio"; export default class HelpDialog extends Component { render() { @@ -9,9 +10,12 @@ export default class HelpDialog extends Component { <div className="info-dialog__help"> <p style={{ textAlign: "center" }}> Join the Hubs community on{" "} - <a target="_blank" rel="noopener noreferrer" href="https://discord.gg/XzrGUY8"> - Discord - </a>! + <WithHoverSound> + <a target="_blank" rel="noopener noreferrer" href="https://discord.gg/XzrGUY8"> + Discord + </a> + </WithHoverSound> + ! </p> <p>When in a room, other avatars can see and hear you.</p> <p> @@ -28,15 +32,25 @@ export default class HelpDialog extends Component { 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> + <WithHoverSound> + <a target="_blank" rel="noopener noreferrer" href="https://github.com/mozilla/hubs/blob/master/TERMS.md"> + <FormattedMessage id="profile.terms_of_use" /> + </a> + </WithHoverSound> + <WithHoverSound> + <a + target="_blank" + rel="noopener noreferrer" + href="https://github.com/mozilla/hubs/blob/master/PRIVACY.md" + > + <FormattedMessage id="profile.privacy_notice" /> + </a> + </WithHoverSound> + <WithHoverSound> + <a target="_blank" rel="noopener noreferrer" href="/?report"> + <FormattedMessage id="help.report_issue" /> + </a> + </WithHoverSound> </p> </div> </DialogContainer> diff --git a/src/react-components/home-root.js b/src/react-components/home-root.js index c95a342fc2d4ac12755a48321a3bcb97848aa4bb..521490271cab4cc98bbdfab4e682f23d4fef974d 100644 --- a/src/react-components/home-root.js +++ b/src/react-components/home-root.js @@ -21,6 +21,7 @@ import ReportDialog from "./report-dialog.js"; import JoinUsDialog from "./join-us-dialog.js"; import UpdatesDialog from "./updates-dialog.js"; import DialogContainer from "./dialog-container.js"; +import { WithHoverSound } from "./wrap-with-audio"; addLocaleData([...en]); @@ -173,17 +174,29 @@ class HomeRoot extends Component { <div className={styles.home}> <div className={mainContentClassNames}> <div className={styles.headerContent}> - <div className={styles.titleAndNav}> + <div className={styles.titleAndNav} onClick={() => (document.location = "/")}> + <WithHoverSound> + <div className={styles.hubs}>hubs</div> + </WithHoverSound> + <WithHoverSound> + <div className={styles.preview}>preview</div> + </WithHoverSound> <div className={styles.links}> - <a href="https://github.com/mozilla/hubs" rel="noreferrer noopener"> - <FormattedMessage id="home.source_link" /> - </a> - <a href="https://discord.gg/XzrGUY8" rel="noreferrer noopener"> - <FormattedMessage id="home.community_link" /> - </a> - <a href="/spoke" rel="noreferrer noopener"> - Spoke - </a> + <WithHoverSound> + <a href="https://github.com/mozilla/hubs" rel="noreferrer noopener"> + <FormattedMessage id="home.source_link" /> + </a> + </WithHoverSound> + <WithHoverSound> + <a href="https://discord.gg/XzrGUY8" rel="noreferrer noopener"> + <FormattedMessage id="home.community_link" /> + </a> + </WithHoverSound> + <WithHoverSound> + <a href="/spoke" rel="noreferrer noopener"> + Spoke + </a> + </WithHoverSound> </div> </div> </div> @@ -219,62 +232,76 @@ class HomeRoot extends Component { </div> {this.state.environments.length > 1 && ( <div> - <div className={styles.joinButton}> - <a href="/link"> - <FormattedMessage id="home.join_room" /> - </a> - </div> - <div className={styles.spokeButton}> - <a href="/spoke"> - <FormattedMessage id="home.create_with_spoke" /> - </a> - </div> + <WithHoverSound> + <div className={styles.joinButton}> + <a href="/link"> + <FormattedMessage id="home.join_room" /> + </a> + </div> + </WithHoverSound> + <WithHoverSound> + <div className={styles.spokeButton}> + <a href="/spoke"> + <FormattedMessage id="home.create_with_spoke" /> + </a> + </div> + </WithHoverSound> </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.showJoinUsDialog.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> + <WithHoverSound> + <a + className={styles.link} + rel="noopener noreferrer" + href="#" + onClick={this.onDialogLinkClicked(this.showJoinUsDialog.bind(this))} + > + <FormattedMessage id="home.join_us" /> + </a> + </WithHoverSound> + <WithHoverSound> + <a + className={styles.link} + rel="noopener noreferrer" + href="#" + onClick={this.onDialogLinkClicked(this.showUpdatesDialog.bind(this))} + > + <FormattedMessage id="home.get_updates" /> + </a> + </WithHoverSound> + <WithHoverSound> + <a + className={styles.link} + rel="noopener noreferrer" + href="#" + onClick={this.onDialogLinkClicked(this.showReportDialog.bind(this))} + > + <FormattedMessage id="home.report_issue" /> + </a> + </WithHoverSound> + <WithHoverSound> + <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> + </WithHoverSound> + <WithHoverSound> + <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> + </WithHoverSound> <img className={styles.mozLogo} src={mozLogo} /> </div> diff --git a/src/react-components/hub-create-panel.js b/src/react-components/hub-create-panel.js index 640f00d5d790516e0d8cd2d71f9bb71f1a13e89c..11f846e744009d5dca6a329d4a371a54c449c2c1 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 { WithHoverSound } from "./wrap-with-audio"; import default_scene_preview_thumbnail from "../assets/images/default_thumbnail.png"; import styles from "../assets/stylesheets/hub-create.scss"; @@ -186,10 +187,12 @@ class HubCreatePanel extends Component { {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> + <WithHoverSound> + <a href={environmentAuthor.url} rel="noopener noreferrer" className={styles.author}> + <FormattedMessage id="home.environment_author_by" /> + <span>{environmentAuthor.name}</span> + </a> + </WithHoverSound> ) : ( <span className={styles.author}> <FormattedMessage id="home.environment_author_by" /> @@ -199,9 +202,15 @@ class HubCreatePanel extends Component { {environmentAuthor && environmentAuthor.organization && (environmentAuthor.organization.url ? ( - <a href={environmentAuthor.organization.url} rel="noopener noreferrer" className={styles.org}> - <span>{environmentAuthor.organization.name}</span> - </a> + <WithHoverSound> + <a + href={environmentAuthor.organization.url} + rel="noopener noreferrer" + className={styles.org} + > + <span>{environmentAuthor.organization.name}</span> + </a> + </WithHoverSound> ) : ( <span className={styles.org}> <span>{environmentAuthor.organization.name}</span> @@ -209,26 +218,38 @@ class HubCreatePanel extends Component { ))} </div> <div className={styles.footer}> - <button onClick={this.showCustomSceneDialog} className={styles.customButton}> - <FormattedMessage id="home.room_create_options" /> - </button> + <WithHoverSound> + <button onClick={this.showCustomSceneDialog} className={styles.customButton}> + <FormattedMessage id="home.room_create_options" /> + </button> + </WithHoverSound> </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} /> - </button> + <WithHoverSound> + <button + className={styles.prev} + type="button" + tabIndex="1" + onClick={this.setToPreviousEnvironment} + > + <FontAwesomeIcon icon={faAngleLeft} /> + </button> + </WithHoverSound> + <WithHoverSound> + <button className={styles.next} type="button" tabIndex="2" onClick={this.setToNextEnvironment}> + <FontAwesomeIcon icon={faAngleRight} /> + </button> + </WithHoverSound> </div> </div> </div> <div className={styles.container}> - <button type="submit" tabIndex="5" className={styles.submitButton}> - <FormattedMessage id="home.room_create_button" /> - </button> + <WithHoverSound> + <button type="submit" tabIndex="5" className={styles.submitButton}> + <FormattedMessage id="home.room_create_button" /> + </button> + </WithHoverSound> </div> </div> </div> diff --git a/src/react-components/invite-dialog.js b/src/react-components/invite-dialog.js index 62fe7975b1b23fdfa8219407d2ba69652bfb84c7..35992a998116acf154ba7be713deff4ede3d4289 100644 --- a/src/react-components/invite-dialog.js +++ b/src/react-components/invite-dialog.js @@ -5,6 +5,7 @@ import classNames from "classnames"; import { FormattedMessage } from "react-intl"; import { share } from "../utils/share"; +import { WithHoverSound } from "./wrap-with-audio"; import styles from "../assets/stylesheets/invite-dialog.scss"; function pad(num, size) { @@ -50,14 +51,18 @@ export default class InviteDialog extends Component { return ( <div className={styles.dialog}> <div className={styles.attachPoint} /> - <div className={styles.close} onClick={() => this.props.onClose()}> - <span>×</span> - </div> + <WithHoverSound> + <div className={styles.close} onClick={() => this.props.onClose()}> + <span>×</span> + </div> + </WithHoverSound> <div> <FormattedMessage id="invite.enter_via" /> - <a href="https://hub.link" target="_blank" className={styles.hubLinkLink} rel="noopener noreferrer"> - hub.link - </a> + <WithHoverSound> + <a href="https://hub.link" target="_blank" className={styles.hubLinkLink} rel="noopener noreferrer"> + hub.link + </a> + </WithHoverSound> <FormattedMessage id="invite.and_enter_code" /> </div> <div className={styles.code}> @@ -74,20 +79,26 @@ export default class InviteDialog extends Component { <input type="text" readOnly onFocus={e => e.target.select()} value={shortLinkText} /> </div> <div className={styles.buttons}> - <button className={styles.linkButton} onClick={this.copyClicked.bind(this, shortLink)}> - <span>{this.state.copyButtonActive ? "copied!" : "copy"}</span> - </button> + <WithHoverSound> + <button className={styles.linkButton} onClick={this.copyClicked.bind(this, shortLink)}> + <span>{this.state.copyButtonActive ? "copied!" : "copy"}</span> + </button> + </WithHoverSound> {this.props.allowShare && navigator.share && ( - <button className={styles.linkButton} onClick={this.shareClicked.bind(this, shortLink)}> - <span>{this.state.shareButtonActive ? "sharing..." : "share"}</span> - </button> + <WithHoverSound> + <button className={styles.linkButton} onClick={this.shareClicked.bind(this, shortLink)}> + <span>{this.state.shareButtonActive ? "sharing..." : "share"}</span> + </button> + </WithHoverSound> )} {this.props.allowShare && !navigator.share && ( - <button className={styles.linkButton} onClick={this.shareClicked.bind(this, shortLink)}> - <FormattedMessage id="invite.tweet" /> - </button> + <WithHoverSound> + <button className={styles.linkButton} onClick={this.shareClicked.bind(this, shortLink)}> + <FormattedMessage id="invite.tweet" /> + </button> + </WithHoverSound> )} </div> </div> diff --git a/src/react-components/invite-team-dialog.js b/src/react-components/invite-team-dialog.js index a2b3bcd50c40331487642e0d103b402d464bd5f9..6c0b685ddfad382bc2dfc022dc152a3c1c4898a6 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 { WithHoverSound } from "./wrap-with-audio"; export default class InviteTeamDialog extends Component { static propTypes = { @@ -30,9 +31,11 @@ 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> + <WithHoverSound> + <button className="invite-team-form__action-button" onClick={this.inviteClicked}> + <span>{this.state.inviteButtonText}</span> + </button> + </WithHoverSound> </div> </div> </div> diff --git a/src/react-components/link-dialog.js b/src/react-components/link-dialog.js index d3f45051d33736e65ecde5805eee3bb465959e07..e8482958e20120f6beb6a0cbfd34f7f21c398af8 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 { WithHoverSound } from "./wrap-with-audio"; import styles from "../assets/stylesheets/link-dialog.scss"; @@ -17,9 +18,11 @@ export default class LinkDialog extends Component { return ( <div className={styles.dialog}> - <div className={styles.close} onClick={() => this.props.onClose()}> - <span>×</span> - </div> + <WithHoverSound> + <div className={styles.close} onClick={() => this.props.onClose()}> + <span>×</span> + </div> + </WithHoverSound> <div> {!linkCode && ( <div> @@ -41,9 +44,11 @@ export default class LinkDialog extends Component { <div> <FormattedMessage id="link.in_your_browser" /> </div> - <a href="https://hub.link" className={styles.domain} target="_blank" rel="noopener noreferrer"> - hub.link - </a> + <WithHoverSound> + <a href="https://hub.link" className={styles.domain} target="_blank" rel="noopener noreferrer"> + hub.link + </a> + </WithHoverSound> <div> <FormattedMessage id="link.enter_code" /> </div> @@ -59,9 +64,11 @@ export default class LinkDialog extends Component { <div className={styles.keepOpen}> <FormattedMessage id="link.do_not_close" /> </div> - <button className={styles.closeButton} onClick={() => this.props.onClose()}> - <FormattedMessage id="link.cancel" /> - </button> + <WithHoverSound> + <button className={styles.closeButton} onClick={() => this.props.onClose()}> + <FormattedMessage id="link.cancel" /> + </button> + </WithHoverSound> </div> )} </div> diff --git a/src/react-components/link-root.js b/src/react-components/link-root.js index e06442749445204c858846428f548e526a38c3d8..130625b8ddb867978a9db24297d05f79305da646 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 { WithHoverSound } from "./wrap-with-audio"; const MAX_DIGITS = 6; const MAX_LETTERS = 4; @@ -186,9 +187,11 @@ class LinkRoot extends Component { {!this.state.isAlphaMode && this.props.showHeadsetLinkOption && ( <span> - <a href="#" onClick={() => this.toggleMode()}> - <FormattedMessage id="link.linking_a_headset" /> - </a> + <WithHoverSound> + <a href="#" onClick={() => this.toggleMode()}> + <FormattedMessage id="link.linking_a_headset" /> + </a> + </WithHoverSound> </span> )} </div> @@ -199,53 +202,61 @@ class LinkRoot extends Component { ? ["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> + <WithHoverSound key={`char_${i}`}> + <button + disabled={this.state.entered.length === this.maxAllowedChars()} + className={styles.keypadButton} + key={`char_${i}`} + onClick={() => { + if (!hasTouchEvents) this.addToEntry(d); + }} + onTouchStart={() => this.addToEntry(d)} + > + {d} + </button> + </WithHoverSound> ))} {this.props.showHeadsetLinkOption ? ( - <button - className={classNames(styles.keypadButton, styles.keypadToggleMode)} - onTouchStart={() => this.toggleMode()} - onClick={() => { - if (!hasTouchEvents) this.toggleMode(); - }} - > - {this.state.isAlphaMode ? "123" : "ABC"} - </button> + <WithHoverSound> + <button + className={classNames(styles.keypadButton, styles.keypadToggleMode)} + onTouchStart={() => this.toggleMode()} + onClick={() => { + if (!hasTouchEvents) this.toggleMode(); + }} + > + {this.state.isAlphaMode ? "123" : "ABC"} + </button> + </WithHoverSound> ) : ( <div /> )} {!this.state.isAlphaMode && ( + <WithHoverSound> + <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> + </WithHoverSound> + )} + <WithHoverSound> <button - disabled={this.state.entered.length === this.maxAllowedChars()} - className={classNames(styles.keypadButton, styles.keypadZeroButton)} - onTouchStart={() => this.addToEntry(0)} + 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.addToEntry(0); + if (!hasTouchEvents) this.removeChar(); }} > - 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> + </WithHoverSound> </div> <div className={styles.footer}> @@ -255,11 +266,15 @@ class LinkRoot extends Component { className={styles.linkHeadsetFooterLink} style={{ visibility: this.state.isAlphaMode ? "hidden" : "visible" }} > - <img onClick={() => this.toggleMode()} src={HeadsetIcon} className={styles.headsetIcon} /> + <WithHoverSound> + <img onClick={() => this.toggleMode()} src={HeadsetIcon} className={styles.headsetIcon} /> + </WithHoverSound> <span> - <a href="#" onClick={() => this.toggleMode()}> - <FormattedMessage id="link.linking_a_headset" /> - </a> + <WithHoverSound> + <a href="#" onClick={() => this.toggleMode()}> + <FormattedMessage id="link.linking_a_headset" /> + </a> + </WithHoverSound> </span> </div> )} diff --git a/src/react-components/presence-list.js b/src/react-components/presence-list.js index 9770da2fdbd950b442314d6fbb39f181bdb75ed2..40d18b9d34b63e0a1f4871cf75b9c6a63923ce55 100644 --- a/src/react-components/presence-list.js +++ b/src/react-components/presence-list.js @@ -6,6 +6,7 @@ import PhoneImage from "../assets/images/presence_phone.png"; import DesktopImage from "../assets/images/presence_desktop.png"; import HMDImage from "../assets/images/presence_vr.png"; import { FormattedMessage } from "react-intl"; +import { WithHoverSound } from "./wrap-with-audio"; export default class PresenceList extends Component { static propTypes = { @@ -21,22 +22,24 @@ export default class PresenceList extends Component { const image = context && context.mobile ? PhoneImage : context && context.hmd ? HMDImage : DesktopImage; return ( - <div className={styles.row} key={sessionId}> - <div className={styles.device}> - <img src={image} /> - </div> - <div - className={classNames({ - [styles.displayName]: true, - [styles.selfDisplayName]: sessionId === this.props.sessionId - })} - > - {profile && profile.displayName} - </div> - <div className={styles.presence}> - <FormattedMessage id={`presence.in_${meta.presence}`} /> + <WithHoverSound> + <div className={styles.row} key={sessionId}> + <div className={styles.device}> + <img src={image} /> + </div> + <div + className={classNames({ + [styles.displayName]: true, + [styles.selfDisplayName]: sessionId === this.props.sessionId + })} + > + {profile && profile.displayName} + </div> + <div className={styles.presence}> + <FormattedMessage id={`presence.in_${meta.presence}`} /> + </div> </div> - </div> + </WithHoverSound> ); }; diff --git a/src/react-components/profile-entry-panel.js b/src/react-components/profile-entry-panel.js index 307ae73b0dac97d5b50908d5773f28bc2953aec1..e4d02c70a4bb896816c44d8018aeae2da8ea0139 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 { WithHoverSound } from "./wrap-with-audio"; class ProfileEntryPanel extends Component { static propTypes = { @@ -107,18 +108,29 @@ class ProfileEntryPanel extends Component { ref={ifr => (this.avatarSelector = ifr)} /> </div> - <input className={styles.formSubmit} type="submit" value={formatMessage({ id: "profile.save" })} /> + <WithHoverSound> + <input className={styles.formSubmit} type="submit" value={formatMessage({ id: "profile.save" })} /> + </WithHoverSound> <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> + <WithHoverSound> + <a + target="_blank" + rel="noopener noreferrer" + href="https://github.com/mozilla/hubs/blob/master/TERMS.md" + > + <FormattedMessage id="profile.terms_of_use" /> + </a> + </WithHoverSound> + + <WithHoverSound> + <a + target="_blank" + rel="noopener noreferrer" + href="https://github.com/mozilla/hubs/blob/master/PRIVACY.md" + > + <FormattedMessage id="profile.privacy_notice" /> + </a> + </WithHoverSound> </div> </div> </form> diff --git a/src/react-components/profile-info-header.js b/src/react-components/profile-info-header.js new file mode 100644 index 0000000000000000000000000000000000000000..9b5b54ce5f9324390936934f531966bb60cb6c20 --- /dev/null +++ b/src/react-components/profile-info-header.js @@ -0,0 +1,35 @@ +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 { WithHoverSound } from "./wrap-with-audio"; + +export const ProfileInfoHeader = props => ( + <div className="profile-info-header"> + <div className="profile-info-header__menu-buttons"> + <WithHoverSound> + <button className="profile-info-header__menu-buttons__menu-button" onClick={props.onClickHelp}> + <i className="profile-info-header__menu-buttons__menu-button__icon"> + <FontAwesomeIcon icon={faQuestion} /> + </i> + </button> + </WithHoverSound> + </div> + <div className="profile-info-header__profile_display_name"> + <WithHoverSound> + <img src="../assets/images/account.svg" onClick={props.onClickName} className="profile-info-header__icon" /> + </WithHoverSound> + <WithHoverSound> + <div onClick={props.onClickName} title={props.name}> + {props.name} + </div> + </WithHoverSound> + </div> + </div> +); + +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..359f4da55236d7d3f36248df94943195252ccb59 100644 --- a/src/react-components/report-dialog.js +++ b/src/react-components/report-dialog.js @@ -1,5 +1,6 @@ import React, { Component } from "react"; import DialogContainer from "./dialog-container.js"; +import { WithHoverSound } from "./wrap-with-audio"; export default class ReportDialog extends Component { render() { @@ -9,20 +10,29 @@ export default class ReportDialog extends Component { <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>. + <WithHoverSound> + <a href="https://github.com/mozilla/hubs/issues" target="_blank" rel="noopener noreferrer"> + GitHub Issue + </a> + </WithHoverSound> + or e-mail us for support at{" "} + <WithHoverSound> + <a href="mailto:hubs@mozilla.com">hubs@mozilla.com</a> + </WithHoverSound> </p> <p> You can also find us in{" "} - <a href="https://webvr.slack.com/messages/social" target="_blank" rel="noopener noreferrer"> - #social - </a>{" "} + <WithHoverSound> + <a href="https://webvr.slack.com/messages/social" target="_blank" rel="noopener noreferrer"> + #social + </a> + </WithHoverSound> on the{" "} - <a href="https://webvr-slack.herokuapp.com/" target="_blank" rel="noopener noreferrer"> - WebVR Slack - </a>. + <WithHoverSound> + <a href="https://webvr-slack.herokuapp.com/" target="_blank" rel="noopener noreferrer"> + WebVR Slack + </a> + </WithHoverSound> </p> </span> </DialogContainer> diff --git a/src/react-components/safari-dialog.js b/src/react-components/safari-dialog.js index de96bfbf4545e6496e97a3e0dab81593adad7519..608eaa03c5b19cdf26de58440116be6d9707d02e 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 { WithHoverSound } from "./wrap-with-audio"; export default class SafariDialog extends Component { state = { @@ -27,9 +28,11 @@ 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> + <WithHoverSound> + <button className="invite-form__action-button" onClick={onCopyClicked}> + <span>{this.state.copyLinkButtonText}</span> + </button> + </WithHoverSound> </div> </div> </div> diff --git a/src/react-components/scene-ui.js b/src/react-components/scene-ui.js index 0a49bd07bd7b3504957dea05ff318e5a6052863f..43cd7df8a9d00aba70423ebd2b7b74b80051e0dc 100644 --- a/src/react-components/scene-ui.js +++ b/src/react-components/scene-ui.js @@ -8,6 +8,7 @@ import hubLogo from "../assets/images/hub-preview-white.png"; import spokeLogo from "../assets/images/spoke_logo_black.png"; import { getReticulumFetchUrl } from "../utils/phoenix-utils"; import { generateHubName } from "../utils/name-generation"; +import { WithHoverSound } from "./wrap-with-audio"; import CreateRoomDialog from "./create-room-dialog.js"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faEllipsisH } from "@fortawesome/free-solid-svg-icons/faEllipsisH"; @@ -92,13 +93,15 @@ class SceneUI extends Component { <span key={a.url}> <a href={a.url} target="_blank" rel="noopener noreferrer"> {a.name} by {a.author} {source} - </a> + </a> + </span> ); } else { return ( <span key={`${a.name} ${a.author}`}> - {a.name} by {a.author} + {a.name} by {a.author} + </span> ); } @@ -108,7 +111,8 @@ class SceneUI extends Component { if (!this.props.sceneAttributions.extras) { attributions = ( <span> - <span>{this.props.sceneAttributions.creator ? `by ${this.props.sceneAttributions.creator}` : ""}</span> + <span>{this.props.sceneAttributions.creator ? `by ${this.props.sceneAttributions.creator}` : ""}</span> + <br /> {this.props.sceneAttributions.content && this.props.sceneAttributions.content.map(toAttributionSpan)} </span> @@ -133,26 +137,37 @@ class SceneUI extends Component { <div className={styles.whiteOverlay} /> <div className={styles.grid}> <div className={styles.mainPanel}> - <a href="/" className={styles.logo}> - <img src={hubLogo} /> - </a> + <WithHoverSound> + <a href="/" className={styles.logo}> + <img src={hubLogo} /> + </a> + </WithHoverSound> <div className={styles.logoTagline}> <FormattedMessage id="scene.logo_tagline" /> </div> <div className={styles.createButtons}> - <button className={styles.createButton} onClick={this.createRoom}> - <FormattedMessage id="scene.create_button" /> - </button> - <button className={styles.optionsButton} onClick={() => this.setState({ showCustomRoomDialog: true })}> - <FontAwesomeIcon icon={faEllipsisH} /> - </button> + <WithHoverSound> + <button className={styles.createButton} onClick={this.createRoom}> + <FormattedMessage id="scene.create_button" /> + </button> + </WithHoverSound> + <WithHoverSound> + <button + className={styles.optionsButton} + onClick={() => this.setState({ showCustomRoomDialog: true })} + > + <FontAwesomeIcon icon={faEllipsisH} /> + </button> + </WithHoverSound> </div> - <a href={tweetLink} rel="noopener noreferrer" target="_blank" className={styles.tweetButton}> - <img src="../assets/images/twitter.svg" /> - <div> - <FormattedMessage id="scene.tweet_button" /> - </div> - </a> + <WithHoverSound> + <a href={tweetLink} rel="noopener noreferrer" target="_blank" className={styles.tweetButton}> + <img src="../assets/images/twitter.svg" /> + <div> + <FormattedMessage id="scene.tweet_button" /> + </div> + </a> + </WithHoverSound> </div> </div> <div className={styles.info}> diff --git a/src/react-components/slack-dialog.js b/src/react-components/slack-dialog.js new file mode 100644 index 0000000000000000000000000000000000000000..d53329b3d7f2ef4ce448f5035898c49107eee9ec --- /dev/null +++ b/src/react-components/slack-dialog.js @@ -0,0 +1,41 @@ +import React, { Component } from "react"; +import DialogContainer from "./dialog-container.js"; +import { WithHoverSound } from "./wrap-with-audio"; + +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{" "} + <WithHoverSound> + <a href="https://webvr-slack.herokuapp.com/" target="_blank" rel="noopener noreferrer"> + WebVR Slack + </a> + </WithHoverSound>{" "} + in the{" "} + <WithHoverSound> + <a href="https://webvr.slack.com/messages/social" target="_blank" rel="noopener noreferrer"> + #social + </a> + </WithHoverSound>{" "} + channel. + <br /> + VR meetups every Friday at noon PDT! + </p> + <p> + Or, tweet at{" "} + <WithHoverSound> + <a href="https://twitter.com/mozillareality" target="_blank" rel="noopener noreferrer"> + @mozillareality + </a> + </WithHoverSound>{" "} + on Twitter. + </p> + </span> + </DialogContainer> + ); + } +} diff --git a/src/react-components/ui-root.js b/src/react-components/ui-root.js index 012afee70e1e2aa4a590a49f800512a5944a7cc4..77b0dfba4310c7986f9e4775cc38aa35c2449e8b 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 { ReactAudioContext, WithHoverSound } from "./wrap-with-audio"; import { lang, messages } from "../utils/i18n"; import AutoExitWarning from "./auto-exit-warning"; @@ -143,6 +144,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: { + playSound: sound => { + scene.emit(sound); + }, + onMouseLeave: () => { + // scene.emit("play_sound-hud_mouse_leave"); + } + } + }); } componentWillUnmount() { @@ -555,25 +567,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.hubId; @@ -607,16 +629,23 @@ class UIRoot extends Component { 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 /> + <p />A room may be closed if we receive reports that it violates our{" "} + <WithHoverSound> + <a target="_blank" rel="noreferrer noopener" href="https://github.com/mozilla/hubs/blob/master/TERMS.md"> + Terms of Use + </a> + </WithHoverSound> + .<br /> + If you have questions, contact us at{" "} + <WithHoverSound> + <a href="mailto:hubs@mozilla.com">hubs@mozilla.com</a> + </WithHoverSound> + .<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>. + <WithHoverSound> + <a href="https://github.com/mozilla/hubs">GitHub</a> + </WithHoverSound> + . </div> ); } else if (this.props.platformUnsupportedReason === "no_data_channels") { @@ -624,15 +653,23 @@ class UIRoot extends Component { 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>. + <WithHoverSound> + <a + href="https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/createDataChannel#Browser_compatibility" + rel="noreferrer noopener" + > + WebRTC Data Channels + </a> + </WithHoverSound> + , which is required to use Hubs. + <br /> + If you"d like to use Hubs with Oculus or SteamVR, you can{" "} + <WithHoverSound> + <a href="https://www.mozilla.org/firefox" rel="noreferrer noopener"> + Download Firefox + </a> + </WithHoverSound> + . </div> ); } else { @@ -644,7 +681,10 @@ class UIRoot extends Component { <p /> {this.props.roomUnavailableReason && ( <div> - You can also <a href="/">create a new room</a>. + You can also{" "} + <WithHoverSound> + <a href="/">create a new room</a>. + </WithHoverSound> </div> )} </div> @@ -698,10 +738,12 @@ class UIRoot extends Component { <div className={entryStyles.name}>{this.props.hubName}</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> + <WithHoverSound> + <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> + </WithHoverSound> <form onSubmit={this.sendMessage}> <div className={styles.messageEntry} style={{ height: pendingMessageFieldHeight }}> @@ -721,7 +763,9 @@ class UIRoot extends Component { }} placeholder="Send a message..." /> - <input className={styles.messageEntrySubmit} type="submit" value="send" /> + <WithHoverSound> + <input className={styles.messageEntrySubmit} type="submit" value="send" /> + </WithHoverSound> </div> </form> </div> @@ -741,12 +785,14 @@ class UIRoot extends Component { )} <div className={entryStyles.buttonContainer}> - <button - className={classNames([entryStyles.actionButton, entryStyles.wideButton])} - onClick={() => this.handleStartEntry()} - > - <FormattedMessage id="entry.enter-room" /> - </button> + <WithHoverSound> + <button + className={classNames([entryStyles.actionButton, entryStyles.wideButton])} + onClick={() => this.handleStartEntry()} + > + <FormattedMessage id="entry.enter-room" /> + </button> + </WithHoverSound> </div> </div> ); @@ -821,20 +867,26 @@ class UIRoot extends Component { </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> + <WithHoverSound> + <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> + </WithHoverSound> ) : ( - <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> + <WithHoverSound> + <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> + </WithHoverSound> )} </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 className="mic-grant-panel__next-container"> + <WithHoverSound> + <button className={classNames("mic-grant-panel__next")} onClick={this.onMicGrantButton}> + <FormattedMessage id="audio.granted-next" /> + </button> + </WithHoverSound> + </div> </div> </div> ); @@ -883,38 +935,43 @@ class UIRoot extends Component { /> )} </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> + <WithHoverSound> + <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> + </WithHoverSound> </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} - > - {this.state.micDevices.map(d => ( - <option key={d.deviceId} value={d.deviceId}> - {d.label} - </option> - ))} - </select> + <WithHoverSound> + <select + className="audio-setup-panel__device-chooser__dropdown" + value={this.selectedMicDeviceId()} + onChange={this.micDeviceChanged} + > + {this.state.micDevices.map(d => ( + <option key={d.deviceId} value={d.deviceId}> + + {d.label} + </option> + ))} + </select> + </WithHoverSound> <img className="audio-setup-panel__device-chooser__mic-icon" src="../assets/images/mic_small.png" @@ -941,9 +998,11 @@ class UIRoot extends Component { )} </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> + <WithHoverSound> + <button className="audio-setup-panel__enter-button" onClick={this.onAudioReadyButton}> + <FormattedMessage id="audio.enter-now" /> + </button> + </WithHoverSound> </div> </div> ); @@ -973,11 +1032,13 @@ class UIRoot extends Component { dialogContents = ( <div className={entryStyles.entryDialog}> <div> </div> - <button onClick={() => this.setState({ entryPanelCollapsed: false })} className={entryStyles.expand}> - <i> - <FontAwesomeIcon icon={faChevronUp} /> - </i> - </button> + <WithHoverSound> + <button onClick={() => this.setState({ entryPanelCollapsed: false })} className={entryStyles.expand}> + <i> + <FontAwesomeIcon icon={faChevronUp} /> + </i> + </button> + </WithHoverSound> </div> ); } else { @@ -989,11 +1050,13 @@ 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> + <WithHoverSound> + <button onClick={() => this.setState({ entryPanelCollapsed: true })} className={entryStyles.collapse}> + <i> + <FontAwesomeIcon icon={faChevronDown} /> + </i> + </button> + </WithHoverSound> )} {startPanel} {devicePanel} @@ -1015,179 +1078,192 @@ class UIRoot extends Component { const textRows = this.state.pendingMessage.split("\n").length; const pendingMessageTextareaHeight = textRows * 28 + "px"; const pendingMessageFieldHeight = textRows * 28 + 20 + "px"; - return ( - <IntlProvider locale={lang} messages={messages}> - <div className={styles.ui}> - {this.state.dialog} - - {this.state.showProfileEntry && ( - <ProfileEntryPanel finished={this.onProfileFinished} store={this.props.store} /> - )} + <ReactAudioContext.Provider value={this.state.audioContext}> + <IntlProvider locale={lang} messages={messages}> + <div className={styles.ui}> + {this.state.dialog} - {(!entryFinished || this.isWaitingForAutoExit()) && ( - <div className={styles.uiDialog}> - <PresenceLog entries={this.props.presenceLogEntries || []} hubId={this.props.hubId} /> - <div className={dialogBoxContentsClassNames}>{dialogContents}</div> - </div> - )} + {this.state.showProfileEntry && ( + <ProfileEntryPanel finished={this.onProfileFinished} store={this.props.store} /> + )} - {entryFinished && ( - <PresenceLog inRoom={true} entries={this.props.presenceLogEntries || []} hubId={this.props.hubId} /> - )} - {entryFinished && ( - <form onSubmit={this.sendMessage}> - <div className={styles.messageEntryInRoom} style={{ height: pendingMessageFieldHeight }}> - <textarea - style={{ height: pendingMessageTextareaHeight }} - className={classNames([ - styles.messageEntryInput, - styles.messageEntryInputInRoom, - "chat-focus-target" - ])} - value={this.state.pendingMessage} - rows={textRows} - onFocus={e => e.target.select()} - onChange={e => { - e.stopPropagation(); - this.setState({ pendingMessage: e.target.value }); - }} - onKeyDown={e => { - if (e.keyCode === 13 && !e.shiftKey) { - this.sendMessage(e); - } else if (e.keyCode === 13 && e.shiftKey && e.ctrlKey) { - spawnChatMessage(e.target.value); - this.setState({ pendingMessage: "" }); - } else if (e.keyCode === 27) { - e.target.blur(); - } - }} - placeholder="Send a message..." - /> - <input - className={classNames([styles.messageEntrySubmit, styles.messageEntrySubmitInRoom])} - type="submit" - value="send" - /> - <button - className={classNames([styles.messageEntrySpawn])} - onClick={() => { - if (this.state.pendingMessage.length > 0) { - spawnChatMessage(this.state.pendingMessage); - this.setState({ pendingMessage: "" }); - } else { - this.showCreateObjectDialog(); - } - }} - /> + {(!entryFinished || this.isWaitingForAutoExit()) && ( + <div className={styles.uiDialog}> + <PresenceLog entries={this.props.presenceLogEntries || []} hubId={this.props.hubId} /> + <div className={dialogBoxContentsClassNames}>{dialogContents}</div> </div> - </form> - )} - - <div - className={classNames({ - [styles.inviteContainer]: true, - [styles.inviteContainerBelowHud]: entryFinished, - [styles.inviteContainerInverted]: this.state.showInviteDialog - })} - > - {!showVREntryButton && ( - <button - className={classNames({ [styles.hideSmallScreens]: this.occupantCount() > 1 && entryFinished })} - onClick={() => this.toggleInviteDialog()} - > - <FormattedMessage id="entry.invite-others-nag" /> - </button> )} - {!showVREntryButton && - this.occupantCount() > 1 && - entryFinished && ( - <button onClick={this.onMiniInviteClicked} className={styles.inviteMiniButton}> - <span> - {this.state.miniInviteActivated - ? navigator.share - ? "sharing..." - : "copied!" - : "hub.link/" + this.props.hubId} - </span> - </button> - )} - {showVREntryButton && ( - <button onClick={() => this.props.scene.enterVR()}> - <FormattedMessage id="entry.return-to-vr" /> - </button> + + {entryFinished && ( + <PresenceLog inRoom={true} entries={this.props.presenceLogEntries || []} hubId={this.props.hubId} /> )} - {this.state.showInviteDialog && ( - <InviteDialog - allowShare={!this.props.availableVREntryTypes.isInHMD} - entryCode={this.props.hubEntryCode} - hubId={this.props.hubId} - onClose={() => this.setState({ showInviteDialog: false })} - /> + {entryFinished && ( + <form onSubmit={this.sendMessage}> + <div className={styles.messageEntryInRoom} style={{ height: pendingMessageFieldHeight }}> + <textarea + style={{ height: pendingMessageTextareaHeight }} + className={classNames([ + styles.messageEntryInput, + styles.messageEntryInputInRoom, + "chat-focus-target" + ])} + value={this.state.pendingMessage} + rows={textRows} + onFocus={e => e.target.select()} + onChange={e => { + e.stopPropagation(); + this.setState({ pendingMessage: e.target.value }); + }} + onKeyDown={e => { + if (e.keyCode === 13 && !e.shiftKey) { + this.sendMessage(e); + } else if (e.keyCode === 13 && e.shiftKey && e.ctrlKey) { + spawnChatMessage(e.target.value); + this.setState({ pendingMessage: "" }); + } else if (e.keyCode === 27) { + e.target.blur(); + } + }} + placeholder="Send a message..." + /> + <input + className={classNames([styles.messageEntrySubmit, styles.messageEntrySubmitInRoom])} + type="submit" + value="send" + /> + <WithHoverSound> + <button + className={classNames([styles.messageEntrySpawn])} + onClick={() => { + if (this.state.pendingMessage.length > 0) { + spawnChatMessage(this.state.pendingMessage); + this.setState({ pendingMessage: "" }); + } else { + this.showCreateObjectDialog(); + } + }} + /> + </WithHoverSound> + </div> + </form> )} - </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 - onClick={() => this.setState({ showPresenceList: !this.state.showPresenceList })} - className={classNames({ - [styles.presenceInfo]: true, - [styles.presenceInfoSelected]: this.state.showPresenceList - })} - > - <FontAwesomeIcon icon={faUsers} /> - <span className={styles.occupantCount}>{this.occupantCount()}</span> - </div> - {this.state.showPresenceList && ( - <PresenceList presences={this.props.presences} sessionId={this.props.sessionId} /> - )} - - {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" /> + <div + className={classNames({ + [styles.inviteContainer]: true, + [styles.inviteContainerBelowHud]: entryFinished, + [styles.inviteContainerInverted]: this.state.showInviteDialog + })} + > + {!showVREntryButton && ( + <WithHoverSound> + <button + className={classNames({ [styles.hideSmallScreens]: this.occupantCount() > 1 && entryFinished })} + onClick={() => this.toggleInviteDialog()} + > + <FormattedMessage id="entry.invite-others-nag" /> </button> - </div> + </WithHoverSound> )} - {!this.isWaitingForAutoExit() && ( - <TwoDHUD.BottomHUD - onCreateObject={() => this.showCreateObjectDialog()} - showPhotoPicker={AFRAME.utils.device.isMobile()} - onMediaPicked={this.createObject} + {!showVREntryButton && + this.occupantCount() > 1 && + entryFinished && ( + <WithHoverSound> + <button onClick={this.onMiniInviteClicked} className={styles.inviteMiniButton}> + <span> + {this.state.miniInviteActivated + ? navigator.share + ? "sharing..." + : "copied!" + : "hub.link/" + this.props.hubId} + </span> + </button> + </WithHoverSound> + )} + {showVREntryButton && ( + <WithHoverSound> + <button onClick={() => this.props.scene.enterVR()}> + <FormattedMessage id="entry.return-to-vr" /> + </button> + </WithHoverSound> + )} + {this.state.showInviteDialog && ( + <InviteDialog + allowShare={!this.props.availableVREntryTypes.isInHMD} + entryCode={this.props.hubEntryCode} + hubId={this.props.hubId} + onClose={() => this.setState({ showInviteDialog: false })} /> )} </div> - ) : null} - </div> - </IntlProvider> + + {this.state.showLinkDialog && ( + <LinkDialog + linkCode={this.state.linkCode} + onClose={() => { + this.state.linkCodeCancel(); + this.setState({ showLinkDialog: false, linkCode: null, linkCodeCancel: null }); + }} + /> + )} + + <WithHoverSound> + <button onClick={() => this.showHelpDialog()} className={styles.helpIcon}> + <i> + <FontAwesomeIcon icon={faQuestion} /> + </i> + </button> + </WithHoverSound> + + <div + onClick={() => this.setState({ showPresenceList: !this.state.showPresenceList })} + className={classNames({ + [styles.presenceInfo]: true, + [styles.presenceInfoSelected]: this.state.showPresenceList + })} + > + <FontAwesomeIcon icon={faUsers} /> + <span className={styles.occupantCount}>{this.occupantCount()}</span> + </div> + + {this.state.showPresenceList && ( + <PresenceList presences={this.props.presences} sessionId={this.props.sessionId} /> + )} + + {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}> + <WithHoverSound> + <button onClick={() => this.showInviteTeamDialog()}> + <FormattedMessage id="entry.invite-team-nag" /> + </button> + </WithHoverSound> + </div> + )} + {!this.isWaitingForAutoExit() && ( + <TwoDHUD.BottomHUD + onCreateObject={() => this.showCreateObjectDialog()} + showPhotoPicker={AFRAME.utils.device.isMobile()} + onMediaPicked={this.createObject} + /> + )} + </div> + ) : null} + </div> + </IntlProvider> + </ReactAudioContext.Provider> ); } } diff --git a/src/react-components/updates-dialog.js b/src/react-components/updates-dialog.js index 71d732c2404a366a7bb5e858ba0609bc6d241473..b5a746582e5b460c73b1bb1b5105ba6003537744 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 { WithHoverSound } from "./wrap-with-audio"; export default class UpdatesDialog extends Component { static propTypes = { @@ -44,30 +45,38 @@ export default class UpdatesDialog extends Component { <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"> + <WithHoverSound> <input - className="mailing-list-form__privacy_checkbox" - type="checkbox" + type="email" + value={this.state.mailingListEmail} + onChange={e => this.setState({ mailingListEmail: e.target.value })} + className="mailing-list-form__email_field" required - value={this.state.mailingListPrivacy} - onChange={e => this.setState({ mailingListPrivacy: e.target.checked })} + placeholder="Your email here" /> + </WithHoverSound> + <label className="mailing-list-form__privacy"> + <WithHoverSound> + <input + className="mailing-list-form__privacy_checkbox" + type="checkbox" + required + value={this.state.mailingListPrivacy} + onChange={e => this.setState({ mailingListPrivacy: e.target.checked })} + /> + </WithHoverSound> <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> + <WithHoverSound> + <a target="_blank" rel="noopener noreferrer" href="https://www.mozilla.org/en-US/privacy/"> + <FormattedMessage id="mailing_list.privacy_link" /> + </a> + </WithHoverSound> </span> </label> - <input className="mailing-list-form__submit" type="submit" value="Sign Up Now" /> + <WithHoverSound> + <input className="mailing-list-form__submit" type="submit" value="Sign Up Now" /> + </WithHoverSound> </div> </form> </span> diff --git a/src/react-components/webvr-recommend-dialog.js b/src/react-components/webvr-recommend-dialog.js index 264245b273f69e3ba6c17a893362d3f0f715c2a7..7080457c4d37164a9e75e6f5af1c8003a534cbbe 100644 --- a/src/react-components/webvr-recommend-dialog.js +++ b/src/react-components/webvr-recommend-dialog.js @@ -1,5 +1,6 @@ import React, { Component } from "react"; import DialogContainer from "./dialog-container.js"; +import { WithHoverSound } from "./wrap-with-audio"; export default class WebVRRecommendDialog extends Component { render() { @@ -7,14 +8,18 @@ export default class WebVRRecommendDialog extends Component { <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> + <WithHoverSound> + <a className="info-dialog--action-button" href="https://www.mozilla.org/firefox"> + Download Firefox + </a> + </WithHoverSound> <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>. + <WithHoverSound> + <a href="https://webvr.rocks" target="_blank" rel="noopener noreferrer"> + WebVR Rocks + </a> + </WithHoverSound> </p> </div> </DialogContainer> diff --git a/src/react-components/wrap-with-audio.js b/src/react-components/wrap-with-audio.js new file mode 100644 index 0000000000000000000000000000000000000000..71cef333b21f4d0a087ce16785c09c78078f7c80 --- /dev/null +++ b/src/react-components/wrap-with-audio.js @@ -0,0 +1,28 @@ +import React from "react"; +import PropTypes from "prop-types"; + +export const ReactAudioContext = React.createContext({}); + +export const hudHoverSound = "play_sound-hud_hover_start"; + +export const WithHoverSound = ({ sound, children }) => { + return ( + <ReactAudioContext.Consumer> + {context => { + return React.cloneElement(children, { + onMouseEnter: e => { + if (context && context.playSound) { + context.playSound(sound || hudHoverSound); + } + children.props.onMouseEnter && children.props.onMouseEnter(e); + } + }); + }} + </ReactAudioContext.Consumer> + ); +}; + +WithHoverSound.propTypes = { + children: PropTypes.object, + sound: PropTypes.string +};