Add confirmation modal when closing media edit modal with unsaved changes (#16518)
* Add confirmation modal when closing media edit modal with unsaved changes * Move focal point media state to redux so it does not get erased by confirmation dialog * Change upload modal behavior to keep it open while saving changes Instead of closing it immediately and losing changes if they fail to save… * Make it work with react-intl 2.9
This commit is contained in:
		
							parent
							
								
									af08229ff4
								
							
						
					
					
						commit
						a8a7066e97
					
				| @ -9,6 +9,7 @@ import { importFetchedAccounts } from './importer'; | |||||||
| import { updateTimeline } from './timelines'; | import { updateTimeline } from './timelines'; | ||||||
| import { showAlertForError } from './alerts'; | import { showAlertForError } from './alerts'; | ||||||
| import { showAlert } from './alerts'; | import { showAlert } from './alerts'; | ||||||
|  | import { openModal } from './modal'; | ||||||
| import { defineMessages } from 'react-intl'; | import { defineMessages } from 'react-intl'; | ||||||
| 
 | 
 | ||||||
| let cancelFetchComposeSuggestionsAccounts, cancelFetchComposeSuggestionsTags; | let cancelFetchComposeSuggestionsAccounts, cancelFetchComposeSuggestionsTags; | ||||||
| @ -63,6 +64,11 @@ export const COMPOSE_POLL_OPTION_CHANGE   = 'COMPOSE_POLL_OPTION_CHANGE'; | |||||||
| export const COMPOSE_POLL_OPTION_REMOVE   = 'COMPOSE_POLL_OPTION_REMOVE'; | export const COMPOSE_POLL_OPTION_REMOVE   = 'COMPOSE_POLL_OPTION_REMOVE'; | ||||||
| export const COMPOSE_POLL_SETTINGS_CHANGE = 'COMPOSE_POLL_SETTINGS_CHANGE'; | export const COMPOSE_POLL_SETTINGS_CHANGE = 'COMPOSE_POLL_SETTINGS_CHANGE'; | ||||||
| 
 | 
 | ||||||
|  | export const INIT_MEDIA_EDIT_MODAL = 'INIT_MEDIA_EDIT_MODAL'; | ||||||
|  | 
 | ||||||
|  | export const COMPOSE_CHANGE_MEDIA_DESCRIPTION = 'COMPOSE_CHANGE_MEDIA_DESCRIPTION'; | ||||||
|  | export const COMPOSE_CHANGE_MEDIA_FOCUS       = 'COMPOSE_CHANGE_MEDIA_FOCUS'; | ||||||
|  | 
 | ||||||
| const messages = defineMessages({ | const messages = defineMessages({ | ||||||
|   uploadErrorLimit: { id: 'upload_error.limit', defaultMessage: 'File upload limit exceeded.' }, |   uploadErrorLimit: { id: 'upload_error.limit', defaultMessage: 'File upload limit exceeded.' }, | ||||||
|   uploadErrorPoll:  { id: 'upload_error.poll', defaultMessage: 'File upload not allowed with polls.' }, |   uploadErrorPoll:  { id: 'upload_error.poll', defaultMessage: 'File upload not allowed with polls.' }, | ||||||
| @ -306,6 +312,32 @@ export const uploadThumbnailFail = error => ({ | |||||||
|   skipLoading: true, |   skipLoading: true, | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
|  | export function initMediaEditModal(id) { | ||||||
|  |   return dispatch => { | ||||||
|  |     dispatch({ | ||||||
|  |       type: INIT_MEDIA_EDIT_MODAL, | ||||||
|  |       id, | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     dispatch(openModal('FOCAL_POINT', { id })); | ||||||
|  |   }; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export function onChangeMediaDescription(description) { | ||||||
|  |   return { | ||||||
|  |     type: COMPOSE_CHANGE_MEDIA_DESCRIPTION, | ||||||
|  |     description, | ||||||
|  |   }; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export function onChangeMediaFocus(focusX, focusY) { | ||||||
|  |   return { | ||||||
|  |     type: COMPOSE_CHANGE_MEDIA_FOCUS, | ||||||
|  |     focusX, | ||||||
|  |     focusY, | ||||||
|  |   }; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
| export function changeUploadCompose(id, params) { | export function changeUploadCompose(id, params) { | ||||||
|   return (dispatch, getState) => { |   return (dispatch, getState) => { | ||||||
|     dispatch(changeUploadComposeRequest()); |     dispatch(changeUploadComposeRequest()); | ||||||
|  | |||||||
| @ -1,7 +1,6 @@ | |||||||
| import { connect } from 'react-redux'; | import { connect } from 'react-redux'; | ||||||
| import Upload from '../components/upload'; | import Upload from '../components/upload'; | ||||||
| import { undoUploadCompose } from '../../../actions/compose'; | import { undoUploadCompose, initMediaEditModal } from '../../../actions/compose'; | ||||||
| import { openModal } from '../../../actions/modal'; |  | ||||||
| import { submitCompose } from '../../../actions/compose'; | import { submitCompose } from '../../../actions/compose'; | ||||||
| 
 | 
 | ||||||
| const mapStateToProps = (state, { id }) => ({ | const mapStateToProps = (state, { id }) => ({ | ||||||
| @ -15,7 +14,7 @@ const mapDispatchToProps = dispatch => ({ | |||||||
|   }, |   }, | ||||||
| 
 | 
 | ||||||
|   onOpenFocalPoint: id => { |   onOpenFocalPoint: id => { | ||||||
|     dispatch(openModal('FOCAL_POINT', { id })); |     dispatch(initMediaEditModal(id)); | ||||||
|   }, |   }, | ||||||
| 
 | 
 | ||||||
|   onSubmit (router) { |   onSubmit (router) { | ||||||
|  | |||||||
| @ -4,7 +4,7 @@ import PropTypes from 'prop-types'; | |||||||
| import ImmutablePureComponent from 'react-immutable-pure-component'; | import ImmutablePureComponent from 'react-immutable-pure-component'; | ||||||
| import { connect } from 'react-redux'; | import { connect } from 'react-redux'; | ||||||
| import classNames from 'classnames'; | import classNames from 'classnames'; | ||||||
| import { changeUploadCompose, uploadThumbnail } from '../../../actions/compose'; | import { changeUploadCompose, uploadThumbnail, onChangeMediaDescription, onChangeMediaFocus } from '../../../actions/compose'; | ||||||
| import { getPointerPosition } from '../../video'; | import { getPointerPosition } from '../../video'; | ||||||
| import { FormattedMessage, defineMessages, injectIntl } from 'react-intl'; | import { FormattedMessage, defineMessages, injectIntl } from 'react-intl'; | ||||||
| import IconButton from 'mastodon/components/icon_button'; | import IconButton from 'mastodon/components/icon_button'; | ||||||
| @ -27,14 +27,22 @@ import { assetHost } from 'mastodon/utils/config'; | |||||||
| const messages = defineMessages({ | const messages = defineMessages({ | ||||||
|   close: { id: 'lightbox.close', defaultMessage: 'Close' }, |   close: { id: 'lightbox.close', defaultMessage: 'Close' }, | ||||||
|   apply: { id: 'upload_modal.apply', defaultMessage: 'Apply' }, |   apply: { id: 'upload_modal.apply', defaultMessage: 'Apply' }, | ||||||
|  |   applying: { id: 'upload_modal.applying', defaultMessage: 'Applying…' }, | ||||||
|   placeholder: { id: 'upload_modal.description_placeholder', defaultMessage: 'A quick brown fox jumps over the lazy dog' }, |   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' }, |   chooseImage: { id: 'upload_modal.choose_image', defaultMessage: 'Choose image' }, | ||||||
|  |   discardMessage: { id: 'confirmations.discard_edit_media.message', defaultMessage: 'You have unsaved changes to the media description or preview, discard them anyway?' }, | ||||||
|  |   discardConfirm: { id: 'confirmations.discard_edit_media.confirm', defaultMessage: 'Discard' }, | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| const mapStateToProps = (state, { id }) => ({ | const mapStateToProps = (state, { id }) => ({ | ||||||
|   media: state.getIn(['compose', 'media_attachments']).find(item => item.get('id') === id), |   media: state.getIn(['compose', 'media_attachments']).find(item => item.get('id') === id), | ||||||
|   account: state.getIn(['accounts', me]), |   account: state.getIn(['accounts', me]), | ||||||
|   isUploadingThumbnail: state.getIn(['compose', 'isUploadingThumbnail']), |   isUploadingThumbnail: state.getIn(['compose', 'isUploadingThumbnail']), | ||||||
|  |   description: state.getIn(['compose', 'media_modal', 'description']), | ||||||
|  |   focusX: state.getIn(['compose', 'media_modal', 'focusX']), | ||||||
|  |   focusY: state.getIn(['compose', 'media_modal', 'focusY']), | ||||||
|  |   dirty: state.getIn(['compose', 'media_modal', 'dirty']), | ||||||
|  |   is_changing_upload: state.getIn(['compose', 'is_changing_upload']), | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| const mapDispatchToProps = (dispatch, { id }) => ({ | const mapDispatchToProps = (dispatch, { id }) => ({ | ||||||
| @ -43,6 +51,14 @@ const mapDispatchToProps = (dispatch, { id }) => ({ | |||||||
|     dispatch(changeUploadCompose(id, { description, focus: `${x.toFixed(2)},${y.toFixed(2)}` })); |     dispatch(changeUploadCompose(id, { description, focus: `${x.toFixed(2)},${y.toFixed(2)}` })); | ||||||
|   }, |   }, | ||||||
| 
 | 
 | ||||||
|  |   onChangeDescription: (description) => { | ||||||
|  |     dispatch(onChangeMediaDescription(description)); | ||||||
|  |   }, | ||||||
|  | 
 | ||||||
|  |   onChangeFocus: (focusX, focusY) => { | ||||||
|  |     dispatch(onChangeMediaFocus(focusX, focusY)); | ||||||
|  |   }, | ||||||
|  | 
 | ||||||
|   onSelectThumbnail: files => { |   onSelectThumbnail: files => { | ||||||
|     dispatch(uploadThumbnail(id, files[0])); |     dispatch(uploadThumbnail(id, files[0])); | ||||||
|   }, |   }, | ||||||
| @ -83,8 +99,8 @@ class ImageLoader extends React.PureComponent { | |||||||
| 
 | 
 | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export default @connect(mapStateToProps, mapDispatchToProps) | export default @connect(mapStateToProps, mapDispatchToProps, null, { forwardRef: true }) | ||||||
| @injectIntl | @(component => injectIntl(component, { withRef: true })) | ||||||
| class FocalPointModal extends ImmutablePureComponent { | class FocalPointModal extends ImmutablePureComponent { | ||||||
| 
 | 
 | ||||||
|   static propTypes = { |   static propTypes = { | ||||||
| @ -92,34 +108,21 @@ class FocalPointModal extends ImmutablePureComponent { | |||||||
|     account: ImmutablePropTypes.map.isRequired, |     account: ImmutablePropTypes.map.isRequired, | ||||||
|     isUploadingThumbnail: PropTypes.bool, |     isUploadingThumbnail: PropTypes.bool, | ||||||
|     onSave: PropTypes.func.isRequired, |     onSave: PropTypes.func.isRequired, | ||||||
|  |     onChangeDescription: PropTypes.func.isRequired, | ||||||
|  |     onChangeFocus: PropTypes.func.isRequired, | ||||||
|     onSelectThumbnail: PropTypes.func.isRequired, |     onSelectThumbnail: PropTypes.func.isRequired, | ||||||
|     onClose: PropTypes.func.isRequired, |     onClose: PropTypes.func.isRequired, | ||||||
|     intl: PropTypes.object.isRequired, |     intl: PropTypes.object.isRequired, | ||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|   state = { |   state = { | ||||||
|     x: 0, |  | ||||||
|     y: 0, |  | ||||||
|     focusX: 0, |  | ||||||
|     focusY: 0, |  | ||||||
|     dragging: false, |     dragging: false, | ||||||
|     description: '', |  | ||||||
|     dirty: false, |     dirty: false, | ||||||
|     progress: 0, |     progress: 0, | ||||||
|     loading: true, |     loading: true, | ||||||
|     ocrStatus: '', |     ocrStatus: '', | ||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|   componentWillMount () { |  | ||||||
|     this.updatePositionFromMedia(this.props.media); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   componentWillReceiveProps (nextProps) { |  | ||||||
|     if (this.props.media.get('id') !== nextProps.media.get('id')) { |  | ||||||
|       this.updatePositionFromMedia(nextProps.media); |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   componentWillUnmount () { |   componentWillUnmount () { | ||||||
|     document.removeEventListener('mousemove', this.handleMouseMove); |     document.removeEventListener('mousemove', this.handleMouseMove); | ||||||
|     document.removeEventListener('mouseup', this.handleMouseUp); |     document.removeEventListener('mouseup', this.handleMouseUp); | ||||||
| @ -164,54 +167,37 @@ class FocalPointModal extends ImmutablePureComponent { | |||||||
|     const focusX   = (x - .5) *  2; |     const focusX   = (x - .5) *  2; | ||||||
|     const focusY   = (y - .5) * -2; |     const focusY   = (y - .5) * -2; | ||||||
| 
 | 
 | ||||||
|     this.setState({ x, y, focusX, focusY, dirty: true }); |     this.props.onChangeFocus(focusX, focusY); | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   updatePositionFromMedia = media => { |  | ||||||
|     const focusX      = media.getIn(['meta', 'focus', 'x']); |  | ||||||
|     const focusY      = media.getIn(['meta', 'focus', 'y']); |  | ||||||
|     const description = media.get('description') || ''; |  | ||||||
| 
 |  | ||||||
|     if (focusX && focusY) { |  | ||||||
|       const x = (focusX /  2) + .5; |  | ||||||
|       const y = (focusY / -2) + .5; |  | ||||||
| 
 |  | ||||||
|       this.setState({ |  | ||||||
|         x, |  | ||||||
|         y, |  | ||||||
|         focusX, |  | ||||||
|         focusY, |  | ||||||
|         description, |  | ||||||
|         dirty: false, |  | ||||||
|       }); |  | ||||||
|     } else { |  | ||||||
|       this.setState({ |  | ||||||
|         x: 0.5, |  | ||||||
|         y: 0.5, |  | ||||||
|         focusX: 0, |  | ||||||
|         focusY: 0, |  | ||||||
|         description, |  | ||||||
|         dirty: false, |  | ||||||
|       }); |  | ||||||
|     } |  | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   handleChange = e => { |   handleChange = e => { | ||||||
|     this.setState({ description: e.target.value, dirty: true }); |     this.props.onChangeDescription(e.target.value); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   handleKeyDown = (e) => { |   handleKeyDown = (e) => { | ||||||
|     if (e.keyCode === 13 && (e.ctrlKey || e.metaKey)) { |     if (e.keyCode === 13 && (e.ctrlKey || e.metaKey)) { | ||||||
|       e.preventDefault(); |       e.preventDefault(); | ||||||
|       e.stopPropagation(); |       e.stopPropagation(); | ||||||
|       this.setState({ description: e.target.value, dirty: true }); |       this.props.onChangeDescription(e.target.value); | ||||||
|       this.handleSubmit(); |       this.handleSubmit(); | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   handleSubmit = () => { |   handleSubmit = () => { | ||||||
|     this.props.onSave(this.state.description, this.state.focusX, this.state.focusY); |     this.props.onSave(this.props.description, this.props.focusX, this.props.focusY); | ||||||
|     this.props.onClose(); |   } | ||||||
|  | 
 | ||||||
|  |   getCloseConfirmationMessage = () => { | ||||||
|  |     const { intl, dirty } = this.props; | ||||||
|  | 
 | ||||||
|  |     if (dirty) { | ||||||
|  |       return { | ||||||
|  |         message: intl.formatMessage(messages.discardMessage), | ||||||
|  |         confirm: intl.formatMessage(messages.discardConfirm), | ||||||
|  |       }; | ||||||
|  |     } else { | ||||||
|  |       return null; | ||||||
|  |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   setRef = c => { |   setRef = c => { | ||||||
| @ -257,7 +243,8 @@ class FocalPointModal extends ImmutablePureComponent { | |||||||
|         await worker.loadLanguage('eng'); |         await worker.loadLanguage('eng'); | ||||||
|         await worker.initialize('eng'); |         await worker.initialize('eng'); | ||||||
|         const { data: { text } } = await worker.recognize(media_url); |         const { data: { text } } = await worker.recognize(media_url); | ||||||
|         this.setState({ description: removeExtraLineBreaks(text), dirty: true, detecting: false }); |         this.setState({ detecting: false }); | ||||||
|  |         this.props.onChangeDescription(removeExtraLineBreaks(text)); | ||||||
|         await worker.terminate(); |         await worker.terminate(); | ||||||
|       })().catch((e) => { |       })().catch((e) => { | ||||||
|         if (refreshCache) { |         if (refreshCache) { | ||||||
| @ -274,7 +261,6 @@ class FocalPointModal extends ImmutablePureComponent { | |||||||
| 
 | 
 | ||||||
|   handleThumbnailChange = e => { |   handleThumbnailChange = e => { | ||||||
|     if (e.target.files.length > 0) { |     if (e.target.files.length > 0) { | ||||||
|       this.setState({ dirty: true }); |  | ||||||
|       this.props.onSelectThumbnail(e.target.files); |       this.props.onSelectThumbnail(e.target.files); | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| @ -288,8 +274,10 @@ class FocalPointModal extends ImmutablePureComponent { | |||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   render () { |   render () { | ||||||
|     const { media, intl, account, onClose, isUploadingThumbnail } = this.props; |     const { media, intl, account, onClose, isUploadingThumbnail, description, focusX, focusY, dirty, is_changing_upload } = this.props; | ||||||
|     const { x, y, dragging, description, dirty, detecting, progress, ocrStatus } = this.state; |     const { dragging, detecting, progress, ocrStatus } = this.state; | ||||||
|  |     const x = (focusX /  2) + .5; | ||||||
|  |     const y = (focusY / -2) + .5; | ||||||
| 
 | 
 | ||||||
|     const width  = media.getIn(['meta', 'original', 'width']) || null; |     const width  = media.getIn(['meta', 'original', 'width']) || null; | ||||||
|     const height = media.getIn(['meta', 'original', 'height']) || null; |     const height = media.getIn(['meta', 'original', 'height']) || null; | ||||||
| @ -344,7 +332,7 @@ class FocalPointModal extends ImmutablePureComponent { | |||||||
|                     accept='image/png,image/jpeg' |                     accept='image/png,image/jpeg' | ||||||
|                     onChange={this.handleThumbnailChange} |                     onChange={this.handleThumbnailChange} | ||||||
|                     style={{ display: 'none' }} |                     style={{ display: 'none' }} | ||||||
|                     disabled={isUploadingThumbnail} |                     disabled={isUploadingThumbnail || is_changing_upload} | ||||||
|                   /> |                   /> | ||||||
|                 </label> |                 </label> | ||||||
| 
 | 
 | ||||||
| @ -363,7 +351,7 @@ class FocalPointModal extends ImmutablePureComponent { | |||||||
|                 value={detecting ? '…' : description} |                 value={detecting ? '…' : description} | ||||||
|                 onChange={this.handleChange} |                 onChange={this.handleChange} | ||||||
|                 onKeyDown={this.handleKeyDown} |                 onKeyDown={this.handleKeyDown} | ||||||
|                 disabled={detecting} |                 disabled={detecting || is_changing_upload} | ||||||
|                 autoFocus |                 autoFocus | ||||||
|               /> |               /> | ||||||
| 
 | 
 | ||||||
| @ -373,11 +361,11 @@ class FocalPointModal extends ImmutablePureComponent { | |||||||
|             </div> |             </div> | ||||||
| 
 | 
 | ||||||
|             <div className='setting-text__toolbar'> |             <div className='setting-text__toolbar'> | ||||||
|               <button disabled={detecting || media.get('type') !== 'image'} className='link-button' onClick={this.handleTextDetection}><FormattedMessage id='upload_modal.detect_text' defaultMessage='Detect text from picture' /></button> |               <button disabled={detecting || media.get('type') !== 'image' || is_changing_upload} className='link-button' onClick={this.handleTextDetection}><FormattedMessage id='upload_modal.detect_text' defaultMessage='Detect text from picture' /></button> | ||||||
|               <CharacterCounter max={1500} text={detecting ? '' : description} /> |               <CharacterCounter max={1500} text={detecting ? '' : description} /> | ||||||
|             </div> |             </div> | ||||||
| 
 | 
 | ||||||
|             <Button disabled={!dirty || detecting || isUploadingThumbnail || length(description) > 1500} text={intl.formatMessage(messages.apply)} onClick={this.handleSubmit} /> |             <Button disabled={!dirty || detecting || isUploadingThumbnail || length(description) > 1500 || is_changing_upload} text={intl.formatMessage(is_changing_upload ? messages.applying : messages.apply)} onClick={this.handleSubmit} /> | ||||||
|           </div> |           </div> | ||||||
| 
 | 
 | ||||||
|           <div className='focal-point-modal__content'> |           <div className='focal-point-modal__content'> | ||||||
|  | |||||||
| @ -77,16 +77,33 @@ export default class ModalRoot extends React.PureComponent { | |||||||
|     return <BundleModalError {...props} onClose={onClose} />; |     return <BundleModalError {...props} onClose={onClose} />; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   handleClose = () => { | ||||||
|  |     const { onClose } = this.props; | ||||||
|  |     let message = null; | ||||||
|  |     try { | ||||||
|  |       message = this._modal?.getWrappedInstance?.().getCloseConfirmationMessage?.(); | ||||||
|  |     } catch (_) { | ||||||
|  |       // injectIntl defines `getWrappedInstance` but errors out if `withRef`
 | ||||||
|  |       // isn't set.
 | ||||||
|  |       // This would be much smoother with react-intl 3+ and `forwardRef`.
 | ||||||
|  |     } | ||||||
|  |     onClose(message); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   setModalRef = (c) => { | ||||||
|  |     this._modal = c; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|   render () { |   render () { | ||||||
|     const { type, props, onClose } = this.props; |     const { type, props } = this.props; | ||||||
|     const { backgroundColor } = this.state; |     const { backgroundColor } = this.state; | ||||||
|     const visible = !!type; |     const visible = !!type; | ||||||
| 
 | 
 | ||||||
|     return ( |     return ( | ||||||
|       <Base backgroundColor={backgroundColor} onClose={onClose}> |       <Base backgroundColor={backgroundColor} onClose={this.handleClose}> | ||||||
|         {visible && ( |         {visible && ( | ||||||
|           <BundleContainer fetchComponent={MODAL_COMPONENTS[type]} loading={this.renderLoading(type)} error={this.renderError} renderDelay={200}> |           <BundleContainer fetchComponent={MODAL_COMPONENTS[type]} loading={this.renderLoading(type)} error={this.renderError} renderDelay={200}> | ||||||
|             {(SpecificComponent) => <SpecificComponent {...props} onChangeBackgroundColor={this.setBackgroundColor} onClose={onClose} />} |             {(SpecificComponent) => <SpecificComponent {...props} onChangeBackgroundColor={this.setBackgroundColor} onClose={this.handleClose} ref={this.setModalRef} />} | ||||||
|           </BundleContainer> |           </BundleContainer> | ||||||
|         )} |         )} | ||||||
|       </Base> |       </Base> | ||||||
|  | |||||||
| @ -1,5 +1,5 @@ | |||||||
| import { connect } from 'react-redux'; | import { connect } from 'react-redux'; | ||||||
| import { closeModal } from '../../../actions/modal'; | import { openModal, closeModal } from '../../../actions/modal'; | ||||||
| import ModalRoot from '../components/modal_root'; | import ModalRoot from '../components/modal_root'; | ||||||
| 
 | 
 | ||||||
| const mapStateToProps = state => ({ | const mapStateToProps = state => ({ | ||||||
| @ -8,8 +8,18 @@ const mapStateToProps = state => ({ | |||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| const mapDispatchToProps = dispatch => ({ | const mapDispatchToProps = dispatch => ({ | ||||||
|   onClose () { |   onClose (confirmationMessage) { | ||||||
|     dispatch(closeModal()); |     if (confirmationMessage) { | ||||||
|  |       dispatch( | ||||||
|  |         openModal('CONFIRM', { | ||||||
|  |           message: confirmationMessage.message, | ||||||
|  |           confirm: confirmationMessage.confirm, | ||||||
|  |           onConfirm: () => dispatch(closeModal()), | ||||||
|  |         }), | ||||||
|  |       ); | ||||||
|  |     } else { | ||||||
|  |       dispatch(closeModal()); | ||||||
|  |     } | ||||||
|   }, |   }, | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -39,6 +39,9 @@ import { | |||||||
|   COMPOSE_POLL_OPTION_CHANGE, |   COMPOSE_POLL_OPTION_CHANGE, | ||||||
|   COMPOSE_POLL_OPTION_REMOVE, |   COMPOSE_POLL_OPTION_REMOVE, | ||||||
|   COMPOSE_POLL_SETTINGS_CHANGE, |   COMPOSE_POLL_SETTINGS_CHANGE, | ||||||
|  |   INIT_MEDIA_EDIT_MODAL, | ||||||
|  |   COMPOSE_CHANGE_MEDIA_DESCRIPTION, | ||||||
|  |   COMPOSE_CHANGE_MEDIA_FOCUS, | ||||||
| } from '../actions/compose'; | } from '../actions/compose'; | ||||||
| import { TIMELINE_DELETE } from '../actions/timelines'; | import { TIMELINE_DELETE } from '../actions/timelines'; | ||||||
| import { STORE_HYDRATE } from '../actions/store'; | import { STORE_HYDRATE } from '../actions/store'; | ||||||
| @ -76,6 +79,13 @@ const initialState = ImmutableMap({ | |||||||
|   resetFileKey: Math.floor((Math.random() * 0x10000)), |   resetFileKey: Math.floor((Math.random() * 0x10000)), | ||||||
|   idempotencyKey: null, |   idempotencyKey: null, | ||||||
|   tagHistory: ImmutableList(), |   tagHistory: ImmutableList(), | ||||||
|  |   media_modal: ImmutableMap({ | ||||||
|  |     id: null, | ||||||
|  |     description: '', | ||||||
|  |     focusX: 0, | ||||||
|  |     focusY: 0, | ||||||
|  |     dirty: false, | ||||||
|  |   }), | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| const initialPoll = ImmutableMap({ | const initialPoll = ImmutableMap({ | ||||||
| @ -354,6 +364,19 @@ export default function compose(state = initialState, action) { | |||||||
| 
 | 
 | ||||||
|         return item; |         return item; | ||||||
|       })); |       })); | ||||||
|  |   case INIT_MEDIA_EDIT_MODAL: | ||||||
|  |     const media =  state.get('media_attachments').find(item => item.get('id') === action.id); | ||||||
|  |     return state.set('media_modal', ImmutableMap({ | ||||||
|  |       id: action.id, | ||||||
|  |       description: media.get('description') || '', | ||||||
|  |       focusX: media.getIn(['meta', 'focus', 'x'], 0), | ||||||
|  |       focusY: media.getIn(['meta', 'focus', 'y'], 0), | ||||||
|  |       dirty: false, | ||||||
|  |     })); | ||||||
|  |   case COMPOSE_CHANGE_MEDIA_DESCRIPTION: | ||||||
|  |     return state.setIn(['media_modal', 'description'], action.description).setIn(['media_modal', 'dirty'], true); | ||||||
|  |   case COMPOSE_CHANGE_MEDIA_FOCUS: | ||||||
|  |     return state.setIn(['media_modal', 'focusX'], action.focusX).setIn(['media_modal', 'focusY'], action.focusY).setIn(['media_modal', 'dirty'], true); | ||||||
|   case COMPOSE_MENTION: |   case COMPOSE_MENTION: | ||||||
|     return state.withMutations(map => { |     return state.withMutations(map => { | ||||||
|       map.update('text', text => [text.trim(), `@${action.account.get('acct')} `].filter((str) => str.length !== 0).join(' ')); |       map.update('text', text => [text.trim(), `@${action.account.get('acct')} `].filter((str) => str.length !== 0).join(' ')); | ||||||
| @ -390,6 +413,7 @@ export default function compose(state = initialState, action) { | |||||||
|   case COMPOSE_UPLOAD_CHANGE_SUCCESS: |   case COMPOSE_UPLOAD_CHANGE_SUCCESS: | ||||||
|     return state |     return state | ||||||
|       .set('is_changing_upload', false) |       .set('is_changing_upload', false) | ||||||
|  |       .setIn(['media_modal', 'dirty'], false) | ||||||
|       .update('media_attachments', list => list.map(item => { |       .update('media_attachments', list => list.map(item => { | ||||||
|         if (item.get('id') === action.media.id) { |         if (item.get('id') === action.media.id) { | ||||||
|           return fromJS(action.media); |           return fromJS(action.media); | ||||||
|  | |||||||
| @ -1,5 +1,6 @@ | |||||||
| import { MODAL_OPEN, MODAL_CLOSE } from '../actions/modal'; | import { MODAL_OPEN, MODAL_CLOSE } from '../actions/modal'; | ||||||
| import { TIMELINE_DELETE } from '../actions/timelines'; | import { TIMELINE_DELETE } from '../actions/timelines'; | ||||||
|  | import { COMPOSE_UPLOAD_CHANGE_SUCCESS } from '../actions/compose'; | ||||||
| import { Stack as ImmutableStack, Map as ImmutableMap } from 'immutable'; | import { Stack as ImmutableStack, Map as ImmutableMap } from 'immutable'; | ||||||
| 
 | 
 | ||||||
| export default function modal(state = ImmutableStack(), action) { | export default function modal(state = ImmutableStack(), action) { | ||||||
| @ -8,6 +9,8 @@ export default function modal(state = ImmutableStack(), action) { | |||||||
|     return state.unshift(ImmutableMap({ modalType: action.modalType, modalProps: action.modalProps })); |     return state.unshift(ImmutableMap({ modalType: action.modalType, modalProps: action.modalProps })); | ||||||
|   case MODAL_CLOSE: |   case MODAL_CLOSE: | ||||||
|     return (action.modalType === undefined || action.modalType === state.getIn([0, 'modalType'])) ? state.shift() : state; |     return (action.modalType === undefined || action.modalType === state.getIn([0, 'modalType'])) ? state.shift() : state; | ||||||
|  |   case COMPOSE_UPLOAD_CHANGE_SUCCESS: | ||||||
|  |     return state.getIn([0, 'modalType']) === 'FOCAL_POINT' ? state.shift() : state; | ||||||
|   case TIMELINE_DELETE: |   case TIMELINE_DELETE: | ||||||
|     return state.filterNot((modal) => modal.get('modalProps').statusId === action.id); |     return state.filterNot((modal) => modal.get('modalProps').statusId === action.id); | ||||||
|   default: |   default: | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user