diff --git a/src/react-components/ui-root.js b/src/react-components/ui-root.js index 81da1aa0d3c34e8c36815411cf3ca807ac558236..08d076d04cd53cbc4775d870dfc134ae0b5ab019 100644 --- a/src/react-components/ui-root.js +++ b/src/react-components/ui-root.js @@ -1,6 +1,7 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; import { VR_DEVICE_AVAILABILITY } from "../utils/vr-caps-detect.js"; +import { SCHEMA } from "../storage/store"; const ENTRY_STEPS = { start: "start", @@ -44,34 +45,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> @@ -94,10 +73,10 @@ class UIRoot extends Component { static propTypes = { enterScene: PropTypes.func, availableVREntryTypes: PropTypes.object, - store: PropTypes.object, concurrentLoadDetector: PropTypes.object, - disableAutoExitOnConcurrentLoad: PropTypes.bool - }; + disableAutoExitOnConcurrentLoad: PropTypes.bool, + store: PropTypes.object, + } state = { entryStep: ENTRY_STEPS.start, @@ -127,7 +106,7 @@ class UIRoot extends Component { this.props.concurrentLoadDetector.addEventListener("concurrentload", this.onConcurrentLoad); } - setupTestTone = () => { + setupTestTone = () => { const toneClip = document.querySelector("#test-tone"); const toneLength = 1800; const toneDelay = 5000; @@ -317,20 +296,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; @@ -364,12 +347,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 c302143ff51078945e432d09cb6a2a648c2f27ab..bfb62d17040a6ee92c4fade4503a7dde19ac77db 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); @@ -202,9 +207,9 @@ function mountUI() { enterScene, exitScene, concurrentLoadDetector, - disableAutoExitOnConcurrentLoad + disableAutoExitOnConcurrentLoad, + 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: {