Fix #3665 - Refactor timelines reducer (#3686)

* Move ancestors/descendants out of timelines reducer

* Refactor timelines reducer

All types of timelines now have a flat structure and use the same
reducer functions and actions

* Reintroduce some missing behaviours

* Fix wrong import in reports

* Fix includes typo

* Fix issue related to "next" pagination in timelines and notifications

* Fix bug with timeline's initial state, expandNotifications
This commit is contained in:
Eugen Rochko
2017-06-11 17:07:35 +02:00
committed by GitHub
parent 85d405c810
commit 47bf7a8047
21 changed files with 216 additions and 593 deletions

View File

@ -2,11 +2,8 @@ import React from 'react';
import { connect } from 'react-redux';
import ImmutablePropTypes from 'react-immutable-proptypes';
import PropTypes from 'prop-types';
import {
fetchAccount,
fetchAccountMediaTimeline,
expandAccountMediaTimeline,
} from '../../actions/accounts';
import { fetchAccount } from '../../actions/accounts';
import { refreshAccountMediaTimeline, expandAccountMediaTimeline } from '../../actions/timelines';
import LoadingIndicator from '../../components/loading_indicator';
import Column from '../ui/components/column';
import ColumnBackButton from '../../components/column_back_button';
@ -21,8 +18,8 @@ import LoadMore from '../../components/load_more';
const mapStateToProps = (state, props) => ({
medias: getAccountGallery(state, Number(props.params.accountId)),
isLoading: state.getIn(['timelines', 'accounts_media_timelines', Number(props.params.accountId), 'isLoading']),
hasMore: !!state.getIn(['timelines', 'accounts_media_timelines', Number(props.params.accountId), 'next']),
isLoading: state.getIn(['timelines', `account:${Number(props.params.accountId)}:media`, 'isLoading']),
hasMore: !!state.getIn(['timelines', `account:${Number(props.params.accountId)}:media`, 'next']),
autoPlayGif: state.getIn(['meta', 'auto_play_gif']),
});
@ -39,13 +36,13 @@ class AccountGallery extends ImmutablePureComponent {
componentDidMount () {
this.props.dispatch(fetchAccount(Number(this.props.params.accountId)));
this.props.dispatch(fetchAccountMediaTimeline(Number(this.props.params.accountId)));
this.props.dispatch(refreshAccountMediaTimeline(Number(this.props.params.accountId)));
}
componentWillReceiveProps (nextProps) {
if (nextProps.params.accountId !== this.props.params.accountId && nextProps.params.accountId) {
this.props.dispatch(fetchAccount(Number(nextProps.params.accountId)));
this.props.dispatch(fetchAccountMediaTimeline(Number(this.props.params.accountId)));
this.props.dispatch(refreshAccountMediaTimeline(Number(this.props.params.accountId)));
}
}

View File

@ -2,11 +2,8 @@ import React from 'react';
import { connect } from 'react-redux';
import ImmutablePropTypes from 'react-immutable-proptypes';
import PropTypes from 'prop-types';
import {
fetchAccount,
fetchAccountTimeline,
expandAccountTimeline,
} from '../../actions/accounts';
import { fetchAccount } from '../../actions/accounts';
import { refreshAccountTimeline, expandAccountTimeline } from '../../actions/timelines';
import StatusList from '../../components/status_list';
import LoadingIndicator from '../../components/loading_indicator';
import Column from '../ui/components/column';
@ -16,9 +13,9 @@ import Immutable from 'immutable';
import ImmutablePureComponent from 'react-immutable-pure-component';
const mapStateToProps = (state, props) => ({
statusIds: state.getIn(['timelines', 'accounts_timelines', Number(props.params.accountId), 'items'], Immutable.List()),
isLoading: state.getIn(['timelines', 'accounts_timelines', Number(props.params.accountId), 'isLoading']),
hasMore: !!state.getIn(['timelines', 'accounts_timelines', Number(props.params.accountId), 'next']),
statusIds: state.getIn(['timelines', `account:${Number(props.params.accountId)}`, 'items'], Immutable.List()),
isLoading: state.getIn(['timelines', `account:${Number(props.params.accountId)}`, 'isLoading']),
hasMore: !!state.getIn(['timelines', `account:${Number(props.params.accountId)}`, 'next']),
me: state.getIn(['meta', 'me']),
});
@ -35,13 +32,13 @@ class AccountTimeline extends ImmutablePureComponent {
componentWillMount () {
this.props.dispatch(fetchAccount(Number(this.props.params.accountId)));
this.props.dispatch(fetchAccountTimeline(Number(this.props.params.accountId)));
this.props.dispatch(refreshAccountTimeline(Number(this.props.params.accountId)));
}
componentWillReceiveProps (nextProps) {
if (nextProps.params.accountId !== this.props.params.accountId && nextProps.params.accountId) {
this.props.dispatch(fetchAccount(Number(nextProps.params.accountId)));
this.props.dispatch(fetchAccountTimeline(Number(nextProps.params.accountId)));
this.props.dispatch(refreshAccountTimeline(Number(nextProps.params.accountId)));
}
}

View File

@ -5,7 +5,8 @@ import StatusListContainer from '../ui/containers/status_list_container';
import Column from '../../components/column';
import ColumnHeader from '../../components/column_header';
import {
refreshTimeline,
refreshCommunityTimeline,
expandCommunityTimeline,
updateTimeline,
deleteFromTimelines,
connectTimeline,
@ -61,7 +62,7 @@ class CommunityTimeline extends React.PureComponent {
componentDidMount () {
const { dispatch, streamingAPIBaseURL, accessToken } = this.props;
dispatch(refreshTimeline('community'));
dispatch(refreshCommunityTimeline());
if (typeof this._subscription !== 'undefined') {
return;
@ -106,6 +107,10 @@ class CommunityTimeline extends React.PureComponent {
this.column = c;
}
handleLoadMore = () => {
this.props.dispatch(expandCommunityTimeline());
}
render () {
const { intl, hasUnread, columnId, multiColumn } = this.props;
const pinned = !!columnId;
@ -126,10 +131,10 @@ class CommunityTimeline extends React.PureComponent {
</ColumnHeader>
<StatusListContainer
{...this.props}
trackScroll={!pinned}
scrollKey={`community_timeline-${columnId}`}
type='community'
timelineId='community'
loadMore={this.handleLoadMore}
emptyMessage={<FormattedMessage id='empty_column.community' defaultMessage='The local timeline is empty. Write something publicly to get the ball rolling!' />}
/>
</Column>

View File

@ -5,7 +5,8 @@ import StatusListContainer from '../ui/containers/status_list_container';
import Column from '../../components/column';
import ColumnHeader from '../../components/column_header';
import {
refreshTimeline,
refreshHashtagTimeline,
expandHashtagTimeline,
updateTimeline,
deleteFromTimelines,
} from '../../actions/timelines';
@ -81,13 +82,13 @@ class HashtagTimeline extends React.PureComponent {
const { dispatch } = this.props;
const { id } = this.props.params;
dispatch(refreshTimeline('tag', id));
dispatch(refreshHashtagTimeline(id));
this._subscribe(dispatch, id);
}
componentWillReceiveProps (nextProps) {
if (nextProps.params.id !== this.props.params.id) {
this.props.dispatch(refreshTimeline('tag', nextProps.params.id));
this.props.dispatch(refreshHashtagTimeline(nextProps.params.id));
this._unsubscribe();
this._subscribe(this.props.dispatch, nextProps.params.id);
}
@ -101,6 +102,10 @@ class HashtagTimeline extends React.PureComponent {
this.column = c;
}
handleLoadMore = () => {
this.props.dispatch(expandHashtagTimeline(this.props.params.id));
}
render () {
const { hasUnread, columnId, multiColumn } = this.props;
const { id } = this.props.params;
@ -123,8 +128,8 @@ class HashtagTimeline extends React.PureComponent {
<StatusListContainer
trackScroll={!pinned}
scrollKey={`hashtag_timeline-${columnId}`}
type='tag'
id={id}
timelineId={`hashtag:${id}`}
loadMore={this.handleLoadMore}
emptyMessage={<FormattedMessage id='empty_column.hashtag' defaultMessage='There is nothing in this hashtag yet.' />}
/>
</Column>

View File

@ -1,5 +1,6 @@
import React from 'react';
import { connect } from 'react-redux';
import { expandHomeTimeline } from '../../actions/timelines';
import PropTypes from 'prop-types';
import StatusListContainer from '../ui/containers/status_list_container';
import Column from '../../components/column';
@ -52,6 +53,10 @@ class HomeTimeline extends React.PureComponent {
this.column = c;
}
handleLoadMore = () => {
this.props.dispatch(expandHomeTimeline());
}
render () {
const { intl, hasUnread, hasFollows, columnId, multiColumn } = this.props;
const pinned = !!columnId;
@ -80,10 +85,10 @@ class HomeTimeline extends React.PureComponent {
</ColumnHeader>
<StatusListContainer
{...this.props}
trackScroll={!pinned}
scrollKey={`home_timeline-${columnId}`}
type='home'
loadMore={this.handleLoadMore}
timelineId='home'
emptyMessage={emptyMessage}
/>
</Column>

View File

@ -5,7 +5,8 @@ import StatusListContainer from '../ui/containers/status_list_container';
import Column from '../../components/column';
import ColumnHeader from '../../components/column_header';
import {
refreshTimeline,
refreshPublicTimeline,
expandPublicTimeline,
updateTimeline,
deleteFromTimelines,
connectTimeline,
@ -61,7 +62,7 @@ class PublicTimeline extends React.PureComponent {
componentDidMount () {
const { dispatch, streamingAPIBaseURL, accessToken } = this.props;
dispatch(refreshTimeline('public'));
dispatch(refreshPublicTimeline());
if (typeof this._subscription !== 'undefined') {
return;
@ -106,6 +107,10 @@ class PublicTimeline extends React.PureComponent {
this.column = c;
}
handleLoadMore = () => {
this.props.dispatch(expandPublicTimeline());
}
render () {
const { intl, columnId, hasUnread, multiColumn } = this.props;
const pinned = !!columnId;
@ -126,8 +131,8 @@ class PublicTimeline extends React.PureComponent {
</ColumnHeader>
<StatusListContainer
{...this.props}
type='public'
timelineId='public'
loadMore={this.handleLoadMore}
trackScroll={!pinned}
scrollKey={`public_timeline-${columnId}`}
emptyMessage={<FormattedMessage id='empty_column.public' defaultMessage='There is nothing here! Write something publicly, or manually follow users from other instances to fill it up' />}

View File

@ -1,7 +1,7 @@
import React from 'react';
import { connect } from 'react-redux';
import { cancelReport, changeReportComment, submitReport } from '../../actions/reports';
import { fetchAccountTimeline } from '../../actions/accounts';
import { refreshAccountTimeline } from '../../actions/timelines';
import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes';
import Column from '../ui/components/column';
@ -28,7 +28,7 @@ const makeMapStateToProps = () => {
isSubmitting: state.getIn(['reports', 'new', 'isSubmitting']),
account: getAccount(state, accountId),
comment: state.getIn(['reports', 'new', 'comment']),
statusIds: Immutable.OrderedSet(state.getIn(['timelines', 'accounts_timelines', accountId, 'items'])).union(state.getIn(['reports', 'new', 'status_ids'])),
statusIds: Immutable.OrderedSet(state.getIn(['timelines', `account:${accountId}`, 'items'])).union(state.getIn(['reports', 'new', 'status_ids'])),
};
};
@ -61,12 +61,12 @@ class Report extends React.PureComponent {
return;
}
this.props.dispatch(fetchAccountTimeline(this.props.account.get('id')));
this.props.dispatch(refreshAccountTimeline(this.props.account.get('id')));
}
componentWillReceiveProps (nextProps) {
if (this.props.account !== nextProps.account && nextProps.account) {
this.props.dispatch(fetchAccountTimeline(nextProps.account.get('id')));
this.props.dispatch(refreshAccountTimeline(nextProps.account.get('id')));
}
}

View File

@ -44,8 +44,8 @@ const makeMapStateToProps = () => {
const mapStateToProps = (state, props) => ({
status: getStatus(state, Number(props.params.statusId)),
ancestorsIds: state.getIn(['timelines', 'ancestors', Number(props.params.statusId)]),
descendantsIds: state.getIn(['timelines', 'descendants', Number(props.params.statusId)]),
ancestorsIds: state.getIn(['contexts', 'ancestors', Number(props.params.statusId)]),
descendantsIds: state.getIn(['contexts', 'descendants', Number(props.params.statusId)]),
me: state.getIn(['meta', 'me']),
boostModal: state.getIn(['meta', 'boost_modal']),
deleteModal: state.getIn(['meta', 'delete_modal']),

View File

@ -1,6 +1,6 @@
import { connect } from 'react-redux';
import StatusList from '../../../components/status_list';
import { expandTimeline, scrollTopTimeline } from '../../../actions/timelines';
import { scrollTopTimeline } from '../../../actions/timelines';
import Immutable from 'immutable';
import { createSelector } from 'reselect';
import { debounce } from 'lodash';
@ -39,31 +39,29 @@ const makeGetStatusIds = () => createSelector([
const makeMapStateToProps = () => {
const getStatusIds = makeGetStatusIds();
const mapStateToProps = (state, props) => ({
scrollKey: props.scrollKey,
shouldUpdateScroll: props.shouldUpdateScroll,
statusIds: getStatusIds(state, props),
isLoading: state.getIn(['timelines', props.type, 'isLoading'], true),
isUnread: state.getIn(['timelines', props.type, 'unread']) > 0,
hasMore: !!state.getIn(['timelines', props.type, 'next']),
const mapStateToProps = (state, { timelineId }) => ({
statusIds: getStatusIds(state, { type: timelineId }),
isLoading: state.getIn(['timelines', timelineId, 'isLoading'], true),
isUnread: state.getIn(['timelines', timelineId, 'unread']) > 0,
hasMore: !!state.getIn(['timelines', timelineId, 'next']),
});
return mapStateToProps;
};
const mapDispatchToProps = (dispatch, { type, id }) => ({
const mapDispatchToProps = (dispatch, { timelineId, loadMore }) => ({
onScrollToBottom: debounce(() => {
dispatch(scrollTopTimeline(type, false));
dispatch(expandTimeline(type, id));
dispatch(scrollTopTimeline(timelineId, false));
loadMore();
}, 300, { leading: true }),
onScrollToTop: debounce(() => {
dispatch(scrollTopTimeline(type, true));
dispatch(scrollTopTimeline(timelineId, true));
}, 100),
onScroll: debounce(() => {
dispatch(scrollTopTimeline(type, false));
dispatch(scrollTopTimeline(timelineId, false));
}, 100),
});

View File

@ -8,7 +8,7 @@ import { connect } from 'react-redux';
import { isMobile } from '../../is_mobile';
import { debounce } from 'lodash';
import { uploadCompose } from '../../actions/compose';
import { refreshTimeline } from '../../actions/timelines';
import { refreshHomeTimeline } from '../../actions/timelines';
import { refreshNotifications } from '../../actions/notifications';
import UploadArea from './components/upload_area';
import ColumnsAreaContainer from './containers/columns_area_container';
@ -95,7 +95,7 @@ class UI extends React.PureComponent {
document.addEventListener('dragleave', this.handleDragLeave, false);
document.addEventListener('dragend', this.handleDragEnd, false);
this.props.dispatch(refreshTimeline('home'));
this.props.dispatch(refreshHomeTimeline());
this.props.dispatch(refreshNotifications());
}