about summary refs log tree commit diff
path: root/app/javascript/flavours/glitch/features/compose/components
diff options
context:
space:
mode:
authorThibaut Girka <thib@sitedethib.com>2019-04-21 12:09:52 +0200
committerThibG <thib@sitedethib.com>2019-04-22 20:15:47 +0200
commita243567a3e6100d65477162308e2c1bb5e056c21 (patch)
treee308927e5453acd62e09f6b6de05c269c362d5b8 /app/javascript/flavours/glitch/features/compose/components
parentc5f49a92dce9157debf3a68487dd30b6f0af6c4a (diff)
ComposerUploadForm → UploadForm + UploadFormContainer
Diffstat (limited to 'app/javascript/flavours/glitch/features/compose/components')
-rw-r--r--app/javascript/flavours/glitch/features/compose/components/compose_form.js28
-rw-r--r--app/javascript/flavours/glitch/features/compose/components/upload.js131
-rw-r--r--app/javascript/flavours/glitch/features/compose/components/upload_form.js28
-rw-r--r--app/javascript/flavours/glitch/features/compose/components/upload_progress.js42
4 files changed, 203 insertions, 26 deletions
diff --git a/app/javascript/flavours/glitch/features/compose/components/compose_form.js b/app/javascript/flavours/glitch/features/compose/components/compose_form.js
index ccbcba571..ecd1aed69 100644
--- a/app/javascript/flavours/glitch/features/compose/components/compose_form.js
+++ b/app/javascript/flavours/glitch/features/compose/components/compose_form.js
@@ -8,7 +8,7 @@ import ImmutablePureComponent from 'react-immutable-pure-component';
 import ComposerOptions from '../../composer/options';
 import ComposerPublisher from '../../composer/publisher';
 import ComposerTextarea from '../../composer/textarea';
-import ComposerUploadForm from '../../composer/upload_form';
+import UploadFormContainer from '../containers/upload_form_container';
 import PollFormContainer from '../containers/poll_form_container';
 import WarningContainer from '../containers/warning_container';
 import ReplyIndicatorContainer from '../containers/reply_indicator_container';
@@ -48,7 +48,6 @@ class ComposeForm extends ImmutablePureComponent {
     media: ImmutablePropTypes.list,
     preselectDate: PropTypes.instanceOf(Date),
     privacy: PropTypes.string,
-    progress: PropTypes.number,
     resetFileKey: PropTypes.number,
     sideArm: PropTypes.string,
     sensitive: PropTypes.bool,
@@ -65,7 +64,6 @@ class ComposeForm extends ImmutablePureComponent {
 
     //  Dispatch props.
     onChangeAdvancedOption: PropTypes.func,
-    onChangeDescription: PropTypes.func,
     onChangeSensitivity: PropTypes.func,
     onChangeSpoilerText: PropTypes.func,
     onChangeSpoilerness: PropTypes.func,
@@ -80,7 +78,6 @@ class ComposeForm extends ImmutablePureComponent {
     onOpenDoodleModal: PropTypes.func,
     onSelectSuggestion: PropTypes.func,
     onSubmit: PropTypes.func,
-    onUndoUpload: PropTypes.func,
     onUnmount: PropTypes.func,
     onUpload: PropTypes.func,
     onMediaDescriptionConfirm: PropTypes.func,
@@ -185,11 +182,6 @@ class ComposeForm extends ImmutablePureComponent {
     }
   }
 
-  //  Sets a reference to the upload form.
-  handleRefUploadForm = (uploadFormComponent) => {
-    this.uploadForm = uploadFormComponent;
-  }
-
   //  Sets a reference to the textarea.
   handleRefTextarea = (textareaComponent) => {
     if (textareaComponent) {
@@ -283,7 +275,6 @@ class ComposeForm extends ImmutablePureComponent {
       handleSecondarySubmit,
       handleSelect,
       handleSubmit,
-      handleRefUploadForm,
       handleRefTextarea,
     } = this;
     const {
@@ -299,7 +290,6 @@ class ComposeForm extends ImmutablePureComponent {
       media,
       poll,
       onChangeAdvancedOption,
-      onChangeDescription,
       onChangeSensitivity,
       onChangeSpoilerness,
       onChangeText,
@@ -310,11 +300,8 @@ class ComposeForm extends ImmutablePureComponent {
       onFetchSuggestions,
       onOpenActionsModal,
       onOpenDoodleModal,
-      onOpenFocalPointModal,
-      onUndoUpload,
       onUpload,
       privacy,
-      progress,
       resetFileKey,
       sensitive,
       showSearch,
@@ -370,18 +357,7 @@ class ComposeForm extends ImmutablePureComponent {
           value={text}
         />
         <div className='compose-form__modifiers'>
-          {isUploading || media && media.size ? (
-            <ComposerUploadForm
-              intl={intl}
-              media={media}
-              onChangeDescription={onChangeDescription}
-              onOpenFocalPointModal={onOpenFocalPointModal}
-              onRemove={onUndoUpload}
-              progress={progress}
-              uploading={isUploading}
-              handleRef={handleRefUploadForm}
-            />
-          ) : null}
+          <UploadFormContainer />
           <PollFormContainer />
         </div>
         <ComposerOptions
diff --git a/app/javascript/flavours/glitch/features/compose/components/upload.js b/app/javascript/flavours/glitch/features/compose/components/upload.js
new file mode 100644
index 000000000..84edf664e
--- /dev/null
+++ b/app/javascript/flavours/glitch/features/compose/components/upload.js
@@ -0,0 +1,131 @@
+import React from 'react';
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import PropTypes from 'prop-types';
+import Motion from 'flavours/glitch/util/optional_motion';
+import spring from 'react-motion/lib/spring';
+import ImmutablePureComponent from 'react-immutable-pure-component';
+import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
+import classNames from 'classnames';
+import Icon from 'flavours/glitch/components/icon';
+import { isUserTouching } from 'flavours/glitch/util/is_mobile';
+
+const messages = defineMessages({
+  description: { id: 'upload_form.description', defaultMessage: 'Describe for the visually impaired' },
+});
+
+//  The component.
+export default @injectIntl
+class Upload extends ImmutablePureComponent {
+
+  static contextTypes = {
+    router: PropTypes.object,
+  };
+
+  static propTypes = {
+    media: ImmutablePropTypes.map.isRequired,
+    intl: PropTypes.object.isRequired,
+    onUndo: PropTypes.func.isRequired,
+    onDescriptionChange: PropTypes.func.isRequired,
+    onOpenFocalPoint: PropTypes.func.isRequired,
+    onSubmit: PropTypes.func.isRequired,
+  };
+
+  state = {
+    hovered: false,
+    focused: false,
+    dirtyDescription: null,
+  };
+
+  handleKeyDown = (e) => {
+    if (e.keyCode === 13 && (e.ctrlKey || e.metaKey)) {
+      this.handleSubmit();
+    }
+  }
+
+  handleSubmit = () => {
+    this.handleInputBlur();
+    this.props.onSubmit(this.context.router.history);
+  }
+
+  handleUndoClick = e => {
+    e.stopPropagation();
+    this.props.onUndo(this.props.media.get('id'));
+  }
+
+  handleFocalPointClick = e => {
+    e.stopPropagation();
+    this.props.onOpenFocalPoint(this.props.media.get('id'));
+  }
+
+  handleInputChange = e => {
+    this.setState({ dirtyDescription: e.target.value });
+  }
+
+  handleMouseEnter = () => {
+    this.setState({ hovered: true });
+  }
+
+  handleMouseLeave = () => {
+    this.setState({ hovered: false });
+  }
+
+  handleInputFocus = () => {
+    this.setState({ focused: true });
+  }
+
+  handleClick = () => {
+    this.setState({ focused: true });
+  }
+
+  handleInputBlur = () => {
+    const { dirtyDescription } = this.state;
+
+    this.setState({ focused: false, dirtyDescription: null });
+
+    if (dirtyDescription !== null) {
+      this.props.onDescriptionChange(this.props.media.get('id'), dirtyDescription);
+    }
+  }
+
+  render () {
+    const { intl, media } = this.props;
+    const active          = this.state.hovered || this.state.focused || isUserTouching();
+    const description     = this.state.dirtyDescription || (this.state.dirtyDescription !== '' && media.get('description')) || '';
+    const computedClass   = classNames('composer--upload_form--item', { active });
+    const focusX = media.getIn(['meta', 'focus', 'x']);
+    const focusY = media.getIn(['meta', 'focus', 'y']);
+    const x = ((focusX /  2) + .5) * 100;
+    const y = ((focusY / -2) + .5) * 100;
+
+    return (
+      <div className={computedClass} tabIndex='0' onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave} onClick={this.handleClick} role='button'>
+        <Motion defaultStyle={{ scale: 0.8 }} style={{ scale: spring(1, { stiffness: 180, damping: 12, }) }}>
+          {({ scale }) => (
+            <div style={{ transform: `scale(${scale})`, backgroundImage: `url(${media.get('preview_url')})`, backgroundPosition: `${x}% ${y}%` }}>
+              <div className={classNames('composer--upload_form--actions', { active })}>
+                <button className='icon-button' onClick={this.handleUndoClick}><Icon icon='times' /> <FormattedMessage id='upload_form.undo' defaultMessage='Delete' /></button>
+                {media.get('type') === 'image' && <button className='icon-button' onClick={this.handleFocalPointClick}><Icon id='crosshairs' /> <FormattedMessage id='upload_form.focus' defaultMessage='Crop' /></button>}
+              </div>
+
+              <div className={classNames('composer--upload_form--description', { active })}>
+                <label>
+                  <span style={{ display: 'none' }}>{intl.formatMessage(messages.description)}</span>
+                  <textarea
+                    placeholder={intl.formatMessage(messages.description)}
+                    value={description}
+                    maxLength={420}
+                    onFocus={this.handleInputFocus}
+                    onChange={this.handleInputChange}
+                    onBlur={this.handleInputBlur}
+                    onKeyDown={this.handleKeyDown}
+                  />
+                </label>
+              </div>
+            </div>
+          )}
+        </Motion>
+      </div>
+    );
+  }
+
+}
diff --git a/app/javascript/flavours/glitch/features/compose/components/upload_form.js b/app/javascript/flavours/glitch/features/compose/components/upload_form.js
new file mode 100644
index 000000000..a126cc7e4
--- /dev/null
+++ b/app/javascript/flavours/glitch/features/compose/components/upload_form.js
@@ -0,0 +1,28 @@
+import React from 'react';
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import UploadProgressContainer from '../containers/upload_progress_container';
+import ImmutablePureComponent from 'react-immutable-pure-component';
+import UploadContainer from '../containers/upload_container';
+
+export default class UploadForm extends ImmutablePureComponent {
+  static propTypes = {
+    mediaIds: ImmutablePropTypes.list.isRequired,
+  };
+
+  render () {
+    const { mediaIds } = this.props;
+
+    return (
+      <div className='composer--upload_form'>
+        <UploadProgressContainer />
+
+        <div className='content'>
+          {mediaIds.map(id => (
+            <UploadContainer id={id} key={id} />
+          ))}
+        </div>
+      </div>
+    );
+  }
+
+}
diff --git a/app/javascript/flavours/glitch/features/compose/components/upload_progress.js b/app/javascript/flavours/glitch/features/compose/components/upload_progress.js
new file mode 100644
index 000000000..264c563f2
--- /dev/null
+++ b/app/javascript/flavours/glitch/features/compose/components/upload_progress.js
@@ -0,0 +1,42 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import Motion from 'flavours/glitch/util/optional_motion';
+import spring from 'react-motion/lib/spring';
+import { FormattedMessage } from 'react-intl';
+import Icon from 'flavours/glitch/components/icon';
+
+export default class UploadProgress extends React.PureComponent {
+
+  static propTypes = {
+    active: PropTypes.bool,
+    progress: PropTypes.number,
+  };
+
+  render () {
+    const { active, progress } = this.props;
+
+    if (!active) {
+      return null;
+    }
+
+    return (
+      <div className='composer--upload_form--progress'>
+        <Icon icon='upload' />
+
+        <div className='message'>
+          <FormattedMessage id='upload_progress.label' defaultMessage='Uploading...' />
+
+          <div className='backdrop'>
+            <Motion defaultStyle={{ width: 0 }} style={{ width: spring(progress) }}>
+              {({ width }) =>
+                (<div className='tracker' style={{ width: `${width}%` }}
+                />)
+              }
+            </Motion>
+          </div>
+        </div>
+      </div>
+    );
+  }
+
+}