Port 7badad7797 to glitch frontend
				
					
				
			This commit is contained in:
		| @@ -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; | ||||
|     } | ||||
|   } | ||||
| } | ||||
		Reference in New Issue
	
	Block a user