Fix height cache (#4909)
This commit is contained in:
		
							parent
							
								
									081f907f90
								
							
						
					
					
						commit
						60944d5dca
					
				
					 10 changed files with 93 additions and 61 deletions
				
			
		
							
								
								
									
										17
									
								
								app/javascript/mastodon/actions/height_cache.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								app/javascript/mastodon/actions/height_cache.js
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,17 @@
 | 
			
		|||
export const HEIGHT_CACHE_SET = 'HEIGHT_CACHE_SET';
 | 
			
		||||
export const HEIGHT_CACHE_CLEAR = 'HEIGHT_CACHE_CLEAR';
 | 
			
		||||
 | 
			
		||||
export function setHeight (key, id, height) {
 | 
			
		||||
  return {
 | 
			
		||||
    type: HEIGHT_CACHE_SET,
 | 
			
		||||
    key,
 | 
			
		||||
    id,
 | 
			
		||||
    height,
 | 
			
		||||
  };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export function clearHeight () {
 | 
			
		||||
  return {
 | 
			
		||||
    type: HEIGHT_CACHE_CLEAR,
 | 
			
		||||
  };
 | 
			
		||||
};
 | 
			
		||||
| 
						 | 
				
			
			@ -23,9 +23,6 @@ export const STATUS_UNMUTE_REQUEST = 'STATUS_UNMUTE_REQUEST';
 | 
			
		|||
export const STATUS_UNMUTE_SUCCESS = 'STATUS_UNMUTE_SUCCESS';
 | 
			
		||||
export const STATUS_UNMUTE_FAIL    = 'STATUS_UNMUTE_FAIL';
 | 
			
		||||
 | 
			
		||||
export const STATUS_SET_HEIGHT = 'STATUS_SET_HEIGHT';
 | 
			
		||||
export const STATUSES_CLEAR_HEIGHT = 'STATUSES_CLEAR_HEIGHT';
 | 
			
		||||
 | 
			
		||||
export function fetchStatusRequest(id, skipLoading) {
 | 
			
		||||
  return {
 | 
			
		||||
    type: STATUS_FETCH_REQUEST,
 | 
			
		||||
| 
						 | 
				
			
			@ -218,17 +215,3 @@ export function unmuteStatusFail(id, error) {
 | 
			
		|||
    error,
 | 
			
		||||
  };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export function setStatusHeight (id, height) {
 | 
			
		||||
  return {
 | 
			
		||||
    type: STATUS_SET_HEIGHT,
 | 
			
		||||
    id,
 | 
			
		||||
    height,
 | 
			
		||||
  };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export function clearStatusesHeight () {
 | 
			
		||||
  return {
 | 
			
		||||
    type: STATUSES_CLEAR_HEIGHT,
 | 
			
		||||
  };
 | 
			
		||||
};
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -7,10 +7,13 @@ import getRectFromEntry from '../features/ui/util/get_rect_from_entry';
 | 
			
		|||
export default class IntersectionObserverArticle extends ImmutablePureComponent {
 | 
			
		||||
 | 
			
		||||
  static propTypes = {
 | 
			
		||||
    intersectionObserverWrapper: PropTypes.object,
 | 
			
		||||
    intersectionObserverWrapper: PropTypes.object.isRequired,
 | 
			
		||||
    id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
 | 
			
		||||
    index: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
 | 
			
		||||
    listLength: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
 | 
			
		||||
    saveHeightKey: PropTypes.string,
 | 
			
		||||
    cachedHeight: PropTypes.number,
 | 
			
		||||
    onHeightChange: PropTypes.func,
 | 
			
		||||
    children: PropTypes.node,
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -34,13 +37,10 @@ export default class IntersectionObserverArticle extends ImmutablePureComponent
 | 
			
		|||
  }
 | 
			
		||||
 | 
			
		||||
  componentDidMount () {
 | 
			
		||||
    if (!this.props.intersectionObserverWrapper) {
 | 
			
		||||
      // TODO: enable IntersectionObserver optimization for notification statuses.
 | 
			
		||||
      // These are managed in notifications/index.js rather than status_list.js
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    this.props.intersectionObserverWrapper.observe(
 | 
			
		||||
      this.props.id,
 | 
			
		||||
    const { intersectionObserverWrapper, id } = this.props;
 | 
			
		||||
 | 
			
		||||
    intersectionObserverWrapper.observe(
 | 
			
		||||
      id,
 | 
			
		||||
      this.node,
 | 
			
		||||
      this.handleIntersection
 | 
			
		||||
    );
 | 
			
		||||
| 
						 | 
				
			
			@ -49,20 +49,21 @@ export default class IntersectionObserverArticle extends ImmutablePureComponent
 | 
			
		|||
  }
 | 
			
		||||
 | 
			
		||||
  componentWillUnmount () {
 | 
			
		||||
    if (this.props.intersectionObserverWrapper) {
 | 
			
		||||
      this.props.intersectionObserverWrapper.unobserve(this.props.id, this.node);
 | 
			
		||||
    }
 | 
			
		||||
    const { intersectionObserverWrapper, id } = this.props;
 | 
			
		||||
    intersectionObserverWrapper.unobserve(id, this.node);
 | 
			
		||||
 | 
			
		||||
    this.componentMounted = false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  handleIntersection = (entry) => {
 | 
			
		||||
    const { onHeightChange, saveHeightKey, id } = this.props;
 | 
			
		||||
 | 
			
		||||
    if (this.node && this.node.children.length !== 0) {
 | 
			
		||||
      // save the height of the fully-rendered element
 | 
			
		||||
      this.height = getRectFromEntry(entry).height;
 | 
			
		||||
 | 
			
		||||
      if (this.props.onHeightChange) {
 | 
			
		||||
        this.props.onHeightChange(this.props.status, this.height);
 | 
			
		||||
      if (onHeightChange && saveHeightKey) {
 | 
			
		||||
        onHeightChange(saveHeightKey, id, this.height);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -94,16 +95,16 @@ export default class IntersectionObserverArticle extends ImmutablePureComponent
 | 
			
		|||
  }
 | 
			
		||||
 | 
			
		||||
  render () {
 | 
			
		||||
    const { children, id, index, listLength } = this.props;
 | 
			
		||||
    const { children, id, index, listLength, cachedHeight } = this.props;
 | 
			
		||||
    const { isIntersecting, isHidden } = this.state;
 | 
			
		||||
 | 
			
		||||
    if (!isIntersecting && isHidden) {
 | 
			
		||||
    if (!isIntersecting && (isHidden || cachedHeight)) {
 | 
			
		||||
      return (
 | 
			
		||||
        <article
 | 
			
		||||
          ref={this.handleRef}
 | 
			
		||||
          aria-posinset={index}
 | 
			
		||||
          aria-setsize={listLength}
 | 
			
		||||
          style={{ height: `${this.height}px`, opacity: 0, overflow: 'hidden' }}
 | 
			
		||||
          style={{ height: `${this.height || cachedHeight}px`, opacity: 0, overflow: 'hidden' }}
 | 
			
		||||
          data-id={id}
 | 
			
		||||
          tabIndex='0'
 | 
			
		||||
        >
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,7 +1,7 @@
 | 
			
		|||
import React, { PureComponent } from 'react';
 | 
			
		||||
import { ScrollContainer } from 'react-router-scroll';
 | 
			
		||||
import PropTypes from 'prop-types';
 | 
			
		||||
import IntersectionObserverArticle from './intersection_observer_article';
 | 
			
		||||
import IntersectionObserverArticleContainer from '../containers/intersection_observer_article_container';
 | 
			
		||||
import LoadMore from './load_more';
 | 
			
		||||
import IntersectionObserverWrapper from '../features/ui/util/intersection_observer_wrapper';
 | 
			
		||||
import { throttle } from 'lodash';
 | 
			
		||||
| 
						 | 
				
			
			@ -9,6 +9,10 @@ import { List as ImmutableList } from 'immutable';
 | 
			
		|||
 | 
			
		||||
export default class ScrollableList extends PureComponent {
 | 
			
		||||
 | 
			
		||||
  static contextTypes = {
 | 
			
		||||
    router: PropTypes.object,
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  static propTypes = {
 | 
			
		||||
    scrollKey: PropTypes.string.isRequired,
 | 
			
		||||
    onScrollToBottom: PropTypes.func,
 | 
			
		||||
| 
						 | 
				
			
			@ -173,9 +177,16 @@ export default class ScrollableList extends PureComponent {
 | 
			
		|||
            {prepend}
 | 
			
		||||
 | 
			
		||||
            {React.Children.map(this.props.children, (child, index) => (
 | 
			
		||||
              <IntersectionObserverArticle key={child.key} id={child.key} index={index} listLength={childrenCount} intersectionObserverWrapper={this.intersectionObserverWrapper}>
 | 
			
		||||
              <IntersectionObserverArticleContainer
 | 
			
		||||
                key={child.key}
 | 
			
		||||
                id={child.key}
 | 
			
		||||
                index={index}
 | 
			
		||||
                listLength={childrenCount}
 | 
			
		||||
                intersectionObserverWrapper={this.intersectionObserverWrapper}
 | 
			
		||||
                saveHeightKey={trackScroll ? `${this.context.router.route.location.key}:${scrollKey}` : null}
 | 
			
		||||
              >
 | 
			
		||||
                {child}
 | 
			
		||||
              </IntersectionObserverArticle>
 | 
			
		||||
              </IntersectionObserverArticleContainer>
 | 
			
		||||
            ))}
 | 
			
		||||
 | 
			
		||||
            {loadMore}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,17 @@
 | 
			
		|||
import { connect } from 'react-redux';
 | 
			
		||||
import IntersectionObserverArticle from '../components/intersection_observer_article';
 | 
			
		||||
import { setHeight } from '../actions/height_cache';
 | 
			
		||||
 | 
			
		||||
const makeMapStateToProps = (state, props) => ({
 | 
			
		||||
  cachedHeight: state.getIn(['height_cache', props.saveHeightKey, props.id]),
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const mapDispatchToProps = (dispatch) => ({
 | 
			
		||||
 | 
			
		||||
  onHeightChange (key, id, height) {
 | 
			
		||||
    dispatch(setHeight(key, id, height));
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
export default connect(makeMapStateToProps, mapDispatchToProps)(IntersectionObserverArticle);
 | 
			
		||||
| 
						 | 
				
			
			@ -18,7 +18,7 @@ import {
 | 
			
		|||
  blockAccount,
 | 
			
		||||
  muteAccount,
 | 
			
		||||
} from '../actions/accounts';
 | 
			
		||||
import { muteStatus, unmuteStatus, deleteStatus, setStatusHeight } from '../actions/statuses';
 | 
			
		||||
import { muteStatus, unmuteStatus, deleteStatus } from '../actions/statuses';
 | 
			
		||||
import { initReport } from '../actions/reports';
 | 
			
		||||
import { openModal } from '../actions/modal';
 | 
			
		||||
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
 | 
			
		||||
| 
						 | 
				
			
			@ -138,10 +138,6 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
 | 
			
		|||
    }
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  onHeightChange (status, height) {
 | 
			
		||||
    dispatch(setStatusHeight(status.get('id'), height));
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
export default injectIntl(connect(makeMapStateToProps, mapDispatchToProps)(Status));
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -11,7 +11,7 @@ import { debounce } from 'lodash';
 | 
			
		|||
import { uploadCompose } from '../../actions/compose';
 | 
			
		||||
import { refreshHomeTimeline } from '../../actions/timelines';
 | 
			
		||||
import { refreshNotifications } from '../../actions/notifications';
 | 
			
		||||
import { clearStatusesHeight } from '../../actions/statuses';
 | 
			
		||||
import { clearHeight } from '../../actions/height_cache';
 | 
			
		||||
import { WrappedSwitch, WrappedRoute } from './util/react_router_helpers';
 | 
			
		||||
import UploadArea from './components/upload_area';
 | 
			
		||||
import ColumnsAreaContainer from './containers/columns_area_container';
 | 
			
		||||
| 
						 | 
				
			
			@ -68,7 +68,7 @@ export default class UI extends React.PureComponent {
 | 
			
		|||
 | 
			
		||||
  handleResize = debounce(() => {
 | 
			
		||||
    // The cached heights are no longer accurate, invalidate
 | 
			
		||||
    this.props.dispatch(clearStatusesHeight());
 | 
			
		||||
    this.props.dispatch(clearHeight());
 | 
			
		||||
 | 
			
		||||
    this.setState({ width: window.innerWidth });
 | 
			
		||||
  }, 500, {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										23
									
								
								app/javascript/mastodon/reducers/height_cache.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								app/javascript/mastodon/reducers/height_cache.js
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,23 @@
 | 
			
		|||
import { Map as ImmutableMap } from 'immutable';
 | 
			
		||||
import { HEIGHT_CACHE_SET, HEIGHT_CACHE_CLEAR } from '../actions/height_cache';
 | 
			
		||||
 | 
			
		||||
const initialState = ImmutableMap();
 | 
			
		||||
 | 
			
		||||
const setHeight = (state, key, id, height) => {
 | 
			
		||||
  return state.update(key, ImmutableMap(), map => map.set(id, height));
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const clearHeights = () => {
 | 
			
		||||
  return ImmutableMap();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default function statuses(state = initialState, action) {
 | 
			
		||||
  switch(action.type) {
 | 
			
		||||
  case HEIGHT_CACHE_SET:
 | 
			
		||||
    return setHeight(state, action.key, action.id, action.height);
 | 
			
		||||
  case HEIGHT_CACHE_CLEAR:
 | 
			
		||||
    return clearHeights();
 | 
			
		||||
  default:
 | 
			
		||||
    return state;
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
| 
						 | 
				
			
			@ -19,6 +19,7 @@ import compose from './compose';
 | 
			
		|||
import search from './search';
 | 
			
		||||
import media_attachments from './media_attachments';
 | 
			
		||||
import notifications from './notifications';
 | 
			
		||||
import height_cache from './height_cache';
 | 
			
		||||
 | 
			
		||||
const reducers = {
 | 
			
		||||
  timelines,
 | 
			
		||||
| 
						 | 
				
			
			@ -41,6 +42,7 @@ const reducers = {
 | 
			
		|||
  search,
 | 
			
		||||
  media_attachments,
 | 
			
		||||
  notifications,
 | 
			
		||||
  height_cache,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default combineReducers(reducers);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -15,8 +15,6 @@ import {
 | 
			
		|||
  CONTEXT_FETCH_SUCCESS,
 | 
			
		||||
  STATUS_MUTE_SUCCESS,
 | 
			
		||||
  STATUS_UNMUTE_SUCCESS,
 | 
			
		||||
  STATUS_SET_HEIGHT,
 | 
			
		||||
  STATUSES_CLEAR_HEIGHT,
 | 
			
		||||
} from '../actions/statuses';
 | 
			
		||||
import {
 | 
			
		||||
  TIMELINE_REFRESH_SUCCESS,
 | 
			
		||||
| 
						 | 
				
			
			@ -95,18 +93,6 @@ const filterStatuses = (state, relationship) => {
 | 
			
		|||
  return state;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const setHeight = (state, id, height) => {
 | 
			
		||||
  return state.update(id, ImmutableMap(), map => map.set('height', height));
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const clearHeights = (state) => {
 | 
			
		||||
  state.forEach(status => {
 | 
			
		||||
    state = state.deleteIn([status.get('id'), 'height']);
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  return state;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const initialState = ImmutableMap();
 | 
			
		||||
 | 
			
		||||
export default function statuses(state = initialState, action) {
 | 
			
		||||
| 
						 | 
				
			
			@ -148,10 +134,6 @@ export default function statuses(state = initialState, action) {
 | 
			
		|||
    return deleteStatus(state, action.id, action.references);
 | 
			
		||||
  case ACCOUNT_BLOCK_SUCCESS:
 | 
			
		||||
    return filterStatuses(state, action.relationship);
 | 
			
		||||
  case STATUS_SET_HEIGHT:
 | 
			
		||||
    return setHeight(state, action.id, action.height);
 | 
			
		||||
  case STATUSES_CLEAR_HEIGHT:
 | 
			
		||||
    return clearHeights(state);
 | 
			
		||||
  default:
 | 
			
		||||
    return state;
 | 
			
		||||
  }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		
		Reference in a new issue