From efc9f2c65454578d7321be285d4e4ca8276b1500 Mon Sep 17 00:00:00 2001
From: Brian Peiris <brianpeiris@gmail.com>
Date: Tue, 24 Apr 2018 21:31:55 -0700
Subject: [PATCH] lazy load avatars

---
 src/react-components/avatar-selector.js | 75 +++++++++++++++++++------
 1 file changed, 57 insertions(+), 18 deletions(-)

diff --git a/src/react-components/avatar-selector.js b/src/react-components/avatar-selector.js
index 266278013..28230ca14 100644
--- a/src/react-components/avatar-selector.js
+++ b/src/react-components/avatar-selector.js
@@ -15,11 +15,20 @@ class AvatarSelector extends Component {
     onChange: PropTypes.func
   };
 
-  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;
+  static getAvatarIndex = (props, offset = 0) => {
+    const currAvatarIndex = props.avatars.findIndex(avatar => avatar.id === props.avatarId);
+    const numAvatars = props.avatars.length;
+    return ((currAvatarIndex + offset) % numAvatars + numAvatars) % numAvatars;
   };
+  static nextAvatarIndex = props => AvatarSelector.getAvatarIndex(props, -1);
+  static previousAvatarIndex = props => AvatarSelector.getAvatarIndex(props, 1);
+
+  state = {
+    initialAvatarIndex: 0,
+    avatarIndices: []
+  };
+
+  getAvatarIndex = (offset = 0) => AvatarSelector.getAvatarIndex(this.props, offset);
   nextAvatarIndex = () => this.getAvatarIndex(-1);
   previousAvatarIndex = () => this.getAvatarIndex(1);
 
@@ -33,6 +42,37 @@ class AvatarSelector extends Component {
     this.props.onChange(previousAvatarId);
   };
 
+  constructor(props) {
+    super(props);
+    this.state.initialAvatarIndex = AvatarSelector.getAvatarIndex(props);
+    this.state.avatarIndices = [
+      AvatarSelector.nextAvatarIndex(props),
+      this.state.initialAvatarIndex,
+      AvatarSelector.previousAvatarIndex(props)
+    ];
+  }
+
+  componentWillReceiveProps(nextProps) {
+    // Push new avatar indices onto the array if necessary.
+    this.setState(state => {
+      if (this.state.avatarIndices.length === nextProps.avatars.length) return;
+      const nextAvatarIndex = AvatarSelector.getAvatarIndex(nextProps);
+      if (
+        nextAvatarIndex === nextProps.avatars.length - 1 ||
+        nextAvatarIndex < AvatarSelector.getAvatarIndex(this.props)
+      ) {
+        const addIndex = AvatarSelector.nextAvatarIndex(nextProps);
+        if (state.avatarIndices.includes(addIndex)) return;
+        state.avatarIndices.unshift(addIndex);
+      } else {
+        const addIndex = AvatarSelector.previousAvatarIndex(nextProps);
+        if (state.avatarIndices.includes(addIndex)) return;
+        state.avatarIndices.push(addIndex);
+      }
+      return state;
+    });
+  }
+
   componentDidUpdate(prevProps) {
     if (this.props.avatarId !== prevProps.avatarId) {
       // HACK - a-animation ought to restart the animation when the `to` attribute changes, but it doesn't
@@ -59,10 +99,10 @@ class AvatarSelector extends Component {
     const avatarAssets = this.props.avatars.map(avatar => (
       <a-asset-item id={avatar.id} key={avatar.id} response-type="arraybuffer" src={`${avatar.model}`} />
     ));
-
-    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-entity position="0 0 5" rotation="0 0 0" gltf-model-plus={`src: #${avatar.id}`} inflate="true">
+    const avatarData = this.state.avatarIndices.map(i => [this.props.avatars[i], i]);
+    const avatarEntities = avatarData.map(([avatar, i]) => (
+      <a-entity key={avatar.id} rotation={`0 ${360 * -i / this.props.avatars.length} 0`}>
+        <a-entity position="0 0 5" gltf-model-plus={`src: #${avatar.id}`} inflate="true">
           <template data-selector=".RootScene">
             <a-entity animation-mixer />
           </template>
@@ -77,33 +117,32 @@ class AvatarSelector extends Component {
       </a-entity>
     ));
 
+    const rotationFromIndex = index => (360 * index / this.props.avatars.length + 180) % 360;
+    const initialRotation = rotationFromIndex(this.state.initialAvatarIndex);
+    const toRotation = rotationFromIndex(this.getAvatarIndex());
+
     return (
       <div className="avatar-selector">
-        <div className="loading-panel">
-          <div className="loader-wrap">
-            <div className="loader">
-              <div className="loader-center" />
-            </div>
-          </div>
-        </div>
         <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-entity rotation={`0 ${initialRotation} 0`}>
             <a-animation
               ref={anm => (this.animation = anm)}
               attribute="rotation"
               dur="2000"
               easing="ease-out"
-              to={`0 ${(360 * this.getAvatarIndex() / this.props.avatars.length + 180) % 360} 0`}
+              to={`0 ${toRotation} 0`}
             />
             {avatarEntities}
           </a-entity>
 
-          <a-entity position="0 1.5 -5.6" rotation="-10 180 0" camera />
+          <a-entity position="0 1.5 -5.6" rotation="-10 180 0">
+            <a-entity camera />
+          </a-entity>
 
           <a-entity
             hide-when-quality="low"
-- 
GitLab