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