131 lines
4.1 KiB
TypeScript
131 lines
4.1 KiB
TypeScript
import React, {
|
|
FC, memo, useLayoutEffect, useRef,
|
|
} from '../../../lib/teact/teact';
|
|
|
|
import { ApiAvailableReaction, ApiMediaFormat } from '../../../api/types';
|
|
|
|
import useMedia from '../../../hooks/useMedia';
|
|
import useHorizontalScroll from '../../../hooks/useHorizontalScroll';
|
|
import useFlag from '../../../hooks/useFlag';
|
|
import useShowTransition from '../../../hooks/useShowTransition';
|
|
import { getTouchY } from '../../../util/scrollLock';
|
|
|
|
import ReactionStaticEmoji from '../../common/ReactionStaticEmoji';
|
|
import AnimatedSticker from '../../common/AnimatedSticker';
|
|
|
|
import './ReactionSelector.scss';
|
|
|
|
const REACTION_SIZE = 32;
|
|
|
|
type OwnProps = {
|
|
enabledReactions?: string[];
|
|
onSendReaction: (reaction: string, x: number, y: number) => void;
|
|
isPrivate?: boolean;
|
|
availableReactions?: ApiAvailableReaction[];
|
|
isReady?: boolean;
|
|
};
|
|
|
|
const AvailableReaction: FC<{
|
|
reaction: ApiAvailableReaction;
|
|
isReady?: boolean;
|
|
onSendReaction: (reaction: string, x: number, y: number) => void;
|
|
}> = ({ reaction, onSendReaction, isReady }) => {
|
|
// eslint-disable-next-line no-null/no-null
|
|
const containerRef = useRef<HTMLDivElement>(null);
|
|
|
|
const mediaData = useMedia(`document${reaction.selectAnimation?.id}`, !isReady, ApiMediaFormat.Lottie);
|
|
|
|
const [isActivated, activate, deactivate] = useFlag();
|
|
const [isAnimationLoaded, markAnimationLoaded] = useFlag();
|
|
|
|
const shouldRenderAnimated = Boolean(isReady && mediaData);
|
|
const { transitionClassNames: animatedClassNames } = useShowTransition(shouldRenderAnimated);
|
|
const { shouldRender: shouldRenderStatic, transitionClassNames: staticClassNames } = useShowTransition(
|
|
!isReady || !isAnimationLoaded, undefined, true,
|
|
);
|
|
|
|
function handleClick() {
|
|
if (!containerRef.current) return;
|
|
const { x, y } = containerRef.current.getBoundingClientRect();
|
|
|
|
onSendReaction(reaction.reaction, x, y);
|
|
}
|
|
|
|
return (
|
|
<div
|
|
className="reaction"
|
|
onClick={handleClick}
|
|
ref={containerRef}
|
|
onMouseEnter={isReady ? activate : undefined}
|
|
>
|
|
{shouldRenderStatic && (
|
|
<ReactionStaticEmoji className={isReady ? staticClassNames : undefined} reaction={reaction.reaction} />
|
|
)}
|
|
{shouldRenderAnimated && (
|
|
<AnimatedSticker
|
|
id={`select_${reaction.reaction}`}
|
|
className={animatedClassNames}
|
|
animationData={mediaData as AnyLiteral}
|
|
play={isActivated}
|
|
noLoop
|
|
size={REACTION_SIZE}
|
|
onLoad={markAnimationLoaded}
|
|
onEnded={deactivate}
|
|
/>
|
|
)}
|
|
</div>
|
|
);
|
|
};
|
|
const ReactionSelector: FC<OwnProps> = ({
|
|
availableReactions,
|
|
enabledReactions,
|
|
onSendReaction,
|
|
isPrivate,
|
|
isReady,
|
|
}) => {
|
|
// eslint-disable-next-line no-null/no-null
|
|
const itemsScrollRef = useRef<HTMLDivElement>(null);
|
|
const [isHorizontalScrollEnabled, enableHorizontalScroll] = useFlag(false);
|
|
useHorizontalScroll(itemsScrollRef.current, !isHorizontalScrollEnabled);
|
|
|
|
useLayoutEffect(() => {
|
|
enableHorizontalScroll();
|
|
}, [enableHorizontalScroll]);
|
|
|
|
const handleWheel = (e: React.WheelEvent | React.TouchEvent) => {
|
|
if (!itemsScrollRef) return;
|
|
const deltaY = 'deltaY' in e ? e.deltaY : getTouchY(e);
|
|
|
|
if (deltaY) {
|
|
e.preventDefault();
|
|
}
|
|
};
|
|
|
|
if ((!isPrivate && !enabledReactions?.length) || !availableReactions) return undefined;
|
|
|
|
return (
|
|
<div className="ReactionSelector" onWheelCapture={handleWheel} onTouchMove={handleWheel}>
|
|
<div className="bubble-big" />
|
|
<div className="bubble-small" />
|
|
<div className="items-wrapper">
|
|
<div className="items no-scrollbar" ref={itemsScrollRef}>
|
|
{availableReactions?.map((reaction) => {
|
|
if (reaction.isInactive
|
|
|| (!isPrivate && (!enabledReactions || !enabledReactions.includes(reaction.reaction)))) return undefined;
|
|
return (
|
|
<AvailableReaction
|
|
key={reaction.reaction}
|
|
isReady={isReady}
|
|
onSendReaction={onSendReaction}
|
|
reaction={reaction}
|
|
/>
|
|
);
|
|
})}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default memo(ReactionSelector);
|