[Glitch] Add dropdown for boost privacy in boost confirmation modal
Port c848e85068 to glitch-soc
Signed-off-by: Claire <claire.github-309c@sitedethib.com>
			
			
This commit is contained in:
		
							parent
							
								
									cc5b429545
								
							
						
					
					
						commit
						12d91755f5
					
				
					 14 changed files with 134 additions and 25 deletions
				
			
		
							
								
								
									
										29
									
								
								app/javascript/flavours/glitch/actions/boosts.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								app/javascript/flavours/glitch/actions/boosts.js
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,29 @@ | |||
| import { openModal } from './modal'; | ||||
| 
 | ||||
| export const BOOSTS_INIT_MODAL = 'BOOSTS_INIT_MODAL'; | ||||
| export const BOOSTS_CHANGE_PRIVACY = 'BOOSTS_CHANGE_PRIVACY'; | ||||
| 
 | ||||
| export function initBoostModal(props) { | ||||
|   return (dispatch, getState) => { | ||||
|     const default_privacy = getState().getIn(['compose', 'default_privacy']); | ||||
| 
 | ||||
|     const privacy = props.status.get('visibility') === 'private' ? 'private' : default_privacy; | ||||
| 
 | ||||
|     dispatch({ | ||||
|       type: BOOSTS_INIT_MODAL, | ||||
|       privacy | ||||
|     }); | ||||
| 
 | ||||
|     dispatch(openModal('BOOST', props)); | ||||
|   }; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| export function changeBoostPrivacy(privacy) { | ||||
|   return dispatch => { | ||||
|     dispatch({ | ||||
|       type: BOOSTS_CHANGE_PRIVACY, | ||||
|       privacy, | ||||
|     }); | ||||
|   }; | ||||
| } | ||||
|  | @ -41,11 +41,11 @@ export const UNBOOKMARK_REQUEST = 'UNBOOKMARKED_REQUEST'; | |||
| export const UNBOOKMARK_SUCCESS = 'UNBOOKMARKED_SUCCESS'; | ||||
| export const UNBOOKMARK_FAIL    = 'UNBOOKMARKED_FAIL'; | ||||
| 
 | ||||
| export function reblog(status) { | ||||
| export function reblog(status, visibility) { | ||||
|   return function (dispatch, getState) { | ||||
|     dispatch(reblogRequest(status)); | ||||
| 
 | ||||
|     api(getState).post(`/api/v1/statuses/${status.get('id')}/reblog`).then(function (response) { | ||||
|     api(getState).post(`/api/v1/statuses/${status.get('id')}/reblog`, { visibility }).then(function (response) { | ||||
|       // The reblog API method returns a new status wrapped around the original. In this case we are only
 | ||||
|       // interested in how the original is modified, hence passing it skipping the wrapper
 | ||||
|       dispatch(importFetchedStatus(response.data.reblog)); | ||||
|  |  | |||
|  | @ -177,7 +177,6 @@ export default class Dropdown extends React.PureComponent { | |||
|     disabled: PropTypes.bool, | ||||
|     status: ImmutablePropTypes.map, | ||||
|     isUserTouching: PropTypes.func, | ||||
|     isModalOpen: PropTypes.bool.isRequired, | ||||
|     onOpen: PropTypes.func.isRequired, | ||||
|     onClose: PropTypes.func.isRequired, | ||||
|     dropdownPlacement: PropTypes.string, | ||||
|  |  | |||
|  | @ -5,7 +5,6 @@ import DropdownMenu from 'flavours/glitch/components/dropdown_menu'; | |||
| import { isUserTouching } from 'flavours/glitch/util/is_mobile'; | ||||
| 
 | ||||
| const mapStateToProps = state => ({ | ||||
|   isModalOpen: state.get('modal').modalType === 'ACTIONS', | ||||
|   dropdownPlacement: state.getIn(['dropdown_menu', 'placement']), | ||||
|   openDropdownId: state.getIn(['dropdown_menu', 'openId']), | ||||
|   openedViaKeyboard: state.getIn(['dropdown_menu', 'keyboard']), | ||||
|  |  | |||
|  | @ -21,6 +21,7 @@ import { muteStatus, unmuteStatus, deleteStatus } from 'flavours/glitch/actions/ | |||
| import { initMuteModal } from 'flavours/glitch/actions/mutes'; | ||||
| import { initBlockModal } from 'flavours/glitch/actions/blocks'; | ||||
| import { initReport } from 'flavours/glitch/actions/reports'; | ||||
| import { initBoostModal } from 'flavours/glitch/actions/boosts'; | ||||
| import { openModal } from 'flavours/glitch/actions/modal'; | ||||
| import { deployPictureInPicture } from 'flavours/glitch/actions/picture_in_picture'; | ||||
| import { changeLocalSetting } from 'flavours/glitch/actions/local_settings'; | ||||
|  | @ -96,11 +97,11 @@ const mapDispatchToProps = (dispatch, { intl, contextType }) => ({ | |||
|     }); | ||||
|   }, | ||||
| 
 | ||||
|   onModalReblog (status) { | ||||
|   onModalReblog (status, privacy) { | ||||
|     if (status.get('reblogged')) { | ||||
|       dispatch(unreblog(status)); | ||||
|     } else { | ||||
|       dispatch(reblog(status)); | ||||
|       dispatch(reblog(status, privacy)); | ||||
|     } | ||||
|   }, | ||||
| 
 | ||||
|  | @ -108,11 +109,11 @@ const mapDispatchToProps = (dispatch, { intl, contextType }) => ({ | |||
|     dispatch((_, getState) => { | ||||
|       let state = getState(); | ||||
|       if (state.getIn(['local_settings', 'confirm_boost_missing_media_description']) && status.get('media_attachments').some(item => !item.get('description')) && !status.get('reblogged')) { | ||||
|         dispatch(openModal('BOOST', { status, onReblog: this.onModalReblog, missingMediaDescription: true })); | ||||
|         dispatch(initBoostModal({ status, onReblog: this.onModalReblog, missingMediaDescription: true })); | ||||
|       } else if (e.shiftKey || !boostModal) { | ||||
|         this.onModalReblog(status); | ||||
|       } else { | ||||
|         dispatch(openModal('BOOST', { status, onReblog: this.onModalReblog })); | ||||
|         dispatch(initBoostModal({ status, onReblog: this.onModalReblog })); | ||||
|       } | ||||
|     }); | ||||
|   }, | ||||
|  |  | |||
|  | @ -31,6 +31,8 @@ export default class ComposerOptionsDropdown extends React.PureComponent { | |||
|     title: PropTypes.string, | ||||
|     value: PropTypes.string, | ||||
|     onChange: PropTypes.func, | ||||
|     noModal: PropTypes.bool, | ||||
|     container: PropTypes.func, | ||||
|   }; | ||||
| 
 | ||||
|   state = { | ||||
|  | @ -42,10 +44,10 @@ export default class ComposerOptionsDropdown extends React.PureComponent { | |||
| 
 | ||||
|   //  Toggles opening and closing the dropdown.
 | ||||
|   handleToggle = ({ target, type }) => { | ||||
|     const { onModalOpen } = this.props; | ||||
|     const { onModalOpen, noModal } = this.props; | ||||
|     const { open } = this.state; | ||||
| 
 | ||||
|     if (isUserTouching()) { | ||||
|     if (!noModal && isUserTouching()) { | ||||
|       if (this.state.open) { | ||||
|         this.props.onModalClose(); | ||||
|       } else { | ||||
|  | @ -183,6 +185,7 @@ export default class ComposerOptionsDropdown extends React.PureComponent { | |||
|       items, | ||||
|       onChange, | ||||
|       value, | ||||
|       container, | ||||
|     } = this.props; | ||||
|     const { open, placement } = this.state; | ||||
|     const computedClass = classNames('composer--options--dropdown', { | ||||
|  | @ -219,6 +222,7 @@ export default class ComposerOptionsDropdown extends React.PureComponent { | |||
|           placement={placement} | ||||
|           show={open} | ||||
|           target={this} | ||||
|           container={container} | ||||
|         > | ||||
|           <DropdownMenu | ||||
|             items={items} | ||||
|  |  | |||
|  | @ -53,12 +53,13 @@ class PrivacyDropdown extends React.PureComponent { | |||
|     value: PropTypes.string.isRequired, | ||||
|     onChange: PropTypes.func.isRequired, | ||||
|     noDirect: PropTypes.bool, | ||||
|     noModal: PropTypes.bool, | ||||
|     container: PropTypes.func, | ||||
|     intl: PropTypes.object.isRequired, | ||||
|   }; | ||||
| 
 | ||||
|   render () { | ||||
|     const { value, onChange, onModalOpen, onModalClose, disabled, intl } = this.props; | ||||
|     const { value, onChange, onModalOpen, onModalClose, disabled, noDirect, noModal, container, intl } = this.props; | ||||
| 
 | ||||
|     //  We predefine our privacy items so that we can easily pick the
 | ||||
|     //  dropdown icon later.
 | ||||
|  | @ -89,7 +90,11 @@ class PrivacyDropdown extends React.PureComponent { | |||
|       }, | ||||
|     }; | ||||
| 
 | ||||
|     const items = [privacyItems.public, privacyItems.unlisted, privacyItems.private, privacyItems.direct]; | ||||
|     const items = [privacyItems.public, privacyItems.unlisted, privacyItems.private]; | ||||
| 
 | ||||
|     if (!noDirect) { | ||||
|       items.push(privacyItems.direct); | ||||
|     } | ||||
| 
 | ||||
|     return ( | ||||
|       <Dropdown | ||||
|  | @ -100,6 +105,8 @@ class PrivacyDropdown extends React.PureComponent { | |||
|         onModalClose={onModalClose} | ||||
|         onModalOpen={onModalOpen} | ||||
|         title={intl.formatMessage(messages.change_privacy)} | ||||
|         container={container} | ||||
|         noModal={noModal} | ||||
|         value={value} | ||||
|       /> | ||||
|     ); | ||||
|  |  | |||
|  | @ -11,6 +11,7 @@ import { replyCompose } from 'flavours/glitch/actions/compose'; | |||
| import { reblog, favourite, unreblog, unfavourite } from 'flavours/glitch/actions/interactions'; | ||||
| import { makeGetStatus } from 'flavours/glitch/selectors'; | ||||
| import { openModal } from 'flavours/glitch/actions/modal'; | ||||
| import { initBoostModal } from 'flavours/glitch/actions/boosts'; | ||||
| 
 | ||||
| const messages = defineMessages({ | ||||
|   reply: { id: 'status.reply', defaultMessage: 'Reply' }, | ||||
|  | @ -82,9 +83,9 @@ class Footer extends ImmutablePureComponent { | |||
|     } | ||||
|   }; | ||||
| 
 | ||||
|   _performReblog = () => { | ||||
|   _performReblog = (privacy) => { | ||||
|     const { dispatch, status } = this.props; | ||||
|     dispatch(reblog(status)); | ||||
|     dispatch(reblog(status, privacy)); | ||||
|   } | ||||
| 
 | ||||
|   handleReblogClick = e => { | ||||
|  | @ -95,7 +96,7 @@ class Footer extends ImmutablePureComponent { | |||
|     } else if ((e && e.shiftKey) || !boostModal) { | ||||
|       this._performReblog(); | ||||
|     } else { | ||||
|       dispatch(openModal('BOOST', { status, onReblog: this._performReblog })); | ||||
|       dispatch(initBoostModal({ status, onReblog: this._performReblog })); | ||||
|     } | ||||
|   }; | ||||
| 
 | ||||
|  |  | |||
|  | @ -24,6 +24,7 @@ import { | |||
| import { initMuteModal } from 'flavours/glitch/actions/mutes'; | ||||
| import { initBlockModal } from 'flavours/glitch/actions/blocks'; | ||||
| import { initReport } from 'flavours/glitch/actions/reports'; | ||||
| import { initBoostModal } from 'flavours/glitch/actions/boosts'; | ||||
| import { openModal } from 'flavours/glitch/actions/modal'; | ||||
| import { defineMessages, injectIntl } from 'react-intl'; | ||||
| import { boostModal, deleteModal } from 'flavours/glitch/util/initial_state'; | ||||
|  | @ -67,8 +68,8 @@ const mapDispatchToProps = (dispatch, { intl }) => ({ | |||
|     }); | ||||
|   }, | ||||
| 
 | ||||
|   onModalReblog (status) { | ||||
|     dispatch(reblog(status)); | ||||
|   onModalReblog (status, privacy) { | ||||
|     dispatch(reblog(status, privacy)); | ||||
|   }, | ||||
| 
 | ||||
|   onReblog (status, e) { | ||||
|  | @ -78,7 +79,7 @@ const mapDispatchToProps = (dispatch, { intl }) => ({ | |||
|       if (e.shiftKey || !boostModal) { | ||||
|         this.onModalReblog(status); | ||||
|       } else { | ||||
|         dispatch(openModal('BOOST', { status, onReblog: this.onModalReblog })); | ||||
|         dispatch(initBoostModal({ status, onReblog: this.onModalReblog })); | ||||
|       } | ||||
|     } | ||||
|   }, | ||||
|  |  | |||
|  | @ -30,6 +30,7 @@ import { muteStatus, unmuteStatus, deleteStatus } from 'flavours/glitch/actions/ | |||
| import { initMuteModal } from 'flavours/glitch/actions/mutes'; | ||||
| import { initBlockModal } from 'flavours/glitch/actions/blocks'; | ||||
| import { initReport } from 'flavours/glitch/actions/reports'; | ||||
| import { initBoostModal } from 'flavours/glitch/actions/boosts'; | ||||
| import { makeGetStatus } from 'flavours/glitch/selectors'; | ||||
| import { ScrollContainer } from 'react-router-scroll-4'; | ||||
| import ColumnBackButton from 'flavours/glitch/components/column_back_button'; | ||||
|  | @ -262,13 +263,13 @@ class Status extends ImmutablePureComponent { | |||
|     } | ||||
|   } | ||||
| 
 | ||||
|   handleModalReblog = (status) => { | ||||
|   handleModalReblog = (status, privacy) => { | ||||
|     const { dispatch } = this.props; | ||||
| 
 | ||||
|     if (status.get('reblogged')) { | ||||
|       dispatch(unreblog(status)); | ||||
|     } else { | ||||
|       dispatch(reblog(status)); | ||||
|       dispatch(reblog(status, privacy)); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|  | @ -276,11 +277,11 @@ class Status extends ImmutablePureComponent { | |||
|     const { settings, dispatch } = this.props; | ||||
| 
 | ||||
|     if (settings.get('confirm_boost_missing_media_description') && status.get('media_attachments').some(item => !item.get('description')) && !status.get('reblogged')) { | ||||
|       dispatch(openModal('BOOST', { status, onReblog: this.handleModalReblog, missingMediaDescription: true })); | ||||
|       dispatch(initBoostModal({ status, onReblog: this.handleModalReblog, missingMediaDescription: true })); | ||||
|     } else if ((e && e.shiftKey) || !boostModal) { | ||||
|       this.handleModalReblog(status); | ||||
|     } else { | ||||
|       dispatch(openModal('BOOST', { status, onReblog: this.handleModalReblog })); | ||||
|       dispatch(initBoostModal({ status, onReblog: this.handleModalReblog })); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,4 +1,5 @@ | |||
| import React from 'react'; | ||||
| import { connect } from 'react-redux'; | ||||
| import ImmutablePropTypes from 'react-immutable-proptypes'; | ||||
| import PropTypes from 'prop-types'; | ||||
| import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; | ||||
|  | @ -10,7 +11,9 @@ import DisplayName from 'flavours/glitch/components/display_name'; | |||
| import AttachmentList from 'flavours/glitch/components/attachment_list'; | ||||
| import Icon from 'flavours/glitch/components/icon'; | ||||
| import ImmutablePureComponent from 'react-immutable-pure-component'; | ||||
| import PrivacyDropdown from 'flavours/glitch/features/compose/components/privacy_dropdown'; | ||||
| import classNames from 'classnames'; | ||||
| import { changeBoostPrivacy } from 'flavours/glitch/actions/boosts'; | ||||
| 
 | ||||
| const messages = defineMessages({ | ||||
|   cancel_reblog: { id: 'status.cancel_reblog_private', defaultMessage: 'Unboost' }, | ||||
|  | @ -21,7 +24,22 @@ const messages = defineMessages({ | |||
|   direct_short: { id: 'privacy.direct.short', defaultMessage: 'Direct' }, | ||||
| }); | ||||
| 
 | ||||
| export default @injectIntl | ||||
| const mapStateToProps = state => { | ||||
|   return { | ||||
|     privacy: state.getIn(['boosts', 'new', 'privacy']), | ||||
|   }; | ||||
| }; | ||||
| 
 | ||||
| const mapDispatchToProps = dispatch => { | ||||
|   return { | ||||
|     onChangeBoostPrivacy(value) { | ||||
|       dispatch(changeBoostPrivacy(value)); | ||||
|     }, | ||||
|   }; | ||||
| }; | ||||
| 
 | ||||
| export default @connect(mapStateToProps, mapDispatchToProps) | ||||
| @injectIntl | ||||
| class BoostModal extends ImmutablePureComponent { | ||||
| 
 | ||||
|   static contextTypes = { | ||||
|  | @ -41,7 +59,7 @@ class BoostModal extends ImmutablePureComponent { | |||
|   } | ||||
| 
 | ||||
|   handleReblog = () => { | ||||
|     this.props.onReblog(this.props.status); | ||||
|     this.props.onReblog(this.props.status, this.props.privacy); | ||||
|     this.props.onClose(); | ||||
|   } | ||||
| 
 | ||||
|  | @ -55,12 +73,16 @@ class BoostModal extends ImmutablePureComponent { | |||
|     } | ||||
|   } | ||||
| 
 | ||||
|   _findContainer = () => { | ||||
|     return document.getElementsByClassName('modal-root__container')[0]; | ||||
|   }; | ||||
| 
 | ||||
|   setRef = (c) => { | ||||
|     this.button = c; | ||||
|   } | ||||
| 
 | ||||
|   render () { | ||||
|     const { status, missingMediaDescription, intl } = this.props; | ||||
|     const { status, missingMediaDescription, privacy, intl } = this.props; | ||||
|     const buttonText = status.get('reblogged') ? messages.cancel_reblog : messages.reblog; | ||||
| 
 | ||||
|     const visibilityIconInfo = { | ||||
|  | @ -111,6 +133,15 @@ class BoostModal extends ImmutablePureComponent { | |||
|                 <FormattedMessage id='boost_modal.combo' defaultMessage='You can press {combo} to skip this next time' values={{ combo: <span>Shift + <Icon id='retweet' /></span> }} /> | ||||
|             } | ||||
|           </div> | ||||
| 
 | ||||
|           {status.get('visibility') !== 'private' && !status.get('reblogged') && ( | ||||
|             <PrivacyDropdown | ||||
|               noDirect | ||||
|               value={privacy} | ||||
|               container={this._findContainer} | ||||
|               onChange={this.props.onChangeBoostPrivacy} | ||||
|             /> | ||||
|           )} | ||||
|           <Button text={intl.formatMessage(buttonText)} onClick={this.handleReblog} ref={this.setRef} /> | ||||
|         </div> | ||||
|       </div> | ||||
|  |  | |||
							
								
								
									
										25
									
								
								app/javascript/flavours/glitch/reducers/boosts.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								app/javascript/flavours/glitch/reducers/boosts.js
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,25 @@ | |||
| import Immutable from 'immutable'; | ||||
| 
 | ||||
| import { | ||||
|   BOOSTS_INIT_MODAL, | ||||
|   BOOSTS_CHANGE_PRIVACY, | ||||
| } from 'flavours/glitch/actions/boosts'; | ||||
| 
 | ||||
| const initialState = Immutable.Map({ | ||||
|   new: Immutable.Map({ | ||||
|     privacy: 'public', | ||||
|   }), | ||||
| }); | ||||
| 
 | ||||
| export default function mutes(state = initialState, action) { | ||||
|   switch (action.type) { | ||||
|   case BOOSTS_INIT_MODAL: | ||||
|     return state.withMutations((state) => { | ||||
|       state.setIn(['new', 'privacy'], action.privacy); | ||||
|     }); | ||||
|   case BOOSTS_CHANGE_PRIVACY: | ||||
|     return state.setIn(['new', 'privacy'], action.privacy); | ||||
|   default: | ||||
|     return state; | ||||
|   } | ||||
| } | ||||
|  | @ -18,6 +18,7 @@ import status_lists from './status_lists'; | |||
| import mutes from './mutes'; | ||||
| import blocks from './blocks'; | ||||
| import reports from './reports'; | ||||
| import boosts from './boosts'; | ||||
| import contexts from './contexts'; | ||||
| import compose from './compose'; | ||||
| import search from './search'; | ||||
|  | @ -61,6 +62,7 @@ const reducers = { | |||
|   mutes, | ||||
|   blocks, | ||||
|   reports, | ||||
|   boosts, | ||||
|   contexts, | ||||
|   compose, | ||||
|   search, | ||||
|  |  | |||
|  | @ -1031,3 +1031,12 @@ | |||
|     } | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| .modal-root__container .composer--options--dropdown { | ||||
|   flex-grow: 0; | ||||
| } | ||||
| 
 | ||||
| .modal-root__container .composer--options--dropdown--content { | ||||
|   pointer-events: auto; | ||||
|   z-index: 9999; | ||||
| } | ||||
|  |  | |||
		Loading…
	
		Reference in a new issue