[Refactoring] Animated icons (#1897)
This commit is contained in:
parent
7997dff459
commit
05ca268f6b
@ -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 {
|
||||
|
||||
@ -3,7 +3,6 @@
|
||||
|
||||
export enum ApiMediaFormat {
|
||||
BlobUrl,
|
||||
Lottie,
|
||||
Progressive,
|
||||
Stream,
|
||||
Text,
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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}
|
||||
/>
|
||||
|
||||
@ -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}
|
||||
/>
|
||||
|
||||
@ -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}
|
||||
|
||||
@ -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')}
|
||||
/>
|
||||
|
||||
@ -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);
|
||||
@ -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);
|
||||
|
||||
41
src/components/common/AnimatedIconFromSticker.tsx
Normal file
41
src/components/common/AnimatedIconFromSticker.tsx
Normal 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);
|
||||
11
src/components/common/AnimatedIconWithPreview.module.scss
Normal file
11
src/components/common/AnimatedIconWithPreview.module.scss
Normal file
@ -0,0 +1,11 @@
|
||||
.root {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.preview {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
45
src/components/common/AnimatedIconWithPreview.tsx
Normal file
45
src/components/common/AnimatedIconWithPreview.tsx
Normal 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);
|
||||
@ -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}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@ -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);
|
||||
@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
@ -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')}
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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}>
|
||||
|
||||
@ -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')}
|
||||
|
||||
@ -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!
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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')}
|
||||
|
||||
@ -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">
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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">
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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,
|
||||
};
|
||||
},
|
||||
|
||||
@ -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}
|
||||
/>
|
||||
)}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
112
src/components/middle/message/AnimatedEmoji.tsx
Normal file
112
src/components/middle/message/AnimatedEmoji.tsx
Normal 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));
|
||||
@ -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: (
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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}
|
||||
|
||||
@ -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}
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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 && (
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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) => {
|
||||
|
||||
@ -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,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user