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 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 => {
|
||||||
|
if (response.status === 206) {
|
||||||
|
dispatch(refreshTimelineSuccess(timelineId, [], skipLoading, null, true));
|
||||||
|
} else {
|
||||||
const next = getLinks(response).refs.find(link => link.rel === 'next');
|
const next = getLinks(response).refs.find(link => link.rel === 'next');
|
||||||
dispatch(refreshTimelineSuccess(timelineId, response.data, skipLoading, next ? next.uri : null));
|
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));
|
||||||
});
|
});
|
||||||
|
|
|
@ -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>
|
||||||
);
|
);
|
||||||
|
|
|
@ -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,
|
||||||
|
@ -49,7 +51,22 @@ 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…' />
|
||||||
|
<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) => (
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -120,13 +120,17 @@ export default class ListTimeline extends React.PureComponent {
|
||||||
if (typeof list === 'undefined') {
|
if (typeof list === 'undefined') {
|
||||||
return (
|
return (
|
||||||
<Column>
|
<Column>
|
||||||
|
<div className='scrollable'>
|
||||||
<LoadingIndicator />
|
<LoadingIndicator />
|
||||||
|
</div>
|
||||||
</Column>
|
</Column>
|
||||||
);
|
);
|
||||||
} else if (list === false) {
|
} else if (list === false) {
|
||||||
return (
|
return (
|
||||||
<Column>
|
<Column>
|
||||||
|
<div className='scrollable'>
|
||||||
<MissingIndicator />
|
<MissingIndicator />
|
||||||
|
</div>
|
||||||
</Column>
|
</Column>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 |
|
@ -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:
|
||||||
|
|
|
@ -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';
|
||||||
|
|
|
@ -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