Skip to content

Commit

Permalink
[Glitch] Add option to disable real-time updates in web UI
Browse files Browse the repository at this point in the history
Port 729723f to glitch-soc

Signed-off-by: Thibaut Girka <thib@sitedethib.com>
  • Loading branch information
Gargron authored and ClearlyClaire committed Jul 16, 2019
1 parent c8a4759 commit e91bf82
Show file tree
Hide file tree
Showing 12 changed files with 154 additions and 51 deletions.
32 changes: 24 additions & 8 deletions app/javascript/flavours/glitch/actions/notifications.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import { defineMessages } from 'react-intl';
import { List as ImmutableList } from 'immutable';
import { unescapeHTML } from 'flavours/glitch/util/html';
import { getFiltersRegex } from 'flavours/glitch/selectors';
import { usePendingItems as preferPendingItems } from 'flavours/glitch/util/initial_state';
import compareId from 'flavours/glitch/util/compare_id';

export const NOTIFICATIONS_UPDATE = 'NOTIFICATIONS_UPDATE';

Expand All @@ -32,8 +34,9 @@ export const NOTIFICATIONS_EXPAND_FAIL = 'NOTIFICATIONS_EXPAND_FAIL';

export const NOTIFICATIONS_FILTER_SET = 'NOTIFICATIONS_FILTER_SET';

export const NOTIFICATIONS_CLEAR = 'NOTIFICATIONS_CLEAR';
export const NOTIFICATIONS_SCROLL_TOP = 'NOTIFICATIONS_SCROLL_TOP';
export const NOTIFICATIONS_CLEAR = 'NOTIFICATIONS_CLEAR';
export const NOTIFICATIONS_SCROLL_TOP = 'NOTIFICATIONS_SCROLL_TOP';
export const NOTIFICATIONS_LOAD_PENDING = 'NOTIFICATIONS_LOAD_PENDING';

export const NOTIFICATIONS_MOUNT = 'NOTIFICATIONS_MOUNT';
export const NOTIFICATIONS_UNMOUNT = 'NOTIFICATIONS_UNMOUNT';
Expand All @@ -52,6 +55,10 @@ const fetchRelatedRelationships = (dispatch, notifications) => {
}
};

export const loadPending = () => ({
type: NOTIFICATIONS_LOAD_PENDING,
});

export function updateNotifications(notification, intlMessages, intlLocale) {
return (dispatch, getState) => {
const showInColumn = getState().getIn(['settings', 'notifications', 'shows', notification.type], true);
Expand Down Expand Up @@ -83,6 +90,7 @@ export function updateNotifications(notification, intlMessages, intlLocale) {
dispatch({
type: NOTIFICATIONS_UPDATE,
notification,
usePendingItems: preferPendingItems,
meta: (playSound && !filtered) ? { sound: 'boop' } : undefined,
});

Expand Down Expand Up @@ -136,10 +144,19 @@ export function expandNotifications({ maxId } = {}, done = noOp) {
: excludeTypesFromFilter(activeFilter),
};

if (!maxId && notifications.get('items').size > 0) {
params.since_id = notifications.getIn(['items', 0, 'id']);
if (!params.max_id && (notifications.get('items', ImmutableList()).size + notifications.get('pendingItems', ImmutableList()).size) > 0) {
const a = notifications.getIn(['pendingItems', 0, 'id']);
const b = notifications.getIn(['items', 0, 'id']);

if (a && b && compareId(a, b) > 0) {
params.since_id = a;
} else {
params.since_id = b || a;
}
}

const isLoadingRecent = !!params.since_id;

dispatch(expandNotificationsRequest(isLoadingMore));

api(getState).get('/api/v1/notifications', { params }).then(response => {
Expand All @@ -148,7 +165,7 @@ export function expandNotifications({ maxId } = {}, done = noOp) {
dispatch(importFetchedAccounts(response.data.map(item => item.account)));
dispatch(importFetchedStatuses(response.data.map(item => item.status).filter(status => !!status)));

dispatch(expandNotificationsSuccess(response.data, next ? next.uri : null, isLoadingMore));
dispatch(expandNotificationsSuccess(response.data, next ? next.uri : null, isLoadingMore, isLoadingRecent && preferPendingItems));
fetchRelatedRelationships(dispatch, response.data);
done();
}).catch(error => {
Expand All @@ -165,13 +182,12 @@ export function expandNotificationsRequest(isLoadingMore) {
};
};

export function expandNotificationsSuccess(notifications, next, isLoadingMore) {
export function expandNotificationsSuccess(notifications, next, isLoadingMore, usePendingItems) {
return {
type: NOTIFICATIONS_EXPAND_SUCCESS,
notifications,
accounts: notifications.map(item => item.account),
statuses: notifications.map(item => item.status).filter(status => !!status),
next,
usePendingItems,
skipLoading: !isLoadingMore,
};
};
Expand Down
41 changes: 28 additions & 13 deletions app/javascript/flavours/glitch/actions/timelines.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { importFetchedStatus, importFetchedStatuses } from './importer';
import api, { getLinks } from 'flavours/glitch/util/api';
import { Map as ImmutableMap, List as ImmutableList } from 'immutable';
import compareId from 'flavours/glitch/util/compare_id';
import { usePendingItems as preferPendingItems } from 'flavours/glitch/util/initial_state';

export const TIMELINE_UPDATE = 'TIMELINE_UPDATE';
export const TIMELINE_DELETE = 'TIMELINE_DELETE';
Expand All @@ -10,10 +12,15 @@ 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 const TIMELINE_SCROLL_TOP = 'TIMELINE_SCROLL_TOP';
export const TIMELINE_SCROLL_TOP = 'TIMELINE_SCROLL_TOP';
export const TIMELINE_LOAD_PENDING = 'TIMELINE_LOAD_PENDING';
export const TIMELINE_DISCONNECT = 'TIMELINE_DISCONNECT';
export const TIMELINE_CONNECT = 'TIMELINE_CONNECT';

export const TIMELINE_CONNECT = 'TIMELINE_CONNECT';
export const TIMELINE_DISCONNECT = 'TIMELINE_DISCONNECT';
export const loadPending = timeline => ({
type: TIMELINE_LOAD_PENDING,
timeline,
});

export function updateTimeline(timeline, status, accept) {
return dispatch => {
Expand All @@ -27,6 +34,7 @@ export function updateTimeline(timeline, status, accept) {
type: TIMELINE_UPDATE,
timeline,
status,
usePendingItems: preferPendingItems,
});
};
};
Expand Down Expand Up @@ -71,8 +79,15 @@ export function expandTimeline(timelineId, path, params = {}, done = noOp) {
return;
}

if (!params.max_id && !params.pinned && timeline.get('items', ImmutableList()).size > 0) {
params.since_id = timeline.getIn(['items', 0]);
if (!params.max_id && !params.pinned && (timeline.get('items', ImmutableList()).size + timeline.get('pendingItems', ImmutableList()).size) > 0) {
const a = timeline.getIn(['pendingItems', 0]);
const b = timeline.getIn(['items', 0]);

if (a && b && compareId(a, b) > 0) {
params.since_id = a;
} else {
params.since_id = b || a;
}
}

const isLoadingRecent = !!params.since_id;
Expand All @@ -82,7 +97,7 @@ export function expandTimeline(timelineId, path, params = {}, done = noOp) {
api(getState).get(path, { params }).then(response => {
const next = getLinks(response).refs.find(link => link.rel === 'next');
dispatch(importFetchedStatuses(response.data));
dispatch(expandTimelineSuccess(timelineId, response.data, next ? next.uri : null, response.code === 206, isLoadingRecent, isLoadingMore));
dispatch(expandTimelineSuccess(timelineId, response.data, next ? next.uri : null, response.code === 206, isLoadingRecent, isLoadingMore, isLoadingRecent && preferPendingItems));
done();
}).catch(error => {
dispatch(expandTimelineFail(timelineId, error, isLoadingMore));
Expand Down Expand Up @@ -117,14 +132,15 @@ export function expandTimelineRequest(timeline, isLoadingMore) {
};
};

export function expandTimelineSuccess(timeline, statuses, next, partial, isLoadingRecent, isLoadingMore) {
export function expandTimelineSuccess(timeline, statuses, next, partial, isLoadingRecent, isLoadingMore, usePendingItems) {
return {
type: TIMELINE_EXPAND_SUCCESS,
timeline,
statuses,
next,
partial,
isLoadingRecent,
usePendingItems,
skipLoading: !isLoadingMore,
};
};
Expand Down Expand Up @@ -153,9 +169,8 @@ export function connectTimeline(timeline) {
};
};

export function disconnectTimeline(timeline) {
return {
type: TIMELINE_DISCONNECT,
timeline,
};
};
export const disconnectTimeline = timeline => ({
type: TIMELINE_DISCONNECT,
timeline,
usePendingItems: preferPendingItems,
});
22 changes: 22 additions & 0 deletions app/javascript/flavours/glitch/components/load_pending.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import React from 'react';
import { FormattedMessage } from 'react-intl';
import PropTypes from 'prop-types';

export default class LoadPending extends React.PureComponent {

static propTypes = {
onClick: PropTypes.func,
count: PropTypes.number,
}

render() {
const { count } = this.props;

return (
<button className='load-more load-gap' onClick={this.props.onClick}>
<FormattedMessage id='load_pending' defaultMessage='{count, plural, one {# new item} other {# new items}}' values={{ count }} />
</button>
);
}

}
13 changes: 12 additions & 1 deletion app/javascript/flavours/glitch/components/scrollable_list.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { ScrollContainer } from 'react-router-scroll-4';
import PropTypes from 'prop-types';
import IntersectionObserverArticleContainer from 'flavours/glitch/containers/intersection_observer_article_container';
import LoadMore from './load_more';
import LoadPending from './load_pending';
import IntersectionObserverWrapper from 'flavours/glitch/util/intersection_observer_wrapper';
import { throttle } from 'lodash';
import { List as ImmutableList } from 'immutable';
Expand All @@ -21,13 +22,15 @@ export default class ScrollableList extends PureComponent {
static propTypes = {
scrollKey: PropTypes.string.isRequired,
onLoadMore: PropTypes.func,
onLoadPending: PropTypes.func,
onScrollToTop: PropTypes.func,
onScroll: PropTypes.func,
trackScroll: PropTypes.bool,
shouldUpdateScroll: PropTypes.func,
isLoading: PropTypes.bool,
showLoading: PropTypes.bool,
hasMore: PropTypes.bool,
numPending: PropTypes.number,
prepend: PropTypes.node,
alwaysPrepend: PropTypes.bool,
emptyMessage: PropTypes.node,
Expand Down Expand Up @@ -222,12 +225,18 @@ export default class ScrollableList extends PureComponent {
return !(location.state && location.state.mastodonModalOpen);
}

handleLoadPending = e => {
e.preventDefault();
this.props.onLoadPending();
}

render () {
const { children, scrollKey, trackScroll, shouldUpdateScroll, showLoading, isLoading, hasMore, prepend, alwaysPrepend, emptyMessage, onLoadMore } = this.props;
const { children, scrollKey, trackScroll, shouldUpdateScroll, showLoading, isLoading, hasMore, numPending, prepend, alwaysPrepend, emptyMessage, onLoadMore } = this.props;
const { fullscreen } = this.state;
const childrenCount = React.Children.count(children);

const loadMore = (hasMore && onLoadMore) ? <LoadMore visible={!isLoading} onClick={this.handleLoadMore} /> : null;
const loadPending = (numPending > 0) ? <LoadPending count={numPending} onClick={this.handleLoadPending} /> : null;
let scrollableArea = null;

if (showLoading) {
Expand All @@ -248,6 +257,8 @@ export default class ScrollableList extends PureComponent {
<div role='feed' className='item-list'>
{prepend}

{loadPending}

{React.Children.map(this.props.children, (child, index) => (
<IntersectionObserverArticleContainer
key={child.key}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export default class ColumnSettings extends React.PureComponent {
return (
<div>
<div className='column-settings__row'>
<SettingToggle settings={settings} settingPath={['other', 'onlyMedia']} onChange={onChange} label={<FormattedMessage id='community.column_settings.media_only' defaultMessage='Media Only' />} />
<SettingToggle settings={settings} settingPath={['other', 'onlyMedia']} onChange={onChange} label={<FormattedMessage id='community.column_settings.media_only' defaultMessage='Media only' />} />
</div>

<span className='column-settings__section'><FormattedMessage id='home.column_settings.advanced' defaultMessage='Advanced' /></span>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,20 @@ export default class SettingToggle extends React.PureComponent {
label: PropTypes.node.isRequired,
meta: PropTypes.node,
onChange: PropTypes.func.isRequired,
defaultValue: PropTypes.bool,
}

onChange = ({ target }) => {
this.props.onChange(this.props.settingPath, target.checked);
}

render () {
const { prefix, settings, settingPath, label, meta } = this.props;
const { prefix, settings, settingPath, label, meta, defaultValue } = this.props;
const id = ['setting-toggle', prefix, ...settingPath].filter(Boolean).join('-');

return (
<div className='setting-toggle'>
<Toggle id={id} checked={settings.getIn(settingPath)} onChange={this.onChange} onKeyDown={this.onKeyDown} />
<Toggle id={id} checked={settings.getIn(settingPath, defaultValue)} onChange={this.onChange} onKeyDown={this.onKeyDown} />
<label htmlFor={id} className='setting-toggle__label'>{label}</label>
{meta && <span className='setting-meta__label'>{meta}</span>}
</div>
Expand Down
11 changes: 10 additions & 1 deletion app/javascript/flavours/glitch/features/notifications/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
scrollTopNotifications,
mountNotifications,
unmountNotifications,
loadPending,
} from 'flavours/glitch/actions/notifications';
import { addColumn, removeColumn, moveColumn } from 'flavours/glitch/actions/columns';
import NotificationContainer from './containers/notification_container';
Expand Down Expand Up @@ -48,6 +49,7 @@ const mapStateToProps = state => ({
isLoading: state.getIn(['notifications', 'isLoading'], true),
isUnread: state.getIn(['notifications', 'unread']) > 0,
hasMore: state.getIn(['notifications', 'hasMore']),
numPending: state.getIn(['notifications', 'pendingItems'], ImmutableList()).size,
notifCleaningActive: state.getIn(['notifications', 'cleaningMode']),
});

Expand Down Expand Up @@ -80,6 +82,7 @@ export default class Notifications extends React.PureComponent {
isUnread: PropTypes.bool,
multiColumn: PropTypes.bool,
hasMore: PropTypes.bool,
numPending: PropTypes.number,
localSettings: ImmutablePropTypes.map,
notifCleaningActive: PropTypes.bool,
onEnterCleaningMode: PropTypes.func,
Expand All @@ -100,6 +103,10 @@ export default class Notifications extends React.PureComponent {
this.props.dispatch(expandNotifications({ maxId: last && last.get('id') }));
}, 300, { leading: true });

handleLoadPending = () => {
this.props.dispatch(loadPending());
};

handleScrollToTop = debounce(() => {
this.props.dispatch(scrollTopNotifications(true));
}, 100);
Expand Down Expand Up @@ -170,7 +177,7 @@ export default class Notifications extends React.PureComponent {
}

render () {
const { intl, notifications, shouldUpdateScroll, isLoading, isUnread, columnId, multiColumn, hasMore, showFilterBar } = this.props;
const { intl, notifications, shouldUpdateScroll, isLoading, isUnread, columnId, multiColumn, hasMore, numPending, showFilterBar } = this.props;
const pinned = !!columnId;
const emptyMessage = <FormattedMessage id='empty_column.notifications' defaultMessage="You don't have any notifications yet. Interact with others to start the conversation." />;

Expand Down Expand Up @@ -212,8 +219,10 @@ export default class Notifications extends React.PureComponent {
isLoading={isLoading}
showLoading={isLoading && notifications.size === 0}
hasMore={hasMore}
numPending={numPending}
emptyMessage={emptyMessage}
onLoadMore={this.handleLoadOlder}
onLoadPending={this.handleLoadPending}
onScrollToTop={this.handleScrollToTop}
onScroll={this.handleScroll}
shouldUpdateScroll={shouldUpdateScroll}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { connect } from 'react-redux';
import StatusList from 'flavours/glitch/components/status_list';
import { scrollTopTimeline } from 'flavours/glitch/actions/timelines';
import { scrollTopTimeline, loadPending } from 'flavours/glitch/actions/timelines';
import { Map as ImmutableMap, List as ImmutableList } from 'immutable';
import { createSelector } from 'reselect';
import { debounce } from 'lodash';
Expand Down Expand Up @@ -62,6 +62,7 @@ const makeMapStateToProps = () => {
isLoading: state.getIn(['timelines', timelineId, 'isLoading'], true),
isPartial: state.getIn(['timelines', timelineId, 'isPartial'], false),
hasMore: state.getIn(['timelines', timelineId, 'hasMore']),
numPending: state.getIn(['timelines', timelineId, 'pendingItems'], ImmutableList()).size,
});

return mapStateToProps;
Expand All @@ -77,6 +78,8 @@ const mapDispatchToProps = (dispatch, { timelineId }) => ({
dispatch(scrollTopTimeline(timelineId, false));
}, 100),

onLoadPending: () => dispatch(loadPending(timelineId)),

});

export default connect(makeMapStateToProps, mapDispatchToProps)(StatusList);
Loading

0 comments on commit e91bf82

Please sign in to comment.