diff --git a/app/javascript/flavours/glitch/actions/timelines.js b/app/javascript/flavours/glitch/actions/timelines.js
index 9a5b2e6da9..3a9d64084c 100644
--- a/app/javascript/flavours/glitch/actions/timelines.js
+++ b/app/javascript/flavours/glitch/actions/timelines.js
@@ -19,13 +19,14 @@ export const TIMELINE_DISCONNECT = 'TIMELINE_DISCONNECT';
export const TIMELINE_CONTEXT_UPDATE = 'CONTEXT_UPDATE';
-export function refreshTimelineSuccess(timeline, statuses, skipLoading, next) {
+export function refreshTimelineSuccess(timeline, statuses, skipLoading, next, partial) {
return {
type: TIMELINE_REFRESH_SUCCESS,
timeline,
statuses,
skipLoading,
next,
+ partial,
};
};
@@ -88,7 +89,7 @@ export function refreshTimeline(timelineId, path, params = {}) {
return function (dispatch, getState) {
const timeline = getState().getIn(['timelines', timelineId], ImmutableMap());
- if (timeline.get('isLoading') || timeline.get('online')) {
+ if (timeline.get('isLoading') || (timeline.get('online') && !timeline.get('isPartial'))) {
return;
}
@@ -104,8 +105,12 @@ export function refreshTimeline(timelineId, path, params = {}) {
dispatch(refreshTimelineRequest(timelineId, skipLoading));
api(getState).get(path, { params }).then(response => {
- const next = getLinks(response).refs.find(link => link.rel === 'next');
- dispatch(refreshTimelineSuccess(timelineId, response.data, skipLoading, next ? next.uri : null));
+ if (response.status === 206) {
+ dispatch(refreshTimelineSuccess(timelineId, [], skipLoading, null, true));
+ } else {
+ const next = getLinks(response).refs.find(link => link.rel === 'next');
+ dispatch(refreshTimelineSuccess(timelineId, response.data, skipLoading, next ? next.uri : null, false));
+ }
}).catch(error => {
dispatch(refreshTimelineFail(timelineId, error, skipLoading));
});
diff --git a/app/javascript/flavours/glitch/components/missing_indicator.js b/app/javascript/flavours/glitch/components/missing_indicator.js
index 87df7f61ce..70d8c3b984 100644
--- a/app/javascript/flavours/glitch/components/missing_indicator.js
+++ b/app/javascript/flavours/glitch/components/missing_indicator.js
@@ -2,9 +2,14 @@ import React from 'react';
import { FormattedMessage } from 'react-intl';
const MissingIndicator = () => (
-
+
);
diff --git a/app/javascript/flavours/glitch/components/status_list.js b/app/javascript/flavours/glitch/components/status_list.js
index f190ba6ab6..f253f0fdc1 100644
--- a/app/javascript/flavours/glitch/components/status_list.js
+++ b/app/javascript/flavours/glitch/components/status_list.js
@@ -4,6 +4,7 @@ import PropTypes from 'prop-types';
import StatusContainer from 'flavours/glitch/containers/status_container';
import ImmutablePureComponent from 'react-immutable-pure-component';
import ScrollableList from './scrollable_list';
+import { FormattedMessage } from 'react-intl';
export default class StatusList extends ImmutablePureComponent {
@@ -16,6 +17,7 @@ export default class StatusList extends ImmutablePureComponent {
trackScroll: PropTypes.bool,
shouldUpdateScroll: PropTypes.func,
isLoading: PropTypes.bool,
+ isPartial: PropTypes.bool,
hasMore: PropTypes.bool,
prepend: PropTypes.node,
emptyMessage: PropTypes.node,
@@ -48,8 +50,23 @@ export default class StatusList extends ImmutablePureComponent {
}
render () {
- const { statusIds, ...other } = this.props;
- const { isLoading } = other;
+ const { statusIds, ...other } = this.props;
+ const { isLoading, isPartial } = other;
+
+ if (isPartial) {
+ return (
+
+ );
+ }
const scrollableContent = (isLoading || statusIds.size > 0) ? (
statusIds.map((statusId) => (
diff --git a/app/javascript/flavours/glitch/features/home_timeline/index.js b/app/javascript/flavours/glitch/features/home_timeline/index.js
index 2dfec6bbed..c20c0244a6 100644
--- a/app/javascript/flavours/glitch/features/home_timeline/index.js
+++ b/app/javascript/flavours/glitch/features/home_timeline/index.js
@@ -1,6 +1,6 @@
import React from 'react';
import { connect } from 'react-redux';
-import { expandHomeTimeline } from 'flavours/glitch/actions/timelines';
+import { expandHomeTimeline, refreshHomeTimeline } from 'flavours/glitch/actions/timelines';
import PropTypes from 'prop-types';
import StatusListContainer from 'flavours/glitch/features/ui/containers/status_list_container';
import Column from 'flavours/glitch/components/column';
@@ -16,6 +16,7 @@ const messages = defineMessages({
const mapStateToProps = state => ({
hasUnread: state.getIn(['timelines', 'home', 'unread']) > 0,
+ isPartial: state.getIn(['timelines', 'home', 'isPartial'], false),
});
@connect(mapStateToProps)
@@ -26,6 +27,7 @@ export default class HomeTimeline extends React.PureComponent {
dispatch: PropTypes.func.isRequired,
intl: PropTypes.object.isRequired,
hasUnread: PropTypes.bool,
+ isPartial: PropTypes.bool,
columnId: PropTypes.string,
multiColumn: PropTypes.bool,
};
@@ -57,6 +59,39 @@ export default class HomeTimeline extends React.PureComponent {
this.props.dispatch(expandHomeTimeline());
}
+ componentDidMount () {
+ this._checkIfReloadNeeded(false, this.props.isPartial);
+ }
+
+ componentDidUpdate (prevProps) {
+ this._checkIfReloadNeeded(prevProps.isPartial, this.props.isPartial);
+ }
+
+ componentWillUnmount () {
+ this._stopPolling();
+ }
+
+ _checkIfReloadNeeded (wasPartial, isPartial) {
+ const { dispatch } = this.props;
+
+ if (wasPartial === isPartial) {
+ return;
+ } else if (!wasPartial && isPartial) {
+ this.polling = setInterval(() => {
+ dispatch(refreshHomeTimeline());
+ }, 3000);
+ } else if (wasPartial && !isPartial) {
+ this._stopPolling();
+ }
+ }
+
+ _stopPolling () {
+ if (this.polling) {
+ clearInterval(this.polling);
+ this.polling = null;
+ }
+ }
+
render () {
const { intl, hasUnread, columnId, multiColumn } = this.props;
const pinned = !!columnId;
diff --git a/app/javascript/flavours/glitch/features/list_timeline/index.js b/app/javascript/flavours/glitch/features/list_timeline/index.js
index c6a89a9202..f9476d92d5 100644
--- a/app/javascript/flavours/glitch/features/list_timeline/index.js
+++ b/app/javascript/flavours/glitch/features/list_timeline/index.js
@@ -120,13 +120,17 @@ export default class ListTimeline extends React.PureComponent {
if (typeof list === 'undefined') {
return (
-
+
+
+
);
} else if (list === false) {
return (
-
+
+
+
);
}
diff --git a/app/javascript/flavours/glitch/features/ui/containers/status_list_container.js b/app/javascript/flavours/glitch/features/ui/containers/status_list_container.js
index eca85b8e63..f85a2eeb8a 100644
--- a/app/javascript/flavours/glitch/features/ui/containers/status_list_container.js
+++ b/app/javascript/flavours/glitch/features/ui/containers/status_list_container.js
@@ -51,6 +51,7 @@ const makeMapStateToProps = () => {
const mapStateToProps = (state, { timelineId }) => ({
statusIds: getStatusIds(state, { type: timelineId }),
isLoading: state.getIn(['timelines', timelineId, 'isLoading'], true),
+ isPartial: state.getIn(['timelines', timelineId, 'isPartial'], false),
hasMore: !!state.getIn(['timelines', timelineId, 'next']),
});
diff --git a/app/javascript/flavours/glitch/images/elephant_ui_disappointed.svg b/app/javascript/flavours/glitch/images/elephant_ui_disappointed.svg
new file mode 100644
index 0000000000..580c15a138
--- /dev/null
+++ b/app/javascript/flavours/glitch/images/elephant_ui_disappointed.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/app/javascript/flavours/glitch/images/elephant_ui_working.svg b/app/javascript/flavours/glitch/images/elephant_ui_working.svg
new file mode 100644
index 0000000000..8ba475db0a
--- /dev/null
+++ b/app/javascript/flavours/glitch/images/elephant_ui_working.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/app/javascript/flavours/glitch/reducers/timelines.js b/app/javascript/flavours/glitch/reducers/timelines.js
index 679e1601eb..4c62d8df39 100644
--- a/app/javascript/flavours/glitch/reducers/timelines.js
+++ b/app/javascript/flavours/glitch/reducers/timelines.js
@@ -30,7 +30,7 @@ const initialTimeline = ImmutableMap({
items: ImmutableList(),
});
-const normalizeTimeline = (state, timeline, statuses, next) => {
+const normalizeTimeline = (state, timeline, statuses, next, isPartial) => {
const oldIds = state.getIn([timeline, 'items'], ImmutableList());
const ids = ImmutableList(statuses.map(status => status.get('id'))).filter(newId => !oldIds.includes(newId));
const wasLoaded = state.getIn([timeline, 'loaded']);
@@ -41,6 +41,7 @@ const normalizeTimeline = (state, timeline, statuses, next) => {
mMap.set('isLoading', false);
if (!hadNext) mMap.set('next', next);
mMap.set('items', wasLoaded ? ids.concat(oldIds) : ids);
+ mMap.set('isPartial', isPartial);
}));
};
@@ -125,7 +126,7 @@ export default function timelines(state = initialState, action) {
case TIMELINE_EXPAND_FAIL:
return state.update(action.timeline, initialTimeline, map => map.set('isLoading', false));
case TIMELINE_REFRESH_SUCCESS:
- return normalizeTimeline(state, action.timeline, fromJS(action.statuses), action.next);
+ return normalizeTimeline(state, action.timeline, fromJS(action.statuses), action.next, action.partial);
case TIMELINE_EXPAND_SUCCESS:
return appendNormalizedTimeline(state, action.timeline, fromJS(action.statuses), action.next);
case TIMELINE_UPDATE:
diff --git a/app/javascript/flavours/glitch/styles/components/index.scss b/app/javascript/flavours/glitch/styles/components/index.scss
index 8f051f7a05..8f06209c61 100644
--- a/app/javascript/flavours/glitch/styles/components/index.scss
+++ b/app/javascript/flavours/glitch/styles/components/index.scss
@@ -838,21 +838,10 @@
}
.missing-indicator {
- text-align: center;
- font-size: 16px;
- font-weight: 500;
- color: lighten($ui-base-color, 16%);
- background: $ui-base-color;
- cursor: default;
- display: flex;
- flex: 1 1 auto;
- align-items: center;
- justify-content: center;
+ padding-top: 20px + 48px;
- & > div {
- background: url('~images/mastodon-not-found.png') no-repeat center -50px;
- padding-top: 210px;
- width: 100%;
+ .regeneration-indicator__figure {
+ background-image: url('~flavours/glitch/images/elephant_ui_disappointed.svg');
}
}
@@ -1162,6 +1151,7 @@ noscript {
@import 'metadata';
@import 'composer';
@import 'columns';
+@import 'regeneration_indicator';
@import 'search';
@import 'emoji';
@import 'doodle';
diff --git a/app/javascript/flavours/glitch/styles/components/regeneration_indicator.scss b/app/javascript/flavours/glitch/styles/components/regeneration_indicator.scss
new file mode 100644
index 0000000000..9c1873cdff
--- /dev/null
+++ b/app/javascript/flavours/glitch/styles/components/regeneration_indicator.scss
@@ -0,0 +1,53 @@
+.regeneration-indicator {
+ text-align: center;
+ font-size: 16px;
+ font-weight: 500;
+ color: lighten($ui-base-color, 16%);
+ background: $ui-base-color;
+ cursor: default;
+ display: flex;
+ flex: 1 1 auto;
+ align-items: center;
+ justify-content: center;
+ padding: 20px;
+
+ & > div {
+ width: 100%;
+ background: transparent;
+ padding-top: 0;
+ }
+
+ &__figure {
+ background: url('~flavours/glitch/images/elephant_ui_working.svg') no-repeat center 0;
+ width: 100%;
+ height: 160px;
+ background-size: contain;
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ }
+
+ &.missing-indicator {
+ padding-top: 20px + 48px;
+
+ .regeneration-indicator__figure {
+ background-image: url('~flavours/glitch/images/elephant_ui_disappointed.svg');
+ }
+ }
+
+ &__label {
+ margin-top: 200px;
+
+ strong {
+ display: block;
+ margin-bottom: 10px;
+ color: lighten($ui-base-color, 34%);
+ }
+
+ span {
+ font-size: 15px;
+ font-weight: 400;
+ }
+ }
+}