Story: Display reaction effect (#3829)
This commit is contained in:
parent
6e43795216
commit
31403702fe
@ -110,7 +110,8 @@
|
|||||||
|
|
||||||
&.story-reaction-button {
|
&.story-reaction-button {
|
||||||
--custom-emoji-size: 1.5rem;
|
--custom-emoji-size: 1.5rem;
|
||||||
--custom-emoji-border-radius: 0.25rem;
|
|
||||||
|
overflow: visible !important;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: var(--color-background) !important;
|
background-color: var(--color-background) !important;
|
||||||
|
|||||||
@ -26,7 +26,8 @@ import type {
|
|||||||
ApiVideo,
|
ApiVideo,
|
||||||
} from '../../api/types';
|
} from '../../api/types';
|
||||||
import type {
|
import type {
|
||||||
ApiDraft, GlobalState, MessageList, MessageListType, TabState,
|
ApiDraft, GlobalState, MessageList,
|
||||||
|
MessageListType, TabState,
|
||||||
} from '../../global/types';
|
} from '../../global/types';
|
||||||
import type { IAnchorPosition, InlineBotSettings, ISettings } from '../../types';
|
import type { IAnchorPosition, InlineBotSettings, ISettings } from '../../types';
|
||||||
|
|
||||||
@ -41,6 +42,7 @@ import {
|
|||||||
import { requestMeasure, requestNextMutation } from '../../lib/fasterdom/fasterdom';
|
import { requestMeasure, requestNextMutation } from '../../lib/fasterdom/fasterdom';
|
||||||
import {
|
import {
|
||||||
getAllowedAttachmentOptions,
|
getAllowedAttachmentOptions,
|
||||||
|
getStoryKey,
|
||||||
isChatAdmin,
|
isChatAdmin,
|
||||||
isChatChannel,
|
isChatChannel,
|
||||||
isChatSuperGroup,
|
isChatSuperGroup,
|
||||||
@ -92,7 +94,6 @@ import applyIosAutoCapitalizationFix from '../middle/composer/helpers/applyIosAu
|
|||||||
import buildAttachment, { prepareAttachmentsToSend } from '../middle/composer/helpers/buildAttachment';
|
import buildAttachment, { prepareAttachmentsToSend } from '../middle/composer/helpers/buildAttachment';
|
||||||
import { buildCustomEmojiHtml } from '../middle/composer/helpers/customEmoji';
|
import { buildCustomEmojiHtml } from '../middle/composer/helpers/customEmoji';
|
||||||
import { isSelectionInsideInput } from '../middle/composer/helpers/selection';
|
import { isSelectionInsideInput } from '../middle/composer/helpers/selection';
|
||||||
import { REM } from './helpers/mediaDimensions';
|
|
||||||
import renderText from './helpers/renderText';
|
import renderText from './helpers/renderText';
|
||||||
import { getTextWithEntitiesAsHtml } from './helpers/renderTextWithEntities';
|
import { getTextWithEntitiesAsHtml } from './helpers/renderTextWithEntities';
|
||||||
|
|
||||||
@ -149,7 +150,7 @@ import ResponsiveHoverButton from '../ui/ResponsiveHoverButton';
|
|||||||
import Spinner from '../ui/Spinner';
|
import Spinner from '../ui/Spinner';
|
||||||
import Avatar from './Avatar';
|
import Avatar from './Avatar';
|
||||||
import DeleteMessageModal from './DeleteMessageModal.async';
|
import DeleteMessageModal from './DeleteMessageModal.async';
|
||||||
import ReactionStaticEmoji from './ReactionStaticEmoji';
|
import ReactionAnimatedEmoji from './reactions/ReactionAnimatedEmoji';
|
||||||
|
|
||||||
import './Composer.scss';
|
import './Composer.scss';
|
||||||
|
|
||||||
@ -260,7 +261,6 @@ const MESSAGE_MAX_LENGTH = 4096;
|
|||||||
const SENDING_ANIMATION_DURATION = 350;
|
const SENDING_ANIMATION_DURATION = 350;
|
||||||
const MOUNT_ANIMATION_DURATION = 430;
|
const MOUNT_ANIMATION_DURATION = 430;
|
||||||
|
|
||||||
const REACTION_SIZE = 1.5 * REM;
|
|
||||||
const HEART_REACTION: ApiReaction = {
|
const HEART_REACTION: ApiReaction = {
|
||||||
emoticon: '❤',
|
emoticon: '❤',
|
||||||
};
|
};
|
||||||
@ -1293,8 +1293,8 @@ const Composer: FC<OwnProps & StateProps> = ({
|
|||||||
|| isMentionTooltipOpen || isInlineBotTooltipOpen || isDeleteModalOpen || isBotCommandMenuOpen || isAttachMenuOpen
|
|| isMentionTooltipOpen || isInlineBotTooltipOpen || isDeleteModalOpen || isBotCommandMenuOpen || isAttachMenuOpen
|
||||||
|| isStickerTooltipOpen || isBotCommandTooltipOpen || isCustomEmojiTooltipOpen || isBotMenuButtonOpen
|
|| isStickerTooltipOpen || isBotCommandTooltipOpen || isCustomEmojiTooltipOpen || isBotMenuButtonOpen
|
||||||
|| isCustomSendMenuOpen || Boolean(activeVoiceRecording) || attachments.length > 0 || isInputHasFocus;
|
|| isCustomSendMenuOpen || Boolean(activeVoiceRecording) || attachments.length > 0 || isInputHasFocus;
|
||||||
const isReactionSelectorOpen = (isComposerHasFocus || isReactionPickerOpen)
|
const isReactionSelectorOpen = isComposerHasFocus && !isReactionPickerOpen && isInStoryViewer && !isAttachMenuOpen
|
||||||
&& isInStoryViewer && !isAttachMenuOpen && !isSymbolMenuOpen;
|
&& !isSymbolMenuOpen;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isComposerHasFocus) {
|
if (isComposerHasFocus) {
|
||||||
@ -1814,13 +1814,14 @@ const Composer: FC<OwnProps & StateProps> = ({
|
|||||||
ariaLabel={lang('AccDescrLike')}
|
ariaLabel={lang('AccDescrLike')}
|
||||||
ref={storyReactionRef}
|
ref={storyReactionRef}
|
||||||
>
|
>
|
||||||
{sentStoryReaction && !isSentStoryReactionHeart ? (
|
{sentStoryReaction && (
|
||||||
<ReactionStaticEmoji
|
<ReactionAnimatedEmoji
|
||||||
|
containerId={getStoryKey(chatId, storyId!)}
|
||||||
reaction={sentStoryReaction}
|
reaction={sentStoryReaction}
|
||||||
availableReactions={availableReactions}
|
withEffectOnly={isSentStoryReactionHeart}
|
||||||
size={REACTION_SIZE}
|
|
||||||
/>
|
/>
|
||||||
) : (
|
)}
|
||||||
|
{(!sentStoryReaction || isSentStoryReactionHeart) && (
|
||||||
<i
|
<i
|
||||||
className={buildClassName(
|
className={buildClassName(
|
||||||
'icon',
|
'icon',
|
||||||
|
|||||||
@ -99,6 +99,7 @@ const ReactionEmoji: FC<OwnProps> = ({
|
|||||||
className={transitionClassNames}
|
className={transitionClassNames}
|
||||||
sharedCanvas={sharedCanvasRef!.current || undefined}
|
sharedCanvas={sharedCanvasRef!.current || undefined}
|
||||||
sharedCanvasCoords={coords}
|
sharedCanvasCoords={coords}
|
||||||
|
forceAlways={forcePlayback}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -9,7 +9,7 @@ import { IS_OFFSET_PATH_SUPPORTED } from '../../../util/windowEnvironment';
|
|||||||
|
|
||||||
import useMedia from '../../../hooks/useMedia';
|
import useMedia from '../../../hooks/useMedia';
|
||||||
|
|
||||||
import CustomEmoji from '../../common/CustomEmoji';
|
import CustomEmoji from '../CustomEmoji';
|
||||||
|
|
||||||
import styles from './CustomEmojiEffect.module.scss';
|
import styles from './CustomEmojiEffect.module.scss';
|
||||||
|
|
||||||
@ -6,23 +6,27 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
|
||||||
width: 1.75rem;
|
width: var(--custom-emoji-size);
|
||||||
height: 1.75rem;
|
height: var(--custom-emoji-size);
|
||||||
margin-inline-end: 0.125rem;
|
|
||||||
|
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.animated-icon, .effect {
|
.animated-icon {
|
||||||
position: fixed;
|
position: absolute;
|
||||||
top: -0.375rem;
|
top: 50%;
|
||||||
left: -0.375rem;
|
left: 50%;
|
||||||
pointer-events: none;
|
transform: translate(-50%, -50%);
|
||||||
|
}
|
||||||
|
|
||||||
&.effect {
|
.effect {
|
||||||
top: -2.25rem;
|
position: fixed;
|
||||||
left: -2.25rem;
|
top: -2.25rem;
|
||||||
}
|
left: -2.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.animated-icon, .effect {
|
||||||
|
pointer-events: none;
|
||||||
|
|
||||||
&:not(:global(.open)) {
|
&:not(:global(.open)) {
|
||||||
opacity: 1 !important;
|
opacity: 1 !important;
|
||||||
@ -39,3 +43,10 @@
|
|||||||
// Fix for redundant scroll in Firefox
|
// Fix for redundant scroll in Firefox
|
||||||
contain: layout;
|
contain: layout;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.withEffectOnly {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
}
|
||||||
@ -1,51 +1,65 @@
|
|||||||
import type { FC } from '../../../lib/teact/teact';
|
|
||||||
import React, {
|
import React, {
|
||||||
memo, useMemo, useRef,
|
memo, useMemo, useRef,
|
||||||
} from '../../../lib/teact/teact';
|
} from '../../../lib/teact/teact';
|
||||||
import { getActions } from '../../../global';
|
import { getActions, withGlobal } from '../../../global';
|
||||||
|
|
||||||
import type { ApiAvailableReaction, ApiReaction, ApiStickerSet } from '../../../api/types';
|
import type { ApiAvailableReaction, ApiReaction, ApiStickerSet } from '../../../api/types';
|
||||||
import type { ActiveReaction } from '../../../global/types';
|
|
||||||
import type { ObserveFn } from '../../../hooks/useIntersectionObserver';
|
import type { ObserveFn } from '../../../hooks/useIntersectionObserver';
|
||||||
|
|
||||||
import { isSameReaction } from '../../../global/helpers';
|
import { isSameReaction } from '../../../global/helpers';
|
||||||
|
import { selectPerformanceSettingsValue, selectTabState } from '../../../global/selectors';
|
||||||
import buildClassName from '../../../util/buildClassName';
|
import buildClassName from '../../../util/buildClassName';
|
||||||
import { REM } from '../../common/helpers/mediaDimensions';
|
import { roundToNearestEven } from '../../../util/math';
|
||||||
|
import { REM } from '../helpers/mediaDimensions';
|
||||||
|
|
||||||
import useFlag from '../../../hooks/useFlag';
|
import useFlag from '../../../hooks/useFlag';
|
||||||
import { useIsIntersecting } from '../../../hooks/useIntersectionObserver';
|
import { useIsIntersecting } from '../../../hooks/useIntersectionObserver';
|
||||||
import useLastCallback from '../../../hooks/useLastCallback';
|
import useLastCallback from '../../../hooks/useLastCallback';
|
||||||
import useMedia from '../../../hooks/useMedia';
|
import useMedia from '../../../hooks/useMedia';
|
||||||
import useShowTransition from '../../../hooks/useShowTransition';
|
import useShowTransition from '../../../hooks/useShowTransition';
|
||||||
import useCustomEmoji from '../../common/hooks/useCustomEmoji';
|
import useCustomEmoji from '../hooks/useCustomEmoji';
|
||||||
|
|
||||||
import AnimatedSticker from '../../common/AnimatedSticker';
|
import AnimatedSticker from '../AnimatedSticker';
|
||||||
import CustomEmoji from '../../common/CustomEmoji';
|
import CustomEmoji from '../CustomEmoji';
|
||||||
import ReactionStaticEmoji from '../../common/ReactionStaticEmoji';
|
import ReactionStaticEmoji from '../ReactionStaticEmoji';
|
||||||
import CustomEmojiEffect from './CustomEmojiEffect';
|
import CustomEmojiEffect from './CustomEmojiEffect';
|
||||||
|
|
||||||
import styles from './ReactionAnimatedEmoji.module.scss';
|
import styles from './ReactionAnimatedEmoji.module.scss';
|
||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
|
containerId: string;
|
||||||
reaction: ApiReaction;
|
reaction: ApiReaction;
|
||||||
activeReactions?: ActiveReaction[];
|
className?: string;
|
||||||
|
size?: number;
|
||||||
|
effectSize?: number;
|
||||||
|
withEffectOnly?: boolean;
|
||||||
|
observeIntersection?: ObserveFn;
|
||||||
|
};
|
||||||
|
|
||||||
|
type StateProps = {
|
||||||
|
activeReactions?: ApiReaction[];
|
||||||
availableReactions?: ApiAvailableReaction[];
|
availableReactions?: ApiAvailableReaction[];
|
||||||
genericEffects?: ApiStickerSet;
|
genericEffects?: ApiStickerSet;
|
||||||
observeIntersection?: ObserveFn;
|
|
||||||
withEffects?: boolean;
|
withEffects?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
const CENTER_ICON_SIZE = 2.5 * REM;
|
const ICON_SIZE = 1.5 * REM;
|
||||||
|
const CENTER_ICON_MULTIPLIER = 1.9;
|
||||||
const EFFECT_SIZE = 6.5 * REM;
|
const EFFECT_SIZE = 6.5 * REM;
|
||||||
|
|
||||||
const ReactionAnimatedEmoji: FC<OwnProps> = ({
|
const ReactionAnimatedEmoji = ({
|
||||||
|
containerId,
|
||||||
reaction,
|
reaction,
|
||||||
genericEffects,
|
className,
|
||||||
|
size = ICON_SIZE,
|
||||||
|
effectSize = EFFECT_SIZE,
|
||||||
activeReactions,
|
activeReactions,
|
||||||
availableReactions,
|
availableReactions,
|
||||||
observeIntersection,
|
genericEffects,
|
||||||
withEffects,
|
withEffects,
|
||||||
}) => {
|
withEffectOnly,
|
||||||
|
observeIntersection,
|
||||||
|
}: OwnProps & StateProps) => {
|
||||||
const { stopActiveReaction } = getActions();
|
const { stopActiveReaction } = getActions();
|
||||||
|
|
||||||
// eslint-disable-next-line no-null/no-null
|
// eslint-disable-next-line no-null/no-null
|
||||||
@ -93,7 +107,7 @@ const ReactionAnimatedEmoji: FC<OwnProps> = ({
|
|||||||
const mediaDataEffect = useMedia(mediaHashEffect, !effectId);
|
const mediaDataEffect = useMedia(mediaHashEffect, !effectId);
|
||||||
|
|
||||||
const activeReaction = useMemo(() => (
|
const activeReaction = useMemo(() => (
|
||||||
activeReactions?.find((active) => isSameReaction(active.reaction, reaction))
|
activeReactions?.find((active) => isSameReaction(active, reaction))
|
||||||
), [activeReactions, reaction]);
|
), [activeReactions, reaction]);
|
||||||
|
|
||||||
const shouldPlay = Boolean(withEffects && activeReaction && (isCustom || mediaDataCenterIcon) && mediaDataEffect);
|
const shouldPlay = Boolean(withEffects && activeReaction && (isCustom || mediaDataCenterIcon) && mediaDataEffect);
|
||||||
@ -103,26 +117,39 @@ const ReactionAnimatedEmoji: FC<OwnProps> = ({
|
|||||||
} = useShowTransition(shouldPlay, undefined, true, 'slow');
|
} = useShowTransition(shouldPlay, undefined, true, 'slow');
|
||||||
|
|
||||||
const handleEnded = useLastCallback(() => {
|
const handleEnded = useLastCallback(() => {
|
||||||
if (!activeReaction?.messageId) return;
|
stopActiveReaction({ containerId, reaction });
|
||||||
stopActiveReaction({ messageId: activeReaction.messageId, reaction });
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const [isAnimationLoaded, markAnimationLoaded, unmarkAnimationLoaded] = useFlag();
|
const [isAnimationLoaded, markAnimationLoaded, unmarkAnimationLoaded] = useFlag();
|
||||||
const shouldRenderStatic = !isCustom && (!shouldPlay || !isAnimationLoaded);
|
const shouldShowStatic = !isCustom && (!shouldPlay || !isAnimationLoaded);
|
||||||
|
const {
|
||||||
|
shouldRender: shouldRenderStatic,
|
||||||
|
transitionClassNames: staticClassNames,
|
||||||
|
} = useShowTransition(shouldShowStatic, undefined, true);
|
||||||
|
|
||||||
const className = buildClassName(
|
const rootClassName = buildClassName(
|
||||||
styles.root,
|
styles.root,
|
||||||
shouldRenderAnimation && styles.animating,
|
shouldRenderAnimation && styles.animating,
|
||||||
isCustom && styles.isCustomEmoji,
|
withEffectOnly && styles.withEffectOnly,
|
||||||
|
className,
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={className} ref={ref}>
|
<div className={rootClassName} ref={ref}>
|
||||||
{shouldRenderStatic && <ReactionStaticEmoji reaction={reaction} availableReactions={availableReactions} />}
|
{!withEffectOnly && shouldRenderStatic && (
|
||||||
{isCustom && (
|
<ReactionStaticEmoji
|
||||||
|
className={staticClassNames}
|
||||||
|
reaction={reaction}
|
||||||
|
availableReactions={availableReactions}
|
||||||
|
size={size}
|
||||||
|
observeIntersection={observeIntersection}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{!withEffectOnly && isCustom && (
|
||||||
<CustomEmoji
|
<CustomEmoji
|
||||||
documentId={reaction.documentId}
|
documentId={reaction.documentId}
|
||||||
className={styles.customEmoji}
|
className={styles.customEmoji}
|
||||||
|
size={size}
|
||||||
observeIntersectionForPlaying={observeIntersection}
|
observeIntersectionForPlaying={observeIntersection}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@ -131,20 +158,19 @@ const ReactionAnimatedEmoji: FC<OwnProps> = ({
|
|||||||
<AnimatedSticker
|
<AnimatedSticker
|
||||||
key={effectId}
|
key={effectId}
|
||||||
className={buildClassName(styles.effect, animationClassNames)}
|
className={buildClassName(styles.effect, animationClassNames)}
|
||||||
size={EFFECT_SIZE}
|
size={effectSize}
|
||||||
tgsUrl={mediaDataEffect}
|
tgsUrl={mediaDataEffect}
|
||||||
play={isIntersecting}
|
play={isIntersecting}
|
||||||
noLoop
|
noLoop
|
||||||
forceAlways
|
forceAlways
|
||||||
onEnded={handleEnded}
|
onEnded={handleEnded}
|
||||||
/>
|
/>
|
||||||
{isCustom ? (
|
{isCustom && !assignedEffectId && isIntersecting && <CustomEmojiEffect reaction={reaction} />}
|
||||||
!assignedEffectId && isIntersecting && <CustomEmojiEffect reaction={reaction} />
|
{!isCustom && !withEffectOnly && (
|
||||||
) : (
|
|
||||||
<AnimatedSticker
|
<AnimatedSticker
|
||||||
key={centerIconId}
|
key={centerIconId}
|
||||||
className={buildClassName(styles.animatedIcon, animationClassNames)}
|
className={buildClassName(styles.animatedIcon, animationClassNames)}
|
||||||
size={CENTER_ICON_SIZE}
|
size={roundToNearestEven(size * CENTER_ICON_MULTIPLIER)}
|
||||||
tgsUrl={mediaDataCenterIcon}
|
tgsUrl={mediaDataCenterIcon}
|
||||||
play={isIntersecting}
|
play={isIntersecting}
|
||||||
noLoop
|
noLoop
|
||||||
@ -159,4 +185,18 @@ const ReactionAnimatedEmoji: FC<OwnProps> = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default memo(ReactionAnimatedEmoji);
|
export default memo(withGlobal<OwnProps>(
|
||||||
|
(global, { containerId }) => {
|
||||||
|
const { availableReactions, genericEmojiEffects } = global;
|
||||||
|
const { activeReactions } = selectTabState(global);
|
||||||
|
|
||||||
|
const withEffects = selectPerformanceSettingsValue(global, 'reactionEffects');
|
||||||
|
|
||||||
|
return {
|
||||||
|
activeReactions: activeReactions?.[containerId],
|
||||||
|
availableReactions,
|
||||||
|
genericEffects: genericEmojiEffects,
|
||||||
|
withEffects,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
)(ReactionAnimatedEmoji));
|
||||||
@ -15,7 +15,7 @@ import useTimeout from '../../../hooks/useTimeout';
|
|||||||
|
|
||||||
import CustomEmoji from '../../common/CustomEmoji';
|
import CustomEmoji from '../../common/CustomEmoji';
|
||||||
import PremiumIcon from '../../common/PremiumIcon';
|
import PremiumIcon from '../../common/PremiumIcon';
|
||||||
import CustomEmojiEffect from '../../middle/message/CustomEmojiEffect';
|
import CustomEmojiEffect from '../../common/reactions/CustomEmojiEffect';
|
||||||
import Button from '../../ui/Button';
|
import Button from '../../ui/Button';
|
||||||
import StatusPickerMenu from './StatusPickerMenu.async';
|
import StatusPickerMenu from './StatusPickerMenu.async';
|
||||||
|
|
||||||
|
|||||||
@ -11,7 +11,6 @@ import type {
|
|||||||
ApiMessage,
|
ApiMessage,
|
||||||
ApiMessageOutgoingStatus,
|
ApiMessageOutgoingStatus,
|
||||||
ApiReaction,
|
ApiReaction,
|
||||||
ApiStickerSet,
|
|
||||||
ApiThreadInfo,
|
ApiThreadInfo,
|
||||||
ApiTopic,
|
ApiTopic,
|
||||||
ApiTypeStory,
|
ApiTypeStory,
|
||||||
@ -20,7 +19,6 @@ import type {
|
|||||||
} from '../../../api/types';
|
} from '../../../api/types';
|
||||||
import type {
|
import type {
|
||||||
ActiveEmojiInteraction,
|
ActiveEmojiInteraction,
|
||||||
ActiveReaction,
|
|
||||||
ChatTranslatedMessages,
|
ChatTranslatedMessages,
|
||||||
MessageListType,
|
MessageListType,
|
||||||
} from '../../../global/types';
|
} from '../../../global/types';
|
||||||
@ -37,6 +35,7 @@ import {
|
|||||||
getMessageContent,
|
getMessageContent,
|
||||||
getMessageCustomShape,
|
getMessageCustomShape,
|
||||||
getMessageHtmlId,
|
getMessageHtmlId,
|
||||||
|
getMessageKey,
|
||||||
getMessageLocation,
|
getMessageLocation,
|
||||||
getMessageSingleCustomEmoji,
|
getMessageSingleCustomEmoji,
|
||||||
getMessageSingleRegularEmoji,
|
getMessageSingleRegularEmoji,
|
||||||
@ -230,7 +229,7 @@ type StateProps = {
|
|||||||
highlight?: string;
|
highlight?: string;
|
||||||
animatedEmoji?: string;
|
animatedEmoji?: string;
|
||||||
animatedCustomEmoji?: string;
|
animatedCustomEmoji?: string;
|
||||||
genericEffects?: ApiStickerSet;
|
hasActiveReactions?: boolean;
|
||||||
isInSelectMode?: boolean;
|
isInSelectMode?: boolean;
|
||||||
isSelected?: boolean;
|
isSelected?: boolean;
|
||||||
isGroupSelected?: boolean;
|
isGroupSelected?: boolean;
|
||||||
@ -247,7 +246,6 @@ type StateProps = {
|
|||||||
reactionMessage?: ApiMessage;
|
reactionMessage?: ApiMessage;
|
||||||
availableReactions?: ApiAvailableReaction[];
|
availableReactions?: ApiAvailableReaction[];
|
||||||
defaultReaction?: ApiReaction;
|
defaultReaction?: ApiReaction;
|
||||||
activeReactions?: ActiveReaction[];
|
|
||||||
activeEmojiInteractions?: ActiveEmojiInteraction[];
|
activeEmojiInteractions?: ActiveEmojiInteraction[];
|
||||||
hasUnreadReaction?: boolean;
|
hasUnreadReaction?: boolean;
|
||||||
isTranscribing?: boolean;
|
isTranscribing?: boolean;
|
||||||
@ -262,7 +260,6 @@ type StateProps = {
|
|||||||
shouldDetectChatLanguage?: boolean;
|
shouldDetectChatLanguage?: boolean;
|
||||||
requestedTranslationLanguage?: string;
|
requestedTranslationLanguage?: string;
|
||||||
requestedChatTranslationLanguage?: string;
|
requestedChatTranslationLanguage?: string;
|
||||||
withReactionEffects?: boolean;
|
|
||||||
withStickerEffects?: boolean;
|
withStickerEffects?: boolean;
|
||||||
webPageStory?: ApiTypeStory;
|
webPageStory?: ApiTypeStory;
|
||||||
isConnected: boolean;
|
isConnected: boolean;
|
||||||
@ -341,7 +338,7 @@ const Message: FC<OwnProps & StateProps> = ({
|
|||||||
highlight,
|
highlight,
|
||||||
animatedEmoji,
|
animatedEmoji,
|
||||||
animatedCustomEmoji,
|
animatedCustomEmoji,
|
||||||
genericEffects,
|
hasActiveReactions,
|
||||||
hasLinkedChat,
|
hasLinkedChat,
|
||||||
isInSelectMode,
|
isInSelectMode,
|
||||||
isSelected,
|
isSelected,
|
||||||
@ -350,7 +347,6 @@ const Message: FC<OwnProps & StateProps> = ({
|
|||||||
reactionMessage,
|
reactionMessage,
|
||||||
availableReactions,
|
availableReactions,
|
||||||
defaultReaction,
|
defaultReaction,
|
||||||
activeReactions,
|
|
||||||
activeEmojiInteractions,
|
activeEmojiInteractions,
|
||||||
messageListType,
|
messageListType,
|
||||||
isPinnedList,
|
isPinnedList,
|
||||||
@ -371,7 +367,6 @@ const Message: FC<OwnProps & StateProps> = ({
|
|||||||
shouldDetectChatLanguage,
|
shouldDetectChatLanguage,
|
||||||
requestedTranslationLanguage,
|
requestedTranslationLanguage,
|
||||||
requestedChatTranslationLanguage,
|
requestedChatTranslationLanguage,
|
||||||
withReactionEffects,
|
|
||||||
withStickerEffects,
|
withStickerEffects,
|
||||||
webPageStory,
|
webPageStory,
|
||||||
isConnected,
|
isConnected,
|
||||||
@ -611,7 +606,7 @@ const Message: FC<OwnProps & StateProps> = ({
|
|||||||
isSwiped && 'is-swiped',
|
isSwiped && 'is-swiped',
|
||||||
transitionClassNames,
|
transitionClassNames,
|
||||||
isJustAdded && 'is-just-added',
|
isJustAdded && 'is-just-added',
|
||||||
(Boolean(activeReactions) || hasActiveStickerEffect) && 'has-active-reaction',
|
(hasActiveReactions || hasActiveStickerEffect) && 'has-active-reaction',
|
||||||
isStoryMention && 'is-story-mention',
|
isStoryMention && 'is-story-mention',
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -864,7 +859,7 @@ const Message: FC<OwnProps & StateProps> = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={buildClassName('quick-reaction', isQuickReactionVisible && !activeReactions && 'visible')}
|
className={buildClassName('quick-reaction', isQuickReactionVisible && !hasActiveReactions && 'visible')}
|
||||||
onClick={handleSendQuickReaction}
|
onClick={handleSendQuickReaction}
|
||||||
ref={quickReactionRef}
|
ref={quickReactionRef}
|
||||||
>
|
>
|
||||||
@ -877,7 +872,7 @@ const Message: FC<OwnProps & StateProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}, [
|
}, [
|
||||||
activeReactions, availableReactions, defaultReaction, handleSendQuickReaction, isQuickReactionVisible,
|
hasActiveReactions, availableReactions, defaultReaction, handleSendQuickReaction, isQuickReactionVisible,
|
||||||
observeIntersectionForPlaying,
|
observeIntersectionForPlaying,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@ -908,14 +903,10 @@ const Message: FC<OwnProps & StateProps> = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Reactions
|
<Reactions
|
||||||
activeReactions={activeReactions}
|
|
||||||
message={reactionMessage!}
|
message={reactionMessage!}
|
||||||
metaChildren={meta}
|
metaChildren={meta}
|
||||||
availableReactions={availableReactions}
|
|
||||||
genericEffects={genericEffects}
|
|
||||||
observeIntersection={observeIntersectionForPlaying}
|
observeIntersection={observeIntersectionForPlaying}
|
||||||
noRecentReactors={isChannel}
|
noRecentReactors={isChannel}
|
||||||
withEffects={withReactionEffects}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -1381,12 +1372,8 @@ const Message: FC<OwnProps & StateProps> = ({
|
|||||||
message={reactionMessage!}
|
message={reactionMessage!}
|
||||||
isOutside
|
isOutside
|
||||||
maxWidth={reactionsMaxWidth}
|
maxWidth={reactionsMaxWidth}
|
||||||
activeReactions={activeReactions}
|
|
||||||
availableReactions={availableReactions}
|
|
||||||
genericEffects={genericEffects}
|
|
||||||
observeIntersection={observeIntersectionForPlaying}
|
observeIntersection={observeIntersectionForPlaying}
|
||||||
noRecentReactors={isChannel}
|
noRecentReactors={isChannel}
|
||||||
withEffects={withReactionEffects}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@ -1437,7 +1424,7 @@ function MessageAppendix({ isOwn } : { isOwn: boolean }) {
|
|||||||
export default memo(withGlobal<OwnProps>(
|
export default memo(withGlobal<OwnProps>(
|
||||||
(global, ownProps): StateProps => {
|
(global, ownProps): StateProps => {
|
||||||
const {
|
const {
|
||||||
focusedMessage, forwardMessages, activeReactions, activeEmojiInteractions,
|
focusedMessage, forwardMessages, activeEmojiInteractions, activeReactions,
|
||||||
} = selectTabState(global);
|
} = selectTabState(global);
|
||||||
const {
|
const {
|
||||||
message, album, withSenderName, withAvatar, threadId, messageListType, isLastInDocumentGroup, isFirstInGroup,
|
message, album, withSenderName, withAvatar, threadId, messageListType, isLastInDocumentGroup, isFirstInGroup,
|
||||||
@ -1538,6 +1525,8 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
|
|
||||||
const isConnected = global.connectionState === 'connectionStateReady';
|
const isConnected = global.connectionState === 'connectionStateReady';
|
||||||
|
|
||||||
|
const hasActiveReactions = Boolean(reactionMessage && activeReactions[getMessageKey(reactionMessage)]?.length);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
theme: selectTheme(global),
|
theme: selectTheme(global),
|
||||||
chatUsernames,
|
chatUsernames,
|
||||||
@ -1584,7 +1573,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
availableReactions: global.availableReactions,
|
availableReactions: global.availableReactions,
|
||||||
defaultReaction: isMessageLocal(message) || messageListType === 'scheduled'
|
defaultReaction: isMessageLocal(message) || messageListType === 'scheduled'
|
||||||
? undefined : selectDefaultReaction(global, chatId),
|
? undefined : selectDefaultReaction(global, chatId),
|
||||||
activeReactions: reactionMessage && activeReactions[reactionMessage.id],
|
hasActiveReactions,
|
||||||
activeEmojiInteractions,
|
activeEmojiInteractions,
|
||||||
hasUnreadReaction,
|
hasUnreadReaction,
|
||||||
isTranscribing: transcriptionId !== undefined && global.transcriptions[transcriptionId]?.isPending,
|
isTranscribing: transcriptionId !== undefined && global.transcriptions[transcriptionId]?.isPending,
|
||||||
@ -1592,7 +1581,6 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
isPremium: selectIsCurrentUserPremium(global),
|
isPremium: selectIsCurrentUserPremium(global),
|
||||||
senderAdminMember,
|
senderAdminMember,
|
||||||
messageTopic,
|
messageTopic,
|
||||||
genericEffects: global.genericEmojiEffects,
|
|
||||||
hasTopicChip,
|
hasTopicChip,
|
||||||
chatTranslations,
|
chatTranslations,
|
||||||
areTranslationsEnabled,
|
areTranslationsEnabled,
|
||||||
@ -1600,7 +1588,6 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
requestedTranslationLanguage,
|
requestedTranslationLanguage,
|
||||||
requestedChatTranslationLanguage,
|
requestedChatTranslationLanguage,
|
||||||
hasLinkedChat: Boolean(chatFullInfo?.linkedChatId),
|
hasLinkedChat: Boolean(chatFullInfo?.linkedChatId),
|
||||||
withReactionEffects: selectPerformanceSettingsValue(global, 'reactionEffects'),
|
|
||||||
withStickerEffects: selectPerformanceSettingsValue(global, 'stickerEffects'),
|
withStickerEffects: selectPerformanceSettingsValue(global, 'stickerEffects'),
|
||||||
webPageStory,
|
webPageStory,
|
||||||
isConnected,
|
isConnected,
|
||||||
|
|||||||
@ -3,41 +3,35 @@ import React, { memo, useMemo } from '../../../lib/teact/teact';
|
|||||||
import { getActions, getGlobal } from '../../../global';
|
import { getActions, getGlobal } from '../../../global';
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
ApiAvailableReaction, ApiChat, ApiMessage, ApiReactionCount, ApiStickerSet, ApiUser,
|
ApiChat, ApiMessage, ApiReactionCount, ApiUser,
|
||||||
} from '../../../api/types';
|
} from '../../../api/types';
|
||||||
import type { ActiveReaction } from '../../../global/types';
|
|
||||||
import type { ObserveFn } from '../../../hooks/useIntersectionObserver';
|
import type { ObserveFn } from '../../../hooks/useIntersectionObserver';
|
||||||
|
|
||||||
import { isReactionChosen, isSameReaction } from '../../../global/helpers';
|
import { getMessageKey, isReactionChosen, isSameReaction } from '../../../global/helpers';
|
||||||
import buildClassName from '../../../util/buildClassName';
|
import buildClassName from '../../../util/buildClassName';
|
||||||
import { formatIntegerCompact } from '../../../util/textFormat';
|
import { formatIntegerCompact } from '../../../util/textFormat';
|
||||||
|
import { REM } from '../../common/helpers/mediaDimensions';
|
||||||
|
|
||||||
import useLastCallback from '../../../hooks/useLastCallback';
|
import useLastCallback from '../../../hooks/useLastCallback';
|
||||||
|
|
||||||
import AnimatedCounter from '../../common/AnimatedCounter';
|
import AnimatedCounter from '../../common/AnimatedCounter';
|
||||||
import AvatarList from '../../common/AvatarList';
|
import AvatarList from '../../common/AvatarList';
|
||||||
|
import ReactionAnimatedEmoji from '../../common/reactions/ReactionAnimatedEmoji';
|
||||||
import Button from '../../ui/Button';
|
import Button from '../../ui/Button';
|
||||||
import ReactionAnimatedEmoji from './ReactionAnimatedEmoji';
|
|
||||||
|
|
||||||
import './Reactions.scss';
|
import './Reactions.scss';
|
||||||
|
|
||||||
|
const REACTION_SIZE = 1.25 * REM;
|
||||||
|
|
||||||
const ReactionButton: FC<{
|
const ReactionButton: FC<{
|
||||||
reaction: ApiReactionCount;
|
reaction: ApiReactionCount;
|
||||||
message: ApiMessage;
|
message: ApiMessage;
|
||||||
activeReactions?: ActiveReaction[];
|
|
||||||
availableReactions?: ApiAvailableReaction[];
|
|
||||||
withRecentReactors?: boolean;
|
withRecentReactors?: boolean;
|
||||||
withEffects?: boolean;
|
|
||||||
genericEffects?: ApiStickerSet;
|
|
||||||
observeIntersection?: ObserveFn;
|
observeIntersection?: ObserveFn;
|
||||||
}> = ({
|
}> = ({
|
||||||
reaction,
|
reaction,
|
||||||
message,
|
message,
|
||||||
activeReactions,
|
|
||||||
availableReactions,
|
|
||||||
withRecentReactors,
|
withRecentReactors,
|
||||||
withEffects,
|
|
||||||
genericEffects,
|
|
||||||
observeIntersection,
|
observeIntersection,
|
||||||
}) => {
|
}) => {
|
||||||
const { toggleReaction } = getActions();
|
const { toggleReaction } = getActions();
|
||||||
@ -68,21 +62,22 @@ const ReactionButton: FC<{
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
className={buildClassName(isReactionChosen(reaction) && 'chosen')}
|
className={buildClassName(isReactionChosen(reaction) && 'chosen', 'message-reaction')}
|
||||||
size="tiny"
|
size="tiny"
|
||||||
onClick={handleClick}
|
onClick={handleClick}
|
||||||
>
|
>
|
||||||
<ReactionAnimatedEmoji
|
<ReactionAnimatedEmoji
|
||||||
activeReactions={activeReactions}
|
className="reaction-animated-emoji"
|
||||||
|
containerId={getMessageKey(message)}
|
||||||
reaction={reaction.reaction}
|
reaction={reaction.reaction}
|
||||||
availableReactions={availableReactions}
|
size={REACTION_SIZE}
|
||||||
genericEffects={genericEffects}
|
|
||||||
observeIntersection={observeIntersection}
|
observeIntersection={observeIntersection}
|
||||||
withEffects={withEffects}
|
|
||||||
/>
|
/>
|
||||||
{recentReactors?.length ? (
|
{recentReactors?.length ? (
|
||||||
<AvatarList size="mini" peers={recentReactors} />
|
<AvatarList size="mini" peers={recentReactors} />
|
||||||
) : <AnimatedCounter text={formatIntegerCompact(reaction.count)} className="counter" />}
|
) : (
|
||||||
|
<AnimatedCounter text={formatIntegerCompact(reaction.count)} className="counter" />
|
||||||
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -8,6 +8,7 @@
|
|||||||
max-width: calc(var(--max-width) + 2.25rem);
|
max-width: calc(var(--max-width) + 2.25rem);
|
||||||
|
|
||||||
.Button {
|
.Button {
|
||||||
|
--custom-emoji-size: 1.25rem;
|
||||||
--reaction-background: var(--color-reaction);
|
--reaction-background: var(--color-reaction);
|
||||||
--reaction-background-hover: var(--hover-color-reaction);
|
--reaction-background-hover: var(--hover-color-reaction);
|
||||||
--reaction-text-color: var(--text-color-reaction);
|
--reaction-text-color: var(--text-color-reaction);
|
||||||
@ -36,10 +37,6 @@
|
|||||||
|
|
||||||
transition: background-color 150ms, color 150ms, backdrop-filter 150ms, filter 150ms;
|
transition: background-color 150ms, color 150ms, backdrop-filter 150ms, filter 150ms;
|
||||||
|
|
||||||
.ReactionStaticEmoji {
|
|
||||||
width: 1.25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.avatars {
|
.avatars {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
||||||
@ -56,6 +53,14 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.message-reaction {
|
||||||
|
gap: 0.125rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reaction-animated-emoji {
|
||||||
|
margin: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
&.chosen {
|
&.chosen {
|
||||||
--reaction-background: var(--color-reaction-chosen);
|
--reaction-background: var(--color-reaction-chosen);
|
||||||
--reaction-background-hover: var(--hover-color-reaction-chosen);
|
--reaction-background-hover: var(--hover-color-reaction-chosen);
|
||||||
|
|||||||
@ -1,8 +1,7 @@
|
|||||||
import type { FC } from '../../../lib/teact/teact';
|
import type { FC } from '../../../lib/teact/teact';
|
||||||
import React, { memo, useMemo } from '../../../lib/teact/teact';
|
import React, { memo, useMemo } from '../../../lib/teact/teact';
|
||||||
|
|
||||||
import type { ApiAvailableReaction, ApiMessage, ApiStickerSet } from '../../../api/types';
|
import type { ApiMessage } from '../../../api/types';
|
||||||
import type { ActiveReaction } from '../../../global/types';
|
|
||||||
import type { ObserveFn } from '../../../hooks/useIntersectionObserver';
|
import type { ObserveFn } from '../../../hooks/useIntersectionObserver';
|
||||||
|
|
||||||
import { getReactionUniqueKey } from '../../../global/helpers';
|
import { getReactionUniqueKey } from '../../../global/helpers';
|
||||||
@ -18,13 +17,9 @@ type OwnProps = {
|
|||||||
message: ApiMessage;
|
message: ApiMessage;
|
||||||
isOutside?: boolean;
|
isOutside?: boolean;
|
||||||
maxWidth?: number;
|
maxWidth?: number;
|
||||||
activeReactions?: ActiveReaction[];
|
|
||||||
availableReactions?: ApiAvailableReaction[];
|
|
||||||
metaChildren?: React.ReactNode;
|
metaChildren?: React.ReactNode;
|
||||||
genericEffects?: ApiStickerSet;
|
|
||||||
observeIntersection?: ObserveFn;
|
observeIntersection?: ObserveFn;
|
||||||
noRecentReactors?: boolean;
|
noRecentReactors?: boolean;
|
||||||
withEffects?: boolean;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const MAX_RECENT_AVATARS = 3;
|
const MAX_RECENT_AVATARS = 3;
|
||||||
@ -33,13 +28,9 @@ const Reactions: FC<OwnProps> = ({
|
|||||||
message,
|
message,
|
||||||
isOutside,
|
isOutside,
|
||||||
maxWidth,
|
maxWidth,
|
||||||
activeReactions,
|
|
||||||
availableReactions,
|
|
||||||
metaChildren,
|
metaChildren,
|
||||||
genericEffects,
|
|
||||||
observeIntersection,
|
observeIntersection,
|
||||||
noRecentReactors,
|
noRecentReactors,
|
||||||
withEffects,
|
|
||||||
}) => {
|
}) => {
|
||||||
const lang = useLang();
|
const lang = useLang();
|
||||||
|
|
||||||
@ -58,12 +49,8 @@ const Reactions: FC<OwnProps> = ({
|
|||||||
key={getReactionUniqueKey(reaction.reaction)}
|
key={getReactionUniqueKey(reaction.reaction)}
|
||||||
reaction={reaction}
|
reaction={reaction}
|
||||||
message={message}
|
message={message}
|
||||||
activeReactions={activeReactions}
|
|
||||||
availableReactions={availableReactions}
|
|
||||||
withRecentReactors={totalCount <= MAX_RECENT_AVATARS && !noRecentReactors}
|
withRecentReactors={totalCount <= MAX_RECENT_AVATARS && !noRecentReactors}
|
||||||
genericEffects={genericEffects}
|
|
||||||
observeIntersection={observeIntersection}
|
observeIntersection={observeIntersection}
|
||||||
withEffects={withEffects}
|
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
{metaChildren}
|
{metaChildren}
|
||||||
|
|||||||
@ -1,11 +1,12 @@
|
|||||||
import React, {
|
import React, {
|
||||||
memo, useEffect, useLayoutEffect, useMemo, useRef, useState,
|
memo, useEffect, useLayoutEffect, useMemo, useRef, useState,
|
||||||
} from '../../lib/teact/teact';
|
} from '../../lib/teact/teact';
|
||||||
import { getGlobal, withGlobal } from '../../global';
|
import { getActions, getGlobal, withGlobal } from '../../global';
|
||||||
|
|
||||||
import type { ApiUserStories } from '../../api/types';
|
import type { ApiUserStories } from '../../api/types';
|
||||||
|
|
||||||
import { ANIMATION_END_DELAY } from '../../config';
|
import { ANIMATION_END_DELAY } from '../../config';
|
||||||
|
import { getStoryKey } from '../../global/helpers';
|
||||||
import { selectIsStoryViewerOpen, selectTabState, selectUser } from '../../global/selectors';
|
import { selectIsStoryViewerOpen, selectTabState, selectUser } from '../../global/selectors';
|
||||||
import buildClassName from '../../util/buildClassName';
|
import buildClassName from '../../util/buildClassName';
|
||||||
import buildStyle from '../../util/buildStyle';
|
import buildStyle from '../../util/buildStyle';
|
||||||
@ -66,6 +67,7 @@ function StorySlides({
|
|||||||
onClose,
|
onClose,
|
||||||
onReport,
|
onReport,
|
||||||
}: OwnProps & StateProps) {
|
}: OwnProps & StateProps) {
|
||||||
|
const { stopActiveReaction } = getActions();
|
||||||
const [renderingUserId, setRenderingUserId] = useState(currentUserId);
|
const [renderingUserId, setRenderingUserId] = useState(currentUserId);
|
||||||
const [renderingStoryId, setRenderingStoryId] = useState(currentStoryId);
|
const [renderingStoryId, setRenderingStoryId] = useState(currentStoryId);
|
||||||
const prevUserId = usePrevious(currentUserId);
|
const prevUserId = usePrevious(currentUserId);
|
||||||
@ -168,6 +170,15 @@ function StorySlides({
|
|||||||
};
|
};
|
||||||
}, [prevUserId, currentUserId, setIsAnimating]);
|
}, [prevUserId, currentUserId, setIsAnimating]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
return () => {
|
||||||
|
if (!currentStoryId || !currentUserId) return;
|
||||||
|
stopActiveReaction({
|
||||||
|
containerId: getStoryKey(currentUserId, currentStoryId),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}, [currentStoryId, currentUserId]);
|
||||||
|
|
||||||
const slideAmount = currentUserPosition - renderingUserPosition;
|
const slideAmount = currentUserPosition - renderingUserPosition;
|
||||||
const isBackward = renderingUserPosition > currentUserPosition;
|
const isBackward = renderingUserPosition > currentUserPosition;
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
import type { IDimensions } from '../../../global/types';
|
import type { IDimensions } from '../../../global/types';
|
||||||
|
|
||||||
|
import { roundToNearestEven } from '../../../util/math';
|
||||||
|
|
||||||
const BASE_SCREEN_WIDTH = 1200;
|
const BASE_SCREEN_WIDTH = 1200;
|
||||||
const BASE_SCREEN_HEIGHT = 800;
|
const BASE_SCREEN_HEIGHT = 800;
|
||||||
const BASE_ACTIVE_SLIDE_WIDTH = 405;
|
const BASE_ACTIVE_SLIDE_WIDTH = 405;
|
||||||
@ -56,8 +58,3 @@ function calculateScale(baseWidth: number, baseHeight: number, newWidth: number,
|
|||||||
|
|
||||||
return Math.min(widthScale, heightScale);
|
return Math.min(widthScale, heightScale);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fractional values cause blurry text. Round to even to keep whole numbers while centering
|
|
||||||
function roundToNearestEven(value: number) {
|
|
||||||
return Math.round(value / 2) * 2;
|
|
||||||
}
|
|
||||||
|
|||||||
@ -7,7 +7,10 @@ import * as mediaLoader from '../../../util/mediaLoader';
|
|||||||
import { callApi } from '../../../api/gramjs';
|
import { callApi } from '../../../api/gramjs';
|
||||||
import {
|
import {
|
||||||
getDocumentMediaHash,
|
getDocumentMediaHash,
|
||||||
getUserReactions, isMessageLocal, isSameReaction,
|
getMessageKey,
|
||||||
|
getUserReactions,
|
||||||
|
isMessageLocal,
|
||||||
|
isSameReaction,
|
||||||
} from '../../helpers';
|
} from '../../helpers';
|
||||||
import { addActionHandler, getGlobal, setGlobal } from '../../index';
|
import { addActionHandler, getGlobal, setGlobal } from '../../index';
|
||||||
import {
|
import {
|
||||||
@ -150,22 +153,14 @@ addActionHandler('toggleReaction', async (global, actions, payload): Promise<voi
|
|||||||
|
|
||||||
const limit = selectMaxUserReactions(global);
|
const limit = selectMaxUserReactions(global);
|
||||||
const reactions = newUserReactions.slice(-limit);
|
const reactions = newUserReactions.slice(-limit);
|
||||||
const tabState = selectTabState(global, tabId);
|
const messageKey = getMessageKey(message);
|
||||||
|
|
||||||
if (selectPerformanceSettingsValue(global, 'reactionEffects')) {
|
if (selectPerformanceSettingsValue(global, 'reactionEffects')) {
|
||||||
const newActiveReactions = hasReaction ? omit(tabState.activeReactions, [messageId]) : {
|
if (hasReaction) {
|
||||||
...tabState.activeReactions,
|
actions.stopActiveReaction({ containerId: messageKey, reaction, tabId });
|
||||||
[messageId]: [
|
} else {
|
||||||
...(tabState.activeReactions[messageId] || []),
|
actions.startActiveReaction({ containerId: messageKey, reaction, tabId });
|
||||||
{
|
}
|
||||||
messageId,
|
|
||||||
reaction,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
global = updateTabState(global, {
|
|
||||||
activeReactions: newActiveReactions,
|
|
||||||
}, tabId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
global = addMessageReaction(global, message, reactions);
|
global = addMessageReaction(global, message, reactions);
|
||||||
@ -185,21 +180,41 @@ addActionHandler('toggleReaction', async (global, actions, payload): Promise<voi
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
addActionHandler('stopActiveReaction', (global, actions, payload): ActionReturnType => {
|
addActionHandler('startActiveReaction', (global, actions, payload): ActionReturnType => {
|
||||||
const { messageId, reaction, tabId = getCurrentTabId() } = payload;
|
const { containerId, reaction, tabId = getCurrentTabId() } = payload;
|
||||||
|
|
||||||
const tabState = selectTabState(global, tabId);
|
const tabState = selectTabState(global, tabId);
|
||||||
if (!tabState.activeReactions[messageId]?.some((active) => isSameReaction(active.reaction, reaction))) {
|
|
||||||
return global;
|
if (!selectPerformanceSettingsValue(global, 'reactionEffects')) return undefined;
|
||||||
|
|
||||||
|
const currentActiveReactions = tabState.activeReactions[containerId] || [];
|
||||||
|
if (currentActiveReactions.some((active) => isSameReaction(active, reaction))) {
|
||||||
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const newMessageActiveReactions = tabState.activeReactions[messageId]
|
const newActiveReactions = currentActiveReactions.concat(reaction);
|
||||||
.filter((active) => !isSameReaction(active.reaction, reaction));
|
|
||||||
|
return updateTabState(global, {
|
||||||
|
activeReactions: {
|
||||||
|
...tabState.activeReactions,
|
||||||
|
[containerId]: newActiveReactions,
|
||||||
|
},
|
||||||
|
}, tabId);
|
||||||
|
});
|
||||||
|
|
||||||
|
addActionHandler('stopActiveReaction', (global, actions, payload): ActionReturnType => {
|
||||||
|
const { containerId, reaction, tabId = getCurrentTabId() } = payload;
|
||||||
|
|
||||||
|
const tabState = selectTabState(global, tabId);
|
||||||
|
|
||||||
|
const currentActiveReactions = tabState.activeReactions[containerId] || [];
|
||||||
|
// Remove all reactions if reaction is not specified
|
||||||
|
const newMessageActiveReactions = reaction
|
||||||
|
? currentActiveReactions.filter((active) => !isSameReaction(active, reaction)) : [];
|
||||||
|
|
||||||
const newActiveReactions = newMessageActiveReactions.length ? {
|
const newActiveReactions = newMessageActiveReactions.length ? {
|
||||||
...tabState.activeReactions,
|
...tabState.activeReactions,
|
||||||
[messageId]: newMessageActiveReactions,
|
[containerId]: newMessageActiveReactions,
|
||||||
} : omit(tabState.activeReactions, [messageId]);
|
} : omit(tabState.activeReactions, [containerId]);
|
||||||
|
|
||||||
return updateTabState(global, {
|
return updateTabState(global, {
|
||||||
activeReactions: newActiveReactions,
|
activeReactions: newActiveReactions,
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import { buildCollectionByKey } from '../../../util/iteratees';
|
|||||||
import { translate } from '../../../util/langProvider';
|
import { translate } from '../../../util/langProvider';
|
||||||
import { getServerTime } from '../../../util/serverTime';
|
import { getServerTime } from '../../../util/serverTime';
|
||||||
import { callApi } from '../../../api/gramjs';
|
import { callApi } from '../../../api/gramjs';
|
||||||
|
import { getStoryKey } from '../../helpers';
|
||||||
import { addActionHandler, getGlobal, setGlobal } from '../../index';
|
import { addActionHandler, getGlobal, setGlobal } from '../../index';
|
||||||
import {
|
import {
|
||||||
addStories,
|
addStories,
|
||||||
@ -428,7 +429,7 @@ addActionHandler('loadStoriesMaxIds', async (global, actions, payload): Promise<
|
|||||||
|
|
||||||
addActionHandler('sendStoryReaction', async (global, actions, payload): Promise<void> => {
|
addActionHandler('sendStoryReaction', async (global, actions, payload): Promise<void> => {
|
||||||
const {
|
const {
|
||||||
userId, storyId, reaction, shouldAddToRecent,
|
userId, storyId, reaction, shouldAddToRecent, tabId = getCurrentTabId(),
|
||||||
} = payload;
|
} = payload;
|
||||||
const user = selectUser(global, userId);
|
const user = selectUser(global, userId);
|
||||||
if (!user) return;
|
if (!user) return;
|
||||||
@ -442,6 +443,13 @@ addActionHandler('sendStoryReaction', async (global, actions, payload): Promise<
|
|||||||
});
|
});
|
||||||
setGlobal(global);
|
setGlobal(global);
|
||||||
|
|
||||||
|
const containerId = getStoryKey(userId, storyId);
|
||||||
|
if (reaction) {
|
||||||
|
actions.startActiveReaction({ containerId, reaction, tabId });
|
||||||
|
} else {
|
||||||
|
actions.stopActiveReaction({ containerId, tabId });
|
||||||
|
}
|
||||||
|
|
||||||
const result = await callApi('sendStoryReaction', {
|
const result = await callApi('sendStoryReaction', {
|
||||||
user, storyId, reaction, shouldAddToRecent,
|
user, storyId, reaction, shouldAddToRecent,
|
||||||
});
|
});
|
||||||
|
|||||||
@ -54,3 +54,7 @@ function getSizeParameter(size: StorySize) {
|
|||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getStoryKey(chatId: string, storyId: number) {
|
||||||
|
return `story${chatId}-${storyId}`;
|
||||||
|
}
|
||||||
|
|||||||
@ -132,11 +132,6 @@ export type IDimensions = {
|
|||||||
|
|
||||||
export type ApiPaymentStatus = 'paid' | 'failed' | 'pending' | 'cancelled';
|
export type ApiPaymentStatus = 'paid' | 'failed' | 'pending' | 'cancelled';
|
||||||
|
|
||||||
export interface ActiveReaction {
|
|
||||||
messageId?: number;
|
|
||||||
reaction?: ApiReaction;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface TabThread {
|
export interface TabThread {
|
||||||
scrollOffset?: number;
|
scrollOffset?: number;
|
||||||
replyStack?: number[];
|
replyStack?: number[];
|
||||||
@ -321,7 +316,7 @@ export type TabState = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
activeEmojiInteractions?: ActiveEmojiInteraction[];
|
activeEmojiInteractions?: ActiveEmojiInteraction[];
|
||||||
activeReactions: Record<number, ActiveReaction[]>;
|
activeReactions: Record<string, ApiReaction[]>;
|
||||||
|
|
||||||
localTextSearch: {
|
localTextSearch: {
|
||||||
byChatThreadKey: Record<string, {
|
byChatThreadKey: Record<string, {
|
||||||
@ -1927,10 +1922,14 @@ export interface ActionPayloads {
|
|||||||
enabledReactions?: ApiChatReactions;
|
enabledReactions?: ApiChatReactions;
|
||||||
} & WithTabId;
|
} & WithTabId;
|
||||||
|
|
||||||
stopActiveReaction: {
|
startActiveReaction: {
|
||||||
messageId: number;
|
containerId: string;
|
||||||
reaction: ApiReaction;
|
reaction: ApiReaction;
|
||||||
} & WithTabId;
|
} & WithTabId;
|
||||||
|
stopActiveReaction: {
|
||||||
|
containerId: string;
|
||||||
|
reaction?: ApiReaction;
|
||||||
|
} & WithTabId;
|
||||||
|
|
||||||
openMessageReactionPicker: {
|
openMessageReactionPicker: {
|
||||||
chatId: string;
|
chatId: string;
|
||||||
@ -2051,7 +2050,7 @@ export interface ActionPayloads {
|
|||||||
storyId: number;
|
storyId: number;
|
||||||
reaction?: ApiReaction;
|
reaction?: ApiReaction;
|
||||||
shouldAddToRecent?: boolean;
|
shouldAddToRecent?: boolean;
|
||||||
};
|
} & WithTabId;
|
||||||
toggleStealthModal: {
|
toggleStealthModal: {
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
} & WithTabId;
|
} & WithTabId;
|
||||||
|
|||||||
@ -4,3 +4,8 @@ export const round = (num: number, decimals: number = 0) => Math.round(num * 10
|
|||||||
export const lerp = (start: number, end: number, interpolationRatio: number) => {
|
export const lerp = (start: number, end: number, interpolationRatio: number) => {
|
||||||
return (1 - interpolationRatio) * start + interpolationRatio * end;
|
return (1 - interpolationRatio) * start + interpolationRatio * end;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Fractional values cause blurry text & canvas. Round to even to keep whole numbers while centering
|
||||||
|
export function roundToNearestEven(value: number) {
|
||||||
|
return Math.round(value / 2) * 2;
|
||||||
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user