diff options
author | Eugen Rochko <eugen@zeonfederated.com> | 2017-03-24 03:50:30 +0100 |
---|---|---|
committer | Eugen Rochko <eugen@zeonfederated.com> | 2017-03-24 03:55:45 +0100 |
commit | d7c6c6dbe109544911f3fca5c547b55d1e79ccc2 (patch) | |
tree | 346b25e3801e5c9d873404e68f88a44cb0ec1636 /app/assets/javascripts/components/features | |
parent | 3e2d6ea4083c878e51eb291f2b04d004d3d0ff60 (diff) |
Fancier drag & drop indicator, emoji icon for emoji, upload progress (fix #295)
Diffstat (limited to 'app/assets/javascripts/components/features')
8 files changed, 118 insertions, 23 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 047c974f2..2a252af4b 100644 --- a/app/assets/javascripts/components/features/compose/components/compose_form.jsx +++ b/app/assets/javascripts/components/features/compose/components/compose_form.jsx @@ -34,7 +34,6 @@ const ComposeForm = React.createClass({ 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, @@ -161,7 +160,6 @@ const ComposeForm = React.createClass({ ref={this.setAutosuggestTextarea} placeholder={intl.formatMessage(messages.placeholder)} disabled={disabled} - fileDropDate={this.props.fileDropDate} value={this.props.text} onChange={this.handleChange} suggestions={this.props.suggestions} diff --git a/app/assets/javascripts/components/features/compose/components/emoji_picker_dropdown.jsx b/app/assets/javascripts/components/features/compose/components/emoji_picker_dropdown.jsx index 3a454a5fb..37e366203 100644 --- a/app/assets/javascripts/components/features/compose/components/emoji_picker_dropdown.jsx +++ b/app/assets/javascripts/components/features/compose/components/emoji_picker_dropdown.jsx @@ -4,7 +4,7 @@ import PureRenderMixin from 'react-addons-pure-render-mixin'; import { defineMessages, injectIntl } from 'react-intl'; const messages = defineMessages({ - emoji: { id: 'emoji_button.label', defaultMessage: 'Emoji' } + emoji: { id: 'emoji_button.label', defaultMessage: 'Insert emoji' } }); const settings = { @@ -36,8 +36,8 @@ const EmojiPickerDropdown = React.createClass({ return ( <Dropdown ref={this.setRef} style={{ marginLeft: '5px' }}> - <DropdownTrigger className='icon-button' title={intl.formatMessage(messages.emoji)} style={{ fontSize: `24px`, width: `24px`, lineHeight: `24px`, display: 'block', marginLeft: '2px' }}> - <i className={`fa fa-smile-o`} style={{ verticalAlign: 'middle' }} /> + <DropdownTrigger className='icon-button emoji-button' title={intl.formatMessage(messages.emoji)} style={{ fontSize: `24px`, width: `24px`, lineHeight: `24px`, display: 'block', marginLeft: '2px' }}> + <img className="emojione" alt="🙂" src="/emoji/1f642.png" /> </DropdownTrigger> <DropdownContent> diff --git a/app/assets/javascripts/components/features/compose/components/upload_form.jsx b/app/assets/javascripts/components/features/compose/components/upload_form.jsx index 94c94b4b7..1a01c2380 100644 --- a/app/assets/javascripts/components/features/compose/components/upload_form.jsx +++ b/app/assets/javascripts/components/features/compose/components/upload_form.jsx @@ -2,6 +2,8 @@ import PureRenderMixin from 'react-addons-pure-render-mixin'; import ImmutablePropTypes from 'react-immutable-proptypes'; import IconButton from '../../../components/icon_button'; import { defineMessages, injectIntl } from 'react-intl'; +import UploadProgressContainer from '../containers/upload_progress_container'; +import { Motion, spring } from 'react-motion'; const messages = defineMessages({ undo: { id: 'upload_form.undo', defaultMessage: 'Undo' } @@ -11,7 +13,6 @@ const UploadForm = React.createClass({ propTypes: { media: ImmutablePropTypes.list.isRequired, - is_uploading: React.PropTypes.bool, onRemoveFile: React.PropTypes.func.isRequired, intl: React.PropTypes.object.isRequired }, @@ -21,20 +22,21 @@ const UploadForm = React.createClass({ render () { const { intl, media } = this.props; - if (!media.size) { - return null; - } - - const uploads = media.map(attachment => ( - <div key={attachment.get('id')} style={{ borderRadius: '4px', marginBottom: '10px' }} className='transparent-background'> - <div style={{ width: '100%', height: '100px', borderRadius: '4px', background: `url(${attachment.get('preview_url')}) no-repeat center`, backgroundSize: 'cover' }}> - <IconButton icon='times' title={intl.formatMessage(messages.undo)} size={36} onClick={this.props.onRemoveFile.bind(this, attachment.get('id'))} /> - </div> + const uploads = media.map(attachment => + <div key={attachment.get('id')} style={{ marginBottom: '10px' }}> + <Motion defaultStyle={{ scale: 0.8 }} style={{ scale: spring(1, { stiffness: 180, damping: 12 }) }}> + {({ scale }) => + <div style={{ transform: `translateZ(0) scale(${scale})`, width: '100%', height: '100px', borderRadius: '4px', background: `url(${attachment.get('preview_url')}) no-repeat center`, backgroundSize: 'cover' }}> + <IconButton icon='times' title={intl.formatMessage(messages.undo)} size={36} onClick={this.props.onRemoveFile.bind(this, attachment.get('id'))} /> + </div> + } + </Motion> </div> - )); + ); return ( <div style={{ marginBottom: '20px', padding: '10px', overflow: 'hidden', flexShrink: '0' }}> + <UploadProgressContainer /> {uploads} </div> ); diff --git a/app/assets/javascripts/components/features/compose/components/upload_progress.jsx b/app/assets/javascripts/components/features/compose/components/upload_progress.jsx new file mode 100644 index 000000000..86ffbf936 --- /dev/null +++ b/app/assets/javascripts/components/features/compose/components/upload_progress.jsx @@ -0,0 +1,44 @@ +import PureRenderMixin from 'react-addons-pure-render-mixin'; +import { Motion, spring } from 'react-motion'; +import { FormattedMessage } from 'react-intl'; + +const UploadProgress = React.createClass({ + + propTypes: { + active: React.PropTypes.bool, + progress: React.PropTypes.number + }, + + mixins: [PureRenderMixin], + + render () { + const { active, progress } = this.props; + + if (!active) { + return null; + } + + return ( + <div className='upload-progress'> + <div> + <i className='fa fa-upload' /> + </div> + + <div style={{ flex: '1 1 auto' }}> + <FormattedMessage id='upload_progress.label' defaultMessage='Uploading...' /> + + <div className='upload-progress__backdrop'> + <Motion defaultStyle={{ width: 0 }} style={{ width: spring(progress) }}> + {({ width }) => + <div className='upload-progress__tracker' style={{ width: `${width}%` }} /> + } + </Motion> + </div> + </div> + </div> + ); + } + +}); + +export default UploadProgress; 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 a67adbdd6..835b37516 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 @@ -30,7 +30,6 @@ const mapStateToProps = (state, props) => { spoiler_text: state.getIn(['compose', 'spoiler_text']), unlisted: state.getIn(['compose', 'unlisted'], ), private: state.getIn(['compose', 'private']), - fileDropDate: state.getIn(['compose', 'fileDropDate']), focusDate: state.getIn(['compose', 'focusDate']), preselectDate: state.getIn(['compose', 'preselectDate']), is_submitting: state.getIn(['compose', 'is_submitting']), diff --git a/app/assets/javascripts/components/features/compose/containers/upload_progress_container.jsx b/app/assets/javascripts/components/features/compose/containers/upload_progress_container.jsx new file mode 100644 index 000000000..b0f1d4d19 --- /dev/null +++ b/app/assets/javascripts/components/features/compose/containers/upload_progress_container.jsx @@ -0,0 +1,9 @@ +import { connect } from 'react-redux'; +import UploadProgress from '../components/upload_progress'; + +const mapStateToProps = (state, props) => ({ + active: state.getIn(['compose', 'is_uploading']), + progress: state.getIn(['compose', 'progress']) +}); + +export default connect(mapStateToProps)(UploadProgress); diff --git a/app/assets/javascripts/components/features/ui/components/upload_area.jsx b/app/assets/javascripts/components/features/ui/components/upload_area.jsx new file mode 100644 index 000000000..70b687019 --- /dev/null +++ b/app/assets/javascripts/components/features/ui/components/upload_area.jsx @@ -0,0 +1,32 @@ +import PureRenderMixin from 'react-addons-pure-render-mixin'; +import { Motion, spring } from 'react-motion'; +import { FormattedMessage } from 'react-intl'; + +const UploadArea = React.createClass({ + + propTypes: { + active: React.PropTypes.bool + }, + + mixins: [PureRenderMixin], + + render () { + const { active } = this.props; + + return ( + <Motion defaultStyle={{ backgroundOpacity: 0, backgroundScale: 0.95 }} style={{ backgroundOpacity: spring(active ? 1 : 0, { stiffness: 150, damping: 15 }), backgroundScale: spring(active ? 1 : 0.95, { stiffness: 200, damping: 3 }) }}> + {({ backgroundOpacity, backgroundScale }) => + <div className='upload-area' style={{ visibility: active ? 'visible' : 'hidden', opacity: backgroundOpacity }}> + <div className='upload-area__drop'> + <div className='upload-area__background' style={{ transform: `translateZ(0) scale(${backgroundScale})` }} /> + <div className='upload-area__content'><FormattedMessage id='upload_area.title' defaultMessage='Drag & drop to upload' /></div> + </div> + </div> + } + </Motion> + ); + } + +}); + +export default UploadArea; diff --git a/app/assets/javascripts/components/features/ui/index.jsx b/app/assets/javascripts/components/features/ui/index.jsx index 900d83dba..10e989b2a 100644 --- a/app/assets/javascripts/components/features/ui/index.jsx +++ b/app/assets/javascripts/components/features/ui/index.jsx @@ -13,6 +13,7 @@ import { debounce } from 'react-decoration'; import { uploadCompose } from '../../actions/compose'; import { refreshTimeline } from '../../actions/timelines'; import { refreshNotifications } from '../../actions/notifications'; +import UploadArea from './components/upload_area'; const UI = React.createClass({ @@ -23,7 +24,8 @@ const UI = React.createClass({ getInitialState () { return { - width: window.innerWidth + width: window.innerWidth, + draggingOver: false }; }, @@ -41,7 +43,7 @@ const UI = React.createClass({ e.dataTransfer.dropEffect = 'copy'; if (e.dataTransfer.effectAllowed === 'all' || e.dataTransfer.effectAllowed === 'uninitialized') { - // + this.setState({ draggingOver: true }); } }, @@ -49,10 +51,15 @@ const UI = React.createClass({ e.preventDefault(); if (e.dataTransfer && e.dataTransfer.files.length === 1) { + this.setState({ draggingOver: false }); this.props.dispatch(uploadCompose(e.dataTransfer.files)); } }, + handleDragLeave () { + this.setState({ draggingOver: false }); + }, + componentWillMount () { window.addEventListener('resize', this.handleResize, { passive: true }); window.addEventListener('dragover', this.handleDragOver); @@ -69,12 +76,15 @@ const UI = React.createClass({ }, render () { + const { width, draggingOver } = this.state; + const { children } = this.props; + let mountedColumns; - if (isMobile(this.state.width)) { + if (isMobile(width)) { mountedColumns = ( <ColumnsArea> - {this.props.children} + {children} </ColumnsArea> ); } else { @@ -83,13 +93,13 @@ const UI = React.createClass({ <Compose withHeader={true} /> <HomeTimeline trackScroll={false} /> <Notifications trackScroll={false} /> - {this.props.children} + {children} </ColumnsArea> ); } return ( - <div className='ui'> + <div className='ui' onDragLeave={this.handleDragLeave}> <TabsBar /> {mountedColumns} @@ -97,6 +107,7 @@ const UI = React.createClass({ <NotificationsContainer /> <LoadingBarContainer style={{ backgroundColor: '#2b90d9', left: '0', top: '0' }} /> <ModalContainer /> + <UploadArea active={draggingOver} /> </div> ); } |