Change alt text modal to use spring animations in web UI (#34345)

This commit is contained in:
Eugen Rochko 2025-04-08 21:22:05 +02:00 committed by GitHub
parent 0e5be63fb3
commit b7c3235349
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -2,7 +2,6 @@ import {
useState, useState,
useCallback, useCallback,
useRef, useRef,
useEffect,
useImperativeHandle, useImperativeHandle,
forwardRef, forwardRef,
} from 'react'; } from 'react';
@ -13,6 +12,7 @@ import classNames from 'classnames';
import type { List as ImmutableList, Map as ImmutableMap } from 'immutable'; import type { List as ImmutableList, Map as ImmutableMap } from 'immutable';
import { useSpring, animated } from '@react-spring/web';
import Textarea from 'react-textarea-autosize'; import Textarea from 'react-textarea-autosize';
import { length } from 'stringz'; import { length } from 'stringz';
// eslint-disable-next-line import/extensions // eslint-disable-next-line import/extensions
@ -31,7 +31,7 @@ import Audio from 'mastodon/features/audio';
import { CharacterCounter } from 'mastodon/features/compose/components/character_counter'; import { CharacterCounter } from 'mastodon/features/compose/components/character_counter';
import { Tesseract as fetchTesseract } from 'mastodon/features/ui/util/async-components'; import { Tesseract as fetchTesseract } from 'mastodon/features/ui/util/async-components';
import { Video, getPointerPosition } from 'mastodon/features/video'; import { Video, getPointerPosition } from 'mastodon/features/video';
import { me } from 'mastodon/initial_state'; import { me, reduceMotion } from 'mastodon/initial_state';
import type { MediaAttachment } from 'mastodon/models/media_attachment'; import type { MediaAttachment } from 'mastodon/models/media_attachment';
import { useAppSelector, useAppDispatch } from 'mastodon/store'; import { useAppSelector, useAppDispatch } from 'mastodon/store';
import { assetHost } from 'mastodon/utils/config'; import { assetHost } from 'mastodon/utils/config';
@ -105,6 +105,17 @@ const Preview: React.FC<{
position: FocalPoint; position: FocalPoint;
onPositionChange: (arg0: FocalPoint) => void; onPositionChange: (arg0: FocalPoint) => void;
}> = ({ mediaId, position, onPositionChange }) => { }> = ({ mediaId, position, onPositionChange }) => {
const draggingRef = useRef<boolean>(false);
const nodeRef = useRef<HTMLImageElement | HTMLVideoElement | null>(null);
const [x, y] = position;
const style = useSpring({
to: {
left: `${x * 100}%`,
top: `${y * 100}%`,
},
immediate: reduceMotion || draggingRef.current,
});
const media = useAppSelector((state) => const media = useAppSelector((state) =>
( (
(state.compose as ImmutableMap<string, unknown>).get( (state.compose as ImmutableMap<string, unknown>).get(
@ -117,9 +128,6 @@ const Preview: React.FC<{
); );
const [dragging, setDragging] = useState(false); const [dragging, setDragging] = useState(false);
const [x, y] = position;
const nodeRef = useRef<HTMLImageElement | HTMLVideoElement | null>(null);
const draggingRef = useRef<boolean>(false);
const setRef = useCallback( const setRef = useCallback(
(e: HTMLImageElement | HTMLVideoElement | null) => { (e: HTMLImageElement | HTMLVideoElement | null) => {
@ -134,36 +142,30 @@ const Preview: React.FC<{
return; 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); const { x, y } = getPointerPosition(nodeRef.current, e.nativeEvent);
setDragging(true); setDragging(true);
draggingRef.current = true;
onPositionChange([x, y]); onPositionChange([x, y]);
document.addEventListener('mouseup', handleMouseUp);
document.addEventListener('mousemove', handleMouseMove);
}, },
[setDragging, onPositionChange], [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]);
}
};
document.addEventListener('mouseup', handleMouseUp);
document.addEventListener('mousemove', handleMouseMove);
return () => {
document.removeEventListener('mouseup', handleMouseUp);
document.removeEventListener('mousemove', handleMouseMove);
};
}, [setDragging, onPositionChange]);
if (!media) { if (!media) {
return null; return null;
} }
@ -179,10 +181,7 @@ const Preview: React.FC<{
role='presentation' role='presentation'
onMouseDown={handleMouseDown} onMouseDown={handleMouseDown}
/> />
<div <animated.div className='focal-point__reticle' style={style} />
className='focal-point__reticle'
style={{ top: `${y * 100}%`, left: `${x * 100}%` }}
/>
</div> </div>
); );
} else if (media.get('type') === 'gifv') { } else if (media.get('type') === 'gifv') {
@ -194,10 +193,7 @@ const Preview: React.FC<{
alt='' alt=''
onMouseDown={handleMouseDown} onMouseDown={handleMouseDown}
/> />
<div <animated.div className='focal-point__reticle' style={style} />
className='focal-point__reticle'
style={{ top: `${y * 100}%`, left: `${x * 100}%` }}
/>
</div> </div>
); );
} else if (media.get('type') === 'video') { } else if (media.get('type') === 'video') {