Merge remote-tracking branch 'upstream/master'

This commit is contained in:
beatrix-bitrot
2017-06-27 20:46:13 +00:00
91 changed files with 1274 additions and 751 deletions

View File

@ -1,5 +1,6 @@
import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
export default class ImageLoader extends React.PureComponent {
@ -20,46 +21,121 @@ export default class ImageLoader extends React.PureComponent {
error: false,
}
componentWillMount() {
this._loadImage(this.props.src);
removers = [];
get canvasContext() {
if (!this.canvas) {
return null;
}
this._canvasContext = this._canvasContext || this.canvas.getContext('2d');
return this._canvasContext;
}
componentWillReceiveProps(props) {
this._loadImage(props.src);
componentDidMount () {
this.loadImage(this.props);
}
_loadImage(src) {
componentWillReceiveProps (nextProps) {
if (this.props.src !== nextProps.src) {
this.loadImage(nextProps);
}
}
loadImage (props) {
this.removeEventListeners();
this.setState({ loading: true, error: false });
Promise.all([
this.loadPreviewCanvas(props),
this.loadOriginalImage(props),
])
.then(() => {
this.setState({ loading: false, error: false });
this.clearPreviewCanvas();
})
.catch(() => this.setState({ loading: false, error: true }));
}
loadPreviewCanvas = ({ previewSrc, width, height }) => new Promise((resolve, reject) => {
const image = new Image();
const removeEventListeners = () => {
image.removeEventListener('error', handleError);
image.removeEventListener('load', handleLoad);
};
const handleError = () => {
removeEventListeners();
reject();
};
const handleLoad = () => {
removeEventListeners();
this.canvasContext.drawImage(image, 0, 0, width, height);
resolve();
};
image.addEventListener('error', handleError);
image.addEventListener('load', handleLoad);
image.src = previewSrc;
this.removers.push(removeEventListeners);
})
image.onerror = () => this.setState({ loading: false, error: true });
image.onload = () => this.setState({ loading: false, error: false });
image.src = src;
this.setState({ loading: true });
clearPreviewCanvas () {
const { width, height } = this.canvas;
this.canvasContext.clearRect(0, 0, width, height);
}
render() {
const { alt, src, previewSrc, width, height } = this.props;
loadOriginalImage = ({ src }) => new Promise((resolve, reject) => {
const image = new Image();
const removeEventListeners = () => {
image.removeEventListener('error', handleError);
image.removeEventListener('load', handleLoad);
};
const handleError = () => {
removeEventListeners();
reject();
};
const handleLoad = () => {
removeEventListeners();
resolve();
};
image.addEventListener('error', handleError);
image.addEventListener('load', handleLoad);
image.src = src;
this.removers.push(removeEventListeners);
});
removeEventListeners () {
this.removers.forEach(listeners => listeners());
this.removers = [];
}
setCanvasRef = c => {
this.canvas = c;
}
render () {
const { alt, src, width, height } = this.props;
const { loading } = this.state;
const className = classNames('image-loader', {
'image-loader--loading': loading,
});
return (
<div className='image-loader'>
<img
alt={alt}
className='image-loader__img'
src={src}
<div className={className}>
<canvas
className='image-loader__preview-canvas'
width={width}
height={height}
ref={this.setCanvasRef}
/>
{loading &&
{!loading && (
<img
alt=''
src={previewSrc}
className='image-loader__preview-img'
alt={alt}
className='image-loader__img'
src={src}
width={width}
height={height}
/>
}
)}
</div>
);
}

View File

@ -5,6 +5,7 @@ import OnboardingModal from './onboarding_modal';
import VideoModal from './video_modal';
import BoostModal from './boost_modal';
import ConfirmationModal from './confirmation_modal';
import ReportModal from './report_modal';
import TransitionMotion from 'react-motion/lib/TransitionMotion';
import spring from 'react-motion/lib/spring';
@ -14,6 +15,7 @@ const MODAL_COMPONENTS = {
'VIDEO': VideoModal,
'BOOST': BoostModal,
'CONFIRM': ConfirmationModal,
'REPORT': ReportModal,
};
export default class ModalRoot extends React.PureComponent {

View File

@ -3,6 +3,7 @@ import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import ReactSwipeable from 'react-swipeable';
import classNames from 'classnames';
import Permalink from '../../../components/permalink';
import TransitionMotion from 'react-motion/lib/TransitionMotion';
@ -274,7 +275,7 @@ export default class OnboardingModal extends React.PureComponent {
<div className='modal-root__modal onboarding-modal'>
<TransitionMotion styles={styles}>
{interpolatedStyles => (
<div className='onboarding-modal__pager'>
<ReactSwipeable onSwipedRight={this.handlePrev} onSwipedLeft={this.handleNext} className='onboarding-modal__pager'>
{interpolatedStyles.map(({ key, data, style }, i) => {
const className = classNames('onboarding-modal__page__wrapper', {
'onboarding-modal__page__wrapper--active': i === currentIndex,
@ -283,7 +284,7 @@ export default class OnboardingModal extends React.PureComponent {
<div key={key} style={style} className={className}>{data}</div>
);
})}
</div>
</ReactSwipeable>
)}
</TransitionMotion>

View File

@ -0,0 +1,105 @@
import React from 'react';
import { connect } from 'react-redux';
import { changeReportComment, submitReport } from '../../../actions/reports';
import { refreshAccountTimeline } from '../../../actions/timelines';
import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes';
import { makeGetAccount } from '../../../selectors';
import { defineMessages, FormattedMessage, injectIntl } from 'react-intl';
import StatusCheckBox from '../../report/containers/status_check_box_container';
import Immutable from 'immutable';
import ImmutablePureComponent from 'react-immutable-pure-component';
import Button from '../../../components/button';
const messages = defineMessages({
placeholder: { id: 'report.placeholder', defaultMessage: 'Additional comments' },
submit: { id: 'report.submit', defaultMessage: 'Submit' },
});
const makeMapStateToProps = () => {
const getAccount = makeGetAccount();
const mapStateToProps = state => {
const accountId = state.getIn(['reports', 'new', 'account_id']);
return {
isSubmitting: state.getIn(['reports', 'new', 'isSubmitting']),
account: getAccount(state, accountId),
comment: state.getIn(['reports', 'new', 'comment']),
statusIds: Immutable.OrderedSet(state.getIn(['timelines', `account:${accountId}`, 'items'])).union(state.getIn(['reports', 'new', 'status_ids'])),
};
};
return mapStateToProps;
};
@connect(makeMapStateToProps)
@injectIntl
export default class ReportModal extends ImmutablePureComponent {
static propTypes = {
isSubmitting: PropTypes.bool,
account: ImmutablePropTypes.map,
statusIds: ImmutablePropTypes.orderedSet.isRequired,
comment: PropTypes.string.isRequired,
dispatch: PropTypes.func.isRequired,
intl: PropTypes.object.isRequired,
};
handleCommentChange = (e) => {
this.props.dispatch(changeReportComment(e.target.value));
}
handleSubmit = () => {
this.props.dispatch(submitReport());
}
componentDidMount () {
this.props.dispatch(refreshAccountTimeline(this.props.account.get('id')));
}
componentWillReceiveProps (nextProps) {
if (this.props.account !== nextProps.account && nextProps.account) {
this.props.dispatch(refreshAccountTimeline(nextProps.account.get('id')));
}
}
render () {
const { account, comment, intl, statusIds, isSubmitting } = this.props;
if (!account) {
return null;
}
return (
<div className='modal-root__modal report-modal'>
<div className='report-modal__target'>
<FormattedMessage id='report.target' defaultMessage='Report {target}' values={{ target: <strong>{account.get('acct')}</strong> }} />
</div>
<div className='report-modal__container'>
<div className='report-modal__statuses'>
<div>
{statusIds.map(statusId => <StatusCheckBox id={statusId} key={statusId} disabled={isSubmitting} />)}
</div>
</div>
<div className='report-modal__comment'>
<textarea
className='setting-text light'
placeholder={intl.formatMessage(messages.placeholder)}
value={comment}
onChange={this.handleCommentChange}
disabled={isSubmitting}
/>
</div>
</div>
<div className='report-modal__action-bar'>
<Button disabled={isSubmitting} text={intl.formatMessage(messages.submit)} onClick={this.handleSubmit} />
</div>
</div>
);
}
}