Initial doodle support
This commit is contained in:
		
							parent
							
								
									7ac5151b74
								
							
						
					
					
						commit
						825f9449d5
					
				
					 8 changed files with 155 additions and 1 deletions
				
			
		| 
						 | 
				
			
			@ -6,6 +6,7 @@ import PropTypes from 'prop-types';
 | 
			
		|||
import ReplyIndicatorContainer from '../containers/reply_indicator_container';
 | 
			
		||||
import AutosuggestTextarea from '../../../components/autosuggest_textarea';
 | 
			
		||||
import UploadButtonContainer from '../containers/upload_button_container';
 | 
			
		||||
import DoodleButtonContainer from '../containers/doodle_button_container';
 | 
			
		||||
import { defineMessages, injectIntl } from 'react-intl';
 | 
			
		||||
import SpoilerButtonContainer from '../containers/spoiler_button_container';
 | 
			
		||||
import PrivacyDropdownContainer from '../containers/privacy_dropdown_container';
 | 
			
		||||
| 
						 | 
				
			
			@ -204,6 +205,7 @@ export default class ComposeForm extends ImmutablePureComponent {
 | 
			
		|||
        <div className='compose-form__buttons-wrapper'>
 | 
			
		||||
          <div className='compose-form__buttons'>
 | 
			
		||||
            <UploadButtonContainer />
 | 
			
		||||
            <DoodleButtonContainer />
 | 
			
		||||
            <PrivacyDropdownContainer />
 | 
			
		||||
            <SensitiveButtonContainer />
 | 
			
		||||
            <SpoilerButtonContainer />
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,41 @@
 | 
			
		|||
import React from 'react';
 | 
			
		||||
import IconButton from '../../../components/icon_button';
 | 
			
		||||
import PropTypes from 'prop-types';
 | 
			
		||||
import { defineMessages, injectIntl } from 'react-intl';
 | 
			
		||||
import ImmutablePureComponent from 'react-immutable-pure-component';
 | 
			
		||||
 | 
			
		||||
const messages = defineMessages({
 | 
			
		||||
  doodle: { id: 'doodle_button.label', defaultMessage: 'Add a drawing' },
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const iconStyle = {
 | 
			
		||||
  height: null,
 | 
			
		||||
  lineHeight: '27px',
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@injectIntl
 | 
			
		||||
export default class UploadButton extends ImmutablePureComponent {
 | 
			
		||||
 | 
			
		||||
  static propTypes = {
 | 
			
		||||
    disabled: PropTypes.bool,
 | 
			
		||||
    onOpenCanvas: PropTypes.func.isRequired,
 | 
			
		||||
    style: PropTypes.object,
 | 
			
		||||
    intl: PropTypes.object.isRequired,
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  handleClick = () => {
 | 
			
		||||
    this.props.onOpenCanvas();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  render () {
 | 
			
		||||
 | 
			
		||||
    const { intl, disabled } = this.props;
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
      <div className='compose-form__upload-button'>
 | 
			
		||||
        <IconButton icon='pencil' title={intl.formatMessage(messages.doodle)} disabled={disabled} onClick={this.handleClick} className='compose-form__upload-button-icon' size={18} inverted style={iconStyle} />
 | 
			
		||||
      </div>
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,33 @@
 | 
			
		|||
import { connect } from 'react-redux';
 | 
			
		||||
import DoodleButton from '../components/doodle_button';
 | 
			
		||||
import { openModal } from '../../../actions/modal';
 | 
			
		||||
import { uploadCompose } from '../../../actions/compose';
 | 
			
		||||
 | 
			
		||||
const mapStateToProps = state => ({
 | 
			
		||||
  disabled: state.getIn(['compose', 'is_uploading']) || (state.getIn(['compose', 'media_attachments']).size > 3 || state.getIn(['compose', 'media_attachments']).some(m => m.get('type') === 'video')),
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
//https://stackoverflow.com/questions/35940290/how-to-convert-base64-string-to-javascript-file-object-like-as-from-file-input-f
 | 
			
		||||
function dataURLtoFile(dataurl, filename) {
 | 
			
		||||
  let arr = dataurl.split(','), mime = arr[0].match(/:(.*?);/)[1],
 | 
			
		||||
    bstr = atob(arr[1]), n = bstr.length, u8arr = new Uint8Array(n);
 | 
			
		||||
  while(n--){
 | 
			
		||||
    u8arr[n] = bstr.charCodeAt(n);
 | 
			
		||||
  }
 | 
			
		||||
  return new File([u8arr], filename, { type: mime });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const mapDispatchToProps = dispatch => ({
 | 
			
		||||
 | 
			
		||||
  onOpenCanvas () {
 | 
			
		||||
    dispatch(openModal('DOODLE', {
 | 
			
		||||
      status,
 | 
			
		||||
      onDoodleSubmit: (b64data) => {
 | 
			
		||||
        dispatch(uploadCompose([dataURLtoFile(b64data, 'doodle.png')]));
 | 
			
		||||
      },
 | 
			
		||||
    }));
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
export default connect(mapStateToProps, mapDispatchToProps)(DoodleButton);
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,65 @@
 | 
			
		|||
import React from 'react';
 | 
			
		||||
import PropTypes from 'prop-types';
 | 
			
		||||
import Button from '../../../components/button';
 | 
			
		||||
import ImmutablePureComponent from 'react-immutable-pure-component';
 | 
			
		||||
import Atrament from 'atrament'; // the doodling library
 | 
			
		||||
 | 
			
		||||
export default class DoodleModal extends ImmutablePureComponent {
 | 
			
		||||
 | 
			
		||||
  static contextTypes = {
 | 
			
		||||
    router: PropTypes.object,
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  static propTypes = {
 | 
			
		||||
    onDoodleSubmit: PropTypes.func.isRequired, // gets the base64 as argument
 | 
			
		||||
    onClose: PropTypes.func.isRequired,
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  handleKeyUp = (e) => {
 | 
			
		||||
    if (e.key === 'Delete' || e.key === 'Backspace') {
 | 
			
		||||
      this.sketcher.clear();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  componentDidMount () {
 | 
			
		||||
    window.addEventListener('keyup', this.handleKeyUp, false);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  handleDone = () => {
 | 
			
		||||
    this.props.onDoodleSubmit(this.sketcher.toImage());
 | 
			
		||||
    this.sketcher.destroy();
 | 
			
		||||
    this.props.onClose();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  setCanvasRef = (elem) => {
 | 
			
		||||
    this.canvas = elem;
 | 
			
		||||
    if (elem) {
 | 
			
		||||
      this.sketcher = new Atrament(elem, 500, 500, 'black');
 | 
			
		||||
 | 
			
		||||
      // pre-fill with white
 | 
			
		||||
      this.sketcher.context.fillStyle = 'white';
 | 
			
		||||
      this.sketcher.context.fillRect(0, 0, elem.width, elem.height);
 | 
			
		||||
 | 
			
		||||
      // .smoothing looks good with mouse but works really poorly with a tablet
 | 
			
		||||
      this.sketcher.smoothing = false;
 | 
			
		||||
 | 
			
		||||
      // There's a bunch of options we should add UI controls for later
 | 
			
		||||
      // ref: https://github.com/jakubfiala/atrament.js
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  render () {
 | 
			
		||||
    return (
 | 
			
		||||
      <div className='modal-root__modal doodle-modal'>
 | 
			
		||||
        <div className='doodle-modal__container'>
 | 
			
		||||
          <canvas ref={this.setCanvasRef} />
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <div className='doodle-modal__action-bar'>
 | 
			
		||||
          <Button text='Done' onClick={this.handleDone} />
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -8,6 +8,7 @@ import ActionsModal from './actions_modal';
 | 
			
		|||
import MediaModal from './media_modal';
 | 
			
		||||
import VideoModal from './video_modal';
 | 
			
		||||
import BoostModal from './boost_modal';
 | 
			
		||||
import DoodleModal from './doodle_modal';
 | 
			
		||||
import ConfirmationModal from './confirmation_modal';
 | 
			
		||||
import FocalPointModal from './focal_point_modal';
 | 
			
		||||
import {
 | 
			
		||||
| 
						 | 
				
			
			@ -23,6 +24,7 @@ const MODAL_COMPONENTS = {
 | 
			
		|||
  'ONBOARDING': OnboardingModal,
 | 
			
		||||
  'VIDEO': () => Promise.resolve({ default: VideoModal }),
 | 
			
		||||
  'BOOST': () => Promise.resolve({ default: BoostModal }),
 | 
			
		||||
  'DOODLE': () => Promise.resolve({ default: DoodleModal }),
 | 
			
		||||
  'CONFIRM': () => Promise.resolve({ default: ConfirmationModal }),
 | 
			
		||||
  'MUTE': MuteModal,
 | 
			
		||||
  'REPORT': ReportModal,
 | 
			
		||||
| 
						 | 
				
			
			@ -52,7 +54,7 @@ export default class ModalRoot extends React.PureComponent {
 | 
			
		|||
  }
 | 
			
		||||
 | 
			
		||||
  renderLoading = modalId => () => {
 | 
			
		||||
    return ['MEDIA', 'VIDEO', 'BOOST', 'CONFIRM', 'ACTIONS'].indexOf(modalId) === -1 ? <ModalLoading /> : null;
 | 
			
		||||
    return ['MEDIA', 'VIDEO', 'BOOST', 'DOODLE', 'CONFIRM', 'ACTIONS'].indexOf(modalId) === -1 ? <ModalLoading /> : null;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  renderError = (props) => {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3998,6 +3998,7 @@ a.status-card {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
.boost-modal,
 | 
			
		||||
.doodle-modal,
 | 
			
		||||
.confirmation-modal,
 | 
			
		||||
.report-modal,
 | 
			
		||||
.actions-modal,
 | 
			
		||||
| 
						 | 
				
			
			@ -4030,6 +4031,10 @@ a.status-card {
 | 
			
		|||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.doodle-modal {
 | 
			
		||||
  width: unset;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.actions-modal {
 | 
			
		||||
  .status {
 | 
			
		||||
    background: $white;
 | 
			
		||||
| 
						 | 
				
			
			@ -4053,6 +4058,7 @@ a.status-card {
 | 
			
		|||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.doodle-modal__action-bar,
 | 
			
		||||
.boost-modal__action-bar,
 | 
			
		||||
.confirmation-modal__action-bar,
 | 
			
		||||
.mute-modal__action-bar {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -21,6 +21,7 @@
 | 
			
		|||
  "private": true,
 | 
			
		||||
  "dependencies": {
 | 
			
		||||
    "array-includes": "^3.0.3",
 | 
			
		||||
    "atrament": "^0.2.3",
 | 
			
		||||
    "autoprefixer": "^7.1.6",
 | 
			
		||||
    "axios": "~0.16.2",
 | 
			
		||||
    "babel-core": "^6.25.0",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -443,6 +443,10 @@ atob@~1.1.0:
 | 
			
		|||
  version "1.1.3"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/atob/-/atob-1.1.3.tgz#95f13629b12c3a51a5d215abdce2aa9f32f80773"
 | 
			
		||||
 | 
			
		||||
atrament@^0.2.3:
 | 
			
		||||
  version "0.2.3"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/atrament/-/atrament-0.2.3.tgz#6ccbc0daa6d3f25e5aeaeb31befeb78e86980348"
 | 
			
		||||
 | 
			
		||||
autoprefixer@^6.3.1:
 | 
			
		||||
  version "6.7.7"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-6.7.7.tgz#1dbd1c835658e35ce3f9984099db00585c782014"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		
		Reference in a new issue