Remember scroll position when navigating back, do not needlessly reload

entire timelines (only fetch since last known ID). Side effect: account
timelines no longer update in real-time
This commit is contained in:
Eugen Rochko
2016-10-18 23:06:28 +02:00
parent 1d2175f73c
commit 8698cd3281
10 changed files with 76 additions and 20 deletions

View File

@ -57,7 +57,16 @@ export function fetchAccountTimeline(id) {
return (dispatch, getState) => {
dispatch(fetchAccountTimelineRequest(id));
api(getState).get(`/api/v1/accounts/${id}/statuses`).then(response => {
const ids = getState().getIn(['timelines', 'accounts_timelines', id], Immutable.List());
const newestId = ids.size > 0 ? ids.first() : null;
let params = '';
if (newestId !== null) {
params = `?since_id=${newestId}`;
}
api(getState).get(`/api/v1/accounts/${id}/statuses${params}`).then(response => {
dispatch(fetchAccountTimelineSuccess(id, response.data));
}).catch(error => {
dispatch(fetchAccountTimelineFail(id, error));

View File

@ -45,7 +45,16 @@ export function refreshTimeline(timeline) {
return function (dispatch, getState) {
dispatch(refreshTimelineRequest(timeline));
api(getState).get(`/api/v1/statuses/${timeline}`).then(function (response) {
const ids = getState().getIn(['timelines', timeline]);
const newestId = ids.size > 0 ? ids.first() : null;
let params = '';
if (newestId !== null) {
params = `?since_id=${newestId}`;
}
api(getState).get(`/api/v1/statuses/${timeline}${params}`).then(function (response) {
dispatch(refreshTimelineSuccess(timeline, response.data));
}).catch(function (error) {
dispatch(refreshTimelineFail(timeline, error));

View File

@ -1,6 +1,7 @@
import Status from './status';
import ImmutablePropTypes from 'react-immutable-proptypes';
import PureRenderMixin from 'react-addons-pure-render-mixin';
import Status from './status';
import ImmutablePropTypes from 'react-immutable-proptypes';
import PureRenderMixin from 'react-addons-pure-render-mixin';
import { ScrollContainer } from 'react-router-scroll';
const StatusList = React.createClass({
@ -11,9 +12,16 @@ const StatusList = React.createClass({
onFavourite: React.PropTypes.func,
onDelete: React.PropTypes.func,
onScrollToBottom: React.PropTypes.func,
trackScroll: React.PropTypes.bool,
me: React.PropTypes.number
},
getDefaultProps () {
return {
trackScroll: true
};
},
mixins: [PureRenderMixin],
handleScroll (e) {
@ -25,9 +33,9 @@ const StatusList = React.createClass({
},
render () {
const { statuses, onScrollToBottom, ...other } = this.props;
const { statuses, onScrollToBottom, trackScroll, ...other } = this.props;
return (
const scrollableArea = (
<div style={{ overflowY: 'scroll', flex: '1 1 auto', overflowX: 'hidden' }} className='scrollable' onScroll={this.handleScroll}>
<div>
{statuses.map((status) => {
@ -36,6 +44,16 @@ const StatusList = React.createClass({
</div>
</div>
);
if (trackScroll) {
return (
<ScrollContainer scrollKey='status-list'>
{scrollableArea}
</ScrollContainer>
);
} else {
return scrollableArea;
}
}
});

View File

@ -10,11 +10,13 @@ import { setAccessToken } from '../actions/meta';
import { setAccountSelf } from '../actions/accounts';
import PureRenderMixin from 'react-addons-pure-render-mixin';
import {
applyRouterMiddleware,
Router,
Route,
hashHistory,
IndexRoute
} from 'react-router';
import { useScroll } from 'react-router-scroll';
import UI from '../features/ui';
import Account from '../features/account';
import Status from '../features/status';
@ -71,7 +73,7 @@ const Mastodon = React.createClass({
render () {
return (
<Provider store={store}>
<Router history={hashHistory}>
<Router history={hashHistory} render={applyRouterMiddleware(useScroll())}>
<Route path='/' component={UI}>
<IndexRoute component={GettingStarted} />
<Route path='/statuses/new' component={Compose} />

View File

@ -19,7 +19,7 @@ const HomeTimeline = React.createClass({
render () {
return (
<Column icon='home' heading='Home'>
<StatusListContainer type='home' />
<StatusListContainer {...this.props} type='home' />
</Column>
);
},

View File

@ -19,7 +19,7 @@ const MentionsTimeline = React.createClass({
render () {
return (
<Column icon='at' heading='Mentions'>
<StatusListContainer type='mentions' />
<StatusListContainer {...this.props} type='mentions' />
</Column>
);
},

View File

@ -28,8 +28,8 @@ const UI = React.createClass({
<MediaQuery minWidth={layoutBreakpoint}>
<ColumnsArea>
<Compose />
<HomeTimeline />
<MentionsTimeline />
<HomeTimeline trackScroll={false} />
<MentionsTimeline trackScroll={false} />
{this.props.children}
</ColumnsArea>
</MediaQuery>

View File

@ -85,7 +85,7 @@ function normalizeTimeline(state, timeline, statuses) {
ids = ids.set(i, status.get('id'));
});
return state.set(timeline, ids);
return state.update(timeline, list => list.unshift(...ids));
};
function appendNormalizedTimeline(state, timeline, statuses) {
@ -100,16 +100,14 @@ function appendNormalizedTimeline(state, timeline, statuses) {
};
function normalizeAccountTimeline(state, accountId, statuses) {
state = state.updateIn(['accounts_timelines', accountId], Immutable.List([]), list => {
return (list.size > 0) ? list.clear() : list;
});
let ids = Immutable.List([]);
statuses.forEach((status, i) => {
state = normalizeStatus(state, status);
state = state.updateIn(['accounts_timelines', accountId], Immutable.List([]), list => list.set(i, status.get('id')));
ids = ids.set(i, status.get('id'));
});
return state;
return state.updateIn(['accounts_timelines', accountId], Immutable.List([]), list => list.unshift(...ids));
};
function appendNormalizedAccountTimeline(state, accountId, statuses) {
@ -137,7 +135,7 @@ function updateTimeline(state, timeline, status) {
return list.unshift(status.get('id'));
});
state = state.updateIn(['accounts_timelines', status.getIn(['account', 'id'])], Immutable.List([]), list => (list.includes(status.get('id')) ? list : list.unshift(status.get('id'))));
//state = state.updateIn(['accounts_timelines', status.getIn(['account', 'id'])], Immutable.List([]), list => (list.includes(status.get('id')) ? list : list.unshift(status.get('id'))));
return state;
};