import { useState, useCallback, useRef, useEffect, 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 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 } 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 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 [x, y] = position; const nodeRef = useRef(null); const draggingRef = useRef(false); const setRef = useCallback( (e: HTMLImageElement | HTMLVideoElement | null) => { nodeRef.current = e; }, [], ); const handleMouseDown = useCallback( (e: React.MouseEvent) => { if (e.button !== 0) { return; } const { x, y } = getPointerPosition(nodeRef.current, e); setDragging(true); draggingRef.current = true; onPositionChange([x, y]); }, [setDragging, onPositionChange], ); const handleTouchStart = useCallback( (e: React.TouchEvent) => { const { x, y } = getPointerPosition(nodeRef.current, e); setDragging(true); draggingRef.current = true; onPositionChange([x, y]); }, [setDragging, onPositionChange], ); useEffect(() => { const handleMouseUp = () => { setDragging(false); draggingRef.current = false; }; const handleMouseMove = (e: MouseEvent) => { if (draggingRef.current) { const { x, y } = getPointerPosition(nodeRef.current, e); onPositionChange([x, y]); } }; const handleTouchEnd = () => { setDragging(false); draggingRef.current = false; }; const handleTouchMove = (e: TouchEvent) => { if (draggingRef.current) { const { x, y } = getPointerPosition(nodeRef.current, e); onPositionChange([x, y]); } }; document.addEventListener('mouseup', handleMouseUp); document.addEventListener('mousemove', handleMouseMove); document.addEventListener('touchend', handleTouchEnd); document.addEventListener('touchmove', handleTouchMove); return () => { document.removeEventListener('mouseup', handleMouseUp); document.removeEventListener('mousemove', handleMouseMove); document.removeEventListener('touchend', handleTouchEnd); document.removeEventListener('touchmove', handleTouchMove); }; }, [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 (