forked from treehouse/mastodon
Port 7badad7797
to glitch frontend
parent
1964a0f941
commit
bcd86404da
|
@ -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));
|
||||
});
|
||||
|
|
|
@ -2,9 +2,14 @@ import React from 'react';
|
|||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
const MissingIndicator = () => (
|
||||
<div className='missing-indicator'>
|
||||
<div className='regeneration-indicator missing-indicator'>
|
||||
<div>
|
||||
<FormattedMessage id='missing_indicator.label' defaultMessage='Not found' />
|
||||
<div className='regeneration-indicator__figure' />
|
||||
|
||||
<div className='regeneration-indicator__label'>
|
||||
<FormattedMessage id='missing_indicator.label' tagName='strong' defaultMessage='Not found' />
|
||||
<FormattedMessage id='missing_indicator.sublabel' defaultMessage='This resource could not be found' />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -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 (
|
||||
<div className='regeneration-indicator'>
|
||||
<div>
|
||||
<div className='regeneration-indicator__figure' />
|
||||
|
||||
<div className='regeneration-indicator__label'>
|
||||
<FormattedMessage id='regeneration_indicator.label' tagName='strong' defaultMessage='Loading…' />
|
||||
<FormattedMessage id='regeneration_indicator.sublabel' defaultMessage='Your home feed is being prepared!' />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const scrollableContent = (isLoading || statusIds.size > 0) ? (
|
||||
statusIds.map((statusId) => (
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -120,13 +120,17 @@ export default class ListTimeline extends React.PureComponent {
|
|||
if (typeof list === 'undefined') {
|
||||
return (
|
||||
<Column>
|
||||
<LoadingIndicator />
|
||||
<div className='scrollable'>
|
||||
<LoadingIndicator />
|
||||
</div>
|
||||
</Column>
|
||||
);
|
||||
} else if (list === false) {
|
||||
return (
|
||||
<Column>
|
||||
<MissingIndicator />
|
||||
<div className='scrollable'>
|
||||
<MissingIndicator />
|
||||
</div>
|
||||
</Column>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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']),
|
||||
});
|
||||
|
||||
|
|
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 11 KiB |
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 8.3 KiB |
|
@ -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:
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue