Browse Source

add asynchronous emojione-picker (code-splitting) (#2863)

Nolan Lawson 7 years ago
parent
commit
df81bc4a97

+ 1 - 0
.babelrc

@@ -12,6 +12,7 @@
     ]
   ],
   "plugins": [
+    "syntax-dynamic-import",
     "transform-object-rest-spread",
     [
       "react-intl",

+ 36 - 5
app/javascript/mastodon/features/compose/components/emoji_picker_dropdown.js

@@ -1,6 +1,5 @@
 import React from 'react';
 import Dropdown, { DropdownTrigger, DropdownContent } from 'react-simple-dropdown';
-import EmojiPicker from 'emojione-picker';
 import PropTypes from 'prop-types';
 import { defineMessages, injectIntl } from 'react-intl';
 
@@ -37,12 +36,20 @@ const dropdownTriggerStyle = {
   width: '24px'
 }
 
+let EmojiPicker; // load asynchronously
+
 class EmojiPickerDropdown extends React.PureComponent {
 
   constructor (props, context) {
     super(props, context);
     this.setRef = this.setRef.bind(this);
     this.handleChange = this.handleChange.bind(this);
+    this.onHideDropdown = this.onHideDropdown.bind(this);
+    this.onShowDropdown = this.onShowDropdown.bind(this);
+    this.state = {
+      active: false,
+      loading: false
+    };
   }
 
   setRef (c) {
@@ -54,6 +61,24 @@ class EmojiPickerDropdown extends React.PureComponent {
     this.props.onPickEmoji(data);
   }
 
+  onShowDropdown () {
+    this.setState({active: true});
+    if (!EmojiPicker) {
+      this.setState({loading: true});
+      import('emojione-picker').then(TheEmojiPicker => {
+        EmojiPicker = TheEmojiPicker.default;
+        this.setState({loading: false});
+      }).catch(err => {
+        // TODO: show the user an error?
+        this.setState({loading: false});
+      });
+    }
+  }
+
+  onHideDropdown () {
+    this.setState({active: false});
+  }
+
   render () {
     const { intl } = this.props;
 
@@ -92,14 +117,20 @@ class EmojiPickerDropdown extends React.PureComponent {
       }
     }
 
+    const { active, loading } = this.state;
+
     return (
-      <Dropdown ref={this.setRef} style={dropdownStyle}>
+      <Dropdown ref={this.setRef} style={dropdownStyle} onShow={this.onShowDropdown} onHide={this.onHideDropdown}>
         <DropdownTrigger className='emoji-button' title={intl.formatMessage(messages.emoji)} style={dropdownTriggerStyle}>
-          <img draggable="false" className="emojione" alt="🙂" src="/emoji/1f602.svg" />
+          <img draggable="false"
+               className={`emojione ${active && loading ? "pulse-loading" : ''}`}
+               alt="🙂" src="/emoji/1f602.svg" />
         </DropdownTrigger>
-
         <DropdownContent className='dropdown__left'>
-          <EmojiPicker emojione={settings} onChange={this.handleChange} searchPlaceholder={intl.formatMessage(messages.emoji_search)} categories={categories} search={true} />
+          {
+            this.state.active && !this.state.loading &&
+            (<EmojiPicker emojione={settings} onChange={this.handleChange} searchPlaceholder={intl.formatMessage(messages.emoji_search)} categories={categories} search={true} />)
+          }
         </DropdownContent>
       </Dropdown>
     );

+ 14 - 0
app/javascript/styles/components.scss

@@ -2141,6 +2141,20 @@ button.icon-button.active i.fa-retweet {
   background: radial-gradient(ellipse, rgba($color4, 0.23) 0%, rgba($color4, 0) 60%);
 }
 
+@keyframes pulse {
+  0% {
+    opacity: 1;
+  }
+  100% {
+    opacity: 0.5;
+  }
+}
+
+.pulse-loading {
+  animation: pulse 1s ease-in-out infinite;
+  animation-direction: alternate;
+}
+
 .emoji-dialog {
   width: 245px;
   height: 270px;

+ 1 - 0
package.json

@@ -25,6 +25,7 @@
     "babel-plugin-lodash": "^3.2.11",
     "babel-plugin-react-intl": "^2.3.1",
     "babel-plugin-react-transform": "^2.0.2",
+    "babel-plugin-syntax-dynamic-import": "^6.18.0",
     "babel-plugin-transform-object-rest-spread": "^6.23.0",
     "babel-plugin-transform-react-constant-elements": "^6.23.0",
     "babel-plugin-transform-react-inline-elements": "^6.22.0",