Skip to content

Commit

Permalink
Bug 1489054 - Add the loader for the initialization phase (#1151)
Browse files Browse the repository at this point in the history
Shows a Wave loader while the l10n files and locales are loading, after what it shows the interface and starts loading the rest.
  • Loading branch information
jotes authored and adngdb committed Feb 5, 2019
1 parent b202d74 commit cb4646a
Show file tree
Hide file tree
Showing 12 changed files with 170 additions and 32 deletions.
2 changes: 1 addition & 1 deletion frontend/public/index.html
Expand Up @@ -25,7 +25,7 @@
<!-- Inlining CSS mitigates/prevents fouc. -->
<style>
body {
background: #333941;
background: #333941 !important;
}
</style>
<link href="/static/css/fontawesome-all.css" rel="stylesheet" type="text/css" />
Expand Down
5 changes: 5 additions & 0 deletions frontend/src/App.css
Expand Up @@ -23,3 +23,8 @@
border-left: 1px solid #5E6475;
box-sizing: border-box;
}

#app .inner {
display: table-cell;
vertical-align: middle;
}
26 changes: 25 additions & 1 deletion frontend/src/App.js
Expand Up @@ -5,14 +5,25 @@ import { connect } from 'react-redux';

import './App.css';

import { NAME as L10N_NAME } from 'core/l10n';
import { NAME as LOCALES_NAME } from 'core/locales';
import { Lightbox } from 'core/lightbox';
import { WaveLoader } from 'core/loaders';
import * as locales from 'core/locales';
import { Navigation } from 'core/navigation';
import { UserAutoUpdater } from 'core/user';
import { EntitiesList } from 'modules/entitieslist';
import { EntityDetails } from 'modules/entitydetails';
import { SearchBox } from 'modules/search';

import type { L10nState } from 'core/l10n';
import type { LocalesState } from 'core/locales';


type Props = {|
l10n: L10nState,
locales: LocalesState,
|};

type InternalProps = {
dispatch: Function,
Expand All @@ -28,6 +39,12 @@ class App extends React.Component<InternalProps> {
}

render() {
const { l10n, locales } = this.props;

if (l10n.fetching || locales.fetching) {
return <WaveLoader />;
}

return <div id="app">
<UserAutoUpdater />
<header>
Expand All @@ -45,4 +62,11 @@ class App extends React.Component<InternalProps> {
}
}

export default connect()(App);
const mapStateToProps = (state: Object): Props => {
return {
l10n: state[L10N_NAME],
locales: state[LOCALES_NAME],
};
};

export default connect(mapStateToProps)(App);
5 changes: 0 additions & 5 deletions frontend/src/core/l10n/components/AppLocalizationProvider.js
Expand Up @@ -47,11 +47,6 @@ export class AppLocalizationProviderBase extends React.Component<InternalProps>
render() {
const { children, l10n } = this.props;

if (!l10n.bundles.length) {
// TODO: Show a loader.
return <div>LOADING</div>;
}

return <LocalizationProvider bundles={ l10n.bundles }>
{ children }
</LocalizationProvider>;
Expand Down
16 changes: 0 additions & 16 deletions frontend/src/core/l10n/components/AppLocalizationProvider.test.js
Expand Up @@ -34,22 +34,6 @@ describe('<AppLocalizationProvider>', () => {
expect(actions.get.callCount).toEqual(1);
});

it('shows a loader when the locales are not loaded yet', () => {
const store = createReduxStore();

store.dispatch(actions.request());

const wrapper = shallowUntilTarget(
<AppLocalizationProvider store={store}>
<div id="content-test-AppLocalizationProvider" />
</AppLocalizationProvider>,
AppLocalizationProviderBase
);

expect(wrapper.text()).toEqual('LOADING');
expect(wrapper.find('#content-test-AppLocalizationProvider')).toHaveLength(0);
});

it('renders its children when locales are loaded', () => {
const store = createReduxStore();
store.dispatch(actions.receive('fr', { messages: [ 'hello' ] }));
Expand Down
Expand Up @@ -4,4 +4,3 @@
padding: 40px 0 10px;
text-align: center;
}

@@ -1,14 +1,14 @@
/* @flow */
import React from 'react';

import './EntitiesLoader.css';
import './CircleLoader.css';


const EntitiesLoader = () => (
const CircleLoader = () => (
<h3 className="loading">
<div className="fa fa-sync fa-spin"></div>
</h3>
);


export default EntitiesLoader;
export default CircleLoader;
101 changes: 101 additions & 0 deletions frontend/src/core/loaders/components/WaveLoader.css
@@ -0,0 +1,101 @@
/*
* SpinKit
* https://github.com/tobiasahlin/SpinKit/blob/master/LICENSE
*/

.wave-loader {
position: fixed;
z-index: 14;
display: table;
overflow: hidden;
width: 100%;
height: 100%;
background: #3f4752;
}

.wave-loader .animation {
margin: 20px auto;
width: 100px;
height: 60px;
text-align: center;
font-size: 20px;
}

.wave-loader .animation > div {
display: inline-block;
width: 12px;
height: 100%;
background-color: #7BC876;

-webkit-animation: stretchdelay 1.2s infinite ease-in-out;
animation: stretchdelay 1.2s infinite ease-in-out;
}

.wave-loader .animation div:nth-child(2) {
-webkit-animation-delay: -1.1s;
animation-delay: -1.1s;
}

.wave-loader .animation div:nth-child(3) {
-webkit-animation-delay: -1.0s;
animation-delay: -1.0s;
}

.wave-loader .animation div:nth-child(4) {
-webkit-animation-delay: -0.9s;
animation-delay: -0.9s;
}

.wave-loader .animation div:nth-child(5) {
-webkit-animation-delay: -0.8s;
animation-delay: -0.8s;
}

@-webkit-keyframes stretchdelay {
0%, 40%, 100% {
-webkit-transform: scaleY(0.4)
}
20% {
-webkit-transform: scaleY(1.0)
}
}

@keyframes stretchdelay {
0%, 40%, 100% {
transform: scaleY(0.4);
-webkit-transform: scaleY(0.4);
}
20% {
transform: scaleY(1.0);
-webkit-transform: scaleY(1.0);
}
}

.wave-loader .inner {
display: table-cell;
vertical-align: middle;
}

/*
* Loading text
*/
.wave-loader .text {
display: block;
text-align: center;
font-size: 1.5em;
opacity: 0;
animation: fadeInLoaderText 1s forwards;
animation-delay: 3s;
}

@keyframes fadeInLoaderText {
0% {
display: None;
opacity: 0;
}

100% {
display: block;
opacity: 1;
}
}
26 changes: 26 additions & 0 deletions frontend/src/core/loaders/components/WaveLoader.js
@@ -0,0 +1,26 @@
/* @flow */

import React from 'react';

import './WaveLoader.css';

export const WaveLoader = (): React.Node => (
<div className="wave-loader">
<div className="inner">
<div className="animation">
<div></div>
&nbsp;
<div></div>
&nbsp;
<div></div>
&nbsp;
<div></div>
&nbsp;
<div></div>
</div>
</div>
</div>
);


export default WaveLoader;
4 changes: 4 additions & 0 deletions frontend/src/core/loaders/index.js
@@ -0,0 +1,4 @@
/* @flow */

export { default as WaveLoader } from './components/WaveLoader';
export { default as CircleLoader} from './components/CircleLoader';
4 changes: 2 additions & 2 deletions frontend/src/modules/entitieslist/components/EntitiesList.js
Expand Up @@ -11,7 +11,7 @@ import * as navigation from 'core/navigation';

import { actions, NAME } from '..';
import Entity from './Entity';
import EntitiesLoader from './EntitiesLoader'
import { CircleLoader } from 'core/loaders'

import type { Locale } from 'core/locales';
import type { NavigationParams } from 'core/navigation';
Expand Down Expand Up @@ -119,7 +119,7 @@ export class EntitiesListBase extends React.Component<InternalProps> {
pageStart={ 1 }
loadMore={ this.getMoreEntities }
hasMore={ hasMore }
loader={ <EntitiesLoader key={0} /> }
loader={ <CircleLoader key={0} /> }
useWindow={ false }
threshold={ 600 }
>
Expand Down
Expand Up @@ -37,7 +37,7 @@ describe('<EntitiesList>', () => {
const wrapper = shallowUntilTarget(<EntitiesList store={store} />, EntitiesListBase);
const scroll = wrapper.find('InfiniteScroll').shallow({ disableLifecycleMethods: true });

expect(scroll.find('EntitiesLoader')).toHaveLength(1);
expect(scroll.find('CircleLoader')).toHaveLength(1);
});

it("doesn't display a loading animation when there aren't entities to load", () => {
Expand All @@ -48,7 +48,7 @@ describe('<EntitiesList>', () => {
const wrapper = shallowUntilTarget(<EntitiesList store={store} />, EntitiesListBase);
const scroll = wrapper.find('InfiniteScroll').shallow({ disableLifecycleMethods: true });

expect(scroll.find('EntitiesLoader')).toHaveLength(0);
expect(scroll.find('CircleLoader')).toHaveLength(0);
});

it("shows a loading animation when entities are being fetched from the server", () => {
Expand All @@ -59,7 +59,7 @@ describe('<EntitiesList>', () => {
const wrapper = shallowUntilTarget(<EntitiesList store={store} />, EntitiesListBase);
const scroll = wrapper.find('InfiniteScroll').shallow({ disableLifecycleMethods: true });

expect(scroll.find('EntitiesLoader')).toHaveLength(1);
expect(scroll.find('CircleLoader')).toHaveLength(1);
});

it('shows the correct number of entities', () => {
Expand Down

0 comments on commit cb4646a

Please sign in to comment.