diff --git a/frontend/public/index.html b/frontend/public/index.html
index 0900d2e4ee..fd2f6b1339 100644
--- a/frontend/public/index.html
+++ b/frontend/public/index.html
@@ -25,7 +25,7 @@
diff --git a/frontend/src/App.css b/frontend/src/App.css
index fbcf1dd9d8..63e26c16bd 100644
--- a/frontend/src/App.css
+++ b/frontend/src/App.css
@@ -23,3 +23,8 @@
border-left: 1px solid #5E6475;
box-sizing: border-box;
}
+
+#app .inner {
+ display: table-cell;
+ vertical-align: middle;
+}
diff --git a/frontend/src/App.js b/frontend/src/App.js
index a1d1fe6785..8b63368ebc 100644
--- a/frontend/src/App.js
+++ b/frontend/src/App.js
@@ -5,7 +5,10 @@ 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';
@@ -13,6 +16,14 @@ 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,
@@ -28,6 +39,12 @@ class App extends React.Component {
}
render() {
+ const { l10n, locales } = this.props;
+
+ if (l10n.fetching || locales.fetching) {
+ return ;
+ }
+
return
@@ -45,4 +62,11 @@ class App extends React.Component {
}
}
-export default connect()(App);
+const mapStateToProps = (state: Object): Props => {
+ return {
+ l10n: state[L10N_NAME],
+ locales: state[LOCALES_NAME],
+ };
+};
+
+export default connect(mapStateToProps)(App);
diff --git a/frontend/src/core/l10n/components/AppLocalizationProvider.js b/frontend/src/core/l10n/components/AppLocalizationProvider.js
index 1e7380ea95..e6cf7074f3 100644
--- a/frontend/src/core/l10n/components/AppLocalizationProvider.js
+++ b/frontend/src/core/l10n/components/AppLocalizationProvider.js
@@ -47,11 +47,6 @@ export class AppLocalizationProviderBase extends React.Component
render() {
const { children, l10n } = this.props;
- if (!l10n.bundles.length) {
- // TODO: Show a loader.
- return LOADING
;
- }
-
return
{ children }
;
diff --git a/frontend/src/core/l10n/components/AppLocalizationProvider.test.js b/frontend/src/core/l10n/components/AppLocalizationProvider.test.js
index 115c5bccd2..833e08195c 100644
--- a/frontend/src/core/l10n/components/AppLocalizationProvider.test.js
+++ b/frontend/src/core/l10n/components/AppLocalizationProvider.test.js
@@ -34,22 +34,6 @@ describe('', () => {
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(
-
-
- ,
- 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' ] }));
diff --git a/frontend/src/modules/entitieslist/components/EntitiesLoader.css b/frontend/src/core/loaders/components/CircleLoader.css
similarity index 99%
rename from frontend/src/modules/entitieslist/components/EntitiesLoader.css
rename to frontend/src/core/loaders/components/CircleLoader.css
index 1ad9ecda81..751e90e04d 100644
--- a/frontend/src/modules/entitieslist/components/EntitiesLoader.css
+++ b/frontend/src/core/loaders/components/CircleLoader.css
@@ -4,4 +4,3 @@
padding: 40px 0 10px;
text-align: center;
}
-
diff --git a/frontend/src/modules/entitieslist/components/EntitiesLoader.js b/frontend/src/core/loaders/components/CircleLoader.js
similarity index 59%
rename from frontend/src/modules/entitieslist/components/EntitiesLoader.js
rename to frontend/src/core/loaders/components/CircleLoader.js
index b5991f1e24..ef6c155193 100644
--- a/frontend/src/modules/entitieslist/components/EntitiesLoader.js
+++ b/frontend/src/core/loaders/components/CircleLoader.js
@@ -1,14 +1,14 @@
/* @flow */
import React from 'react';
-import './EntitiesLoader.css';
+import './CircleLoader.css';
-const EntitiesLoader = () => (
+const CircleLoader = () => (
);
-export default EntitiesLoader;
+export default CircleLoader;
diff --git a/frontend/src/core/loaders/components/WaveLoader.css b/frontend/src/core/loaders/components/WaveLoader.css
new file mode 100644
index 0000000000..8f513df0b9
--- /dev/null
+++ b/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;
+ }
+}
diff --git a/frontend/src/core/loaders/components/WaveLoader.js b/frontend/src/core/loaders/components/WaveLoader.js
new file mode 100644
index 0000000000..ccd570921f
--- /dev/null
+++ b/frontend/src/core/loaders/components/WaveLoader.js
@@ -0,0 +1,26 @@
+/* @flow */
+
+import React from 'react';
+
+import './WaveLoader.css';
+
+export const WaveLoader = (): React.Node => (
+
+);
+
+
+export default WaveLoader;
diff --git a/frontend/src/core/loaders/index.js b/frontend/src/core/loaders/index.js
new file mode 100644
index 0000000000..c67c3a30f9
--- /dev/null
+++ b/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';
diff --git a/frontend/src/modules/entitieslist/components/EntitiesList.js b/frontend/src/modules/entitieslist/components/EntitiesList.js
index 50f17d7c47..c5c5f6e09e 100644
--- a/frontend/src/modules/entitieslist/components/EntitiesList.js
+++ b/frontend/src/modules/entitieslist/components/EntitiesList.js
@@ -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';
@@ -119,7 +119,7 @@ export class EntitiesListBase extends React.Component {
pageStart={ 1 }
loadMore={ this.getMoreEntities }
hasMore={ hasMore }
- loader={ }
+ loader={ }
useWindow={ false }
threshold={ 600 }
>
diff --git a/frontend/src/modules/entitieslist/components/EntitiesList.test.js b/frontend/src/modules/entitieslist/components/EntitiesList.test.js
index 788467ccf4..5a973eb2f3 100644
--- a/frontend/src/modules/entitieslist/components/EntitiesList.test.js
+++ b/frontend/src/modules/entitieslist/components/EntitiesList.test.js
@@ -37,7 +37,7 @@ describe('', () => {
const wrapper = shallowUntilTarget(, 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", () => {
@@ -48,7 +48,7 @@ describe('', () => {
const wrapper = shallowUntilTarget(, 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", () => {
@@ -59,7 +59,7 @@ describe('', () => {
const wrapper = shallowUntilTarget(, 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', () => {