about summary refs log tree commit diff
diff options
context:
space:
mode:
authorSorin Davidoi <sorin.davidoi@gmail.com>2017-05-20 14:58:13 +0200
committerEugen Rochko <eugen@zeonfederated.com>2017-05-20 14:58:13 +0200
commit2c405aed553067bfba2daf1b235a27f7ba52c956 (patch)
tree1f541b16216471732b28715ada3995489665c6e2
parentda0a18a318c9810fed95785c42460950b9d71183 (diff)
Performance improvements (#3168)
* refactor(components/status_list): Avoid quering scrollTop if not necessary

* refactor(components/dropdown_menu): Do not render items if not expanded

* refactor: Cherry-pick react-motion imports

* refactor(compose/privacy_dropdown): Do not render options if not open

* refactor(components/column_collapsable): Do not render children if collapsed
-rw-r--r--app/javascript/mastodon/components/collapsable.js3
-rw-r--r--app/javascript/mastodon/components/column_collapsable.js2
-rw-r--r--app/javascript/mastodon/components/dropdown_menu.js20
-rw-r--r--app/javascript/mastodon/components/icon_button.js3
-rw-r--r--app/javascript/mastodon/components/status_list.js2
-rw-r--r--app/javascript/mastodon/features/account/components/header.js3
-rw-r--r--app/javascript/mastodon/features/compose/components/privacy_dropdown.js2
-rw-r--r--app/javascript/mastodon/features/compose/components/upload_form.js3
-rw-r--r--app/javascript/mastodon/features/compose/components/upload_progress.js3
-rw-r--r--app/javascript/mastodon/features/compose/containers/sensitive_button_container.js3
-rw-r--r--app/javascript/mastodon/features/compose/index.js3
-rw-r--r--app/javascript/mastodon/features/ui/components/modal_root.js3
-rw-r--r--app/javascript/mastodon/features/ui/components/onboarding_modal.js3
-rw-r--r--app/javascript/mastodon/features/ui/components/upload_area.js3
-rw-r--r--spec/javascript/components/dropdown_menu.test.jsx37
15 files changed, 73 insertions, 20 deletions
diff --git a/app/javascript/mastodon/components/collapsable.js b/app/javascript/mastodon/components/collapsable.js
index a61f67d8e..ac42bc6c7 100644
--- a/app/javascript/mastodon/components/collapsable.js
+++ b/app/javascript/mastodon/components/collapsable.js
@@ -1,5 +1,6 @@
 import React from 'react';
-import { Motion, spring } from 'react-motion';
+import Motion from 'react-motion/lib/Motion';
+import spring from 'react-motion/lib/spring';
 import PropTypes from 'prop-types';
 
 const Collapsable = ({ fullHeight, isVisible, children }) => (
diff --git a/app/javascript/mastodon/components/column_collapsable.js b/app/javascript/mastodon/components/column_collapsable.js
index 44ec63af8..c75f8ff36 100644
--- a/app/javascript/mastodon/components/column_collapsable.js
+++ b/app/javascript/mastodon/components/column_collapsable.js
@@ -36,7 +36,7 @@ class ColumnCollapsable extends React.PureComponent {
         </div>
 
         <div className='column-collapsable__content' style={{ height: `${fullHeight}px` }}>
-          {children}
+          {!collapsed && children}
         </div>
       </div>
     );
diff --git a/app/javascript/mastodon/components/dropdown_menu.js b/app/javascript/mastodon/components/dropdown_menu.js
index 03595318c..8dee211fa 100644
--- a/app/javascript/mastodon/components/dropdown_menu.js
+++ b/app/javascript/mastodon/components/dropdown_menu.js
@@ -21,7 +21,8 @@ class DropdownMenu extends React.PureComponent {
   };
 
   state = {
-    direction: 'left'
+    direction: 'left',
+    expanded: false,
   };
 
   setRef = (c) => {
@@ -43,6 +44,10 @@ class DropdownMenu extends React.PureComponent {
     this.dropdown.hide();
   }
 
+  handleShow = () => this.setState({ expanded: true })
+
+  handleHide = () => this.setState({ expanded: false })
+
   renderItem = (item, i) => {
     if (item === null) {
       return <li key={ 'sep' + i } className='dropdown__sep' />;
@@ -61,18 +66,23 @@ class DropdownMenu extends React.PureComponent {
 
   render () {
     const { icon, items, size, direction, ariaLabel } = this.props;
+    const { expanded } = this.state;
     const directionClass = (direction === "left") ? "dropdown__left" : "dropdown__right";
 
+    const dropdownItems = expanded && (
+      <ul className='dropdown__content-list'>
+        {items.map(this.renderItem)}
+      </ul>
+    );
+
     return (
-      <Dropdown ref={this.setRef}>
+      <Dropdown ref={this.setRef} onShow={this.handleShow} onHide={this.handleHide}>
         <DropdownTrigger className='icon-button' style={{ fontSize: `${size}px`, width: `${size}px`, lineHeight: `${size}px` }} aria-label={ariaLabel}>
           <i className={ `fa fa-fw fa-${icon} dropdown__icon` }  aria-hidden={true} />
         </DropdownTrigger>
 
         <DropdownContent className={directionClass}>
-          <ul className='dropdown__content-list'>
-            {items.map(this.renderItem)}
-          </ul>
+          {dropdownItems}
         </DropdownContent>
       </Dropdown>
     );
diff --git a/app/javascript/mastodon/components/icon_button.js b/app/javascript/mastodon/components/icon_button.js
index c2fbbd4b9..3ae22faef 100644
--- a/app/javascript/mastodon/components/icon_button.js
+++ b/app/javascript/mastodon/components/icon_button.js
@@ -1,5 +1,6 @@
 import React from 'react';
-import { Motion, spring } from 'react-motion';
+import Motion from 'react-motion/lib/Motion';
+import spring from 'react-motion/lib/spring';
 import PropTypes from 'prop-types';
 
 class IconButton extends React.PureComponent {
diff --git a/app/javascript/mastodon/components/status_list.js b/app/javascript/mastodon/components/status_list.js
index 0413e9d5f..9e6c8fd18 100644
--- a/app/javascript/mastodon/components/status_list.js
+++ b/app/javascript/mastodon/components/status_list.js
@@ -46,7 +46,7 @@ class StatusList extends ImmutablePureComponent {
   }
 
   componentDidUpdate (prevProps) {
-    if (this.node.scrollTop > 0 && (prevProps.statusIds.size < this.props.statusIds.size && prevProps.statusIds.first() !== this.props.statusIds.first() && !!this._oldScrollPosition)) {
+    if ((prevProps.statusIds.size < this.props.statusIds.size && prevProps.statusIds.first() !== this.props.statusIds.first() && !!this._oldScrollPosition) && this.node.scrollTop > 0) {
       this.node.scrollTop = this.node.scrollHeight - this._oldScrollPosition;
     }
   }
diff --git a/app/javascript/mastodon/features/account/components/header.js b/app/javascript/mastodon/features/account/components/header.js
index 5d2586f4d..8a21c6039 100644
--- a/app/javascript/mastodon/features/account/components/header.js
+++ b/app/javascript/mastodon/features/account/components/header.js
@@ -5,7 +5,8 @@ import emojify from '../../../emoji';
 import escapeTextContentForBrowser from 'escape-html';
 import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
 import IconButton from '../../../components/icon_button';
-import { Motion, spring } from 'react-motion';
+import Motion from 'react-motion/lib/Motion';
+import spring from 'react-motion/lib/spring';
 import { connect } from 'react-redux';
 import ImmutablePureComponent from 'react-immutable-pure-component';
 
diff --git a/app/javascript/mastodon/features/compose/components/privacy_dropdown.js b/app/javascript/mastodon/features/compose/components/privacy_dropdown.js
index 1e0bb3d09..b02421949 100644
--- a/app/javascript/mastodon/features/compose/components/privacy_dropdown.js
+++ b/app/javascript/mastodon/features/compose/components/privacy_dropdown.js
@@ -80,7 +80,7 @@ class PrivacyDropdown extends React.PureComponent {
       <div ref={this.setRef} className={`privacy-dropdown ${open ? 'active' : ''}`}>
         <div className='privacy-dropdown__value'><IconButton className='privacy-dropdown__value-icon' icon={valueOption.icon} title={intl.formatMessage(messages.change_privacy)} size={18} active={open} inverted onClick={this.handleToggle} style={iconStyle}/></div>
         <div className='privacy-dropdown__dropdown'>
-          {options.map(item =>
+          {open && options.map(item =>
             <div role='button' tabIndex='0' key={item.value} data-index={item.value} onClick={this.handleClick} className={`privacy-dropdown__option ${item.value === value ? 'active' : ''}`}>
               <div className='privacy-dropdown__option__icon'><i className={`fa fa-fw fa-${item.icon}`} /></div>
               <div className='privacy-dropdown__option__content'>
diff --git a/app/javascript/mastodon/features/compose/components/upload_form.js b/app/javascript/mastodon/features/compose/components/upload_form.js
index f2579bf60..ac26e018a 100644
--- a/app/javascript/mastodon/features/compose/components/upload_form.js
+++ b/app/javascript/mastodon/features/compose/components/upload_form.js
@@ -4,7 +4,8 @@ import PropTypes from 'prop-types';
 import IconButton from '../../../components/icon_button';
 import { defineMessages, injectIntl } from 'react-intl';
 import UploadProgressContainer from '../containers/upload_progress_container';
-import { Motion, spring } from 'react-motion';
+import Motion from 'react-motion/lib/Motion';
+import spring from 'react-motion/lib/spring';
 
 const messages = defineMessages({
   undo: { id: 'upload_form.undo', defaultMessage: 'Undo' }
diff --git a/app/javascript/mastodon/features/compose/components/upload_progress.js b/app/javascript/mastodon/features/compose/components/upload_progress.js
index 92a9f09e6..dae6bf259 100644
--- a/app/javascript/mastodon/features/compose/components/upload_progress.js
+++ b/app/javascript/mastodon/features/compose/components/upload_progress.js
@@ -1,6 +1,7 @@
 import React from 'react';
 import PropTypes from 'prop-types';
-import { Motion, spring } from 'react-motion';
+import Motion from 'react-motion/lib/Motion';
+import spring from 'react-motion/lib/spring';
 import { FormattedMessage } from 'react-intl';
 
 class UploadProgress extends React.PureComponent {
diff --git a/app/javascript/mastodon/features/compose/containers/sensitive_button_container.js b/app/javascript/mastodon/features/compose/containers/sensitive_button_container.js
index de73b506a..c8aed5589 100644
--- a/app/javascript/mastodon/features/compose/containers/sensitive_button_container.js
+++ b/app/javascript/mastodon/features/compose/containers/sensitive_button_container.js
@@ -3,7 +3,8 @@ import { connect } from 'react-redux';
 import PropTypes from 'prop-types';
 import TextIconButton from '../components/text_icon_button';
 import { changeComposeSensitivity } from '../../../actions/compose';
-import { Motion, spring } from 'react-motion';
+import Motion from 'react-motion/lib/Motion';
+import spring from 'react-motion/lib/spring';
 import { injectIntl, defineMessages } from 'react-intl';
 
 const messages = defineMessages({
diff --git a/app/javascript/mastodon/features/compose/index.js b/app/javascript/mastodon/features/compose/index.js
index 3d066ba55..0bea9a090 100644
--- a/app/javascript/mastodon/features/compose/index.js
+++ b/app/javascript/mastodon/features/compose/index.js
@@ -8,7 +8,8 @@ import { mountCompose, unmountCompose } from '../../actions/compose';
 import Link from 'react-router/lib/Link';
 import { injectIntl, defineMessages } from 'react-intl';
 import SearchContainer from './containers/search_container';
-import { Motion, spring } from 'react-motion';
+import Motion from 'react-motion/lib/Motion';
+import spring from 'react-motion/lib/spring';
 import SearchResultsContainer from './containers/search_results_container';
 
 const messages = defineMessages({
diff --git a/app/javascript/mastodon/features/ui/components/modal_root.js b/app/javascript/mastodon/features/ui/components/modal_root.js
index c4015a119..24b466bc5 100644
--- a/app/javascript/mastodon/features/ui/components/modal_root.js
+++ b/app/javascript/mastodon/features/ui/components/modal_root.js
@@ -5,7 +5,8 @@ import OnboardingModal from './onboarding_modal';
 import VideoModal from './video_modal';
 import BoostModal from './boost_modal';
 import ConfirmationModal from './confirmation_modal';
-import { TransitionMotion, spring } from 'react-motion';
+import TransitionMotion from 'react-motion/lib/TransitionMotion';
+import spring from 'react-motion/lib/spring';
 
 const MODAL_COMPONENTS = {
   'MEDIA': MediaModal,
diff --git a/app/javascript/mastodon/features/ui/components/onboarding_modal.js b/app/javascript/mastodon/features/ui/components/onboarding_modal.js
index d83a3d5c4..ee20fc94a 100644
--- a/app/javascript/mastodon/features/ui/components/onboarding_modal.js
+++ b/app/javascript/mastodon/features/ui/components/onboarding_modal.js
@@ -4,7 +4,8 @@ import PropTypes from 'prop-types';
 import ImmutablePropTypes from 'react-immutable-proptypes';
 import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
 import Permalink from '../../../components/permalink';
-import { TransitionMotion, spring } from 'react-motion';
+import TransitionMotion from 'react-motion/lib/TransitionMotion';
+import spring from 'react-motion/lib/spring';
 import ComposeForm from '../../compose/components/compose_form';
 import Search from '../../compose/components/search';
 import NavigationBar from '../../compose/components/navigation_bar';
diff --git a/app/javascript/mastodon/features/ui/components/upload_area.js b/app/javascript/mastodon/features/ui/components/upload_area.js
index c7d109a33..c7f546588 100644
--- a/app/javascript/mastodon/features/ui/components/upload_area.js
+++ b/app/javascript/mastodon/features/ui/components/upload_area.js
@@ -1,6 +1,7 @@
 import React from 'react';
 import PropTypes from 'prop-types';
-import { Motion, spring } from 'react-motion';
+import Motion from 'react-motion/lib/Motion';
+import spring from 'react-motion/lib/spring';
 import { FormattedMessage } from 'react-intl';
 
 class UploadArea extends React.PureComponent {
diff --git a/spec/javascript/components/dropdown_menu.test.jsx b/spec/javascript/components/dropdown_menu.test.jsx
index 717bd51ac..c5bbf5ad6 100644
--- a/spec/javascript/components/dropdown_menu.test.jsx
+++ b/spec/javascript/components/dropdown_menu.test.jsx
@@ -38,7 +38,40 @@ describe('<DropdownMenu />', () => {
     expect(wrapper.find(DropdownTrigger).find('i')).to.have.className(`fa-${icon}`)
   });
 
-  it('renders list elements for each props.items', () => {
+  it('is not expanded by default', () => {
+    expect(wrapper.state('expanded')).to.be.equal(false);
+  })
+
+  it('does not render the list elements if not expanded', () => {
+    const lis = wrapper.find(DropdownContent).find('li');
+    expect(lis.length).to.be.equal(0);
+  })
+
+  it('sets expanded to true when clicking the trigger', () => {
+    const wrapper = mount(<DropdownMenu icon={icon} items={items} size={size} />);
+    wrapper.find(DropdownTrigger).first().simulate('click');
+    expect(wrapper.state('expanded')).to.be.equal(true);
+  })
+
+  // Error: ReactWrapper::state() can only be called on the root
+  /*it('sets expanded to false when clicking outside', () => {
+    const wrapper = mount((
+      <div>
+        <DropdownMenu icon={icon} items={items} size={size} />
+        <span />
+      </div>
+    ));
+
+    wrapper.find(DropdownTrigger).first().simulate('click');
+    expect(wrapper.find(DropdownMenu).first().state('expanded')).to.be.equal(true);
+
+    wrapper.find('span').first().simulate('click');
+    expect(wrapper.find(DropdownMenu).first().state('expanded')).to.be.equal(false);
+  })*/
+
+  it('renders list elements for each props.items if expanded', () => {
+    const wrapper = mount(<DropdownMenu icon={icon} items={items} size={size} />);
+    wrapper.find(DropdownTrigger).first().simulate('click');
     const lis = wrapper.find(DropdownContent).find('li');
     expect(lis.length).to.be.equal(items.length);
   });
@@ -57,7 +90,7 @@ describe('<DropdownMenu />', () => {
 
   it('uses the action passed in via props.items as click handler', () => {
     const wrapper = mount(<DropdownMenu icon={icon} items={items} size={size} />);
-
+    wrapper.find(DropdownTrigger).first().simulate('click');
     wrapper.find(DropdownContent).find('li a').first().simulate('click');
     expect(action.calledOnce).to.equal(true);
   });