131 lines
4 KiB
TypeScript
131 lines
4 KiB
TypeScript
|
import { useCallback } from 'react';
|
||
|
|
||
|
import { FormattedMessage } from 'react-intl';
|
||
|
|
||
|
import classNames from 'classnames';
|
||
|
|
||
|
import { useSortable } from '@dnd-kit/sortable';
|
||
|
import { CSS } from '@dnd-kit/utilities';
|
||
|
|
||
|
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 type { MediaAttachment } from 'mastodon/models/media_attachment';
|
||
|
import { useAppDispatch, useAppSelector } from 'mastodon/store';
|
||
|
|
||
|
export const Upload: React.FC<{
|
||
|
id: string;
|
||
|
dragging?: boolean;
|
||
|
overlay?: boolean;
|
||
|
tall?: boolean;
|
||
|
wide?: boolean;
|
||
|
}> = ({ id, dragging, overlay, tall, wide }) => {
|
||
|
const dispatch = useAppDispatch();
|
||
|
const media = useAppSelector(
|
||
|
(state) =>
|
||
|
state.compose // eslint-disable-line @typescript-eslint/no-unsafe-call
|
||
|
.get('media_attachments') // eslint-disable-line @typescript-eslint/no-unsafe-member-access
|
||
|
.find((item: MediaAttachment) => item.get('id') === id) as // eslint-disable-line @typescript-eslint/no-unsafe-member-access
|
||
|
| MediaAttachment
|
||
|
| undefined,
|
||
|
);
|
||
|
const sensitive = useAppSelector(
|
||
|
(state) => state.compose.get('spoiler') as boolean, // eslint-disable-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
|
||
|
);
|
||
|
|
||
|
const handleUndoClick = useCallback(() => {
|
||
|
dispatch(undoUploadCompose(id));
|
||
|
}, [dispatch, id]);
|
||
|
|
||
|
const handleFocalPointClick = useCallback(() => {
|
||
|
dispatch(initMediaEditModal(id));
|
||
|
}, [dispatch, id]);
|
||
|
|
||
|
const { attributes, listeners, setNodeRef, transform, transition } =
|
||
|
useSortable({ id });
|
||
|
|
||
|
if (!media) {
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
const focusX = media.getIn(['meta', 'focus', 'x']) as number;
|
||
|
const focusY = media.getIn(['meta', 'focus', 'y']) as number;
|
||
|
const x = (focusX / 2 + 0.5) * 100;
|
||
|
const y = (focusY / -2 + 0.5) * 100;
|
||
|
const missingDescription =
|
||
|
((media.get('description') as string | undefined) ?? '').length === 0;
|
||
|
|
||
|
const style = {
|
||
|
transform: CSS.Transform.toString(transform),
|
||
|
transition,
|
||
|
};
|
||
|
|
||
|
return (
|
||
|
<div
|
||
|
className={classNames('compose-form__upload media-gallery__item', {
|
||
|
dragging,
|
||
|
overlay,
|
||
|
'media-gallery__item--tall': tall,
|
||
|
'media-gallery__item--wide': wide,
|
||
|
})}
|
||
|
ref={setNodeRef}
|
||
|
style={style}
|
||
|
{...attributes}
|
||
|
{...listeners}
|
||
|
>
|
||
|
<div
|
||
|
className='compose-form__upload__thumbnail'
|
||
|
style={{
|
||
|
backgroundImage: !sensitive
|
||
|
? `url(${media.get('preview_url') as string})`
|
||
|
: undefined,
|
||
|
backgroundPosition: `${x}% ${y}%`,
|
||
|
}}
|
||
|
>
|
||
|
{sensitive && (
|
||
|
<Blurhash
|
||
|
hash={media.get('blurhash') as string}
|
||
|
className='compose-form__upload__preview'
|
||
|
/>
|
||
|
)}
|
||
|
|
||
|
<div className='compose-form__upload__actions'>
|
||
|
<button
|
||
|
type='button'
|
||
|
className='icon-button compose-form__upload__delete'
|
||
|
onClick={handleUndoClick}
|
||
|
>
|
||
|
<Icon id='close' icon={CloseIcon} />
|
||
|
</button>
|
||
|
<button
|
||
|
type='button'
|
||
|
className='icon-button'
|
||
|
onClick={handleFocalPointClick}
|
||
|
>
|
||
|
<Icon id='edit' 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 id='warning' icon={WarningIcon} />} ALT
|
||
|
</button>
|
||
|
</div>
|
||
|
</div>
|
||
|
</div>
|
||
|
);
|
||
|
};
|