diff --git a/app/javascript/gabsocial/actions/streaming.js b/app/javascript/gabsocial/actions/streaming.js index 5e2d355a..e599805e 100644 --- a/app/javascript/gabsocial/actions/streaming.js +++ b/app/javascript/gabsocial/actions/streaming.js @@ -1,10 +1,10 @@ import { connectStream } from '../stream'; import { - updateTimeline, deleteFromTimelines, expandHomeTimeline, connectTimeline, disconnectTimeline, + updateTimelineQueue, } from './timelines'; import { updateNotificationsQueue, expandNotifications } from './notifications'; import { updateConversations } from './conversations'; @@ -30,7 +30,7 @@ export function connectTimelineStream (timelineId, path, pollingRefresh = null, onReceive (data) { switch(data.event) { case 'update': - dispatch(updateTimeline(timelineId, JSON.parse(data.payload), accept)); + dispatch(updateTimelineQueue(timelineId, JSON.parse(data.payload), accept)); break; case 'delete': dispatch(deleteFromTimelines(data.payload)); diff --git a/app/javascript/gabsocial/actions/timelines.js b/app/javascript/gabsocial/actions/timelines.js index ee57145e..4214cbde 100644 --- a/app/javascript/gabsocial/actions/timelines.js +++ b/app/javascript/gabsocial/actions/timelines.js @@ -1,10 +1,12 @@ import { importFetchedStatus, importFetchedStatuses } from './importer'; import api, { getLinks } from '../api'; -import { Map as ImmutableMap, List as ImmutableList } from 'immutable'; +import { Map as ImmutableMap, List as ImmutableList, toJS } from 'immutable'; export const TIMELINE_UPDATE = 'TIMELINE_UPDATE'; export const TIMELINE_DELETE = 'TIMELINE_DELETE'; export const TIMELINE_CLEAR = 'TIMELINE_CLEAR'; +export const TIMELINE_UPDATE_QUEUE = 'TIMELINE_UPDATE_QUEUE'; +export const TIMELINE_DEQUEUE = 'TIMELINE_DEQUEUE'; export const TIMELINE_EXPAND_REQUEST = 'TIMELINE_EXPAND_REQUEST'; export const TIMELINE_EXPAND_SUCCESS = 'TIMELINE_EXPAND_SUCCESS'; @@ -13,6 +15,8 @@ export const TIMELINE_EXPAND_FAIL = 'TIMELINE_EXPAND_FAIL'; export const TIMELINE_CONNECT = 'TIMELINE_CONNECT'; export const TIMELINE_DISCONNECT = 'TIMELINE_DISCONNECT'; +export const MAX_QUEUED_ITEMS = 40; + export function updateTimeline(timeline, status, accept) { return dispatch => { if (typeof accept === 'function' && !accept(status)) { @@ -29,6 +33,64 @@ export function updateTimeline(timeline, status, accept) { }; }; +export function updateTimelineQueue(timeline, status, accept) { + return dispatch => { + if (typeof accept === 'function' && !accept(status)) { + return; + } + + dispatch({ + type: TIMELINE_UPDATE_QUEUE, + timeline, + status, + }); + } +}; + +export function dequeueTimeline(timeline, expandFunc, optionalExpandArgs) { + return (dispatch, getState) => { + const queuedItems = getState().getIn(['timelines', timeline, 'queuedItems'], ImmutableList()); + const totalQueuedItemsCount = getState().getIn(['timelines', timeline, 'totalQueuedItemsCount'], 0); + + let shouldDispatchDequeue = true; + + if (totalQueuedItemsCount == 0) { + return; + } + else if (totalQueuedItemsCount > 0 && totalQueuedItemsCount <= MAX_QUEUED_ITEMS) { + queuedItems.forEach(status => { + dispatch(updateTimeline(timeline, status.toJS(), null)); + }); + } + else { + if (typeof expandFunc === 'function') { + dispatch(clearTimeline(timeline)); + expandFunc(); + } + else { + if (timeline === 'home') { + dispatch(clearTimeline(timeline)); + dispatch(expandHomeTimeline(optionalExpandArgs)); + } + else if (timeline === 'community') { + dispatch(clearTimeline(timeline)); + dispatch(expandCommunityTimeline(optionalExpandArgs)); + } + else { + shouldDispatchDequeue = false; + } + } + } + + if (!shouldDispatchDequeue) return; + + dispatch({ + type: TIMELINE_DEQUEUE, + timeline, + }); + } +}; + export function deleteFromTimelines(id) { return (dispatch, getState) => { const accountId = getState().getIn(['statuses', id, 'account']); diff --git a/app/javascript/gabsocial/reducers/timelines.js b/app/javascript/gabsocial/reducers/timelines.js index 8e20bdf3..b0c1babf 100644 --- a/app/javascript/gabsocial/reducers/timelines.js +++ b/app/javascript/gabsocial/reducers/timelines.js @@ -7,6 +7,9 @@ import { TIMELINE_EXPAND_FAIL, TIMELINE_CONNECT, TIMELINE_DISCONNECT, + TIMELINE_UPDATE_QUEUE, + TIMELINE_DEQUEUE, + MAX_QUEUED_ITEMS, } from '../actions/timelines'; import { ACCOUNT_BLOCK_SUCCESS, @@ -25,6 +28,8 @@ const initialTimeline = ImmutableMap({ isLoading: false, hasMore: true, items: ImmutableList(), + queuedItems: ImmutableList(), //max= MAX_QUEUED_ITEMS + totalQueuedItemsCount: 0, //used for queuedItems overflow for MAX_QUEUED_ITEMS+ }); const expandNormalizedTimeline = (state, timeline, statuses, next, isPartial, isLoadingRecent) => { @@ -77,6 +82,28 @@ const updateTimeline = (state, timeline, status) => { })); }; +const updateTimelineQueue = (state, timeline, status) => { + const queuedStatuses = state.getIn([timeline, 'queuedItems'], ImmutableList()); + const listedStatuses = state.getIn([timeline, 'items'], ImmutableList()); + const totalQueuedItemsCount = state.getIn([timeline, 'totalQueuedItemsCount'], 0); + + let alreadyExists = queuedStatuses.find(existingQueuedStatus => existingQueuedStatus.get('id') === status.get('id')); + if (!alreadyExists) alreadyExists = listedStatuses.find(existingListedStatusId => existingListedStatusId === status.get('id')); + + if (alreadyExists) { + return state; + } + + let newQueuedStatuses = queuedStatuses; + + return state.update(timeline, initialTimeline, map => map.withMutations(mMap => { + if (totalQueuedItemsCount <= MAX_QUEUED_ITEMS) { + mMap.set('queuedItems', newQueuedStatuses.push(status)); + } + mMap.set('totalQueuedItemsCount', totalQueuedItemsCount + 1); + })); +}; + const deleteStatus = (state, id, accountId, references, exclude_account = null) => { state.keySeq().forEach(timeline => { if (exclude_account === null || (timeline !== `account:${exclude_account}` && !timeline.startsWith(`account:${exclude_account}:`))) @@ -126,6 +153,13 @@ export default function timelines(state = initialState, action) { return expandNormalizedTimeline(state, action.timeline, fromJS(action.statuses), action.next, action.partial, action.isLoadingRecent); case TIMELINE_UPDATE: return updateTimeline(state, action.timeline, fromJS(action.status)); + case TIMELINE_UPDATE_QUEUE: + return updateTimelineQueue(state, action.timeline, fromJS(action.status)); + case TIMELINE_DEQUEUE: + return state.update(action.timeline, initialTimeline, map => map.withMutations(mMap => { + mMap.set('queuedItems', ImmutableList()) + mMap.set('totalQueuedItemsCount', 0) + })); case TIMELINE_DELETE: return deleteStatus(state, action.id, action.accountId, action.references, action.reblogOf); case TIMELINE_CLEAR: