From 5542becc448f2cb6a6bc5680d7084e2225e34b48 Mon Sep 17 00:00:00 2001
From: Paul Kaplan <pkaplan@media.mit.edu>
Date: Fri, 15 Dec 2017 15:26:12 -0500
Subject: [PATCH] Add fetching and loading states to display loading screen.

---
 src/components/gui/gui.jsx             |   7 +-
 src/components/loader/bottom-block.svg | Bin 0 -> 2644 bytes
 src/components/loader/loader.css       |  89 +++++++++++++++
 src/components/loader/loader.jsx       | 144 +++++++++++++++++++++++++
 src/components/loader/middle-block.svg | Bin 0 -> 2644 bytes
 src/components/loader/top-block.svg    | Bin 0 -> 2628 bytes
 src/containers/gui.jsx                 |  24 ++++-
 src/lib/project-loader-hoc.jsx         |  23 ++--
 8 files changed, 273 insertions(+), 14 deletions(-)
 create mode 100644 src/components/loader/bottom-block.svg
 create mode 100644 src/components/loader/loader.css
 create mode 100644 src/components/loader/loader.jsx
 create mode 100644 src/components/loader/middle-block.svg
 create mode 100644 src/components/loader/top-block.svg

diff --git a/src/components/gui/gui.jsx b/src/components/gui/gui.jsx
index 5c7a378dc..b7f2a0acd 100644
--- a/src/components/gui/gui.jsx
+++ b/src/components/gui/gui.jsx
@@ -14,7 +14,7 @@ import TargetPane from '../../containers/target-pane.jsx';
 import SoundTab from '../../containers/sound-tab.jsx';
 import StageHeader from '../../containers/stage-header.jsx';
 import Stage from '../../containers/stage.jsx';
-
+import Loader from '../loader/loader.jsx';
 import Box from '../box/box.jsx';
 import FeedbackForm from '../feedback-form/feedback-form.jsx';
 import MenuBar from '../menu-bar/menu-bar.jsx';
@@ -44,6 +44,7 @@ const GUIComponent = props => {
         feedbackFormVisible,
         importInfoVisible,
         intl,
+        loading,
         onExtensionButtonClick,
         onActivateTab,
         previewInfoVisible,
@@ -78,6 +79,9 @@ const GUIComponent = props => {
             {previewInfoVisible ? (
                 <PreviewModal />
             ) : null}
+            {loading ? (
+                <Loader />
+            ) : null}
             {importInfoVisible ? (
                 <ImportModal />
             ) : null}
@@ -176,6 +180,7 @@ GUIComponent.propTypes = {
     feedbackFormVisible: PropTypes.bool,
     importInfoVisible: PropTypes.bool,
     intl: intlShape.isRequired,
+    loading: PropTypes.bool,
     onActivateTab: PropTypes.func,
     onExtensionButtonClick: PropTypes.func,
     onTabSelect: PropTypes.func,
diff --git a/src/components/loader/bottom-block.svg b/src/components/loader/bottom-block.svg
new file mode 100644
index 0000000000000000000000000000000000000000..b397068c448eeabc04d4245e1f5cdd210b72df5c
GIT binary patch
literal 2644
zcmZ`*$&TAb5WV{=I`qW`hOOS0fg~d#;9QuKlZ$T}i6aRkkpqbyP5wTw7m97b(W&dP
zx~gk?rRA6R^NGB5m+NupR~4tMB3-{7cE^6ds{Z-=Ywap>9XI`Ma~k??RrN#lW&L4!
zecO}UaW@`TmD8{96*+Xr{b5{HGD>g9?)Jy<zN#2u#AzaRRXX7nf!qGNst)7$`nXu!
zZZ~?9bhzvnf-$y$`K<EzemeFqzf$Dh`$ha(tv?X_mcP|C`LpZ0%Vr!dkL24+H*OC^
zIm*c=Wrb#+NlglmPs5=*A2&H%x*g7oYr0d{+2nW}Pu==y7{}pUKb?l{%W{#h<Z{<t
zx9jH8ZN_eggp7yml`a?Y)1>u$O$OsTUpLU(t*XD+gE3SYW~4Htfut#C+z2ZOr~JVv
zBB;_f99l<RL>iA9B0!u`=E6rRw3klufN13cVg+YLbJB7}nSi>ZNUUIrlLnA-GC;;~
z=M3%zzqse97hD(*_nKN$0<Dl#>u6}wX#_koRcZlo!k4CnrU{tbx5B|RZq1Kugr`F2
zG7=yz07N1r7Q=0wpfX-0jguk5@ZANEIL(=xkU6t$hCJHa81I;ag>=s&!N{I-f@GT>
zaSCg=hVA6AK-&DA0t%1EQwt<V1H?MxJ=#Bq?2#|!jANR#DKFk44(qtrBxEz>&ZAwO
z*No#@$}bS5oO9@rT!aE?Q>6-|$<-{NTyY?i>D(5D|28*I@yI53SP@BTz9QJzmWAA=
zjxCTTv~nS{ha%|^=AyJe&BOPK95EzeSZZFamy2-9V~OFKCTh8lYVEKIoK=4tF^Yk*
zF2hTttNt$4nXte<&&Sg#a9Q{m&h_QT3EJIT*AKg0MaIjfzdjF_^Qwx+6O4YUweuA0
zgx>?Hg*I9EocSltc6!b4{qejRL8<>^s<gzBj%2koR2VDSr*IWC!f*NG+NB_d*Uk1g
z{tMQnVv6zO*ZBK&n2|jn50~zFRehWj4TCdVB`0tbSu5qWE<7QdQ?l>AveumMNOlwh
zT?{1#yDWYaWkI=SjB{`jmDWgRy$Q|3@<`shYXTB478>A3?UiL1;_@O|SpsEJ1XN0I
zb)rzuxZ^51!PrTMaSfy?txmATQNb7{6tGp2GwmM2;3y%Dh~z00bDGJ3`aG$5EN5UM
z&_XASJhPlDkEhO>K(%qsD?D{}1Jq0KwN?S0-W1qWVP`~G(kX%lus6zkgQw2nf|)G0
z0_iDJGjrOU)I0M{P@r6;6m~;-6)Y;jzUbhoY!gyUb%u)C2lfs3z}nJU!;}o{o6Tb}
z=!tscc_Q5?1iJN3#B@ghvT`~W1+vA~!zv*_IR~+#uyDdqHf^yD!MaAt1Uu|AsXW$S
z1DH7<o5SED^ITvdAmdURbP{$YoaD_o?ZFwz6kO<pPC<jkL>DknAw(=n;f71>VHYci
z1w6ee&@uNa!E*%YZYK<SgCnpxT#s~m^pU~tjp<8&7Ldry#}Ne+-)AZUVdRP$L~)+b
X??037od_2DnH`ps5D&}n$8G%|%lKc4

literal 0
HcmV?d00001

diff --git a/src/components/loader/loader.css b/src/components/loader/loader.css
new file mode 100644
index 000000000..68f8e5f4f
--- /dev/null
+++ b/src/components/loader/loader.css
@@ -0,0 +1,89 @@
+@import "../../css/colors.css";
+
+.background {
+    position: fixed;
+    top: 0;
+    left: 0;
+    width: 100%;
+    height: 100%;
+    z-index: 999; /* Below preview modal */
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    background-color: $motion-primary;
+    font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+    text-align: center;
+    color: white;
+}
+
+.block-animation {
+    width: 125px;
+    height: 150px;
+    margin: 50px auto 0px;
+}
+
+.block-animation img {
+    display: block;
+    position: relative;
+    height: 30%;
+    margin-top: -4px;
+}
+
+.topBlock {
+    animation: top-slide-in 1.5s ease infinite;
+}
+
+.middleBlock {
+    animation: middle-slide-in 1.5s ease infinite;
+}
+
+.bottomBlock {
+    animation: bottom-slide-in 1.5s ease infinite;
+}
+
+
+@keyframes top-slide-in {
+  0% {
+    transform: translateY(50px);
+    opacity: 0;
+  }
+
+  33% {
+    transform: translateY(0px);
+    opacity: 1;
+  }
+}
+
+@keyframes middle-slide-in {
+  0% {
+    transform: translateY(50px);
+    opacity: 0;
+  }
+
+  33% {
+    transform: translateY(50px);
+    opacity: 0;
+  }
+
+  66% {
+    transform: translateY(0px);
+    opacity: 1;
+  }
+}
+
+@keyframes bottom-slide-in {
+  0% {
+    transform: translateY(50px);
+    opacity: 0;
+  }
+
+  66% {
+    transform: translateY(50px);
+    opacity: 0;
+  }
+
+  100% {
+    transform: translateY(0px);
+    opacity: 1;
+  }
+}
diff --git a/src/components/loader/loader.jsx b/src/components/loader/loader.jsx
new file mode 100644
index 000000000..8a93e5c36
--- /dev/null
+++ b/src/components/loader/loader.jsx
@@ -0,0 +1,144 @@
+import React from 'react';
+import {FormattedMessage} from 'react-intl';
+import styles from './loader.css';
+
+import topBlock from './top-block.svg';
+import middleBlock from './middle-block.svg';
+import bottomBlock from './bottom-block.svg';
+
+const LoaderComponent = () => {
+    const messages = [
+        {
+            message: (
+                <FormattedMessage
+                    defaultMessage="Creating blocks …"
+                    description="One of the loading messages"
+                    id="gui.loader.message1"
+                />
+            ),
+            weight: 50
+        },
+        {
+            message: (
+                <FormattedMessage
+                    defaultMessage="Loading sprites …"
+                    description="One of the loading messages"
+                    id="gui.loader.message2"
+                />
+            ),
+            weight: 50
+        },
+        {
+            message: (
+                <FormattedMessage
+                    defaultMessage="Loading sounds …"
+                    description="One of the loading messages"
+                    id="gui.loader.message3"
+                />
+            ),
+            weight: 50
+        },
+        {
+            message: (
+                <FormattedMessage
+                    defaultMessage="Loading extensions …"
+                    description="One of the loading messages"
+                    id="gui.loader.message4"
+                />
+            ),
+            weight: 50
+        },
+        {
+            message: (
+                <FormattedMessage
+                    defaultMessage="Creating blocks …"
+                    description="One of the loading messages"
+                    id="gui.loader.message1"
+                />
+            ),
+            weight: 20
+        },
+        {
+            message: (
+                <FormattedMessage
+                    defaultMessage="Herding cats …"
+                    description="One of the loading messages"
+                    id="gui.loader.message5"
+                />
+            ),
+            weight: 1
+        },
+        {
+            message: (
+                <FormattedMessage
+                    defaultMessage="Transmitting nanos …"
+                    description="One of the loading messages"
+                    id="gui.loader.message6"
+                />
+            ),
+            weight: 1
+        },
+        {
+            message: (
+                <FormattedMessage
+                    defaultMessage="Inflating gobos …"
+                    description="One of the loading messages"
+                    id="gui.loader.message7"
+                />
+            ),
+            weight: 1
+        },
+        {
+            message: (
+                <FormattedMessage
+                    defaultMessage="Preparing emojiis …"
+                    description="One of the loading messages"
+                    id="gui.loader.message8"
+                />
+            ),
+            weight: 1
+        }
+    ];
+
+    let message;
+    const sum = messages.reduce((acc, m) => acc + m.weight, 0);
+    let rand = sum * Math.random();
+    for (let i = 0; i < messages.length; i++) {
+        rand -= messages[i].weight;
+        if (rand <= 0) {
+            message = messages[i].message;
+            break;
+        }
+    }
+
+    return (
+        <div className={styles.background}>
+            <div className={styles.container}>
+                <div className={styles.blockAnimation}>
+                    <img
+                        className={styles.topBlock}
+                        src={topBlock}
+                    />
+                    <img
+                        className={styles.middleBlock}
+                        src={middleBlock}
+                    />
+                    <img
+                        className={styles.bottomBlock}
+                        src={bottomBlock}
+                    />
+                </div>
+                <h1 className={styles.title}>
+                    <FormattedMessage
+                        defaultMessage="Loading Project"
+                        description="Main loading message"
+                        id="gui.loader.headline"
+                    />
+                </h1>
+                <p>{message}</p>
+            </div>
+        </div>
+    );
+};
+
+export default LoaderComponent;
diff --git a/src/components/loader/middle-block.svg b/src/components/loader/middle-block.svg
new file mode 100644
index 0000000000000000000000000000000000000000..6e52d4213a9e8bfff2c8a0289fa324cfcbd60c27
GIT binary patch
literal 2644
zcmZ`*%aYnS6y4`56larEVWIb<3rNklD%p5enb~AVzzwbjy9=DA^Y=NDWvo>7u#%3i
z?)#q0>htUQL|&T9we7og!6_?9)9w0w+a1=$fB$|dT|utHw%cz{eb=mut}i}s-mR`L
z2Xbro!*N|0_xxIrW78gv!@7_<++Nz|_ILlfE*N3NI3hK$*$6KP%y!pxaU6!{hvo8i
zyV0AZ{pGL}jIkxuCy|HOQ`<egCCI(^%lNg}yd(In-j^l$+H}ojJM@<a^6jY^c1NNd
z<>aHXLbFe#B$>q@{joW>+Y~O{_2=bvd{dT5<<JhNW^-=$`%_c?IQ6@y)pEo}ll$hn
z+f<ikJ2ZPFWH=_PbhV71CaK5ks4)8ZyoKC;UHroqQc-1?k;;$?H%U3;Mp!{O<qIi@
zpi0|ts2z0?sXShY0C7f{3m>V_UOLGGqLmAX6`UE(NzD~y0^)`uv4Sa1DnQD~02#xb
zGkB+A7w-)9f(zrJTT^Rtpcay99TiP7l~7Qsaw#BA_*}KnbOa{zwQx|4SF@9q@Kgw$
zM*_qJfJlVIVz{-@RK|y-GA2YAz8edRIL(=xkU6t;g0!d181I;ahVh+YR3UrLLDQ(y
zaH@qhTq}|cW=Ngf$)MmoT+NUa4G`;$_fC@(vO%SkGmdFek9mOwE3D&QlaS4jJGjA}
z*No%Ym|q}DIp@$LsR$WTkCn=hDpfOsQpJHxs#9BJ_UqI<nUhuOuq=|X`LbYRTW0rq
z?AQ#cLMvx?w#bss7Fkj1Kjz_kL0Sw+7?zS3o7FO$(pX}+rioe|qFCK;180@r28?2$
ztV{nC>8iWm%0yUTpU3ue3S1UGhI4uOd4hEF(sccPUy$Ll?XHjg<-9K9@dTwGOYP7=
z@wXtQKy}8;r^G*TwnuON>e};m0Hywqs$3KI;tfTavYdS~Q$Zs9RzI)J7)1ZP-L=Cn
zur3u-j32(l-`inE_Ix;An#XnVVNNs@&TKUrftko!V_wV560$iZ`)(^q&H0XGM={XZ
zP-3w2;#c90&fuCc&cR7kS|geDCNvLABYE$x2^e`XlK?+zuPnn5=MT}!5-5`*pi+9P
zjS7V68FySoBN#jBFs^|#rPU)0(y<E4Frk30lALL`2!o@9G$N9xP|Rs21M1VHrm>uX
zi9j=%F!IcDt~^|wC4p+=oL6vlHUrd4@U>O}oqP&xs<1O6%;^+C1=t(qy@9K9xL_vB
ztw4J6)XbcAPin9Ls?F0H=|o{S<S1BFf_<~HB_yBf1ZA}k>>J*JwWYO&DH+&Tsi+={
zK~K~h&lBm!OQ2itL`-)Ca92*pqTp_^^{`3^P|iWDC@hRHlucV~L$Iz<a)b@`nN%L@
zuL8`RkIf-1R-xwt3jrCI(x8*DD=pT<xEZHCI3tOIGnvpSXt0>*3<fHMh(*cFaEU$a
zVg<2)C!YcxbFW5tjsV^5gh6j`1U853kxq|3GT6N#ed*5vPKl2r3MRhKR0P7vWi^P`
Zc|yPcNxF9;SRQ6}SWTCBScN}so8PzXU4;Mu

literal 0
HcmV?d00001

diff --git a/src/components/loader/top-block.svg b/src/components/loader/top-block.svg
new file mode 100644
index 0000000000000000000000000000000000000000..2b52a0d5d09684056643fb04aec7c80967569c95
GIT binary patch
literal 2628
zcmZ`*O^@3)5WVMDu*xL?EScXQ3)?|!Aib!kw3pl*TWbsJl@_+L-v0Z(p(u+Bqyh|+
zPvi{e&CH`#U*4Y2?6tXE+rC>DqT&T>x_y6WyW_g}=kKqjFW7b1b%))#@0xYd^~IOX
zht>7<$ZqXnIIW9dU)~CKYTDyzSQpCB?6qxffAnwbf-}y7Wy%1Xorr?LZFgN4r(t+`
zST1k3TXj=ae>pBC=X?qCY01Oex$T~QwNQi*mho+|`M~h3ek)7%XVW#8-Oyhi*te%<
z*q@m8m0+K=lZJn0CCfbi=ugdayG!9#`~G=(9ru)FGC8!vx!DZ;OZns6@1Iu75g7d)
zn(KbEy)?U_Ie?Aflw4M;WqdWQdc2O-;vrvlSa(<#fANK^wB=T5E7_JWL?yVe(n-lM
zTyTch#^JyAm8bbOV7<gZ@QQo9siX<YE1@hwMteX`3T_Sefz?7+T*`&6K&j+fXto8U
zy#i#d@ZR#kgn?TRBuF7`kctpzbD);0GKLJ@*K8$O)j0&j%K+qVTAC^XclWjQFs<L6
zaw|h6rFj<&AP<Bo1jQ-fI;cv~MsT1hM2v!gKasPRkc`!Of?UO|4W4@#828~Q8uWQD
zSuzQ)Mmj5uX31TK)afi4L?_2FGXz5l1%J-l5WHb2U~HD!Th9%vM^=<V7|x4e7;#C=
zDP0L43>VlovI|6O@4dDxr65D<F;5w?O}WgVly4xD=~NV%|2ox9cF1k2uq=|X_Of7O
zQD*acEY}R#QW<A+6#Ps&CUa8icfIhvU@aPi8l@D)X0@bI>PIxz)J>~nTvqox607pt
zfHowpy7W&`r@H&BOiBgWd2G*T5;1y7<MQ(JjMdF+)Aff#!G_DOyFT`p=XDW>XBhod
z8Xqdi5}pMsMUZLoISEhf>+v+dckT0TfR_G`soc`94Q0mevXl9t*YvD@UYjw3{$;ms
zhkv29m7D_n_%%MijxlQH!|BpIu8WT|%P=_0)aV3mqFjxnmYF9|IWc|rl~&DUN0qD4
z5VOwJXz4uj+c<|p8_tCgP(J0X;x1UK9G?2|URo0{N@BJG+Gv7y937l*L>()jTuDNe
z3eH$fVi~ybLPsa)Ipxu-q?gK>2%|le(=dki1njgD-1r4`jh4zv92DZFxi?%98d964
zo}7V6JF_*^@7xKYgUh!9HP-u}W$p%OP|#|l37yVFLaK3F5RAB{2)2NO)gf4)hl>?X
zI0<@kYGy&ZlRC@Z1d-fot#L2pTOpwe(hF}>+9o7x=?R*1Ptr^KD96rnEj>wZGLJf#
zWS54M-qM(aHh3Aira7kCo0t&Xcew2!x=?n&LO@cHReZ)EWyWzA_10<?VK8iz4pu1G
zG`K@UtPHvVgODB<FX&dvqKwqS;C@IqViMmBN#dI!R3@md$Z`e)l~TrxWNw7QE$L(W
zvNMqoi=ZQ%MS#-w(xNbgrJM5}FX~waRFMsU4I4iah`O;`B6C?g(Hy^=eVS&y`Tf1t
U-X&mpoONL}P2ylhA8MQb0KC3gDF6Tf

literal 0
HcmV?d00001

diff --git a/src/containers/gui.jsx b/src/containers/gui.jsx
index de4fe27a2..320eedcdb 100644
--- a/src/containers/gui.jsx
+++ b/src/containers/gui.jsx
@@ -17,16 +17,29 @@ import vmListenerHOC from '../lib/vm-listener-hoc.jsx';
 import GUIComponent from '../components/gui/gui.jsx';
 
 class GUI extends React.Component {
+    constructor (props) {
+        super(props);
+        this.state = {
+            loading: true
+        };
+    }
     componentDidMount () {
         this.audioEngine = new AudioEngine();
         this.props.vm.attachAudioEngine(this.audioEngine);
-        this.props.vm.loadProject(this.props.projectData);
-        this.props.vm.setCompatibilityMode(true);
-        this.props.vm.start();
+        this.props.vm.loadProject(this.props.projectData).then(() => {
+            this.setState({loading: false}, () => {
+                this.props.vm.setCompatibilityMode(true);
+                this.props.vm.start();
+            });
+        });
     }
     componentWillReceiveProps (nextProps) {
         if (this.props.projectData !== nextProps.projectData) {
-            this.props.vm.loadProject(nextProps.projectData);
+            this.setState({loading: true}, () => {
+                this.props.vm.loadProject(nextProps.projectData).then(() => {
+                    this.setState({loading: false});
+                });
+            });
         }
     }
     componentWillUnmount () {
@@ -35,12 +48,14 @@ class GUI extends React.Component {
     render () {
         const {
             children,
+            fetchingProject,
             projectData, // eslint-disable-line no-unused-vars
             vm,
             ...componentProps
         } = this.props;
         return (
             <GUIComponent
+                loading={fetchingProject || this.state.loading}
                 vm={vm}
                 {...componentProps}
             >
@@ -53,6 +68,7 @@ class GUI extends React.Component {
 GUI.propTypes = {
     ...GUIComponent.propTypes,
     feedbackFormVisible: PropTypes.bool,
+    fetchingProject: PropTypes.bool,
     importInfoVisible: PropTypes.bool,
     previewInfoVisible: PropTypes.bool,
     projectData: PropTypes.string,
diff --git a/src/lib/project-loader-hoc.jsx b/src/lib/project-loader-hoc.jsx
index 854928c7e..2c20b9fed 100644
--- a/src/lib/project-loader-hoc.jsx
+++ b/src/lib/project-loader-hoc.jsx
@@ -17,21 +17,25 @@ const ProjectLoaderHOC = function (WrappedComponent) {
             this.updateProject = this.updateProject.bind(this);
             this.state = {
                 projectId: null,
-                projectData: null
+                projectData: null,
+                fetchingProject: false
             };
         }
         componentDidMount () {
             window.addEventListener('hashchange', this.updateProject);
             this.updateProject();
         }
-        componentDidUpdate (prevProps, prevState) {
-            if (this.state.projectId !== prevState.projectId) {
-                storage
-                    .load(storage.AssetType.Project, this.state.projectId, storage.DataFormat.JSON)
-                    .then(projectAsset => projectAsset && this.setState({
-                        projectData: projectAsset.data.toString()
-                    }))
-                    .catch(err => log.error(err));
+        componentWillUpdate (nextProps, nextState) {
+            if (this.state.projectId !== nextState.projectId) {
+                this.setState({fetchingProject: true}, () => {
+                    storage
+                        .load(storage.AssetType.Project, this.state.projectId, storage.DataFormat.JSON)
+                        .then(projectAsset => projectAsset && this.setState({
+                            projectData: projectAsset.data.toString(),
+                            fetchingProject: false
+                        }))
+                        .catch(err => log.error(err));
+                });
             }
         }
         componentWillUnmount () {
@@ -60,6 +64,7 @@ const ProjectLoaderHOC = function (WrappedComponent) {
             if (!this.state.projectData) return null;
             return (
                 <WrappedComponent
+                    fetchingProject={this.state.fetchingProject}
                     projectData={this.state.projectData}
                     {...this.props}
                 />
-- 
GitLab