about summary refs log tree commit diff
path: root/app/javascript/flavours/glitch/features/composer/options
diff options
context:
space:
mode:
authorkibigo! <marrus-sh@users.noreply.github.com>2018-01-03 12:36:21 -0800
committerkibigo! <marrus-sh@users.noreply.github.com>2018-01-04 18:33:13 -0800
commit42f50049ff29ccdc484c22f8a5a19cda2dd03a5b (patch)
tree5e04c0bc0106f634316202eb8dc2f8c356d1b556 /app/javascript/flavours/glitch/features/composer/options
parentb4a3792201ccc01713b536e50428e027bd094d2b (diff)
WIP <Compose> Refactor; 1000 tiny edits
Diffstat (limited to 'app/javascript/flavours/glitch/features/composer/options')
-rw-r--r--app/javascript/flavours/glitch/features/composer/options/dropdown/content/index.js138
-rw-r--r--app/javascript/flavours/glitch/features/composer/options/dropdown/content/item/index.js (renamed from app/javascript/flavours/glitch/features/composer/options/dropdown/item/index.js)17
-rw-r--r--app/javascript/flavours/glitch/features/composer/options/dropdown/index.js221
-rw-r--r--app/javascript/flavours/glitch/features/composer/options/index.js25
4 files changed, 259 insertions, 142 deletions
diff --git a/app/javascript/flavours/glitch/features/composer/options/dropdown/content/index.js b/app/javascript/flavours/glitch/features/composer/options/dropdown/content/index.js
new file mode 100644
index 000000000..28bdfc0db
--- /dev/null
+++ b/app/javascript/flavours/glitch/features/composer/options/dropdown/content/index.js
@@ -0,0 +1,138 @@
+//  Package imports.
+import PropTypes from 'prop-types';
+import React from 'react';
+import spring from 'react-motion/lib/spring';
+
+//  Components.
+import ComposerOptionsDropdownContentItem from './item';
+
+//  Utils.
+import { withPassive } from 'flavours/glitch/util/dom_helpers';
+import Motion from 'flavours/glitch/util/optional_motion';
+import { assignHandlers } from 'flavours/glitch/util/react_helpers';
+
+//  Handlers.
+const handlers = {
+
+  //  When the document is clicked elsewhere, we close the dropdown.
+  handleDocumentClick ({ target }) {
+    const { node } = this;
+    const { onClose } = this.props;
+    if (onClose && node && !node.contains(target)) {
+      onClose();
+    }
+  },
+
+  //  Stores our node in `this.node`.
+  handleRef (node) {
+    this.node = node;
+  },
+};
+
+//  The spring to use with our motion.
+const springMotion = spring(1, {
+  damping: 35,
+  stiffness: 400,
+});
+
+//  The component.
+export default class ComposerOptionsDropdownContent extends React.PureComponent {
+
+  //  Constructor.
+  constructor (props) {
+    super(props);
+    assignHandlers(this, handlers);
+
+    //  Instance variables.
+    this.node = null;
+  }
+
+  //  On mounting, we add our listeners.
+  componentDidMount () {
+    const { handleDocumentClick } = this.handlers;
+    document.addEventListener('click', handleDocumentClick, false);
+    document.addEventListener('touchend', handleDocumentClick, withPassive);
+  }
+
+  //  On unmounting, we remove our listeners.
+  componentWillUnmount () {
+    const { handleDocumentClick } = this.handlers;
+    document.removeEventListener('click', handleDocumentClick, false);
+    document.removeEventListener('touchend', handleDocumentClick, withPassive);
+  }
+
+  //  Rendering.
+  render () {
+    const { handleRef } = this.handlers;
+    const {
+      items,
+      onChange,
+      onClose,
+      style,
+      value,
+    } = this.props;
+
+    //  The result.
+    return (
+      <Motion
+        defaultStyle={{
+          opacity: 0,
+          scaleX: 0.85,
+          scaleY: 0.75,
+        }}
+        style={{
+          opacity: springMotion,
+          scaleX: springMotion,
+          scaleY: springMotion,
+        }}
+      >
+        {({ opacity, scaleX, scaleY }) => (
+          <div
+            className='composer--options--dropdown--content'
+            ref={handleRef}
+            style={{
+              ...style,
+              opacity: opacity,
+              transform: `scale(${scaleX}, ${scaleY})`,
+            }}
+          >
+            {items.map(
+              ({
+                name,
+                ...rest
+              }) => (
+                <ComposerOptionsDropdownContentItem
+                  active={name === value}
+                  key={name}
+                  name={name}
+                  onChange={onChange}
+                  onClose={onClose}
+                  options={rest}
+                />
+              )
+            )}
+          </div>
+        )}
+      </Motion>
+    );
+  }
+
+}
+
+//  Props.
+ComposerOptionsDropdownContent.propTypes = {
+  items: PropTypes.arrayOf(PropTypes.shape({
+    icon: PropTypes.string,
+    meta: PropTypes.node,
+    name: PropTypes.string.isRequired,
+    on: PropTypes.bool,
+    text: PropTypes.node,
+  })).isRequired,
+  onChange: PropTypes.func,
+  onClose: PropTypes.func,
+  style: PropTypes.object,
+  value: PropTypes.string,
+};
+
+//  Default props.
+ComposerOptionsDropdownContent.defaultProps = { style: {} };
diff --git a/app/javascript/flavours/glitch/features/composer/options/dropdown/item/index.js b/app/javascript/flavours/glitch/features/composer/options/dropdown/content/item/index.js
index e9047dc50..605c945bd 100644
--- a/app/javascript/flavours/glitch/features/composer/options/dropdown/item/index.js
+++ b/app/javascript/flavours/glitch/features/composer/options/dropdown/content/item/index.js
@@ -14,7 +14,7 @@ import { assignHandlers } from 'flavours/glitch/util/react_helpers';
 const handlers = {
 
   //  This function activates the dropdown item.
-  activate (e) {
+  handleActivate (e) {
     const {
       name,
       onChange,
@@ -35,11 +35,10 @@ const handlers = {
       onChange(name);
     }
   },
-
 };
 
 //  The component.
-export default class ComposerOptionsDropdownItem extends React.PureComponent {
+export default class ComposerOptionsDropdownContentItem extends React.PureComponent {
 
   //  Constructor.
   constructor (props) {
@@ -49,7 +48,7 @@ export default class ComposerOptionsDropdownItem extends React.PureComponent {
 
   //  Rendering.
   render () {
-    const { activate } = this.handlers;
+    const { handleActivate } = this.handlers;
     const {
       active,
       options: {
@@ -59,7 +58,7 @@ export default class ComposerOptionsDropdownItem extends React.PureComponent {
         text,
       },
     } = this.props;
-    const computedClass = classNames('composer--options--dropdown_item', {
+    const computedClass = classNames('composer--options--dropdown--content--item', {
       active,
       lengthy: meta,
       'toggled-off': !on && on !== null && typeof on !== 'undefined',
@@ -71,8 +70,8 @@ export default class ComposerOptionsDropdownItem extends React.PureComponent {
     return (
       <div
         className={computedClass}
-        onClick={activate}
-        onKeyDown={activate}
+        onClick={handleActivate}
+        onKeyDown={handleActivate}
         role='button'
         tabIndex='0'
       >
@@ -85,7 +84,7 @@ export default class ComposerOptionsDropdownItem extends React.PureComponent {
             return (
               <Toggle
                 checked={on}
-                onChange={activate}
+                onChange={handleActivate}
               />
             );
           case !!icon:
@@ -113,7 +112,7 @@ export default class ComposerOptionsDropdownItem extends React.PureComponent {
 };
 
 //  Props.
-ComposerOptionsDropdownItem.propTypes = {
+ComposerOptionsDropdownContentItem.propTypes = {
   active: PropTypes.bool,
   name: PropTypes.string,
   onChange: PropTypes.func,
diff --git a/app/javascript/flavours/glitch/features/composer/options/dropdown/index.js b/app/javascript/flavours/glitch/features/composer/options/dropdown/index.js
index daed4ec8a..d63d90a9f 100644
--- a/app/javascript/flavours/glitch/features/composer/options/dropdown/index.js
+++ b/app/javascript/flavours/glitch/features/composer/options/dropdown/index.js
@@ -2,108 +2,120 @@
 import classNames from 'classnames';
 import PropTypes from 'prop-types';
 import React from 'react';
-import spring from 'react-motion/lib/spring';
 import Overlay from 'react-overlays/lib/Overlay';
 
 //  Components.
 import IconButton from 'flavours/glitch/components/icon_button';
-import ComposerOptionsDropdownItem from './item';
+import ComposerOptionsDropdownContent from './content';
 
 //  Utils.
-import { withPassive } from 'flavours/glitch/util/dom_helpers';
 import { isUserTouching } from 'flavours/glitch/util/is_mobile';
-import Motion from 'flavours/glitch/util/optional_motion';
 import { assignHandlers } from 'flavours/glitch/util/react_helpers';
 
-//  We'll use this to define our various transitions.
-const springMotion = spring(1, {
-  damping: 35,
-  stiffness: 400,
-});
-
 //  Handlers.
 const handlers = {
 
   //  Closes the dropdown.
-  close () {
+  handleClose () {
     this.setState({ open: false });
   },
 
-  //  When the document is clicked elsewhere, we close the dropdown.
-  documentClick ({ target }) {
-    const { node } = this;
-    const { onClose } = this.props;
-    if (onClose && node && !node.contains(target)) {
-      onClose();
-    }
-  },
-
   //  The enter key toggles the dropdown's open state, and the escape
   //  key closes it.
-  keyDown ({ key }) {
+  handleKeyDown ({ key }) {
     const {
-      close,
-      toggle,
+      handleClose,
+      handleToggle,
     } = this.handlers;
     switch (key) {
     case 'Enter':
-      toggle();
+      handleToggle();
       break;
     case 'Escape':
-      close();
+      handleClose();
       break;
     }
   },
 
-  //  Toggles opening and closing the dropdown.
-  toggle () {
+  //  Creates an action modal object.
+  handleMakeModal () {
+    const component = this;
     const {
       items,
       onChange,
-      onModalClose,
       onModalOpen,
+      onModalClose,
       value,
     } = this.props;
+
+    //  Required props.
+    if (!(onChange && onModalOpen && onModalClose && items)) {
+      return null;
+    }
+
+    //  The object.
+    return {
+      actions: items.map(
+        ({
+          name,
+          ...rest
+        }) => ({
+          ...rest,
+          active: value && name === value,
+          name,
+          onClick (e) {
+            e.preventDefault();  //  Prevents focus from changing
+            onModalClose();
+            onChange(name);
+          },
+          onPassiveClick (e) {
+            e.preventDefault();  //  Prevents focus from changing
+            onChange(name);
+            component.setState({ needsModalUpdate: true });
+          },
+        })
+      ),
+    };
+  },
+
+  //  Toggles opening and closing the dropdown.
+  handleToggle () {
+    const { handleMakeModal } = this.handlers;
+    const { onModalOpen } = this.props;
     const { open } = this.state;
 
     //  If this is a touch device, we open a modal instead of the
     //  dropdown.
-    if (onModalClose && isUserTouching()) {
-      if (open) {
-        onModalClose();
-      } else if (onChange && onModalOpen) {
-        onModalOpen({
-          actions: items.map(
-            ({
-              name,
-              ...rest
-            }) => ({
-              ...rest,
-              active: value && name === value,
-              name,
-              onClick (e) {
-                e.preventDefault();  //  Prevents focus from changing
-                onModalClose();
-                onChange(name);
-              },
-              onPassiveClick (e) {
-                e.preventDefault();  //  Prevents focus from changing
-                onChange(name);
-              },
-            })
-          ),
-        });
+    if (isUserTouching()) {
+
+      //  This gets the modal to open.
+      const modal = handleMakeModal();
+
+      //  If we can, we then open the modal.
+      if (modal && onModalOpen) {
+        onModalOpen(modal);
+        return;
       }
+    }
 
     //  Otherwise, we just set our state to open.
-    } else {
-      this.setState({ open: !open });
-    }
+    this.setState({ open: !open });
   },
 
-  //  Stores our node in `this.node`.
-  ref (node) {
-    this.node = node;
+  //  If our modal is open and our props update, we need to also update
+  //  the modal.
+  handleUpdate () {
+    const { handleMakeModal } = this.handlers;
+    const { onModalOpen } = this.props;
+    const { needsModalUpdate } = this.state;
+
+    //  Gets our modal object.
+    const modal = handleMakeModal();
+
+    //  Reopens the modal with the new object.
+    if (needsModalUpdate && modal && onModalOpen) {
+      onModalOpen(modal);
+    }
   },
 };
 
@@ -114,33 +126,31 @@ export default class ComposerOptionsDropdown extends React.PureComponent {
   constructor (props) {
     super(props);
     assignHandlers(this, handlers);
-    this.state = { open: false };
-
-    //  Instance variables.
-    this.node = null;
+    this.state = {
+      needsModalUpdate: false,
+      open: false,
+    };
   }
 
-  //  On mounting, we add our listeners.
-  componentDidMount () {
-    const { documentClick } = this.handlers;
-    document.addEventListener('click', documentClick, false);
-    document.addEventListener('touchend', documentClick, withPassive);
-  }
-
-  //  On unmounting, we remove our listeners.
-  componentWillUnmount () {
-    const { documentClick } = this.handlers;
-    document.removeEventListener('click', documentClick, false);
-    document.removeEventListener('touchend', documentClick, withPassive);
+  //  Updates our modal as necessary.
+  componentDidUpdate (prevProps) {
+    const { handleUpdate } = this.handlers;
+    const { items } = this.props;
+    const { needsModalUpdate } = this.state;
+    if (needsModalUpdate && items.find(
+      (item, i) => item.on !== prevProps.items[i].on
+    )) {
+      handleUpdate();
+      this.setState({ needsModalUpdate: false });
+    }
   }
 
   //  Rendering.
   render () {
     const {
-      close,
-      keyDown,
-      ref,
-      toggle,
+      handleClose,
+      handleKeyDown,
+      handleToggle,
     } = this.handlers;
     const {
       active,
@@ -154,22 +164,21 @@ export default class ComposerOptionsDropdown extends React.PureComponent {
     const { open } = this.state;
     const computedClass = classNames('composer--options--dropdown', {
       active,
-      open: open || active,
+      open,
     });
 
     //  The result.
     return (
       <div
         className={computedClass}
-        onKeyDown={keyDown}
-        ref={ref}
+        onKeyDown={handleKeyDown}
       >
         <IconButton
           active={open || active}
           className='value'
           disabled={disabled}
           icon={icon}
-          onClick={toggle}
+          onClick={handleToggle}
           size={18}
           style={{
             height: null,
@@ -178,49 +187,17 @@ export default class ComposerOptionsDropdown extends React.PureComponent {
           title={title}
         />
         <Overlay
+          containerPadding={20}
           placement='bottom'
           show={open}
           target={this}
         >
-          <Motion
-            defaultStyle={{
-              opacity: 0,
-              scaleX: 0.85,
-              scaleY: 0.75,
-            }}
-            style={{
-              opacity: springMotion,
-              scaleX: springMotion,
-              scaleY: springMotion,
-            }}
-          >
-            {({ opacity, scaleX, scaleY }) => (
-              <div
-                className='composer--options--dropdown__dropdown'
-                ref={this.setRef}
-                style={{
-                  opacity: opacity,
-                  transform: `scale(${scaleX}, ${scaleY})`,
-                }}
-              >
-                {items.map(
-                  ({
-                    name,
-                    ...rest
-                  }) => (
-                    <ComposerOptionsDropdownItem
-                      active={name === value}
-                      key={name}
-                      name={name}
-                      onChange={onChange}
-                      onClose={close}
-                      options={rest}
-                    />
-                  )
-                )}
-              </div>
-            )}
-          </Motion>
+          <ComposerOptionsDropdownContent
+            items={items}
+            onChange={onChange}
+            onClose={handleClose}
+            value={value}
+          />
         </Overlay>
       </div>
     );
diff --git a/app/javascript/flavours/glitch/features/composer/options/index.js b/app/javascript/flavours/glitch/features/composer/options/index.js
index ea998a421..e805372ab 100644
--- a/app/javascript/flavours/glitch/features/composer/options/index.js
+++ b/app/javascript/flavours/glitch/features/composer/options/index.js
@@ -95,7 +95,7 @@ const messages = defineMessages({
 const handlers = {
 
   //  Handles file selection.
-  changeFiles ({ target: { files } }) {
+  handleChangeFiles ({ target: { files } }) {
     const { onUpload } = this.props;
     if (files.length && onUpload) {
       onUpload(files);
@@ -103,7 +103,7 @@ const handlers = {
   },
 
   //  Handles attachment clicks.
-  clickAttach (name) {
+  handleClickAttach (name) {
     const { fileElement } = this;
     const { onDoodleOpen } = this.props;
 
@@ -123,7 +123,7 @@ const handlers = {
   },
 
   //  Handles a ref to the file input.
-  refFileElement (fileElement) {
+  handleRefFileElement (fileElement) {
     this.fileElement = fileElement;
   },
 };
@@ -143,9 +143,9 @@ export default class ComposerOptions extends React.PureComponent {
   //  Rendering.
   render () {
     const {
-      changeFiles,
-      clickAttach,
-      refFileElement,
+      handleChangeFiles,
+      handleClickAttach,
+      handleRefFileElement,
     } = this.handlers;
     const {
       acceptContentTypes,
@@ -159,6 +159,7 @@ export default class ComposerOptions extends React.PureComponent {
       onModalClose,
       onModalOpen,
       onToggleAdvancedOption,
+      onToggleSpoiler,
       privacy,
       resetFileKey,
       sensitive,
@@ -201,8 +202,8 @@ export default class ComposerOptions extends React.PureComponent {
           accept={acceptContentTypes}
           disabled={disabled || full}
           key={resetFileKey}
-          onChange={changeFiles}
-          ref={refFileElement}
+          onChange={handleChangeFiles}
+          ref={handleRefFileElement}
           type='file'
           {...hiddenComponent}
         />
@@ -221,10 +222,10 @@ export default class ComposerOptions extends React.PureComponent {
               text: <FormattedMessage {...messages.doodle} />,
             },
           ]}
-          onChange={clickAttach}
+          onChange={handleClickAttach}
           onModalClose={onModalClose}
           onModalOpen={onModalOpen}
-          title={messages.attach}
+          title={intl.formatMessage(messages.attach)}
         />
         <Motion
           defaultStyle={{ scale: 0.87 }}
@@ -279,6 +280,7 @@ export default class ComposerOptions extends React.PureComponent {
           active={spoiler}
           ariaControls='glitch.composer.spoiler.input'
           label='CW'
+          onClick={onToggleSpoiler}
           title={intl.formatMessage(messages.spoiler)}
         />
         <Dropdown
@@ -318,9 +320,10 @@ ComposerOptions.propTypes = {
   onModalClose: PropTypes.func,
   onModalOpen: PropTypes.func,
   onToggleAdvancedOption: PropTypes.func,
+  onToggleSpoiler: PropTypes.func,
   onUpload: PropTypes.func,
   privacy: PropTypes.string,
-  resetFileKey: PropTypes.string,
+  resetFileKey: PropTypes.number,
   sensitive: PropTypes.bool,
   spoiler: PropTypes.bool,
 };