Initial doodle support
This commit is contained in:
		
							parent
							
								
									e960e2c1d2
								
							
						
					
					
						commit
						f1f6d8220a
					
				
					 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 Collapsable from '../../../components/collapsable'; | ||||
| 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'> | ||||
|             <UploadButtonContainer /> | ||||
|             <DoodleButtonContainer /> | ||||
|             <PrivacyDropdownContainer /> | ||||
|             <ComposeAdvancedOptionsContainer /> | ||||
|             <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,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> | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
| } | ||||
|  | @ -7,6 +7,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 { | ||||
|   OnboardingModal, | ||||
|  | @ -21,6 +22,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, | ||||
|  | @ -88,7 +90,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) => { | ||||
|  |  | |||
|  | @ -3874,6 +3874,7 @@ button.icon-button.active i.fa-retweet { | |||
| } | ||||
| 
 | ||||
| .boost-modal, | ||||
| .doodle-modal, | ||||
| .confirmation-modal, | ||||
| .report-modal, | ||||
| .actions-modal, | ||||
|  | @ -3892,6 +3893,10 @@ button.icon-button.active i.fa-retweet { | |||
|   } | ||||
| } | ||||
| 
 | ||||
| .doodle-modal { | ||||
|   width: unset; | ||||
| } | ||||
| 
 | ||||
| .actions-modal { | ||||
|   .status { | ||||
|     background: $white; | ||||
|  | @ -3915,6 +3920,7 @@ button.icon-button.active i.fa-retweet { | |||
|   } | ||||
| } | ||||
| 
 | ||||
| .doodle-modal__action-bar, | ||||
| .boost-modal__action-bar, | ||||
| .confirmation-modal__action-bar, | ||||
| .mute-modal__action-bar, | ||||
|  |  | |||
|  | @ -19,6 +19,7 @@ | |||
|   "private": true, | ||||
|   "dependencies": { | ||||
|     "array-includes": "^3.0.3", | ||||
|     "atrament": "^0.2.3", | ||||
|     "autoprefixer": "^7.1.2", | ||||
|     "axios": "^0.16.2", | ||||
|     "babel-core": "^6.25.0", | ||||
|  |  | |||
|  | @ -300,6 +300,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…
	
		Reference in a new issue