InteractiveSparkles: Support in modals (#6120)
This commit is contained in:
parent
88ebd32335
commit
922fc7da35
@ -89,6 +89,7 @@ type OwnProps = {
|
||||
observeIntersection?: ObserveFn;
|
||||
onClick?: (e: ReactMouseEvent<HTMLDivElement, MouseEvent>, hasMedia: boolean) => void;
|
||||
onContextMenu?: (e: React.MouseEvent) => void;
|
||||
onMouseMove?: (e: React.MouseEvent) => void;
|
||||
};
|
||||
|
||||
const Avatar: FC<OwnProps> = ({
|
||||
@ -115,6 +116,7 @@ const Avatar: FC<OwnProps> = ({
|
||||
asMessageBubble,
|
||||
onClick,
|
||||
onContextMenu,
|
||||
onMouseMove,
|
||||
}) => {
|
||||
const { openStoryViewer } = getActions();
|
||||
|
||||
@ -304,6 +306,7 @@ const Avatar: FC<OwnProps> = ({
|
||||
onClick={handleClick}
|
||||
onContextMenu={onContextMenu}
|
||||
onMouseDown={handleMouseDown}
|
||||
onMouseMove={onMouseMove}
|
||||
>
|
||||
<div className="inner">
|
||||
{typeof content === 'string' ? renderText(content, [isBig ? 'hq_emoji' : 'emoji']) : content}
|
||||
|
||||
5
src/components/common/InteractiveSparkles.module.scss
Normal file
5
src/components/common/InteractiveSparkles.module.scss
Normal file
@ -0,0 +1,5 @@
|
||||
.sparkles {
|
||||
pointer-events: auto;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
}
|
||||
62
src/components/common/InteractiveSparkles.tsx
Normal file
62
src/components/common/InteractiveSparkles.tsx
Normal file
@ -0,0 +1,62 @@
|
||||
import { memo, useEffect, useLayoutEffect, useRef } from '../../lib/teact/teact';
|
||||
|
||||
import buildClassName from '../../util/buildClassName';
|
||||
import { PARTICLE_BURST_PARAMS, PARTICLE_COLORS, setupParticles } from '../../util/particles';
|
||||
|
||||
import styles from './InteractiveSparkles.module.scss';
|
||||
|
||||
interface OwnProps {
|
||||
color?: 'purple' | 'gold' | 'blue';
|
||||
centerShift?: readonly [number, number];
|
||||
isDisabled?: boolean;
|
||||
className?: string;
|
||||
onRequestAnimation?: (animate: NoneToVoidFunction) => void;
|
||||
}
|
||||
|
||||
const DEFAULT_PARTICLE_PARAMS = {
|
||||
centerShift: [0, -36] as const,
|
||||
};
|
||||
|
||||
const InteractiveSparkles = ({
|
||||
color = 'purple',
|
||||
centerShift = DEFAULT_PARTICLE_PARAMS.centerShift,
|
||||
isDisabled,
|
||||
className,
|
||||
onRequestAnimation,
|
||||
}: OwnProps) => {
|
||||
const canvasRef = useRef<HTMLCanvasElement>();
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (isDisabled) return undefined;
|
||||
|
||||
return setupParticles(canvasRef.current!, {
|
||||
color: PARTICLE_COLORS[`${color}Gradient`],
|
||||
centerShift,
|
||||
});
|
||||
}, [centerShift, color, isDisabled]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!onRequestAnimation) return;
|
||||
|
||||
const animate = () => {
|
||||
if (isDisabled) return;
|
||||
|
||||
setupParticles(canvasRef.current!, {
|
||||
color: PARTICLE_COLORS[`${color}Gradient`],
|
||||
centerShift,
|
||||
...PARTICLE_BURST_PARAMS,
|
||||
});
|
||||
};
|
||||
|
||||
onRequestAnimation(animate);
|
||||
}, [centerShift, color, isDisabled, onRequestAnimation]);
|
||||
|
||||
return (
|
||||
<canvas
|
||||
ref={canvasRef}
|
||||
className={buildClassName(styles.sparkles, className)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(InteractiveSparkles);
|
||||
@ -1,14 +1,14 @@
|
||||
import type { TeactNode } from '@teact';
|
||||
import { memo, useLayoutEffect, useRef } from '@teact';
|
||||
import { memo, useRef } from '@teact';
|
||||
|
||||
import type { ApiSticker } from '../../../api/types';
|
||||
|
||||
import buildClassName from '../../../util/buildClassName';
|
||||
import { PARTICLE_BURST_PARAMS, PARTICLE_COLORS, setupParticles } from '../../../util/particles.ts';
|
||||
import { REM } from '../../common/helpers/mediaDimensions';
|
||||
|
||||
import useLastCallback from '../../../hooks/useLastCallback.ts';
|
||||
|
||||
import InteractiveSparkles from '../../common/InteractiveSparkles';
|
||||
import StickerView from '../../common/StickerView';
|
||||
import SpeedingDiamond from './SpeedingDiamond.tsx';
|
||||
import SwayingStar from './SwayingStar.tsx';
|
||||
@ -40,29 +40,26 @@ function ParticlesHeader({
|
||||
isDisabled,
|
||||
className,
|
||||
}: OwnProps) {
|
||||
const canvasRef = useRef<HTMLCanvasElement>();
|
||||
const stickerRef = useRef<HTMLDivElement>();
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (isDisabled) return undefined;
|
||||
|
||||
return setupParticles(canvasRef.current!, {
|
||||
color: PARTICLE_COLORS[`${color}Gradient`],
|
||||
...PARTICLE_PARAMS,
|
||||
});
|
||||
}, [color, isDisabled]);
|
||||
const triggerSparklesRef = useRef<(() => void) | undefined>();
|
||||
|
||||
const handleMouseMove = useLastCallback(() => {
|
||||
setupParticles(canvasRef.current!, {
|
||||
color: PARTICLE_COLORS[`${color}Gradient`],
|
||||
...PARTICLE_PARAMS,
|
||||
...PARTICLE_BURST_PARAMS,
|
||||
});
|
||||
triggerSparklesRef.current?.();
|
||||
});
|
||||
|
||||
const handleRequestAnimation = useLastCallback((animate: NoneToVoidFunction) => {
|
||||
triggerSparklesRef.current = animate;
|
||||
});
|
||||
|
||||
return (
|
||||
<div className={buildClassName(styles.root, className)}>
|
||||
<canvas ref={canvasRef} className={styles.particles} />
|
||||
<InteractiveSparkles
|
||||
color={color}
|
||||
centerShift={PARTICLE_PARAMS.centerShift}
|
||||
isDisabled={isDisabled}
|
||||
className={styles.particles}
|
||||
onRequestAnimation={handleRequestAnimation}
|
||||
/>
|
||||
|
||||
{model === 'swaying-star' ? (
|
||||
<SwayingStar
|
||||
|
||||
@ -199,6 +199,15 @@
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
z-index: 2;
|
||||
transition: transform 0.25s ease-out;
|
||||
|
||||
&:hover {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
}
|
||||
|
||||
.logoBackground {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
|
||||
@ -31,6 +31,7 @@ import useLastCallback from '../../../hooks/useLastCallback';
|
||||
import useOldLang from '../../../hooks/useOldLang';
|
||||
|
||||
import Avatar from '../../common/Avatar';
|
||||
import InteractiveSparkles from '../../common/InteractiveSparkles';
|
||||
import SafeLink from '../../common/SafeLink';
|
||||
import Button from '../../ui/Button';
|
||||
import Modal from '../../ui/Modal';
|
||||
@ -45,8 +46,6 @@ import StarGiftCategoryList from './StarGiftCategoryList';
|
||||
|
||||
import styles from './GiftModal.module.scss';
|
||||
|
||||
import StarsBackground from '../../../assets/stars-bg.png';
|
||||
|
||||
export type OwnProps = {
|
||||
modal: TabState['giftModal'];
|
||||
};
|
||||
@ -68,6 +67,7 @@ type StateProps = {
|
||||
const AVATAR_SIZE = 100;
|
||||
const INTERSECTION_THROTTLE = 200;
|
||||
const SCROLL_THROTTLE = 200;
|
||||
const AVATAR_SPARKLES_CENTER_SHIFT = [0, -50] as const;
|
||||
|
||||
const runThrottledForScroll = throttle((cb) => cb(), SCROLL_THROTTLE, true);
|
||||
|
||||
@ -104,6 +104,7 @@ const GiftModal: FC<OwnProps & StateProps> = ({
|
||||
const [isGiftScreenHeaderForStarGifts, setIsGiftScreenHeaderForStarGifts] = useState(false);
|
||||
|
||||
const [selectedCategory, setSelectedCategory] = useState<StarGiftCategory>('all');
|
||||
const triggerSparklesRef = useRef<(() => void) | undefined>();
|
||||
|
||||
const areAllGiftsDisallowed = useMemo(() => {
|
||||
if (!disallowedGifts) {
|
||||
@ -357,15 +358,30 @@ const GiftModal: FC<OwnProps & StateProps> = ({
|
||||
handleCloseModal();
|
||||
});
|
||||
|
||||
const handleAvatarMouseMove = useLastCallback(() => {
|
||||
triggerSparklesRef.current?.();
|
||||
});
|
||||
|
||||
const handleRequestAnimation = useLastCallback((animate: NoneToVoidFunction) => {
|
||||
triggerSparklesRef.current = animate;
|
||||
});
|
||||
|
||||
function renderMainScreen() {
|
||||
return (
|
||||
<div ref={scrollerRef} className={buildClassName(styles.main, 'custom-scroll')} onScroll={handleScroll}>
|
||||
<div className={styles.avatars}>
|
||||
<Avatar
|
||||
className={styles.avatar}
|
||||
size={AVATAR_SIZE}
|
||||
peer={peer}
|
||||
onMouseMove={handleAvatarMouseMove}
|
||||
/>
|
||||
<InteractiveSparkles
|
||||
className={styles.logoBackground}
|
||||
color="gold"
|
||||
centerShift={AVATAR_SPARKLES_CENTER_SHIFT}
|
||||
onRequestAnimation={handleRequestAnimation}
|
||||
/>
|
||||
<img className={styles.logoBackground} src={StarsBackground} alt="" draggable={false} />
|
||||
</div>
|
||||
{!isSelf && !chat && !disallowedGifts?.shouldDisallowPremiumGifts && (
|
||||
<>
|
||||
|
||||
@ -135,3 +135,12 @@
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
z-index: 2;
|
||||
transition: transform 0.25s ease-out;
|
||||
|
||||
&:hover {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import type { FC } from '../../../../lib/teact/teact';
|
||||
import { memo, useMemo } from '../../../../lib/teact/teact';
|
||||
import { memo, useMemo, useRef } from '../../../../lib/teact/teact';
|
||||
import { getActions, withGlobal } from '../../../../global';
|
||||
|
||||
import type {
|
||||
@ -39,6 +39,7 @@ import AnimatedIconFromSticker from '../../../common/AnimatedIconFromSticker';
|
||||
import Avatar from '../../../common/Avatar';
|
||||
import Icon from '../../../common/icons/Icon';
|
||||
import StarIcon from '../../../common/icons/StarIcon';
|
||||
import InteractiveSparkles from '../../../common/InteractiveSparkles';
|
||||
import SafeLink from '../../../common/SafeLink';
|
||||
import TableInfoModal, { type TableData } from '../../common/TableInfoModal';
|
||||
import UniqueGiftHeader from '../../gift/UniqueGiftHeader';
|
||||
@ -46,7 +47,7 @@ import PaidMediaThumb from './PaidMediaThumb';
|
||||
|
||||
import styles from './StarsTransactionModal.module.scss';
|
||||
|
||||
import StarsBackground from '../../../../assets/stars-bg.png';
|
||||
const AVATAR_SPARKLES_CENTER_SHIFT = [0, -50] as const;
|
||||
|
||||
export type OwnProps = {
|
||||
modal: TabState['starsTransactionModal'];
|
||||
@ -71,6 +72,7 @@ const StarsTransactionModal: FC<OwnProps & StateProps> = ({
|
||||
const lang = useLang();
|
||||
const oldLang = useOldLang();
|
||||
const { transaction } = modal || {};
|
||||
const triggerSparklesRef = useRef<(() => void) | undefined>();
|
||||
|
||||
const handleOpenMedia = useLastCallback(() => {
|
||||
const media = transaction?.extendedMedia;
|
||||
@ -82,6 +84,14 @@ const StarsTransactionModal: FC<OwnProps & StateProps> = ({
|
||||
});
|
||||
});
|
||||
|
||||
const handleAvatarMouseMove = useLastCallback(() => {
|
||||
triggerSparklesRef.current?.();
|
||||
});
|
||||
|
||||
const handleRequestAnimation = useLastCallback((animate: NoneToVoidFunction) => {
|
||||
triggerSparklesRef.current = animate;
|
||||
});
|
||||
|
||||
const starModalData = useMemo(() => {
|
||||
if (!transaction) {
|
||||
return undefined;
|
||||
@ -160,14 +170,20 @@ const StarsTransactionModal: FC<OwnProps & StateProps> = ({
|
||||
/>
|
||||
)}
|
||||
{shouldDisplayAvatar && (
|
||||
<Avatar peer={avatarPeer} webPhoto={photo} size="giant" />
|
||||
<Avatar
|
||||
className={styles.avatar}
|
||||
peer={avatarPeer}
|
||||
webPhoto={photo}
|
||||
size="giant"
|
||||
onMouseMove={handleAvatarMouseMove}
|
||||
/>
|
||||
)}
|
||||
{!sticker && !transaction.isPostsSearch && (
|
||||
<img
|
||||
<InteractiveSparkles
|
||||
className={buildClassName(styles.starsBackground)}
|
||||
src={StarsBackground}
|
||||
alt=""
|
||||
draggable={false}
|
||||
color="gold"
|
||||
onRequestAnimation={handleRequestAnimation}
|
||||
centerShift={AVATAR_SPARKLES_CENTER_SHIFT}
|
||||
/>
|
||||
)}
|
||||
{Boolean(title) && <h1 className={styles.title}>{title}</h1>}
|
||||
@ -305,7 +321,8 @@ const StarsTransactionModal: FC<OwnProps & StateProps> = ({
|
||||
tableData,
|
||||
footer,
|
||||
};
|
||||
}, [transaction, oldLang, lang, peer, canPlayAnimatedEmojis, topSticker, paidMessageCommission]);
|
||||
}, [transaction, oldLang, lang, peer, canPlayAnimatedEmojis, topSticker,
|
||||
paidMessageCommission, handleRequestAnimation]);
|
||||
|
||||
const prevModalData = usePrevious(starModalData);
|
||||
const renderingModalData = prevModalData || starModalData;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user