commit
						daa04c39b6
					
				| @ -6,6 +6,7 @@ import PropTypes from 'prop-types'; | |||||||
| import ReplyIndicatorContainer from '../containers/reply_indicator_container'; | import ReplyIndicatorContainer from '../containers/reply_indicator_container'; | ||||||
| import AutosuggestTextarea from '../../../components/autosuggest_textarea'; | import AutosuggestTextarea from '../../../components/autosuggest_textarea'; | ||||||
| import UploadButtonContainer from '../containers/upload_button_container'; | import UploadButtonContainer from '../containers/upload_button_container'; | ||||||
|  | import DoodleButtonContainer from '../containers/doodle_button_container'; | ||||||
| import { defineMessages, injectIntl } from 'react-intl'; | import { defineMessages, injectIntl } from 'react-intl'; | ||||||
| import Collapsable from '../../../components/collapsable'; | import Collapsable from '../../../components/collapsable'; | ||||||
| import SpoilerButtonContainer from '../containers/spoiler_button_container'; | import SpoilerButtonContainer from '../containers/spoiler_button_container'; | ||||||
| @ -249,6 +250,7 @@ export default class ComposeForm extends ImmutablePureComponent { | |||||||
|         <div className='compose-form__buttons-wrapper'> |         <div className='compose-form__buttons-wrapper'> | ||||||
|           <div className='compose-form__buttons'> |           <div className='compose-form__buttons'> | ||||||
|             <UploadButtonContainer /> |             <UploadButtonContainer /> | ||||||
|  |             <DoodleButtonContainer /> | ||||||
|             <PrivacyDropdownContainer /> |             <PrivacyDropdownContainer /> | ||||||
|             <ComposeAdvancedOptionsContainer /> |             <ComposeAdvancedOptionsContainer /> | ||||||
|             <SensitiveButtonContainer /> |             <SensitiveButtonContainer /> | ||||||
|  | |||||||
| @ -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,68 @@ | |||||||
|  | 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.clearScreen(); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   clearScreen () { | ||||||
|  |     this.sketcher.context.fillStyle = 'white'; | ||||||
|  |     this.sketcher.context.fillRect(0, 0, this.canvas.width, this.canvas.height); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   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'); | ||||||
|  | 
 | ||||||
|  |       this.clearScreen(); | ||||||
|  | 
 | ||||||
|  |       // .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> | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  | } | ||||||
| @ -7,6 +7,7 @@ import ActionsModal from './actions_modal'; | |||||||
| import MediaModal from './media_modal'; | import MediaModal from './media_modal'; | ||||||
| import VideoModal from './video_modal'; | import VideoModal from './video_modal'; | ||||||
| import BoostModal from './boost_modal'; | import BoostModal from './boost_modal'; | ||||||
|  | import DoodleModal from './doodle_modal'; | ||||||
| import ConfirmationModal from './confirmation_modal'; | import ConfirmationModal from './confirmation_modal'; | ||||||
| import { | import { | ||||||
|   OnboardingModal, |   OnboardingModal, | ||||||
| @ -21,6 +22,7 @@ const MODAL_COMPONENTS = { | |||||||
|   'ONBOARDING': OnboardingModal, |   'ONBOARDING': OnboardingModal, | ||||||
|   'VIDEO': () => Promise.resolve({ default: VideoModal }), |   'VIDEO': () => Promise.resolve({ default: VideoModal }), | ||||||
|   'BOOST': () => Promise.resolve({ default: BoostModal }), |   'BOOST': () => Promise.resolve({ default: BoostModal }), | ||||||
|  |   'DOODLE': () => Promise.resolve({ default: DoodleModal }), | ||||||
|   'CONFIRM': () => Promise.resolve({ default: ConfirmationModal }), |   'CONFIRM': () => Promise.resolve({ default: ConfirmationModal }), | ||||||
|   'MUTE': MuteModal, |   'MUTE': MuteModal, | ||||||
|   'REPORT': ReportModal, |   'REPORT': ReportModal, | ||||||
| @ -88,7 +90,7 @@ export default class ModalRoot extends React.PureComponent { | |||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   renderLoading = modalId => () => { |   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) => { |   renderError = (props) => { | ||||||
|  | |||||||
| @ -3874,6 +3874,7 @@ button.icon-button.active i.fa-retweet { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .boost-modal, | .boost-modal, | ||||||
|  | .doodle-modal, | ||||||
| .confirmation-modal, | .confirmation-modal, | ||||||
| .report-modal, | .report-modal, | ||||||
| .actions-modal, | .actions-modal, | ||||||
| @ -3892,6 +3893,10 @@ button.icon-button.active i.fa-retweet { | |||||||
|   } |   } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | .doodle-modal { | ||||||
|  |   width: unset; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| .actions-modal { | .actions-modal { | ||||||
|   .status { |   .status { | ||||||
|     background: $white; |     background: $white; | ||||||
| @ -3915,6 +3920,7 @@ button.icon-button.active i.fa-retweet { | |||||||
|   } |   } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | .doodle-modal__action-bar, | ||||||
| .boost-modal__action-bar, | .boost-modal__action-bar, | ||||||
| .confirmation-modal__action-bar, | .confirmation-modal__action-bar, | ||||||
| .mute-modal__action-bar, | .mute-modal__action-bar, | ||||||
|  | |||||||
| @ -19,6 +19,7 @@ | |||||||
|   "private": true, |   "private": true, | ||||||
|   "dependencies": { |   "dependencies": { | ||||||
|     "array-includes": "^3.0.3", |     "array-includes": "^3.0.3", | ||||||
|  |     "atrament": "^0.2.3", | ||||||
|     "autoprefixer": "^7.1.2", |     "autoprefixer": "^7.1.2", | ||||||
|     "axios": "^0.16.2", |     "axios": "^0.16.2", | ||||||
|     "babel-core": "^6.25.0", |     "babel-core": "^6.25.0", | ||||||
|  | |||||||
| @ -300,6 +300,10 @@ atob@~1.1.0: | |||||||
|   version "1.1.3" |   version "1.1.3" | ||||||
|   resolved "https://registry.yarnpkg.com/atob/-/atob-1.1.3.tgz#95f13629b12c3a51a5d215abdce2aa9f32f80773" |   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: | autoprefixer@^6.3.1: | ||||||
|   version "6.7.7" |   version "6.7.7" | ||||||
|   resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-6.7.7.tgz#1dbd1c835658e35ce3f9984099db00585c782014" |   resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-6.7.7.tgz#1dbd1c835658e35ce3f9984099db00585c782014" | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user