[Glitch] Add ability to choose media thumbnail in web UI
Port 825f73c3f9 to glitch-soc
Signed-off-by: Thibaut Girka <thib@sitedethib.com>
			
			
This commit is contained in:
		
							parent
							
								
									f0aee465d9
								
							
						
					
					
						commit
						409d00d256
					
				
					 4 changed files with 132 additions and 3 deletions
				
			
		|  | @ -30,6 +30,11 @@ export const COMPOSE_UPLOAD_FAIL     = 'COMPOSE_UPLOAD_FAIL'; | |||
| export const COMPOSE_UPLOAD_PROGRESS = 'COMPOSE_UPLOAD_PROGRESS'; | ||||
| export const COMPOSE_UPLOAD_UNDO     = 'COMPOSE_UPLOAD_UNDO'; | ||||
| 
 | ||||
| export const THUMBNAIL_UPLOAD_REQUEST  = 'THUMBNAIL_UPLOAD_REQUEST'; | ||||
| export const THUMBNAIL_UPLOAD_SUCCESS  = 'THUMBNAIL_UPLOAD_SUCCESS'; | ||||
| export const THUMBNAIL_UPLOAD_FAIL     = 'THUMBNAIL_UPLOAD_FAIL'; | ||||
| export const THUMBNAIL_UPLOAD_PROGRESS = 'THUMBNAIL_UPLOAD_PROGRESS'; | ||||
| 
 | ||||
| export const COMPOSE_SUGGESTIONS_CLEAR = 'COMPOSE_SUGGESTIONS_CLEAR'; | ||||
| export const COMPOSE_SUGGESTIONS_READY = 'COMPOSE_SUGGESTIONS_READY'; | ||||
| export const COMPOSE_SUGGESTION_SELECT = 'COMPOSE_SUGGESTION_SELECT'; | ||||
|  | @ -289,6 +294,49 @@ export function uploadCompose(files) { | |||
|   }; | ||||
| }; | ||||
| 
 | ||||
| export const uploadThumbnail = (id, file) => (dispatch, getState) => { | ||||
|   dispatch(uploadThumbnailRequest()); | ||||
| 
 | ||||
|   const total = file.size; | ||||
|   const data = new FormData(); | ||||
| 
 | ||||
|   data.append('thumbnail', file); | ||||
| 
 | ||||
|   api(getState).put(`/api/v1/media/${id}`, data, { | ||||
|     onUploadProgress: ({ loaded }) => { | ||||
|       dispatch(uploadThumbnailProgress(loaded, total)); | ||||
|     }, | ||||
|   }).then(({ data }) => { | ||||
|     dispatch(uploadThumbnailSuccess(data)); | ||||
|   }).catch(error => { | ||||
|     dispatch(uploadThumbnailFail(id, error)); | ||||
|   }); | ||||
| }; | ||||
| 
 | ||||
| export const uploadThumbnailRequest = () => ({ | ||||
|   type: THUMBNAIL_UPLOAD_REQUEST, | ||||
|   skipLoading: true, | ||||
| }); | ||||
| 
 | ||||
| export const uploadThumbnailProgress = (loaded, total) => ({ | ||||
|   type: THUMBNAIL_UPLOAD_PROGRESS, | ||||
|   loaded, | ||||
|   total, | ||||
|   skipLoading: true, | ||||
| }); | ||||
| 
 | ||||
| export const uploadThumbnailSuccess = media => ({ | ||||
|   type: THUMBNAIL_UPLOAD_SUCCESS, | ||||
|   media, | ||||
|   skipLoading: true, | ||||
| }); | ||||
| 
 | ||||
| export const uploadThumbnailFail = error => ({ | ||||
|   type: THUMBNAIL_UPLOAD_FAIL, | ||||
|   error, | ||||
|   skipLoading: true, | ||||
| }); | ||||
| 
 | ||||
| export function changeUploadCompose(id, params) { | ||||
|   return (dispatch, getState) => { | ||||
|     dispatch(changeUploadComposeRequest()); | ||||
|  | @ -307,6 +355,7 @@ export function changeUploadComposeRequest() { | |||
|     skipLoading: true, | ||||
|   }; | ||||
| }; | ||||
| 
 | ||||
| export function changeUploadComposeSuccess(media) { | ||||
|   return { | ||||
|     type: COMPOSE_UPLOAD_CHANGE_SUCCESS, | ||||
|  |  | |||
|  | @ -4,7 +4,7 @@ import PropTypes from 'prop-types'; | |||
| import ImmutablePureComponent from 'react-immutable-pure-component'; | ||||
| import { connect } from 'react-redux'; | ||||
| import classNames from 'classnames'; | ||||
| import { changeUploadCompose } from 'flavours/glitch/actions/compose'; | ||||
| import { changeUploadCompose, uploadThumbnail } from 'flavours/glitch/actions/compose'; | ||||
| import { getPointerPosition } from 'flavours/glitch/features/video'; | ||||
| import { FormattedMessage, defineMessages, injectIntl } from 'react-intl'; | ||||
| import IconButton from 'flavours/glitch/components/icon_button'; | ||||
|  | @ -23,11 +23,13 @@ const messages = defineMessages({ | |||
|   close: { id: 'lightbox.close', defaultMessage: 'Close' }, | ||||
|   apply: { id: 'upload_modal.apply', defaultMessage: 'Apply' }, | ||||
|   placeholder: { id: 'upload_modal.description_placeholder', defaultMessage: 'A quick brown fox jumps over the lazy dog' }, | ||||
|   chooseImage: { id: 'upload_modal.choose_image', defaultMessage: 'Choose image' }, | ||||
| }); | ||||
| 
 | ||||
| const mapStateToProps = (state, { id }) => ({ | ||||
|   media: state.getIn(['compose', 'media_attachments']).find(item => item.get('id') === id), | ||||
|   account: state.getIn(['accounts', me]), | ||||
|   isUploadingThumbnail: state.getIn(['compose', 'isUploadingThumbnail']), | ||||
| }); | ||||
| 
 | ||||
| const mapDispatchToProps = (dispatch, { id }) => ({ | ||||
|  | @ -36,6 +38,10 @@ const mapDispatchToProps = (dispatch, { id }) => ({ | |||
|     dispatch(changeUploadCompose(id, { description, focus: `${x.toFixed(2)},${y.toFixed(2)}` })); | ||||
|   }, | ||||
| 
 | ||||
|   onSelectThumbnail: files => { | ||||
|     dispatch(uploadThumbnail(id, files[0])); | ||||
|   }, | ||||
| 
 | ||||
| }); | ||||
| 
 | ||||
| const removeExtraLineBreaks = str => str.replace(/\n\n/g, '******') | ||||
|  | @ -81,6 +87,9 @@ class FocalPointModal extends ImmutablePureComponent { | |||
|   static propTypes = { | ||||
|     media: ImmutablePropTypes.map.isRequired, | ||||
|     account: ImmutablePropTypes.map.isRequired, | ||||
|     isUploadingThumbnail: PropTypes.bool, | ||||
|     onSave: PropTypes.func.isRequired, | ||||
|     onSelectThumbnail: PropTypes.func.isRequired, | ||||
|     onClose: PropTypes.func.isRequired, | ||||
|     intl: PropTypes.object.isRequired, | ||||
|   }; | ||||
|  | @ -235,13 +244,29 @@ class FocalPointModal extends ImmutablePureComponent { | |||
|     }).catch(() => this.setState({ detecting: false })); | ||||
|   } | ||||
| 
 | ||||
|   handleThumbnailChange = e => { | ||||
|     if (e.target.files.length > 0) { | ||||
|       this.setState({ dirty: true }); | ||||
|       this.props.onSelectThumbnail(e.target.files); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   setFileInputRef = c => { | ||||
|     this.fileInput = c; | ||||
|   } | ||||
| 
 | ||||
|   handleFileInputClick = () => { | ||||
|     this.fileInput.click(); | ||||
|   } | ||||
| 
 | ||||
|   render () { | ||||
|     const { media, intl, account, onClose } = this.props; | ||||
|     const { media, intl, account, onClose, isUploadingThumbnail } = this.props; | ||||
|     const { x, y, dragging, description, dirty, detecting, progress } = this.state; | ||||
| 
 | ||||
|     const width  = media.getIn(['meta', 'original', 'width']) || null; | ||||
|     const height = media.getIn(['meta', 'original', 'height']) || null; | ||||
|     const focals = ['image', 'gifv'].includes(media.get('type')); | ||||
|     const thumbnailable = ['audio', 'video'].includes(media.get('type')); | ||||
| 
 | ||||
|     const previewRatio  = 16/9; | ||||
|     const previewWidth  = 200; | ||||
|  | @ -268,6 +293,30 @@ class FocalPointModal extends ImmutablePureComponent { | |||
|           <div className='report-modal__comment'> | ||||
|             {focals && <p><FormattedMessage id='upload_modal.hint' defaultMessage='Click or drag the circle on the preview to choose the focal point which will always be in view on all thumbnails.' /></p>} | ||||
| 
 | ||||
|             {thumbnailable && ( | ||||
|               <React.Fragment> | ||||
|                 <label className='setting-text-label' htmlFor='upload-modal__thumbnail'><FormattedMessage id='upload_form.thumbnail' defaultMessage='Change thumbnail' /></label> | ||||
| 
 | ||||
|                 <Button disabled={isUploadingThumbnail} text={intl.formatMessage(messages.chooseImage)} onClick={this.handleFileInputClick} /> | ||||
| 
 | ||||
|                 <label> | ||||
|                   <span style={{ display: 'none' }}>{intl.formatMessage(messages.chooseImage)}</span> | ||||
| 
 | ||||
|                   <input | ||||
|                     id='upload-modal__thumbnail' | ||||
|                     ref={this.setFileInputRef} | ||||
|                     type='file' | ||||
|                     accept='image/png,image/jpeg' | ||||
|                     onChange={this.handleThumbnailChange} | ||||
|                     style={{ display: 'none' }} | ||||
|                     disabled={isUploadingThumbnail} | ||||
|                   /> | ||||
|                 </label> | ||||
| 
 | ||||
|                 <hr className='setting-divider' /> | ||||
|               </React.Fragment> | ||||
|             )} | ||||
| 
 | ||||
|             <label className='setting-text-label' htmlFor='upload-modal__description'> | ||||
|               {descriptionLabel} | ||||
|             </label> | ||||
|  | @ -293,7 +342,7 @@ class FocalPointModal extends ImmutablePureComponent { | |||
|               <CharacterCounter max={1500} text={detecting ? '' : description} /> | ||||
|             </div> | ||||
| 
 | ||||
|             <Button disabled={!dirty || detecting || length(description) > 1500} text={intl.formatMessage(messages.apply)} onClick={this.handleSubmit} /> | ||||
|             <Button disabled={!dirty || detecting || isUploadingThumbnail || length(description) > 1500} text={intl.formatMessage(messages.apply)} onClick={this.handleSubmit} /> | ||||
|           </div> | ||||
| 
 | ||||
|           <div className='focal-point-modal__content'> | ||||
|  |  | |||
|  | @ -15,6 +15,10 @@ import { | |||
|   COMPOSE_UPLOAD_FAIL, | ||||
|   COMPOSE_UPLOAD_UNDO, | ||||
|   COMPOSE_UPLOAD_PROGRESS, | ||||
|   THUMBNAIL_UPLOAD_REQUEST, | ||||
|   THUMBNAIL_UPLOAD_SUCCESS, | ||||
|   THUMBNAIL_UPLOAD_FAIL, | ||||
|   THUMBNAIL_UPLOAD_PROGRESS, | ||||
|   COMPOSE_SUGGESTIONS_CLEAR, | ||||
|   COMPOSE_SUGGESTIONS_READY, | ||||
|   COMPOSE_SUGGESTION_SELECT, | ||||
|  | @ -77,6 +81,8 @@ const initialState = ImmutableMap({ | |||
|   is_uploading: false, | ||||
|   is_changing_upload: false, | ||||
|   progress: 0, | ||||
|   isUploadingThumbnail: false, | ||||
|   thumbnailProgress: 0, | ||||
|   media_attachments: ImmutableList(), | ||||
|   pending_media_attachments: 0, | ||||
|   poll: null, | ||||
|  | @ -433,6 +439,22 @@ export default function compose(state = initialState, action) { | |||
|     return removeMedia(state, action.media_id); | ||||
|   case COMPOSE_UPLOAD_PROGRESS: | ||||
|     return state.set('progress', Math.round((action.loaded / action.total) * 100)); | ||||
|   case THUMBNAIL_UPLOAD_REQUEST: | ||||
|     return state.set('isUploadingThumbnail', true); | ||||
|   case THUMBNAIL_UPLOAD_PROGRESS: | ||||
|     return state.set('thumbnailProgress', Math.round((action.loaded / action.total) * 100)); | ||||
|   case THUMBNAIL_UPLOAD_FAIL: | ||||
|     return state.set('isUploadingThumbnail', false); | ||||
|   case THUMBNAIL_UPLOAD_SUCCESS: | ||||
|     return state | ||||
|       .set('isUploadingThumbnail', false) | ||||
|       .update('media_attachments', list => list.map(item => { | ||||
|         if (item.get('id') === action.media.id) { | ||||
|           return fromJS(action.media); | ||||
|         } | ||||
| 
 | ||||
|         return item; | ||||
|       })); | ||||
|   case COMPOSE_MENTION: | ||||
|     return state.withMutations(map => { | ||||
|       map.update('text', text => [text.trim(), `@${action.account.get('acct')} `].filter((str) => str.length !== 0).join(' ')); | ||||
|  |  | |||
|  | @ -555,6 +555,15 @@ | |||
|   } | ||||
| } | ||||
| 
 | ||||
| .setting-divider { | ||||
|   background: transparent; | ||||
|   border: 0; | ||||
|   margin: 0; | ||||
|   width: 100%; | ||||
|   height: 1px; | ||||
|   margin-bottom: 29px; | ||||
| } | ||||
| 
 | ||||
| .report-modal__comment { | ||||
|   padding: 20px; | ||||
|   border-right: 1px solid $ui-secondary-color; | ||||
|  |  | |||
		Loading…
	
		Reference in a new issue