[Refactoring] Animated icons (#1897)

This commit is contained in:
Alexander Zinchuk 2022-05-31 20:58:47 +04:00
parent 7997dff459
commit 05ca268f6b
52 changed files with 581 additions and 827 deletions

View File

@ -17,7 +17,6 @@ import * as cacheApi from '../../../util/cacheApi';
import { getEntityTypeById } from '../gramjsBuilders';
const MEDIA_ENTITY_TYPES = new Set(['msg', 'sticker', 'gif', 'wallpaper', 'photo', 'webDocument', 'document']);
const TGS_MIME_TYPE = 'application/x-tgsticker';
export default async function downloadMedia(
{
@ -63,8 +62,8 @@ export default async function downloadMedia(
export type EntityType = (
'msg' | 'sticker' | 'wallpaper' | 'gif' | 'channel' | 'chat' | 'user' | 'photo' | 'stickerSet' | 'webDocument' |
'document'
);
'document' | 'staticMap'
);
async function download(
url: string,
@ -84,12 +83,6 @@ async function download(
entityType, entityId, sizeType, params, mediaMatchType,
} = parsed;
if (entityType === 'file') {
const response = await fetch(entityId);
const data = await response.arrayBuffer();
return { data };
}
if (!isConnected) {
return Promise.reject(new Error('ERROR: Client is not connected'));
}
@ -192,7 +185,7 @@ async function download(
return { mimeType, data, fullSize };
} else if (entityType === 'stickerSet') {
const data = await client.downloadStickerSetThumb(entity);
const mimeType = mediaFormat === ApiMediaFormat.Lottie ? TGS_MIME_TYPE : getMimeType(data);
const mimeType = getMimeType(data);
return { mimeType, data };
} else {
@ -247,15 +240,11 @@ async function parseMedia(
): Promise<ApiParsedMedia | undefined> {
switch (mediaFormat) {
case ApiMediaFormat.BlobUrl:
case ApiMediaFormat.Lottie: {
return new Blob([data], { type: mimeType });
}
case ApiMediaFormat.Text: {
case ApiMediaFormat.Text:
return data.toString();
}
case ApiMediaFormat.Progressive: {
case ApiMediaFormat.Progressive:
return data.buffer;
}
}
return undefined;
@ -307,7 +296,7 @@ export function parseMediaUrl(url: string) {
: url.startsWith('webDocument')
? url.match(/(webDocument):(.+)/)
: url.match(
/(avatar|profile|photo|msg|stickerSet|sticker|wallpaper|gif|file|document)([-\d\w./]+)(?::\d+)?(\?size=\w+)?/,
/(avatar|profile|photo|msg|stickerSet|sticker|wallpaper|gif|document)([-\d\w./]+)(?::\d+)?(\?size=\w+)?/,
);
if (!mediaMatch) {
return undefined;
@ -316,14 +305,6 @@ export function parseMediaUrl(url: string) {
const mediaMatchType = mediaMatch[1];
const entityId: string | number = mediaMatch[2];
if (mediaMatchType === 'file') {
return {
mediaMatchType,
entityType: 'file',
entityId,
};
}
let entityType: EntityType;
const params = mediaMatch[3];
const sizeType = params?.replace('?size=', '') || undefined;
@ -331,9 +312,7 @@ export function parseMediaUrl(url: string) {
if (mediaMatch[1] === 'avatar' || mediaMatch[1] === 'profile') {
entityType = getEntityTypeById(entityId);
} else {
entityType = mediaMatch[1] as (
'msg' | 'sticker' | 'wallpaper' | 'gif' | 'stickerSet' | 'photo' | 'webDocument' | 'document'
);
entityType = mediaMatch[1] as EntityType;
}
return {

View File

@ -3,7 +3,6 @@
export enum ApiMediaFormat {
BlobUrl,
Lottie,
Progressive,
Stream,
Text,

View File

@ -1,7 +1,7 @@
import QrCodeStyling from 'qr-code-styling';
import type { FC } from '../../lib/teact/teact';
import React, {
useEffect, useRef, memo, useCallback, useState,
useEffect, useRef, memo, useCallback,
} from '../../lib/teact/teact';
import { getActions, withGlobal } from '../../global';
@ -9,11 +9,11 @@ import type { GlobalState } from '../../global/types';
import type { LangCode } from '../../types';
import { DEFAULT_LANG_CODE } from '../../config';
import { LOCAL_TGS_URLS } from '../common/helpers/animatedAssets';
import { setLanguage } from '../../util/langProvider';
import buildClassName from '../../util/buildClassName';
import renderText from '../common/helpers/renderText';
import { getSuggestedLanguage } from './helpers/getSuggestedLanguage';
import getAnimationData from '../common/helpers/animatedAssets';
import useLangString from '../../hooks/useLangString';
import useFlag from '../../hooks/useFlag';
@ -22,8 +22,8 @@ import useMediaTransition from '../../hooks/useMediaTransition';
import Loading from '../ui/Loading';
import Button from '../ui/Button';
import AnimatedSticker from '../common/AnimatedSticker';
import blankUrl from '../../assets/blank.png';
import AnimatedIcon from '../common/AnimatedIcon';
type StateProps =
Pick<GlobalState, 'connectionState' | 'authState' | 'authQrCode'>
@ -73,17 +73,7 @@ const AuthCode: FC<StateProps> = ({
const [isLoading, markIsLoading, unmarkIsLoading] = useFlag();
const [isQrMounted, markQrMounted, unmarkQrMounted] = useFlag();
const [animationData, setAnimationData] = useState<string>();
const [isAnimationLoaded, markAnimationLoaded] = useFlag();
const transitionClassNames = useMediaTransition(isQrMounted);
const airplaneTransitionClassNames = useMediaTransition(isAnimationLoaded);
useEffect(() => {
if (!animationData) {
getAnimationData('QrPlane').then(setAnimationData);
}
}, [animationData]);
useEffect(() => {
if (!authQrCode) {
@ -142,17 +132,13 @@ const AuthCode: FC<StateProps> = ({
ref={qrCodeRef}
style={`width: ${QR_SIZE}px; height: ${QR_SIZE}px`}
/>
{animationData && (
<AnimatedSticker
id="qrPlane"
className={buildClassName('qr-plane', airplaneTransitionClassNames)}
size={QR_PLANE_SIZE}
animationData={animationData}
play={isAnimationLoaded}
onLoad={markAnimationLoaded}
key="qrPlane"
/>
)}
<AnimatedIcon
tgsUrl={LOCAL_TGS_URLS.QrPlane}
size={QR_PLANE_SIZE}
className="qr-plane"
nonInteractive
noLoop={false}
/>
</div>
{!isQrMounted && <div className="qr-loading"><Loading /></div>}
</div>

View File

@ -19,6 +19,7 @@ import {
IS_REQUEST_FULLSCREEN_SUPPORTED,
IS_SINGLE_COLUMN_LAYOUT,
} from '../../../util/environment';
import { LOCAL_TGS_URLS } from '../../common/helpers/animatedAssets';
import buildClassName from '../../../util/buildClassName';
import {
selectGroupCall,
@ -338,7 +339,11 @@ const GroupCall: FC<OwnProps & StateProps> = ({
<div className="video-buttons">
{hasVideo && (IS_ANDROID || IS_IOS) && (
<button className="smaller-button" onClick={switchCameraInput}>
<AnimatedIcon name="CameraFlip" playSegment={CAMERA_FLIP_PLAY_SEGMENT} size={24} />
<AnimatedIcon
tgsUrl={LOCAL_TGS_URLS.CameraFlip}
playSegment={CAMERA_FLIP_PLAY_SEGMENT}
size={24}
/>
</button>
)}
<button

View File

@ -7,13 +7,14 @@ import { getActions, withGlobal } from '../../../global';
import type { IAnchorPosition } from '../../../types';
import { GROUP_CALL_DEFAULT_VOLUME, GROUP_CALL_VOLUME_MULTIPLIER } from '../../../config';
import { LOCAL_TGS_URLS } from '../../common/helpers/animatedAssets';
import buildClassName from '../../../util/buildClassName';
import buildStyle from '../../../util/buildStyle';
import useRunThrottled from '../../../hooks/useRunThrottled';
import useFlag from '../../../hooks/useFlag';
import useLang from '../../../hooks/useLang';
import { selectIsAdminInActiveGroupCall } from '../../../global/selectors/calls';
import { GROUP_CALL_DEFAULT_VOLUME, GROUP_CALL_VOLUME_MULTIPLIER } from '../../../config';
import Menu from '../../ui/Menu';
import MenuItem from '../../ui/MenuItem';
@ -171,7 +172,7 @@ const GroupCallParticipantMenu: FC<OwnProps & StateProps> = ({
/>
<div className="info">
<AnimatedIcon
name="Speaker"
tgsUrl={LOCAL_TGS_URLS.Speaker}
playSegment={speakerIconPlaySegment}
size={SPEAKER_ICON_SIZE}
/>

View File

@ -5,6 +5,7 @@ import React, {
} from '../../../lib/teact/teact';
import { getActions, withGlobal } from '../../../global';
import { LOCAL_TGS_URLS } from '../../common/helpers/animatedAssets';
import buildClassName from '../../../util/buildClassName';
import { vibrateShort } from '../../../util/vibrate';
import usePrevious from '../../../hooks/usePrevious';
@ -147,7 +148,7 @@ const MicrophoneButton: FC<StateProps> = ({
onMouseUp={handleMouseUpMute}
>
<AnimatedIcon
name={animatedIconName}
tgsUrl={LOCAL_TGS_URLS[animatedIconName]}
size={ICON_SIZE}
playSegment={playSegment}
/>

View File

@ -2,9 +2,12 @@ import type { GroupCallParticipant } from '../../../lib/secret-sauce';
import { THRESHOLD } from '../../../lib/secret-sauce';
import type { FC } from '../../../lib/teact/teact';
import React, { memo, useMemo } from '../../../lib/teact/teact';
import AnimatedIcon from '../../common/AnimatedIcon';
import { LOCAL_TGS_URLS } from '../../common/helpers/animatedAssets';
import usePrevious from '../../../hooks/usePrevious';
import AnimatedIcon from '../../common/AnimatedIcon';
type OwnProps = {
participant: GroupCallParticipant;
noColor?: boolean;
@ -59,7 +62,7 @@ const OutlinedMicrophoneIcon: FC<OwnProps> = ({
return (
<AnimatedIcon
name="VoiceOutlined"
tgsUrl={LOCAL_TGS_URLS.VoiceOutlined}
playSegment={playSegment}
size={28}
color={microphoneColor}

View File

@ -13,6 +13,7 @@ import {
IS_REQUEST_FULLSCREEN_SUPPORTED,
IS_SINGLE_COLUMN_LAYOUT,
} from '../../../util/environment';
import { LOCAL_TGS_URLS } from '../../common/helpers/animatedAssets';
import buildClassName from '../../../util/buildClassName';
import { selectPhoneCallUser } from '../../../global/selectors/calls';
import useLang from '../../../hooks/useLang';
@ -311,9 +312,13 @@ const PhoneCall: FC<StateProps> = ({
{hasOwnVideo && (IS_ANDROID || IS_IOS) && (
<PhoneCallButton
onClick={handleFlipCamera}
customIcon={
<AnimatedIcon name="CameraFlip" playSegment={!isFlipping ? [0, 1] : [0, 10]} size={32} />
}
customIcon={(
<AnimatedIcon
tgsUrl={LOCAL_TGS_URLS.CameraFlip}
playSegment={!isFlipping ? [0, 1] : [0, 10]}
size={32}
/>
)}
isDisabled={!isActive}
label={lang('VoipFlip')}
/>

View File

@ -1,105 +0,0 @@
import type { FC } from '../../lib/teact/teact';
import React, { memo } from '../../lib/teact/teact';
import type { ApiSticker } from '../../api/types';
import { ApiMediaFormat } from '../../api/types';
import type { ActiveEmojiInteraction } from '../../global/types';
import buildClassName from '../../util/buildClassName';
import type { ObserveFn } from '../../hooks/useIntersectionObserver';
import { useIsIntersecting } from '../../hooks/useIntersectionObserver';
import useMedia from '../../hooks/useMedia';
import useMediaTransition from '../../hooks/useMediaTransition';
import useAnimatedEmoji from './hooks/useAnimatedEmoji';
import { LIKE_STICKER_ID } from './helpers/mediaDimensions';
import AnimatedSticker from './AnimatedSticker';
import './AnimatedEmoji.scss';
type OwnProps = {
sticker?: ApiSticker;
effect?: ApiSticker;
isOwn?: boolean;
soundId?: string;
observeIntersection?: ObserveFn;
size?: 'large' | 'medium' | 'small';
lastSyncTime?: number;
forceLoadPreview?: boolean;
messageId?: number;
chatId?: string;
activeEmojiInteractions?: ActiveEmojiInteraction[];
};
const QUALITY = 1;
const AnimatedEmoji: FC<OwnProps> = ({
sticker,
effect,
isOwn,
soundId,
size = 'medium',
observeIntersection,
lastSyncTime,
forceLoadPreview,
messageId,
chatId,
activeEmojiInteractions,
}) => {
const {
markAnimationLoaded,
isAnimationLoaded,
ref,
width,
style,
handleClick,
playKey,
} = useAnimatedEmoji(size, chatId, messageId, soundId, activeEmojiInteractions, isOwn, undefined, effect?.emoji);
const localMediaHash = `sticker${sticker?.id}`;
const isIntersecting = useIsIntersecting(ref, observeIntersection);
const thumbDataUri = sticker?.thumbnail?.dataUri;
const previewBlobUrl = useMedia(
sticker ? `${localMediaHash}?size=m` : undefined,
!isIntersecting && !forceLoadPreview,
ApiMediaFormat.BlobUrl,
lastSyncTime,
);
const transitionClassNames = useMediaTransition(previewBlobUrl);
const mediaData = useMedia(localMediaHash, !isIntersecting, ApiMediaFormat.Lottie, lastSyncTime);
const isMediaLoaded = Boolean(mediaData);
return (
<div
ref={ref}
className={buildClassName('AnimatedEmoji media-inner', sticker?.id === LIKE_STICKER_ID && 'like-sticker-thumb')}
style={style}
onClick={handleClick}
>
{!isAnimationLoaded && thumbDataUri && (
<img src={thumbDataUri} alt="" />
)}
{!isAnimationLoaded && previewBlobUrl && (
<img src={previewBlobUrl} className={transitionClassNames} alt="" />
)}
{isMediaLoaded && localMediaHash && (
<AnimatedSticker
key={localMediaHash}
id={localMediaHash}
animationData={mediaData!}
size={width}
quality={QUALITY}
play={isIntersecting && playKey}
forceOnHeavyAnimation
noLoop
onLoad={markAnimationLoaded}
/>
)}
</div>
);
};
export default memo(AnimatedEmoji);

View File

@ -1,42 +1,70 @@
import type { FC } from '../../lib/teact/teact';
import React, { memo, useEffect, useState } from '../../lib/teact/teact';
import React, {
memo, useCallback, useState,
} from '../../lib/teact/teact';
import type { ANIMATED_STICKERS_PATHS } from './helpers/animatedAssets';
import getAnimationData from './helpers/animatedAssets';
import type { OwnProps as AnimatedStickerProps } from './AnimatedSticker';
import buildClassName from '../../util/buildClassName';
import useMediaTransition from '../../hooks/useMediaTransition';
import useFlag from '../../hooks/useFlag';
import AnimatedSticker from './AnimatedSticker';
type OwnProps = {
name: keyof typeof ANIMATED_STICKERS_PATHS;
size: number;
playSegment?: [number, number];
color?: [number, number, number];
};
const DEFAULT_SIZE = 150;
const AnimatedIcon: FC<OwnProps> = ({
size,
name,
playSegment,
color,
}) => {
const [iconData, setIconData] = useState<string>();
export type OwnProps =
Partial<AnimatedStickerProps>
& { noTransition?: boolean; nonInteractive?: boolean };
useEffect(() => {
getAnimationData(name).then(setIconData);
}, [name]);
const loadedAnimationUrls = new Set();
function AnimatedIcon(props: OwnProps) {
const {
size = DEFAULT_SIZE,
play = true,
noLoop = true,
className,
noTransition,
nonInteractive,
onLoad,
onClick,
...otherProps
} = props;
const { tgsUrl } = props;
const key = `${tgsUrl}_${size}`;
const [isAnimationLoaded, markAnimationLoaded] = useFlag(loadedAnimationUrls.has(key));
const transitionClassNames = useMediaTransition(noTransition || isAnimationLoaded);
const handleLoad = useCallback(() => {
markAnimationLoaded();
loadedAnimationUrls.add(key);
onLoad?.();
}, [key, markAnimationLoaded, onLoad]);
const [playKey, setPlayKey] = useState(String(Math.random()));
const handleClick = useCallback(() => {
if (play === true) {
setPlayKey(String(Math.random()));
}
onClick?.();
}, [onClick, play]);
return (
<AnimatedSticker
id={name}
play
noLoop
playSegment={playSegment}
className={buildClassName(className, transitionClassNames, 'shown')}
size={size}
speed={1}
animationData={iconData}
color={color}
play={play === true ? playKey : play}
noLoop={noLoop}
onClick={!nonInteractive ? handleClick : undefined}
onLoad={handleLoad}
/* eslint-disable-next-line react/jsx-props-no-spreading */
{...otherProps}
/>
);
};
}
export default memo(AnimatedIcon);

View File

@ -0,0 +1,41 @@
import React, { memo } from '../../lib/teact/teact';
import type { OwnProps as AnimatedIconProps } from './AnimatedIcon';
import type { ApiSticker } from '../../api/types';
import { ApiMediaFormat } from '../../api/types';
import useMedia from '../../hooks/useMedia';
import AnimatedIconWithPreview from './AnimatedIconWithPreview';
type OwnProps =
Partial<AnimatedIconProps>
& { sticker?: ApiSticker; noLoad?: boolean; forcePreview?: boolean; lastSyncTime?: number };
function AnimatedIconFromSticker(props: OwnProps) {
const {
sticker, noLoad, forcePreview, lastSyncTime, ...otherProps
} = props;
const thumbDataUri = sticker?.thumbnail?.dataUri;
const localMediaHash = `sticker${sticker?.id}`;
const previewBlobUrl = useMedia(
sticker ? `${localMediaHash}?size=m` : undefined,
noLoad && !forcePreview,
ApiMediaFormat.BlobUrl,
lastSyncTime,
);
const tgsUrl = useMedia(localMediaHash, noLoad, undefined, lastSyncTime);
return (
<AnimatedIconWithPreview
tgsUrl={tgsUrl}
previewUrl={previewBlobUrl}
thumbDataUri={thumbDataUri}
// eslint-disable-next-line react/jsx-props-no-spreading
{...otherProps}
/>
);
}
export default memo(AnimatedIconFromSticker);

View File

@ -0,0 +1,11 @@
.root {
position: relative;
}
.preview {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
}

View File

@ -0,0 +1,45 @@
import React, { memo } from '../../lib/teact/teact';
import type { OwnProps as AnimatedIconProps } from './AnimatedIcon';
import buildClassName from '../../util/buildClassName';
import useMediaTransition from '../../hooks/useMediaTransition';
import AnimatedIcon from './AnimatedIcon';
import styles from './AnimatedIconWithPreview.module.scss';
import useFlag from '../../hooks/useFlag';
import buildStyle from '../../util/buildStyle';
type OwnProps =
Partial<AnimatedIconProps>
& { previewUrl?: string; thumbDataUri?: string };
function AnimatedIconWithPreview(props: OwnProps) {
const {
previewUrl, thumbDataUri, className, ...otherProps
} = props;
const transitionClassNames = useMediaTransition(previewUrl);
const [isAnimationReady, markAnimationReady] = useFlag(false);
const { size } = props;
return (
<div
className={buildClassName(className, styles.root)}
style={buildStyle(size !== undefined && `width: ${size}px; height: ${size}px;`)}
>
{!isAnimationReady && thumbDataUri && (
// eslint-disable-next-line jsx-a11y/alt-text
<img src={thumbDataUri} className={buildClassName(styles.preview)} />
)}
{!isAnimationReady && (
// eslint-disable-next-line jsx-a11y/alt-text
<img src={previewUrl} className={buildClassName(styles.preview, transitionClassNames)} />
)}
{/* eslint-disable-next-line react/jsx-props-no-spreading */}
<AnimatedIcon {...otherProps} onLoad={markAnimationReady} noTransition />
</div>
);
}
export default memo(AnimatedIconWithPreview);

View File

@ -1,17 +1,21 @@
import type { RefObject } from 'react';
import type { FC } from '../../lib/teact/teact';
import React, {
useEffect, useRef, memo, useCallback, useState,
} from '../../lib/teact/teact';
import { fastRaf } from '../../util/schedulers';
import buildClassName from '../../util/buildClassName';
import buildStyle from '../../util/buildStyle';
import useHeavyAnimationCheck from '../../hooks/useHeavyAnimationCheck';
import useBackgroundMode from '../../hooks/useBackgroundMode';
type OwnProps = {
export type OwnProps = {
ref?: RefObject<HTMLDivElement>;
className?: string;
id: string;
animationData?: string;
style?: string;
tgsUrl?: string;
play?: boolean | string;
playSegment?: [number, number];
speed?: number;
@ -19,9 +23,10 @@ type OwnProps = {
size: number;
quality?: number;
isLowPriority?: boolean;
onLoad?: NoneToVoidFunction;
forceOnHeavyAnimation?: boolean;
color?: [number, number, number];
onClick?: NoneToVoidFunction;
onLoad?: NoneToVoidFunction;
onEnded?: NoneToVoidFunction;
};
@ -45,9 +50,10 @@ async function ensureLottie() {
setTimeout(ensureLottie, LOTTIE_LOAD_DELAY);
const AnimatedSticker: FC<OwnProps> = ({
ref,
className,
id,
animationData,
style,
tgsUrl,
play,
playSegment,
speed,
@ -57,12 +63,17 @@ const AnimatedSticker: FC<OwnProps> = ({
isLowPriority,
color,
forceOnHeavyAnimation,
onClick,
onLoad,
onEnded,
}) => {
const [animation, setAnimation] = useState<RLottieInstance>();
// eslint-disable-next-line no-null/no-null
const container = useRef<HTMLDivElement>(null);
let containerRef = useRef<HTMLDivElement>(null);
if (ref) {
containerRef = ref;
}
const [animation, setAnimation] = useState<RLottieInstance>();
const wasPlaying = useRef(false);
const isFrozen = useRef(false);
const isFirstRender = useRef(true);
@ -73,19 +84,18 @@ const AnimatedSticker: FC<OwnProps> = ({
playSegmentRef.current = playSegment;
useEffect(() => {
if (animation || !animationData) {
if (animation || !tgsUrl) {
return;
}
const exec = () => {
if (!container.current) {
if (!containerRef.current) {
return;
}
const newAnimation = new RLottie(
id,
container.current,
animationData,
containerRef.current,
tgsUrl,
{
noLoop,
size,
@ -109,13 +119,13 @@ const AnimatedSticker: FC<OwnProps> = ({
} else {
ensureLottie().then(() => {
fastRaf(() => {
if (container.current) {
if (containerRef.current) {
exec();
}
});
});
}
}, [color, animation, animationData, id, isLowPriority, noLoop, onLoad, quality, size, speed, onEnded]);
}, [color, animation, tgsUrl, isLowPriority, noLoop, onLoad, quality, size, speed, onEnded]);
useEffect(() => {
if (!animation) return;
@ -135,10 +145,8 @@ const AnimatedSticker: FC<OwnProps> = ({
if (animation && (playRef.current || playSegmentRef.current)) {
if (playSegmentRef.current) {
animation.playSegment(playSegmentRef.current);
} else if (shouldRestart) {
animation.goToAndPlay(0);
} else {
animation.play();
animation.play(shouldRestart);
}
}
}, [animation]);
@ -167,12 +175,12 @@ const AnimatedSticker: FC<OwnProps> = ({
const unfreezeAnimation = useCallback(() => {
if (wasPlaying.current) {
playAnimation();
playAnimation(noLoop);
}
wasPlaying.current = false;
isFrozen.current = false;
}, [playAnimation]);
}, [noLoop, playAnimation]);
const unfreezeAnimationOnRaf = useCallback(() => {
fastRaf(unfreezeAnimation);
@ -203,12 +211,12 @@ const AnimatedSticker: FC<OwnProps> = ({
if (animation) {
if (isFirstRender.current) {
isFirstRender.current = false;
} else if (animationData) {
animation.changeData(animationData);
} else if (tgsUrl) {
animation.changeData(tgsUrl);
playAnimation();
}
}
}, [playAnimation, animation, animationData]);
}, [playAnimation, animation, tgsUrl]);
useHeavyAnimationCheck(freezeAnimation, unfreezeAnimation, forceOnHeavyAnimation);
// Pausing frame may not happen in background
@ -216,15 +224,16 @@ const AnimatedSticker: FC<OwnProps> = ({
// then we can play again.
useBackgroundMode(freezeAnimation, unfreezeAnimationOnRaf);
const fullClassName = buildClassName('AnimatedSticker', className);
const style = size ? `width: ${size}px; height: ${size}px;` : undefined;
return (
<div
ref={container}
className={fullClassName}
style={style}
ref={containerRef}
className={buildClassName('AnimatedSticker', className)}
style={buildStyle(
size !== undefined && `width: ${size}px; height: ${size}px;`,
onClick && 'cursor: pointer',
style,
)}
onClick={onClick}
/>
);
};

View File

@ -1,86 +0,0 @@
import type { FC } from '../../lib/teact/teact';
import React, { memo, useEffect, useState } from '../../lib/teact/teact';
import type { ActiveEmojiInteraction } from '../../global/types';
import type { ObserveFn } from '../../hooks/useIntersectionObserver';
import { useIsIntersecting } from '../../hooks/useIntersectionObserver';
import type { ANIMATED_STICKERS_PATHS } from './helpers/animatedAssets';
import getAnimationData from './helpers/animatedAssets';
import useAnimatedEmoji from './hooks/useAnimatedEmoji';
import AnimatedSticker from './AnimatedSticker';
const QUALITY = 1;
type OwnProps = {
localSticker?: string;
localEffect?: string;
isOwn?: boolean;
soundId?: string;
observeIntersection?: ObserveFn;
size?: 'large' | 'medium' | 'small';
lastSyncTime?: number;
forceLoadPreview?: boolean;
messageId?: number;
chatId?: string;
activeEmojiInteractions?: ActiveEmojiInteraction[];
};
const LocalAnimatedEmoji: FC<OwnProps> = ({
localSticker,
localEffect,
isOwn,
soundId,
size = 'medium',
observeIntersection,
messageId,
chatId,
activeEmojiInteractions,
}) => {
const {
playKey,
ref,
style,
width,
handleClick,
markAnimationLoaded,
} = useAnimatedEmoji(size, chatId, messageId, soundId, activeEmojiInteractions, isOwn, localEffect);
const id = `local_emoji_${localSticker}`;
const isIntersecting = useIsIntersecting(ref, observeIntersection);
const [localStickerAnimationData, setLocalStickerAnimationData] = useState<string>();
useEffect(() => {
if (localSticker) {
getAnimationData(localSticker as keyof typeof ANIMATED_STICKERS_PATHS).then((data) => {
setLocalStickerAnimationData(data);
});
}
}, [localSticker]);
return (
<div
ref={ref}
className="AnimatedEmoji media-inner"
style={style}
onClick={handleClick}
>
{localStickerAnimationData && (
<AnimatedSticker
key={id}
id={id}
animationData={localStickerAnimationData}
size={width}
quality={QUALITY}
play={isIntersecting && playKey}
forceOnHeavyAnimation
noLoop
onLoad={markAnimationLoaded}
/>
)}
</div>
);
};
export default memo(LocalAnimatedEmoji);

View File

@ -1,14 +1,15 @@
import type { FC } from '../../lib/teact/teact';
import React, {
useState, useEffect, useCallback, memo,
} from '../../lib/teact/teact';
import React, { useCallback, memo } from '../../lib/teact/teact';
import { STICKER_SIZE_AUTH, STICKER_SIZE_AUTH_MOBILE, STICKER_SIZE_TWO_FA } from '../../config';
import { IS_SINGLE_COLUMN_LAYOUT } from '../../util/environment';
import getAnimationData from './helpers/animatedAssets';
import { LOCAL_TGS_URLS } from './helpers/animatedAssets';
import AnimatedSticker from './AnimatedSticker';
import useTimeout from '../../hooks/useTimeout';
import useFlag from '../../hooks/useFlag';
import './PasswordMonkey.scss';
type OwnProps = {
@ -23,53 +24,32 @@ const SEGMENT_COVER_EYE: [number, number] = [20, 0];
const STICKER_SIZE = IS_SINGLE_COLUMN_LAYOUT ? STICKER_SIZE_AUTH_MOBILE : STICKER_SIZE_AUTH;
const PasswordMonkey: FC<OwnProps> = ({ isPasswordVisible, isBig }) => {
const [closeMonkeyData, setCloseMonkeyData] = useState<string>();
const [peekMonkeyData, setPeekMonkeyData] = useState<string>();
const [isFirstMonkeyLoaded, setIsFirstMonkeyLoaded] = useState(false);
const [isPeekShown, setIsPeekShown] = useState(false);
const [isFirstMonkeyLoaded, markFirstMonkeyLoaded] = useFlag(false);
const [isPeekShown, markPeekShown] = useFlag(false);
useEffect(() => {
if (!closeMonkeyData) {
getAnimationData('MonkeyClose').then(setCloseMonkeyData);
} else {
setTimeout(() => setIsPeekShown(true), PEEK_MONKEY_SHOW_DELAY);
}
}, [closeMonkeyData]);
useEffect(() => {
if (!peekMonkeyData) {
getAnimationData('MonkeyPeek').then(setPeekMonkeyData);
}
}, [peekMonkeyData]);
const handleFirstMonkeyLoad = useCallback(() => setIsFirstMonkeyLoaded(true), []);
useTimeout(markPeekShown, PEEK_MONKEY_SHOW_DELAY);
const handleFirstMonkeyLoad = useCallback(markFirstMonkeyLoaded, [markFirstMonkeyLoaded]);
return (
<div id="monkey" className={isBig ? 'big' : ''}>
{!isFirstMonkeyLoaded && (
<div className="monkey-preview" />
)}
{closeMonkeyData && (
<AnimatedSticker
id="closeMonkey"
size={isBig ? STICKER_SIZE_TWO_FA : STICKER_SIZE}
className={isPeekShown ? 'hidden' : 'shown'}
animationData={closeMonkeyData}
playSegment={SEGMENT_COVER_EYES}
noLoop
onLoad={handleFirstMonkeyLoad}
/>
)}
{peekMonkeyData && (
<AnimatedSticker
id="peekMonkey"
size={isBig ? STICKER_SIZE_TWO_FA : STICKER_SIZE}
className={isPeekShown ? 'shown' : 'hidden'}
animationData={peekMonkeyData}
playSegment={isPasswordVisible ? SEGMENT_UNCOVER_EYE : SEGMENT_COVER_EYE}
noLoop
/>
)}
<AnimatedSticker
size={isBig ? STICKER_SIZE_TWO_FA : STICKER_SIZE}
className={isPeekShown ? 'hidden' : 'shown'}
tgsUrl={LOCAL_TGS_URLS.MonkeyClose}
playSegment={SEGMENT_COVER_EYES}
noLoop
onLoad={handleFirstMonkeyLoad}
/>
<AnimatedSticker
size={isBig ? STICKER_SIZE_TWO_FA : STICKER_SIZE}
className={isPeekShown ? 'shown' : 'hidden'}
tgsUrl={LOCAL_TGS_URLS.MonkeyPeek}
playSegment={isPasswordVisible ? SEGMENT_UNCOVER_EYE : SEGMENT_COVER_EYE}
noLoop
/>
</div>
);
};

View File

@ -75,7 +75,7 @@ const StickerButton = <T extends number | ApiSticker | ApiBotInlineMediaResult |
const previewBlobUrl = useMedia(`${localMediaHash}?size=m`, !isIntersecting, ApiMediaFormat.BlobUrl);
const shouldPlay = isIntersecting && !noAnimate;
const lottieData = useMedia(sticker.isLottie && localMediaHash, !shouldPlay, ApiMediaFormat.Lottie);
const lottieData = useMedia(sticker.isLottie && localMediaHash, !shouldPlay);
const [isLottieLoaded, markLoaded, unmarkLoaded] = useFlag(Boolean(lottieData));
const canLottiePlay = isLottieLoaded && shouldPlay;
const isVideo = sticker.isVideo && IS_WEBM_SUPPORTED;
@ -217,8 +217,7 @@ const StickerButton = <T extends number | ApiSticker | ApiBotInlineMediaResult |
)}
{shouldPlay && lottieData && (
<AnimatedSticker
id={localMediaHash}
animationData={lottieData}
tgsUrl={lottieData}
play
size={size}
isLowPriority

View File

@ -1,11 +1,9 @@
import type { FC } from '../../lib/teact/teact';
import React, {
useState, useEffect, useCallback, memo,
} from '../../lib/teact/teact';
import React, { useState, useCallback, memo } from '../../lib/teact/teact';
import { STICKER_SIZE_AUTH, STICKER_SIZE_AUTH_MOBILE, STICKER_SIZE_TWO_FA } from '../../config';
import { IS_SINGLE_COLUMN_LAYOUT } from '../../util/environment';
import getAnimationData from './helpers/animatedAssets';
import { LOCAL_TGS_URLS } from './helpers/animatedAssets';
import AnimatedSticker from './AnimatedSticker';
@ -30,23 +28,9 @@ const TrackingMonkey: FC<OwnProps> = ({
isTracking,
isBig,
}) => {
const [idleMonkeyData, setIdleMonkeyData] = useState<string>();
const [trackingMonkeyData, setTrackingMonkeyData] = useState<string>();
const [isFirstMonkeyLoaded, setIsFirstMonkeyLoaded] = useState(false);
const TRACKING_FRAMES_PER_SYMBOL = (TRACKING_END_FRAME - TRACKING_START_FRAME) / codeLength;
useEffect(() => {
if (!idleMonkeyData) {
getAnimationData('MonkeyIdle').then(setIdleMonkeyData);
}
}, [idleMonkeyData]);
useEffect(() => {
if (!trackingMonkeyData) {
getAnimationData('MonkeyTracking').then(setTrackingMonkeyData);
}
}, [trackingMonkeyData]);
const handleFirstMonkeyLoad = useCallback(() => setIsFirstMonkeyLoaded(true), []);
function getTrackingFrames(): [number, number] {
@ -75,27 +59,21 @@ const TrackingMonkey: FC<OwnProps> = ({
{!isFirstMonkeyLoaded && (
<div className="monkey-preview" />
)}
{idleMonkeyData && (
<AnimatedSticker
id="idleMonkey"
size={isBig ? STICKER_SIZE_TWO_FA : STICKER_SIZE}
className={isTracking ? 'hidden' : undefined}
animationData={idleMonkeyData}
play={!isTracking}
onLoad={handleFirstMonkeyLoad}
/>
)}
{trackingMonkeyData && (
<AnimatedSticker
id="trackingMonkey"
size={isBig ? STICKER_SIZE_TWO_FA : STICKER_SIZE}
className={!isTracking ? 'hidden' : 'shown'}
animationData={trackingMonkeyData}
playSegment={isTracking ? getTrackingFrames() : undefined}
speed={2}
noLoop
/>
)}
<AnimatedSticker
size={isBig ? STICKER_SIZE_TWO_FA : STICKER_SIZE}
className={isTracking ? 'hidden' : undefined}
tgsUrl={LOCAL_TGS_URLS.MonkeyIdle}
play={!isTracking}
onLoad={handleFirstMonkeyLoad}
/>
<AnimatedSticker
size={isBig ? STICKER_SIZE_TWO_FA : STICKER_SIZE}
className={!isTracking ? 'hidden' : 'shown'}
tgsUrl={LOCAL_TGS_URLS.MonkeyTracking}
playSegment={isTracking ? getTrackingFrames() : undefined}
speed={2}
noLoop
/>
</div>
);
};

View File

@ -1,7 +1,3 @@
import { ApiMediaFormat } from '../../../api/types';
import * as mediaLoader from '../../../util/mediaLoader';
import MonkeyIdle from '../../../assets/tgs/monkeys/TwoFactorSetupMonkeyIdle.tgs';
import MonkeyTracking from '../../../assets/tgs/monkeys/TwoFactorSetupMonkeyTracking.tgs';
import MonkeyClose from '../../../assets/tgs/monkeys/TwoFactorSetupMonkeyClose.tgs';
@ -31,7 +27,7 @@ import Invite from '../../../assets/tgs/invites/Invite.tgs';
import QrPlane from '../../../assets/tgs/auth/QrPlane.tgs';
export const ANIMATED_STICKERS_PATHS = {
export const LOCAL_TGS_URLS = {
MonkeyIdle,
MonkeyTracking,
MonkeyClose,
@ -56,9 +52,3 @@ export const ANIMATED_STICKERS_PATHS = {
QrPlane,
Congratulations,
};
export default function getAnimationData(name: keyof typeof ANIMATED_STICKERS_PATHS) {
const path = ANIMATED_STICKERS_PATHS[name].replace(window.location.origin, '');
return mediaLoader.fetch(`file${path}`, ApiMediaFormat.Lottie);
}

View File

@ -1,24 +1,18 @@
import {
useCallback, useEffect, useRef, useState,
useCallback, useEffect, useRef,
} from '../../../lib/teact/teact';
import safePlay from '../../../util/safePlay';
import { getActions } from '../../../global';
import useMedia from '../../../hooks/useMedia';
import type { ActiveEmojiInteraction } from '../../../global/types';
import useFlag from '../../../hooks/useFlag';
import { selectLocalAnimatedEmojiEffectByName } from '../../../global/selectors';
const WIDTH = {
large: 160,
medium: 128,
small: 104,
};
const SIZE = 104;
const INTERACTION_BUNCH_TIME = 1000;
const MS_DIVIDER = 1000;
const TIME_DEFAULT = 0;
export default function useAnimatedEmoji(
size: 'large' | 'medium' | 'small',
chatId?: string,
messageId?: number,
soundId?: string,
@ -32,7 +26,6 @@ export default function useAnimatedEmoji(
} = getActions();
const hasEffect = localEffect || emoji;
const [isAnimationLoaded, markAnimationLoaded] = useFlag();
// eslint-disable-next-line no-null/no-null
const ref = useRef<HTMLDivElement>(null);
@ -42,10 +35,8 @@ export default function useAnimatedEmoji(
const soundMediaData = useMedia(soundId ? `document${soundId}` : undefined, !soundId);
const width = WIDTH[size];
const style = `width: ${width}px; height: ${width}px;`;
const style = `width: ${SIZE}px; height: ${SIZE}px;`;
const [playKey, setPlayKey] = useState(String(Math.random()));
const interactions = useRef<number[] | undefined>(undefined);
const startedInteractions = useRef<number | undefined>(undefined);
const sendInteractionBunch = useCallback(() => {
@ -65,8 +56,6 @@ export default function useAnimatedEmoji(
}, [sendEmojiInteraction, chatId, messageId, localEffect, emoji]);
const play = useCallback(() => {
setPlayKey(String(Math.random()));
const audio = audioRef.current;
if (soundMediaData) {
if (audio) {
@ -98,7 +87,7 @@ export default function useAnimatedEmoji(
emoji,
x,
y,
startSize: width,
startSize: SIZE,
isReversed: !isOwn,
});
@ -113,7 +102,7 @@ export default function useAnimatedEmoji(
: TIME_DEFAULT);
}, [
chatId, emoji, hasEffect, interactWithAnimatedEmoji, isOwn,
localEffect, messageId, play, sendInteractionBunch, width,
localEffect, messageId, play, sendInteractionBunch,
]);
// Set an end anchor for remote activated interaction
@ -137,7 +126,7 @@ export default function useAnimatedEmoji(
id,
chatId,
emoticon: localEffect ? selectLocalAnimatedEmojiEffectByName(localEffect) : emoji,
startSize: width,
startSize: SIZE,
x,
y,
isReversed: !isOwn,
@ -145,16 +134,13 @@ export default function useAnimatedEmoji(
play();
});
}, [
activeEmojiInteractions, chatId, emoji, isOwn, localEffect, messageId, play, sendWatchingEmojiInteraction, width,
activeEmojiInteractions, chatId, emoji, isOwn, localEffect, messageId, play, sendWatchingEmojiInteraction,
]);
return {
playKey,
ref,
size: SIZE,
style,
width,
handleClick,
markAnimationLoaded,
isAnimationLoaded,
};
}

View File

@ -11,7 +11,7 @@ import { selectAnimatedEmoji, selectChatFolder } from '../../../global/selectors
import useLang from '../../../hooks/useLang';
import Button from '../../ui/Button';
import AnimatedEmoji from '../../common/AnimatedEmoji';
import AnimatedIconFromSticker from '../../common/AnimatedIconFromSticker';
import './EmptyFolder.scss';
@ -27,6 +27,8 @@ type StateProps = {
animatedEmoji?: ApiSticker;
};
const ICON_SIZE = 128;
const EmptyFolder: FC<OwnProps & StateProps> = ({
chatFolder, animatedEmoji, foldersDispatch, onScreenSelect,
}) => {
@ -39,7 +41,9 @@ const EmptyFolder: FC<OwnProps & StateProps> = ({
return (
<div className="EmptyFolder">
<div className="sticker">{animatedEmoji && <AnimatedEmoji sticker={animatedEmoji} />}</div>
<div className="sticker">
{animatedEmoji && <AnimatedIconFromSticker sticker={animatedEmoji} size={ICON_SIZE} />}
</div>
<h3 className="title" dir="auto">{lang('FilterNoChatsToDisplay')}</h3>
<p className="description" dir="auto">
{lang(chatFolder ? 'ChatList.EmptyChatListFilterText' : 'Chat.EmptyChat')}

View File

@ -90,22 +90,7 @@
}
.settings-content-icon {
width: 5rem;
height: 5rem;
margin-bottom: 2.5rem;
position: relative;
.AnimatedSticker {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
&.hidden {
display: none;
}
}
}
.settings-main-menu {

View File

@ -5,9 +5,9 @@ import React, {
import { getActions, withGlobal } from '../../../../global';
import { STICKER_SIZE_FOLDER_SETTINGS } from '../../../../config';
import { LOCAL_TGS_URLS } from '../../../common/helpers/animatedAssets';
import { findIntersectionWithSet } from '../../../../util/iteratees';
import { isUserId } from '../../../../global/helpers';
import getAnimationData from '../../../common/helpers/animatedAssets';
import type {
FolderEditDispatch,
FoldersState,
@ -21,13 +21,13 @@ import useLang from '../../../../hooks/useLang';
import useHistoryBack from '../../../../hooks/useHistoryBack';
import ListItem from '../../../ui/ListItem';
import AnimatedSticker from '../../../common/AnimatedSticker';
import InputText from '../../../ui/InputText';
import PrivateChatInfo from '../../../common/PrivateChatInfo';
import GroupChatInfo from '../../../common/GroupChatInfo';
import FloatingActionButton from '../../../ui/FloatingActionButton';
import Spinner from '../../../ui/Spinner';
import ShowMoreButton from '../../../ui/ShowMoreButton';
import AnimatedIcon from '../../../common/AnimatedIcon';
type OwnProps = {
state: FoldersState;
@ -67,10 +67,6 @@ const SettingsFoldersEdit: FC<OwnProps & StateProps> = ({
addChatFolder,
} = getActions();
const [animationData, setAnimationData] = useState<string>();
const [isAnimationLoaded, setIsAnimationLoaded] = useState(false);
const handleAnimationLoad = useCallback(() => setIsAnimationLoaded(true), []);
const [isIncludedChatsListExpanded, setIsIncludedChatsListExpanded] = useState(false);
const [isExcludedChatsListExpanded, setIsExcludedChatsListExpanded] = useState(false);
@ -83,12 +79,6 @@ const SettingsFoldersEdit: FC<OwnProps & StateProps> = ({
selectedChatTypes: excludedChatTypes,
} = selectChatFilters(state, 'excluded');
useEffect(() => {
if (!animationData) {
getAnimationData('FoldersNew').then(setAnimationData);
}
}, [animationData]);
useEffect(() => {
setIsIncludedChatsListExpanded(false);
setIsExcludedChatsListExpanded(false);
@ -219,18 +209,12 @@ const SettingsFoldersEdit: FC<OwnProps & StateProps> = ({
<div className="settings-fab-wrapper">
<div className="settings-content no-border custom-scroll">
<div className="settings-content-header">
<div className="settings-content-icon">
{animationData && (
<AnimatedSticker
id="settingsFoldersEdit"
size={STICKER_SIZE_FOLDER_SETTINGS}
animationData={animationData}
play={isAnimationLoaded && String(state.folderId)}
noLoop
onLoad={handleAnimationLoad}
/>
)}
</div>
<AnimatedIcon
size={STICKER_SIZE_FOLDER_SETTINGS}
tgsUrl={LOCAL_TGS_URLS.FoldersNew}
play={String(state.folderId)}
className="settings-content-icon"
/>
{state.mode === 'create' && (
<p className="settings-item-description mb-3" dir={lang.isRtl ? 'rtl' : undefined}>

View File

@ -1,14 +1,14 @@
import type { FC } from '../../../../lib/teact/teact';
import React, {
memo, useMemo, useCallback, useState, useEffect,
memo, useMemo, useCallback, useEffect,
} from '../../../../lib/teact/teact';
import { getActions, withGlobal } from '../../../../global';
import type { ApiChatFolder } from '../../../../api/types';
import { STICKER_SIZE_FOLDER_SETTINGS } from '../../../../config';
import { LOCAL_TGS_URLS } from '../../../common/helpers/animatedAssets';
import { throttle } from '../../../../util/schedulers';
import getAnimationData from '../../../common/helpers/animatedAssets';
import { getFolderDescriptionText } from '../../../../global/helpers';
import useLang from '../../../../hooks/useLang';
import useHistoryBack from '../../../../hooks/useHistoryBack';
@ -17,7 +17,7 @@ import { useFolderManagerForChatsCount } from '../../../../hooks/useFolderManage
import ListItem from '../../../ui/ListItem';
import Button from '../../../ui/Button';
import Loading from '../../../ui/Loading';
import AnimatedSticker from '../../../common/AnimatedSticker';
import AnimatedIcon from '../../../common/AnimatedIcon';
type OwnProps = {
isActive?: boolean;
@ -51,16 +51,6 @@ const SettingsFoldersMain: FC<OwnProps & StateProps> = ({
showDialog,
} = getActions();
const [animationData, setAnimationData] = useState<string>();
const [isAnimationLoaded, setIsAnimationLoaded] = useState(false);
const handleAnimationLoad = useCallback(() => setIsAnimationLoaded(true), []);
useEffect(() => {
if (!animationData) {
getAnimationData('FoldersAll').then(setAnimationData);
}
}, [animationData]);
// Due to the parent Transition, this component never gets unmounted,
// that's why we use throttled API call on every update.
useEffect(() => {
@ -126,18 +116,11 @@ const SettingsFoldersMain: FC<OwnProps & StateProps> = ({
return (
<div className="settings-content no-border custom-scroll">
<div className="settings-content-header">
<div className="settings-content-icon">
{animationData && (
<AnimatedSticker
id="settingsFoldersMain"
size={STICKER_SIZE_FOLDER_SETTINGS}
animationData={animationData}
play={isAnimationLoaded}
noLoop
onLoad={handleAnimationLoad}
/>
)}
</div>
<AnimatedIcon
size={STICKER_SIZE_FOLDER_SETTINGS}
tgsUrl={LOCAL_TGS_URLS.FoldersAll}
className="settings-content-icon"
/>
<p className="settings-item-description mb-3" dir="auto">
{lang('CreateNewFilterInfo')}

View File

@ -2,6 +2,7 @@ import type { FC } from '../../../../lib/teact/teact';
import React, { memo, useCallback } from '../../../../lib/teact/teact';
import { STICKER_SIZE_PASSCODE } from '../../../../config';
import { LOCAL_TGS_URLS } from '../../../common/helpers/animatedAssets';
import useLang from '../../../../hooks/useLang';
import useHistoryBack from '../../../../hooks/useHistoryBack';
@ -27,7 +28,11 @@ const SettingsPasscodeCongratulations: FC<OwnProps> = ({
return (
<div className="settings-content local-passcode custom-scroll">
<div className="settings-content-header no-border">
<AnimatedIcon size={STICKER_SIZE_PASSCODE} name="Congratulations" />
<AnimatedIcon
size={STICKER_SIZE_PASSCODE}
tgsUrl={LOCAL_TGS_URLS.Congratulations}
className="settings-content-icon"
/>
<p className="settings-item-description mb-3" dir="auto">
Congratulations!

View File

@ -1,16 +1,16 @@
import type { FC } from '../../../../lib/teact/teact';
import React, { memo } from '../../../../lib/teact/teact';
import { withGlobal } from '../../../../global';
import type { ApiSticker } from '../../../../api/types';
import { SettingsScreens } from '../../../../types';
import { selectAnimatedEmoji } from '../../../../global/selectors';
import useLang from '../../../../hooks/useLang';
import useHistoryBack from '../../../../hooks/useHistoryBack';
import { LOCAL_TGS_URLS } from '../../../common/helpers/animatedAssets';
import ListItem from '../../../ui/ListItem';
import AnimatedEmoji from '../../../common/AnimatedEmoji';
import AnimatedIconWithPreview from '../../../common/AnimatedIconWithPreview';
import lockPreviewUrl from '../../../../assets/lock.png';
type OwnProps = {
isActive?: boolean;
@ -18,12 +18,8 @@ type OwnProps = {
onReset: () => void;
};
type StateProps = {
animatedEmoji: ApiSticker;
};
const SettingsPasscodeEnabled: FC<OwnProps & StateProps> = ({
isActive, onReset, animatedEmoji, onScreenSelect,
const SettingsPasscodeEnabled: FC<OwnProps> = ({
isActive, onReset, onScreenSelect,
}) => {
const lang = useLang();
@ -32,7 +28,12 @@ const SettingsPasscodeEnabled: FC<OwnProps & StateProps> = ({
return (
<div className="settings-content local-passcode custom-scroll">
<div className="settings-content-header no-border">
<AnimatedEmoji sticker={animatedEmoji} size="large" />
<AnimatedIconWithPreview
tgsUrl={LOCAL_TGS_URLS.Lock}
previewUrl={lockPreviewUrl}
size={160}
className="settings-content-icon"
/>
<p className="settings-item-description mb-3" dir="auto">
Local passcode is enabled.
@ -59,8 +60,4 @@ const SettingsPasscodeEnabled: FC<OwnProps & StateProps> = ({
);
};
export default memo(withGlobal<OwnProps>((global) => {
return {
animatedEmoji: selectAnimatedEmoji(global, '🔐'),
};
})(SettingsPasscodeEnabled));
export default memo(SettingsPasscodeEnabled);

View File

@ -2,11 +2,14 @@ import type { FC } from '../../../../lib/teact/teact';
import React, { memo } from '../../../../lib/teact/teact';
import { STICKER_SIZE_PASSCODE } from '../../../../config';
import { LOCAL_TGS_URLS } from '../../../common/helpers/animatedAssets';
import useLang from '../../../../hooks/useLang';
import useHistoryBack from '../../../../hooks/useHistoryBack';
import Button from '../../../ui/Button';
import AnimatedIcon from '../../../common/AnimatedIcon';
import AnimatedIconWithPreview from '../../../common/AnimatedIconWithPreview';
import lockPreviewUrl from '../../../../assets/lock.png';
type OwnProps = {
onStart: NoneToVoidFunction;
@ -24,7 +27,12 @@ const SettingsPasscodeStart: FC<OwnProps> = ({
return (
<div className="settings-content local-passcode custom-scroll">
<div className="settings-content-header no-border">
<AnimatedIcon size={STICKER_SIZE_PASSCODE} name="Lock" />
<AnimatedIconWithPreview
tgsUrl={LOCAL_TGS_URLS.Lock}
previewUrl={lockPreviewUrl}
size={STICKER_SIZE_PASSCODE}
className="settings-content-icon"
/>
<p className="settings-item-description" dir="auto">
When you set up an additional passcode, a lock icon will appear on the chats page.

View File

@ -4,6 +4,7 @@ import React, { memo, useCallback } from '../../../../lib/teact/teact';
import { SettingsScreens } from '../../../../types';
import { STICKER_SIZE_TWO_FA } from '../../../../config';
import { LOCAL_TGS_URLS } from '../../../common/helpers/animatedAssets';
import useLang from '../../../../hooks/useLang';
import useHistoryBack from '../../../../hooks/useHistoryBack';
@ -33,7 +34,11 @@ const SettingsTwoFaCongratulations: FC<OwnProps> = ({
return (
<div className="settings-content two-fa custom-scroll">
<div className="settings-content-header no-border">
<AnimatedIcon size={STICKER_SIZE_TWO_FA} name="Congratulations" />
<AnimatedIcon
size={STICKER_SIZE_TWO_FA}
tgsUrl={LOCAL_TGS_URLS.Congratulations}
className="settings-content-icon"
/>
<p className="settings-item-description mb-3" dir="auto">
{lang('TwoStepVerificationPasswordSetInfo')}

View File

@ -11,9 +11,9 @@ import { selectAnimatedEmoji } from '../../../../global/selectors';
import useLang from '../../../../hooks/useLang';
import useHistoryBack from '../../../../hooks/useHistoryBack';
import AnimatedEmoji from '../../../common/AnimatedEmoji';
import InputText from '../../../ui/InputText';
import Loading from '../../../ui/Loading';
import AnimatedIconFromSticker from '../../../common/AnimatedIconFromSticker';
type OwnProps = {
isLoading?: boolean;
@ -30,6 +30,7 @@ type StateProps = {
};
const FOCUS_DELAY_TIMEOUT_MS = IS_SINGLE_COLUMN_LAYOUT ? 550 : 400;
const ICON_SIZE = 160;
const SettingsTwoFaEmailCode: FC<OwnProps & StateProps> = ({
animatedEmoji,
@ -79,7 +80,7 @@ const SettingsTwoFaEmailCode: FC<OwnProps & StateProps> = ({
return (
<div className="settings-content two-fa custom-scroll">
<div className="settings-content-header no-border">
<AnimatedEmoji sticker={animatedEmoji} size="large" />
<AnimatedIconFromSticker sticker={animatedEmoji} size={ICON_SIZE} className="settings-content-icon" />
</div>
<div className="settings-item pt-0">

View File

@ -1,17 +1,17 @@
import type { FC } from '../../../../lib/teact/teact';
import React, { memo } from '../../../../lib/teact/teact';
import { withGlobal } from '../../../../global';
import type { ApiSticker } from '../../../../api/types';
import { SettingsScreens } from '../../../../types';
import { selectAnimatedEmoji } from '../../../../global/selectors';
import useLang from '../../../../hooks/useLang';
import useHistoryBack from '../../../../hooks/useHistoryBack';
import { LOCAL_TGS_URLS } from '../../../common/helpers/animatedAssets';
import ListItem from '../../../ui/ListItem';
import AnimatedEmoji from '../../../common/AnimatedEmoji';
import renderText from '../../../common/helpers/renderText';
import AnimatedIconWithPreview from '../../../common/AnimatedIconWithPreview';
import lockPreviewUrl from '../../../../assets/lock.png';
type OwnProps = {
isActive?: boolean;
@ -19,12 +19,8 @@ type OwnProps = {
onReset: () => void;
};
type StateProps = {
animatedEmoji: ApiSticker;
};
const SettingsTwoFaEnabled: FC<OwnProps & StateProps> = ({
isActive, onReset, animatedEmoji, onScreenSelect,
const SettingsTwoFaEnabled: FC<OwnProps> = ({
isActive, onReset, onScreenSelect,
}) => {
const lang = useLang();
@ -36,7 +32,12 @@ const SettingsTwoFaEnabled: FC<OwnProps & StateProps> = ({
return (
<div className="settings-content two-fa custom-scroll">
<div className="settings-content-header no-border">
<AnimatedEmoji sticker={animatedEmoji} size="large" />
<AnimatedIconWithPreview
tgsUrl={LOCAL_TGS_URLS.Lock}
previewUrl={lockPreviewUrl}
size={160}
className="settings-content-icon"
/>
<p className="settings-item-description mb-3" dir="auto">
{renderText(lang('EnabledPasswordText'), ['br'])}
@ -70,8 +71,4 @@ const SettingsTwoFaEnabled: FC<OwnProps & StateProps> = ({
);
};
export default memo(withGlobal<OwnProps>((global) => {
return {
animatedEmoji: selectAnimatedEmoji(global, '🔐'),
};
})(SettingsTwoFaEnabled));
export default memo(SettingsTwoFaEnabled);

View File

@ -8,15 +8,15 @@ import type { ApiSticker } from '../../../../api/types';
import { IS_SINGLE_COLUMN_LAYOUT, IS_TOUCH_ENV } from '../../../../util/environment';
import { selectAnimatedEmoji } from '../../../../global/selectors';
import renderText from '../../../common/helpers/renderText';
import useFlag from '../../../../hooks/useFlag';
import useLang from '../../../../hooks/useLang';
import useHistoryBack from '../../../../hooks/useHistoryBack';
import Button from '../../../ui/Button';
import Modal from '../../../ui/Modal';
import AnimatedEmoji from '../../../common/AnimatedEmoji';
import InputText from '../../../ui/InputText';
import renderText from '../../../common/helpers/renderText';
import AnimatedIconFromSticker from '../../../common/AnimatedIconFromSticker';
type OwnProps = {
icon: 'hint' | 'email';
@ -36,6 +36,7 @@ type StateProps = {
};
const FOCUS_DELAY_TIMEOUT_MS = IS_SINGLE_COLUMN_LAYOUT ? 550 : 400;
const ICON_SIZE = 160;
const SettingsTwoFaSkippableForm: FC<OwnProps & StateProps> = ({
animatedEmoji,
@ -100,7 +101,7 @@ const SettingsTwoFaSkippableForm: FC<OwnProps & StateProps> = ({
return (
<div className="settings-content two-fa custom-scroll">
<div className="settings-content-header no-border">
<AnimatedEmoji sticker={animatedEmoji} size="large" />
<AnimatedIconFromSticker sticker={animatedEmoji} size={ICON_SIZE} className="settings-content-icon" />
</div>
<div className="settings-item pt-0">

View File

@ -1,15 +1,14 @@
import type { FC } from '../../../../lib/teact/teact';
import React, { memo } from '../../../../lib/teact/teact';
import { withGlobal } from '../../../../global';
import type { ApiSticker } from '../../../../api/types';
import { selectAnimatedEmoji } from '../../../../global/selectors';
import useLang from '../../../../hooks/useLang';
import useHistoryBack from '../../../../hooks/useHistoryBack';
import Button from '../../../ui/Button';
import AnimatedEmoji from '../../../common/AnimatedEmoji';
import AnimatedIconWithPreview from '../../../common/AnimatedIconWithPreview';
import lockPreviewUrl from '../../../../assets/lock.png';
import { LOCAL_TGS_URLS } from '../../../common/helpers/animatedAssets';
type OwnProps = {
onStart: NoneToVoidFunction;
@ -17,12 +16,8 @@ type OwnProps = {
onReset: () => void;
};
type StateProps = {
animatedEmoji: ApiSticker;
};
const SettingsTwoFaStart: FC<OwnProps & StateProps> = ({
isActive, onReset, animatedEmoji, onStart,
const SettingsTwoFaStart: FC<OwnProps> = ({
isActive, onReset, onStart,
}) => {
const lang = useLang();
@ -34,7 +29,12 @@ const SettingsTwoFaStart: FC<OwnProps & StateProps> = ({
return (
<div className="settings-content two-fa custom-scroll">
<div className="settings-content-header no-border">
<AnimatedEmoji sticker={animatedEmoji} size="large" />
<AnimatedIconWithPreview
tgsUrl={LOCAL_TGS_URLS.Lock}
previewUrl={lockPreviewUrl}
size={160}
className="settings-content-icon"
/>
<p className="settings-item-description mb-3" dir="auto">
{lang('SetAdditionalPasswordInfo')}
@ -48,8 +48,4 @@ const SettingsTwoFaStart: FC<OwnProps & StateProps> = ({
);
};
export default memo(withGlobal<OwnProps>((global) => {
return {
animatedEmoji: selectAnimatedEmoji(global, '🔐'),
};
})(SettingsTwoFaStart));
export default memo(SettingsTwoFaStart);

View File

@ -26,26 +26,11 @@
}
.icon {
position: relative;
width: 10rem;
height: 10rem;
margin: 0 auto 1rem;
}
.iconAnimated,
.iconStatic {
position: absolute;
left: 0;
top: 0;
width: 10rem;
height: 10rem;
}
.iconStatic {
background: url("../../assets/lock.png") no-repeat center;
background-size: contain;
}
.help {
margin-top: 2rem;
}

View File

@ -6,21 +6,22 @@ import { getActions, withGlobal } from '../../global';
import type { GlobalState } from '../../global/types';
import { LOCAL_TGS_URLS } from '../common/helpers/animatedAssets';
import useLang from '../../hooks/useLang';
import buildClassName from '../../util/buildClassName';
import { decryptSession } from '../../util/passcode';
import getAnimationData from '../common/helpers/animatedAssets';
import useShowTransition from '../../hooks/useShowTransition';
import useTimeout from '../../hooks/useTimeout';
import useFlag from '../../hooks/useFlag';
import AnimatedSticker from '../common/AnimatedSticker';
import AnimatedIconWithPreview from '../common/AnimatedIconWithPreview';
import PasswordForm from '../common/PasswordForm';
import ConfirmDialog from '../ui/ConfirmDialog';
import Button from '../ui/Button';
import Link from '../ui/Link';
import styles from './LockScreen.module.scss';
import lockPreviewUrl from '../../assets/lock.png';
export type OwnProps = {
isLocked?: boolean;
@ -55,18 +56,6 @@ const LockScreen: FC<OwnProps & StateProps> = ({
const [shouldShowPasscode, setShouldShowPasscode] = useState(false);
const [isSignOutDialogOpen, openSignOutConfirmation, closeSignOutConfirmation] = useFlag(false);
const { transitionClassNames, shouldRender } = useShowTransition(isLocked);
const [animationData, setAnimationData] = useState<string>();
const [isAnimationLoaded, markAnimationLoaded] = useFlag();
const shouldRenderAnimated = Boolean(animationData);
useEffect(() => {
getAnimationData('Lock').then(setAnimationData);
}, []);
const { transitionClassNames: animatedClassNames } = useShowTransition(shouldRenderAnimated);
const { shouldRender: shouldRenderStatic, transitionClassNames: staticClassNames } = useShowTransition(
!isAnimationLoaded, undefined, true,
);
useTimeout(
resetInvalidUnlockAttempts,
@ -128,22 +117,12 @@ const LockScreen: FC<OwnProps & StateProps> = ({
return (
<div className={buildClassName(styles.container, transitionClassNames)}>
<div className={styles.wrapper} dir={lang.isRtl ? 'rtl' : undefined}>
<div className={styles.icon}>
{shouldRenderStatic && (
<div className={buildClassName(styles.iconStatic, staticClassNames)} />
)}
{shouldRenderAnimated && (
<AnimatedSticker
id="lock_screen_icon"
animationData={animationData}
className={buildClassName(styles.iconAnimated, animatedClassNames)}
play
noLoop
size={ICON_SIZE}
onLoad={markAnimationLoaded}
/>
)}
</div>
<AnimatedIconWithPreview
tgsUrl={LOCAL_TGS_URLS.Lock}
previewUrl={lockPreviewUrl}
size={ICON_SIZE}
className={styles.icon}
/>
<PasswordForm
key="password-form"

View File

@ -1,11 +1,10 @@
import type { FC } from '../../lib/teact/teact';
import React, {
memo, useCallback, useEffect, useLayoutEffect, useRef, useState,
memo, useCallback, useEffect, useLayoutEffect, useRef,
} from '../../lib/teact/teact';
import { getActions, withGlobal } from '../../global';
import type { ActiveEmojiInteraction } from '../../global/types';
import { ApiMediaFormat } from '../../api/types';
import { IS_ANDROID } from '../../util/environment';
import useFlag from '../../hooks/useFlag';
@ -14,7 +13,7 @@ import buildClassName from '../../util/buildClassName';
import {
selectAnimatedEmojiEffect,
} from '../../global/selectors';
import getAnimationData, { ANIMATED_STICKERS_PATHS } from '../common/helpers/animatedAssets';
import { LOCAL_TGS_URLS } from '../common/helpers/animatedAssets';
import { dispatchHeavyAnimationEvent } from '../../hooks/useHeavyAnimationCheck';
import AnimatedSticker from '../common/AnimatedSticker';
@ -84,22 +83,16 @@ const EmojiInteractionAnimation: FC<OwnProps & StateProps> = ({
}, PLAYING_DURATION);
}, [stop]);
const effectAnimationData = useMedia(`sticker${effectAnimationId}`, !effectAnimationId, ApiMediaFormat.Lottie);
const [localEffectAnimationData, setLocalEffectAnimationData] = useState<string | undefined>();
useEffect(() => {
if (localEffectAnimation) {
getAnimationData(localEffectAnimation as keyof typeof ANIMATED_STICKERS_PATHS).then((data) => {
setLocalEffectAnimationData(data);
});
}
}, [localEffectAnimation]);
const effectTgsUrl = useMedia(`sticker${effectAnimationId}`, !effectAnimationId);
if (!activeEmojiInteraction.startSize) {
return undefined;
}
const scale = (activeEmojiInteraction.startSize || 0) / EFFECT_SIZE;
const tgsUrl = localEffectAnimation && (localEffectAnimation in LOCAL_TGS_URLS)
? LOCAL_TGS_URLS[localEffectAnimation as keyof typeof LOCAL_TGS_URLS]
: effectTgsUrl;
return (
<div
@ -113,9 +106,8 @@ const EmojiInteractionAnimation: FC<OwnProps & StateProps> = ({
>
<AnimatedSticker
key={`effect_${effectAnimationId}`}
id={`effect_${effectAnimationId}`}
size={EFFECT_SIZE}
animationData={localEffectAnimationData || effectAnimationData}
tgsUrl={tgsUrl}
play={isPlaying}
quality={IS_ANDROID ? 0.5 : undefined}
forceOnHeavyAnimation
@ -133,7 +125,7 @@ export default memo(withGlobal<OwnProps>(
return {
effectAnimationId: animatedEffect ? animatedEffect.id : undefined,
localEffectAnimation: !animatedEffect && activeEmojiInteraction.animatedEffect
&& Object.keys(ANIMATED_STICKERS_PATHS).includes(activeEmojiInteraction.animatedEffect)
&& Object.keys(LOCAL_TGS_URLS).includes(activeEmojiInteraction.animatedEffect)
? activeEmojiInteraction.animatedEffect : undefined,
};
},

View File

@ -2,7 +2,6 @@ import type { FC } from '../../../lib/teact/teact';
import React, { memo, useMemo, useRef } from '../../../lib/teact/teact';
import type { ApiStickerSet } from '../../../api/types';
import { ApiMediaFormat } from '../../../api/types';
import { STICKER_SIZE_PICKER_HEADER } from '../../../config';
import type { ObserveFn } from '../../../hooks/useIntersectionObserver';
@ -30,7 +29,7 @@ const StickerSetCoverAnimated: FC<OwnProps> = ({
const isIntersecting = useIsIntersecting(ref, observeIntersection);
const mediaHash = `stickerSet${stickerSet.id}`;
const lottieData = useMedia(mediaHash, !isIntersecting, ApiMediaFormat.Lottie);
const lottieData = useMedia(mediaHash, !isIntersecting);
const transitionClassNames = useMediaTransition(lottieData);
const firstLetters = useMemo(() => {
@ -44,9 +43,8 @@ const StickerSetCoverAnimated: FC<OwnProps> = ({
{firstLetters}
{lottieData && (
<AnimatedSticker
id={mediaHash}
size={size}
animationData={lottieData}
tgsUrl={lottieData}
className={transitionClassNames}
/>
)}

View File

@ -1,13 +1,6 @@
.AnimatedEmoji {
cursor: pointer;
margin-bottom: 0.75rem;
img {
position: absolute;
width: 100%;
height: 100%;
}
&.like-sticker-thumb img {
transform: scale(0.8);
}

View File

@ -0,0 +1,112 @@
import type { FC } from '../../../lib/teact/teact';
import React, { memo } from '../../../lib/teact/teact';
import { withGlobal } from '../../../global';
import type { ApiSticker } from '../../../api/types';
import type { ActiveEmojiInteraction } from '../../../global/types';
import type { ObserveFn } from '../../../hooks/useIntersectionObserver';
import { LOCAL_TGS_URLS } from '../../common/helpers/animatedAssets';
import { LIKE_STICKER_ID } from '../../common/helpers/mediaDimensions';
import {
selectAnimatedEmoji,
selectAnimatedEmojiEffect,
selectAnimatedEmojiSound,
selectLocalAnimatedEmoji,
selectLocalAnimatedEmojiEffect,
} from '../../../global/selectors';
import buildClassName from '../../../util/buildClassName';
import { useIsIntersecting } from '../../../hooks/useIntersectionObserver';
import useAnimatedEmoji from '../../common/hooks/useAnimatedEmoji';
import AnimatedIconFromSticker from '../../common/AnimatedIconFromSticker';
import AnimatedIconWithPreview from '../../common/AnimatedIconWithPreview';
import './AnimatedEmoji.scss';
type OwnProps = {
emoji: string;
withEffects: boolean;
isOwn?: boolean;
observeIntersection?: ObserveFn;
size?: 'large' | 'medium' | 'small';
lastSyncTime?: number;
forceLoadPreview?: boolean;
messageId?: number;
chatId?: string;
activeEmojiInteractions?: ActiveEmojiInteraction[];
};
interface StateProps {
sticker?: ApiSticker;
effect?: ApiSticker;
localSticker?: keyof typeof LOCAL_TGS_URLS;
localEffect?: string;
soundId?: string;
}
const QUALITY = 1;
const AnimatedEmoji: FC<OwnProps & StateProps> = ({
isOwn,
observeIntersection,
lastSyncTime,
forceLoadPreview,
messageId,
chatId,
activeEmojiInteractions,
sticker,
effect,
localSticker,
localEffect,
soundId,
}) => {
const {
ref,
size,
style,
handleClick,
} = useAnimatedEmoji(chatId, messageId, soundId, activeEmojiInteractions, isOwn, localEffect, effect?.emoji);
const isIntersecting = useIsIntersecting(ref, observeIntersection);
return localSticker ? (
<AnimatedIconWithPreview
tgsUrl={LOCAL_TGS_URLS[localSticker]}
size={size}
quality={QUALITY}
play={isIntersecting}
forceOnHeavyAnimation
ref={ref}
className="AnimatedEmoji media-inner"
style={style}
onClick={handleClick}
/>
) : (
<AnimatedIconFromSticker
sticker={sticker}
size={size}
quality={QUALITY}
noLoad={!isIntersecting}
forcePreview={forceLoadPreview}
lastSyncTime={lastSyncTime}
play={isIntersecting}
forceOnHeavyAnimation
ref={ref}
className={buildClassName('AnimatedEmoji media-inner', sticker?.id === LIKE_STICKER_ID && 'like-sticker-thumb')}
style={style}
onClick={handleClick}
/>
);
};
export default memo(withGlobal<OwnProps>((global, { emoji, withEffects }) => {
const localSticker = selectLocalAnimatedEmoji(global, emoji);
return {
sticker: selectAnimatedEmoji(global, emoji),
effect: withEffects ? selectAnimatedEmojiEffect(global, emoji) : undefined,
soundId: selectAnimatedEmojiSound(global, emoji),
localSticker,
localEffect: localSticker && withEffects ? selectLocalAnimatedEmojiEffect(localSticker) : undefined,
};
})(AnimatedEmoji));

View File

@ -14,7 +14,6 @@ import type {
ApiMessageOutgoingStatus,
ApiUser,
ApiChat,
ApiSticker,
ApiThreadInfo,
ApiAvailableReaction,
} from '../../../api/types';
@ -33,7 +32,6 @@ import {
selectUser,
selectIsMessageFocused,
selectCurrentTextSearch,
selectAnimatedEmoji,
selectIsInSelectMode,
selectIsMessageSelected,
selectIsDocumentGroupSelected,
@ -47,14 +45,12 @@ import {
selectAllowedMessageActions,
selectIsDownloading,
selectThreadInfo,
selectAnimatedEmojiEffect,
selectAnimatedEmojiSound,
selectMessageIdsByGroupId,
selectLocalAnimatedEmoji,
selectIsMessageProtected,
selectLocalAnimatedEmojiEffect,
selectDefaultReaction,
selectReplySender,
selectAnimatedEmoji,
selectLocalAnimatedEmoji,
} from '../../../global/selectors';
import {
getMessageContent,
@ -102,7 +98,7 @@ import Audio from '../../common/Audio';
import MessageMeta from './MessageMeta';
import ContextMenuContainer from './ContextMenuContainer.async';
import Sticker from './Sticker';
import AnimatedEmoji from '../../common/AnimatedEmoji';
import AnimatedEmoji from './AnimatedEmoji';
import Photo from './Photo';
import Video from './Video';
import Contact from './Contact';
@ -117,7 +113,6 @@ import InlineButtons from './InlineButtons';
import CommentButton from './CommentButton';
import Reactions from './Reactions';
import ReactionStaticEmoji from '../../common/ReactionStaticEmoji';
import LocalAnimatedEmoji from '../../common/LocalAnimatedEmoji';
import MessagePhoneCall from './MessagePhoneCall';
import './Message.scss';
@ -176,12 +171,7 @@ type StateProps = {
lastSyncTime?: number;
serverTimeOffset: number;
highlight?: string;
isSingleEmoji?: boolean;
animatedEmoji?: ApiSticker;
localSticker?: string;
localEffect?: string;
animatedEmojiEffect?: ApiSticker;
animatedEmojiSoundId?: string;
animatedEmoji?: string;
isInSelectMode?: boolean;
isSelected?: boolean;
isGroupSelected?: boolean;
@ -264,10 +254,6 @@ const Message: FC<OwnProps & StateProps> = ({
serverTimeOffset,
highlight,
animatedEmoji,
localSticker,
localEffect,
animatedEmojiEffect,
animatedEmojiSoundId,
isInSelectMode,
isSelected,
isGroupSelected,
@ -338,7 +324,7 @@ const Message: FC<OwnProps & StateProps> = ({
const hasReply = isReplyMessage(message) && !shouldHideReply;
const hasThread = Boolean(threadInfo) && messageListType === 'thread';
const customShape = getMessageCustomShape(message);
const hasAnimatedEmoji = localSticker || animatedEmoji;
const hasAnimatedEmoji = animatedEmoji;
const hasReactions = reactionMessage?.reactions && !areReactionsEmpty(reactionMessage.reactions);
const asForwarded = (
forwardInfo
@ -662,26 +648,9 @@ const Message: FC<OwnProps & StateProps> = ({
)}
{animatedEmoji && (
<AnimatedEmoji
size="small"
emoji={animatedEmoji}
withEffects={isUserId(chatId)}
isOwn={isOwn}
sticker={animatedEmoji}
effect={animatedEmojiEffect}
soundId={animatedEmojiSoundId}
observeIntersection={observeIntersectionForMedia}
lastSyncTime={lastSyncTime}
forceLoadPreview={isLocal}
messageId={messageId}
chatId={chatId}
activeEmojiInteractions={activeEmojiInteractions}
/>
)}
{localSticker && (
<LocalAnimatedEmoji
size="small"
isOwn={isOwn}
localSticker={localSticker}
localEffect={localEffect}
soundId={animatedEmojiSoundId}
observeIntersection={observeIntersectionForMedia}
lastSyncTime={lastSyncTime}
forceLoadPreview={isLocal}
@ -1057,8 +1026,11 @@ export default memo(withGlobal<OwnProps>(
const { query: highlight } = selectCurrentTextSearch(global) || {};
const singleEmoji = getMessageSingleEmoji(message);
let isSelected: boolean;
const animatedEmoji = singleEmoji && (
selectAnimatedEmoji(global, singleEmoji) || selectLocalAnimatedEmoji(global, singleEmoji)
) ? singleEmoji : undefined;
let isSelected: boolean;
if (album?.messages) {
isSelected = album.messages.every(({ id: messageId }) => selectIsMessageSelected(global, messageId));
} else {
@ -1079,8 +1051,6 @@ export default memo(withGlobal<OwnProps>(
isLastInDocumentGroup ? selectChatMessage(global, chatId, documentGroupFirstMessageId!) : undefined
) : message;
const localSticker = singleEmoji ? selectLocalAnimatedEmoji(global, singleEmoji) : undefined;
const hasUnreadReaction = chat?.unreadReactions?.includes(message.id);
return {
@ -1107,12 +1077,7 @@ export default memo(withGlobal<OwnProps>(
lastSyncTime,
serverTimeOffset,
highlight,
isSingleEmoji: Boolean(singleEmoji),
animatedEmoji: singleEmoji ? selectAnimatedEmoji(global, singleEmoji) : undefined,
animatedEmojiEffect: singleEmoji && isUserId(chatId) ? selectAnimatedEmojiEffect(global, singleEmoji) : undefined,
animatedEmojiSoundId: singleEmoji ? selectAnimatedEmojiSound(global, singleEmoji) : undefined,
localSticker,
localEffect: localSticker && isUserId(chatId) ? selectLocalAnimatedEmojiEffect(localSticker) : undefined,
animatedEmoji,
isInSelectMode: selectIsInSelectMode(global),
isSelected,
isGroupSelected: (

View File

@ -4,7 +4,6 @@ import { getActions } from '../../../global';
import type { ActiveReaction } from '../../../global/types';
import type { ApiAvailableReaction } from '../../../api/types';
import { ApiMediaFormat } from '../../../api/types';
import buildClassName from '../../../util/buildClassName';
import useMedia from '../../../hooks/useMedia';
@ -37,8 +36,8 @@ const ReactionAnimatedEmoji: FC<OwnProps> = ({
const availableReaction = availableReactions?.find((r) => r.reaction === reaction);
const centerIconId = availableReaction?.centerIcon?.id;
const effectId = availableReaction?.aroundAnimation?.id;
const mediaDataCenterIcon = useMedia(`sticker${centerIconId}`, !centerIconId, ApiMediaFormat.Lottie);
const mediaDataEffect = useMedia(`sticker${effectId}`, !effectId, ApiMediaFormat.Lottie);
const mediaDataCenterIcon = useMedia(`sticker${centerIconId}`, !centerIconId);
const mediaDataEffect = useMedia(`sticker${effectId}`, !effectId);
const shouldPlay = Boolean(activeReaction?.reaction === reaction && mediaDataCenterIcon && mediaDataEffect);
const {
@ -66,10 +65,9 @@ const ReactionAnimatedEmoji: FC<OwnProps> = ({
<>
<AnimatedSticker
key={centerIconId}
id={`reaction_emoji_${centerIconId}`}
className={animationClassNames}
size={CENTER_ICON_SIZE}
animationData={mediaDataCenterIcon}
tgsUrl={mediaDataCenterIcon}
play
noLoop
forceOnHeavyAnimation
@ -78,10 +76,9 @@ const ReactionAnimatedEmoji: FC<OwnProps> = ({
/>
<AnimatedSticker
key={effectId}
id={`reaction_effect_${effectId}`}
className={buildClassName('effect', animationClassNames)}
size={EFFECT_SIZE}
animationData={mediaDataEffect}
tgsUrl={mediaDataEffect}
play
noLoop
forceOnHeavyAnimation

View File

@ -2,7 +2,6 @@ import type { FC } from '../../../lib/teact/teact';
import React, { memo, useRef } from '../../../lib/teact/teact';
import type { ApiAvailableReaction } from '../../../api/types';
import { ApiMediaFormat } from '../../../api/types';
import useMedia from '../../../hooks/useMedia';
import useFlag from '../../../hooks/useFlag';
@ -31,7 +30,7 @@ const ReactionSelectorReaction: FC<OwnProps> = ({
// eslint-disable-next-line no-null/no-null
const containerRef = useRef<HTMLDivElement>(null);
const mediaData = useMedia(`document${reaction.selectAnimation?.id}`, !isReady, ApiMediaFormat.Lottie);
const mediaData = useMedia(`document${reaction.selectAnimation?.id}`, !isReady);
const [isActivated, activate, deactivate] = useFlag();
const [isAnimationLoaded, markAnimationLoaded] = useFlag();
@ -67,9 +66,8 @@ const ReactionSelectorReaction: FC<OwnProps> = ({
)}
{shouldRenderAnimated && (
<AnimatedSticker
id={`select_${reaction.reaction}`}
className={cn('animated', [animatedClassNames])}
animationData={mediaData}
tgsUrl={mediaData}
play={isActivated}
noLoop
size={REACTION_SIZE}

View File

@ -118,8 +118,7 @@ const Sticker: FC<OwnProps> = ({
<AnimatedSticker
key={mediaHash}
className={buildClassName('full-media', transitionClassNames)}
id={mediaHash}
animationData={mediaData}
tgsUrl={mediaData}
size={width}
play={shouldPlay}
noLoop={!shouldLoop}

View File

@ -8,20 +8,20 @@ import type { ApiChat } from '../../../api/types';
import { ManagementScreens } from '../../../types';
import { STICKER_SIZE_DISCUSSION_GROUPS } from '../../../config';
import { LOCAL_TGS_URLS } from '../../common/helpers/animatedAssets';
import { selectChat } from '../../../global/selectors';
import getAnimationData from '../../common/helpers/animatedAssets';
import useLang from '../../../hooks/useLang';
import useHistoryBack from '../../../hooks/useHistoryBack';
import ListItem from '../../ui/ListItem';
import NothingFound from '../../common/NothingFound';
import GroupChatInfo from '../../common/GroupChatInfo';
import AnimatedSticker from '../../common/AnimatedSticker';
import ConfirmDialog from '../../ui/ConfirmDialog';
import useFlag from '../../../hooks/useFlag';
import renderText from '../../common/helpers/renderText';
import Avatar from '../../common/Avatar';
import { isChatChannel } from '../../../global/helpers';
import AnimatedIcon from '../../common/AnimatedIcon';
type OwnProps = {
chatId: string;
@ -56,9 +56,6 @@ const ManageDiscussion: FC<OwnProps & StateProps> = ({
} = getActions();
const [linkedGroupId, setLinkedGroupId] = useState<string>();
const [animationData, setAnimationData] = useState<string>();
const [isAnimationLoaded, setIsAnimationLoaded] = useState(false);
const handleAnimationLoad = useCallback(() => setIsAnimationLoaded(true), []);
const [isConfirmUnlinkGroupDialogOpen, openConfirmUnlinkGroupDialog, closeConfirmUnlinkGroupDialog] = useFlag();
const [isConfirmLinkGroupDialogOpen, openConfirmLinkGroupDialog, closeConfirmLinkGroupDialog] = useFlag();
const lang = useLang();
@ -73,12 +70,6 @@ const ManageDiscussion: FC<OwnProps & StateProps> = ({
loadGroupsForDiscussion();
}, [loadGroupsForDiscussion]);
useEffect(() => {
if (!animationData) {
getAnimationData('DiscussionGroups').then(setAnimationData);
}
}, [animationData]);
const handleUnlinkGroupSessions = useCallback(() => {
closeConfirmUnlinkGroupDialog();
unlinkDiscussionGroup({ channelId: isChannel ? chatId : linkedChatId });
@ -143,10 +134,6 @@ const ManageDiscussion: FC<OwnProps & StateProps> = ({
`Do you want to make **${linkedGroup.title}** the discussion board for **${chat!.title}**?`,
['br', 'simple_markdown'],
);
// return renderText(
// lang('DiscussionLinkGroupPublicAlert', linkedChat.title, chat!.title),
// ['br', 'simple_markdown'],
// );
}
return renderText(
@ -154,10 +141,6 @@ const ManageDiscussion: FC<OwnProps & StateProps> = ({
`Do you want to make **${linkedGroup.title}** the discussion board for **${chat!.title}**?\n\nAnyone from the channel will be able to see messages in this group.`,
['br', 'simple_markdown'],
);
// return renderText(
// lang('DiscussionLinkGroupPrivateAlert', linkedChat.title, chat!.title),
// ['br', 'simple_markdown'],
// );
}
function renderLinkedGroup() {
@ -244,18 +227,11 @@ const ManageDiscussion: FC<OwnProps & StateProps> = ({
<div className="Management">
<div className="custom-scroll">
<div className="section">
<div className="section-icon">
{animationData && (
<AnimatedSticker
id="discussionGroupsDucks"
size={STICKER_SIZE_DISCUSSION_GROUPS}
animationData={animationData}
play={isAnimationLoaded}
noLoop
onLoad={handleAnimationLoad}
/>
)}
</div>
<AnimatedIcon
tgsUrl={LOCAL_TGS_URLS.DiscussionGroups}
size={STICKER_SIZE_DISCUSSION_GROUPS}
className="section-icon"
/>
{linkedChat && renderLinkedGroup()}
{!linkedChat && renderDiscussionGroups()}
</div>

View File

@ -1,6 +1,6 @@
import type { FC } from '../../../lib/teact/teact';
import React, {
memo, useCallback, useEffect, useMemo, useState,
memo, useCallback, useMemo, useState,
} from '../../../lib/teact/teact';
import { getActions, withGlobal } from '../../../global';
@ -8,7 +8,7 @@ import type { ApiChat, ApiExportedInvite } from '../../../api/types';
import { ManagementScreens } from '../../../types';
import { STICKER_SIZE_INVITES } from '../../../config';
import getAnimationData from '../../common/helpers/animatedAssets';
import { LOCAL_TGS_URLS } from '../../common/helpers/animatedAssets';
import useHistoryBack from '../../../hooks/useHistoryBack';
import useLang from '../../../hooks/useLang';
import { formatCountdown, MILLISECONDS_IN_DAY } from '../../../util/dateFormat';
@ -27,7 +27,7 @@ import Button from '../../ui/Button';
import DropdownMenu from '../../ui/DropdownMenu';
import MenuItem from '../../ui/MenuItem';
import ConfirmDialog from '../../ui/ConfirmDialog';
import AnimatedSticker from '../../common/AnimatedSticker';
import AnimatedIcon from '../../common/AnimatedIcon';
type OwnProps = {
chatId: string;
@ -82,16 +82,6 @@ const ManageInvites: FC<OwnProps & StateProps> = ({
const [isDeleteDialogOpen, openDeleteDialog, closeDeleteDialog] = useFlag();
const [deletingInvite, setDeletingInvite] = useState<ApiExportedInvite | undefined>();
const [animationData, setAnimationData] = useState<string>();
const [isAnimationLoaded, setIsAnimationLoaded] = useState(false);
const handleAnimationLoad = useCallback(() => setIsAnimationLoaded(true), []);
useEffect(() => {
if (!animationData) {
getAnimationData('Invite').then(setAnimationData);
}
}, [animationData]);
useHistoryBack({
isActive,
onBack: onClose,
@ -288,17 +278,11 @@ const ManageInvites: FC<OwnProps & StateProps> = ({
<div className="Management ManageInvites">
<div className="custom-scroll">
<div className="section">
<div className="section-icon">
{animationData && (
<AnimatedSticker
id="inviteDuck"
size={STICKER_SIZE_INVITES}
animationData={animationData}
play={isAnimationLoaded}
onLoad={handleAnimationLoad}
/>
)}
</div>
<AnimatedIcon
tgsUrl={LOCAL_TGS_URLS.Invite}
size={STICKER_SIZE_INVITES}
className="section-icon"
/>
<p className="text-muted">{isChannel ? lang('PrimaryLinkHelpChannel') : lang('PrimaryLinkHelp')}</p>
</div>
{primaryInviteLink && (

View File

@ -1,24 +1,22 @@
import type { FC } from '../../../lib/teact/teact';
import React, {
memo, useCallback, useEffect, useState,
} from '../../../lib/teact/teact';
import React, { memo, useCallback, useEffect } from '../../../lib/teact/teact';
import { getActions, withGlobal } from '../../../global';
import type { ApiChat } from '../../../api/types';
import { STICKER_SIZE_JOIN_REQUESTS } from '../../../config';
import { LOCAL_TGS_URLS } from '../../common/helpers/animatedAssets';
import useHistoryBack from '../../../hooks/useHistoryBack';
import { selectChat } from '../../../global/selectors';
import { isChatChannel, isUserId } from '../../../global/helpers';
import useLang from '../../../hooks/useLang';
import useFlag from '../../../hooks/useFlag';
import getAnimationData from '../../common/helpers/animatedAssets';
import JoinRequest from './JoinRequest';
import Button from '../../ui/Button';
import ConfirmDialog from '../../ui/ConfirmDialog';
import AnimatedSticker from '../../common/AnimatedSticker';
import Spinner from '../../ui/Spinner';
import AnimatedIcon from '../../common/AnimatedIcon';
type OwnProps = {
chatId: string;
@ -45,16 +43,6 @@ const ManageJoinRequests: FC<OwnProps & StateProps> = ({
const lang = useLang();
const [animationData, setAnimationData] = useState<string>();
const [isAnimationLoaded, setIsAnimationLoaded] = useState(false);
const handleAnimationLoad = useCallback(() => setIsAnimationLoaded(true), []);
useEffect(() => {
if (!animationData) {
getAnimationData('JoinRequest').then(setAnimationData);
}
}, [animationData]);
useHistoryBack({
isActive,
onBack: onClose,
@ -80,17 +68,11 @@ const ManageJoinRequests: FC<OwnProps & StateProps> = ({
<div className="Management ManageJoinRequests">
<div className="custom-scroll">
<div className="section">
<div className="section-icon">
{animationData && (
<AnimatedSticker
id="joinRequestDucks"
size={STICKER_SIZE_JOIN_REQUESTS}
animationData={animationData}
play={isAnimationLoaded}
onLoad={handleAnimationLoad}
/>
)}
</div>
<AnimatedIcon
tgsUrl={LOCAL_TGS_URLS.JoinRequest}
size={STICKER_SIZE_JOIN_REQUESTS}
className="section-icon"
/>
{Boolean(chat?.joinRequests?.length) && (
<div className="bulk-actions">
<Button className="bulk-action-button" onClick={openAcceptAllDialog}>Accept all</Button>

View File

@ -36,22 +36,7 @@
}
.section-icon {
width: 8.75rem;
height: 8.75rem;
margin: 0 auto 2rem;
position: relative;
.AnimatedSticker {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
&.hidden {
display: none;
}
}
}
.ListItem {

View File

@ -31,10 +31,10 @@ addActionHandler('loadAvailableReactions', async () => {
// Preload animations
result.forEach((availableReaction) => {
if (availableReaction.aroundAnimation) {
mediaLoader.fetch(`sticker${availableReaction.aroundAnimation.id}`, ApiMediaFormat.Lottie);
mediaLoader.fetch(`sticker${availableReaction.aroundAnimation.id}`, ApiMediaFormat.BlobUrl);
}
if (availableReaction.centerIcon) {
mediaLoader.fetch(`sticker${availableReaction.centerIcon.id}`, ApiMediaFormat.Lottie);
mediaLoader.fetch(`sticker${availableReaction.centerIcon.id}`, ApiMediaFormat.BlobUrl);
}
});

View File

@ -302,15 +302,10 @@ export function getAudioHasCover(media: ApiAudio) {
export function getMessageMediaFormat(
message: ApiMessage, target: Target,
): ApiMediaFormat {
const {
sticker, video, audio, voice,
} = message.content;
const { video, audio, voice } = message.content;
const fullVideo = video || getMessageWebPageVideo(message);
if (sticker && target === 'inline' && sticker.isLottie) {
return ApiMediaFormat.Lottie;
} else if (fullVideo && IS_PROGRESSIVE_SUPPORTED && (
if (fullVideo && IS_PROGRESSIVE_SUPPORTED && (
target === 'viewerFull' || target === 'inline'
)) {
return ApiMediaFormat.Progressive;

View File

@ -7,6 +7,7 @@ import {
import WorkerConnector from '../../util/WorkerConnector';
import { animate } from '../../util/animation';
import cycleRestrict from '../../util/cycleRestrict';
import generateIdFor from '../../util/generateIdFor';
interface Params {
noLoop?: boolean;
@ -25,6 +26,7 @@ const HIGH_PRIORITY_QUALITY = IS_SINGLE_COLUMN_LAYOUT ? 0.75 : 1;
const LOW_PRIORITY_QUALITY = IS_ANDROID ? 0.5 : 0.75;
const HIGH_PRIORITY_CACHE_MODULO = IS_SAFARI ? 2 : 4;
const LOW_PRIORITY_CACHE_MODULO = 0;
const KEY_STORE = {};
const workers = new Array(MAX_WORKERS).fill(undefined).map(
() => new WorkerConnector(new Worker(new URL('./rlottie.worker.ts', import.meta.url))),
@ -36,7 +38,7 @@ class RLottie {
private imgSize!: number;
private key!: string;
private key = generateIdFor(KEY_STORE);
private msPerFrame = 1000 / 60;
@ -66,6 +68,8 @@ class RLottie {
private isWaiting = true;
private isEnded = false;
private isOnLoadFired = false;
private isDestroyed = false;
@ -83,9 +87,8 @@ class RLottie {
private lastRenderAt?: number;
constructor(
private id: string,
private container: HTMLDivElement,
private animationData: string,
private tgsUrl: string,
private params: Params = {},
private onLoad?: () => void,
private customColor?: [number, number, number],
@ -100,7 +103,11 @@ class RLottie {
return this.isAnimating || this.isWaiting;
}
play() {
play(forceRestart = false) {
if (this.isEnded && forceRestart) {
this.approxFrameIndex = Math.floor(0);
}
this.stopFrameIndex = undefined;
this.direction = 1;
this.doPlay();
@ -117,20 +124,6 @@ class RLottie {
this.chunks = this.chunks.map((chunk, i) => (i === currentChunkIndex ? chunk : undefined));
}
goToAndPlay(frameIndex: number) {
this.approxFrameIndex = Math.floor(frameIndex / this.reduceFactor);
this.stopFrameIndex = undefined;
this.direction = 1;
this.doPlay();
}
goToAndStop(frameIndex: number) {
this.approxFrameIndex = Math.floor(frameIndex / this.reduceFactor);
this.stopFrameIndex = Math.floor(frameIndex / this.reduceFactor);
this.direction = 1;
this.doPlay();
}
playSegment([startFrameIndex, stopFrameIndex]: [number, number]) {
this.approxFrameIndex = Math.floor(startFrameIndex / this.reduceFactor);
this.stopFrameIndex = Math.floor(stopFrameIndex / this.reduceFactor);
@ -184,8 +177,6 @@ class RLottie {
}
private initConfig() {
this.key = `${this.id}_${this.imgSize}`;
const { isLowPriority } = this.params;
this.cacheModulo = isLowPriority ? LOW_PRIORITY_CACHE_MODULO : HIGH_PRIORITY_CACHE_MODULO;
@ -221,7 +212,7 @@ class RLottie {
name: 'init',
args: [
this.key,
this.animationData,
this.tgsUrl,
this.imgSize,
this.params.isLowPriority,
this.onRendererInit.bind(this),
@ -247,16 +238,16 @@ class RLottie {
}
}
changeData(animationData: string) {
changeData(tgsUrl: string) {
this.pause();
this.animationData = animationData;
this.tgsUrl = tgsUrl;
this.initConfig();
workers[this.workerIndex].request({
name: 'changeData',
args: [
this.key,
this.animationData,
this.tgsUrl,
this.params.isLowPriority,
this.onChangeData.bind(this),
],
@ -291,6 +282,7 @@ class RLottie {
this.lastRenderAt = undefined;
}
this.isEnded = false;
this.isAnimating = true;
this.isWaiting = false;
@ -359,6 +351,7 @@ class RLottie {
if (delta > 0 && (frameIndex === this.framesCount! - 1 || expectedNextFrameIndex > this.framesCount! - 1)) {
if (this.params.noLoop) {
this.isAnimating = false;
this.isEnded = true;
this.onEnded?.();
return false;
}
@ -369,6 +362,7 @@ class RLottie {
} else if (delta < 0 && (frameIndex === 0 || expectedNextFrameIndex < 0)) {
if (this.params.noLoop) {
this.isAnimating = false;
this.isEnded = true;
this.onEnded?.();
return false;
}
@ -379,10 +373,10 @@ class RLottie {
} else if (
this.stopFrameIndex !== undefined
&& (frameIndex === this.stopFrameIndex
|| (
(delta > 0 && expectedNextFrameIndex > this.stopFrameIndex)
|| (delta < 0 && expectedNextFrameIndex < this.stopFrameIndex)
))
|| (
(delta > 0 && expectedNextFrameIndex > this.stopFrameIndex)
|| (delta < 0 && expectedNextFrameIndex < this.stopFrameIndex)
))
) {
this.stopFrameIndex = undefined;
this.isAnimating = false;

View File

@ -39,7 +39,7 @@ const renderers = new Map<string, {
async function init(
key: string,
animationData: string,
tgsUrl: string,
imgSize: number,
isLowPriority: boolean,
onInit: CancellableCallback,
@ -48,7 +48,7 @@ async function init(
await rLottieApiPromise;
}
const json = await extractJson(animationData);
const json = await extractJson(tgsUrl);
const stringOnWasmHeap = allocate(intArrayFromString(json), 'i8', 0);
const handle = rLottieApi.init();
const framesCount = rLottieApi.loadFromData(handle, stringOnWasmHeap);
@ -62,7 +62,7 @@ async function init(
async function changeData(
key: string,
animationData: string,
tgsUrl: string,
isLowPriority: boolean,
onInit: CancellableCallback,
) {
@ -70,7 +70,7 @@ async function changeData(
await rLottieApiPromise;
}
const json = await extractJson(animationData);
const json = await extractJson(tgsUrl);
const stringOnWasmHeap = allocate(intArrayFromString(json), 'i8', 0);
const { handle } = renderers.get(key)!;
const framesCount = rLottieApi.loadFromData(handle, stringOnWasmHeap);
@ -79,8 +79,8 @@ async function changeData(
onInit(reduceFactor, msPerFrame, reducedFramesCount);
}
async function extractJson(animationData: string) {
const response = await fetch(animationData);
async function extractJson(tgsUrl: string) {
const response = await fetch(tgsUrl);
const contentType = response.headers.get('Content-Type');
// Support deprecated JSON format cached locally

View File

@ -6,7 +6,7 @@ import { pause } from './util/schedulers';
declare const self: ServiceWorkerGlobalScope;
const ASSET_CACHE_PATTERN = /[0-9a-f]{20}.*\.(js|css|woff2?|svg|png|jpg|jpeg|json|wasm)$/;
const ASSET_CACHE_PATTERN = /[\da-f]{20}.*\.(js|css|woff2?|svg|png|jpg|jpeg|tgs|json|wasm)$/;
const ACTIVATE_TIMEOUT = 3000;
self.addEventListener('install', (e) => {

View File

@ -19,7 +19,6 @@ import { webpToPng } from './webpToPng';
const asCacheApiType = {
[ApiMediaFormat.BlobUrl]: cacheApi.Type.Blob,
[ApiMediaFormat.Lottie]: cacheApi.Type.Blob,
[ApiMediaFormat.Text]: cacheApi.Type.Text,
[ApiMediaFormat.Progressive]: undefined,
[ApiMediaFormat.Stream]: undefined,