diff --git a/src/components/common/Composer.scss b/src/components/common/Composer.scss index 2e556b1b2..510a67e4c 100644 --- a/src/components/common/Composer.scss +++ b/src/components/common/Composer.scss @@ -110,7 +110,8 @@ &.story-reaction-button { --custom-emoji-size: 1.5rem; - --custom-emoji-border-radius: 0.25rem; + + overflow: visible !important; &:hover { background-color: var(--color-background) !important; diff --git a/src/components/common/Composer.tsx b/src/components/common/Composer.tsx index ca2d47807..63a3de90a 100644 --- a/src/components/common/Composer.tsx +++ b/src/components/common/Composer.tsx @@ -26,7 +26,8 @@ import type { ApiVideo, } from '../../api/types'; import type { - ApiDraft, GlobalState, MessageList, MessageListType, TabState, + ApiDraft, GlobalState, MessageList, + MessageListType, TabState, } from '../../global/types'; import type { IAnchorPosition, InlineBotSettings, ISettings } from '../../types'; @@ -41,6 +42,7 @@ import { import { requestMeasure, requestNextMutation } from '../../lib/fasterdom/fasterdom'; import { getAllowedAttachmentOptions, + getStoryKey, isChatAdmin, isChatChannel, isChatSuperGroup, @@ -92,7 +94,6 @@ import applyIosAutoCapitalizationFix from '../middle/composer/helpers/applyIosAu import buildAttachment, { prepareAttachmentsToSend } from '../middle/composer/helpers/buildAttachment'; import { buildCustomEmojiHtml } from '../middle/composer/helpers/customEmoji'; import { isSelectionInsideInput } from '../middle/composer/helpers/selection'; -import { REM } from './helpers/mediaDimensions'; import renderText from './helpers/renderText'; import { getTextWithEntitiesAsHtml } from './helpers/renderTextWithEntities'; @@ -149,7 +150,7 @@ import ResponsiveHoverButton from '../ui/ResponsiveHoverButton'; import Spinner from '../ui/Spinner'; import Avatar from './Avatar'; import DeleteMessageModal from './DeleteMessageModal.async'; -import ReactionStaticEmoji from './ReactionStaticEmoji'; +import ReactionAnimatedEmoji from './reactions/ReactionAnimatedEmoji'; import './Composer.scss'; @@ -260,7 +261,6 @@ const MESSAGE_MAX_LENGTH = 4096; const SENDING_ANIMATION_DURATION = 350; const MOUNT_ANIMATION_DURATION = 430; -const REACTION_SIZE = 1.5 * REM; const HEART_REACTION: ApiReaction = { emoticon: '❤', }; @@ -1293,8 +1293,8 @@ const Composer: FC = ({ || isMentionTooltipOpen || isInlineBotTooltipOpen || isDeleteModalOpen || isBotCommandMenuOpen || isAttachMenuOpen || isStickerTooltipOpen || isBotCommandTooltipOpen || isCustomEmojiTooltipOpen || isBotMenuButtonOpen || isCustomSendMenuOpen || Boolean(activeVoiceRecording) || attachments.length > 0 || isInputHasFocus; - const isReactionSelectorOpen = (isComposerHasFocus || isReactionPickerOpen) - && isInStoryViewer && !isAttachMenuOpen && !isSymbolMenuOpen; + const isReactionSelectorOpen = isComposerHasFocus && !isReactionPickerOpen && isInStoryViewer && !isAttachMenuOpen + && !isSymbolMenuOpen; useEffect(() => { if (isComposerHasFocus) { @@ -1814,13 +1814,14 @@ const Composer: FC = ({ ariaLabel={lang('AccDescrLike')} ref={storyReactionRef} > - {sentStoryReaction && !isSentStoryReactionHeart ? ( - - ) : ( + )} + {(!sentStoryReaction || isSentStoryReactionHeart) && ( = ({ className={transitionClassNames} sharedCanvas={sharedCanvasRef!.current || undefined} sharedCanvasCoords={coords} + forceAlways={forcePlayback} /> )} diff --git a/src/components/middle/message/CustomEmojiEffect.module.scss b/src/components/common/reactions/CustomEmojiEffect.module.scss similarity index 100% rename from src/components/middle/message/CustomEmojiEffect.module.scss rename to src/components/common/reactions/CustomEmojiEffect.module.scss diff --git a/src/components/middle/message/CustomEmojiEffect.tsx b/src/components/common/reactions/CustomEmojiEffect.tsx similarity index 97% rename from src/components/middle/message/CustomEmojiEffect.tsx rename to src/components/common/reactions/CustomEmojiEffect.tsx index 5d6566e2d..fbc52ca70 100644 --- a/src/components/middle/message/CustomEmojiEffect.tsx +++ b/src/components/common/reactions/CustomEmojiEffect.tsx @@ -9,7 +9,7 @@ import { IS_OFFSET_PATH_SUPPORTED } from '../../../util/windowEnvironment'; import useMedia from '../../../hooks/useMedia'; -import CustomEmoji from '../../common/CustomEmoji'; +import CustomEmoji from '../CustomEmoji'; import styles from './CustomEmojiEffect.module.scss'; diff --git a/src/components/middle/message/ReactionAnimatedEmoji.module.scss b/src/components/common/reactions/ReactionAnimatedEmoji.module.scss similarity index 59% rename from src/components/middle/message/ReactionAnimatedEmoji.module.scss rename to src/components/common/reactions/ReactionAnimatedEmoji.module.scss index 3fc749f4e..7189da4a1 100644 --- a/src/components/middle/message/ReactionAnimatedEmoji.module.scss +++ b/src/components/common/reactions/ReactionAnimatedEmoji.module.scss @@ -6,23 +6,27 @@ align-items: center; justify-content: center; - width: 1.75rem; - height: 1.75rem; - margin-inline-end: 0.125rem; + width: var(--custom-emoji-size); + height: var(--custom-emoji-size); z-index: 1; } -.animated-icon, .effect { - position: fixed; - top: -0.375rem; - left: -0.375rem; - pointer-events: none; +.animated-icon { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); +} - &.effect { - top: -2.25rem; - left: -2.25rem; - } +.effect { + position: fixed; + top: -2.25rem; + left: -2.25rem; +} + +.animated-icon, .effect { + pointer-events: none; &:not(:global(.open)) { opacity: 1 !important; @@ -39,3 +43,10 @@ // Fix for redundant scroll in Firefox contain: layout; } + +.withEffectOnly { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); +} diff --git a/src/components/middle/message/ReactionAnimatedEmoji.tsx b/src/components/common/reactions/ReactionAnimatedEmoji.tsx similarity index 63% rename from src/components/middle/message/ReactionAnimatedEmoji.tsx rename to src/components/common/reactions/ReactionAnimatedEmoji.tsx index 1e13f65ef..c8319dcd0 100644 --- a/src/components/middle/message/ReactionAnimatedEmoji.tsx +++ b/src/components/common/reactions/ReactionAnimatedEmoji.tsx @@ -1,51 +1,65 @@ -import type { FC } from '../../../lib/teact/teact'; import React, { memo, useMemo, useRef, } from '../../../lib/teact/teact'; -import { getActions } from '../../../global'; +import { getActions, withGlobal } from '../../../global'; import type { ApiAvailableReaction, ApiReaction, ApiStickerSet } from '../../../api/types'; -import type { ActiveReaction } from '../../../global/types'; import type { ObserveFn } from '../../../hooks/useIntersectionObserver'; import { isSameReaction } from '../../../global/helpers'; +import { selectPerformanceSettingsValue, selectTabState } from '../../../global/selectors'; 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 { useIsIntersecting } from '../../../hooks/useIntersectionObserver'; import useLastCallback from '../../../hooks/useLastCallback'; import useMedia from '../../../hooks/useMedia'; import useShowTransition from '../../../hooks/useShowTransition'; -import useCustomEmoji from '../../common/hooks/useCustomEmoji'; +import useCustomEmoji from '../hooks/useCustomEmoji'; -import AnimatedSticker from '../../common/AnimatedSticker'; -import CustomEmoji from '../../common/CustomEmoji'; -import ReactionStaticEmoji from '../../common/ReactionStaticEmoji'; +import AnimatedSticker from '../AnimatedSticker'; +import CustomEmoji from '../CustomEmoji'; +import ReactionStaticEmoji from '../ReactionStaticEmoji'; import CustomEmojiEffect from './CustomEmojiEffect'; import styles from './ReactionAnimatedEmoji.module.scss'; type OwnProps = { + containerId: string; reaction: ApiReaction; - activeReactions?: ActiveReaction[]; + className?: string; + size?: number; + effectSize?: number; + withEffectOnly?: boolean; + observeIntersection?: ObserveFn; +}; + +type StateProps = { + activeReactions?: ApiReaction[]; availableReactions?: ApiAvailableReaction[]; genericEffects?: ApiStickerSet; - observeIntersection?: ObserveFn; 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 ReactionAnimatedEmoji: FC = ({ +const ReactionAnimatedEmoji = ({ + containerId, reaction, - genericEffects, + className, + size = ICON_SIZE, + effectSize = EFFECT_SIZE, activeReactions, availableReactions, - observeIntersection, + genericEffects, withEffects, -}) => { + withEffectOnly, + observeIntersection, +}: OwnProps & StateProps) => { const { stopActiveReaction } = getActions(); // eslint-disable-next-line no-null/no-null @@ -93,7 +107,7 @@ const ReactionAnimatedEmoji: FC = ({ const mediaDataEffect = useMedia(mediaHashEffect, !effectId); const activeReaction = useMemo(() => ( - activeReactions?.find((active) => isSameReaction(active.reaction, reaction)) + activeReactions?.find((active) => isSameReaction(active, reaction)) ), [activeReactions, reaction]); const shouldPlay = Boolean(withEffects && activeReaction && (isCustom || mediaDataCenterIcon) && mediaDataEffect); @@ -103,26 +117,39 @@ const ReactionAnimatedEmoji: FC = ({ } = useShowTransition(shouldPlay, undefined, true, 'slow'); const handleEnded = useLastCallback(() => { - if (!activeReaction?.messageId) return; - stopActiveReaction({ messageId: activeReaction.messageId, reaction }); + stopActiveReaction({ containerId, reaction }); }); 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, shouldRenderAnimation && styles.animating, - isCustom && styles.isCustomEmoji, + withEffectOnly && styles.withEffectOnly, + className, ); return ( -
- {shouldRenderStatic && } - {isCustom && ( +
+ {!withEffectOnly && shouldRenderStatic && ( + + )} + {!withEffectOnly && isCustom && ( )} @@ -131,20 +158,19 @@ const ReactionAnimatedEmoji: FC = ({ - {isCustom ? ( - !assignedEffectId && isIntersecting && - ) : ( + {isCustom && !assignedEffectId && isIntersecting && } + {!isCustom && !withEffectOnly && ( = ({ ); }; -export default memo(ReactionAnimatedEmoji); +export default memo(withGlobal( + (global, { containerId }) => { + const { availableReactions, genericEmojiEffects } = global; + const { activeReactions } = selectTabState(global); + + const withEffects = selectPerformanceSettingsValue(global, 'reactionEffects'); + + return { + activeReactions: activeReactions?.[containerId], + availableReactions, + genericEffects: genericEmojiEffects, + withEffects, + }; + }, +)(ReactionAnimatedEmoji)); diff --git a/src/components/left/main/StatusButton.tsx b/src/components/left/main/StatusButton.tsx index 3970c127d..9a2bc0aeb 100644 --- a/src/components/left/main/StatusButton.tsx +++ b/src/components/left/main/StatusButton.tsx @@ -15,7 +15,7 @@ import useTimeout from '../../../hooks/useTimeout'; import CustomEmoji from '../../common/CustomEmoji'; import PremiumIcon from '../../common/PremiumIcon'; -import CustomEmojiEffect from '../../middle/message/CustomEmojiEffect'; +import CustomEmojiEffect from '../../common/reactions/CustomEmojiEffect'; import Button from '../../ui/Button'; import StatusPickerMenu from './StatusPickerMenu.async'; diff --git a/src/components/middle/message/Message.tsx b/src/components/middle/message/Message.tsx index 4a31924cc..b1a9310ed 100644 --- a/src/components/middle/message/Message.tsx +++ b/src/components/middle/message/Message.tsx @@ -11,7 +11,6 @@ import type { ApiMessage, ApiMessageOutgoingStatus, ApiReaction, - ApiStickerSet, ApiThreadInfo, ApiTopic, ApiTypeStory, @@ -20,7 +19,6 @@ import type { } from '../../../api/types'; import type { ActiveEmojiInteraction, - ActiveReaction, ChatTranslatedMessages, MessageListType, } from '../../../global/types'; @@ -37,6 +35,7 @@ import { getMessageContent, getMessageCustomShape, getMessageHtmlId, + getMessageKey, getMessageLocation, getMessageSingleCustomEmoji, getMessageSingleRegularEmoji, @@ -230,7 +229,7 @@ type StateProps = { highlight?: string; animatedEmoji?: string; animatedCustomEmoji?: string; - genericEffects?: ApiStickerSet; + hasActiveReactions?: boolean; isInSelectMode?: boolean; isSelected?: boolean; isGroupSelected?: boolean; @@ -247,7 +246,6 @@ type StateProps = { reactionMessage?: ApiMessage; availableReactions?: ApiAvailableReaction[]; defaultReaction?: ApiReaction; - activeReactions?: ActiveReaction[]; activeEmojiInteractions?: ActiveEmojiInteraction[]; hasUnreadReaction?: boolean; isTranscribing?: boolean; @@ -262,7 +260,6 @@ type StateProps = { shouldDetectChatLanguage?: boolean; requestedTranslationLanguage?: string; requestedChatTranslationLanguage?: string; - withReactionEffects?: boolean; withStickerEffects?: boolean; webPageStory?: ApiTypeStory; isConnected: boolean; @@ -341,7 +338,7 @@ const Message: FC = ({ highlight, animatedEmoji, animatedCustomEmoji, - genericEffects, + hasActiveReactions, hasLinkedChat, isInSelectMode, isSelected, @@ -350,7 +347,6 @@ const Message: FC = ({ reactionMessage, availableReactions, defaultReaction, - activeReactions, activeEmojiInteractions, messageListType, isPinnedList, @@ -371,7 +367,6 @@ const Message: FC = ({ shouldDetectChatLanguage, requestedTranslationLanguage, requestedChatTranslationLanguage, - withReactionEffects, withStickerEffects, webPageStory, isConnected, @@ -611,7 +606,7 @@ const Message: FC = ({ isSwiped && 'is-swiped', transitionClassNames, isJustAdded && 'is-just-added', - (Boolean(activeReactions) || hasActiveStickerEffect) && 'has-active-reaction', + (hasActiveReactions || hasActiveStickerEffect) && 'has-active-reaction', isStoryMention && 'is-story-mention', ); @@ -864,7 +859,7 @@ const Message: FC = ({ return (
@@ -877,7 +872,7 @@ const Message: FC = ({
); }, [ - activeReactions, availableReactions, defaultReaction, handleSendQuickReaction, isQuickReactionVisible, + hasActiveReactions, availableReactions, defaultReaction, handleSendQuickReaction, isQuickReactionVisible, observeIntersectionForPlaying, ]); @@ -908,14 +903,10 @@ const Message: FC = ({ return ( ); } @@ -1381,12 +1372,8 @@ const Message: FC = ({ message={reactionMessage!} isOutside maxWidth={reactionsMaxWidth} - activeReactions={activeReactions} - availableReactions={availableReactions} - genericEffects={genericEffects} observeIntersection={observeIntersectionForPlaying} noRecentReactors={isChannel} - withEffects={withReactionEffects} /> )}
@@ -1437,7 +1424,7 @@ function MessageAppendix({ isOwn } : { isOwn: boolean }) { export default memo(withGlobal( (global, ownProps): StateProps => { const { - focusedMessage, forwardMessages, activeReactions, activeEmojiInteractions, + focusedMessage, forwardMessages, activeEmojiInteractions, activeReactions, } = selectTabState(global); const { message, album, withSenderName, withAvatar, threadId, messageListType, isLastInDocumentGroup, isFirstInGroup, @@ -1538,6 +1525,8 @@ export default memo(withGlobal( const isConnected = global.connectionState === 'connectionStateReady'; + const hasActiveReactions = Boolean(reactionMessage && activeReactions[getMessageKey(reactionMessage)]?.length); + return { theme: selectTheme(global), chatUsernames, @@ -1584,7 +1573,7 @@ export default memo(withGlobal( availableReactions: global.availableReactions, defaultReaction: isMessageLocal(message) || messageListType === 'scheduled' ? undefined : selectDefaultReaction(global, chatId), - activeReactions: reactionMessage && activeReactions[reactionMessage.id], + hasActiveReactions, activeEmojiInteractions, hasUnreadReaction, isTranscribing: transcriptionId !== undefined && global.transcriptions[transcriptionId]?.isPending, @@ -1592,7 +1581,6 @@ export default memo(withGlobal( isPremium: selectIsCurrentUserPremium(global), senderAdminMember, messageTopic, - genericEffects: global.genericEmojiEffects, hasTopicChip, chatTranslations, areTranslationsEnabled, @@ -1600,7 +1588,6 @@ export default memo(withGlobal( requestedTranslationLanguage, requestedChatTranslationLanguage, hasLinkedChat: Boolean(chatFullInfo?.linkedChatId), - withReactionEffects: selectPerformanceSettingsValue(global, 'reactionEffects'), withStickerEffects: selectPerformanceSettingsValue(global, 'stickerEffects'), webPageStory, isConnected, diff --git a/src/components/middle/message/ReactionButton.tsx b/src/components/middle/message/ReactionButton.tsx index d16ca0b69..fee069e97 100644 --- a/src/components/middle/message/ReactionButton.tsx +++ b/src/components/middle/message/ReactionButton.tsx @@ -3,41 +3,35 @@ import React, { memo, useMemo } from '../../../lib/teact/teact'; import { getActions, getGlobal } from '../../../global'; import type { - ApiAvailableReaction, ApiChat, ApiMessage, ApiReactionCount, ApiStickerSet, ApiUser, + ApiChat, ApiMessage, ApiReactionCount, ApiUser, } from '../../../api/types'; -import type { ActiveReaction } from '../../../global/types'; 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 { formatIntegerCompact } from '../../../util/textFormat'; +import { REM } from '../../common/helpers/mediaDimensions'; import useLastCallback from '../../../hooks/useLastCallback'; import AnimatedCounter from '../../common/AnimatedCounter'; import AvatarList from '../../common/AvatarList'; +import ReactionAnimatedEmoji from '../../common/reactions/ReactionAnimatedEmoji'; import Button from '../../ui/Button'; -import ReactionAnimatedEmoji from './ReactionAnimatedEmoji'; import './Reactions.scss'; +const REACTION_SIZE = 1.25 * REM; + const ReactionButton: FC<{ reaction: ApiReactionCount; message: ApiMessage; - activeReactions?: ActiveReaction[]; - availableReactions?: ApiAvailableReaction[]; withRecentReactors?: boolean; - withEffects?: boolean; - genericEffects?: ApiStickerSet; observeIntersection?: ObserveFn; }> = ({ reaction, message, - activeReactions, - availableReactions, withRecentReactors, - withEffects, - genericEffects, observeIntersection, }) => { const { toggleReaction } = getActions(); @@ -68,21 +62,22 @@ const ReactionButton: FC<{ return ( ); }; diff --git a/src/components/middle/message/Reactions.scss b/src/components/middle/message/Reactions.scss index e3146aab0..f60269b5b 100644 --- a/src/components/middle/message/Reactions.scss +++ b/src/components/middle/message/Reactions.scss @@ -8,6 +8,7 @@ max-width: calc(var(--max-width) + 2.25rem); .Button { + --custom-emoji-size: 1.25rem; --reaction-background: var(--color-reaction); --reaction-background-hover: var(--hover-color-reaction); --reaction-text-color: var(--text-color-reaction); @@ -36,10 +37,6 @@ transition: background-color 150ms, color 150ms, backdrop-filter 150ms, filter 150ms; - .ReactionStaticEmoji { - width: 1.25rem; - } - .avatars { display: flex; @@ -56,6 +53,14 @@ } } + &.message-reaction { + gap: 0.125rem; + } + + .reaction-animated-emoji { + margin: 0.25rem; + } + &.chosen { --reaction-background: var(--color-reaction-chosen); --reaction-background-hover: var(--hover-color-reaction-chosen); diff --git a/src/components/middle/message/Reactions.tsx b/src/components/middle/message/Reactions.tsx index 952a652e7..08ffd28cd 100644 --- a/src/components/middle/message/Reactions.tsx +++ b/src/components/middle/message/Reactions.tsx @@ -1,8 +1,7 @@ import type { FC } from '../../../lib/teact/teact'; import React, { memo, useMemo } from '../../../lib/teact/teact'; -import type { ApiAvailableReaction, ApiMessage, ApiStickerSet } from '../../../api/types'; -import type { ActiveReaction } from '../../../global/types'; +import type { ApiMessage } from '../../../api/types'; import type { ObserveFn } from '../../../hooks/useIntersectionObserver'; import { getReactionUniqueKey } from '../../../global/helpers'; @@ -18,13 +17,9 @@ type OwnProps = { message: ApiMessage; isOutside?: boolean; maxWidth?: number; - activeReactions?: ActiveReaction[]; - availableReactions?: ApiAvailableReaction[]; metaChildren?: React.ReactNode; - genericEffects?: ApiStickerSet; observeIntersection?: ObserveFn; noRecentReactors?: boolean; - withEffects?: boolean; }; const MAX_RECENT_AVATARS = 3; @@ -33,13 +28,9 @@ const Reactions: FC = ({ message, isOutside, maxWidth, - activeReactions, - availableReactions, metaChildren, - genericEffects, observeIntersection, noRecentReactors, - withEffects, }) => { const lang = useLang(); @@ -58,12 +49,8 @@ const Reactions: FC = ({ key={getReactionUniqueKey(reaction.reaction)} reaction={reaction} message={message} - activeReactions={activeReactions} - availableReactions={availableReactions} withRecentReactors={totalCount <= MAX_RECENT_AVATARS && !noRecentReactors} - genericEffects={genericEffects} observeIntersection={observeIntersection} - withEffects={withEffects} /> ))} {metaChildren} diff --git a/src/components/story/StorySlides.tsx b/src/components/story/StorySlides.tsx index eb407299f..d2996a3c9 100644 --- a/src/components/story/StorySlides.tsx +++ b/src/components/story/StorySlides.tsx @@ -1,11 +1,12 @@ import React, { memo, useEffect, useLayoutEffect, useMemo, useRef, useState, } from '../../lib/teact/teact'; -import { getGlobal, withGlobal } from '../../global'; +import { getActions, getGlobal, withGlobal } from '../../global'; import type { ApiUserStories } from '../../api/types'; import { ANIMATION_END_DELAY } from '../../config'; +import { getStoryKey } from '../../global/helpers'; import { selectIsStoryViewerOpen, selectTabState, selectUser } from '../../global/selectors'; import buildClassName from '../../util/buildClassName'; import buildStyle from '../../util/buildStyle'; @@ -66,6 +67,7 @@ function StorySlides({ onClose, onReport, }: OwnProps & StateProps) { + const { stopActiveReaction } = getActions(); const [renderingUserId, setRenderingUserId] = useState(currentUserId); const [renderingStoryId, setRenderingStoryId] = useState(currentStoryId); const prevUserId = usePrevious(currentUserId); @@ -168,6 +170,15 @@ function StorySlides({ }; }, [prevUserId, currentUserId, setIsAnimating]); + useEffect(() => { + return () => { + if (!currentStoryId || !currentUserId) return; + stopActiveReaction({ + containerId: getStoryKey(currentUserId, currentStoryId), + }); + }; + }, [currentStoryId, currentUserId]); + const slideAmount = currentUserPosition - renderingUserPosition; const isBackward = renderingUserPosition > currentUserPosition; diff --git a/src/components/story/helpers/dimensions.ts b/src/components/story/helpers/dimensions.ts index 74e561193..b06c7da25 100644 --- a/src/components/story/helpers/dimensions.ts +++ b/src/components/story/helpers/dimensions.ts @@ -1,5 +1,7 @@ import type { IDimensions } from '../../../global/types'; +import { roundToNearestEven } from '../../../util/math'; + const BASE_SCREEN_WIDTH = 1200; const BASE_SCREEN_HEIGHT = 800; const BASE_ACTIVE_SLIDE_WIDTH = 405; @@ -56,8 +58,3 @@ function calculateScale(baseWidth: number, baseHeight: number, newWidth: number, 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; -} diff --git a/src/global/actions/api/reactions.ts b/src/global/actions/api/reactions.ts index 00b6dd81e..26a0c7cf9 100644 --- a/src/global/actions/api/reactions.ts +++ b/src/global/actions/api/reactions.ts @@ -7,7 +7,10 @@ import * as mediaLoader from '../../../util/mediaLoader'; import { callApi } from '../../../api/gramjs'; import { getDocumentMediaHash, - getUserReactions, isMessageLocal, isSameReaction, + getMessageKey, + getUserReactions, + isMessageLocal, + isSameReaction, } from '../../helpers'; import { addActionHandler, getGlobal, setGlobal } from '../../index'; import { @@ -150,22 +153,14 @@ addActionHandler('toggleReaction', async (global, actions, payload): Promise { - const { messageId, reaction, tabId = getCurrentTabId() } = payload; - +addActionHandler('startActiveReaction', (global, actions, payload): ActionReturnType => { + const { containerId, reaction, tabId = getCurrentTabId() } = payload; 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] - .filter((active) => !isSameReaction(active.reaction, reaction)); + const newActiveReactions = currentActiveReactions.concat(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 ? { ...tabState.activeReactions, - [messageId]: newMessageActiveReactions, - } : omit(tabState.activeReactions, [messageId]); + [containerId]: newMessageActiveReactions, + } : omit(tabState.activeReactions, [containerId]); return updateTabState(global, { activeReactions: newActiveReactions, diff --git a/src/global/actions/api/stories.ts b/src/global/actions/api/stories.ts index d32db6c72..fe90baa18 100644 --- a/src/global/actions/api/stories.ts +++ b/src/global/actions/api/stories.ts @@ -6,6 +6,7 @@ import { buildCollectionByKey } from '../../../util/iteratees'; import { translate } from '../../../util/langProvider'; import { getServerTime } from '../../../util/serverTime'; import { callApi } from '../../../api/gramjs'; +import { getStoryKey } from '../../helpers'; import { addActionHandler, getGlobal, setGlobal } from '../../index'; import { addStories, @@ -428,7 +429,7 @@ addActionHandler('loadStoriesMaxIds', async (global, actions, payload): Promise< addActionHandler('sendStoryReaction', async (global, actions, payload): Promise => { const { - userId, storyId, reaction, shouldAddToRecent, + userId, storyId, reaction, shouldAddToRecent, tabId = getCurrentTabId(), } = payload; const user = selectUser(global, userId); if (!user) return; @@ -442,6 +443,13 @@ addActionHandler('sendStoryReaction', async (global, actions, payload): Promise< }); setGlobal(global); + const containerId = getStoryKey(userId, storyId); + if (reaction) { + actions.startActiveReaction({ containerId, reaction, tabId }); + } else { + actions.stopActiveReaction({ containerId, tabId }); + } + const result = await callApi('sendStoryReaction', { user, storyId, reaction, shouldAddToRecent, }); diff --git a/src/global/helpers/media.ts b/src/global/helpers/media.ts index db51ff367..69ba549d2 100644 --- a/src/global/helpers/media.ts +++ b/src/global/helpers/media.ts @@ -54,3 +54,7 @@ function getSizeParameter(size: StorySize) { return ''; } } + +export function getStoryKey(chatId: string, storyId: number) { + return `story${chatId}-${storyId}`; +} diff --git a/src/global/types.ts b/src/global/types.ts index 41af1c31b..7fc77704b 100644 --- a/src/global/types.ts +++ b/src/global/types.ts @@ -132,11 +132,6 @@ export type IDimensions = { export type ApiPaymentStatus = 'paid' | 'failed' | 'pending' | 'cancelled'; -export interface ActiveReaction { - messageId?: number; - reaction?: ApiReaction; -} - export interface TabThread { scrollOffset?: number; replyStack?: number[]; @@ -321,7 +316,7 @@ export type TabState = { }; activeEmojiInteractions?: ActiveEmojiInteraction[]; - activeReactions: Record; + activeReactions: Record; localTextSearch: { byChatThreadKey: Record Math.round(num * 10 export const lerp = (start: number, end: number, interpolationRatio: number) => { 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; +}