forked from cybrespace/mastodon
Extract columns area from UI component (#6650)
UI component used to toggle isComposing state by directly manipulating the DOM element to avoid the expensive rendering. However, it is hacky, and is not effective for other states. Instead, this change makes the rendering cheaper by extracting the huge columns area.
This commit is contained in:
parent
13cf92df27
commit
a07cfee644
|
@ -1,3 +1,4 @@
|
||||||
|
import classNames from 'classnames';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import NotificationsContainer from './containers/notifications_container';
|
import NotificationsContainer from './containers/notifications_container';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
@ -84,10 +85,93 @@ const keyMap = {
|
||||||
goToMuted: 'g m',
|
goToMuted: 'g m',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class SwitchingColumnsArea extends React.PureComponent {
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
children: PropTypes.node,
|
||||||
|
location: PropTypes.object,
|
||||||
|
onLayoutChange: PropTypes.func.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
state = {
|
||||||
|
mobile: isMobile(window.innerWidth),
|
||||||
|
};
|
||||||
|
|
||||||
|
componentWillMount () {
|
||||||
|
window.addEventListener('resize', this.handleResize, { passive: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidUpdate (prevProps) {
|
||||||
|
if (![this.props.location.pathname, '/'].includes(prevProps.location.pathname)) {
|
||||||
|
this.node.handleChildrenContentChange();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount () {
|
||||||
|
window.removeEventListener('resize', this.handleResize);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleResize = debounce(() => {
|
||||||
|
// The cached heights are no longer accurate, invalidate
|
||||||
|
this.props.onLayoutChange();
|
||||||
|
|
||||||
|
this.setState({ mobile: isMobile(window.innerWidth) });
|
||||||
|
}, 500, {
|
||||||
|
trailing: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
setRef = c => {
|
||||||
|
this.node = c.getWrappedInstance().getWrappedInstance();
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const { children } = this.props;
|
||||||
|
const { mobile } = this.state;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ColumnsAreaContainer ref={this.setRef} singleColumn={mobile}>
|
||||||
|
<WrappedSwitch>
|
||||||
|
<Redirect from='/' to='/getting-started' exact />
|
||||||
|
<WrappedRoute path='/getting-started' component={GettingStarted} content={children} />
|
||||||
|
<WrappedRoute path='/keyboard-shortcuts' component={KeyboardShortcuts} content={children} />
|
||||||
|
<WrappedRoute path='/timelines/home' component={HomeTimeline} content={children} />
|
||||||
|
<WrappedRoute path='/timelines/public' exact component={PublicTimeline} content={children} />
|
||||||
|
<WrappedRoute path='/timelines/public/local' component={CommunityTimeline} content={children} />
|
||||||
|
<WrappedRoute path='/timelines/tag/:id' component={HashtagTimeline} content={children} />
|
||||||
|
<WrappedRoute path='/timelines/list/:id' component={ListTimeline} content={children} />
|
||||||
|
|
||||||
|
<WrappedRoute path='/notifications' component={Notifications} content={children} />
|
||||||
|
<WrappedRoute path='/favourites' component={FavouritedStatuses} content={children} />
|
||||||
|
<WrappedRoute path='/pinned' component={PinnedStatuses} content={children} />
|
||||||
|
|
||||||
|
<WrappedRoute path='/statuses/new' component={Compose} content={children} />
|
||||||
|
<WrappedRoute path='/statuses/:statusId' exact component={Status} content={children} />
|
||||||
|
<WrappedRoute path='/statuses/:statusId/reblogs' component={Reblogs} content={children} />
|
||||||
|
<WrappedRoute path='/statuses/:statusId/favourites' component={Favourites} content={children} />
|
||||||
|
|
||||||
|
<WrappedRoute path='/accounts/:accountId' exact component={AccountTimeline} content={children} />
|
||||||
|
<WrappedRoute path='/accounts/:accountId/with_replies' component={AccountTimeline} content={children} componentParams={{ withReplies: true }} />
|
||||||
|
<WrappedRoute path='/accounts/:accountId/followers' component={Followers} content={children} />
|
||||||
|
<WrappedRoute path='/accounts/:accountId/following' component={Following} content={children} />
|
||||||
|
<WrappedRoute path='/accounts/:accountId/media' component={AccountGallery} content={children} />
|
||||||
|
|
||||||
|
<WrappedRoute path='/follow_requests' component={FollowRequests} content={children} />
|
||||||
|
<WrappedRoute path='/blocks' component={Blocks} content={children} />
|
||||||
|
<WrappedRoute path='/mutes' component={Mutes} content={children} />
|
||||||
|
<WrappedRoute path='/lists' component={Lists} content={children} />
|
||||||
|
|
||||||
|
<WrappedRoute component={GenericNotFound} content={children} />
|
||||||
|
</WrappedSwitch>
|
||||||
|
</ColumnsAreaContainer>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@connect(mapStateToProps)
|
@connect(mapStateToProps)
|
||||||
@injectIntl
|
@injectIntl
|
||||||
@withRouter
|
@withRouter
|
||||||
export default class UI extends React.Component {
|
export default class UI extends React.PureComponent {
|
||||||
|
|
||||||
static contextTypes = {
|
static contextTypes = {
|
||||||
router: PropTypes.object.isRequired,
|
router: PropTypes.object.isRequired,
|
||||||
|
@ -103,7 +187,6 @@ export default class UI extends React.Component {
|
||||||
};
|
};
|
||||||
|
|
||||||
state = {
|
state = {
|
||||||
width: window.innerWidth,
|
|
||||||
draggingOver: false,
|
draggingOver: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -118,14 +201,10 @@ export default class UI extends React.Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleResize = debounce(() => {
|
handleLayoutChange = () => {
|
||||||
// The cached heights are no longer accurate, invalidate
|
// The cached heights are no longer accurate, invalidate
|
||||||
this.props.dispatch(clearHeight());
|
this.props.dispatch(clearHeight());
|
||||||
|
}
|
||||||
this.setState({ width: window.innerWidth });
|
|
||||||
}, 500, {
|
|
||||||
trailing: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
handleDragEnter = (e) => {
|
handleDragEnter = (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
@ -193,7 +272,6 @@ export default class UI extends React.Component {
|
||||||
|
|
||||||
componentWillMount () {
|
componentWillMount () {
|
||||||
window.addEventListener('beforeunload', this.handleBeforeUnload, false);
|
window.addEventListener('beforeunload', this.handleBeforeUnload, false);
|
||||||
window.addEventListener('resize', this.handleResize, { passive: true });
|
|
||||||
document.addEventListener('dragenter', this.handleDragEnter, false);
|
document.addEventListener('dragenter', this.handleDragEnter, false);
|
||||||
document.addEventListener('dragover', this.handleDragOver, false);
|
document.addEventListener('dragover', this.handleDragOver, false);
|
||||||
document.addEventListener('drop', this.handleDrop, false);
|
document.addEventListener('drop', this.handleDrop, false);
|
||||||
|
@ -214,28 +292,8 @@ export default class UI extends React.Component {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
shouldComponentUpdate (nextProps) {
|
|
||||||
if (nextProps.isComposing !== this.props.isComposing) {
|
|
||||||
// Avoid expensive update just to toggle a class
|
|
||||||
this.node.classList.toggle('is-composing', nextProps.isComposing);
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Why isn't this working?!?
|
|
||||||
// return super.shouldComponentUpdate(nextProps, nextState);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidUpdate (prevProps) {
|
|
||||||
if (![this.props.location.pathname, '/'].includes(prevProps.location.pathname)) {
|
|
||||||
this.columnsAreaNode.handleChildrenContentChange();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillUnmount () {
|
componentWillUnmount () {
|
||||||
window.removeEventListener('beforeunload', this.handleBeforeUnload);
|
window.removeEventListener('beforeunload', this.handleBeforeUnload);
|
||||||
window.removeEventListener('resize', this.handleResize);
|
|
||||||
document.removeEventListener('dragenter', this.handleDragEnter);
|
document.removeEventListener('dragenter', this.handleDragEnter);
|
||||||
document.removeEventListener('dragover', this.handleDragOver);
|
document.removeEventListener('dragover', this.handleDragOver);
|
||||||
document.removeEventListener('drop', this.handleDrop);
|
document.removeEventListener('drop', this.handleDrop);
|
||||||
|
@ -247,10 +305,6 @@ export default class UI extends React.Component {
|
||||||
this.node = c;
|
this.node = c;
|
||||||
}
|
}
|
||||||
|
|
||||||
setColumnsAreaRef = c => {
|
|
||||||
this.columnsAreaNode = c.getWrappedInstance().getWrappedInstance();
|
|
||||||
}
|
|
||||||
|
|
||||||
handleHotkeyNew = e => {
|
handleHotkeyNew = e => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
|
@ -350,8 +404,8 @@ export default class UI extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { width, draggingOver } = this.state;
|
const { draggingOver } = this.state;
|
||||||
const { children } = this.props;
|
const { children, isComposing, location } = this.props;
|
||||||
|
|
||||||
const handlers = {
|
const handlers = {
|
||||||
help: this.handleHotkeyToggleHelp,
|
help: this.handleHotkeyToggleHelp,
|
||||||
|
@ -374,43 +428,12 @@ export default class UI extends React.Component {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<HotKeys keyMap={keyMap} handlers={handlers} ref={this.setHotkeysRef}>
|
<HotKeys keyMap={keyMap} handlers={handlers} ref={this.setHotkeysRef}>
|
||||||
<div className='ui' ref={this.setRef}>
|
<div className={classNames('ui', { 'is-composing': isComposing })} ref={this.setRef}>
|
||||||
<TabsBar />
|
<TabsBar />
|
||||||
|
|
||||||
<ColumnsAreaContainer ref={this.setColumnsAreaRef} singleColumn={isMobile(width)}>
|
<SwitchingColumnsArea location={location} onLayoutChange={this.handleLayoutChange}>
|
||||||
<WrappedSwitch>
|
{children}
|
||||||
<Redirect from='/' to='/getting-started' exact />
|
</SwitchingColumnsArea>
|
||||||
<WrappedRoute path='/getting-started' component={GettingStarted} content={children} />
|
|
||||||
<WrappedRoute path='/keyboard-shortcuts' component={KeyboardShortcuts} content={children} />
|
|
||||||
<WrappedRoute path='/timelines/home' component={HomeTimeline} content={children} />
|
|
||||||
<WrappedRoute path='/timelines/public' exact component={PublicTimeline} content={children} />
|
|
||||||
<WrappedRoute path='/timelines/public/local' component={CommunityTimeline} content={children} />
|
|
||||||
<WrappedRoute path='/timelines/tag/:id' component={HashtagTimeline} content={children} />
|
|
||||||
<WrappedRoute path='/timelines/list/:id' component={ListTimeline} content={children} />
|
|
||||||
|
|
||||||
<WrappedRoute path='/notifications' component={Notifications} content={children} />
|
|
||||||
<WrappedRoute path='/favourites' component={FavouritedStatuses} content={children} />
|
|
||||||
<WrappedRoute path='/pinned' component={PinnedStatuses} content={children} />
|
|
||||||
|
|
||||||
<WrappedRoute path='/statuses/new' component={Compose} content={children} />
|
|
||||||
<WrappedRoute path='/statuses/:statusId' exact component={Status} content={children} />
|
|
||||||
<WrappedRoute path='/statuses/:statusId/reblogs' component={Reblogs} content={children} />
|
|
||||||
<WrappedRoute path='/statuses/:statusId/favourites' component={Favourites} content={children} />
|
|
||||||
|
|
||||||
<WrappedRoute path='/accounts/:accountId' exact component={AccountTimeline} content={children} />
|
|
||||||
<WrappedRoute path='/accounts/:accountId/with_replies' component={AccountTimeline} content={children} componentParams={{ withReplies: true }} />
|
|
||||||
<WrappedRoute path='/accounts/:accountId/followers' component={Followers} content={children} />
|
|
||||||
<WrappedRoute path='/accounts/:accountId/following' component={Following} content={children} />
|
|
||||||
<WrappedRoute path='/accounts/:accountId/media' component={AccountGallery} content={children} />
|
|
||||||
|
|
||||||
<WrappedRoute path='/follow_requests' component={FollowRequests} content={children} />
|
|
||||||
<WrappedRoute path='/blocks' component={Blocks} content={children} />
|
|
||||||
<WrappedRoute path='/mutes' component={Mutes} content={children} />
|
|
||||||
<WrappedRoute path='/lists' component={Lists} content={children} />
|
|
||||||
|
|
||||||
<WrappedRoute component={GenericNotFound} content={children} />
|
|
||||||
</WrappedSwitch>
|
|
||||||
</ColumnsAreaContainer>
|
|
||||||
|
|
||||||
<NotificationsContainer />
|
<NotificationsContainer />
|
||||||
<LoadingBarContainer className='loading-bar' />
|
<LoadingBarContainer className='loading-bar' />
|
||||||
|
|
Loading…
Reference in New Issue