Fancier drag & drop indicator, emoji icon for emoji, upload progress (fix #295)

This commit is contained in:
Eugen Rochko 2017-03-24 03:50:30 +01:00
parent 3e2d6ea408
commit d7c6c6dbe1
13 changed files with 244 additions and 75 deletions

View file

@ -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}

View file

@ -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>

View file

@ -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>
);

View file

@ -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;

View file

@ -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']),

View file

@ -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);

View file

@ -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;

View file

@ -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>
);
}