Fix height cache (#4909)

pull/4923/head
abcang 2017-09-13 17:24:33 +09:00 committed by Eugen Rochko
parent 081f907f90
commit 60944d5dca
10 changed files with 93 additions and 61 deletions

View 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,
};
};

View File

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

View File

@ -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'
>

View File

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

View File

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

View File

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

View File

@ -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, {

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

View File

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

View File

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