diff options
author | Eugen Rochko <eugen@zeonfederated.com> | 2017-02-26 01:23:44 +0100 |
---|---|---|
committer | Eugen Rochko <eugen@zeonfederated.com> | 2017-02-26 01:23:44 +0100 |
commit | 2c5068727997d4b223e74e765df75d9773b954f7 (patch) | |
tree | 7d60793db17c146438e3eeff5ff8026e673d5891 /app | |
parent | 3e9d794ea50986db5647b4e05a408bf0208bbfa1 (diff) |
Improve compose form performance, upgrade JS dependencies. LightingBox
now allows to cycle through multiple images
Diffstat (limited to 'app')
12 files changed, 166 insertions, 70 deletions
diff --git a/app/assets/javascripts/components/features/compose/components/compose_form.jsx b/app/assets/javascripts/components/features/compose/components/compose_form.jsx index 45812def6..31ae8e034 100644 --- a/app/assets/javascripts/components/features/compose/components/compose_form.jsx +++ b/app/assets/javascripts/components/features/compose/components/compose_form.jsx @@ -12,6 +12,9 @@ import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import Toggle from 'react-toggle'; import Collapsable from '../../../components/collapsable'; import UnlistedToggleContainer from '../containers/unlisted_toggle_container'; +import SpoilerToggleContainer from '../containers/spoiler_toggle_container'; +import PrivateToggleContainer from '../containers/private_toggle_container'; +import SensitiveToggleContainer from '../containers/sensitive_toggle_container'; const messages = defineMessages({ placeholder: { id: 'compose_form.placeholder', defaultMessage: 'What is on your mind?' }, @@ -26,17 +29,15 @@ const ComposeForm = React.createClass({ text: React.PropTypes.string.isRequired, suggestion_token: React.PropTypes.string, suggestions: ImmutablePropTypes.list, - sensitive: React.PropTypes.bool, spoiler: React.PropTypes.bool, - spoiler_text: React.PropTypes.string, - unlisted: React.PropTypes.bool, private: React.PropTypes.bool, + unlisted: React.PropTypes.bool, + spoiler_text: React.PropTypes.string, fileDropDate: React.PropTypes.instanceOf(Date), focusDate: React.PropTypes.instanceOf(Date), preselectDate: React.PropTypes.instanceOf(Date), is_submitting: React.PropTypes.bool, is_uploading: React.PropTypes.bool, - media_count: React.PropTypes.number, me: React.PropTypes.number, needsPrivacyWarning: React.PropTypes.bool, mentionedDomains: React.PropTypes.array.isRequired, @@ -45,10 +46,7 @@ const ComposeForm = React.createClass({ onClearSuggestions: React.PropTypes.func.isRequired, onFetchSuggestions: React.PropTypes.func.isRequired, onSuggestionSelected: React.PropTypes.func.isRequired, - onChangeSensitivity: React.PropTypes.func.isRequired, - onChangeSpoilerness: React.PropTypes.func.isRequired, onChangeSpoilerText: React.PropTypes.func.isRequired, - onChangeVisibility: React.PropTypes.func.isRequired }, mixins: [PureRenderMixin], @@ -80,23 +78,10 @@ const ComposeForm = React.createClass({ this.props.onSuggestionSelected(tokenStart, token, value); }, - handleChangeSensitivity (e) { - this.props.onChangeSensitivity(e.target.checked); - }, - - handleChangeSpoilerness (e) { - this.props.onChangeSpoilerness(e.target.checked); - this.props.onChangeSpoilerText(''); - }, - handleChangeSpoilerText (e) { this.props.onChangeSpoilerText(e.target.value); }, - handleChangeVisibility (e) { - this.props.onChangeVisibility(e.target.checked); - }, - componentDidUpdate (prevProps) { if (this.props.focusDate !== prevProps.focusDate) { // If replying to zero or one users, places the cursor at the end of the textbox. @@ -172,24 +157,10 @@ const ComposeForm = React.createClass({ <UploadButtonContainer style={{ paddingTop: '4px' }} /> </div> - <label className='compose-form__label with-border' style={{ marginTop: '10px' }}> - <Toggle checked={this.props.spoiler} onChange={this.handleChangeSpoilerness} /> - <span className='compose-form__label__text'><FormattedMessage id='compose_form.spoiler' defaultMessage='Hide text behind warning' /></span> - </label> - - <label className='compose-form__label with-border'> - <Toggle checked={this.props.private} onChange={this.handleChangeVisibility} /> - <span className='compose-form__label__text'><FormattedMessage id='compose_form.private' defaultMessage='Mark as private' /></span> - </label> - + <SpoilerToggleContainer /> + <PrivateToggleContainer /> <UnlistedToggleContainer /> - - <Collapsable isVisible={this.props.media_count > 0} fullHeight={39.5}> - <label className='compose-form__label'> - <Toggle checked={this.props.sensitive} onChange={this.handleChangeSensitivity} /> - <span className='compose-form__label__text'><FormattedMessage id='compose_form.sensitive' defaultMessage='Mark media as sensitive' /></span> - </label> - </Collapsable> + <SensitiveToggleContainer /> </div> ); } diff --git a/app/assets/javascripts/components/features/compose/components/private_toggle.jsx b/app/assets/javascripts/components/features/compose/components/private_toggle.jsx new file mode 100644 index 000000000..902ee70ca --- /dev/null +++ b/app/assets/javascripts/components/features/compose/components/private_toggle.jsx @@ -0,0 +1,27 @@ +import PureRenderMixin from 'react-addons-pure-render-mixin'; +import { FormattedMessage } from 'react-intl'; +import Toggle from 'react-toggle'; + +const PrivateToggle = React.createClass({ + + propTypes: { + isPrivate: React.PropTypes.bool, + onChange: React.PropTypes.func.isRequired + }, + + mixins: [PureRenderMixin], + + render () { + const { isPrivate, onChange } = this.props; + + return ( + <label className='compose-form__label with-border'> + <Toggle checked={isPrivate} onChange={onChange} /> + <span className='compose-form__label__text'><FormattedMessage id='compose_form.private' defaultMessage='Mark as private' /></span> + </label> + ); + } + +}); + +export default PrivateToggle; diff --git a/app/assets/javascripts/components/features/compose/components/sensitive_toggle.jsx b/app/assets/javascripts/components/features/compose/components/sensitive_toggle.jsx new file mode 100644 index 000000000..97cc9487e --- /dev/null +++ b/app/assets/javascripts/components/features/compose/components/sensitive_toggle.jsx @@ -0,0 +1,31 @@ +import PureRenderMixin from 'react-addons-pure-render-mixin'; +import { FormattedMessage } from 'react-intl'; +import Toggle from 'react-toggle'; +import Collapsable from '../../../components/collapsable'; + +const SensitiveToggle = React.createClass({ + + propTypes: { + hasMedia: React.PropTypes.bool, + isSensitive: React.PropTypes.bool, + onChange: React.PropTypes.func.isRequired + }, + + mixins: [PureRenderMixin], + + render () { + const { hasMedia, isSensitive, onChange } = this.props; + + return ( + <Collapsable isVisible={hasMedia} fullHeight={39.5}> + <label className='compose-form__label'> + <Toggle checked={isSensitive} onChange={onChange} /> + <span className='compose-form__label__text'><FormattedMessage id='compose_form.sensitive' defaultMessage='Mark media as sensitive' /></span> + </label> + </Collapsable> + ); + } + +}); + +export default SensitiveToggle; diff --git a/app/assets/javascripts/components/features/compose/components/spoiler_toggle.jsx b/app/assets/javascripts/components/features/compose/components/spoiler_toggle.jsx new file mode 100644 index 000000000..1c59e4393 --- /dev/null +++ b/app/assets/javascripts/components/features/compose/components/spoiler_toggle.jsx @@ -0,0 +1,27 @@ +import PureRenderMixin from 'react-addons-pure-render-mixin'; +import { FormattedMessage } from 'react-intl'; +import Toggle from 'react-toggle'; + +const SpoilerToggle = React.createClass({ + + propTypes: { + isSpoiler: React.PropTypes.bool, + onChange: React.PropTypes.func.isRequired + }, + + mixins: [PureRenderMixin], + + render () { + const { isSpoiler, onChange } = this.props; + + return ( + <label className='compose-form__label with-border' style={{ marginTop: '10px' }}> + <Toggle checked={isSpoiler} onChange={onChange} /> + <span className='compose-form__label__text'><FormattedMessage id='compose_form.spoiler' defaultMessage='Hide text behind warning' /></span> + </label> + ); + } + +}); + +export default SpoilerToggle; diff --git a/app/assets/javascripts/components/features/compose/containers/compose_form_container.jsx b/app/assets/javascripts/components/features/compose/containers/compose_form_container.jsx index bff273c15..53129af6e 100644 --- a/app/assets/javascripts/components/features/compose/containers/compose_form_container.jsx +++ b/app/assets/javascripts/components/features/compose/containers/compose_form_container.jsx @@ -1,26 +1,29 @@ import { connect } from 'react-redux'; import ComposeForm from '../components/compose_form'; +import { createSelector } from 'reselect'; import { changeCompose, submitCompose, clearComposeSuggestions, fetchComposeSuggestions, selectComposeSuggestion, - changeComposeSensitivity, - changeComposeSpoilerness, changeComposeSpoilerText, - changeComposeVisibility, - changeComposeListability } from '../../../actions/compose'; +const getMentionedUsernames = createSelector(state => state.getIn(['compose', 'text']), text => text.match(/(?:^|[^\/\w])@([a-z0-9_]+@[a-z0-9\.\-]+)/ig)); + +const getMentionedDomains = createSelector(getMentionedUsernames, mentionedUsernamesWithDomains => { + return mentionedUsernamesWithDomains !== null ? [...new Set(mentionedUsernamesWithDomains.map(item => item.split('@')[2]))] : []; +}); + const mapStateToProps = (state, props) => { - const mentionedUsernamesWithDomains = state.getIn(['compose', 'text']).match(/(?:^|[^\/\w])@([a-z0-9_]+@[a-z0-9\.\-]+)/ig); + const mentionedUsernames = getMentionedUsernames(state); + const mentionedUsernamesWithDomains = getMentionedDomains(state); return { text: state.getIn(['compose', 'text']), suggestion_token: state.getIn(['compose', 'suggestion_token']), suggestions: state.getIn(['compose', 'suggestions']), - sensitive: state.getIn(['compose', 'sensitive']), spoiler: state.getIn(['compose', 'spoiler']), spoiler_text: state.getIn(['compose', 'spoiler_text']), unlisted: state.getIn(['compose', 'unlisted'], ), @@ -30,10 +33,9 @@ const mapStateToProps = (state, props) => { preselectDate: state.getIn(['compose', 'preselectDate']), is_submitting: state.getIn(['compose', 'is_submitting']), is_uploading: state.getIn(['compose', 'is_uploading']), - media_count: state.getIn(['compose', 'media_attachments']).size, me: state.getIn(['compose', 'me']), - needsPrivacyWarning: state.getIn(['compose', 'private']) && mentionedUsernamesWithDomains !== null, - mentionedDomains: mentionedUsernamesWithDomains !== null ? [...new Set(mentionedUsernamesWithDomains.map(item => item.split('@')[2]))] : [] + needsPrivacyWarning: state.getIn(['compose', 'private']) && mentionedUsernames !== null, + mentionedDomains: mentionedUsernamesWithDomains }; }; @@ -59,22 +61,10 @@ const mapDispatchToProps = (dispatch) => ({ dispatch(selectComposeSuggestion(position, token, accountId)); }, - onChangeSensitivity (checked) { - dispatch(changeComposeSensitivity(checked)); - }, - - onChangeSpoilerness (checked) { - dispatch(changeComposeSpoilerness(checked)); - }, - onChangeSpoilerText (checked) { dispatch(changeComposeSpoilerText(checked)); }, - onChangeVisibility (checked) { - dispatch(changeComposeVisibility(checked)); - }, - }); export default connect(mapStateToProps, mapDispatchToProps)(ComposeForm); diff --git a/app/assets/javascripts/components/features/compose/containers/private_toggle_container.jsx b/app/assets/javascripts/components/features/compose/containers/private_toggle_container.jsx new file mode 100644 index 000000000..ee3596902 --- /dev/null +++ b/app/assets/javascripts/components/features/compose/containers/private_toggle_container.jsx @@ -0,0 +1,17 @@ +import { connect } from 'react-redux'; +import PrivateToggle from '../components/private_toggle'; +import { changeComposeVisibility } from '../../../actions/compose'; + +const mapStateToProps = state => ({ + isPrivate: state.getIn(['compose', 'private']) +}); + +const mapDispatchToProps = dispatch => ({ + + onChange (e) { + dispatch(changeComposeVisibility(e.target.checked)); + } + +}); + +export default connect(mapStateToProps, mapDispatchToProps)(PrivateToggle); diff --git a/app/assets/javascripts/components/features/compose/containers/sensitive_toggle_container.jsx b/app/assets/javascripts/components/features/compose/containers/sensitive_toggle_container.jsx new file mode 100644 index 000000000..97b3361ba --- /dev/null +++ b/app/assets/javascripts/components/features/compose/containers/sensitive_toggle_container.jsx @@ -0,0 +1,18 @@ +import { connect } from 'react-redux'; +import SensitiveToggle from '../components/sensitive_toggle'; +import { changeComposeSensitivity } from '../../../actions/compose'; + +const mapStateToProps = state => ({ + hasMedia: state.getIn(['compose', 'media_attachments']).size > 0, + isSensitive: state.getIn(['compose', 'sensitive']) +}); + +const mapDispatchToProps = dispatch => ({ + + onChange (e) { + dispatch(changeComposeSensitivity(e.target.checked)); + } + +}); + +export default connect(mapStateToProps, mapDispatchToProps)(SensitiveToggle); diff --git a/app/assets/javascripts/components/features/compose/containers/spoiler_toggle_container.jsx b/app/assets/javascripts/components/features/compose/containers/spoiler_toggle_container.jsx new file mode 100644 index 000000000..0bd4df759 --- /dev/null +++ b/app/assets/javascripts/components/features/compose/containers/spoiler_toggle_container.jsx @@ -0,0 +1,17 @@ +import { connect } from 'react-redux'; +import SpoilerToggle from '../components/spoiler_toggle'; +import { changeComposeSpoilerness } from '../../../actions/compose'; + +const mapStateToProps = state => ({ + isSpoiler: state.getIn(['compose', 'spoiler']) +}); + +const mapDispatchToProps = dispatch => ({ + + onChange (e) { + dispatch(changeComposeSpoilerness(e.target.checked)); + } + +}); + +export default connect(mapStateToProps, mapDispatchToProps)(SpoilerToggle); diff --git a/app/assets/javascripts/components/features/ui/containers/modal_container.jsx b/app/assets/javascripts/components/features/ui/containers/modal_container.jsx index 4c47fb8c5..d8301b20f 100644 --- a/app/assets/javascripts/components/features/ui/containers/modal_container.jsx +++ b/app/assets/javascripts/components/features/ui/containers/modal_container.jsx @@ -131,19 +131,14 @@ const Modal = React.createClass({ return null; } - const url = media.get(index).get('url'); - const hasLeft = index > 0; - const hasRight = index + 1 < media.size; + const url = media.get(index).get('url'); let leftNav, rightNav; leftNav = rightNav = ''; - if (hasLeft) { - leftNav = <div style={leftNavStyle} className='modal-container--nav' onClick={this.handlePrevClick}><i className='fa fa-fw fa-chevron-left' /></div>; - } - - if (hasRight) { + if (media.size > 1) { + leftNav = <div style={leftNavStyle} className='modal-container--nav' onClick={this.handlePrevClick}><i className='fa fa-fw fa-chevron-left' /></div>; rightNav = <div style={rightNavStyle} className='modal-container--nav' onClick={this.handleNextClick}><i className='fa fa-fw fa-chevron-right' /></div>; } diff --git a/app/assets/javascripts/components/reducers/compose.jsx b/app/assets/javascripts/components/reducers/compose.jsx index e401a2d98..dead5fd77 100644 --- a/app/assets/javascripts/components/reducers/compose.jsx +++ b/app/assets/javascripts/components/reducers/compose.jsx @@ -116,7 +116,10 @@ export default function compose(state = initialState, action) { case COMPOSE_SENSITIVITY_CHANGE: return state.set('sensitive', action.checked); case COMPOSE_SPOILERNESS_CHANGE: - return (action.checked ? state : state.set('spoiler_text', '')).set('spoiler', action.checked); + return state.withMutations(map => { + map.set('spoiler_text', ''); + map.set('spoiler', action.checked); + }); case COMPOSE_SPOILER_TEXT_CHANGE: return state.set('spoiler_text', action.text); case COMPOSE_VISIBILITY_CHANGE: diff --git a/app/assets/javascripts/components/reducers/modal.jsx b/app/assets/javascripts/components/reducers/modal.jsx index 07da65771..37ffbc62b 100644 --- a/app/assets/javascripts/components/reducers/modal.jsx +++ b/app/assets/javascripts/components/reducers/modal.jsx @@ -23,9 +23,9 @@ export default function modal(state = initialState, action) { case MODAL_CLOSE: return state.set('open', false); case MODAL_INDEX_DECREASE: - return state.update('index', index => Math.max(index - 1, 0)); + return state.update('index', index => (index - 1) % state.get('media').size); case MODAL_INDEX_INCREASE: - return state.update('index', index => Math.min(index + 1, state.get('media').size - 1)); + return state.update('index', index => (index + 1) % state.get('media').size); default: return state; } diff --git a/app/assets/javascripts/components/selectors/index.jsx b/app/assets/javascripts/components/selectors/index.jsx index faa7f92d0..0e88654a1 100644 --- a/app/assets/javascripts/components/selectors/index.jsx +++ b/app/assets/javascripts/components/selectors/index.jsx @@ -1,4 +1,4 @@ -import { createSelector } from 'reselect' +import { createSelector } from 'reselect'; import Immutable from 'immutable'; const getStatuses = state => state.get('statuses'); |