10 changed files with 159 additions and 171 deletions
@ -1,77 +1,81 @@
|
||||
import PropTypes from 'prop-types'; |
||||
import { useCallback } from 'react'; |
||||
|
||||
import { FormattedMessage } from 'react-intl'; |
||||
|
||||
import classNames from 'classnames'; |
||||
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes'; |
||||
import ImmutablePureComponent from 'react-immutable-pure-component'; |
||||
import { useDispatch, useSelector } from 'react-redux'; |
||||
|
||||
import spring from 'react-motion/lib/spring'; |
||||
|
||||
import CloseIcon from '@/material-icons/400-20px/close.svg?react'; |
||||
import EditIcon from '@/material-icons/400-24px/edit.svg?react'; |
||||
import WarningIcon from '@/material-icons/400-24px/warning.svg?react'; |
||||
import { undoUploadCompose, initMediaEditModal } from 'mastodon/actions/compose'; |
||||
import { Blurhash } from 'mastodon/components/blurhash'; |
||||
import { Icon } from 'mastodon/components/icon'; |
||||
import Motion from 'mastodon/features/ui/util/optional_motion'; |
||||
|
||||
import Motion from '../../ui/util/optional_motion'; |
||||
|
||||
export default class Upload extends ImmutablePureComponent { |
||||
|
||||
static propTypes = { |
||||
media: ImmutablePropTypes.map.isRequired, |
||||
sensitive: PropTypes.bool, |
||||
onUndo: PropTypes.func.isRequired, |
||||
onOpenFocalPoint: PropTypes.func.isRequired, |
||||
}; |
||||
|
||||
handleUndoClick = e => { |
||||
e.stopPropagation(); |
||||
this.props.onUndo(this.props.media.get('id')); |
||||
}; |
||||
|
||||
handleFocalPointClick = e => { |
||||
e.stopPropagation(); |
||||
this.props.onOpenFocalPoint(this.props.media.get('id')); |
||||
}; |
||||
|
||||
render () { |
||||
const { media, sensitive } = this.props; |
||||
|
||||
if (!media) { |
||||
return null; |
||||
} |
||||
|
||||
const focusX = media.getIn(['meta', 'focus', 'x']); |
||||
const focusY = media.getIn(['meta', 'focus', 'y']); |
||||
const x = ((focusX / 2) + .5) * 100; |
||||
const y = ((focusY / -2) + .5) * 100; |
||||
const missingDescription = (media.get('description') || '').length === 0; |
||||
|
||||
return ( |
||||
<div className='compose-form__upload'> |
||||
<Motion defaultStyle={{ scale: 0.8 }} style={{ scale: spring(1, { stiffness: 180, damping: 12 }) }}> |
||||
{({ scale }) => ( |
||||
<div className='compose-form__upload__thumbnail' style={{ transform: `scale(${scale})`, backgroundImage: !sensitive ? `url(${media.get('preview_url')})` : null, backgroundPosition: `${x}% ${y}%` }}> |
||||
{sensitive && <Blurhash |
||||
hash={media.get('blurhash')} |
||||
className='compose-form__upload__preview' |
||||
/>} |
||||
|
||||
<div className='compose-form__upload__actions'> |
||||
<button type='button' className='icon-button compose-form__upload__delete' onClick={this.handleUndoClick}><Icon icon={CloseIcon} /></button> |
||||
<button type='button' className='icon-button' onClick={this.handleFocalPointClick}><Icon icon={EditIcon} /> <FormattedMessage id='upload_form.edit' defaultMessage='Edit' /></button> |
||||
</div> |
||||
|
||||
<div className='compose-form__upload__warning'> |
||||
<button type='button' className={classNames('icon-button', { active: missingDescription })} onClick={this.handleFocalPointClick}>{missingDescription && <Icon icon={WarningIcon} />} ALT</button> |
||||
</div> |
||||
</div> |
||||
)} |
||||
</Motion> |
||||
</div> |
||||
); |
||||
export const Upload = ({ id, onDragStart, onDragEnter, onDragEnd }) => { |
||||
const dispatch = useDispatch(); |
||||
const media = useSelector(state => state.getIn(['compose', 'media_attachments']).find(item => item.get('id') === id)); |
||||
const sensitive = useSelector(state => state.getIn(['compose', 'spoiler'])); |
||||
|
||||
const handleUndoClick = useCallback(() => { |
||||
dispatch(undoUploadCompose(id)); |
||||
}, [dispatch, id]); |
||||
|
||||
const handleFocalPointClick = useCallback(() => { |
||||
dispatch(initMediaEditModal(id)); |
||||
}, [dispatch, id]); |
||||
|
||||
const handleDragStart = useCallback(() => { |
||||
onDragStart(id); |
||||
}, [onDragStart, id]); |
||||
|
||||
const handleDragEnter = useCallback(() => { |
||||
onDragEnter(id); |
||||
}, [onDragEnter, id]); |
||||
|
||||
if (!media) { |
||||
return null; |
||||
} |
||||
|
||||
} |
||||
const focusX = media.getIn(['meta', 'focus', 'x']); |
||||
const focusY = media.getIn(['meta', 'focus', 'y']); |
||||
const x = ((focusX / 2) + .5) * 100; |
||||
const y = ((focusY / -2) + .5) * 100; |
||||
const missingDescription = (media.get('description') || '').length === 0; |
||||
|
||||
return ( |
||||
<div className='compose-form__upload' draggable onDragStart={handleDragStart} onDragEnter={handleDragEnter} onDragEnd={onDragEnd}> |
||||
<Motion defaultStyle={{ scale: 0.8 }} style={{ scale: spring(1, { stiffness: 180, damping: 12 }) }}> |
||||
{({ scale }) => ( |
||||
<div className='compose-form__upload__thumbnail' style={{ transform: `scale(${scale})`, backgroundImage: !sensitive ? `url(${media.get('preview_url')})` : null, backgroundPosition: `${x}% ${y}%` }}> |
||||
{sensitive && <Blurhash |
||||
hash={media.get('blurhash')} |
||||
className='compose-form__upload__preview' |
||||
/>} |
||||
|
||||
<div className='compose-form__upload__actions'> |
||||
<button type='button' className='icon-button compose-form__upload__delete' onClick={handleUndoClick}><Icon icon={CloseIcon} /></button> |
||||
<button type='button' className='icon-button' onClick={handleFocalPointClick}><Icon icon={EditIcon} /> <FormattedMessage id='upload_form.edit' defaultMessage='Edit' /></button> |
||||
</div> |
||||
|
||||
<div className='compose-form__upload__warning'> |
||||
<button type='button' className={classNames('icon-button', { active: missingDescription })} onClick={handleFocalPointClick}>{missingDescription && <Icon icon={WarningIcon} />} ALT</button> |
||||
</div> |
||||
</div> |
||||
)} |
||||
</Motion> |
||||
</div> |
||||
); |
||||
}; |
||||
|
||||
Upload.propTypes = { |
||||
id: PropTypes.string, |
||||
onDragEnter: PropTypes.func, |
||||
onDragStart: PropTypes.func, |
||||
onDragEnd: PropTypes.func, |
||||
}; |
||||
|
||||
@ -1,31 +1,53 @@
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes'; |
||||
import ImmutablePureComponent from 'react-immutable-pure-component'; |
||||
import { useRef, useCallback } from 'react'; |
||||
|
||||
import UploadContainer from '../containers/upload_container'; |
||||
import UploadProgressContainer from '../containers/upload_progress_container'; |
||||
import { useSelector, useDispatch } from 'react-redux'; |
||||
|
||||
export default class UploadForm extends ImmutablePureComponent { |
||||
import { changeMediaOrder } from 'mastodon/actions/compose'; |
||||
|
||||
static propTypes = { |
||||
mediaIds: ImmutablePropTypes.list.isRequired, |
||||
}; |
||||
import { Upload } from './upload'; |
||||
import { UploadProgress } from './upload_progress'; |
||||
|
||||
render () { |
||||
const { mediaIds } = this.props; |
||||
export const UploadForm = () => { |
||||
const dispatch = useDispatch(); |
||||
const mediaIds = useSelector(state => state.getIn(['compose', 'media_attachments']).map(item => item.get('id'))); |
||||
const active = useSelector(state => state.getIn(['compose', 'is_uploading'])); |
||||
const progress = useSelector(state => state.getIn(['compose', 'progress'])); |
||||
const isProcessing = useSelector(state => state.getIn(['compose', 'is_processing'])); |
||||
|
||||
return ( |
||||
<> |
||||
<UploadProgressContainer /> |
||||
const dragItem = useRef(); |
||||
const dragOverItem = useRef(); |
||||
|
||||
{mediaIds.size > 0 && ( |
||||
<div className='compose-form__uploads'> |
||||
{mediaIds.map(id => ( |
||||
<UploadContainer id={id} key={id} /> |
||||
))} |
||||
</div> |
||||
)} |
||||
</> |
||||
); |
||||
} |
||||
const handleDragStart = useCallback(id => { |
||||
dragItem.current = id; |
||||
}, [dragItem]); |
||||
|
||||
} |
||||
const handleDragEnter = useCallback(id => { |
||||
dragOverItem.current = id; |
||||
}, [dragOverItem]); |
||||
|
||||
const handleDragEnd = useCallback(() => { |
||||
dispatch(changeMediaOrder(dragItem.current, dragOverItem.current)); |
||||
dragItem.current = null; |
||||
dragOverItem.current = null; |
||||
}, [dispatch, dragItem, dragOverItem]); |
||||
|
||||
return ( |
||||
<> |
||||
<UploadProgress active={active} progress={progress} isProcessing={isProcessing} /> |
||||
|
||||
{mediaIds.size > 0 && ( |
||||
<div className='compose-form__uploads'> |
||||
{mediaIds.map(id => ( |
||||
<Upload |
||||
key={id} |
||||
id={id} |
||||
onDragStart={handleDragStart} |
||||
onDragEnter={handleDragEnter} |
||||
onDragEnd={handleDragEnd} |
||||
/> |
||||
))} |
||||
</div> |
||||
)} |
||||
</> |
||||
); |
||||
}; |
||||
|
||||
@ -1,27 +0,0 @@
|
||||
import { connect } from 'react-redux'; |
||||
|
||||
import { undoUploadCompose, initMediaEditModal, submitCompose } from '../../../actions/compose'; |
||||
import Upload from '../components/upload'; |
||||
|
||||
const mapStateToProps = (state, { id }) => ({ |
||||
media: state.getIn(['compose', 'media_attachments']).find(item => item.get('id') === id), |
||||
sensitive: state.getIn(['compose', 'spoiler']), |
||||
}); |
||||
|
||||
const mapDispatchToProps = dispatch => ({ |
||||
|
||||
onUndo: id => { |
||||
dispatch(undoUploadCompose(id)); |
||||
}, |
||||
|
||||
onOpenFocalPoint: id => { |
||||
dispatch(initMediaEditModal(id)); |
||||
}, |
||||
|
||||
onSubmit (router) { |
||||
dispatch(submitCompose(router)); |
||||
}, |
||||
|
||||
}); |
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(Upload); |
||||
@ -1,9 +0,0 @@
|
||||
import { connect } from 'react-redux'; |
||||
|
||||
import UploadForm from '../components/upload_form'; |
||||
|
||||
const mapStateToProps = state => ({ |
||||
mediaIds: state.getIn(['compose', 'media_attachments']).map(item => item.get('id')), |
||||
}); |
||||
|
||||
export default connect(mapStateToProps)(UploadForm); |
||||
@ -1,11 +0,0 @@
|
||||
import { connect } from 'react-redux'; |
||||
|
||||
import UploadProgress from '../components/upload_progress'; |
||||
|
||||
const mapStateToProps = state => ({ |
||||
active: state.getIn(['compose', 'is_uploading']), |
||||
progress: state.getIn(['compose', 'progress']), |
||||
isProcessing: state.getIn(['compose', 'is_processing']), |
||||
}); |
||||
|
||||
export default connect(mapStateToProps)(UploadProgress); |
||||
Loading…
Reference in new issue