Improve initialState loading

shrike
Eugen Rochko 2017-01-09 12:37:15 +01:00
parent 2e71bb031b
commit 23ebf60b95
11 changed files with 108 additions and 90 deletions

View File

@ -1,8 +1,6 @@
import api, { getLinks } from '../api' import api, { getLinks } from '../api'
import Immutable from 'immutable'; import Immutable from 'immutable';
export const ACCOUNT_SET_SELF = 'ACCOUNT_SET_SELF';
export const ACCOUNT_FETCH_REQUEST = 'ACCOUNT_FETCH_REQUEST'; export const ACCOUNT_FETCH_REQUEST = 'ACCOUNT_FETCH_REQUEST';
export const ACCOUNT_FETCH_SUCCESS = 'ACCOUNT_FETCH_SUCCESS'; export const ACCOUNT_FETCH_SUCCESS = 'ACCOUNT_FETCH_SUCCESS';
export const ACCOUNT_FETCH_FAIL = 'ACCOUNT_FETCH_FAIL'; export const ACCOUNT_FETCH_FAIL = 'ACCOUNT_FETCH_FAIL';
@ -67,13 +65,6 @@ export const FOLLOW_REQUEST_REJECT_REQUEST = 'FOLLOW_REQUEST_REJECT_REQUEST';
export const FOLLOW_REQUEST_REJECT_SUCCESS = 'FOLLOW_REQUEST_REJECT_SUCCESS'; export const FOLLOW_REQUEST_REJECT_SUCCESS = 'FOLLOW_REQUEST_REJECT_SUCCESS';
export const FOLLOW_REQUEST_REJECT_FAIL = 'FOLLOW_REQUEST_REJECT_FAIL'; export const FOLLOW_REQUEST_REJECT_FAIL = 'FOLLOW_REQUEST_REJECT_FAIL';
export function setAccountSelf(account) {
return {
type: ACCOUNT_SET_SELF,
account
};
};
export function fetchAccount(id) { export function fetchAccount(id) {
return (dispatch, getState) => { return (dispatch, getState) => {
dispatch(fetchAccountRequest(id)); dispatch(fetchAccountRequest(id));

View File

@ -1,8 +0,0 @@
export const ACCESS_TOKEN_SET = 'ACCESS_TOKEN_SET';
export function setAccessToken(token) {
return {
type: ACCESS_TOKEN_SET,
token: token
};
};

View File

@ -0,0 +1,17 @@
import Immutable from 'immutable';
export const STORE_HYDRATE = 'STORE_HYDRATE';
const convertState = rawState =>
Immutable.fromJS(rawState, (k, v) =>
Immutable.Iterable.isIndexed(v) ? v.toList() : v.toMap().mapKeys(x =>
Number.isNaN(x * 1) ? x : x * 1));
export function hydrateStore(rawState) {
const state = convertState(rawState);
return {
type: STORE_HYDRATE,
state
};
};

View File

@ -7,8 +7,6 @@ import {
refreshTimeline refreshTimeline
} from '../actions/timelines'; } from '../actions/timelines';
import { updateNotifications } from '../actions/notifications'; import { updateNotifications } from '../actions/notifications';
import { setAccessToken } from '../actions/meta';
import { setAccountSelf } from '../actions/accounts';
import createBrowserHistory from 'history/lib/createBrowserHistory'; import createBrowserHistory from 'history/lib/createBrowserHistory';
import { import {
applyRouterMiddleware, applyRouterMiddleware,
@ -44,9 +42,12 @@ import pt from 'react-intl/locale-data/pt';
import hu from 'react-intl/locale-data/hu'; import hu from 'react-intl/locale-data/hu';
import uk from 'react-intl/locale-data/uk'; import uk from 'react-intl/locale-data/uk';
import getMessagesForLocale from '../locales'; import getMessagesForLocale from '../locales';
import { hydrateStore } from '../actions/store';
const store = configureStore(); const store = configureStore();
store.dispatch(hydrateStore(window.INITIAL_STATE));
const browserHistory = useRouterHistory(createBrowserHistory)({ const browserHistory = useRouterHistory(createBrowserHistory)({
basename: '/web' basename: '/web'
}); });
@ -56,29 +57,26 @@ addLocaleData([...en, ...de, ...es, ...fr, ...pt, ...hu, ...uk]);
const Mastodon = React.createClass({ const Mastodon = React.createClass({
propTypes: { propTypes: {
token: React.PropTypes.string.isRequired,
timelines: React.PropTypes.object,
account: React.PropTypes.string,
locale: React.PropTypes.string.isRequired locale: React.PropTypes.string.isRequired
}, },
componentWillMount() { componentWillMount() {
const { token, account, locale } = this.props; const { locale } = this.props;
store.dispatch(setAccessToken(token));
store.dispatch(setAccountSelf(JSON.parse(account)));
if (typeof App !== 'undefined') { if (typeof App !== 'undefined') {
this.subscription = App.cable.subscriptions.create('TimelineChannel', { this.subscription = App.cable.subscriptions.create('TimelineChannel', {
received (data) { received (data) {
switch(data.type) { switch(data.type) {
case 'update': case 'update':
return store.dispatch(updateTimeline(data.timeline, JSON.parse(data.message))); store.dispatch(updateTimeline(data.timeline, JSON.parse(data.message)));
case 'delete': break;
return store.dispatch(deleteFromTimelines(data.id)); case 'delete':
case 'notification': store.dispatch(deleteFromTimelines(data.id));
return store.dispatch(updateNotifications(JSON.parse(data.message), getMessagesForLocale(locale), locale)); break;
case 'notification':
store.dispatch(updateNotifications(JSON.parse(data.message), getMessagesForLocale(locale), locale));
break;
} }
} }

View File

@ -1,8 +1,10 @@
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import NavigationBar from '../components/navigation_bar'; import NavigationBar from '../components/navigation_bar';
const mapStateToProps = (state, props) => ({ const mapStateToProps = (state, props) => {
account: state.getIn(['accounts', state.getIn(['meta', 'me'])]) return {
}); account: state.getIn(['accounts', state.getIn(['meta', 'me'])])
};
};
export default connect(mapStateToProps)(NavigationBar); export default connect(mapStateToProps)(NavigationBar);

View File

@ -1,5 +1,4 @@
import { import {
ACCOUNT_SET_SELF,
ACCOUNT_FETCH_SUCCESS, ACCOUNT_FETCH_SUCCESS,
FOLLOWERS_FETCH_SUCCESS, FOLLOWERS_FETCH_SUCCESS,
FOLLOWERS_EXPAND_SUCCESS, FOLLOWERS_EXPAND_SUCCESS,
@ -33,6 +32,7 @@ import {
NOTIFICATIONS_REFRESH_SUCCESS, NOTIFICATIONS_REFRESH_SUCCESS,
NOTIFICATIONS_EXPAND_SUCCESS NOTIFICATIONS_EXPAND_SUCCESS
} from '../actions/notifications'; } from '../actions/notifications';
import { STORE_HYDRATE } from '../actions/store';
import Immutable from 'immutable'; import Immutable from 'immutable';
const normalizeAccount = (state, account) => state.set(account.id, Immutable.fromJS(account)); const normalizeAccount = (state, account) => state.set(account.id, Immutable.fromJS(account));
@ -67,38 +67,39 @@ const initialState = Immutable.Map();
export default function accounts(state = initialState, action) { export default function accounts(state = initialState, action) {
switch(action.type) { switch(action.type) {
case ACCOUNT_SET_SELF: case STORE_HYDRATE:
case ACCOUNT_FETCH_SUCCESS: return state.merge(action.state.get('accounts'));
case NOTIFICATIONS_UPDATE: case ACCOUNT_FETCH_SUCCESS:
return normalizeAccount(state, action.account); case NOTIFICATIONS_UPDATE:
case FOLLOWERS_FETCH_SUCCESS: return normalizeAccount(state, action.account);
case FOLLOWERS_EXPAND_SUCCESS: case FOLLOWERS_FETCH_SUCCESS:
case FOLLOWING_FETCH_SUCCESS: case FOLLOWERS_EXPAND_SUCCESS:
case FOLLOWING_EXPAND_SUCCESS: case FOLLOWING_FETCH_SUCCESS:
case REBLOGS_FETCH_SUCCESS: case FOLLOWING_EXPAND_SUCCESS:
case FAVOURITES_FETCH_SUCCESS: case REBLOGS_FETCH_SUCCESS:
case COMPOSE_SUGGESTIONS_READY: case FAVOURITES_FETCH_SUCCESS:
case SEARCH_SUGGESTIONS_READY: case COMPOSE_SUGGESTIONS_READY:
case FOLLOW_REQUESTS_FETCH_SUCCESS: case SEARCH_SUGGESTIONS_READY:
return normalizeAccounts(state, action.accounts); case FOLLOW_REQUESTS_FETCH_SUCCESS:
case NOTIFICATIONS_REFRESH_SUCCESS: return normalizeAccounts(state, action.accounts);
case NOTIFICATIONS_EXPAND_SUCCESS: case NOTIFICATIONS_REFRESH_SUCCESS:
return normalizeAccountsFromStatuses(normalizeAccounts(state, action.accounts), action.statuses); case NOTIFICATIONS_EXPAND_SUCCESS:
case TIMELINE_REFRESH_SUCCESS: return normalizeAccountsFromStatuses(normalizeAccounts(state, action.accounts), action.statuses);
case TIMELINE_EXPAND_SUCCESS: case TIMELINE_REFRESH_SUCCESS:
case ACCOUNT_TIMELINE_FETCH_SUCCESS: case TIMELINE_EXPAND_SUCCESS:
case ACCOUNT_TIMELINE_EXPAND_SUCCESS: case ACCOUNT_TIMELINE_FETCH_SUCCESS:
case CONTEXT_FETCH_SUCCESS: case ACCOUNT_TIMELINE_EXPAND_SUCCESS:
return normalizeAccountsFromStatuses(state, action.statuses); case CONTEXT_FETCH_SUCCESS:
case REBLOG_SUCCESS: return normalizeAccountsFromStatuses(state, action.statuses);
case FAVOURITE_SUCCESS: case REBLOG_SUCCESS:
case UNREBLOG_SUCCESS: case FAVOURITE_SUCCESS:
case UNFAVOURITE_SUCCESS: case UNREBLOG_SUCCESS:
return normalizeAccountFromStatus(state, action.response); case UNFAVOURITE_SUCCESS:
case TIMELINE_UPDATE: return normalizeAccountFromStatus(state, action.response);
case STATUS_FETCH_SUCCESS: case TIMELINE_UPDATE:
return normalizeAccountFromStatus(state, action.status); case STATUS_FETCH_SUCCESS:
default: return normalizeAccountFromStatus(state, action.status);
return state; default:
return state;
} }
}; };

View File

@ -21,7 +21,7 @@ import {
COMPOSE_LISTABILITY_CHANGE COMPOSE_LISTABILITY_CHANGE
} from '../actions/compose'; } from '../actions/compose';
import { TIMELINE_DELETE } from '../actions/timelines'; import { TIMELINE_DELETE } from '../actions/timelines';
import { ACCOUNT_SET_SELF } from '../actions/accounts'; import { STORE_HYDRATE } from '../actions/store';
import Immutable from 'immutable'; import Immutable from 'immutable';
const initialState = Immutable.Map({ const initialState = Immutable.Map({
@ -88,6 +88,8 @@ const insertSuggestion = (state, position, token, completion) => {
export default function compose(state = initialState, action) { export default function compose(state = initialState, action) {
switch(action.type) { switch(action.type) {
case STORE_HYDRATE:
return state.merge(action.state.get('compose'));
case COMPOSE_MOUNT: case COMPOSE_MOUNT:
return state.set('mounted', true); return state.set('mounted', true);
case COMPOSE_UNMOUNT: case COMPOSE_UNMOUNT:
@ -97,7 +99,7 @@ export default function compose(state = initialState, action) {
case COMPOSE_VISIBILITY_CHANGE: case COMPOSE_VISIBILITY_CHANGE:
return state.set('private', action.checked); return state.set('private', action.checked);
case COMPOSE_LISTABILITY_CHANGE: case COMPOSE_LISTABILITY_CHANGE:
return state.set('unlisted', action.checked); return state.set('unlisted', action.checked);
case COMPOSE_CHANGE: case COMPOSE_CHANGE:
return state.set('text', action.text); return state.set('text', action.text);
case COMPOSE_REPLY: case COMPOSE_REPLY:
@ -143,8 +145,6 @@ export default function compose(state = initialState, action) {
} else { } else {
return state; return state;
} }
case ACCOUNT_SET_SELF:
return state.set('me', action.account.id).set('private', action.account.locked);
default: default:
return state; return state;
} }

View File

@ -1,16 +1,16 @@
import { ACCESS_TOKEN_SET } from '../actions/meta'; import { STORE_HYDRATE } from '../actions/store';
import { ACCOUNT_SET_SELF } from '../actions/accounts';
import Immutable from 'immutable'; import Immutable from 'immutable';
const initialState = Immutable.Map(); const initialState = Immutable.Map({
access_token: null,
me: null
});
export default function meta(state = initialState, action) { export default function meta(state = initialState, action) {
switch(action.type) { switch(action.type) {
case ACCESS_TOKEN_SET: case STORE_HYDRATE:
return state.set('access_token', action.token); return state.merge(action.state.get('meta'));
case ACCOUNT_SET_SELF: default:
return state.set('me', action.account.id); return state;
default:
return state;
} }
}; };

View File

@ -1,11 +1,12 @@
import { createStore, applyMiddleware, compose } from 'redux'; import { createStore, applyMiddleware, compose } from 'redux';
import thunk from 'redux-thunk'; import thunk from 'redux-thunk';
import appReducer from '../reducers'; import appReducer from '../reducers';
import { loadingBarMiddleware } from 'react-redux-loading-bar'; import { loadingBarMiddleware } from 'react-redux-loading-bar';
import errorsMiddleware from '../middleware/errors'; import errorsMiddleware from '../middleware/errors';
import Immutable from 'immutable';
export default function configureStore(initialState) { export default function configureStore() {
return createStore(appReducer, initialState, compose(applyMiddleware(thunk, loadingBarMiddleware({ return createStore(appReducer, compose(applyMiddleware(thunk, loadingBarMiddleware({
promiseTypeSuffixes: ['REQUEST', 'SUCCESS', 'FAIL'], promiseTypeSuffixes: ['REQUEST', 'SUCCESS', 'FAIL'],
}), errorsMiddleware()), window.devToolsExtension ? window.devToolsExtension() : f => f)); }), errorsMiddleware()), window.devToolsExtension ? window.devToolsExtension() : f => f));
}; };

View File

@ -3,8 +3,6 @@
module HomeHelper module HomeHelper
def default_props def default_props
{ {
token: @token,
account: render(file: 'api/v1/accounts/show', locals: { account: current_user.account }, formats: :json),
locale: I18n.locale, locale: I18n.locale,
} }
end end

View File

@ -1,4 +1,22 @@
- content_for :header_tags do - content_for :header_tags do
:javascript
window.INITIAL_STATE = {
"meta": {
"access_token": "#{@token}",
"locale": "#{I18n.locale}",
"me": #{current_account.id}
},
"compose": {
"me": #{current_account.id},
"private": #{current_account.locked?}
},
"accounts": {
#{current_account.id}: #{render(file: 'api/v1/accounts/show', locals: { account: current_user.account }, formats: :json)}
}
};
= javascript_include_tag 'application' = javascript_include_tag 'application'
= react_component 'Mastodon', default_props, class: 'app-holder', prerender: false = react_component 'Mastodon', default_props, class: 'app-holder', prerender: false