Skip to content
Snippets Groups Projects
Commit 565cd95b authored by Eugen Rochko's avatar Eugen Rochko
Browse files

Keep timelines in the UI trimmed when possible

parent b14b5e3b
No related branches found
No related tags found
No related merge requests found
......@@ -12,12 +12,13 @@ export const TIMELINE_EXPAND_REQUEST = 'TIMELINE_EXPAND_REQUEST';
export const TIMELINE_EXPAND_SUCCESS = 'TIMELINE_EXPAND_SUCCESS';
export const TIMELINE_EXPAND_FAIL = 'TIMELINE_EXPAND_FAIL';
export function refreshTimelineSuccess(timeline, statuses, replace) {
export const TIMELINE_SCROLL_TOP = 'TIMELINE_SCROLL_TOP';
export function refreshTimelineSuccess(timeline, statuses) {
return {
type: TIMELINE_REFRESH_SUCCESS,
timeline: timeline,
statuses: statuses,
replace: replace
statuses: statuses
};
};
......@@ -48,24 +49,25 @@ export function deleteFromTimelines(id) {
};
};
export function refreshTimelineRequest(timeline) {
export function refreshTimelineRequest(timeline, id) {
return {
type: TIMELINE_REFRESH_REQUEST,
timeline: timeline
timeline,
id
};
};
export function refreshTimeline(timeline, replace = false, id = null) {
export function refreshTimeline(timeline, id = null) {
return function (dispatch, getState) {
dispatch(refreshTimelineRequest(timeline));
dispatch(refreshTimelineRequest(timeline, id));
const ids = getState().getIn(['timelines', timeline], Immutable.List());
const ids = getState().getIn(['timelines', timeline, 'items'], Immutable.List());
const newestId = ids.size > 0 ? ids.first() : null;
let params = '';
let path = timeline;
if (newestId !== null && !replace) {
if (newestId !== null) {
params = `?since_id=${newestId}`;
}
......@@ -74,7 +76,7 @@ export function refreshTimeline(timeline, replace = false, id = null) {
}
api(getState).get(`/api/v1/timelines/${path}${params}`).then(function (response) {
dispatch(refreshTimelineSuccess(timeline, response.data, replace));
dispatch(refreshTimelineSuccess(timeline, response.data));
}).catch(function (error) {
dispatch(refreshTimelineFail(timeline, error));
});
......@@ -84,14 +86,14 @@ export function refreshTimeline(timeline, replace = false, id = null) {
export function refreshTimelineFail(timeline, error) {
return {
type: TIMELINE_REFRESH_FAIL,
timeline: timeline,
error: error
timeline,
error
};
};
export function expandTimeline(timeline, id = null) {
return (dispatch, getState) => {
const lastId = getState().getIn(['timelines', timeline], Immutable.List()).last();
const lastId = getState().getIn(['timelines', timeline, 'items'], Immutable.List()).last();
dispatch(expandTimelineRequest(timeline));
......@@ -112,22 +114,30 @@ export function expandTimeline(timeline, id = null) {
export function expandTimelineRequest(timeline) {
return {
type: TIMELINE_EXPAND_REQUEST,
timeline: timeline
timeline
};
};
export function expandTimelineSuccess(timeline, statuses) {
return {
type: TIMELINE_EXPAND_SUCCESS,
timeline: timeline,
statuses: statuses
timeline,
statuses
};
};
export function expandTimelineFail(timeline, error) {
return {
type: TIMELINE_EXPAND_FAIL,
timeline: timeline,
error: error
timeline,
error
};
};
export function scrollTopTimeline(timeline, top) {
return {
type: TIMELINE_SCROLL_TOP,
timeline,
top
};
};
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';
import StatusContainer from '../containers/status_container';
import StatusContainer from '../containers/status_container';
const StatusList = React.createClass({
propTypes: {
statusIds: ImmutablePropTypes.list.isRequired,
onScrollToBottom: React.PropTypes.func,
onScrollToTop: React.PropTypes.func,
onScroll: React.PropTypes.func,
trackScroll: React.PropTypes.bool
},
......@@ -27,6 +29,10 @@ const StatusList = React.createClass({
if (scrollTop === scrollHeight - clientHeight) {
this.props.onScrollToBottom();
} else if (scrollTop < 100) {
this.props.onScrollToTop();
} else {
this.props.onScroll();
}
},
......
......@@ -47,13 +47,13 @@ const HashtagTimeline = React.createClass({
const { dispatch } = this.props;
const { id } = this.props.params;
dispatch(refreshTimeline('tag', true, id));
dispatch(refreshTimeline('tag', id));
this._subscribe(dispatch, id);
},
componentWillReceiveProps (nextProps) {
if (nextProps.params.id !== this.props.params.id) {
this.props.dispatch(refreshTimeline('tag', true, nextProps.params.id));
this.props.dispatch(refreshTimeline('tag', nextProps.params.id));
this._unsubscribe();
this._subscribe(this.props.dispatch, nextProps.params.id);
}
......
import { connect } from 'react-redux';
import StatusList from '../../../components/status_list';
import { expandTimeline } from '../../../actions/timelines';
import { expandTimeline, scrollTopTimeline } from '../../../actions/timelines';
import Immutable from 'immutable';
const mapStateToProps = (state, props) => ({
statusIds: state.getIn(['timelines', props.type], Immutable.List())
statusIds: state.getIn(['timelines', props.type, 'items'], Immutable.List())
});
const mapDispatchToProps = function (dispatch, props) {
return {
onScrollToBottom () {
dispatch(scrollTopTimeline(props.type, false));
dispatch(expandTimeline(props.type, props.id));
},
onScrollToTop () {
dispatch(scrollTopTimeline(props.type, true));
},
onScroll () {
dispatch(scrollTopTimeline(props.type, false));
}
};
};
......
import {
TIMELINE_REFRESH_REQUEST,
TIMELINE_REFRESH_SUCCESS,
TIMELINE_UPDATE,
TIMELINE_DELETE,
TIMELINE_EXPAND_SUCCESS
TIMELINE_EXPAND_SUCCESS,
TIMELINE_SCROLL_TOP
} from '../actions/timelines';
import {
REBLOG_SUCCESS,
......@@ -23,10 +25,31 @@ import {
import Immutable from 'immutable';
const initialState = Immutable.Map({
home: Immutable.List(),
mentions: Immutable.List(),
public: Immutable.List(),
tag: Immutable.List(),
home: Immutable.Map({
loaded: false,
top: true,
items: Immutable.List()
}),
mentions: Immutable.Map({
loaded: false,
top: true,
items: Immutable.List()
}),
public: Immutable.Map({
loaded: false,
top: true,
items: Immutable.List()
}),
tag: Immutable.Map({
id: null,
loaded: false,
top: true,
items: Immutable.List()
}),
accounts_timelines: Immutable.Map(),
ancestors: Immutable.Map(),
descendants: Immutable.Map()
......@@ -50,14 +73,17 @@ const normalizeStatus = (state, status) => {
};
const normalizeTimeline = (state, timeline, statuses, replace = false) => {
let ids = Immutable.List();
let ids = Immutable.List();
const loaded = state.getIn([timeline, 'loaded']);
statuses.forEach((status, i) => {
state = normalizeStatus(state, status);
ids = ids.set(i, status.get('id'));
});
return state.update(timeline, Immutable.List(), list => (replace ? ids : list.unshift(...ids)));
state = state.setIn([timeline, 'loaded'], true);
return state.updateIn([timeline, 'items'], Immutable.List(), list => (loaded ? list.unshift(...ids) : list.push(...ids)));
};
const appendNormalizedTimeline = (state, timeline, statuses) => {
......@@ -68,7 +94,7 @@ const appendNormalizedTimeline = (state, timeline, statuses) => {
moreIds = moreIds.set(i, status.get('id'));
});
return state.update(timeline, Immutable.List(), list => list.push(...moreIds));
return state.updateIn([timeline, 'items'], Immutable.List(), list => list.push(...moreIds));
};
const normalizeAccountTimeline = (state, accountId, statuses, replace = false) => {
......@@ -94,9 +120,15 @@ const appendNormalizedAccountTimeline = (state, accountId, statuses) => {
};
const updateTimeline = (state, timeline, status, references) => {
const top = state.getIn([timeline, 'top']);
state = normalizeStatus(state, status);
state = state.update(timeline, Immutable.List(), list => {
state = state.updateIn([timeline, 'items'], Immutable.List(), list => {
if (top && list.size > 40) {
list = list.take(20);
}
if (list.includes(status.get('id'))) {
return list;
}
......@@ -116,7 +148,7 @@ const updateTimeline = (state, timeline, status, references) => {
const deleteStatus = (state, id, accountId, references) => {
// Remove references from timelines
['home', 'mentions', 'public', 'tag'].forEach(function (timeline) {
state = state.update(timeline, list => list.filterNot(item => item === id));
state = state.updateIn([timeline, 'items'], list => list.filterNot(item => item === id));
});
// Remove references from account timelines
......@@ -166,10 +198,23 @@ const normalizeContext = (state, id, ancestors, descendants) => {
});
};
const resetTimeline = (state, timeline, id) => {
if (timeline === 'tag' && state.getIn([timeline, 'id']) !== id) {
state = state.update(timeline, map => map
.set('id', id)
.set('loaded', false)
.update('items', list => list.clear()));
}
return state;
};
export default function timelines(state = initialState, action) {
switch(action.type) {
case TIMELINE_REFRESH_REQUEST:
return resetTimeline(state, action.timeline, action.id);
case TIMELINE_REFRESH_SUCCESS:
return normalizeTimeline(state, action.timeline, Immutable.fromJS(action.statuses), action.replace);
return normalizeTimeline(state, action.timeline, Immutable.fromJS(action.statuses));
case TIMELINE_EXPAND_SUCCESS:
return appendNormalizedTimeline(state, action.timeline, Immutable.fromJS(action.statuses));
case TIMELINE_UPDATE:
......@@ -184,6 +229,8 @@ export default function timelines(state = initialState, action) {
return appendNormalizedAccountTimeline(state, action.id, Immutable.fromJS(action.statuses));
case ACCOUNT_BLOCK_SUCCESS:
return filterTimelines(state, action.relationship, action.statuses);
case TIMELINE_SCROLL_TOP:
return state.setIn([action.timeline, 'top'], action.top);
default:
return state;
}
......
......@@ -2,10 +2,10 @@ class AddFromAccountIdToNotifications < ActiveRecord::Migration[5.0]
def up
add_column :notifications, :from_account_id, :integer
Notification.where(activity_type: 'Status').update_all('from_account_id = (SELECT statuses.account_id FROM notifications AS notifications1 INNER JOIN statuses ON notifications1.activity_id = statuses.id WHERE notifications1.activity_type = \'Status\' AND notifications1.id = notifications.id)')
Notification.where(activity_type: 'Mention').update_all('from_account_id = (SELECT statuses.account_id FROM notifications AS notifications1 INNER JOIN mentions ON notifications1.activity_id = mentions.id INNER JOIN statuses ON mentions.status_id = statuses.id WHERE notifications1.activity_type = \'Mention\' AND notifications1.id = notifications.id)')
Notification.where(activity_type: 'Favourite').update_all('from_account_id = (SELECT favourites.account_id FROM notifications AS notifications1 INNER JOIN favourites ON notifications1.activity_id = favourites.id WHERE notifications1.activity_type = \'Favourite\' AND notifications1.id = notifications.id)')
Notification.where(activity_type: 'Follow').update_all('from_account_id = (SELECT follows.account_id FROM notifications AS notifications1 INNER JOIN follows ON notifications1.activity_id = follows.id WHERE notifications1.activity_type = \'Follow\' AND notifications1.id = notifications.id)')
Notification.where(from_account_id: nil).where(activity_type: 'Status').update_all('from_account_id = (SELECT statuses.account_id FROM notifications AS notifications1 INNER JOIN statuses ON notifications1.activity_id = statuses.id WHERE notifications1.activity_type = \'Status\' AND notifications1.id = notifications.id)')
Notification.where(from_account_id: nil).where(activity_type: 'Mention').update_all('from_account_id = (SELECT statuses.account_id FROM notifications AS notifications1 INNER JOIN mentions ON notifications1.activity_id = mentions.id INNER JOIN statuses ON mentions.status_id = statuses.id WHERE notifications1.activity_type = \'Mention\' AND notifications1.id = notifications.id)')
Notification.where(from_account_id: nil).where(activity_type: 'Favourite').update_all('from_account_id = (SELECT favourites.account_id FROM notifications AS notifications1 INNER JOIN favourites ON notifications1.activity_id = favourites.id WHERE notifications1.activity_type = \'Favourite\' AND notifications1.id = notifications.id)')
Notification.where(from_account_id: nil).where(activity_type: 'Follow').update_all('from_account_id = (SELECT follows.account_id FROM notifications AS notifications1 INNER JOIN follows ON notifications1.activity_id = follows.id WHERE notifications1.activity_type = \'Follow\' AND notifications1.id = notifications.id)')
end
def down
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment