diff --git a/src/assets/stylesheets/avatar-selector.scss b/src/assets/stylesheets/avatar-selector.scss index 1663ec5b6ba92ef81c1b7fc9955f98dd0788272b..5367b3c3c2826dbf3e1d67bf9454e23e776efb9a 100644 --- a/src/assets/stylesheets/avatar-selector.scss +++ b/src/assets/stylesheets/avatar-selector.scss @@ -1,4 +1,3 @@ -@import 'fonts'; @import 'shared'; #selector-root { diff --git a/src/assets/stylesheets/loader.scss b/src/assets/stylesheets/loader.scss index 400636ca8f4b7fb541ed62359b8f0b30238bb63e..a770b54d46a7a4126c69017b89257e343d66c4ce 100644 --- a/src/assets/stylesheets/loader.scss +++ b/src/assets/stylesheets/loader.scss @@ -5,6 +5,12 @@ } .loading-panel { + @extend %default-font; + pointer-events: none; + color: white; + position: absolute; + top: 0; + left: 0; background-color: black; width: 100%; height: 100%; diff --git a/src/react-components/avatar-selector.js b/src/react-components/avatar-selector.js index 7cf71b7c5e130c2bbd16a54e5b247f3a1cea74d5..6ce9a8a172726bb1b6d6194e13d5f88aa29185b5 100644 --- a/src/react-components/avatar-selector.js +++ b/src/react-components/avatar-selector.js @@ -1,49 +1,49 @@ -import React, { Component } from 'react'; -import PropTypes from 'prop-types'; -import { injectIntl, FormattedMessage } from 'react-intl'; -import FontAwesomeIcon from '@fortawesome/react-fontawesome'; -import faAngleLeft from '@fortawesome/fontawesome-free-solid/faAngleLeft'; -import faAngleRight from '@fortawesome/fontawesome-free-solid/faAngleRight'; -import meetingSpace from '../assets/environments/MeetingSpace1_mesh.glb'; +import React, { Component } from "react"; +import PropTypes from "prop-types"; +import { injectIntl, FormattedMessage } from "react-intl"; +import FontAwesomeIcon from "@fortawesome/react-fontawesome"; +import faAngleLeft from "@fortawesome/fontawesome-free-solid/faAngleLeft"; +import faAngleRight from "@fortawesome/fontawesome-free-solid/faAngleRight"; +import meetingSpace from "../assets/environments/cliff_meeting_space/MeetingSpace1_mesh.glb"; class AvatarSelector extends Component { static propTypes = { avatars: PropTypes.array, avatarId: PropTypes.string, - onChange: PropTypes.func, - } + onChange: PropTypes.func + }; - getAvatarIndex = (direction=0) => { + getAvatarIndex = (direction = 0) => { const currAvatarIndex = this.props.avatars.findIndex(avatar => avatar.id === this.props.avatarId); const numAvatars = this.props.avatars.length; return ((currAvatarIndex + direction) % numAvatars + numAvatars) % numAvatars; - } - nextAvatarIndex = () => this.getAvatarIndex(1) - previousAvatarIndex = () => this.getAvatarIndex(-1) + }; + nextAvatarIndex = () => this.getAvatarIndex(1); + previousAvatarIndex = () => this.getAvatarIndex(-1); emitChangeToNext = () => { const nextAvatarId = this.props.avatars[this.nextAvatarIndex()].id; this.props.onChange(nextAvatarId); - } + }; emitChangeToPrevious = () => { const previousAvatarId = this.props.avatars[this.previousAvatarIndex()].id; this.props.onChange(previousAvatarId); - } + }; componentDidUpdate(prevProps) { - if (this.props.avatarId !== prevProps.avatarId) { + if (this.props.avatarId !== prevProps.avatarId) { // HACK - a-animation ought to restart the animation when the `to` attribute changes, but it doesn't // so we need to force it here. - const currRot = this.animation.parentNode.getAttribute('rotation'); - this.animation.setAttribute('from', `${currRot.x} ${currRot.y} ${currRot.z}`); + const currRot = this.animation.parentNode.getAttribute("rotation"); + this.animation.setAttribute("from", `${currRot.x} ${currRot.y} ${currRot.z}`); this.animation.stop(); this.animation.handleMixinUpdate(); this.animation.start(); } } - render () { + render() { const avatarAssets = this.props.avatars.map(avatar => ( <a-progressive-asset id={avatar.id} @@ -51,74 +51,63 @@ class AvatarSelector extends Component { response-type="arraybuffer" high-src={`${avatar.models.high}`} low-src={`${avatar.models.low}`} - ></a-progressive-asset> + /> )); const avatarEntities = this.props.avatars.map((avatar, i) => ( <a-entity key={avatar.id} position="0 0 0" rotation={`0 ${360 * -i / this.props.avatars.length} 0`}> - <a-gltf-entity position="0 0 5" rotation="0 0 0" src={'#' + avatar.id} inflate="true"> + <a-gltf-entity position="0 0 5" rotation="0 0 0" src={"#" + avatar.id} inflate="true"> <template data-selector=".RootScene"> - <a-entity animation-mixer></a-entity> + <a-entity animation-mixer /> </template> <a-animation attribute="rotation" dur="2000" to={`0 ${this.getAvatarIndex() === i ? 360 : 0} 0`} - repeat="indefinite"> - </a-animation> + repeat="indefinite" + /> </a-gltf-entity> </a-entity> )); return ( <div className="avatar-selector"> - <span className="avatar-selector__loading"> - <FormattedMessage id="profile.avatar-selector.loading"/> - </span> - <a-scene vr-mode-ui="enabled: false" ref={sce => this.scene = sce}> - <a-assets> - {avatarAssets} - <a-asset-item - id="meeting-space1-mesh" - response-type="arraybuffer" - src={meetingSpace} - ></a-asset-item> - </a-assets> + <span className="avatar-selector__loading"> + <FormattedMessage id="profile.avatar-selector.loading" /> + </span> + <a-scene vr-mode-ui="enabled: false" ref={sce => (this.scene = sce)}> + <a-assets> + {avatarAssets} + <a-asset-item id="meeting-space1-mesh" response-type="arraybuffer" src={meetingSpace} /> + </a-assets> - <a-entity> - <a-animation - ref={anm => this.animation = anm} - attribute="rotation" - dur="1000" - easing="ease-out" - to={`0 ${360 * this.getAvatarIndex() / this.props.avatars.length + 180} 0`}> - </a-animation> - {avatarEntities} - </a-entity> + <a-entity> + <a-animation + ref={anm => (this.animation = anm)} + attribute="rotation" + dur="1000" + easing="ease-out" + to={`0 ${360 * this.getAvatarIndex() / this.props.avatars.length + 180} 0`} + /> + {avatarEntities} + </a-entity> - <a-entity position="0 1.5 -5.6" rotation="-10 180 0" camera></a-entity> + <a-entity position="0 1.5 -5.6" rotation="-10 180 0" camera /> - <a-entity - hide-when-quality="low" - light="type: directional; color: #F9FFCE; intensity: 0.6" - position="0 5 -15" - ></a-entity> - <a-entity - hide-when-quality="low" - light="type: ambient; color: #FFF" - ></a-entity> - <a-gltf-entity - id="meeting-space" - src="#meeting-space1-mesh" - position="0 0 0" - ></a-gltf-entity> - </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> + <a-entity + hide-when-quality="low" + light="type: directional; color: #F9FFCE; intensity: 0.6" + position="0 5 -15" + /> + <a-entity hide-when-quality="low" light="type: ambient; color: #FFF" /> + <a-gltf-entity id="meeting-space" src="#meeting-space1-mesh" position="0 0 0" /> + </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> </div> ); } diff --git a/src/react-components/ui-root.js b/src/react-components/ui-root.js index 7cca41e23076270371bb33ed3ce37d7b08fa5a38..5c8918cd5646b779efd79295a42e0b38370c973a 100644 --- a/src/react-components/ui-root.js +++ b/src/react-components/ui-root.js @@ -94,7 +94,7 @@ class UIRoot extends Component { showProfileEntry: false, janusRoomId: null - } + }; componentDidMount() { this.setupTestTone(); @@ -213,18 +213,17 @@ class UIRoot extends Component { hasGrantedMicPermissions = async () => { if (this.state.requestedScreen) { - // There is no way to tell if you've granted mic permissions in a previous session if we've + // There is no way to tell if you've granted mic permissions in a previous session if we've // already prompted for screen sharing permissions, so we have to assume that we've never granted permissions. - // Fortunately, if you *have* granted permissions permanently, there won't be a second browser prompt, but we + // Fortunately, if you *have* granted permissions permanently, there won't be a second browser prompt, but we // can't determine that before hand. // See https://bugzilla.mozilla.org/show_bug.cgi?id=1449783 for a potential solution in the future. return false; - } - else { + } else { // If we haven't requested the screen in this session, check if we've granted permissions in a previous session. return (await grantedMicLabels()).length > 0; } - } + }; performDirectEntryFlow = async enterInVR => { this.startTestTone(); @@ -289,35 +288,36 @@ class UIRoot extends Component { const constraints = { audio: { deviceId: { exact: [ev.target.value] } } }; await this.fetchAudioTrack(constraints); await this.setupNewMediaStream(); - } + }; setMediaStreamToDefault = async () => { await this.fetchAudioTrack({ audio: true }); await this.setupNewMediaStream(); - } + }; setStateAndRequestScreen = async e => { const checked = e.target.checked; await this.setState({ requestedScreen: true, shareScreen: checked }); if (checked) { - this.fetchVideoTrack({ video: { - mediaSource: "screen", - // Work around BMO 1449832 by calculating the width. This will break for multi monitors if you share anything - // other than your current monitor that has a different aspect ratio. - width: screen.width / screen.height * 720, - height: 720, - frameRate: 30 - } }); - } - else { + this.fetchVideoTrack({ + video: { + mediaSource: "screen", + // Work around BMO 1449832 by calculating the width. This will break for multi monitors if you share anything + // other than your current monitor that has a different aspect ratio. + width: screen.width / screen.height * 720, + height: 720, + frameRate: 30 + } + }); + } else { this.setState({ videoTrack: null }); } - } + }; fetchVideoTrack = async constraints => { const mediaStream = await navigator.mediaDevices.getUserMedia(constraints); this.setState({ videoTrack: mediaStream.getVideoTracks()[0] }); - } + }; fetchAudioTrack = async constraints => { if (this.state.audioTrack) { @@ -325,12 +325,12 @@ class UIRoot extends Component { } const mediaStream = await navigator.mediaDevices.getUserMedia(constraints); this.setState({ audioTrack: mediaStream.getAudioTracks()[0] }); - } + }; setupNewMediaStream = async constraints => { const mediaStream = new MediaStream(); - // we should definitely have an audioTrack at this point. + // we should definitely have an audioTrack at this point. mediaStream.addTrack(this.state.audioTrack); if (this.state.videoTrack) { @@ -383,10 +383,8 @@ class UIRoot extends Component { fetchMicDevices = async () => { const mediaDevices = await navigator.mediaDevices.enumerateDevices(); - this.setState({ - micDevices: mediaDevices. - filter(d => d.kind === "audioinput"). - map(d => ({ deviceId: d.deviceId, label: d.label })) + this.setState({ + micDevices: mediaDevices.filter(d => d.kind === "audioinput").map(d => ({ deviceId: d.deviceId, label: d.label })) }); }; @@ -471,45 +469,44 @@ class UIRoot extends Component { // Only show this in desktop firefox since other browsers/platforms will ignore the "screen" media constraint and // will attempt to share your webcam instead! - const screenSharingCheckbox = ( - this.props.enableScreenSharing && - !mobiledetect.mobile() && - /firefox/i.test(navigator.userAgent) && - ( + const screenSharingCheckbox = this.props.enableScreenSharing && + !mobiledetect.mobile() && + /firefox/i.test(navigator.userAgent) && ( <label className="entry-panel__screen-sharing"> - <input className="entry-panel__screen-sharing-checkbox" type="checkbox" + <input + className="entry-panel__screen-sharing-checkbox" + type="checkbox" value={this.state.shareScreen} onChange={this.setStateAndRequestScreen} /> <FormattedMessage id="entry.enable-screen-sharing" /> </label> - ) - ); + ); - const entryPanel = + const entryPanel = this.state.entryStep === ENTRY_STEPS.start ? ( <div className="entry-panel"> <TwoDEntryButton onClick={this.enter2D} /> - { this.state.availableVREntryTypes.generic !== VR_DEVICE_AVAILABILITY.no && ( - <GenericEntryButton onClick={this.enterVR} /> + {this.state.availableVREntryTypes.generic !== VR_DEVICE_AVAILABILITY.no && ( + <GenericEntryButton onClick={this.enterVR} /> )} - { this.state.availableVREntryTypes.gearvr !== VR_DEVICE_AVAILABILITY.no && ( - <GearVREntryButton onClick={this.enterGearVR} /> + {this.state.availableVREntryTypes.gearvr !== VR_DEVICE_AVAILABILITY.no && ( + <GearVREntryButton onClick={this.enterGearVR} /> )} - { this.state.availableVREntryTypes.daydream !== VR_DEVICE_AVAILABILITY.no && ( + {this.state.availableVREntryTypes.daydream !== VR_DEVICE_AVAILABILITY.no && ( <DaydreamEntryButton onClick={this.enterDaydream} subtitle={ - this.state.availableVREntryTypes.daydream == VR_DEVICE_AVAILABILITY.maybe ? daydreamMaybeSubtitle : "" + this.state.availableVREntryTypes.daydream == VR_DEVICE_AVAILABILITY.maybe ? daydreamMaybeSubtitle : "" } - /> + /> )} {this.state.availableVREntryTypes.cardboard !== VR_DEVICE_AVAILABILITY.no && ( <div className="entry-panel__secondary" onClick={this.enterVR}> <FormattedMessage id="entry.cardboard" /> </div> )} - { screenSharingCheckbox } + {screenSharingCheckbox} </div> ) : null;