about summary refs log tree commit diff
diff options
context:
space:
mode:
authorEugen Rochko <eugen@zeonfederated.com>2016-09-19 23:25:59 +0200
committerEugen Rochko <eugen@zeonfederated.com>2016-09-19 23:26:21 +0200
commit337462aa5e68014aa15788e4513e190b2e434d7e (patch)
treea760e80e2bc9b1fef55f118ddeb8603b20155a61
parentf820edb463109e313e836d8e2f210927a0eba7d1 (diff)
Re-organizing components to be more modular, adding loading bars
-rw-r--r--app/assets/javascripts/components.js2
-rw-r--r--app/assets/javascripts/components/components/frontend.jsx50
-rw-r--r--app/assets/javascripts/components/containers/mastodon.jsx (renamed from app/assets/javascripts/components/containers/root.jsx)8
-rw-r--r--app/assets/javascripts/components/features/status/index.jsx4
-rw-r--r--app/assets/javascripts/components/features/ui/components/character_counter.jsx (renamed from app/assets/javascripts/components/components/character_counter.jsx)0
-rw-r--r--app/assets/javascripts/components/features/ui/components/column.jsx (renamed from app/assets/javascripts/components/components/column.jsx)0
-rw-r--r--app/assets/javascripts/components/features/ui/components/column_header.jsx (renamed from app/assets/javascripts/components/components/column_header.jsx)0
-rw-r--r--app/assets/javascripts/components/features/ui/components/columns_area.jsx (renamed from app/assets/javascripts/components/components/columns_area.jsx)0
-rw-r--r--app/assets/javascripts/components/features/ui/components/compose_form.jsx (renamed from app/assets/javascripts/components/components/compose_form.jsx)2
-rw-r--r--app/assets/javascripts/components/features/ui/components/drawer.jsx (renamed from app/assets/javascripts/components/components/drawer.jsx)0
-rw-r--r--app/assets/javascripts/components/features/ui/components/follow_form.jsx (renamed from app/assets/javascripts/components/components/follow_form.jsx)2
-rw-r--r--app/assets/javascripts/components/features/ui/components/media_gallery.jsx (renamed from app/assets/javascripts/components/components/media_gallery.jsx)0
-rw-r--r--app/assets/javascripts/components/features/ui/components/navigation_bar.jsx (renamed from app/assets/javascripts/components/components/navigation_bar.jsx)6
-rw-r--r--app/assets/javascripts/components/features/ui/components/reply_indicator.jsx (renamed from app/assets/javascripts/components/components/reply_indicator.jsx)6
-rw-r--r--app/assets/javascripts/components/features/ui/components/upload_button.jsx (renamed from app/assets/javascripts/components/components/upload_button.jsx)2
-rw-r--r--app/assets/javascripts/components/features/ui/components/upload_form.jsx (renamed from app/assets/javascripts/components/components/upload_form.jsx)2
-rw-r--r--app/assets/javascripts/components/features/ui/containers/compose_form_container.jsx (renamed from app/assets/javascripts/components/containers/compose_form_container.jsx)2
-rw-r--r--app/assets/javascripts/components/features/ui/containers/follow_form_container.jsx (renamed from app/assets/javascripts/components/containers/follow_form_container.jsx)2
-rw-r--r--app/assets/javascripts/components/features/ui/containers/navigation_container.jsx (renamed from app/assets/javascripts/components/containers/navigation_container.jsx)0
-rw-r--r--app/assets/javascripts/components/features/ui/containers/notifications_container.jsx (renamed from app/assets/javascripts/components/containers/notifications_container.jsx)2
-rw-r--r--app/assets/javascripts/components/features/ui/containers/status_list_container.jsx (renamed from app/assets/javascripts/components/containers/status_list_container.jsx)8
-rw-r--r--app/assets/javascripts/components/features/ui/containers/upload_form_container.jsx (renamed from app/assets/javascripts/components/containers/upload_form_container.jsx)2
-rw-r--r--app/assets/javascripts/components/features/ui/index.jsx56
-rw-r--r--app/assets/javascripts/components/reducers/compose.jsx36
-rw-r--r--app/assets/javascripts/components/reducers/follow.jsx19
-rw-r--r--app/assets/javascripts/components/reducers/index.jsx16
-rw-r--r--app/assets/javascripts/components/reducers/notifications.jsx2
-rw-r--r--app/assets/javascripts/components/reducers/timelines.jsx38
-rw-r--r--app/assets/javascripts/components/store/configureStore.jsx11
-rw-r--r--app/views/home/index.html.haml2
-rw-r--r--package.json1
31 files changed, 155 insertions, 126 deletions
diff --git a/app/assets/javascripts/components.js b/app/assets/javascripts/components.js
index d4d9b97e4..16e33e58e 100644
--- a/app/assets/javascripts/components.js
+++ b/app/assets/javascripts/components.js
@@ -6,4 +6,4 @@ window.ReactDOM = require('react-dom');
 
 //= require_tree ./components
 
-window.Root = require('./components/containers/root');
+window.Mastodon = require('./components/containers/mastodon');
diff --git a/app/assets/javascripts/components/components/frontend.jsx b/app/assets/javascripts/components/components/frontend.jsx
deleted file mode 100644
index faa141af8..000000000
--- a/app/assets/javascripts/components/components/frontend.jsx
+++ /dev/null
@@ -1,50 +0,0 @@
-import ColumnsArea            from './columns_area';
-import Column                 from './column';
-import Drawer                 from './drawer';
-import ComposeFormContainer   from '../containers/compose_form_container';
-import FollowFormContainer    from '../containers/follow_form_container';
-import UploadFormContainer    from '../containers/upload_form_container';
-import StatusListContainer    from '../containers/status_list_container';
-import NotificationsContainer from '../containers/notifications_container';
-import NavigationContainer    from '../containers/navigation_container';
-import PureRenderMixin from 'react-addons-pure-render-mixin';
-
-const Frontend = React.createClass({
-
-  mixins: [PureRenderMixin],
-
-  render () {
-    return (
-      <div style={{ flex: '0 0 auto', display: 'flex', width: '100%', height: '100%', background: '#1a1c23' }}>
-        <Drawer>
-          <div style={{ flex: '1 1 auto' }}>
-            <NavigationContainer />
-            <ComposeFormContainer />
-            <UploadFormContainer />
-          </div>
-
-          <FollowFormContainer />
-        </Drawer>
-
-        <ColumnsArea>
-          <Column icon='home' heading='Home'>
-            <StatusListContainer type='home' />
-          </Column>
-
-          <Column icon='at' heading='Mentions'>
-            <StatusListContainer type='mentions' />
-          </Column>
-
-          <Column>
-            {this.props.children}
-          </Column>
-        </ColumnsArea>
-
-        <NotificationsContainer />
-      </div>
-    );
-  }
-
-});
-
-export default Frontend;
diff --git a/app/assets/javascripts/components/containers/root.jsx b/app/assets/javascripts/components/containers/mastodon.jsx
index e30330beb..ba6e568ac 100644
--- a/app/assets/javascripts/components/containers/root.jsx
+++ b/app/assets/javascripts/components/containers/mastodon.jsx
@@ -1,6 +1,5 @@
 import { Provider }                                                          from 'react-redux';
 import configureStore                                                        from '../store/configureStore';
-import Frontend                                                              from '../components/frontend';
 import { setTimeline, updateTimeline, deleteFromTimelines, refreshTimeline } from '../actions/timelines';
 import { setAccessToken }                                                    from '../actions/meta';
 import { setAccountSelf }                                                    from '../actions/accounts';
@@ -10,10 +9,11 @@ import Account                                                               fro
 import Settings                                                              from '../features/settings';
 import Status                                                                from '../features/status';
 import Subscriptions                                                         from '../features/subscriptions';
+import UI                                                                    from '../features/ui';
 
 const store = configureStore();
 
-const Root = React.createClass({
+const Mastodon = React.createClass({
 
   propTypes: {
     token: React.PropTypes.string.isRequired,
@@ -58,7 +58,7 @@ const Root = React.createClass({
     return (
       <Provider store={store}>
         <Router history={hashHistory}>
-          <Route path='/' component={Frontend}>
+          <Route path='/' component={UI}>
             <Route path='/settings' component={Settings} />
             <Route path='/subscriptions' component={Subscriptions} />
             <Route path='/statuses/:statusId' component={Status} />
@@ -71,4 +71,4 @@ const Root = React.createClass({
 
 });
 
-export default Root;
+export default Mastodon;
diff --git a/app/assets/javascripts/components/features/status/index.jsx b/app/assets/javascripts/components/features/status/index.jsx
index 72ff6a944..7a810d474 100644
--- a/app/assets/javascripts/components/features/status/index.jsx
+++ b/app/assets/javascripts/components/features/status/index.jsx
@@ -31,12 +31,12 @@ const Status = React.createClass({
   mixins: [PureRenderMixin],
 
   componentWillMount () {
-    this.props.dispatch(fetchStatus(this.props.params.statusId));
+    this.props.dispatch(fetchStatus(Number(this.props.params.statusId)));
   },
 
   componentWillReceiveProps (nextProps) {
     if (nextProps.params.statusId !== this.props.params.statusId && nextProps.params.statusId) {
-      this.props.dispatch(fetchStatus(nextProps.params.statusId));
+      this.props.dispatch(fetchStatus(Number(nextProps.params.statusId)));
     }
   },
 
diff --git a/app/assets/javascripts/components/components/character_counter.jsx b/app/assets/javascripts/components/features/ui/components/character_counter.jsx
index dd9218844..dd9218844 100644
--- a/app/assets/javascripts/components/components/character_counter.jsx
+++ b/app/assets/javascripts/components/features/ui/components/character_counter.jsx
diff --git a/app/assets/javascripts/components/components/column.jsx b/app/assets/javascripts/components/features/ui/components/column.jsx
index 7109bcc19..7109bcc19 100644
--- a/app/assets/javascripts/components/components/column.jsx
+++ b/app/assets/javascripts/components/features/ui/components/column.jsx
diff --git a/app/assets/javascripts/components/components/column_header.jsx b/app/assets/javascripts/components/features/ui/components/column_header.jsx
index 21def69c7..21def69c7 100644
--- a/app/assets/javascripts/components/components/column_header.jsx
+++ b/app/assets/javascripts/components/features/ui/components/column_header.jsx
diff --git a/app/assets/javascripts/components/components/columns_area.jsx b/app/assets/javascripts/components/features/ui/components/columns_area.jsx
index e45a6466d..e45a6466d 100644
--- a/app/assets/javascripts/components/components/columns_area.jsx
+++ b/app/assets/javascripts/components/features/ui/components/columns_area.jsx
diff --git a/app/assets/javascripts/components/components/compose_form.jsx b/app/assets/javascripts/components/features/ui/components/compose_form.jsx
index 40d9b7773..1aa0b447f 100644
--- a/app/assets/javascripts/components/components/compose_form.jsx
+++ b/app/assets/javascripts/components/features/ui/components/compose_form.jsx
@@ -1,5 +1,5 @@
 import CharacterCounter   from './character_counter';
-import Button             from './button';
+import Button             from '../../../components/button';
 import PureRenderMixin    from 'react-addons-pure-render-mixin';
 import ImmutablePropTypes from 'react-immutable-proptypes';
 import ReplyIndicator     from './reply_indicator';
diff --git a/app/assets/javascripts/components/components/drawer.jsx b/app/assets/javascripts/components/features/ui/components/drawer.jsx
index dfba11ad2..dfba11ad2 100644
--- a/app/assets/javascripts/components/components/drawer.jsx
+++ b/app/assets/javascripts/components/features/ui/components/drawer.jsx
diff --git a/app/assets/javascripts/components/components/follow_form.jsx b/app/assets/javascripts/components/features/ui/components/follow_form.jsx
index 0563c511f..a9d73a9a1 100644
--- a/app/assets/javascripts/components/components/follow_form.jsx
+++ b/app/assets/javascripts/components/features/ui/components/follow_form.jsx
@@ -1,4 +1,4 @@
-import IconButton      from './icon_button';
+import IconButton      from '../../../components/icon_button';
 import PureRenderMixin from 'react-addons-pure-render-mixin';
 
 const FollowForm = React.createClass({
diff --git a/app/assets/javascripts/components/components/media_gallery.jsx b/app/assets/javascripts/components/features/ui/components/media_gallery.jsx
index 20f9a3d87..20f9a3d87 100644
--- a/app/assets/javascripts/components/components/media_gallery.jsx
+++ b/app/assets/javascripts/components/features/ui/components/media_gallery.jsx
diff --git a/app/assets/javascripts/components/components/navigation_bar.jsx b/app/assets/javascripts/components/features/ui/components/navigation_bar.jsx
index 2e7d6e6e1..b5d374a88 100644
--- a/app/assets/javascripts/components/components/navigation_bar.jsx
+++ b/app/assets/javascripts/components/features/ui/components/navigation_bar.jsx
@@ -1,8 +1,8 @@
 import PureRenderMixin    from 'react-addons-pure-render-mixin';
 import ImmutablePropTypes from 'react-immutable-proptypes';
-import Avatar             from './avatar';
-import IconButton         from './icon_button';
-import DisplayName        from './display_name';
+import Avatar             from '../../../components/avatar';
+import IconButton         from '../../../components/icon_button';
+import DisplayName        from '../../../components/display_name';
 import { Link }           from 'react-router';
 
 const NavigationBar = React.createClass({
diff --git a/app/assets/javascripts/components/components/reply_indicator.jsx b/app/assets/javascripts/components/features/ui/components/reply_indicator.jsx
index 9598a5874..316a23b54 100644
--- a/app/assets/javascripts/components/components/reply_indicator.jsx
+++ b/app/assets/javascripts/components/features/ui/components/reply_indicator.jsx
@@ -1,8 +1,8 @@
 import PureRenderMixin    from 'react-addons-pure-render-mixin';
 import ImmutablePropTypes from 'react-immutable-proptypes';
-import Avatar             from './avatar';
-import IconButton         from './icon_button';
-import DisplayName        from './display_name';
+import Avatar             from '../../../components/avatar';
+import IconButton         from '../../../components/icon_button';
+import DisplayName        from '../../../components/display_name';
 
 const ReplyIndicator = React.createClass({
 
diff --git a/app/assets/javascripts/components/components/upload_button.jsx b/app/assets/javascripts/components/features/ui/components/upload_button.jsx
index a77aacc10..d1b093242 100644
--- a/app/assets/javascripts/components/components/upload_button.jsx
+++ b/app/assets/javascripts/components/features/ui/components/upload_button.jsx
@@ -1,5 +1,5 @@
 import PureRenderMixin from 'react-addons-pure-render-mixin';
-import Button          from './button';
+import Button          from '../../../components/button';
 
 const UploadButton = React.createClass({
 
diff --git a/app/assets/javascripts/components/components/upload_form.jsx b/app/assets/javascripts/components/features/ui/components/upload_form.jsx
index f0b6f7992..d584e9ab7 100644
--- a/app/assets/javascripts/components/components/upload_form.jsx
+++ b/app/assets/javascripts/components/features/ui/components/upload_form.jsx
@@ -1,7 +1,7 @@
 import PureRenderMixin    from 'react-addons-pure-render-mixin';
 import ImmutablePropTypes from 'react-immutable-proptypes';
 import UploadButton       from './upload_button';
-import IconButton         from './icon_button';
+import IconButton         from '../../../components/icon_button';
 
 const UploadForm = React.createClass({
 
diff --git a/app/assets/javascripts/components/containers/compose_form_container.jsx b/app/assets/javascripts/components/features/ui/containers/compose_form_container.jsx
index d49217a90..a092a1e8e 100644
--- a/app/assets/javascripts/components/containers/compose_form_container.jsx
+++ b/app/assets/javascripts/components/features/ui/containers/compose_form_container.jsx
@@ -1,6 +1,6 @@
 import { connect }                                          from 'react-redux';
 import ComposeForm                                          from '../components/compose_form';
-import { changeCompose, submitCompose, cancelReplyCompose } from '../actions/compose';
+import { changeCompose, submitCompose, cancelReplyCompose } from '../../../actions/compose';
 
 function selectStatus(state) {
   let statusId = state.getIn(['compose', 'in_reply_to'], null);
diff --git a/app/assets/javascripts/components/containers/follow_form_container.jsx b/app/assets/javascripts/components/features/ui/containers/follow_form_container.jsx
index b5f787aba..a21c1291b 100644
--- a/app/assets/javascripts/components/containers/follow_form_container.jsx
+++ b/app/assets/javascripts/components/features/ui/containers/follow_form_container.jsx
@@ -1,6 +1,6 @@
 import { connect }                    from 'react-redux';
 import FollowForm                     from '../components/follow_form';
-import { changeFollow, submitFollow } from '../actions/follow';
+import { changeFollow, submitFollow } from '../../../actions/follow';
 
 const mapStateToProps = function (state, props) {
   return {
diff --git a/app/assets/javascripts/components/containers/navigation_container.jsx b/app/assets/javascripts/components/features/ui/containers/navigation_container.jsx
index 4aeea4c37..4aeea4c37 100644
--- a/app/assets/javascripts/components/containers/navigation_container.jsx
+++ b/app/assets/javascripts/components/features/ui/containers/navigation_container.jsx
diff --git a/app/assets/javascripts/components/containers/notifications_container.jsx b/app/assets/javascripts/components/features/ui/containers/notifications_container.jsx
index 68173b34e..2db1603fc 100644
--- a/app/assets/javascripts/components/containers/notifications_container.jsx
+++ b/app/assets/javascripts/components/features/ui/containers/notifications_container.jsx
@@ -1,6 +1,6 @@
 import { connect }             from 'react-redux';
 import { NotificationStack }   from 'react-notification';
-import { dismissNotification } from '../actions/notifications';
+import { dismissNotification } from '../../../actions/notifications';
 
 const mapStateToProps = (state, props) => {
   return {
diff --git a/app/assets/javascripts/components/containers/status_list_container.jsx b/app/assets/javascripts/components/features/ui/containers/status_list_container.jsx
index a4fba95e1..4ea599fc0 100644
--- a/app/assets/javascripts/components/containers/status_list_container.jsx
+++ b/app/assets/javascripts/components/features/ui/containers/status_list_container.jsx
@@ -1,8 +1,8 @@
 import { connect }           from 'react-redux';
-import StatusList            from '../components/status_list';
-import { replyCompose }      from '../actions/compose';
-import { reblog, favourite } from '../actions/interactions';
-import { selectStatus }      from '../reducers/timelines';
+import StatusList            from '../../../components/status_list';
+import { replyCompose }      from '../../../actions/compose';
+import { reblog, favourite } from '../../../actions/interactions';
+import { selectStatus }      from '../../../reducers/timelines';
 
 const mapStateToProps = function (state, props) {
   return {
diff --git a/app/assets/javascripts/components/containers/upload_form_container.jsx b/app/assets/javascripts/components/features/ui/containers/upload_form_container.jsx
index b168e8187..6554f944f 100644
--- a/app/assets/javascripts/components/containers/upload_form_container.jsx
+++ b/app/assets/javascripts/components/features/ui/containers/upload_form_container.jsx
@@ -1,6 +1,6 @@
 import { connect }                          from 'react-redux';
 import UploadForm                           from '../components/upload_form';
-import { uploadCompose, undoUploadCompose } from '../actions/compose';
+import { uploadCompose, undoUploadCompose } from '../../../actions/compose';
 
 const mapStateToProps = function (state, props) {
   return {
diff --git a/app/assets/javascripts/components/features/ui/index.jsx b/app/assets/javascripts/components/features/ui/index.jsx
new file mode 100644
index 000000000..fbfa361f2
--- /dev/null
+++ b/app/assets/javascripts/components/features/ui/index.jsx
@@ -0,0 +1,56 @@
+import ColumnsArea            from './components/columns_area';
+import Column                 from './components/column';
+import Drawer                 from './components/drawer';
+import ComposeFormContainer   from './containers/compose_form_container';
+import FollowFormContainer    from './containers/follow_form_container';
+import UploadFormContainer    from './containers/upload_form_container';
+import StatusListContainer    from './containers/status_list_container';
+import NotificationsContainer from './containers/notifications_container';
+import NavigationContainer    from './containers/navigation_container';
+import PureRenderMixin        from 'react-addons-pure-render-mixin';
+import LoadingBar             from 'react-redux-loading-bar';
+
+const UI = React.createClass({
+
+  propTypes: {
+    router: React.PropTypes.object
+  },
+
+  mixins: [PureRenderMixin],
+
+  render () {
+    return (
+      <div style={{ flex: '0 0 auto', display: 'flex', width: '100%', height: '100%', background: '#1a1c23' }}>
+        <Drawer>
+          <div style={{ flex: '1 1 auto' }}>
+            <NavigationContainer />
+            <ComposeFormContainer />
+            <UploadFormContainer />
+          </div>
+
+          <FollowFormContainer />
+        </Drawer>
+
+        <ColumnsArea>
+          <Column icon='home' heading='Home'>
+            <StatusListContainer type='home' />
+          </Column>
+
+          <Column icon='at' heading='Mentions'>
+            <StatusListContainer type='mentions' />
+          </Column>
+
+          <Column>
+            {this.props.children}
+          </Column>
+        </ColumnsArea>
+
+        <NotificationsContainer />
+        <LoadingBar style={{ backgroundColor: '#2b90d9', left: '0', top: '0' }} />
+      </div>
+    );
+  }
+
+});
+
+export default UI;
diff --git a/app/assets/javascripts/components/reducers/compose.jsx b/app/assets/javascripts/components/reducers/compose.jsx
index c0d9f4ff2..688c494c0 100644
--- a/app/assets/javascripts/components/reducers/compose.jsx
+++ b/app/assets/javascripts/components/reducers/compose.jsx
@@ -1,4 +1,16 @@
-import * as constants      from '../actions/compose';
+import {
+  COMPOSE_CHANGE,
+  COMPOSE_REPLY,
+  COMPOSE_REPLY_CANCEL,
+  COMPOSE_SUBMIT_REQUEST,
+  COMPOSE_SUBMIT_SUCCESS,
+  COMPOSE_SUBMIT_FAIL,
+  COMPOSE_UPLOAD_REQUEST,
+  COMPOSE_UPLOAD_SUCCESS,
+  COMPOSE_UPLOAD_FAIL,
+  COMPOSE_UPLOAD_UNDO,
+  COMPOSE_UPLOAD_PROGRESS
+}                          from '../actions/compose';
 import { TIMELINE_DELETE } from '../actions/timelines';
 import Immutable           from 'immutable';
 
@@ -13,41 +25,41 @@ const initialState = Immutable.Map({
 
 export default function compose(state = initialState, action) {
   switch(action.type) {
-    case constants.COMPOSE_CHANGE:
+    case COMPOSE_CHANGE:
       return state.set('text', action.text);
-    case constants.COMPOSE_REPLY:
+    case COMPOSE_REPLY:
       return state.withMutations(map => {
         map.set('in_reply_to', action.status.get('id'));
         map.set('text', `@${action.status.getIn(['account', 'acct'])} `);
       });
-    case constants.COMPOSE_REPLY_CANCEL:
+    case COMPOSE_REPLY_CANCEL:
       return state.withMutations(map => {
         map.set('in_reply_to', null);
         map.set('text', '');
       });
-    case constants.COMPOSE_SUBMIT_REQUEST:
+    case COMPOSE_SUBMIT_REQUEST:
       return state.set('is_submitting', true);
-    case constants.COMPOSE_SUBMIT_SUCCESS:
+    case COMPOSE_SUBMIT_SUCCESS:
       return state.withMutations(map => {
         map.set('text', '');
         map.set('is_submitting', false);
         map.set('in_reply_to', null);
         map.update('media_attachments', list => list.clear());
       });
-    case constants.COMPOSE_SUBMIT_FAIL:
+    case COMPOSE_SUBMIT_FAIL:
       return state.set('is_submitting', false);
-    case constants.COMPOSE_UPLOAD_REQUEST:
+    case COMPOSE_UPLOAD_REQUEST:
       return state.set('is_uploading', true);
-    case constants.COMPOSE_UPLOAD_SUCCESS:
+    case COMPOSE_UPLOAD_SUCCESS:
       return state.withMutations(map => {
         map.update('media_attachments', list => list.push(Immutable.fromJS(action.media)));
         map.set('is_uploading', false);
       });
-    case constants.COMPOSE_UPLOAD_FAIL:
+    case COMPOSE_UPLOAD_FAIL:
       return state.set('is_uploading', false);
-    case constants.COMPOSE_UPLOAD_UNDO:
+    case COMPOSE_UPLOAD_UNDO:
       return state.update('media_attachments', list => list.filterNot(item => item.get('id') === action.media_id));
-    case constants.COMPOSE_UPLOAD_PROGRESS:
+    case COMPOSE_UPLOAD_PROGRESS:
       return state.set('progress', Math.round((action.loaded / action.total) * 100));
     case TIMELINE_DELETE:
       if (action.id === state.get('in_reply_to')) {
diff --git a/app/assets/javascripts/components/reducers/follow.jsx b/app/assets/javascripts/components/reducers/follow.jsx
index 838f12259..e1dc293c6 100644
--- a/app/assets/javascripts/components/reducers/follow.jsx
+++ b/app/assets/javascripts/components/reducers/follow.jsx
@@ -1,22 +1,27 @@
-import * as constants from '../actions/follow';
-import Immutable                                                                                              from 'immutable';
+import {
+  FOLLOW_CHANGE,
+  FOLLOW_SUBMIT_REQUEST,
+  FOLLOW_SUBMIT_SUCCESS,
+  FOLLOW_SUBMIT_FAIL
+}                from '../actions/follow';
+import Immutable from 'immutable';
 
 const initialState = Immutable.Map({
   text: '',
   is_submitting: false
 });
 
-export default function compose(state = initialState, action) {
+export default function follow(state = initialState, action) {
   switch(action.type) {
-    case constants.FOLLOW_CHANGE:
+    case FOLLOW_CHANGE:
       return state.set('text', action.text);
-    case constants.FOLLOW_SUBMIT_REQUEST:
+    case FOLLOW_SUBMIT_REQUEST:
       return state.set('is_submitting', true);
-    case constants.FOLLOW_SUBMIT_SUCCESS:
+    case FOLLOW_SUBMIT_SUCCESS:
       return state.withMutations(map => {
         map.set('text', '').set('is_submitting', false);
       });
-    case constants.FOLLOW_SUBMIT_FAIL:
+    case FOLLOW_SUBMIT_FAIL:
       return state.set('is_submitting', false);
     default:
       return state;
diff --git a/app/assets/javascripts/components/reducers/index.jsx b/app/assets/javascripts/components/reducers/index.jsx
index 6d275654a..da9ab1a21 100644
--- a/app/assets/javascripts/components/reducers/index.jsx
+++ b/app/assets/javascripts/components/reducers/index.jsx
@@ -1,14 +1,16 @@
-import { combineReducers } from 'redux-immutable';
-import timelines           from './timelines';
-import meta                from './meta';
-import compose             from './compose';
-import follow              from './follow';
-import notifications       from './notifications';
+import { combineReducers }   from 'redux-immutable';
+import timelines             from './timelines';
+import meta                  from './meta';
+import compose               from './compose';
+import follow                from './follow';
+import notifications         from './notifications';
+import { loadingBarReducer } from 'react-redux-loading-bar';
 
 export default combineReducers({
   timelines,
   meta,
   compose,
   follow,
-  notifications
+  notifications,
+  loadingBar: loadingBarReducer,
 });
diff --git a/app/assets/javascripts/components/reducers/notifications.jsx b/app/assets/javascripts/components/reducers/notifications.jsx
index bdd2de993..694d9ff4b 100644
--- a/app/assets/javascripts/components/reducers/notifications.jsx
+++ b/app/assets/javascripts/components/reducers/notifications.jsx
@@ -24,7 +24,7 @@ function notificationFromError(state, error) {
   return state.push(n);
 };
 
-export default function meta(state = initialState, action) {
+export default function notifications(state = initialState, action) {
   switch(action.type) {
     case COMPOSE_SUBMIT_FAIL:
     case COMPOSE_UPLOAD_FAIL:
diff --git a/app/assets/javascripts/components/reducers/timelines.jsx b/app/assets/javascripts/components/reducers/timelines.jsx
index 04a49e16d..2bc4e6eb6 100644
--- a/app/assets/javascripts/components/reducers/timelines.jsx
+++ b/app/assets/javascripts/components/reducers/timelines.jsx
@@ -45,7 +45,7 @@ export function selectStatus(state, id) {
   return status;
 };
 
-function statusToMaps(state, status) {
+function normalizeStatus(state, status) {
   // Separate account
   let account = status.get('account');
   status = status.set('account', account.get('id'));
@@ -55,7 +55,7 @@ function statusToMaps(state, status) {
 
   if (reblog !== null) {
     status = status.set('reblog', reblog.get('id'));
-    state  = statusToMaps(state, reblog);
+    state  = normalizeStatus(state, reblog);
   }
 
   // Replies
@@ -80,26 +80,26 @@ function statusToMaps(state, status) {
   });
 };
 
-function timelineToMaps(state, timeline, statuses) {
+function normalizeTimeline(state, timeline, statuses) {
   statuses.forEach((status, i) => {
-    state = statusToMaps(state, status);
+    state = normalizeStatus(state, status);
     state = state.setIn([timeline, i], status.get('id'));
   });
 
   return state;
 };
 
-function accountTimelineToMaps(state, accountId, statuses) {
+function normalizeAccountTimeline(state, accountId, statuses) {
   statuses.forEach((status, i) => {
-    state = statusToMaps(state, status);
+    state = normalizeStatus(state, status);
     state = state.updateIn(['accounts_timelines', accountId], Immutable.List(), list => list.set(i, status.get('id')));
   });
 
   return state;
 };
 
-function updateTimelineWithMaps(state, timeline, status) {
-  state = statusToMaps(state, status);
+function updateTimeline(state, timeline, status) {
+  state = normalizeStatus(state, status);
   state = state.update(timeline, list => list.unshift(status.get('id')));
   state = state.updateIn(['accounts_timelines', status.getIn(['account', 'id'])], Immutable.List(), list => list.unshift(status.get('id')));
 
@@ -114,20 +114,20 @@ function deleteStatus(state, id) {
   return state.deleteIn(['statuses', id]);
 };
 
-function accountToMaps(state, account) {
+function normalizeAccount(state, account) {
   return state.setIn(['accounts', account.get('id')], account);
 };
 
-function contextToMaps(state, status, ancestors, descendants) {
-  state = statusToMaps(state, status);
+function normalizeContext(state, status, ancestors, descendants) {
+  state = normalizeStatus(state, status);
 
   let ancestorsIds = ancestors.map(ancestor => {
-    state = statusToMaps(state, ancestor);
+    state = normalizeStatus(state, ancestor);
     return ancestor.get('id');
   }).toOrderedSet();
 
   let descendantsIds = descendants.map(descendant => {
-    state = statusToMaps(state, descendant);
+    state = normalizeStatus(state, descendant);
     return descendant.get('id');
   }).toOrderedSet();
 
@@ -140,14 +140,14 @@ function contextToMaps(state, status, ancestors, descendants) {
 export default function timelines(state = initialState, action) {
   switch(action.type) {
     case TIMELINE_SET:
-      return timelineToMaps(state, action.timeline, Immutable.fromJS(action.statuses));
+      return normalizeTimeline(state, action.timeline, Immutable.fromJS(action.statuses));
     case TIMELINE_UPDATE:
-      return updateTimelineWithMaps(state, action.timeline, Immutable.fromJS(action.status));
+      return updateTimeline(state, action.timeline, Immutable.fromJS(action.status));
     case TIMELINE_DELETE:
       return deleteStatus(state, action.id);
     case REBLOG_SUCCESS:
     case FAVOURITE_SUCCESS:
-      return statusToMaps(state, Immutable.fromJS(action.response));
+      return normalizeStatus(state, Immutable.fromJS(action.response));
     case ACCOUNT_SET_SELF:
       return state.withMutations(map => {
         map.setIn(['accounts', action.account.id], Immutable.fromJS(action.account));
@@ -157,11 +157,11 @@ export default function timelines(state = initialState, action) {
     case FOLLOW_SUBMIT_SUCCESS:
     case ACCOUNT_FOLLOW_SUCCESS:
     case ACCOUNT_UNFOLLOW_SUCCESS:
-      return accountToMaps(state, Immutable.fromJS(action.account));
+      return normalizeAccount(state, Immutable.fromJS(action.account));
     case STATUS_FETCH_SUCCESS:
-      return contextToMaps(state, Immutable.fromJS(action.status), Immutable.fromJS(action.context.ancestors), Immutable.fromJS(action.context.descendants));
+      return normalizeContext(state, Immutable.fromJS(action.status), Immutable.fromJS(action.context.ancestors), Immutable.fromJS(action.context.descendants));
     case ACCOUNT_TIMELINE_FETCH_SUCCESS:
-      return accountTimelineToMaps(state, action.id, Immutable.fromJS(action.statuses));
+      return normalizeAccountTimeline(state, action.id, Immutable.fromJS(action.statuses));
     default:
       return state;
   }
diff --git a/app/assets/javascripts/components/store/configureStore.jsx b/app/assets/javascripts/components/store/configureStore.jsx
index 1f2fcf2cf..89f498217 100644
--- a/app/assets/javascripts/components/store/configureStore.jsx
+++ b/app/assets/javascripts/components/store/configureStore.jsx
@@ -1,7 +1,10 @@
 import { createStore, applyMiddleware, compose } from 'redux';
-import thunk from 'redux-thunk';
-import appReducer from '../reducers';
+import thunk                                     from 'redux-thunk';
+import appReducer                                from '../reducers';
+import { loadingBarMiddleware }                  from 'react-redux-loading-bar';
 
 export default function configureStore(initialState) {
-  return createStore(appReducer, initialState, compose(applyMiddleware(thunk), window.devToolsExtension ? window.devToolsExtension() : f => f));
-}
+  return createStore(appReducer, initialState, compose(applyMiddleware(thunk, loadingBarMiddleware({
+    promiseTypeSuffixes: ['REQUEST', 'SUCCESS', 'FAIL'],
+  })), window.devToolsExtension ? window.devToolsExtension() : f => f));
+};
diff --git a/app/views/home/index.html.haml b/app/views/home/index.html.haml
index 34028462c..f06c26e9d 100644
--- a/app/views/home/index.html.haml
+++ b/app/views/home/index.html.haml
@@ -1 +1 @@
-= react_component 'Root', default_props, class: 'app-holder', prerender: false
+= react_component 'Mastodon', default_props, class: 'app-holder', prerender: false
diff --git a/package.json b/package.json
index 5234a37f0..f8df13a2f 100644
--- a/package.json
+++ b/package.json
@@ -21,6 +21,7 @@
     "react-immutable-proptypes": "^2.1.0",
     "react-notification": "^6.1.1",
     "react-redux": "^4.4.5",
+    "react-redux-loading-bar": "^2.3.3",
     "react-router": "^2.8.0",
     "redux": "^3.5.2",
     "redux-immutable": "^3.0.8",