diff --git a/src/react-components/ui-root.js b/src/react-components/ui-root.js index cc31044b9ec0079e6bda336ff631c4d3f023994d..53f4455185bdd0662172b6ba7a26daeb0be86861 100644 --- a/src/react-components/ui-root.js +++ b/src/react-components/ui-root.js @@ -2,8 +2,9 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; import { VR_DEVICE_AVAILABILITY } from "../utils/vr-caps-detect.js"; import queryString from "query-string"; - +import { SCHEMA } from "../storage/store"; const { detect } = require("detect-browser"); + const browser = detect(); const ENTRY_STEPS = { @@ -48,34 +49,12 @@ const DaydreamEntryButton = (props) => ( </button> ); -class ProfileEntryPanel extends Component { - static propTypes = { - store: PropTypes.object - }; - - state = { - name: "", - } - - nameChanged = (ev) => { - this.setState({ name: ev.target.value }); - } - - render() { - return ( - <div> - <input type="text" value={this.state.name} onChange={this.nameChanged}/> - </div> - ); - } -} - const AutoExitWarning = (props) => ( <div> <p> Exit in <span>{props.secondsRemaining}</span> </p> - + <button onClick={props.onCancel}> Cancel </button> @@ -98,11 +77,11 @@ class UIRoot extends Component { static propTypes = { enterScene: PropTypes.func, availableVREntryTypes: PropTypes.object, - store: PropTypes.object, concurrentLoadDetector: PropTypes.object, disableAutoExitOnConcurrentLoad: PropTypes.bool, - forcedVREntryType: PropTypes.string - }; + forcedVREntryType: PropTypes.string, + store: PropTypes.object, + } state = { entryStep: ENTRY_STEPS.start, @@ -143,7 +122,7 @@ class UIRoot extends Component { } } - setupTestTone = () => { + setupTestTone = () => { const toneClip = document.querySelector("#test-tone"); const toneLength = 1800; const toneDelay = 5000; @@ -354,20 +333,24 @@ class UIRoot extends Component { } } - this.props.enterScene(mediaStream); this.stopTestTone(); + this.props.enterScene(this.state.mediaStream); this.setState({ entryStep: ENTRY_STEPS.finished }); } + saveName = (e) => { + e.preventDefault(); + this.props.store.update({ profile: { display_name: this.nameInput.value } }); + } + render() { - const entryPanel = this.state.entryStep === ENTRY_STEPS.start + const entryPanel = this.state.entryStep === ENTRY_STEPS.start ? ( <div> <TwoDEntryButton onClick={this.enter2D}/> { this.props.availableVREntryTypes.generic !== VR_DEVICE_AVAILABILITY.no && <GenericEntryButton onClick={this.enterVR}/> } { this.props.availableVREntryTypes.gearvr !== VR_DEVICE_AVAILABILITY.no && <GearVREntryButton onClick={this.enterGearVR}/> } { this.props.availableVREntryTypes.daydream !== VR_DEVICE_AVAILABILITY.no && <DaydreamEntryButton onClick={this.enterDaydream}/> } - <ProfileEntryPanel profileName={ this.state.profileNamePending }/> </div> ) : null; @@ -401,12 +384,31 @@ class UIRoot extends Component { </div> ) : null; + const nameEntryPanel = ( + <div> + Name Entry + <form onSubmit={this.saveName}> + <label>Name: + <input + defaultValue={this.props.store.state.profile.display_name} + required pattern={SCHEMA.definitions.profile.properties.display_name.pattern} + title="Alphanumerics and hyphens. At least 3 characters, no more than 32" + ref={inp => this.nameInput = inp}/> + </label> + <input type="submit" value="Save" /> + </form> + </div> + ); + const overlay = this.isWaitingForAutoExit() ? (<AutoExitWarning secondsRemaining={this.state.secondsRemainingBeforeAutoExit} onCancel={this.endAutoExitTimer} />) : - (<div> - {entryPanel} - {micPanel} - {audioSetupPanel} + ( + <div> + {entryPanel} + {micPanel} + {audioSetupPanel} + + {nameEntryPanel} </div> ); diff --git a/src/room.css b/src/room.css index b74a6c28f48547c09097cfc5b5d9b96119bb1ab9..d98df797ef70bcb1d09f98d5447d7196d4dfedd4 100644 --- a/src/room.css +++ b/src/room.css @@ -19,3 +19,8 @@ left: 0; position: absolute; } + +.rs-base { + top: auto; + bottom: 20px; +} diff --git a/src/room.js b/src/room.js index 97e5311aaf4adbb50df42f2d2c3412f44ebc7afd..3e0ef36c6ac18e18a776f3bbc23be977fb9cdd57 100644 --- a/src/room.js +++ b/src/room.js @@ -121,6 +121,11 @@ async function exitScene() { document.body.removeChild(scene); } +function setNameTagFromStore() { + const myNametag = document.querySelector("#player-rig .nametag"); + myNametag.setAttribute("text", "value", store.state.profile.display_name); +} + async function enterScene(mediaStream) { const qs = queryString.parse(location.search); const scene = document.querySelector("a-scene"); @@ -139,8 +144,8 @@ async function enterScene(mediaStream) { playerRig.setAttribute("virtual-gamepad-controls", {}); } - const myNametag = document.querySelector("#player-rig .nametag"); - myNametag.setAttribute("text", "value", store.state.profile.display_name); + setNameTagFromStore(); + store.subscribe(setNameTagFromStore); const avatarScale = parseInt(qs.avatarScale, 10); @@ -208,9 +213,9 @@ function mountUI() { exitScene, concurrentLoadDetector, disableAutoExitOnConcurrentLoad, - forcedVREntryType + forcedVREntryType, + store }} />, document.getElementById("ui-root")); - document.getElementById("loader").style.display = "none"; }); } diff --git a/src/storage/store.js b/src/storage/store.js index 34caa9edb55add11d60f6a739dc45f551bfbc39b..b1b362d534dfb5db7f0bf0483a6cd7581346b529 100644 --- a/src/storage/store.js +++ b/src/storage/store.js @@ -7,7 +7,7 @@ const validator = new Validator(); // Durable (via local-storage) schema-enforced state that is meant to be consumed via forward data flow. // (Think flux but with way less incidental complexity, at least for now :)) -const SCHEMA = { +export const SCHEMA = { id: "/MozillaDuckStore", definitions: {