Message List: Optimizations (#4595)
This commit is contained in:
parent
1978419e84
commit
c04014b7a1
@ -16,6 +16,7 @@ import { IS_ELECTRON } from '../../util/windowEnvironment';
|
|||||||
|
|
||||||
import useColorFilter from '../../hooks/stickers/useColorFilter';
|
import useColorFilter from '../../hooks/stickers/useColorFilter';
|
||||||
import useEffectWithPrevDeps from '../../hooks/useEffectWithPrevDeps';
|
import useEffectWithPrevDeps from '../../hooks/useEffectWithPrevDeps';
|
||||||
|
import useFlag from '../../hooks/useFlag';
|
||||||
import useHeavyAnimationCheck, { isHeavyAnimating } from '../../hooks/useHeavyAnimationCheck';
|
import useHeavyAnimationCheck, { isHeavyAnimating } from '../../hooks/useHeavyAnimationCheck';
|
||||||
import useLastCallback from '../../hooks/useLastCallback';
|
import useLastCallback from '../../hooks/useLastCallback';
|
||||||
import usePriorityPlaybackCheck, { isPriorityPlaybackActive } from '../../hooks/usePriorityPlaybackCheck';
|
import usePriorityPlaybackCheck, { isPriorityPlaybackActive } from '../../hooks/usePriorityPlaybackCheck';
|
||||||
@ -96,6 +97,16 @@ const AnimatedSticker: FC<OwnProps> = ({
|
|||||||
|
|
||||||
const rgbColor = useRef<[number, number, number] | undefined>();
|
const rgbColor = useRef<[number, number, number] | undefined>();
|
||||||
|
|
||||||
|
const shouldForceOnHeavyAnimation = forceAlways || forceOnHeavyAnimation;
|
||||||
|
// Delay initialization until heavy animation ends
|
||||||
|
const [
|
||||||
|
canInitialize, markCanInitialize, unmarkCanInitialize,
|
||||||
|
] = useFlag(!isHeavyAnimating() || shouldForceOnHeavyAnimation);
|
||||||
|
useHeavyAnimationCheck(unmarkCanInitialize, markCanInitialize, shouldForceOnHeavyAnimation);
|
||||||
|
useEffect(() => {
|
||||||
|
if (shouldForceOnHeavyAnimation) markCanInitialize();
|
||||||
|
}, [shouldForceOnHeavyAnimation]);
|
||||||
|
|
||||||
useSyncEffect(() => {
|
useSyncEffect(() => {
|
||||||
if (color && !shouldUseColorFilter) {
|
if (color && !shouldUseColorFilter) {
|
||||||
const { r, g, b } = hexToRgb(color);
|
const { r, g, b } = hexToRgb(color);
|
||||||
@ -118,6 +129,7 @@ const AnimatedSticker: FC<OwnProps> = ({
|
|||||||
|| isUnmountedRef.current
|
|| isUnmountedRef.current
|
||||||
|| !tgsUrl
|
|| !tgsUrl
|
||||||
|| (sharedCanvas && (!sharedCanvasCoords || !sharedCanvas.offsetWidth || !sharedCanvas.offsetHeight))
|
|| (sharedCanvas && (!sharedCanvasCoords || !sharedCanvas.offsetWidth || !sharedCanvas.offsetHeight))
|
||||||
|
|| (isHeavyAnimating() && !shouldForceOnHeavyAnimation)
|
||||||
) {
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -154,12 +166,13 @@ const AnimatedSticker: FC<OwnProps> = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (!canInitialize) return;
|
||||||
if (getRLottie()) {
|
if (getRLottie()) {
|
||||||
init();
|
init();
|
||||||
} else {
|
} else {
|
||||||
ensureRLottie().then(init);
|
ensureRLottie().then(init);
|
||||||
}
|
}
|
||||||
}, [init, tgsUrl, sharedCanvas, sharedCanvasCoords]);
|
}, [init, tgsUrl, sharedCanvas, sharedCanvasCoords, canInitialize]);
|
||||||
|
|
||||||
const throttledInit = useThrottledCallback(init, [init], THROTTLE_MS);
|
const throttledInit = useThrottledCallback(init, [init], THROTTLE_MS);
|
||||||
useSharedIntersectionObserver(sharedCanvas, throttledInit);
|
useSharedIntersectionObserver(sharedCanvas, throttledInit);
|
||||||
@ -239,7 +252,7 @@ const AnimatedSticker: FC<OwnProps> = ({
|
|||||||
}
|
}
|
||||||
}, [playAnimation, animation, tgsUrl]);
|
}, [playAnimation, animation, tgsUrl]);
|
||||||
|
|
||||||
useHeavyAnimationCheck(pauseAnimation, playAnimation, !playKey || forceAlways || forceOnHeavyAnimation);
|
useHeavyAnimationCheck(pauseAnimation, playAnimation, !playKey || shouldForceOnHeavyAnimation);
|
||||||
usePriorityPlaybackCheck(pauseAnimation, playAnimation, !playKey || forceAlways);
|
usePriorityPlaybackCheck(pauseAnimation, playAnimation, !playKey || forceAlways);
|
||||||
// Pausing frame may not happen in background,
|
// Pausing frame may not happen in background,
|
||||||
// so we need to make sure it happens right after focusing,
|
// so we need to make sure it happens right after focusing,
|
||||||
|
|||||||
@ -120,7 +120,7 @@ const File: FC<OwnProps> = ({
|
|||||||
{withThumb && (
|
{withThumb && (
|
||||||
<canvas
|
<canvas
|
||||||
ref={thumbRef}
|
ref={thumbRef}
|
||||||
className={buildClassName('thumbnail', thumbClassNames)}
|
className={buildClassName('thumbnail', 'canvas-blur-setup', thumbClassNames)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -154,7 +154,7 @@ const GifButton: FC<OwnProps> = ({
|
|||||||
{withThumb && (
|
{withThumb && (
|
||||||
<canvas
|
<canvas
|
||||||
ref={thumbRef}
|
ref={thumbRef}
|
||||||
className="thumbnail"
|
className="thumbnail canvas-blur-setup"
|
||||||
// We need to always render to avoid blur re-calculation
|
// We need to always render to avoid blur re-calculation
|
||||||
style={isVideoReady ? 'display: none;' : undefined}
|
style={isVideoReady ? 'display: none;' : undefined}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -57,7 +57,12 @@ const MediaSpoiler: FC<OwnProps> = ({
|
|||||||
ref={ref}
|
ref={ref}
|
||||||
onClick={withAnimation ? handleClick : undefined}
|
onClick={withAnimation ? handleClick : undefined}
|
||||||
>
|
>
|
||||||
<canvas ref={canvasRef} className={styles.canvas} width={width} height={height} />
|
<canvas
|
||||||
|
ref={canvasRef}
|
||||||
|
className={buildClassName(styles.canvas, 'canvas-blur-setup')}
|
||||||
|
width={width}
|
||||||
|
height={height}
|
||||||
|
/>
|
||||||
<div className={styles.dots} />
|
<div className={styles.dots} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -119,7 +119,7 @@ const ProfilePhoto: FC<OwnProps> = ({
|
|||||||
content = (
|
content = (
|
||||||
<>
|
<>
|
||||||
{isBlurredThumb ? (
|
{isBlurredThumb ? (
|
||||||
<canvas ref={blurredThumbCanvasRef} className="thumb" />
|
<canvas ref={blurredThumbCanvasRef} className="thumb canvas-blur-setup" />
|
||||||
) : (
|
) : (
|
||||||
<img src={avatarBlobUrl} draggable={false} className="thumb" alt="" />
|
<img src={avatarBlobUrl} draggable={false} className="thumb" alt="" />
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -9,7 +9,7 @@ import { getStickerPreviewHash } from '../../global/helpers';
|
|||||||
import { selectIsAlwaysHighPriorityEmoji } from '../../global/selectors';
|
import { selectIsAlwaysHighPriorityEmoji } from '../../global/selectors';
|
||||||
import buildClassName from '../../util/buildClassName';
|
import buildClassName from '../../util/buildClassName';
|
||||||
import * as mediaLoader from '../../util/mediaLoader';
|
import * as mediaLoader from '../../util/mediaLoader';
|
||||||
import { IS_ANDROID, IS_WEBM_SUPPORTED } from '../../util/windowEnvironment';
|
import { IS_WEBM_SUPPORTED } from '../../util/windowEnvironment';
|
||||||
|
|
||||||
import useColorFilter from '../../hooks/stickers/useColorFilter';
|
import useColorFilter from '../../hooks/stickers/useColorFilter';
|
||||||
import useCoordsInSharedCanvas from '../../hooks/useCoordsInSharedCanvas';
|
import useCoordsInSharedCanvas from '../../hooks/useCoordsInSharedCanvas';
|
||||||
@ -115,8 +115,8 @@ const StickerView: FC<OwnProps> = ({
|
|||||||
const fullMediaData = useMedia(fullMediaHash, !shouldLoad || shouldSkipFullMedia);
|
const fullMediaData = useMedia(fullMediaHash, !shouldLoad || shouldSkipFullMedia);
|
||||||
// If Lottie data is loaded we will only render thumb if it's good enough (from preview)
|
// If Lottie data is loaded we will only render thumb if it's good enough (from preview)
|
||||||
const [isPlayerReady, markPlayerReady] = useFlag(Boolean(isLottie && fullMediaData && !previewMediaData));
|
const [isPlayerReady, markPlayerReady] = useFlag(Boolean(isLottie && fullMediaData && !previewMediaData));
|
||||||
// Delay mounting on Android until heavy animation ends
|
// Delay mounting until heavy animation ends
|
||||||
const [isReadyToMount, markReadyToMount, unmarkReadyToMount] = useFlag(!IS_ANDROID || !isHeavyAnimating());
|
const [isReadyToMount, markReadyToMount, unmarkReadyToMount] = useFlag(!isHeavyAnimating());
|
||||||
useHeavyAnimationCheck(unmarkReadyToMount, markReadyToMount, isReadyToMount);
|
useHeavyAnimationCheck(unmarkReadyToMount, markReadyToMount, isReadyToMount);
|
||||||
const isFullMediaReady = isReadyToMount && fullMediaData && (isStatic || isPlayerReady);
|
const isFullMediaReady = isReadyToMount && fullMediaData && (isStatic || isPlayerReady);
|
||||||
|
|
||||||
|
|||||||
@ -9,6 +9,9 @@ import { preloadImage } from '../../../util/files';
|
|||||||
import { REM } from '../helpers/mediaDimensions';
|
import { REM } from '../helpers/mediaDimensions';
|
||||||
|
|
||||||
import useDynamicColorListener from '../../../hooks/stickers/useDynamicColorListener';
|
import useDynamicColorListener from '../../../hooks/stickers/useDynamicColorListener';
|
||||||
|
import useEffectWithPrevDeps from '../../../hooks/useEffectWithPrevDeps';
|
||||||
|
import useFlag from '../../../hooks/useFlag';
|
||||||
|
import useHeavyAnimationCheck, { isHeavyAnimating } from '../../../hooks/useHeavyAnimationCheck';
|
||||||
import useLang from '../../../hooks/useLang';
|
import useLang from '../../../hooks/useLang';
|
||||||
import useLastCallback from '../../../hooks/useLastCallback';
|
import useLastCallback from '../../../hooks/useLastCallback';
|
||||||
import useMedia from '../../../hooks/useMedia';
|
import useMedia from '../../../hooks/useMedia';
|
||||||
@ -76,6 +79,10 @@ const EmojiIconBackground = ({
|
|||||||
|
|
||||||
const lang = useLang();
|
const lang = useLang();
|
||||||
|
|
||||||
|
// Delay mounting until heavy animation ends
|
||||||
|
const [canUpdate, markCanUpdate, unmarkCanUpdate] = useFlag(!isHeavyAnimating());
|
||||||
|
useHeavyAnimationCheck(unmarkCanUpdate, markCanUpdate);
|
||||||
|
|
||||||
const { customEmoji } = useCustomEmoji(emojiDocumentId);
|
const { customEmoji } = useCustomEmoji(emojiDocumentId);
|
||||||
const previewMediaHash = customEmoji ? getStickerPreviewHash(customEmoji.id) : undefined;
|
const previewMediaHash = customEmoji ? getStickerPreviewHash(customEmoji.id) : undefined;
|
||||||
const previewUrl = useMedia(previewMediaHash);
|
const previewUrl = useMedia(previewMediaHash);
|
||||||
@ -83,10 +90,10 @@ const EmojiIconBackground = ({
|
|||||||
const customColor = useDynamicColorListener(containerRef);
|
const customColor = useDynamicColorListener(containerRef);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!previewUrl) return;
|
if (!previewUrl || !canUpdate) return;
|
||||||
|
|
||||||
preloadImage(previewUrl).then(setEmojiImage);
|
preloadImage(previewUrl).then(setEmojiImage);
|
||||||
}, [previewUrl]);
|
}, [previewUrl, canUpdate]);
|
||||||
|
|
||||||
const updateCanvas = useLastCallback(() => {
|
const updateCanvas = useLastCallback(() => {
|
||||||
const canvas = canvasRef.current;
|
const canvas = canvasRef.current;
|
||||||
@ -122,13 +129,15 @@ const EmojiIconBackground = ({
|
|||||||
context.restore();
|
context.restore();
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffectWithPrevDeps(([prevEmojiImage, prevLangRtl, prevCustomColor]) => {
|
||||||
|
// No need to trigger update if only `canUpdate` changed
|
||||||
|
if (emojiImage === prevEmojiImage && lang.isRtl === prevLangRtl && customColor === prevCustomColor) return;
|
||||||
updateCanvas();
|
updateCanvas();
|
||||||
}, [emojiImage, lang.isRtl, customColor]);
|
}, [emojiImage, lang.isRtl, customColor, canUpdate]);
|
||||||
|
|
||||||
const updateCanvasSize = useLastCallback((parentWidth: number, parentHeight: number) => {
|
const updateCanvasSize = useLastCallback((parentWidth: number, parentHeight: number) => {
|
||||||
const canvas = canvasRef.current;
|
const canvas = canvasRef.current;
|
||||||
if (!canvas) return;
|
if (!canvas || isHeavyAnimating()) return;
|
||||||
|
|
||||||
canvas.width = parentWidth * dpr;
|
canvas.width = parentWidth * dpr;
|
||||||
canvas.height = parentHeight * dpr;
|
canvas.height = parentHeight * dpr;
|
||||||
@ -147,18 +156,19 @@ const EmojiIconBackground = ({
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
useResizeObserver(containerRef, handleResize);
|
useResizeObserver(containerRef, handleResize, !canUpdate);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffectWithPrevDeps(([prevDpr]) => {
|
||||||
|
if (dpr === prevDpr) return;
|
||||||
const container = containerRef.current;
|
const container = containerRef.current;
|
||||||
if (!container) return;
|
if (!container || !canUpdate) return;
|
||||||
|
|
||||||
const { width, height } = container.getBoundingClientRect();
|
const { width, height } = container.getBoundingClientRect();
|
||||||
|
|
||||||
requestMutation(() => {
|
requestMutation(() => {
|
||||||
updateCanvasSize(width, height);
|
updateCanvasSize(width, height);
|
||||||
});
|
});
|
||||||
}, [dpr]);
|
}, [dpr, canUpdate]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={buildClassName(styles.root, className)} ref={containerRef}>
|
<div className={buildClassName(styles.root, className)} ref={containerRef}>
|
||||||
|
|||||||
@ -36,6 +36,7 @@ type OwnProps = {
|
|||||||
shouldPause?: boolean;
|
shouldPause?: boolean;
|
||||||
shouldLoop?: boolean;
|
shouldLoop?: boolean;
|
||||||
loopLimit?: number;
|
loopLimit?: number;
|
||||||
|
shouldDelayInit?: boolean;
|
||||||
observeIntersection?: ObserveFn;
|
observeIntersection?: ObserveFn;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -66,6 +67,7 @@ const ReactionAnimatedEmoji = ({
|
|||||||
shouldPause,
|
shouldPause,
|
||||||
shouldLoop,
|
shouldLoop,
|
||||||
loopLimit,
|
loopLimit,
|
||||||
|
shouldDelayInit,
|
||||||
observeIntersection,
|
observeIntersection,
|
||||||
}: OwnProps & StateProps) => {
|
}: OwnProps & StateProps) => {
|
||||||
const { stopActiveReaction } = getActions();
|
const { stopActiveReaction } = getActions();
|
||||||
@ -167,7 +169,7 @@ const ReactionAnimatedEmoji = ({
|
|||||||
size={size}
|
size={size}
|
||||||
noPlay={shouldPause}
|
noPlay={shouldPause}
|
||||||
loopLimit={loopLimit}
|
loopLimit={loopLimit}
|
||||||
forceAlways
|
forceAlways={!shouldDelayInit}
|
||||||
observeIntersectionForPlaying={observeIntersection}
|
observeIntersectionForPlaying={observeIntersection}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@ -179,7 +181,7 @@ const ReactionAnimatedEmoji = ({
|
|||||||
tgsUrl={mediaDataCenterIcon}
|
tgsUrl={mediaDataCenterIcon}
|
||||||
play={isIntersecting && !shouldPause}
|
play={isIntersecting && !shouldPause}
|
||||||
noLoop={!shouldLoop}
|
noLoop={!shouldLoop}
|
||||||
forceAlways
|
forceAlways={!shouldDelayInit}
|
||||||
onLoad={markAnimationLoaded}
|
onLoad={markAnimationLoaded}
|
||||||
onEnded={unmarkAnimationLoaded}
|
onEnded={unmarkAnimationLoaded}
|
||||||
/>
|
/>
|
||||||
@ -193,7 +195,7 @@ const ReactionAnimatedEmoji = ({
|
|||||||
tgsUrl={mediaDataEffect}
|
tgsUrl={mediaDataEffect}
|
||||||
play={isIntersecting}
|
play={isIntersecting}
|
||||||
noLoop
|
noLoop
|
||||||
forceAlways
|
forceAlways={!shouldDelayInit}
|
||||||
onEnded={handleEnded}
|
onEnded={handleEnded}
|
||||||
/>
|
/>
|
||||||
{isCustom && !assignedEffectId && isIntersecting && (
|
{isCustom && !assignedEffectId && isIntersecting && (
|
||||||
|
|||||||
@ -83,7 +83,9 @@ function BaseStory({
|
|||||||
className={fullClassName}
|
className={fullClassName}
|
||||||
onClick={isConnected ? handleClick : undefined}
|
onClick={isConnected ? handleClick : undefined}
|
||||||
>
|
>
|
||||||
{!isExpired && isPreview && <canvas ref={blurredBackgroundRef} className="thumbnail blurred-bg" />}
|
{!isExpired && isPreview && (
|
||||||
|
<canvas ref={blurredBackgroundRef} className="thumbnail canvas-blur-setup blurred-bg" />
|
||||||
|
)}
|
||||||
{shouldRender && (
|
{shouldRender && (
|
||||||
<>
|
<>
|
||||||
<img
|
<img
|
||||||
|
|||||||
@ -972,6 +972,7 @@ const Message: FC<OwnProps & StateProps> = ({
|
|||||||
noRecentReactors={isChannel}
|
noRecentReactors={isChannel}
|
||||||
tags={tags}
|
tags={tags}
|
||||||
isCurrentUserPremium={isPremium}
|
isCurrentUserPremium={isPremium}
|
||||||
|
getIsMessageListReady={getIsMessageListReady}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -1475,6 +1476,7 @@ const Message: FC<OwnProps & StateProps> = ({
|
|||||||
observeIntersection={observeIntersectionForPlaying}
|
observeIntersection={observeIntersectionForPlaying}
|
||||||
noRecentReactors={isChannel}
|
noRecentReactors={isChannel}
|
||||||
tags={tags}
|
tags={tags}
|
||||||
|
getIsMessageListReady={getIsMessageListReady}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -199,7 +199,9 @@ const Photo: FC<OwnProps> = ({
|
|||||||
style={style}
|
style={style}
|
||||||
onClick={isUploading ? undefined : handleClick}
|
onClick={isUploading ? undefined : handleClick}
|
||||||
>
|
>
|
||||||
{withBlurredBackground && <canvas ref={blurredBackgroundRef} className="thumbnail blurred-bg" />}
|
{withBlurredBackground && (
|
||||||
|
<canvas ref={blurredBackgroundRef} className="thumbnail canvas-blur-setup blurred-bg" />
|
||||||
|
)}
|
||||||
<img
|
<img
|
||||||
src={fullMediaData}
|
src={fullMediaData}
|
||||||
className={buildClassName('full-media', withBlurredBackground && 'with-blurred-bg')}
|
className={buildClassName('full-media', withBlurredBackground && 'with-blurred-bg')}
|
||||||
@ -208,7 +210,10 @@ const Photo: FC<OwnProps> = ({
|
|||||||
draggable={!isProtected}
|
draggable={!isProtected}
|
||||||
/>
|
/>
|
||||||
{withThumb && (
|
{withThumb && (
|
||||||
<canvas ref={thumbRef} className={buildClassName('thumbnail', thumbClassNames)} />
|
<canvas
|
||||||
|
ref={thumbRef}
|
||||||
|
className={buildClassName('thumbnail', !noThumb && 'canvas-blur-setup', thumbClassNames)}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
{isProtected && <span className="protector" />}
|
{isProtected && <span className="protector" />}
|
||||||
{shouldRenderSpinner && !shouldRenderDownloadButton && (
|
{shouldRenderSpinner && !shouldRenderDownloadButton && (
|
||||||
|
|||||||
@ -260,7 +260,7 @@ const RoundVideo: FC<OwnProps> = ({
|
|||||||
{!shouldRenderSpoiler && (
|
{!shouldRenderSpoiler && (
|
||||||
<canvas
|
<canvas
|
||||||
ref={thumbRef}
|
ref={thumbRef}
|
||||||
className={buildClassName('thumbnail', thumbClassNames)}
|
className={buildClassName('thumbnail', 'canvas-blur-setup', thumbClassNames)}
|
||||||
style={`width: ${ROUND_VIDEO_DIMENSIONS_PX}px; height: ${ROUND_VIDEO_DIMENSIONS_PX}px`}
|
style={`width: ${ROUND_VIDEO_DIMENSIONS_PX}px; height: ${ROUND_VIDEO_DIMENSIONS_PX}px`}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -211,7 +211,9 @@ const Video: FC<OwnProps> = ({
|
|||||||
style={style}
|
style={style}
|
||||||
onClick={isUploading ? undefined : handleClick}
|
onClick={isUploading ? undefined : handleClick}
|
||||||
>
|
>
|
||||||
{withBlurredBackground && <canvas ref={blurredBackgroundRef} className="thumbnail blurred-bg" />}
|
{withBlurredBackground && (
|
||||||
|
<canvas ref={blurredBackgroundRef} className="thumbnail canvas-blur-setup blurred-bg" />
|
||||||
|
)}
|
||||||
{isInline && (
|
{isInline && (
|
||||||
<OptimizedVideo
|
<OptimizedVideo
|
||||||
ref={videoRef}
|
ref={videoRef}
|
||||||
@ -237,7 +239,7 @@ const Video: FC<OwnProps> = ({
|
|||||||
{hasThumb && !isPreviewPreloaded && (
|
{hasThumb && !isPreviewPreloaded && (
|
||||||
<canvas
|
<canvas
|
||||||
ref={thumbRef}
|
ref={thumbRef}
|
||||||
className={buildClassName('thumbnail', thumbClassNames)}
|
className={buildClassName('thumbnail', !noThumb && 'canvas-blur-setup', thumbClassNames)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{isProtected && <span className="protector" />}
|
{isProtected && <span className="protector" />}
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
import type { FC } from '../../../../lib/teact/teact';
|
|
||||||
import React, { memo } from '../../../../lib/teact/teact';
|
import React, { memo } from '../../../../lib/teact/teact';
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
@ -22,25 +21,29 @@ import styles from './ReactionButton.module.scss';
|
|||||||
|
|
||||||
const REACTION_SIZE = 1.25 * REM;
|
const REACTION_SIZE = 1.25 * REM;
|
||||||
|
|
||||||
const ReactionButton: FC<{
|
type OwnProps = {
|
||||||
reaction: ApiReactionCount;
|
reaction: ApiReactionCount;
|
||||||
containerId: string;
|
containerId: string;
|
||||||
isOwnMessage?: boolean;
|
isOwnMessage?: boolean;
|
||||||
recentReactors?: ApiPeer[];
|
recentReactors?: ApiPeer[];
|
||||||
className?: string;
|
className?: string;
|
||||||
chosenClassName?: string;
|
chosenClassName?: string;
|
||||||
|
shouldDelayInit?: boolean;
|
||||||
observeIntersection?: ObserveFn;
|
observeIntersection?: ObserveFn;
|
||||||
onClick?: (reaction: ApiReaction) => void;
|
onClick?: (reaction: ApiReaction) => void;
|
||||||
}> = ({
|
};
|
||||||
|
|
||||||
|
const ReactionButton = ({
|
||||||
reaction,
|
reaction,
|
||||||
containerId,
|
containerId,
|
||||||
isOwnMessage,
|
isOwnMessage,
|
||||||
recentReactors,
|
recentReactors,
|
||||||
className,
|
className,
|
||||||
chosenClassName,
|
chosenClassName,
|
||||||
|
shouldDelayInit,
|
||||||
observeIntersection,
|
observeIntersection,
|
||||||
onClick,
|
onClick,
|
||||||
}) => {
|
}: OwnProps) => {
|
||||||
const handleClick = useLastCallback(() => {
|
const handleClick = useLastCallback(() => {
|
||||||
onClick?.(reaction.reaction);
|
onClick?.(reaction.reaction);
|
||||||
});
|
});
|
||||||
@ -63,6 +66,7 @@ const ReactionButton: FC<{
|
|||||||
reaction={reaction.reaction}
|
reaction={reaction.reaction}
|
||||||
size={REACTION_SIZE}
|
size={REACTION_SIZE}
|
||||||
observeIntersection={observeIntersection}
|
observeIntersection={observeIntersection}
|
||||||
|
shouldDelayInit={shouldDelayInit}
|
||||||
/>
|
/>
|
||||||
{recentReactors?.length ? (
|
{recentReactors?.length ? (
|
||||||
<AvatarList size="mini" peers={recentReactors} />
|
<AvatarList size="mini" peers={recentReactors} />
|
||||||
|
|||||||
@ -10,12 +10,14 @@ import type {
|
|||||||
ApiSavedReactionTag,
|
ApiSavedReactionTag,
|
||||||
} from '../../../../api/types';
|
} from '../../../../api/types';
|
||||||
import type { ObserveFn } from '../../../../hooks/useIntersectionObserver';
|
import type { ObserveFn } from '../../../../hooks/useIntersectionObserver';
|
||||||
|
import type { Signal } from '../../../../util/signals';
|
||||||
|
|
||||||
import { getReactionKey, isReactionChosen } from '../../../../global/helpers';
|
import { getReactionKey, isReactionChosen } from '../../../../global/helpers';
|
||||||
import { selectPeer } from '../../../../global/selectors';
|
import { selectPeer } from '../../../../global/selectors';
|
||||||
import buildClassName from '../../../../util/buildClassName';
|
import buildClassName from '../../../../util/buildClassName';
|
||||||
import { getMessageKey } from '../../../../util/messageKey';
|
import { getMessageKey } from '../../../../util/messageKey';
|
||||||
|
|
||||||
|
import useDerivedState from '../../../../hooks/useDerivedState';
|
||||||
import useLang from '../../../../hooks/useLang';
|
import useLang from '../../../../hooks/useLang';
|
||||||
import useLastCallback from '../../../../hooks/useLastCallback';
|
import useLastCallback from '../../../../hooks/useLastCallback';
|
||||||
|
|
||||||
@ -33,6 +35,7 @@ type OwnProps = {
|
|||||||
isCurrentUserPremium?: boolean;
|
isCurrentUserPremium?: boolean;
|
||||||
observeIntersection?: ObserveFn;
|
observeIntersection?: ObserveFn;
|
||||||
noRecentReactors?: boolean;
|
noRecentReactors?: boolean;
|
||||||
|
getIsMessageListReady: Signal<boolean>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const MAX_RECENT_AVATARS = 3;
|
const MAX_RECENT_AVATARS = 3;
|
||||||
@ -46,6 +49,7 @@ const Reactions: FC<OwnProps> = ({
|
|||||||
noRecentReactors,
|
noRecentReactors,
|
||||||
isCurrentUserPremium,
|
isCurrentUserPremium,
|
||||||
tags,
|
tags,
|
||||||
|
getIsMessageListReady,
|
||||||
}) => {
|
}) => {
|
||||||
const {
|
const {
|
||||||
toggleReaction,
|
toggleReaction,
|
||||||
@ -61,6 +65,8 @@ const Reactions: FC<OwnProps> = ({
|
|||||||
results.reduce((acc, reaction) => acc + reaction.count, 0)
|
results.reduce((acc, reaction) => acc + reaction.count, 0)
|
||||||
), [results]);
|
), [results]);
|
||||||
|
|
||||||
|
const isMessageListReady = useDerivedState(getIsMessageListReady);
|
||||||
|
|
||||||
const recentReactorsByReactionKey = useMemo(() => {
|
const recentReactorsByReactionKey = useMemo(() => {
|
||||||
const global = getGlobal();
|
const global = getGlobal();
|
||||||
|
|
||||||
@ -149,6 +155,7 @@ const Reactions: FC<OwnProps> = ({
|
|||||||
onClick={handleClick}
|
onClick={handleClick}
|
||||||
onRemove={handleRemoveReaction}
|
onRemove={handleRemoveReaction}
|
||||||
observeIntersection={observeIntersection}
|
observeIntersection={observeIntersection}
|
||||||
|
shouldDelayInit={!isMessageListReady}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<ReactionButton
|
<ReactionButton
|
||||||
@ -161,6 +168,7 @@ const Reactions: FC<OwnProps> = ({
|
|||||||
reaction={reaction}
|
reaction={reaction}
|
||||||
onClick={handleClick}
|
onClick={handleClick}
|
||||||
observeIntersection={observeIntersection}
|
observeIntersection={observeIntersection}
|
||||||
|
shouldDelayInit={!isMessageListReady}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
))}
|
))}
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
import type { FC } from '../../../../lib/teact/teact';
|
|
||||||
import React, { memo, useRef } from '../../../../lib/teact/teact';
|
import React, { memo, useRef } from '../../../../lib/teact/teact';
|
||||||
import { getActions } from '../../../../global';
|
import { getActions } from '../../../../global';
|
||||||
|
|
||||||
@ -28,7 +27,7 @@ const REACTION_SIZE = 1.25 * REM;
|
|||||||
const TITLE_MAX_LENGTH = 15;
|
const TITLE_MAX_LENGTH = 15;
|
||||||
const LOOP_LIMIT = 1;
|
const LOOP_LIMIT = 1;
|
||||||
|
|
||||||
const SavedTagButton: FC<{
|
type OwnProps = {
|
||||||
reaction: ApiReaction;
|
reaction: ApiReaction;
|
||||||
tag?: ApiSavedReactionTag;
|
tag?: ApiSavedReactionTag;
|
||||||
containerId: string;
|
containerId: string;
|
||||||
@ -39,10 +38,13 @@ const SavedTagButton: FC<{
|
|||||||
chosenClassName?: string;
|
chosenClassName?: string;
|
||||||
isDisabled?: boolean;
|
isDisabled?: boolean;
|
||||||
withContextMenu?: boolean;
|
withContextMenu?: boolean;
|
||||||
|
shouldDelayInit?: boolean;
|
||||||
observeIntersection?: ObserveFn;
|
observeIntersection?: ObserveFn;
|
||||||
onClick?: (reaction: ApiReaction) => void;
|
onClick?: (reaction: ApiReaction) => void;
|
||||||
onRemove?: (reaction: ApiReaction) => void;
|
onRemove?: (reaction: ApiReaction) => void;
|
||||||
}> = ({
|
};
|
||||||
|
|
||||||
|
const SavedTagButton = ({
|
||||||
reaction,
|
reaction,
|
||||||
tag,
|
tag,
|
||||||
containerId,
|
containerId,
|
||||||
@ -53,10 +55,11 @@ const SavedTagButton: FC<{
|
|||||||
withCount,
|
withCount,
|
||||||
isDisabled,
|
isDisabled,
|
||||||
withContextMenu,
|
withContextMenu,
|
||||||
|
shouldDelayInit,
|
||||||
observeIntersection,
|
observeIntersection,
|
||||||
onClick,
|
onClick,
|
||||||
onRemove,
|
onRemove,
|
||||||
}) => {
|
}: OwnProps) => {
|
||||||
const { editSavedReactionTag } = getActions();
|
const { editSavedReactionTag } = getActions();
|
||||||
// eslint-disable-next-line no-null/no-null
|
// eslint-disable-next-line no-null/no-null
|
||||||
const ref = useRef<HTMLButtonElement>(null);
|
const ref = useRef<HTMLButtonElement>(null);
|
||||||
@ -138,6 +141,7 @@ const SavedTagButton: FC<{
|
|||||||
loopLimit={LOOP_LIMIT}
|
loopLimit={LOOP_LIMIT}
|
||||||
size={REACTION_SIZE}
|
size={REACTION_SIZE}
|
||||||
observeIntersection={observeIntersection}
|
observeIntersection={observeIntersection}
|
||||||
|
shouldDelayInit={shouldDelayInit}
|
||||||
/>
|
/>
|
||||||
{hasText && (
|
{hasText && (
|
||||||
<span className={styles.tagText}>
|
<span className={styles.tagText}>
|
||||||
|
|||||||
@ -52,6 +52,8 @@ export default function useCanvasBlur(
|
|||||||
|
|
||||||
ctx.drawImage(img, -radius * 2, -radius * 2, width + radius * 4, height + radius * 4);
|
ctx.drawImage(img, -radius * 2, -radius * 2, width + radius * 4, height + radius * 4);
|
||||||
|
|
||||||
|
canvas.classList.remove('canvas-blur-setup');
|
||||||
|
|
||||||
if (!IS_CANVAS_FILTER_SUPPORTED) {
|
if (!IS_CANVAS_FILTER_SUPPORTED) {
|
||||||
fastBlur(ctx, 0, 0, width, height, radius, ITERATIONS);
|
fastBlur(ctx, 0, 0, width, height, radius, ITERATIONS);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,9 +8,9 @@
|
|||||||
-webkit-user-select: none;
|
-webkit-user-select: none;
|
||||||
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
|
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
|
||||||
|
|
||||||
&.lovely-chart--state-invisible > * {
|
// &.lovely-chart--state-invisible > * {
|
||||||
display: none;
|
// display: none;
|
||||||
}
|
// }
|
||||||
|
|
||||||
> canvas,
|
> canvas,
|
||||||
.lovely-chart--tooltip canvas {
|
.lovely-chart--tooltip canvas {
|
||||||
|
|||||||
@ -201,6 +201,10 @@ body:not(.is-ios) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.canvas-blur-setup {
|
||||||
|
will-change: width, height;
|
||||||
|
}
|
||||||
|
|
||||||
.emoji-small {
|
.emoji-small {
|
||||||
background: no-repeat;
|
background: no-repeat;
|
||||||
background-size: var(--emoji-size);
|
background-size: var(--emoji-size);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user