Photo, Video: Leverage the new useShowTransition

This commit is contained in:
Alexander Zinchuk 2024-09-06 15:43:14 +02:00
parent 63f1ca396d
commit 9d788a2fd2
5 changed files with 51 additions and 46 deletions

View File

@ -8,10 +8,7 @@ import type { IMediaDimensions } from './helpers/calculateAlbumLayout';
import { CUSTOM_APPENDIX_ATTRIBUTE, MESSAGE_CONTENT_SELECTOR } from '../../../config'; import { CUSTOM_APPENDIX_ATTRIBUTE, MESSAGE_CONTENT_SELECTOR } from '../../../config';
import { requestMutation } from '../../../lib/fasterdom/fasterdom'; import { requestMutation } from '../../../lib/fasterdom/fasterdom';
import { import {
getMediaFormat, getMediaFormat, getMediaThumbUri, getMediaTransferState, getPhotoMediaHash,
getMediaThumbUri,
getMediaTransferState,
getPhotoMediaHash,
} from '../../../global/helpers'; } from '../../../global/helpers';
import buildClassName from '../../../util/buildClassName'; import buildClassName from '../../../util/buildClassName';
import getCustomAppendixBg from './helpers/getCustomAppendixBg'; import getCustomAppendixBg from './helpers/getCustomAppendixBg';
@ -22,10 +19,10 @@ import useFlag from '../../../hooks/useFlag';
import { useIsIntersecting } from '../../../hooks/useIntersectionObserver'; import { useIsIntersecting } from '../../../hooks/useIntersectionObserver';
import useLastCallback from '../../../hooks/useLastCallback'; import useLastCallback from '../../../hooks/useLastCallback';
import useLayoutEffectWithPrevDeps from '../../../hooks/useLayoutEffectWithPrevDeps'; import useLayoutEffectWithPrevDeps from '../../../hooks/useLayoutEffectWithPrevDeps';
import useMediaTransitionDeprecated from '../../../hooks/useMediaTransitionDeprecated'; import useMediaTransition from '../../../hooks/useMediaTransition';
import useMediaWithLoadProgress from '../../../hooks/useMediaWithLoadProgress'; import useMediaWithLoadProgress from '../../../hooks/useMediaWithLoadProgress';
import usePreviousDeprecated from '../../../hooks/usePreviousDeprecated'; import usePreviousDeprecated from '../../../hooks/usePreviousDeprecated';
import useShowTransitionDeprecated from '../../../hooks/useShowTransitionDeprecated'; import useShowTransition from '../../../hooks/useShowTransition';
import useBlurredMediaThumbRef from './hooks/useBlurredMediaThumbRef'; import useBlurredMediaThumbRef from './hooks/useBlurredMediaThumbRef';
import MediaSpoiler from '../../common/MediaSpoiler'; import MediaSpoiler from '../../common/MediaSpoiler';
@ -105,8 +102,8 @@ const Photo = <T,>({
const [withThumb] = useState(!fullMediaData); const [withThumb] = useState(!fullMediaData);
const noThumb = Boolean(fullMediaData); const noThumb = Boolean(fullMediaData);
const thumbRef = useBlurredMediaThumbRef(photo, noThumb); const thumbRef = useBlurredMediaThumbRef(photo, noThumb);
useMediaTransition(!noThumb, { ref: thumbRef });
const blurredBackgroundRef = useBlurredMediaThumbRef(photo, !withBlurredBackground); const blurredBackgroundRef = useBlurredMediaThumbRef(photo, !withBlurredBackground);
const thumbClassNames = useMediaTransitionDeprecated(!noThumb);
const thumbDataUri = getMediaThumbUri(photo); const thumbDataUri = getMediaThumbUri(photo);
const [isSpoilerShown, showSpoiler, hideSpoiler] = useFlag(isPaidPreview || photo.isSpoiler); const [isSpoilerShown, showSpoiler, hideSpoiler] = useFlag(isPaidPreview || photo.isSpoiler);
@ -137,13 +134,21 @@ const Photo = <T,>({
const wasLoadDisabled = usePreviousDeprecated(isLoadAllowed) === false; const wasLoadDisabled = usePreviousDeprecated(isLoadAllowed) === false;
const { const {
ref: spinnerRef,
shouldRender: shouldRenderSpinner, shouldRender: shouldRenderSpinner,
transitionClassNames: spinnerClassNames, } = useShowTransition({
} = useShowTransitionDeprecated(isTransferring, undefined, wasLoadDisabled, 'slow'); isOpen: isTransferring,
noMountTransition: wasLoadDisabled,
className: 'slow',
withShouldRender: true,
});
const { const {
ref: downloadButtonRef,
shouldRender: shouldRenderDownloadButton, shouldRender: shouldRenderDownloadButton,
transitionClassNames: downloadButtonClassNames, } = useShowTransition({
} = useShowTransitionDeprecated(!fullMediaData && !isLoadAllowed); isOpen: !fullMediaData && !isLoadAllowed,
withShouldRender: true,
});
const handleClick = useLastCallback((e: React.MouseEvent<HTMLElement>) => { const handleClick = useLastCallback((e: React.MouseEvent<HTMLElement>) => {
if (isUploading) { if (isUploading) {
@ -228,19 +233,15 @@ const Photo = <T,>({
/> />
)} )}
{withThumb && ( {withThumb && (
<canvas <canvas ref={thumbRef} className="thumbnail" />
ref={thumbRef}
className={buildClassName('thumbnail', thumbClassNames)}
/>
)} )}
{isProtected && <span className="protector" />} {isProtected && <span className="protector" />}
{shouldRenderSpinner && !shouldRenderDownloadButton && ( {shouldRenderSpinner && !shouldRenderDownloadButton && (
<div className={`media-loading ${spinnerClassNames}`}> <div ref={spinnerRef} className="media-loading">
<ProgressSpinner progress={transferProgress} onClick={isUploading ? handleClick : undefined} /> <ProgressSpinner progress={transferProgress} onClick={isUploading ? handleClick : undefined} />
</div> </div>
)} )}
{shouldRenderDownloadButton {shouldRenderDownloadButton && <i ref={downloadButtonRef} className="icon icon-download" />}
&& <i className={buildClassName('icon', 'icon-download', downloadButtonClassNames)} />}
<MediaSpoiler <MediaSpoiler
isVisible={isSpoilerShown} isVisible={isSpoilerShown}
withAnimation withAnimation

View File

@ -6,10 +6,7 @@ import type { ObserveFn } from '../../../hooks/useIntersectionObserver';
import type { IMediaDimensions } from './helpers/calculateAlbumLayout'; import type { IMediaDimensions } from './helpers/calculateAlbumLayout';
import { import {
getMediaFormat, getMediaFormat, getMediaThumbUri, getMediaTransferState, getVideoMediaHash,
getMediaThumbUri,
getMediaTransferState,
getVideoMediaHash,
} from '../../../global/helpers'; } from '../../../global/helpers';
import buildClassName from '../../../util/buildClassName'; import buildClassName from '../../../util/buildClassName';
import { formatMediaDuration } from '../../../util/dates/dateFormat'; import { formatMediaDuration } from '../../../util/dates/dateFormat';
@ -23,10 +20,10 @@ import useFlag from '../../../hooks/useFlag';
import { useIsIntersecting } from '../../../hooks/useIntersectionObserver'; import { useIsIntersecting } from '../../../hooks/useIntersectionObserver';
import useLastCallback from '../../../hooks/useLastCallback'; import useLastCallback from '../../../hooks/useLastCallback';
import useMedia from '../../../hooks/useMedia'; import useMedia from '../../../hooks/useMedia';
import useMediaTransitionDeprecated from '../../../hooks/useMediaTransitionDeprecated'; import useMediaTransition from '../../../hooks/useMediaTransition';
import useMediaWithLoadProgress from '../../../hooks/useMediaWithLoadProgress'; import useMediaWithLoadProgress from '../../../hooks/useMediaWithLoadProgress';
import usePreviousDeprecated from '../../../hooks/usePreviousDeprecated'; import usePreviousDeprecated from '../../../hooks/usePreviousDeprecated';
import useShowTransitionDeprecated from '../../../hooks/useShowTransitionDeprecated'; import useShowTransition from '../../../hooks/useShowTransition';
import useBlurredMediaThumbRef from './hooks/useBlurredMediaThumbRef'; import useBlurredMediaThumbRef from './hooks/useBlurredMediaThumbRef';
import MediaSpoiler from '../../common/MediaSpoiler'; import MediaSpoiler from '../../common/MediaSpoiler';
@ -130,12 +127,12 @@ const Video = <T,>({
const [isPreviewPreloaded] = useState(Boolean(previewMediaHash && mediaLoader.getFromMemory(previewMediaHash))); const [isPreviewPreloaded] = useState(Boolean(previewMediaHash && mediaLoader.getFromMemory(previewMediaHash)));
const canLoadPreview = isIntersectingForLoading; const canLoadPreview = isIntersectingForLoading;
const previewBlobUrl = useMedia(previewMediaHash, !canLoadPreview); const previewBlobUrl = useMedia(previewMediaHash, !canLoadPreview);
const previewClassNames = useMediaTransitionDeprecated((hasThumb || previewBlobUrl) && !isPlayerReady); const previewRef = useMediaTransition<HTMLImageElement>((hasThumb || previewBlobUrl) && !isPlayerReady);
const noThumb = Boolean(!hasThumb || previewBlobUrl || isPlayerReady); const noThumb = Boolean(!hasThumb || previewBlobUrl || isPlayerReady);
const thumbRef = useBlurredMediaThumbRef(video, noThumb); const thumbRef = useBlurredMediaThumbRef(video, noThumb);
useMediaTransition(!noThumb, { ref: thumbRef });
const blurredBackgroundRef = useBlurredMediaThumbRef(video, !withBlurredBackground); const blurredBackgroundRef = useBlurredMediaThumbRef(video, !withBlurredBackground);
const thumbClassNames = useMediaTransitionDeprecated(!noThumb);
const isInline = fullMediaData && wasIntersectedRef.current; const isInline = fullMediaData && wasIntersectedRef.current;
@ -154,12 +151,18 @@ const Video = <T,>({
const wasLoadDisabled = usePreviousDeprecated(isLoadAllowed) === false; const wasLoadDisabled = usePreviousDeprecated(isLoadAllowed) === false;
const { const {
ref: spinnerRef,
shouldRender: shouldRenderSpinner, shouldRender: shouldRenderSpinner,
transitionClassNames: spinnerClassNames, } = useShowTransition({
} = useShowTransitionDeprecated(isTransferring && !isUnsupported, undefined, wasLoadDisabled); isOpen: isTransferring && !isUnsupported,
noMountTransition: wasLoadDisabled,
withShouldRender: true,
});
const { const {
transitionClassNames: playButtonClassNames, ref: playButtonRef,
} = useShowTransitionDeprecated(Boolean((isLoadAllowed || fullMediaData) && !isPlayAllowed && !shouldRenderSpinner)); } = useShowTransition({
isOpen: Boolean((isLoadAllowed || fullMediaData) && !isPlayAllowed && !shouldRenderSpinner),
});
const [playProgress, setPlayProgress] = useState<number>(0); const [playProgress, setPlayProgress] = useState<number>(0);
const handleTimeUpdate = useLastCallback((e: React.SyntheticEvent<HTMLVideoElement>) => { const handleTimeUpdate = useLastCallback((e: React.SyntheticEvent<HTMLVideoElement>) => {
@ -253,20 +256,18 @@ const Video = <T,>({
/> />
)} )}
<img <img
ref={previewRef}
src={previewBlobUrl} src={previewBlobUrl}
className={buildClassName('thumbnail', previewClassNames, withBlurredBackground && 'with-blurred-bg')} className={buildClassName('thumbnail', withBlurredBackground && 'with-blurred-bg')}
alt="" alt=""
style={forcedWidth ? `width: ${forcedWidth}px;` : undefined} style={forcedWidth ? `width: ${forcedWidth}px;` : undefined}
draggable={!isProtected} draggable={!isProtected}
/> />
{hasThumb && !isPreviewPreloaded && ( {hasThumb && !isPreviewPreloaded && (
<canvas <canvas ref={thumbRef} className="thumbnail" />
ref={thumbRef}
className={buildClassName('thumbnail', thumbClassNames)}
/>
)} )}
{isProtected && <span className="protector" />} {isProtected && <span className="protector" />}
<i className={buildClassName('icon', 'icon-large-play', playButtonClassNames)} /> <i ref={playButtonRef} className="icon icon-large-play" />
<MediaSpoiler <MediaSpoiler
isVisible={isSpoilerShown} isVisible={isSpoilerShown}
withAnimation withAnimation
@ -276,7 +277,7 @@ const Video = <T,>({
className="media-spoiler" className="media-spoiler"
/> />
{shouldRenderSpinner && ( {shouldRenderSpinner && (
<div className={buildClassName('media-loading', spinnerClassNames)}> <div ref={spinnerRef} className="media-loading">
<ProgressSpinner <ProgressSpinner
progress={transferProgress} progress={transferProgress}
onClick={handleClickOnSpinner} onClick={handleClickOnSpinner}

View File

@ -9,7 +9,6 @@ import { disableDirectTextInput, enableDirectTextInput } from '../../util/direct
import freezeWhenClosed from '../../util/hoc/freezeWhenClosed'; import freezeWhenClosed from '../../util/hoc/freezeWhenClosed';
import trapFocus from '../../util/trapFocus'; import trapFocus from '../../util/trapFocus';
import useDerivedState from '../../hooks/useDerivedState';
import { dispatchHeavyAnimationEvent } from '../../hooks/useHeavyAnimationCheck'; import { dispatchHeavyAnimationEvent } from '../../hooks/useHeavyAnimationCheck';
import useHistoryBack from '../../hooks/useHistoryBack'; import useHistoryBack from '../../hooks/useHistoryBack';
import useLastCallback from '../../hooks/useLastCallback'; import useLastCallback from '../../hooks/useLastCallback';
@ -66,12 +65,12 @@ const Modal: FC<OwnProps> = ({
}) => { }) => {
const { const {
ref: modalRef, ref: modalRef,
getShouldRender, shouldRender,
} = useShowTransition({ } = useShowTransition({
isOpen, isOpen,
onCloseAnimationEnd, onCloseAnimationEnd,
withShouldRender: true,
}); });
const shouldRender = useDerivedState(getShouldRender);
const withCloseButton = hasCloseButton || hasAbsoluteCloseButton; const withCloseButton = hasCloseButton || hasAbsoluteCloseButton;

View File

@ -1,7 +1,6 @@
import type { FC } from '../../lib/teact/teact'; import type { FC } from '../../lib/teact/teact';
import React, { useRef } from '../../lib/teact/teact'; import React, { useRef } from '../../lib/teact/teact';
import useDerivedState from '../../hooks/useDerivedState';
import usePreviousDeprecated from '../../hooks/usePreviousDeprecated'; import usePreviousDeprecated from '../../hooks/usePreviousDeprecated';
import useShowTransition from '../../hooks/useShowTransition'; import useShowTransition from '../../hooks/useShowTransition';
@ -37,14 +36,14 @@ const ShowTransition: FC<OwnProps> = ({
const fromChildrenRef = useRef<React.ReactNode>(); const fromChildrenRef = useRef<React.ReactNode>();
const isFirstRender = prevIsOpen === undefined; const isFirstRender = prevIsOpen === undefined;
const { ref, getShouldRender } = useShowTransition({ const { ref, shouldRender } = useShowTransition({
isOpen: isOpen && !isHidden, isOpen: isOpen && !isHidden,
ref: externalRef, ref: externalRef,
noMountTransition: isFirstRender && !shouldAnimateFirstRender, noMountTransition: isFirstRender && !shouldAnimateFirstRender,
className: isCustom ? false : undefined, className: isCustom ? false : undefined,
noCloseTransition, noCloseTransition,
withShouldRender: true,
}); });
const shouldRender = useDerivedState(getShouldRender);
if (prevIsOpen && !isOpen) { if (prevIsOpen && !isOpen) {
fromChildrenRef.current = prevChildren; fromChildrenRef.current = prevChildren;

View File

@ -3,7 +3,7 @@ import { useLayoutEffect, useRef, useSignal } from '../lib/teact/teact';
import { addExtraClass, toggleExtraClass } from '../lib/teact/teact-dom'; import { addExtraClass, toggleExtraClass } from '../lib/teact/teact-dom';
import { requestMeasure } from '../lib/fasterdom/fasterdom'; import { requestMeasure } from '../lib/fasterdom/fasterdom';
import useDerivedSignal from './useDerivedSignal'; import useDerivedState from './useDerivedState';
import useLastCallback from './useLastCallback'; import useLastCallback from './useLastCallback';
import { useStateRef } from './useStateRef'; import { useStateRef } from './useStateRef';
import useSyncEffectWithPrevDeps from './useSyncEffectWithPrevDeps'; import useSyncEffectWithPrevDeps from './useSyncEffectWithPrevDeps';
@ -26,6 +26,7 @@ export default function useShowTransition<RefType extends HTMLElement = HTMLDivE
className = 'fast', className = 'fast',
prefix = '', prefix = '',
onCloseAnimationEnd, onCloseAnimationEnd,
withShouldRender,
}: { }: {
isOpen: boolean | undefined; isOpen: boolean | undefined;
ref?: RefObject<RefType>; ref?: RefObject<RefType>;
@ -35,6 +36,7 @@ export default function useShowTransition<RefType extends HTMLElement = HTMLDivE
closeDuration?: number; closeDuration?: number;
className?: string | false; className?: string | false;
prefix?: string; prefix?: string;
withShouldRender?: boolean;
onCloseAnimationEnd?: NoneToVoidFunction; onCloseAnimationEnd?: NoneToVoidFunction;
}) { }) {
// eslint-disable-next-line no-null/no-null // eslint-disable-next-line no-null/no-null
@ -97,7 +99,10 @@ export default function useShowTransition<RefType extends HTMLElement = HTMLDivE
toggleExtraClass(element, `${prefix}closing`, isClosing); toggleExtraClass(element, `${prefix}closing`, isClosing);
}, [className, getState, prefix, ref]); }, [className, getState, prefix, ref]);
const getShouldRender = useDerivedSignal(() => getState() !== 'closed', [getState]); const shouldRender = useDerivedState(
() => (withShouldRender && getState() !== 'closed'),
[withShouldRender, getState],
);
return { ref, getShouldRender }; return { ref, shouldRender };
} }