From ced6d963de1426befbe67906d77bda90c1de6e4b Mon Sep 17 00:00:00 2001
From: Brian Peiris <brianpeiris@gmail.com>
Date: Mon, 26 Mar 2018 17:38:45 -0700
Subject: [PATCH] i18n loading message

---
 src/assets/stylesheets/avatar-selector.scss |  9 +++++++
 src/assets/stylesheets/profile.scss         |  1 +
 src/assets/translations.data.json           |  1 +
 src/avatar-selector.js                      | 28 ++++++++++++++-------
 src/react-components/avatar-selector.js     | 28 +++++++++++++--------
 src/react-components/profile-entry-panel.js | 25 ++++++++++--------
 6 files changed, 61 insertions(+), 31 deletions(-)

diff --git a/src/assets/stylesheets/avatar-selector.scss b/src/assets/stylesheets/avatar-selector.scss
index 319d6fb08..67856ca8e 100644
--- a/src/assets/stylesheets/avatar-selector.scss
+++ b/src/assets/stylesheets/avatar-selector.scss
@@ -1,3 +1,6 @@
+@import 'fonts';
+@import 'shared';
+
 #selector-root {
   height: 100%;
 }
@@ -30,4 +33,10 @@
   &__button-icon {
     font-size: 84pt;
   }
+  &__loading {
+    @extend %default-font;
+    display: block;
+    text-align: center;
+    color: white;
+  }
 }
diff --git a/src/assets/stylesheets/profile.scss b/src/assets/stylesheets/profile.scss
index db9326a45..9b45afd38 100644
--- a/src/assets/stylesheets/profile.scss
+++ b/src/assets/stylesheets/profile.scss
@@ -27,6 +27,7 @@
   flex: 1 1 100%;
   width: 60vw;
   min-width: 300px;
+  max-width: 700px;
   height: 500px
 }
 
diff --git a/src/assets/translations.data.json b/src/assets/translations.data.json
index e10a95ae4..e45a644cc 100644
--- a/src/assets/translations.data.json
+++ b/src/assets/translations.data.json
@@ -15,6 +15,7 @@
     "profile.save": "SAVE",
     "profile.display_name.validation_warning": "Alphanumerics and hyphens. At least 3 characters, no more than 32",
     "profile.header": "Your identity",
+    "profile.avatar-selector.loading": "Loading avatar selector...",
     "audio.title": "Test your audio",
     "audio.subtitle-desktop": "Confirm HMD speaker output",
     "audio.subtitle-mobile": "Earphones are recommended",
diff --git a/src/avatar-selector.js b/src/avatar-selector.js
index 55ac8df6f..d2e593596 100644
--- a/src/avatar-selector.js
+++ b/src/avatar-selector.js
@@ -1,6 +1,8 @@
 import ReactDOM from "react-dom";
 import React from "react";
 import queryString from "query-string";
+import { IntlProvider, FormattedMessage, addLocaleData } from "react-intl";
+import en from "react-intl/locale-data/en";
 
 import "./assets/stylesheets/avatar-selector.scss";
 import "./vendor/GLTFLoader";
@@ -15,6 +17,7 @@ import { avatarIds } from "./utils/identity";
 
 import { App } from "./App";
 import AvatarSelector from "./react-components/avatar-selector";
+import localeData from "./assets/translations.data.json";
 
 window.APP = new App();
 const hash = queryString.parse(location.hash);
@@ -25,21 +28,28 @@ if (hash.quality) {
   window.APP.quality = isMobile ? "low" : "high";
 }
 
-const avatar = hash.avatar;
+const lang = ((navigator.languages && navigator.languages[0]) || navigator.language || navigator.userLanguage)
+  .toLowerCase()
+  .split(/[_-]+/)[0];
+addLocaleData([...en]);
+const messages = localeData[lang] || localeData.en;
+
+let avatar = hash.avatar;
 
 function postAvatarToParent(newAvatar) {
-  window.parent.postMessage({avatar: newAvatar}, location.origin);
+  window.parent.postMessage({ avatar: newAvatar }, location.origin);
 }
 
 function mountUI() {
-  const selector = ReactDOM.render(
-    <AvatarSelector {...{ avatars, avatar, onChange: postAvatarToParent }} />,
+  const hash = queryString.parse(location.hash);
+  const avatar = hash.avatar;
+  ReactDOM.render(
+    <IntlProvider locale={lang} messages={messages}>
+      <AvatarSelector {...{ avatars, avatar, onChange: postAvatarToParent }} />
+    </IntlProvider>,
     document.getElementById("selector-root")
   );
-
-  window.addEventListener('hashchange', () => {
-    const hash = queryString.parse(location.hash);
-    selector.setState({avatar: hash.avatar});
-  });
 }
+
+window.addEventListener("hashchange", mountUI);
 document.addEventListener("DOMContentLoaded", mountUI);
diff --git a/src/react-components/avatar-selector.js b/src/react-components/avatar-selector.js
index 6b579c40b..6609af853 100644
--- a/src/react-components/avatar-selector.js
+++ b/src/react-components/avatar-selector.js
@@ -1,5 +1,6 @@
 import React, { Component } from 'react';
 import PropTypes from 'prop-types';
+import { injectIntl, FormattedMessage } from 'react-intl';
 
 class AvatarSelector extends Component {
   static propTypes = {
@@ -8,13 +9,8 @@ class AvatarSelector extends Component {
     onChange: PropTypes.func,
   }
 
-  constructor(props) {
-    super(props);
-    this.state = { avatar: this.props.avatar };
-  }
-
   getAvatarIndex(direction=0) {
-    const currAvatarIndex = this.props.avatars.findIndex(avatar => avatar.id === this.state.avatar);
+    const currAvatarIndex = this.props.avatars.findIndex(avatar => avatar.id === this.props.avatar);
     const numAvatars = this.props.avatars.length;
     return ((currAvatarIndex + direction) % numAvatars + numAvatars) % numAvatars;
   }
@@ -29,6 +25,13 @@ class AvatarSelector extends Component {
     this.props.onChange(this.props.avatars[newAvatarIndex].id);
   }
 
+  componentDidMount() {
+    const start = performance.now();
+    this.scene.addEventListener('loaded', () => {
+      this.loading.style.display = 'none';
+    });
+  }
+
   render () {
     const avatarAssets = this.props.avatars.map(avatar => (
       <a-progressive-asset
@@ -53,7 +56,10 @@ class AvatarSelector extends Component {
 
     return (
       <div className="avatar-selector">
-      <a-scene vr-mode-ui="enabled: false" debug>
+      <span className="avatar-selector__loading" ref={ldg => this.loading = ldg}>
+        <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
@@ -63,7 +69,7 @@ class AvatarSelector extends Component {
           ></a-asset-item>
         </a-assets>
 
-        <a-entity rotation={`0 ${360 * this.getAvatarIndex() / this.props.avatars.length - 180} 0`}>
+        <a-entity rotation={`0 ${360 * -this.getAvatarIndex() / this.props.avatars.length + 180} 0`}>
         {avatarEntities}
         </a-entity>
 
@@ -84,10 +90,10 @@ class AvatarSelector extends Component {
           position="0 0 0"
         ></a-gltf-entity>
       </a-scene>
-      <button className="avatar-selector__prev-button" onClick={this.nextAvatar}>
+      <button className="avatar-selector__prev-button" onClick={this.prevAvatar}>
         <i className="avatar-selector__button-icon material-icons">keyboard_arrow_left</i>
       </button>
-      <button className="avatar-selector__next-button" onClick={this.prevAvatar}>
+      <button className="avatar-selector__next-button" onClick={this.nextAvatar}>
         <i className="avatar-selector__button-icon material-icons">keyboard_arrow_right</i>
       </button>
       </div>
@@ -95,4 +101,4 @@ class AvatarSelector extends Component {
   }
 }
 
-export default AvatarSelector;
+export default injectIntl(AvatarSelector);
diff --git a/src/react-components/profile-entry-panel.js b/src/react-components/profile-entry-panel.js
index 457211386..dd7899ea6 100644
--- a/src/react-components/profile-entry-panel.js
+++ b/src/react-components/profile-entry-panel.js
@@ -13,7 +13,7 @@ class ProfileEntryPanel extends Component {
   constructor(props) {
     super(props);
     window.store = this.props.store;
-    this.state = { 
+    this.state = {
       display_name: this.props.store.state.profile.display_name,
       avatar: this.props.store.state.profile.avatar,
     };
@@ -21,7 +21,7 @@ class ProfileEntryPanel extends Component {
   }
 
   storeUpdated = () => {
-    this.setState({ 
+    this.setState({
       display_name: this.props.store.state.profile.display_name,
       avatar: this.props.store.state.profile.avatar,
     });
@@ -29,7 +29,7 @@ class ProfileEntryPanel extends Component {
 
   saveStateAndFinish = (e) => {
     e.preventDefault();
-    this.props.store.update({profile: { 
+    this.props.store.update({profile: {
       display_name: this.state.display_name,
       avatar: this.state.avatar
     }});
@@ -40,22 +40,25 @@ class ProfileEntryPanel extends Component {
     e.stopPropagation();
   }
 
+  setAvatarStateFromIframeMessage = (e) => {
+    if (e.source !== this.avatarSelector.contentWindow) { return; }
+    this.setState({avatar: e.data.avatar});
+  }
+
   componentDidMount() {
     // stop propagation so that avatar doesn't move when wasd'ing during text input.
     this.nameInput.addEventListener('keydown', this.stopPropagation);
     this.nameInput.addEventListener('keypress', this.stopPropagation);
     this.nameInput.addEventListener('keyup', this.stopPropagation);
-    window.addEventListener('message', (e) => {
-      if (e.source !== this.avatarSelector.contentWindow) { return; }
-      this.setState({avatar: e.data.avatar});
-    });
+    window.addEventListener('message', this.setAvatarStateFromIframeMessage);
   }
-  
+
   componentWillUnmount() {
     this.props.store.removeEventListener('statechanged', this.storeUpdated);
     this.nameInput.removeEventListener('keydown', this.stopPropagation);
     this.nameInput.removeEventListener('keypress', this.stopPropagation);
     this.nameInput.removeEventListener('keyup', this.stopPropagation);
+    window.removeEventListener('message', this.setAvatarStateFromIframeMessage);
   }
 
   render () {
@@ -74,10 +77,10 @@ class ProfileEntryPanel extends Component {
             required pattern={SCHEMA.definitions.profile.properties.display_name.pattern}
             title={formatMessage({ id: "profile.display_name.validation_warning" })}
             ref={inp => this.nameInput = inp}/>
-          <iframe 
-            className="profile-entry__avatar-selector" 
+          <iframe
+            className="profile-entry__avatar-selector"
             src={`avatar-selector.html#avatar=${this.state.avatar}`}
-            ref={ifr => this.avatarSelector = ifr}>loading...</iframe>
+            ref={ifr => this.avatarSelector = ifr}></iframe>
           <input className="profile-entry__form-submit" type="submit" value={formatMessage({ id: "profile.save" }) }/>
         </div>
         </form>
-- 
GitLab