Port 7badad7797 to glitch frontend

pull/332/head
David Yip 2018-01-18 10:25:37 -06:00
parent 1964a0f941
commit bcd86404da
No known key found for this signature in database
GPG Key ID: 7DA0036508FCC0CC
11 changed files with 140 additions and 27 deletions

View File

@ -19,13 +19,14 @@ export const TIMELINE_DISCONNECT = 'TIMELINE_DISCONNECT';
export const TIMELINE_CONTEXT_UPDATE = 'CONTEXT_UPDATE'; export const TIMELINE_CONTEXT_UPDATE = 'CONTEXT_UPDATE';
export function refreshTimelineSuccess(timeline, statuses, skipLoading, next) { export function refreshTimelineSuccess(timeline, statuses, skipLoading, next, partial) {
return { return {
type: TIMELINE_REFRESH_SUCCESS, type: TIMELINE_REFRESH_SUCCESS,
timeline, timeline,
statuses, statuses,
skipLoading, skipLoading,
next, next,
partial,
}; };
}; };
@ -88,7 +89,7 @@ export function refreshTimeline(timelineId, path, params = {}) {
return function (dispatch, getState) { return function (dispatch, getState) {
const timeline = getState().getIn(['timelines', timelineId], ImmutableMap()); 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; return;
} }
@ -104,8 +105,12 @@ export function refreshTimeline(timelineId, path, params = {}) {
dispatch(refreshTimelineRequest(timelineId, skipLoading)); dispatch(refreshTimelineRequest(timelineId, skipLoading));
api(getState).get(path, { params }).then(response => { api(getState).get(path, { params }).then(response => {
const next = getLinks(response).refs.find(link => link.rel === 'next'); if (response.status === 206) {
dispatch(refreshTimelineSuccess(timelineId, response.data, skipLoading, next ? next.uri : null)); 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 => { }).catch(error => {
dispatch(refreshTimelineFail(timelineId, error, skipLoading)); dispatch(refreshTimelineFail(timelineId, error, skipLoading));
}); });

View File

@ -2,9 +2,14 @@ import React from 'react';
import { FormattedMessage } from 'react-intl'; import { FormattedMessage } from 'react-intl';
const MissingIndicator = () => ( const MissingIndicator = () => (
<div className='missing-indicator'> <div className='regeneration-indicator missing-indicator'>
<div> <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>
</div> </div>
); );

View File

@ -4,6 +4,7 @@ import PropTypes from 'prop-types';
import StatusContainer from 'flavours/glitch/containers/status_container'; import StatusContainer from 'flavours/glitch/containers/status_container';
import ImmutablePureComponent from 'react-immutable-pure-component'; import ImmutablePureComponent from 'react-immutable-pure-component';
import ScrollableList from './scrollable_list'; import ScrollableList from './scrollable_list';
import { FormattedMessage } from 'react-intl';
export default class StatusList extends ImmutablePureComponent { export default class StatusList extends ImmutablePureComponent {
@ -16,6 +17,7 @@ export default class StatusList extends ImmutablePureComponent {
trackScroll: PropTypes.bool, trackScroll: PropTypes.bool,
shouldUpdateScroll: PropTypes.func, shouldUpdateScroll: PropTypes.func,
isLoading: PropTypes.bool, isLoading: PropTypes.bool,
isPartial: PropTypes.bool,
hasMore: PropTypes.bool, hasMore: PropTypes.bool,
prepend: PropTypes.node, prepend: PropTypes.node,
emptyMessage: PropTypes.node, emptyMessage: PropTypes.node,
@ -48,8 +50,23 @@ export default class StatusList extends ImmutablePureComponent {
} }
render () { render () {
const { statusIds, ...other } = this.props; const { statusIds, ...other } = this.props;
const { isLoading } = other; 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&hellip;' />
<FormattedMessage id='regeneration_indicator.sublabel' defaultMessage='Your home feed is being prepared!' />
</div>
</div>
</div>
);
}
const scrollableContent = (isLoading || statusIds.size > 0) ? ( const scrollableContent = (isLoading || statusIds.size > 0) ? (
statusIds.map((statusId) => ( statusIds.map((statusId) => (

View File

@ -1,6 +1,6 @@
import React from 'react'; import React from 'react';
import { connect } from 'react-redux'; 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 PropTypes from 'prop-types';
import StatusListContainer from 'flavours/glitch/features/ui/containers/status_list_container'; import StatusListContainer from 'flavours/glitch/features/ui/containers/status_list_container';
import Column from 'flavours/glitch/components/column'; import Column from 'flavours/glitch/components/column';
@ -16,6 +16,7 @@ const messages = defineMessages({
const mapStateToProps = state => ({ const mapStateToProps = state => ({
hasUnread: state.getIn(['timelines', 'home', 'unread']) > 0, hasUnread: state.getIn(['timelines', 'home', 'unread']) > 0,
isPartial: state.getIn(['timelines', 'home', 'isPartial'], false),
}); });
@connect(mapStateToProps) @connect(mapStateToProps)
@ -26,6 +27,7 @@ export default class HomeTimeline extends React.PureComponent {
dispatch: PropTypes.func.isRequired, dispatch: PropTypes.func.isRequired,
intl: PropTypes.object.isRequired, intl: PropTypes.object.isRequired,
hasUnread: PropTypes.bool, hasUnread: PropTypes.bool,
isPartial: PropTypes.bool,
columnId: PropTypes.string, columnId: PropTypes.string,
multiColumn: PropTypes.bool, multiColumn: PropTypes.bool,
}; };
@ -57,6 +59,39 @@ export default class HomeTimeline extends React.PureComponent {
this.props.dispatch(expandHomeTimeline()); 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 () { render () {
const { intl, hasUnread, columnId, multiColumn } = this.props; const { intl, hasUnread, columnId, multiColumn } = this.props;
const pinned = !!columnId; const pinned = !!columnId;

View File

@ -120,13 +120,17 @@ export default class ListTimeline extends React.PureComponent {
if (typeof list === 'undefined') { if (typeof list === 'undefined') {
return ( return (
<Column> <Column>
<LoadingIndicator /> <div className='scrollable'>
<LoadingIndicator />
</div>
</Column> </Column>
); );
} else if (list === false) { } else if (list === false) {
return ( return (
<Column> <Column>
<MissingIndicator /> <div className='scrollable'>
<MissingIndicator />
</div>
</Column> </Column>
); );
} }

View File

@ -51,6 +51,7 @@ const makeMapStateToProps = () => {
const mapStateToProps = (state, { timelineId }) => ({ const mapStateToProps = (state, { timelineId }) => ({
statusIds: getStatusIds(state, { type: timelineId }), statusIds: getStatusIds(state, { type: timelineId }),
isLoading: state.getIn(['timelines', timelineId, 'isLoading'], true), isLoading: state.getIn(['timelines', timelineId, 'isLoading'], true),
isPartial: state.getIn(['timelines', timelineId, 'isPartial'], false),
hasMore: !!state.getIn(['timelines', timelineId, 'next']), 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

View File

@ -30,7 +30,7 @@ const initialTimeline = ImmutableMap({
items: ImmutableList(), items: ImmutableList(),
}); });
const normalizeTimeline = (state, timeline, statuses, next) => { const normalizeTimeline = (state, timeline, statuses, next, isPartial) => {
const oldIds = state.getIn([timeline, 'items'], ImmutableList()); const oldIds = state.getIn([timeline, 'items'], ImmutableList());
const ids = ImmutableList(statuses.map(status => status.get('id'))).filter(newId => !oldIds.includes(newId)); const ids = ImmutableList(statuses.map(status => status.get('id'))).filter(newId => !oldIds.includes(newId));
const wasLoaded = state.getIn([timeline, 'loaded']); const wasLoaded = state.getIn([timeline, 'loaded']);
@ -41,6 +41,7 @@ const normalizeTimeline = (state, timeline, statuses, next) => {
mMap.set('isLoading', false); mMap.set('isLoading', false);
if (!hadNext) mMap.set('next', next); if (!hadNext) mMap.set('next', next);
mMap.set('items', wasLoaded ? ids.concat(oldIds) : ids); 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: case TIMELINE_EXPAND_FAIL:
return state.update(action.timeline, initialTimeline, map => map.set('isLoading', false)); return state.update(action.timeline, initialTimeline, map => map.set('isLoading', false));
case TIMELINE_REFRESH_SUCCESS: 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: case TIMELINE_EXPAND_SUCCESS:
return appendNormalizedTimeline(state, action.timeline, fromJS(action.statuses), action.next); return appendNormalizedTimeline(state, action.timeline, fromJS(action.statuses), action.next);
case TIMELINE_UPDATE: case TIMELINE_UPDATE:

View File

@ -838,21 +838,10 @@
} }
.missing-indicator { .missing-indicator {
text-align: center; padding-top: 20px + 48px;
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;
& > div { .regeneration-indicator__figure {
background: url('~images/mastodon-not-found.png') no-repeat center -50px; background-image: url('~flavours/glitch/images/elephant_ui_disappointed.svg');
padding-top: 210px;
width: 100%;
} }
} }
@ -1162,6 +1151,7 @@ noscript {
@import 'metadata'; @import 'metadata';
@import 'composer'; @import 'composer';
@import 'columns'; @import 'columns';
@import 'regeneration_indicator';
@import 'search'; @import 'search';
@import 'emoji'; @import 'emoji';
@import 'doodle'; @import 'doodle';

View File

@ -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;
}
}
}