Fancier drag & drop indicator, emoji icon for emoji, upload progress (fix #295)
This commit is contained in:
		
							parent
							
								
									3e2d6ea408
								
							
						
					
					
						commit
						d7c6c6dbe1
					
				| 
						 | 
				
			
			@ -142,7 +142,8 @@ export function uploadCompose(files) {
 | 
			
		|||
 | 
			
		||||
export function uploadComposeRequest() {
 | 
			
		||||
  return {
 | 
			
		||||
    type: COMPOSE_UPLOAD_REQUEST
 | 
			
		||||
    type: COMPOSE_UPLOAD_REQUEST,
 | 
			
		||||
    skipLoading: true
 | 
			
		||||
  };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -157,14 +158,16 @@ export function uploadComposeProgress(loaded, total) {
 | 
			
		|||
export function uploadComposeSuccess(media) {
 | 
			
		||||
  return {
 | 
			
		||||
    type: COMPOSE_UPLOAD_SUCCESS,
 | 
			
		||||
    media: media
 | 
			
		||||
    media: media,
 | 
			
		||||
    skipLoading: true
 | 
			
		||||
  };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export function uploadComposeFail(error) {
 | 
			
		||||
  return {
 | 
			
		||||
    type: COMPOSE_UPLOAD_FAIL,
 | 
			
		||||
    error: error
 | 
			
		||||
    error: error,
 | 
			
		||||
    skipLoading: true
 | 
			
		||||
  };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -33,7 +33,6 @@ const AutosuggestTextarea = React.createClass({
 | 
			
		|||
    value: React.PropTypes.string,
 | 
			
		||||
    suggestions: ImmutablePropTypes.list,
 | 
			
		||||
    disabled: React.PropTypes.bool,
 | 
			
		||||
    fileDropDate: React.PropTypes.instanceOf(Date),
 | 
			
		||||
    placeholder: React.PropTypes.string,
 | 
			
		||||
    onSuggestionSelected: React.PropTypes.func.isRequired,
 | 
			
		||||
    onSuggestionsClearRequested: React.PropTypes.func.isRequired,
 | 
			
		||||
| 
						 | 
				
			
			@ -46,8 +45,6 @@ const AutosuggestTextarea = React.createClass({
 | 
			
		|||
 | 
			
		||||
  getInitialState () {
 | 
			
		||||
    return {
 | 
			
		||||
      isFileDragging: false,
 | 
			
		||||
      fileDraggingDate: undefined,
 | 
			
		||||
      suggestionsHidden: false,
 | 
			
		||||
      selectedSuggestion: 0,
 | 
			
		||||
      lastToken: null,
 | 
			
		||||
| 
						 | 
				
			
			@ -139,41 +136,12 @@ const AutosuggestTextarea = React.createClass({
 | 
			
		|||
    if (nextProps.suggestions !== this.props.suggestions && nextProps.suggestions.size > 0 && this.state.suggestionsHidden) {
 | 
			
		||||
      this.setState({ suggestionsHidden: false });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const fileDropDate = nextProps.fileDropDate;
 | 
			
		||||
    const { isFileDragging, fileDraggingDate } = this.state;
 | 
			
		||||
 | 
			
		||||
    /*
 | 
			
		||||
     * We can't detect drop events, because they might not be on the textarea (the app allows dropping anywhere in the
 | 
			
		||||
     * window). Instead, on-drop, we notify this textarea to stop its hover effect by passing in a prop with the
 | 
			
		||||
     * drop-date.
 | 
			
		||||
     */
 | 
			
		||||
    if (isFileDragging && fileDraggingDate && fileDropDate // if dragging when props updated, and dates aren't undefined
 | 
			
		||||
      && fileDropDate > fileDraggingDate) { // and if the drop date is now greater than when we started dragging
 | 
			
		||||
      // then we should stop dragging
 | 
			
		||||
      this.setState({
 | 
			
		||||
        isFileDragging: false
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  setTextarea (c) {
 | 
			
		||||
    this.textarea = c;
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  onDragEnter () {
 | 
			
		||||
    this.setState({
 | 
			
		||||
      isFileDragging: true,
 | 
			
		||||
      fileDraggingDate: new Date()
 | 
			
		||||
    })
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  onDragExit () {
 | 
			
		||||
    this.setState({
 | 
			
		||||
      isFileDragging: false
 | 
			
		||||
    })
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  onPaste (e) {
 | 
			
		||||
    if (e.clipboardData && e.clipboardData.files.length === 1) {
 | 
			
		||||
      this.props.onPaste(e.clipboardData.files)
 | 
			
		||||
| 
						 | 
				
			
			@ -182,9 +150,9 @@ const AutosuggestTextarea = React.createClass({
 | 
			
		|||
  },
 | 
			
		||||
 | 
			
		||||
  render () {
 | 
			
		||||
    const { value, suggestions, fileDropDate, disabled, placeholder, onKeyUp } = this.props;
 | 
			
		||||
    const { isFileDragging, suggestionsHidden, selectedSuggestion } = this.state;
 | 
			
		||||
    const className = isFileDragging ? 'autosuggest-textarea__textarea file-drop' : 'autosuggest-textarea__textarea';
 | 
			
		||||
    const { value, suggestions, disabled, placeholder, onKeyUp } = this.props;
 | 
			
		||||
    const { suggestionsHidden, selectedSuggestion } = this.state;
 | 
			
		||||
    const className = 'autosuggest-textarea__textarea';
 | 
			
		||||
    const style     = { direction: 'ltr' };
 | 
			
		||||
 | 
			
		||||
    if (isRtl(value)) {
 | 
			
		||||
| 
						 | 
				
			
			@ -204,8 +172,6 @@ const AutosuggestTextarea = React.createClass({
 | 
			
		|||
          onKeyDown={this.onKeyDown}
 | 
			
		||||
          onKeyUp={onKeyUp}
 | 
			
		||||
          onBlur={this.onBlur}
 | 
			
		||||
          onDragEnter={this.onDragEnter}
 | 
			
		||||
          onDragExit={this.onDragExit}
 | 
			
		||||
          onPaste={this.onPaste}
 | 
			
		||||
          style={style}
 | 
			
		||||
        />
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -45,7 +45,7 @@ const ColumnCollapsable = React.createClass({
 | 
			
		|||
 | 
			
		||||
    return (
 | 
			
		||||
      <div style={{ position: 'relative' }}>
 | 
			
		||||
        <div style={{...iconStyle }} className={collapsedClassName} onClick={this.handleToggleCollapsed}><i className={`fa fa-${icon}`} /></div>
 | 
			
		||||
        <div style={{...iconStyle }} className={`column-icon ${collapsedClassName}`} onClick={this.handleToggleCollapsed}><i className={`fa fa-${icon}`} /></div>
 | 
			
		||||
 | 
			
		||||
        <Motion defaultStyle={{ opacity: 0, height: 0 }} style={{ opacity: spring(collapsed ? 0 : 100), height: spring(collapsed ? 0 : fullHeight, collapsed ? undefined : { stiffness: 150, damping: 9 }) }}>
 | 
			
		||||
          {({ opacity, height }) =>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -34,7 +34,6 @@ const ComposeForm = React.createClass({
 | 
			
		|||
    private: React.PropTypes.bool,
 | 
			
		||||
    unlisted: React.PropTypes.bool,
 | 
			
		||||
    spoiler_text: React.PropTypes.string,
 | 
			
		||||
    fileDropDate: React.PropTypes.instanceOf(Date),
 | 
			
		||||
    focusDate: React.PropTypes.instanceOf(Date),
 | 
			
		||||
    preselectDate: React.PropTypes.instanceOf(Date),
 | 
			
		||||
    is_submitting: React.PropTypes.bool,
 | 
			
		||||
| 
						 | 
				
			
			@ -161,7 +160,6 @@ const ComposeForm = React.createClass({
 | 
			
		|||
          ref={this.setAutosuggestTextarea}
 | 
			
		||||
          placeholder={intl.formatMessage(messages.placeholder)}
 | 
			
		||||
          disabled={disabled}
 | 
			
		||||
          fileDropDate={this.props.fileDropDate}
 | 
			
		||||
          value={this.props.text}
 | 
			
		||||
          onChange={this.handleChange}
 | 
			
		||||
          suggestions={this.props.suggestions}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,7 +4,7 @@ import PureRenderMixin from 'react-addons-pure-render-mixin';
 | 
			
		|||
import { defineMessages, injectIntl } from 'react-intl';
 | 
			
		||||
 | 
			
		||||
const messages = defineMessages({
 | 
			
		||||
  emoji: { id: 'emoji_button.label', defaultMessage: 'Emoji' }
 | 
			
		||||
  emoji: { id: 'emoji_button.label', defaultMessage: 'Insert emoji' }
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const settings = {
 | 
			
		||||
| 
						 | 
				
			
			@ -36,8 +36,8 @@ const EmojiPickerDropdown = React.createClass({
 | 
			
		|||
 | 
			
		||||
    return (
 | 
			
		||||
      <Dropdown ref={this.setRef} style={{ marginLeft: '5px' }}>
 | 
			
		||||
        <DropdownTrigger className='icon-button' title={intl.formatMessage(messages.emoji)} style={{ fontSize: `24px`, width: `24px`, lineHeight: `24px`, display: 'block', marginLeft: '2px' }}>
 | 
			
		||||
          <i className={`fa fa-smile-o`} style={{ verticalAlign: 'middle' }} />
 | 
			
		||||
        <DropdownTrigger className='icon-button emoji-button' title={intl.formatMessage(messages.emoji)} style={{ fontSize: `24px`, width: `24px`, lineHeight: `24px`, display: 'block', marginLeft: '2px' }}>
 | 
			
		||||
          <img className="emojione" alt="🙂" src="/emoji/1f642.png" />
 | 
			
		||||
        </DropdownTrigger>
 | 
			
		||||
 | 
			
		||||
        <DropdownContent>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,6 +2,8 @@ import PureRenderMixin from 'react-addons-pure-render-mixin';
 | 
			
		|||
import ImmutablePropTypes from 'react-immutable-proptypes';
 | 
			
		||||
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';
 | 
			
		||||
 | 
			
		||||
const messages = defineMessages({
 | 
			
		||||
  undo: { id: 'upload_form.undo', defaultMessage: 'Undo' }
 | 
			
		||||
| 
						 | 
				
			
			@ -11,7 +13,6 @@ const UploadForm = React.createClass({
 | 
			
		|||
 | 
			
		||||
  propTypes: {
 | 
			
		||||
    media: ImmutablePropTypes.list.isRequired,
 | 
			
		||||
    is_uploading: React.PropTypes.bool,
 | 
			
		||||
    onRemoveFile: React.PropTypes.func.isRequired,
 | 
			
		||||
    intl: React.PropTypes.object.isRequired
 | 
			
		||||
  },
 | 
			
		||||
| 
						 | 
				
			
			@ -21,20 +22,21 @@ const UploadForm = React.createClass({
 | 
			
		|||
  render () {
 | 
			
		||||
    const { intl, media } = this.props;
 | 
			
		||||
 | 
			
		||||
    if (!media.size) {
 | 
			
		||||
      return null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const uploads = media.map(attachment => (
 | 
			
		||||
      <div key={attachment.get('id')} style={{ borderRadius: '4px', marginBottom: '10px' }} className='transparent-background'>
 | 
			
		||||
        <div style={{ width: '100%', height: '100px', borderRadius: '4px', background: `url(${attachment.get('preview_url')}) no-repeat center`, backgroundSize: 'cover' }}>
 | 
			
		||||
          <IconButton icon='times' title={intl.formatMessage(messages.undo)} size={36} onClick={this.props.onRemoveFile.bind(this, attachment.get('id'))} />
 | 
			
		||||
        </div>
 | 
			
		||||
    const uploads = media.map(attachment =>
 | 
			
		||||
      <div key={attachment.get('id')} style={{ marginBottom: '10px' }}>
 | 
			
		||||
        <Motion defaultStyle={{ scale: 0.8 }} style={{ scale: spring(1, { stiffness: 180, damping: 12 }) }}>
 | 
			
		||||
          {({ scale }) =>
 | 
			
		||||
            <div style={{ transform: `translateZ(0) scale(${scale})`, width: '100%', height: '100px', borderRadius: '4px', background: `url(${attachment.get('preview_url')}) no-repeat center`, backgroundSize: 'cover' }}>
 | 
			
		||||
              <IconButton icon='times' title={intl.formatMessage(messages.undo)} size={36} onClick={this.props.onRemoveFile.bind(this, attachment.get('id'))} />
 | 
			
		||||
            </div>
 | 
			
		||||
          }
 | 
			
		||||
        </Motion>
 | 
			
		||||
      </div>
 | 
			
		||||
    ));
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
      <div style={{ marginBottom: '20px', padding: '10px', overflow: 'hidden', flexShrink: '0' }}>
 | 
			
		||||
        <UploadProgressContainer />
 | 
			
		||||
        {uploads}
 | 
			
		||||
      </div>
 | 
			
		||||
    );
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,44 @@
 | 
			
		|||
import PureRenderMixin from 'react-addons-pure-render-mixin';
 | 
			
		||||
import { Motion, spring } from 'react-motion';
 | 
			
		||||
import { FormattedMessage } from 'react-intl';
 | 
			
		||||
 | 
			
		||||
const UploadProgress = React.createClass({
 | 
			
		||||
 | 
			
		||||
  propTypes: {
 | 
			
		||||
    active: React.PropTypes.bool,
 | 
			
		||||
    progress: React.PropTypes.number
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  mixins: [PureRenderMixin],
 | 
			
		||||
 | 
			
		||||
  render () {
 | 
			
		||||
    const { active, progress } = this.props;
 | 
			
		||||
 | 
			
		||||
    if (!active) {
 | 
			
		||||
      return null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
      <div className='upload-progress'>
 | 
			
		||||
        <div>
 | 
			
		||||
          <i className='fa fa-upload' />
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <div style={{ flex: '1 1 auto' }}>
 | 
			
		||||
          <FormattedMessage id='upload_progress.label' defaultMessage='Uploading...' />
 | 
			
		||||
 | 
			
		||||
          <div className='upload-progress__backdrop'>
 | 
			
		||||
            <Motion defaultStyle={{ width: 0 }} style={{ width: spring(progress) }}>
 | 
			
		||||
              {({ width }) =>
 | 
			
		||||
                <div className='upload-progress__tracker' style={{ width: `${width}%` }} />
 | 
			
		||||
              }
 | 
			
		||||
            </Motion>
 | 
			
		||||
          </div>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
export default UploadProgress;
 | 
			
		||||
| 
						 | 
				
			
			@ -30,7 +30,6 @@ const mapStateToProps = (state, props) => {
 | 
			
		|||
    spoiler_text: state.getIn(['compose', 'spoiler_text']),
 | 
			
		||||
    unlisted: state.getIn(['compose', 'unlisted'], ),
 | 
			
		||||
    private: state.getIn(['compose', 'private']),
 | 
			
		||||
    fileDropDate: state.getIn(['compose', 'fileDropDate']),
 | 
			
		||||
    focusDate: state.getIn(['compose', 'focusDate']),
 | 
			
		||||
    preselectDate: state.getIn(['compose', 'preselectDate']),
 | 
			
		||||
    is_submitting: state.getIn(['compose', 'is_submitting']),
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,9 @@
 | 
			
		|||
import { connect } from 'react-redux';
 | 
			
		||||
import UploadProgress from '../components/upload_progress';
 | 
			
		||||
 | 
			
		||||
const mapStateToProps = (state, props) => ({
 | 
			
		||||
  active: state.getIn(['compose', 'is_uploading']),
 | 
			
		||||
  progress: state.getIn(['compose', 'progress'])
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
export default connect(mapStateToProps)(UploadProgress);
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,32 @@
 | 
			
		|||
import PureRenderMixin from 'react-addons-pure-render-mixin';
 | 
			
		||||
import { Motion, spring } from 'react-motion';
 | 
			
		||||
import { FormattedMessage } from 'react-intl';
 | 
			
		||||
 | 
			
		||||
const UploadArea = React.createClass({
 | 
			
		||||
 | 
			
		||||
  propTypes: {
 | 
			
		||||
    active: React.PropTypes.bool
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  mixins: [PureRenderMixin],
 | 
			
		||||
 | 
			
		||||
  render () {
 | 
			
		||||
    const { active } = this.props;
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
      <Motion defaultStyle={{ backgroundOpacity: 0, backgroundScale: 0.95 }} style={{ backgroundOpacity: spring(active ? 1 : 0, { stiffness: 150, damping: 15 }), backgroundScale: spring(active ? 1 : 0.95, { stiffness: 200, damping: 3 }) }}>
 | 
			
		||||
        {({ backgroundOpacity, backgroundScale }) =>
 | 
			
		||||
          <div className='upload-area' style={{ visibility: active ? 'visible' : 'hidden', opacity: backgroundOpacity }}>
 | 
			
		||||
            <div className='upload-area__drop'>
 | 
			
		||||
              <div className='upload-area__background' style={{ transform: `translateZ(0) scale(${backgroundScale})` }} />
 | 
			
		||||
              <div className='upload-area__content'><FormattedMessage id='upload_area.title' defaultMessage='Drag & drop to upload' /></div>
 | 
			
		||||
            </div>
 | 
			
		||||
          </div>
 | 
			
		||||
        }
 | 
			
		||||
      </Motion>
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
export default UploadArea;
 | 
			
		||||
| 
						 | 
				
			
			@ -13,6 +13,7 @@ import { debounce } from 'react-decoration';
 | 
			
		|||
import { uploadCompose } from '../../actions/compose';
 | 
			
		||||
import { refreshTimeline } from '../../actions/timelines';
 | 
			
		||||
import { refreshNotifications } from '../../actions/notifications';
 | 
			
		||||
import UploadArea from './components/upload_area';
 | 
			
		||||
 | 
			
		||||
const UI = React.createClass({
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -23,7 +24,8 @@ const UI = React.createClass({
 | 
			
		|||
 | 
			
		||||
  getInitialState () {
 | 
			
		||||
    return {
 | 
			
		||||
      width: window.innerWidth
 | 
			
		||||
      width: window.innerWidth,
 | 
			
		||||
      draggingOver: false
 | 
			
		||||
    };
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -41,7 +43,7 @@ const UI = React.createClass({
 | 
			
		|||
    e.dataTransfer.dropEffect = 'copy';
 | 
			
		||||
 | 
			
		||||
    if (e.dataTransfer.effectAllowed === 'all' || e.dataTransfer.effectAllowed === 'uninitialized') {
 | 
			
		||||
      //
 | 
			
		||||
      this.setState({ draggingOver: true });
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -49,10 +51,15 @@ const UI = React.createClass({
 | 
			
		|||
    e.preventDefault();
 | 
			
		||||
 | 
			
		||||
    if (e.dataTransfer && e.dataTransfer.files.length === 1) {
 | 
			
		||||
      this.setState({ draggingOver: false });
 | 
			
		||||
      this.props.dispatch(uploadCompose(e.dataTransfer.files));
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  handleDragLeave () {
 | 
			
		||||
    this.setState({ draggingOver: false });
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  componentWillMount () {
 | 
			
		||||
    window.addEventListener('resize', this.handleResize, { passive: true });
 | 
			
		||||
    window.addEventListener('dragover', this.handleDragOver);
 | 
			
		||||
| 
						 | 
				
			
			@ -69,12 +76,15 @@ const UI = React.createClass({
 | 
			
		|||
  },
 | 
			
		||||
 | 
			
		||||
  render () {
 | 
			
		||||
    const { width, draggingOver } = this.state;
 | 
			
		||||
    const { children } = this.props;
 | 
			
		||||
 | 
			
		||||
    let mountedColumns;
 | 
			
		||||
 | 
			
		||||
    if (isMobile(this.state.width)) {
 | 
			
		||||
    if (isMobile(width)) {
 | 
			
		||||
      mountedColumns = (
 | 
			
		||||
        <ColumnsArea>
 | 
			
		||||
          {this.props.children}
 | 
			
		||||
          {children}
 | 
			
		||||
        </ColumnsArea>
 | 
			
		||||
      );
 | 
			
		||||
    } else {
 | 
			
		||||
| 
						 | 
				
			
			@ -83,13 +93,13 @@ const UI = React.createClass({
 | 
			
		|||
          <Compose withHeader={true} />
 | 
			
		||||
          <HomeTimeline trackScroll={false} />
 | 
			
		||||
          <Notifications trackScroll={false} />
 | 
			
		||||
          {this.props.children}
 | 
			
		||||
          {children}
 | 
			
		||||
        </ColumnsArea>
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
      <div className='ui'>
 | 
			
		||||
      <div className='ui' onDragLeave={this.handleDragLeave}>
 | 
			
		||||
        <TabsBar />
 | 
			
		||||
 | 
			
		||||
        {mountedColumns}
 | 
			
		||||
| 
						 | 
				
			
			@ -97,6 +107,7 @@ const UI = React.createClass({
 | 
			
		|||
        <NotificationsContainer />
 | 
			
		||||
        <LoadingBarContainer style={{ backgroundColor: '#2b90d9', left: '0', top: '0' }} />
 | 
			
		||||
        <ModalContainer />
 | 
			
		||||
        <UploadArea active={draggingOver} />
 | 
			
		||||
      </div>
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -35,7 +35,6 @@ const initialState = Immutable.Map({
 | 
			
		|||
  unlisted: false,
 | 
			
		||||
  private: false,
 | 
			
		||||
  text: '',
 | 
			
		||||
  fileDropDate: null,
 | 
			
		||||
  focusDate: null,
 | 
			
		||||
  preselectDate: null,
 | 
			
		||||
  in_reply_to: null,
 | 
			
		||||
| 
						 | 
				
			
			@ -163,7 +162,6 @@ export default function compose(state = initialState, action) {
 | 
			
		|||
  case COMPOSE_UPLOAD_REQUEST:
 | 
			
		||||
    return state.withMutations(map => {
 | 
			
		||||
      map.set('is_uploading', true);
 | 
			
		||||
      map.set('fileDropDate', new Date());
 | 
			
		||||
    });
 | 
			
		||||
  case COMPOSE_UPLOAD_SUCCESS:
 | 
			
		||||
    return appendMedia(state, Immutable.fromJS(action.media));
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -960,17 +960,11 @@ a.status__content__spoiler-link {
 | 
			
		|||
  resize: none;
 | 
			
		||||
  margin: 0;
 | 
			
		||||
  color: $color1;
 | 
			
		||||
  padding: 7px;
 | 
			
		||||
  padding: 10px;
 | 
			
		||||
  font-family: inherit;
 | 
			
		||||
  font-size: 14px;
 | 
			
		||||
  resize: vertical;
 | 
			
		||||
 | 
			
		||||
  border: 3px dashed transparent;
 | 
			
		||||
  transition: border-color 0.3s ease;
 | 
			
		||||
 | 
			
		||||
  &.file-drop {
 | 
			
		||||
    border-color: darken($color5, 33%);
 | 
			
		||||
  }
 | 
			
		||||
  border: 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.autosuggest-textarea__textarea {
 | 
			
		||||
| 
						 | 
				
			
			@ -1094,7 +1088,7 @@ button.active i.fa-retweet {
 | 
			
		|||
  text-decoration: none;
 | 
			
		||||
 | 
			
		||||
  &:hover {
 | 
			
		||||
    background: lighten($color1, 8%);
 | 
			
		||||
    background: lighten($color1, 2%);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -1431,3 +1425,116 @@ button.active i.fa-retweet {
 | 
			
		|||
    font-weight: 500;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.upload-area {
 | 
			
		||||
  position: absolute;
 | 
			
		||||
  top: 0;
 | 
			
		||||
  left: 0;
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  height: 100%;
 | 
			
		||||
  visibility: hidden;
 | 
			
		||||
  background: rgba($color8, 0.8);
 | 
			
		||||
  display: flex;
 | 
			
		||||
  align-items: center;
 | 
			
		||||
  justify-content: center;
 | 
			
		||||
  opacity: 0;
 | 
			
		||||
  z-index: 2000;
 | 
			
		||||
 | 
			
		||||
  * {
 | 
			
		||||
    pointer-events: none;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.upload-area__drop {
 | 
			
		||||
  width: 320px;
 | 
			
		||||
  height: 160px;
 | 
			
		||||
  display: flex;
 | 
			
		||||
  box-sizing: border-box;
 | 
			
		||||
  position: relative;
 | 
			
		||||
  padding: 8px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.upload-area__background {
 | 
			
		||||
  position: absolute;
 | 
			
		||||
  top: 0;
 | 
			
		||||
  right: 0;
 | 
			
		||||
  bottom: 0;
 | 
			
		||||
  left: 0;
 | 
			
		||||
  z-index: -1;
 | 
			
		||||
  border-radius: 4px;
 | 
			
		||||
  background: $color1;
 | 
			
		||||
  box-shadow: 0 0 5px rgba($color8, 0.2);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.upload-area__content {
 | 
			
		||||
  flex: 1;
 | 
			
		||||
  display: flex;
 | 
			
		||||
  align-items: center;
 | 
			
		||||
  justify-content: center;
 | 
			
		||||
  color: $color2;
 | 
			
		||||
  font-size: 18px;
 | 
			
		||||
  font-weight: 500;
 | 
			
		||||
  border: 2px dashed lighten($color1, 26%);
 | 
			
		||||
  border-radius: 4px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.upload-progress {
 | 
			
		||||
  padding-bottom: 20px;
 | 
			
		||||
  color: lighten($color1, 26%);
 | 
			
		||||
  overflow: hidden;
 | 
			
		||||
  display: flex;
 | 
			
		||||
 | 
			
		||||
  .fa {
 | 
			
		||||
    font-size: 34px;
 | 
			
		||||
    margin-right: 10px;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  span {
 | 
			
		||||
    font-size: 12px;
 | 
			
		||||
    text-transform: uppercase;
 | 
			
		||||
    font-weight: 500;
 | 
			
		||||
    display: block;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.upload-progress__backdrop {
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  height: 6px;
 | 
			
		||||
  border-radius: 6px;
 | 
			
		||||
  background: lighten($color1, 26%);
 | 
			
		||||
  position: relative;
 | 
			
		||||
  margin-top: 5px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.upload-progress__tracker {
 | 
			
		||||
  position: absolute;
 | 
			
		||||
  left: 0;
 | 
			
		||||
  top: 0;
 | 
			
		||||
  height: 6px;
 | 
			
		||||
  background: $color2;
 | 
			
		||||
  border-radius: 6px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.emoji-button {
 | 
			
		||||
  img {
 | 
			
		||||
    filter: grayscale(100%);
 | 
			
		||||
    opacity: 0.4;
 | 
			
		||||
    display: block;
 | 
			
		||||
    margin: 0;
 | 
			
		||||
    width: 22px;
 | 
			
		||||
    height: 22px;
 | 
			
		||||
    margin-top: 2px;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  &:hover {
 | 
			
		||||
    img {
 | 
			
		||||
      opacity: 1;
 | 
			
		||||
      filter: none;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.dropdown--active .emoji-button img {
 | 
			
		||||
  opacity: 1;
 | 
			
		||||
  filter: none;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue