import { useState, useCallback, useRef, useImperativeHandle, forwardRef, } from 'react'; import { FormattedMessage, useIntl, defineMessages } from 'react-intl'; import classNames from 'classnames'; import type { List as ImmutableList, Map as ImmutableMap } from 'immutable'; import { useSpring, animated } from '@react-spring/web'; import Textarea from 'react-textarea-autosize'; import { length } from 'stringz'; // eslint-disable-next-line import/extensions import tesseractWorkerPath from 'tesseract.js/dist/worker.min.js'; // eslint-disable-next-line import/no-extraneous-dependencies import tesseractCorePath from 'tesseract.js-core/tesseract-core.wasm.js'; import { showAlertForError } from 'mastodon/actions/alerts'; import { uploadThumbnail } from 'mastodon/actions/compose'; import { changeUploadCompose } from 'mastodon/actions/compose_typed'; import { Button } from 'mastodon/components/button'; import { GIFV } from 'mastodon/components/gifv'; import { LoadingIndicator } from 'mastodon/components/loading_indicator'; import { Skeleton } from 'mastodon/components/skeleton'; import Audio from 'mastodon/features/audio'; import { CharacterCounter } from 'mastodon/features/compose/components/character_counter'; import { Tesseract as fetchTesseract } from 'mastodon/features/ui/util/async-components'; import { Video, getPointerPosition } from 'mastodon/features/video'; import { me, reduceMotion } from 'mastodon/initial_state'; import type { MediaAttachment } from 'mastodon/models/media_attachment'; import { useAppSelector, useAppDispatch } from 'mastodon/store'; import { assetHost } from 'mastodon/utils/config'; import { InfoButton } from './components/info_button'; const messages = defineMessages({ placeholderVisual: { id: 'alt_text_modal.describe_for_people_with_visual_impairments', defaultMessage: 'Describe this for people with visual impairments…', }, placeholderHearing: { id: 'alt_text_modal.describe_for_people_with_hearing_impairments', defaultMessage: 'Describe this for people with hearing impairments…', }, discardMessage: { id: 'confirmations.discard_edit_media.message', defaultMessage: 'You have unsaved changes to the media description or preview, discard them anyway?', }, discardConfirm: { id: 'confirmations.discard_edit_media.confirm', defaultMessage: 'Discard', }, }); const MAX_LENGTH = 1500; type FocalPoint = [number, number]; const UploadButton: React.FC<{ children: React.ReactNode; onSelectFile: (arg0: File) => void; mimeTypes: string; }> = ({ children, onSelectFile, mimeTypes }) => { const fileRef = useRef(null); const handleClick = useCallback(() => { fileRef.current?.click(); }, []); const handleChange = useCallback( (e: React.ChangeEvent) => { const file = e.target.files?.[0]; if (file) { onSelectFile(file); } }, [onSelectFile], ); return ( ); }; const Preview: React.FC<{ mediaId: string; position: FocalPoint; onPositionChange: (arg0: FocalPoint) => void; }> = ({ mediaId, position, onPositionChange }) => { const draggingRef = useRef(false); const nodeRef = useRef(null); const [x, y] = position; const style = useSpring({ to: { left: `${x * 100}%`, top: `${y * 100}%`, }, immediate: reduceMotion || draggingRef.current, }); const media = useAppSelector((state) => ( (state.compose as ImmutableMap).get( 'media_attachments', ) as ImmutableList ).find((x) => x.get('id') === mediaId), ); const account = useAppSelector((state) => me ? state.accounts.get(me) : undefined, ); const [dragging, setDragging] = useState(false); const setRef = useCallback( (e: HTMLImageElement | HTMLVideoElement | null) => { nodeRef.current = e; }, [], ); const handleMouseDown = useCallback( (e: React.MouseEvent) => { if (e.button !== 0) { return; } const handleMouseMove = (e: MouseEvent) => { const { x, y } = getPointerPosition(nodeRef.current, e); draggingRef.current = true; // This will disable the animation for quicker feedback, only do this if the mouse actually moves onPositionChange([x, y]); }; const handleMouseUp = () => { setDragging(false); draggingRef.current = false; document.removeEventListener('mouseup', handleMouseUp); document.removeEventListener('mousemove', handleMouseMove); }; const { x, y } = getPointerPosition(nodeRef.current, e.nativeEvent); setDragging(true); onPositionChange([x, y]); document.addEventListener('mouseup', handleMouseUp); document.addEventListener('mousemove', handleMouseMove); }, [setDragging, onPositionChange], ); if (!media) { return null; } if (media.get('type') === 'image') { return (
); } else if (media.get('type') === 'gifv') { return (
); } else if (media.get('type') === 'video') { return (