161 lines
4.1 KiB
TypeScript
161 lines
4.1 KiB
TypeScript
import type { FC } from '../../../../lib/teact/teact';
|
|
import React, {
|
|
memo, useCallback, useEffect, useRef, useState,
|
|
} from '../../../../lib/teact/teact';
|
|
import { withGlobal } from '../../../../global';
|
|
|
|
import type { GlobalState } from '../../../../global/types';
|
|
import type { ApiSticker } from '../../../../api/types';
|
|
|
|
import cycleRestrict from '../../../../util/cycleRestrict';
|
|
import useMedia from '../../../../hooks/useMedia';
|
|
import useFlag from '../../../../hooks/useFlag';
|
|
|
|
import AnimatedSticker from '../../../common/AnimatedSticker';
|
|
|
|
import styles from './PremiumFeaturePreviewStickers.module.scss';
|
|
|
|
type OwnProps = {
|
|
isActive: boolean;
|
|
};
|
|
|
|
type StateProps = {
|
|
stickers: GlobalState['stickers']['premium']['stickers'];
|
|
};
|
|
|
|
const EMOJI_SIZE_MULTIPLIER = 0.6;
|
|
const EFFECT_SIZE_MULTIPLIER = 0.8;
|
|
const MAX_EMOJIS = 15;
|
|
const ENDED_DELAY = 150;
|
|
|
|
const AnimatedCircleSticker: FC<{
|
|
size: number;
|
|
realIndex: number;
|
|
sticker: ApiSticker;
|
|
index: number;
|
|
maxLength: number;
|
|
onClick: (index: number) => void;
|
|
onEnded: NoneToVoidFunction;
|
|
canPlay: boolean;
|
|
}> = ({
|
|
size, realIndex, canPlay,
|
|
sticker, index, maxLength, onClick, onEnded,
|
|
}) => {
|
|
const mediaData = useMedia(`sticker${sticker.id}`);
|
|
const mediaDataAround = useMedia(`sticker${sticker.id}?size=f`);
|
|
|
|
const isActivated = index === 0;
|
|
const [isAnimated, animate, inanimate] = useFlag(isActivated);
|
|
|
|
const circleSize = size - size * EMOJI_SIZE_MULTIPLIER;
|
|
const width = circleSize * 3;
|
|
const height = circleSize * 3.2;
|
|
|
|
const a = index / maxLength;
|
|
|
|
const angle = a * (Math.PI * 2);
|
|
const scale = isActivated ? 1 : 0.66;
|
|
|
|
const x = Math.cos(angle) * width - circleSize * 2.8;
|
|
const y = Math.sin(angle) * height;
|
|
|
|
const handleClick = useCallback(() => {
|
|
onClick(realIndex);
|
|
}, [onClick, realIndex]);
|
|
|
|
const handleEnded = useCallback(() => {
|
|
inanimate();
|
|
onEnded();
|
|
}, [inanimate, onEnded]);
|
|
|
|
useEffect(() => {
|
|
if (isActivated) {
|
|
animate();
|
|
}
|
|
}, [isActivated, animate]);
|
|
|
|
return (
|
|
<>
|
|
{isActivated && (
|
|
<AnimatedSticker
|
|
className={styles.effectSticker}
|
|
tgsUrl={mediaDataAround}
|
|
play={canPlay}
|
|
isLowPriority
|
|
noLoop
|
|
size={EFFECT_SIZE_MULTIPLIER * size}
|
|
style={`--x: calc(${x}px - 10%); --y: ${y}px;`}
|
|
/>
|
|
)}
|
|
<AnimatedSticker
|
|
className={styles.sticker}
|
|
tgsUrl={mediaData}
|
|
play={canPlay && isAnimated}
|
|
noLoop
|
|
size={EMOJI_SIZE_MULTIPLIER * size}
|
|
style={`--x: ${x}px; --y: ${y}px; --opacity: ${scale}`}
|
|
onClick={handleClick}
|
|
onEnded={handleEnded}
|
|
/>
|
|
</>
|
|
);
|
|
};
|
|
|
|
const PremiumFeaturePreviewStickers: FC<OwnProps & StateProps> = ({
|
|
stickers, isActive,
|
|
}) => {
|
|
// eslint-disable-next-line no-null/no-null
|
|
const containerRef = useRef<HTMLDivElement>(null);
|
|
const [offset, setOffset] = useState(0);
|
|
const [size, setSize] = useState(0);
|
|
|
|
const renderedStickers = stickers?.slice(0, MAX_EMOJIS);
|
|
|
|
const handleClick = useCallback((i: number) => {
|
|
setOffset(-i);
|
|
}, []);
|
|
|
|
const handleEnded = useCallback(() => {
|
|
setTimeout(() => {
|
|
setOffset((current) => cycleRestrict(renderedStickers.length, current + 1));
|
|
}, ENDED_DELAY);
|
|
}, [renderedStickers.length]);
|
|
|
|
useEffect(() => {
|
|
const container = containerRef.current;
|
|
if (!container) return;
|
|
|
|
setSize(container.closest('.modal-dialog')!.clientWidth);
|
|
}, []);
|
|
|
|
return (
|
|
<div
|
|
className={styles.root}
|
|
ref={containerRef}
|
|
>
|
|
{Boolean(size) && renderedStickers?.map((l, i) => {
|
|
return (
|
|
<AnimatedCircleSticker
|
|
size={size}
|
|
sticker={l}
|
|
realIndex={i}
|
|
index={(i + offset + renderedStickers.length) % renderedStickers.length}
|
|
maxLength={renderedStickers.length}
|
|
onClick={handleClick}
|
|
onEnded={handleEnded}
|
|
canPlay={isActive}
|
|
/>
|
|
);
|
|
})}
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default memo(withGlobal<OwnProps>(
|
|
(global): StateProps => {
|
|
return {
|
|
stickers: global.stickers.premium.stickers,
|
|
};
|
|
},
|
|
)(PremiumFeaturePreviewStickers));
|