Message: Send effects (#4727)
This commit is contained in:
parent
e4c21c5b56
commit
4768a8103c
@ -803,6 +803,7 @@ export function buildLocalMessage(
|
||||
sendAs?: ApiPeer,
|
||||
story?: ApiStory | ApiStorySkipped,
|
||||
isInvertedMedia?: true,
|
||||
effectId?: string,
|
||||
): ApiMessage {
|
||||
const localId = getNextLocalMessageId(lastMessageId);
|
||||
const media = attachment && buildUploadingMedia(attachment);
|
||||
@ -838,6 +839,7 @@ export function buildLocalMessage(
|
||||
...(scheduledAt && { isScheduled: true }),
|
||||
isForwardingAllowed: true,
|
||||
isInvertedMedia,
|
||||
effectId,
|
||||
} satisfies ApiMessage;
|
||||
|
||||
const emojiOnlyCount = getEmojiOnlyCountForMessage(message.content, message.groupedId);
|
||||
|
||||
@ -9,7 +9,8 @@ import { compact } from '../../../util/iteratees';
|
||||
import localDb from '../localDb';
|
||||
import { buildApiThumbnailFromCached, buildApiThumbnailFromPath } from './common';
|
||||
|
||||
export function buildStickerFromDocument(document: GramJs.TypeDocument, isNoPremium?: boolean): ApiSticker | undefined {
|
||||
export function buildStickerFromDocument(document: GramJs.TypeDocument,
|
||||
isNoPremium?: boolean, isPremium?: boolean): ApiSticker | undefined {
|
||||
if (document instanceof GramJs.DocumentEmpty) {
|
||||
return undefined;
|
||||
}
|
||||
@ -46,7 +47,7 @@ export function buildStickerFromDocument(document: GramJs.TypeDocument, isNoPrem
|
||||
const stickerOrEmojiAttribute = (stickerAttribute || customEmojiAttribute)!;
|
||||
const stickerSetInfo = buildApiStickerSetInfo(stickerOrEmojiAttribute?.stickerset);
|
||||
const emoji = stickerOrEmojiAttribute?.alt;
|
||||
const isFree = Boolean(customEmojiAttribute?.free ?? true);
|
||||
const isFree = Boolean(customEmojiAttribute?.free ?? true) && !isPremium;
|
||||
|
||||
const cachedThumb = document.thumbs && document.thumbs.find(
|
||||
(s): s is GramJs.PhotoCachedSize => s instanceof GramJs.PhotoCachedSize,
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import BigInt from 'big-integer';
|
||||
import { Api as GramJs } from '../../../lib/gramjs';
|
||||
|
||||
import type { ThreadId } from '../../../types';
|
||||
@ -270,6 +271,7 @@ export function sendMessage(
|
||||
shouldUpdateStickerSetOrder,
|
||||
wasDrafted,
|
||||
isInvertedMedia,
|
||||
effectId,
|
||||
}: {
|
||||
chat: ApiChat;
|
||||
lastMessageId?: number;
|
||||
@ -290,6 +292,7 @@ export function sendMessage(
|
||||
shouldUpdateStickerSetOrder?: boolean;
|
||||
wasDrafted?: boolean;
|
||||
isInvertedMedia?: true;
|
||||
effectId?: string;
|
||||
},
|
||||
onProgress?: ApiOnProgress,
|
||||
) {
|
||||
@ -309,6 +312,7 @@ export function sendMessage(
|
||||
sendAs,
|
||||
story,
|
||||
isInvertedMedia,
|
||||
effectId,
|
||||
);
|
||||
|
||||
onUpdate({
|
||||
@ -396,6 +400,7 @@ export function sendMessage(
|
||||
...(sendAs && { sendAs: buildInputPeer(sendAs.id, sendAs.accessHash) }),
|
||||
...(shouldUpdateStickerSetOrder && { updateStickersetsOrder: shouldUpdateStickerSetOrder }),
|
||||
...(isInvertedMedia && { invertMedia: isInvertedMedia }),
|
||||
...(effectId && { effect: BigInt(effectId) }),
|
||||
}), {
|
||||
shouldThrow: true,
|
||||
shouldIgnoreUpdates: true,
|
||||
|
||||
@ -1,7 +1,9 @@
|
||||
import BigInt from 'big-integer';
|
||||
import { Api as GramJs } from '../../../lib/gramjs';
|
||||
|
||||
import type { ApiChat, ApiReaction } from '../../types';
|
||||
import type {
|
||||
ApiChat, ApiReaction, ApiSticker,
|
||||
} from '../../types';
|
||||
|
||||
import {
|
||||
API_GENERAL_ID_LIMIT,
|
||||
@ -18,6 +20,7 @@ import {
|
||||
buildApiSavedReactionTag,
|
||||
buildMessagePeerReaction,
|
||||
} from '../apiBuilders/reactions';
|
||||
import { buildStickerFromDocument } from '../apiBuilders/symbols';
|
||||
import { buildApiUser } from '../apiBuilders/users';
|
||||
import { buildInputPeer, buildInputReaction } from '../gramjsBuilders';
|
||||
import { addEntitiesToLocalDb } from '../helpers';
|
||||
@ -103,13 +106,32 @@ export async function fetchAvailableEffects() {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const documentsMap = new Map(result.documents.map((doc) => [String(doc.id), doc]));
|
||||
|
||||
result.documents.forEach((document) => {
|
||||
if (document instanceof GramJs.Document) {
|
||||
localDb.documents[String(document.id)] = document;
|
||||
}
|
||||
});
|
||||
|
||||
return result.effects.map(buildApiAvailableEffect);
|
||||
const effects = result.effects.map(buildApiAvailableEffect);
|
||||
|
||||
const stickers : ApiSticker[] = [];
|
||||
const emojis : ApiSticker[] = [];
|
||||
|
||||
for (const effect of effects) {
|
||||
if (effect.effectAnimationId) {
|
||||
const document = documentsMap.get(effect.effectStickerId);
|
||||
const emoji = document && buildStickerFromDocument(document, false, effect.isPremium);
|
||||
if (emoji) emojis.push(emoji);
|
||||
} else {
|
||||
const document = localDb.documents[effect.effectStickerId];
|
||||
const sticker = buildStickerFromDocument(document);
|
||||
if (sticker) { stickers.push(sticker); }
|
||||
}
|
||||
}
|
||||
|
||||
return { effects, emojis, stickers };
|
||||
}
|
||||
|
||||
export function sendReaction({
|
||||
|
||||
@ -1269,3 +1269,4 @@
|
||||
"MenuBetaChangelog" = "Beta Changelog";
|
||||
"MenuSwitchToK" = "Switch to K Version";
|
||||
"MenuInstallApp" = "Install App";
|
||||
"RemoveEffect" = "Remove effect"
|
||||
|
||||
@ -30,6 +30,17 @@
|
||||
--border-width: 0;
|
||||
}
|
||||
|
||||
.effect-icon {
|
||||
font-size: 1.25rem;
|
||||
transform: translate(-50%, 0);
|
||||
|
||||
color: var(--color-text);
|
||||
& > .emoji {
|
||||
width: 1rem !important;
|
||||
height: 1rem !important;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes show-send-as-button {
|
||||
from {
|
||||
/* stylelint-disable-next-line plugin/no-low-performance-animation-properties */
|
||||
|
||||
@ -7,6 +7,7 @@ import { getActions, getGlobal, withGlobal } from '../../global';
|
||||
import type {
|
||||
ApiAttachment,
|
||||
ApiAttachMenuPeerType,
|
||||
ApiAvailableEffect,
|
||||
ApiAvailableReaction,
|
||||
ApiBotCommand,
|
||||
ApiBotInlineMediaResult,
|
||||
@ -45,6 +46,7 @@ import {
|
||||
REPLIES_USER_ID,
|
||||
SCHEDULED_WHEN_ONLINE,
|
||||
SEND_MESSAGE_ACTION_INTERVAL,
|
||||
SERVICE_NOTIFICATIONS_USER_ID,
|
||||
} from '../../config';
|
||||
import { requestMeasure, requestNextMutation } from '../../lib/fasterdom/fasterdom';
|
||||
import {
|
||||
@ -78,6 +80,7 @@ import {
|
||||
selectNewestMessageWithBotKeyboardButtons,
|
||||
selectNoWebPage,
|
||||
selectPeerStory,
|
||||
selectPerformanceSettingsValue,
|
||||
selectRequestedDraft,
|
||||
selectRequestedDraftFiles,
|
||||
selectScheduledIds,
|
||||
@ -153,6 +156,7 @@ import SendAsMenu from '../middle/composer/SendAsMenu.async';
|
||||
import StickerTooltip from '../middle/composer/StickerTooltip.async';
|
||||
import SymbolMenuButton from '../middle/composer/SymbolMenuButton';
|
||||
import WebPagePreview from '../middle/composer/WebPagePreview';
|
||||
import MessageEffect from '../middle/message/MessageEffect';
|
||||
import ReactionSelector from '../middle/message/reactions/ReactionSelector';
|
||||
import Button from '../ui/Button';
|
||||
import ResponsiveHoverButton from '../ui/ResponsiveHoverButton';
|
||||
@ -253,6 +257,11 @@ type StateProps =
|
||||
canSendQuickReplies?: boolean;
|
||||
webPagePreview?: ApiWebPage;
|
||||
noWebPage?: boolean;
|
||||
effect?: ApiAvailableEffect;
|
||||
effectReactions?: ApiReaction[];
|
||||
areEffectsSupported?: boolean;
|
||||
canPlayEffect?: boolean;
|
||||
shouldPlayEffect?: boolean;
|
||||
};
|
||||
|
||||
enum MainButtonState {
|
||||
@ -361,6 +370,11 @@ const Composer: FC<OwnProps & StateProps> = ({
|
||||
onForward,
|
||||
webPagePreview,
|
||||
noWebPage,
|
||||
effect,
|
||||
effectReactions,
|
||||
areEffectsSupported,
|
||||
canPlayEffect,
|
||||
shouldPlayEffect,
|
||||
}) => {
|
||||
const {
|
||||
sendMessage,
|
||||
@ -384,6 +398,9 @@ const Composer: FC<OwnProps & StateProps> = ({
|
||||
sendStoryReaction,
|
||||
editMessage,
|
||||
updateAttachmentSettings,
|
||||
saveEffectInDraft,
|
||||
setReactionEffect,
|
||||
hideEffectInComposer,
|
||||
} = getActions();
|
||||
|
||||
const lang = useOldLang();
|
||||
@ -1023,11 +1040,15 @@ const Composer: FC<OwnProps & StateProps> = ({
|
||||
|
||||
const messageInput = document.querySelector<HTMLDivElement>(editableInputCssSelector);
|
||||
|
||||
const effectId = effect?.id;
|
||||
|
||||
if (text) {
|
||||
if (!checkSlowMode()) return;
|
||||
|
||||
const isInvertedMedia = hasWebPagePreview ? attachmentSettings.isInvertedMedia : undefined;
|
||||
|
||||
if (areEffectsSupported) saveEffectInDraft({ chatId, threadId, effectId: undefined });
|
||||
|
||||
sendMessage({
|
||||
messageList: currentMessageList,
|
||||
text,
|
||||
@ -1036,6 +1057,7 @@ const Composer: FC<OwnProps & StateProps> = ({
|
||||
isSilent,
|
||||
shouldUpdateStickerSetOrder,
|
||||
isInvertedMedia,
|
||||
effectId,
|
||||
});
|
||||
}
|
||||
|
||||
@ -1077,7 +1099,7 @@ const Composer: FC<OwnProps & StateProps> = ({
|
||||
});
|
||||
|
||||
const handleMessageSchedule = useLastCallback((
|
||||
args: ScheduledMessageArgs, scheduledAt: number, messageList: MessageList,
|
||||
args: ScheduledMessageArgs, scheduledAt: number, messageList: MessageList, effectId?: string,
|
||||
) => {
|
||||
if (args && 'queryId' in args) {
|
||||
const { id, queryId, isSilent } = args;
|
||||
@ -1103,6 +1125,7 @@ const Composer: FC<OwnProps & StateProps> = ({
|
||||
...args,
|
||||
messageList,
|
||||
scheduledAt,
|
||||
effectId,
|
||||
});
|
||||
}
|
||||
});
|
||||
@ -1429,7 +1452,7 @@ const Composer: FC<OwnProps & StateProps> = ({
|
||||
return;
|
||||
}
|
||||
requestCalendar((scheduledAt) => {
|
||||
handleMessageSchedule({}, scheduledAt, currentMessageList);
|
||||
handleMessageSchedule({}, scheduledAt, currentMessageList, effect?.id);
|
||||
});
|
||||
break;
|
||||
default:
|
||||
@ -1494,6 +1517,12 @@ const Composer: FC<OwnProps & StateProps> = ({
|
||||
closeReactionPicker();
|
||||
});
|
||||
|
||||
const handleToggleEffectReaction = useLastCallback((reaction: ApiReaction) => {
|
||||
setReactionEffect({ chatId, threadId, reaction });
|
||||
|
||||
closeReactionPicker();
|
||||
});
|
||||
|
||||
const handleReactionPickerOpen = useLastCallback((position: IAnchorPosition) => {
|
||||
openStoryReactionPicker({
|
||||
peerId: chatId,
|
||||
@ -1524,7 +1553,7 @@ const Composer: FC<OwnProps & StateProps> = ({
|
||||
});
|
||||
|
||||
const handleSendWhenOnline = useLastCallback(() => {
|
||||
handleMessageSchedule({}, SCHEDULED_WHEN_ONLINE, currentMessageList!);
|
||||
handleMessageSchedule({}, SCHEDULED_WHEN_ONLINE, currentMessageList!, effect?.id);
|
||||
});
|
||||
|
||||
const handleSendScheduledAttachments = useLastCallback(
|
||||
@ -1541,6 +1570,10 @@ const Composer: FC<OwnProps & StateProps> = ({
|
||||
},
|
||||
);
|
||||
|
||||
const handleRemoveEffect = useLastCallback(() => { saveEffectInDraft({ chatId, threadId, effectId: undefined }); });
|
||||
|
||||
const handleStopEffect = useLastCallback(() => { hideEffectInComposer({ }); });
|
||||
|
||||
const onSend = useMemo(() => {
|
||||
switch (mainButtonState) {
|
||||
case MainButtonState.Edit:
|
||||
@ -1555,6 +1588,8 @@ const Composer: FC<OwnProps & StateProps> = ({
|
||||
const withBotCommands = isChatWithBot && botMenuButton?.type === 'commands' && !editingMessage
|
||||
&& botCommands !== false && !activeVoiceRecording;
|
||||
|
||||
const effectEmoji = areEffectsSupported && effect?.emoticon;
|
||||
|
||||
return (
|
||||
<div className={fullClassName}>
|
||||
{isInMessageList && canAttachMedia && isReady && (
|
||||
@ -1984,6 +2019,18 @@ const Composer: FC<OwnProps & StateProps> = ({
|
||||
{isInMessageList && <i className="icon icon-schedule" />}
|
||||
{isInMessageList && <i className="icon icon-check" />}
|
||||
</Button>
|
||||
{effectEmoji && (
|
||||
<span className="effect-icon">
|
||||
{renderText(effectEmoji)}
|
||||
</span>
|
||||
)}
|
||||
{effect && canPlayEffect && (
|
||||
<MessageEffect
|
||||
shouldPlay={shouldPlayEffect}
|
||||
effect={effect}
|
||||
onStop={handleStopEffect}
|
||||
/>
|
||||
)}
|
||||
{canShowCustomSendMenu && (
|
||||
<CustomSendMenu
|
||||
isOpen={isCustomSendMenuOpen}
|
||||
@ -1992,9 +2039,20 @@ const Composer: FC<OwnProps & StateProps> = ({
|
||||
onSendSilent={!isChatWithSelf ? handleSendSilent : undefined}
|
||||
onSendSchedule={!isInScheduledList ? handleSendScheduled : undefined}
|
||||
onSendWhenOnline={handleSendWhenOnline}
|
||||
onRemoveEffect={handleRemoveEffect}
|
||||
onClose={handleContextMenuClose}
|
||||
onCloseAnimationEnd={handleContextMenuHide}
|
||||
isSavedMessages={isChatWithSelf}
|
||||
chatId={chatId}
|
||||
withEffects={areEffectsSupported}
|
||||
hasCurrentEffect={Boolean(effect)}
|
||||
effectReactions={effectReactions}
|
||||
allAvailableReactions={availableReactions}
|
||||
onToggleReaction={handleToggleEffectReaction}
|
||||
isCurrentUserPremium={isCurrentUserPremium}
|
||||
isInSavedMessages={isChatWithSelf}
|
||||
isInStoryViewer={isInStoryViewer}
|
||||
canPlayAnimatedEmojis={canPlayAnimatedEmojis}
|
||||
/>
|
||||
)}
|
||||
{calendar}
|
||||
@ -2068,8 +2126,16 @@ export default memo(withGlobal<OwnProps>(
|
||||
|
||||
const noWebPage = selectNoWebPage(global, chatId, threadId);
|
||||
|
||||
const areEffectsSupported = isChatWithUser && !isChatWithBot
|
||||
&& !isInScheduledList && !isChatWithSelf && type !== 'story' && chatId !== SERVICE_NOTIFICATIONS_USER_ID;
|
||||
const canPlayEffect = selectPerformanceSettingsValue(global, 'stickerEffects');
|
||||
const shouldPlayEffect = tabState.shouldPlayEffectInComposer;
|
||||
const effectId = areEffectsSupported && draft?.effectId;
|
||||
const effect = effectId ? global.availableEffectById[effectId] : undefined;
|
||||
const effectReactions = global.reactions.effectReactions;
|
||||
|
||||
return {
|
||||
availableReactions: type === 'story' ? global.reactions.availableReactions : undefined,
|
||||
availableReactions: global.reactions.availableReactions,
|
||||
topReactions: type === 'story' ? global.reactions.topReactions : undefined,
|
||||
isOnActiveTab: !tabState.isBlurred,
|
||||
editingMessage: selectEditingMessage(global, chatId, threadId, messageListType),
|
||||
@ -2137,6 +2203,11 @@ export default memo(withGlobal<OwnProps>(
|
||||
canSendQuickReplies,
|
||||
noWebPage,
|
||||
webPagePreview: selectTabState(global).webPagePreview,
|
||||
effect,
|
||||
effectReactions,
|
||||
areEffectsSupported,
|
||||
canPlayEffect,
|
||||
shouldPlayEffect,
|
||||
};
|
||||
},
|
||||
)(Composer));
|
||||
|
||||
@ -390,6 +390,7 @@ const CustomEmojiPicker: FC<OwnProps & StateProps> = ({
|
||||
pickerStyles.main_customEmoji,
|
||||
IS_TOUCH_ENV ? 'no-scrollbar' : 'custom-scroll',
|
||||
pickerListClassName,
|
||||
pickerStyles.hasHeader,
|
||||
);
|
||||
|
||||
return (
|
||||
|
||||
@ -27,6 +27,12 @@
|
||||
}
|
||||
}
|
||||
|
||||
&.effect-emoji .sticker-locked {
|
||||
font-size: 0.75rem;
|
||||
width: 0.875rem;
|
||||
height: 0.875rem;
|
||||
}
|
||||
|
||||
&.set-expand {
|
||||
padding: 0;
|
||||
vertical-align: bottom;
|
||||
@ -38,8 +44,7 @@
|
||||
.sticker-locked {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 50%;
|
||||
transform: translate(-50%, 50%);
|
||||
right: 0;
|
||||
width: 1.25rem;
|
||||
height: 1.25rem;
|
||||
display: flex;
|
||||
@ -47,6 +52,8 @@
|
||||
align-items: center;
|
||||
border-radius: 50%;
|
||||
color: white;
|
||||
z-index: 2;
|
||||
opacity: 0.75;
|
||||
background: var(--premium-gradient);
|
||||
}
|
||||
|
||||
|
||||
@ -54,6 +54,7 @@ type OwnProps<T> = {
|
||||
onContextMenuOpen?: NoneToVoidFunction;
|
||||
onContextMenuClose?: NoneToVoidFunction;
|
||||
onContextMenuClick?: NoneToVoidFunction;
|
||||
isEffectEmoji?: boolean;
|
||||
};
|
||||
|
||||
const contentForStatusMenuContext = [
|
||||
@ -91,6 +92,7 @@ const StickerButton = <T extends number | ApiSticker | ApiBotInlineMediaResult |
|
||||
onContextMenuOpen,
|
||||
onContextMenuClose,
|
||||
onContextMenuClick,
|
||||
isEffectEmoji,
|
||||
}: OwnProps<T>) => {
|
||||
const { openStickerSet, openPremiumModal, setEmojiStatus } = getActions();
|
||||
// eslint-disable-next-line no-null/no-null
|
||||
@ -102,8 +104,11 @@ const StickerButton = <T extends number | ApiSticker | ApiBotInlineMediaResult |
|
||||
const customColor = useDynamicColorListener(ref, !hasCustomColor);
|
||||
|
||||
const {
|
||||
id, isCustomEmoji, hasEffect: isPremium, stickerSetInfo,
|
||||
id, stickerSetInfo,
|
||||
} = sticker;
|
||||
|
||||
const isPremium = (!sticker.isFree && isEffectEmoji) || sticker.hasEffect;
|
||||
const isCustomEmoji = sticker.isCustomEmoji || isEffectEmoji;
|
||||
const isLocked = !isCurrentUserPremium && isPremium && !shouldIgnorePremium;
|
||||
|
||||
const isIntersecting = useIsIntersecting(ref, observeIntersection);
|
||||
@ -213,6 +218,7 @@ const StickerButton = <T extends number | ApiSticker | ApiBotInlineMediaResult |
|
||||
onClick && 'interactive',
|
||||
isSelected && 'selected',
|
||||
isCustomEmoji && 'custom-emoji',
|
||||
isEffectEmoji && 'effect-emoji',
|
||||
className,
|
||||
);
|
||||
|
||||
|
||||
@ -11,6 +11,8 @@ import type { StickerSetOrReactionsSetOrRecent } from '../../types';
|
||||
import {
|
||||
DEFAULT_STATUS_ICON_ID,
|
||||
DEFAULT_TOPIC_ICON_STICKER_ID,
|
||||
EFFECT_EMOJIS_SET_ID,
|
||||
EFFECT_STICKERS_SET_ID,
|
||||
EMOJI_SIZE_PICKER,
|
||||
FAVORITE_SYMBOL_SET_ID,
|
||||
POPULAR_SYMBOL_SET_ID,
|
||||
@ -232,8 +234,11 @@ const StickerSet: FC<OwnProps> = ({
|
||||
const isLocked = !isSavedMessages && !isCurrentUserPremium && isPremiumSet && !isChatEmojiSet;
|
||||
|
||||
const isInstalled = stickerSet.installedDate && !stickerSet.isArchived;
|
||||
const canCut = !isInstalled && stickerSet.id !== RECENT_SYMBOL_SET_ID && stickerSet.id !== POPULAR_SYMBOL_SET_ID
|
||||
&& !isChatEmojiSet && !isChatStickerSet;
|
||||
|
||||
const canCut = !isInstalled && stickerSet.id !== RECENT_SYMBOL_SET_ID
|
||||
&& stickerSet.id !== POPULAR_SYMBOL_SET_ID && stickerSet.id !== EFFECT_EMOJIS_SET_ID
|
||||
&& stickerSet.id !== EFFECT_STICKERS_SET_ID && !isChatEmojiSet && !isChatStickerSet;
|
||||
|
||||
const [isCut, , expand] = useFlag(canCut);
|
||||
const itemsBeforeCutout = itemsPerRow * 3 - 1;
|
||||
const totalItemsCount = withDefaultTopicIcon ? stickerSet.count + 1 : stickerSet.count;
|
||||
@ -298,7 +303,11 @@ const StickerSet: FC<OwnProps> = ({
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
className={buildClassName('symbol-set-container shared-canvas-container', transitionClassNames)}
|
||||
className={buildClassName(
|
||||
'symbol-set-container shared-canvas-container',
|
||||
transitionClassNames,
|
||||
stickerSet.id === EFFECT_EMOJIS_SET_ID && 'effect-emojis',
|
||||
)}
|
||||
style={`height: ${height}px;`}
|
||||
>
|
||||
<canvas
|
||||
@ -382,6 +391,9 @@ const StickerSet: FC<OwnProps> = ({
|
||||
onContextMenuClose={onContextMenuClose}
|
||||
onContextMenuClick={onContextMenuClick}
|
||||
forcePlayback={forcePlayback}
|
||||
isEffectEmoji={stickerSet.id === EFFECT_EMOJIS_SET_ID}
|
||||
noShowPremium={isCurrentUserPremium
|
||||
&& (stickerSet.id === EFFECT_STICKERS_SET_ID || stickerSet.id === EFFECT_EMOJIS_SET_ID)}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
||||
@ -19,4 +19,45 @@
|
||||
.bubble {
|
||||
width: 16rem;
|
||||
}
|
||||
|
||||
&.with-effects .bubble {
|
||||
overflow: initial;
|
||||
|
||||
background: none !important;
|
||||
backdrop-filter: none !important;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
&.with-effects &_items {
|
||||
background: var(--color-background-compact-menu);
|
||||
backdrop-filter: blur(10px);
|
||||
box-shadow: 0 0.25rem 0.5rem 0.125rem var(--color-default-shadow);
|
||||
border-radius: var(--border-radius-default);
|
||||
padding: 0.25rem 0;
|
||||
|
||||
@media (min-width: 600px) {
|
||||
margin-inline-end: 2.75rem;
|
||||
}
|
||||
|
||||
body.no-menu-blur & {
|
||||
background: var(--color-background);
|
||||
backdrop-filter: none;
|
||||
}
|
||||
|
||||
&-hidden {
|
||||
opacity: 0;
|
||||
transition: 300ms opacity;
|
||||
}
|
||||
}
|
||||
|
||||
.ReactionSelector {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
transform: translateY(calc(-100% - 0.5rem));
|
||||
}
|
||||
}
|
||||
|
||||
.ReactionSelector-hidden {
|
||||
opacity: 0;
|
||||
transition: 300ms opacity;
|
||||
}
|
||||
|
||||
@ -1,14 +1,28 @@
|
||||
import type { FC } from '../../../lib/teact/teact';
|
||||
import React, { memo, useState } from '../../../lib/teact/teact';
|
||||
import React, {
|
||||
memo, useEffect, useState,
|
||||
} from '../../../lib/teact/teact';
|
||||
import { getActions } from '../../../global';
|
||||
|
||||
import type {
|
||||
ApiAvailableReaction,
|
||||
ApiReaction,
|
||||
} from '../../../api/types';
|
||||
import type { IAnchorPosition } from '../../../types';
|
||||
|
||||
import buildClassName from '../../../util/buildClassName';
|
||||
import { IS_TOUCH_ENV } from '../../../util/windowEnvironment';
|
||||
|
||||
import useEffectWithPrevDeps from '../../../hooks/useEffectWithPrevDeps';
|
||||
import useFlag from '../../../hooks/useFlag';
|
||||
import useLang from '../../../hooks/useLang';
|
||||
import useLastCallback from '../../../hooks/useLastCallback';
|
||||
import useMouseInside from '../../../hooks/useMouseInside';
|
||||
import useOldLang from '../../../hooks/useOldLang';
|
||||
|
||||
import Menu from '../../ui/Menu';
|
||||
import MenuItem from '../../ui/MenuItem';
|
||||
import ReactionSelector from '../message/reactions/ReactionSelector';
|
||||
|
||||
import './CustomSendMenu.scss';
|
||||
|
||||
@ -21,10 +35,24 @@ export type OwnProps = {
|
||||
onSendSilent?: NoneToVoidFunction;
|
||||
onSendSchedule?: NoneToVoidFunction;
|
||||
onSendWhenOnline?: NoneToVoidFunction;
|
||||
onRemoveEffect?: NoneToVoidFunction;
|
||||
onClose: NoneToVoidFunction;
|
||||
onCloseAnimationEnd?: NoneToVoidFunction;
|
||||
chatId?: string;
|
||||
withEffects?: boolean;
|
||||
hasCurrentEffect?: boolean;
|
||||
effectReactions?: ApiReaction[];
|
||||
allAvailableReactions?: ApiAvailableReaction[];
|
||||
onToggleReaction?: (reaction: ApiReaction) => void;
|
||||
canBuyPremium?: boolean;
|
||||
isCurrentUserPremium?: boolean;
|
||||
isInSavedMessages?: boolean;
|
||||
isInStoryViewer?: boolean;
|
||||
canPlayAnimatedEmojis?: boolean;
|
||||
};
|
||||
|
||||
const ANIMATION_DURATION = 200;
|
||||
|
||||
const CustomSendMenu: FC<OwnProps> = ({
|
||||
isOpen,
|
||||
isOpenToBottom = false,
|
||||
@ -34,45 +62,117 @@ const CustomSendMenu: FC<OwnProps> = ({
|
||||
onSendSilent,
|
||||
onSendSchedule,
|
||||
onSendWhenOnline,
|
||||
onRemoveEffect,
|
||||
onClose,
|
||||
onCloseAnimationEnd,
|
||||
chatId,
|
||||
withEffects,
|
||||
hasCurrentEffect,
|
||||
effectReactions,
|
||||
allAvailableReactions,
|
||||
onToggleReaction,
|
||||
canBuyPremium,
|
||||
isCurrentUserPremium,
|
||||
isInSavedMessages,
|
||||
isInStoryViewer,
|
||||
canPlayAnimatedEmojis,
|
||||
}) => {
|
||||
const {
|
||||
openEffectPicker,
|
||||
} = getActions();
|
||||
|
||||
const [handleMouseEnter, handleMouseLeave] = useMouseInside(isOpen, onClose);
|
||||
const [displayScheduleUntilOnline, setDisplayScheduleUntilOnline] = useState(false);
|
||||
|
||||
const lang = useOldLang();
|
||||
const oldLang = useOldLang();
|
||||
const lang = useLang();
|
||||
const [areItemsHidden, hideItems, showItems] = useFlag();
|
||||
|
||||
useEffectWithPrevDeps(([prevIsOpen]) => {
|
||||
// Avoid context menu item shuffling when opened
|
||||
if (isOpen && !prevIsOpen) {
|
||||
showItems();
|
||||
setDisplayScheduleUntilOnline(Boolean(canScheduleUntilOnline));
|
||||
}
|
||||
}, [isOpen, canScheduleUntilOnline]);
|
||||
|
||||
const [isReady, markIsReady, unmarkIsReady] = useFlag();
|
||||
|
||||
const handleOpenMessageEffectsPicker = useLastCallback((position: IAnchorPosition) => {
|
||||
hideItems();
|
||||
if (chatId) openEffectPicker({ chatId, position });
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (!isOpen) {
|
||||
unmarkIsReady();
|
||||
return;
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
markIsReady();
|
||||
}, ANIMATION_DURATION);
|
||||
}, [isOpen, markIsReady, unmarkIsReady]);
|
||||
|
||||
return (
|
||||
<Menu
|
||||
isOpen={isOpen}
|
||||
autoClose
|
||||
positionX="right"
|
||||
positionY={isOpenToBottom ? 'top' : 'bottom'}
|
||||
className="CustomSendMenu with-menu-transitions"
|
||||
className={buildClassName(
|
||||
'CustomSendMenu', 'fluid', 'with-menu-transitions', withEffects && 'with-effects',
|
||||
)}
|
||||
onClose={onClose}
|
||||
onCloseAnimationEnd={onCloseAnimationEnd}
|
||||
onMouseEnter={!IS_TOUCH_ENV ? handleMouseEnter : undefined}
|
||||
onMouseLeave={!IS_TOUCH_ENV ? handleMouseLeave : undefined}
|
||||
noCloseOnBackdrop={!IS_TOUCH_ENV}
|
||||
>
|
||||
{onSendSilent && <MenuItem icon="mute" onClick={onSendSilent}>{lang('SendWithoutSound')}</MenuItem>}
|
||||
{canSchedule && onSendSchedule && (
|
||||
<MenuItem icon="schedule" onClick={onSendSchedule}>
|
||||
{lang(isSavedMessages ? 'SetReminder' : 'ScheduleMessage')}
|
||||
</MenuItem>
|
||||
)}
|
||||
{canSchedule && onSendSchedule && displayScheduleUntilOnline && (
|
||||
<MenuItem icon="user-online" onClick={onSendWhenOnline}>
|
||||
{lang('SendWhenOnline')}
|
||||
</MenuItem>
|
||||
|
||||
{withEffects && !isInStoryViewer && (
|
||||
<ReactionSelector
|
||||
allAvailableReactions={allAvailableReactions}
|
||||
effectReactions={effectReactions}
|
||||
currentReactions={undefined}
|
||||
onToggleReaction={onToggleReaction!}
|
||||
isPrivate
|
||||
isReady={isReady}
|
||||
canBuyPremium={canBuyPremium}
|
||||
isCurrentUserPremium={isCurrentUserPremium}
|
||||
isInSavedMessages={isInSavedMessages}
|
||||
isForEffects
|
||||
canPlayAnimatedEmojis={canPlayAnimatedEmojis}
|
||||
onShowMore={handleOpenMessageEffectsPicker}
|
||||
onClose={onClose}
|
||||
className={buildClassName(areItemsHidden && 'ReactionSelector-hidden')}
|
||||
/>
|
||||
)}
|
||||
|
||||
<div
|
||||
className={buildClassName(
|
||||
'CustomSendMenu_items',
|
||||
areItemsHidden && 'CustomSendMenu_items-hidden',
|
||||
)}
|
||||
dir={oldLang.isRtl ? 'rtl' : undefined}
|
||||
>
|
||||
{onSendSilent && <MenuItem icon="mute" onClick={onSendSilent}>{oldLang('SendWithoutSound')}</MenuItem>}
|
||||
{canSchedule && onSendSchedule && (
|
||||
<MenuItem icon="schedule" onClick={onSendSchedule}>
|
||||
{oldLang(isSavedMessages ? 'SetReminder' : 'ScheduleMessage')}
|
||||
</MenuItem>
|
||||
)}
|
||||
{canSchedule && onSendSchedule && displayScheduleUntilOnline && (
|
||||
<MenuItem icon="user-online" onClick={onSendWhenOnline}>
|
||||
{oldLang('SendWhenOnline')}
|
||||
</MenuItem>
|
||||
)}
|
||||
{withEffects && hasCurrentEffect && (
|
||||
<MenuItem icon="delete" onClick={onRemoveEffect}>
|
||||
{lang('RemoveEffect')}
|
||||
</MenuItem>
|
||||
)}
|
||||
</div>
|
||||
</Menu>
|
||||
);
|
||||
};
|
||||
|
||||
@ -11,7 +11,11 @@
|
||||
--symbol-set-gap-size: 0.25rem;
|
||||
|
||||
position: relative;
|
||||
height: calc(100% - 3rem);
|
||||
height: 100%;
|
||||
|
||||
&.hasHeader {
|
||||
height: calc(100% - 3rem);
|
||||
}
|
||||
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
|
||||
@ -10,6 +10,8 @@ import type { StickerSetOrReactionsSetOrRecent, ThreadId } from '../../../types'
|
||||
|
||||
import {
|
||||
CHAT_STICKER_SET_ID,
|
||||
EFFECT_EMOJIS_SET_ID,
|
||||
EFFECT_STICKERS_SET_ID,
|
||||
FAVORITE_SYMBOL_SET_ID,
|
||||
RECENT_SYMBOL_SET_ID,
|
||||
SLIDE_TRANSITION_DURATION,
|
||||
@ -57,12 +59,15 @@ type OwnProps = {
|
||||
onStickerSelect: (
|
||||
sticker: ApiSticker, isSilent?: boolean, shouldSchedule?: boolean, canUpdateStickerSetsOrder?: boolean,
|
||||
) => void;
|
||||
isForEffects?: boolean;
|
||||
};
|
||||
|
||||
type StateProps = {
|
||||
chat?: ApiChat;
|
||||
recentStickers: ApiSticker[];
|
||||
favoriteStickers: ApiSticker[];
|
||||
effectStickers?: ApiSticker[];
|
||||
effectEmojis?: ApiSticker[];
|
||||
stickerSetsById: Record<string, ApiStickerSet>;
|
||||
chatStickerSetId?: string;
|
||||
addedSetIds?: string[];
|
||||
@ -83,6 +88,8 @@ const StickerPicker: FC<OwnProps & StateProps> = ({
|
||||
canSendStickers,
|
||||
recentStickers,
|
||||
favoriteStickers,
|
||||
effectStickers,
|
||||
effectEmojis,
|
||||
addedSetIds,
|
||||
stickerSetsById,
|
||||
chatStickerSetId,
|
||||
@ -92,6 +99,7 @@ const StickerPicker: FC<OwnProps & StateProps> = ({
|
||||
noContextMenus,
|
||||
idPrefix,
|
||||
onStickerSelect,
|
||||
isForEffects,
|
||||
}) => {
|
||||
const {
|
||||
loadRecentStickers,
|
||||
@ -113,7 +121,7 @@ const StickerPicker: FC<OwnProps & StateProps> = ({
|
||||
isAtBeginning: shouldHideTopBorder,
|
||||
} = useScrolledState();
|
||||
|
||||
const sendMessageAction = useSendMessageAction(chat!.id, threadId);
|
||||
const sendMessageAction = useSendMessageAction(chat?.id, threadId);
|
||||
|
||||
const prefix = `${idPrefix}-sticker-set`;
|
||||
const {
|
||||
@ -130,6 +138,30 @@ const StickerPicker: FC<OwnProps & StateProps> = ({
|
||||
const areAddedLoaded = Boolean(addedSetIds);
|
||||
|
||||
const allSets = useMemo(() => {
|
||||
if (isForEffects && effectStickers) {
|
||||
const effectSets: StickerSetOrReactionsSetOrRecent[] = [];
|
||||
if (effectEmojis?.length) {
|
||||
effectSets.push({
|
||||
id: EFFECT_EMOJIS_SET_ID,
|
||||
accessHash: '0',
|
||||
title: '',
|
||||
stickers: effectEmojis,
|
||||
count: effectEmojis.length,
|
||||
isEmoji: true,
|
||||
});
|
||||
}
|
||||
if (effectStickers?.length) {
|
||||
effectSets.push({
|
||||
id: EFFECT_STICKERS_SET_ID,
|
||||
accessHash: '0',
|
||||
title: lang('StickerEffects'),
|
||||
stickers: effectStickers,
|
||||
count: effectStickers.length,
|
||||
});
|
||||
}
|
||||
return effectSets;
|
||||
}
|
||||
|
||||
if (!addedSetIds) {
|
||||
return MEMO_EMPTY_ARRAY;
|
||||
}
|
||||
@ -167,7 +199,17 @@ const StickerPicker: FC<OwnProps & StateProps> = ({
|
||||
...defaultSets,
|
||||
...existingAddedSetIds,
|
||||
];
|
||||
}, [addedSetIds, stickerSetsById, favoriteStickers, recentStickers, chatStickerSetId, lang]);
|
||||
}, [
|
||||
addedSetIds,
|
||||
stickerSetsById,
|
||||
favoriteStickers,
|
||||
recentStickers,
|
||||
chatStickerSetId,
|
||||
lang,
|
||||
effectStickers,
|
||||
isForEffects,
|
||||
effectEmojis,
|
||||
]);
|
||||
|
||||
const noPopulatedSets = useMemo(() => (
|
||||
areAddedLoaded
|
||||
@ -182,7 +224,8 @@ const StickerPicker: FC<OwnProps & StateProps> = ({
|
||||
}, [canSendStickers, loadAndPlay, loadRecentStickers, sendMessageAction]);
|
||||
|
||||
const canRenderContents = useAsyncRendering([], SLIDE_TRANSITION_DURATION);
|
||||
const shouldRenderContents = areAddedLoaded && canRenderContents && !noPopulatedSets && canSendStickers;
|
||||
const shouldRenderContents = areAddedLoaded && canRenderContents
|
||||
&& !noPopulatedSets && (canSendStickers || isForEffects);
|
||||
|
||||
useHorizontalScroll(headerRef, !shouldRenderContents || !headerRef.current);
|
||||
|
||||
@ -224,6 +267,8 @@ const StickerPicker: FC<OwnProps & StateProps> = ({
|
||||
removeRecentSticker({ sticker });
|
||||
});
|
||||
|
||||
if (!chat) return undefined;
|
||||
|
||||
function renderCover(stickerSet: StickerSetOrReactionsSetOrRecent, index: number) {
|
||||
const firstSticker = stickerSet.stickers?.[0];
|
||||
const buttonClassName = buildClassName(styles.stickerCover, index === activeSetIndex && styles.activated);
|
||||
@ -290,7 +335,7 @@ const StickerPicker: FC<OwnProps & StateProps> = ({
|
||||
if (!shouldRenderContents) {
|
||||
return (
|
||||
<div className={fullClassName}>
|
||||
{!canSendStickers ? (
|
||||
{!canSendStickers && !isForEffects ? (
|
||||
<div className={styles.pickerDisabled}>{lang('ErrorSendRestrictedStickersAll')}</div>
|
||||
) : noPopulatedSets ? (
|
||||
<div className={styles.pickerDisabled}>{lang('NoStickers')}</div>
|
||||
@ -309,17 +354,25 @@ const StickerPicker: FC<OwnProps & StateProps> = ({
|
||||
|
||||
return (
|
||||
<div className={fullClassName}>
|
||||
<div ref={headerRef} className={headerClassName}>
|
||||
<div className="shared-canvas-container">
|
||||
<canvas ref={sharedCanvasRef} className="shared-canvas" />
|
||||
{allSets.map(renderCover)}
|
||||
{ !isForEffects && (
|
||||
<div ref={headerRef} className={headerClassName}>
|
||||
<div className="shared-canvas-container">
|
||||
<canvas ref={sharedCanvasRef} className="shared-canvas" />
|
||||
{allSets.map(renderCover)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) }
|
||||
<div
|
||||
ref={containerRef}
|
||||
onMouseMove={handleMouseMove}
|
||||
onScroll={handleContentScroll}
|
||||
className={buildClassName(styles.main, IS_TOUCH_ENV ? 'no-scrollbar' : 'custom-scroll')}
|
||||
className={
|
||||
buildClassName(
|
||||
styles.main,
|
||||
IS_TOUCH_ENV ? 'no-scrollbar' : 'custom-scroll',
|
||||
!isForEffects && styles.hasHeader,
|
||||
)
|
||||
}
|
||||
>
|
||||
{allSets.map((stickerSet, i) => (
|
||||
<StickerSet
|
||||
@ -343,6 +396,7 @@ const StickerPicker: FC<OwnProps & StateProps> = ({
|
||||
onStickerFave={handleStickerFave}
|
||||
onStickerRemoveRecent={handleRemoveRecentSticker}
|
||||
forcePlayback
|
||||
shouldHideHeader={stickerSet.id === EFFECT_EMOJIS_SET_ID}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
@ -357,6 +411,7 @@ export default memo(withGlobal<OwnProps>(
|
||||
added,
|
||||
recent,
|
||||
favorite,
|
||||
effect,
|
||||
} = global.stickers;
|
||||
|
||||
const isSavedMessages = selectIsChatWithSelf(global, chatId);
|
||||
@ -365,6 +420,8 @@ export default memo(withGlobal<OwnProps>(
|
||||
|
||||
return {
|
||||
chat,
|
||||
effectStickers: effect?.stickers,
|
||||
effectEmojis: effect?.emojis,
|
||||
recentStickers: recent.stickers,
|
||||
favoriteStickers: favorite.stickers,
|
||||
stickerSetsById: setsById,
|
||||
|
||||
@ -270,6 +270,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
.effect-emojis.symbol-set-container {
|
||||
--emoji-size: 2.25rem;
|
||||
}
|
||||
|
||||
.symbol-set-container {
|
||||
display: grid !important;
|
||||
justify-content: space-between;
|
||||
|
||||
@ -1115,10 +1115,11 @@ const Message: FC<OwnProps & StateProps> = ({
|
||||
activeEmojiInteractions={activeEmojiInteractions}
|
||||
/>
|
||||
)}
|
||||
{withAnimatedEffects && effect && (
|
||||
{withAnimatedEffects && effect && !isLocal && (
|
||||
<MessageEffect
|
||||
shouldPlay={shouldPlayEffect}
|
||||
message={message}
|
||||
messageId={message.id}
|
||||
isMirrored={!message.isOutgoing}
|
||||
effect={effect}
|
||||
observeIntersectionForLoading={observeIntersectionForLoading}
|
||||
observeIntersectionForPlaying={observeIntersectionForPlaying}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import React, { memo, useEffect, useRef } from '../../../lib/teact/teact';
|
||||
|
||||
import type { ApiAvailableEffect, ApiMessage } from '../../../api/types';
|
||||
import type { ApiAvailableEffect } from '../../../api/types';
|
||||
|
||||
import buildClassName from '../../../util/buildClassName';
|
||||
|
||||
@ -16,18 +16,20 @@ import Portal from '../../ui/Portal';
|
||||
import styles from './MessageEffect.module.scss';
|
||||
|
||||
type OwnProps = {
|
||||
message: ApiMessage;
|
||||
messageId?: number;
|
||||
isMirrored?: boolean;
|
||||
effect: ApiAvailableEffect;
|
||||
shouldPlay?: boolean;
|
||||
observeIntersectionForLoading: ObserveFn;
|
||||
observeIntersectionForPlaying: ObserveFn;
|
||||
observeIntersectionForLoading?: ObserveFn;
|
||||
observeIntersectionForPlaying?: ObserveFn;
|
||||
onStop?: VoidFunction;
|
||||
};
|
||||
|
||||
const EFFECT_SIZE = 256;
|
||||
|
||||
const MessageEffect = ({
|
||||
message,
|
||||
messageId,
|
||||
isMirrored,
|
||||
effect,
|
||||
shouldPlay,
|
||||
observeIntersectionForLoading,
|
||||
@ -42,31 +44,37 @@ const MessageEffect = ({
|
||||
const canPlay = useIsIntersecting(anchorRef, observeIntersectionForPlaying);
|
||||
|
||||
const [isPlaying, startPlaying, stopPlaying] = useFlag();
|
||||
const [isPositionUpdateRequired, requirePositionUpdate, resetPositionUpdate] = useFlag();
|
||||
|
||||
const effectHash = getEffectHash(effect);
|
||||
const effectBlob = useMedia(effectHash, !canLoad);
|
||||
|
||||
const isMirrored = !message.isOutgoing;
|
||||
|
||||
const handleEnded = useLastCallback(() => {
|
||||
stopPlaying();
|
||||
onStop?.();
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (canPlay && shouldPlay && effectBlob) {
|
||||
startPlaying();
|
||||
}
|
||||
}, [canPlay, effectBlob, shouldPlay]);
|
||||
|
||||
useOverlayPosition({
|
||||
const updatePosition = useOverlayPosition({
|
||||
anchorRef,
|
||||
overlayRef: ref,
|
||||
isMirrored,
|
||||
isDisabled: !isPlaying,
|
||||
isForMessageEffect: true,
|
||||
id: effect.id,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (isPositionUpdateRequired) updatePosition();
|
||||
resetPositionUpdate();
|
||||
}, [updatePosition, resetPositionUpdate, isPositionUpdateRequired]);
|
||||
|
||||
useEffect(() => {
|
||||
if (canPlay && shouldPlay && effectBlob) {
|
||||
startPlaying();
|
||||
requirePositionUpdate();
|
||||
}
|
||||
}, [canPlay, effectBlob, shouldPlay, updatePosition]);
|
||||
|
||||
const effectClassName = buildClassName(
|
||||
styles.root,
|
||||
isMirrored && styles.mirror,
|
||||
@ -78,7 +86,7 @@ const MessageEffect = ({
|
||||
<Portal>
|
||||
<AnimatedSticker
|
||||
ref={ref}
|
||||
key={`effect-${message.id}`}
|
||||
key={`effect-${messageId ?? effect.id}`}
|
||||
className={effectClassName}
|
||||
tgsUrl={effectBlob}
|
||||
size={EFFECT_SIZE}
|
||||
|
||||
@ -14,12 +14,14 @@ export default function useOverlayPosition({
|
||||
isMirrored,
|
||||
isForMessageEffect,
|
||||
isDisabled,
|
||||
id,
|
||||
} : {
|
||||
anchorRef: RefObject<HTMLDivElement>;
|
||||
overlayRef: RefObject<HTMLDivElement>;
|
||||
isMirrored?: boolean;
|
||||
isForMessageEffect?: boolean;
|
||||
isDisabled?: boolean;
|
||||
id?: string;
|
||||
}) {
|
||||
const updatePosition = useLastCallback(() => {
|
||||
const element = overlayRef.current;
|
||||
@ -49,12 +51,13 @@ export default function useOverlayPosition({
|
||||
useEffect(() => {
|
||||
if (isDisabled) return;
|
||||
updatePosition();
|
||||
}, [isDisabled]);
|
||||
}, [isDisabled, id]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isDisabled) return undefined;
|
||||
|
||||
const messagesContainer = anchorRef.current!.closest<HTMLDivElement>('.MessageList')!;
|
||||
if (!messagesContainer) return undefined;
|
||||
|
||||
messagesContainer.addEventListener('scroll', updatePosition, { passive: true });
|
||||
|
||||
@ -62,4 +65,6 @@ export default function useOverlayPosition({
|
||||
messagesContainer.removeEventListener('scroll', updatePosition);
|
||||
};
|
||||
}, [isDisabled, anchorRef]);
|
||||
|
||||
return updatePosition;
|
||||
}
|
||||
|
||||
@ -2,11 +2,13 @@ import type { FC } from '../../../../lib/teact/teact';
|
||||
import React, { memo, useMemo, useRef } from '../../../../lib/teact/teact';
|
||||
import { getActions, getGlobal, withGlobal } from '../../../../global';
|
||||
|
||||
import type {
|
||||
ApiMessage, ApiMessageEntity,
|
||||
ApiReaction, ApiReactionCustomEmoji, ApiSticker, ApiStory, ApiStorySkipped,
|
||||
} from '../../../../api/types';
|
||||
import type { IAnchorPosition } from '../../../../types';
|
||||
import {
|
||||
type ApiAvailableEffect,
|
||||
type ApiMessage, type ApiMessageEntity,
|
||||
type ApiReaction, type ApiReactionCustomEmoji, type ApiSticker, type ApiStory, type ApiStorySkipped,
|
||||
MAIN_THREAD_ID,
|
||||
} from '../../../../api/types';
|
||||
|
||||
import { getReactionKey, getStoryKey, isUserId } from '../../../../global/helpers';
|
||||
import {
|
||||
@ -26,6 +28,7 @@ import useOldLang from '../../../../hooks/useOldLang';
|
||||
|
||||
import CustomEmojiPicker from '../../../common/CustomEmojiPicker';
|
||||
import Menu from '../../../ui/Menu';
|
||||
import StickerPicker from '../../composer/StickerPicker';
|
||||
import ReactionPickerLimited from './ReactionPickerLimited';
|
||||
|
||||
import styles from './ReactionPicker.module.scss';
|
||||
@ -42,6 +45,9 @@ interface StateProps {
|
||||
position?: IAnchorPosition;
|
||||
isTranslucent?: boolean;
|
||||
sendAsMessage?: boolean;
|
||||
chatId?: string;
|
||||
isForEffects?: boolean;
|
||||
availableEffectById: Record<string, ApiAvailableEffect>;
|
||||
}
|
||||
|
||||
const FULL_PICKER_SHIFT_DELTA = { x: -23, y: -64 };
|
||||
@ -57,9 +63,13 @@ const ReactionPicker: FC<OwnProps & StateProps> = ({
|
||||
isCurrentUserPremium,
|
||||
withCustomReactions,
|
||||
sendAsMessage,
|
||||
chatId,
|
||||
isForEffects,
|
||||
availableEffectById,
|
||||
}) => {
|
||||
const {
|
||||
toggleReaction, closeReactionPicker, sendMessage, showNotification, sendStoryReaction,
|
||||
toggleReaction, closeReactionPicker, sendMessage, showNotification, sendStoryReaction, saveEffectInDraft,
|
||||
requestEffectInComposer,
|
||||
} = getActions();
|
||||
|
||||
const lang = useOldLang();
|
||||
@ -171,6 +181,16 @@ const ReactionPicker: FC<OwnProps & StateProps> = ({
|
||||
closeReactionPicker();
|
||||
});
|
||||
|
||||
const handleStickerSelect = useLastCallback((sticker: ApiSticker) => {
|
||||
const availableEffects = Object.values(availableEffectById);
|
||||
const effectId = availableEffects.find((effect) => effect.effectStickerId === sticker.id)?.id;
|
||||
|
||||
if (chatId) saveEffectInDraft({ chatId, threadId: MAIN_THREAD_ID, effectId });
|
||||
|
||||
if (effectId) requestEffectInComposer({ });
|
||||
closeReactionPicker();
|
||||
});
|
||||
|
||||
const selectedReactionIds = useMemo(() => {
|
||||
return (message?.reactions?.results || []).reduce<string[]>((acc, { chosenOrder, reaction }) => {
|
||||
if (chosenOrder !== undefined) {
|
||||
@ -201,25 +221,42 @@ const ReactionPicker: FC<OwnProps & StateProps> = ({
|
||||
backdropExcludedSelector=".Modal.confirm"
|
||||
onClose={closeReactionPicker}
|
||||
>
|
||||
<CustomEmojiPicker
|
||||
chatId={renderedChatId}
|
||||
idPrefix="message-emoji-set-"
|
||||
isHidden={!isOpen || !(withCustomReactions || renderedStoryId)}
|
||||
loadAndPlay={Boolean(isOpen && withCustomReactions)}
|
||||
isReactionPicker
|
||||
className={!withCustomReactions && !renderedStoryId ? styles.hidden : undefined}
|
||||
selectedReactionIds={selectedReactionIds}
|
||||
isTranslucent={isTranslucent}
|
||||
onCustomEmojiSelect={renderedStoryId ? handleStoryReactionSelect : handleToggleCustomReaction}
|
||||
onReactionSelect={renderedStoryId ? handleStoryReactionSelect : handleToggleReaction}
|
||||
/>
|
||||
{!withCustomReactions && Boolean(renderedChatId) && (
|
||||
<ReactionPickerLimited
|
||||
chatId={renderedChatId}
|
||||
loadAndPlay={isOpen}
|
||||
onReactionSelect={renderedStoryId ? handleStoryReactionSelect : handleToggleReaction}
|
||||
selectedReactionIds={selectedReactionIds}
|
||||
{isForEffects && chatId ? (
|
||||
<StickerPicker
|
||||
className=""
|
||||
isHidden={!isOpen}
|
||||
loadAndPlay={Boolean(isOpen && withCustomReactions)}
|
||||
idPrefix="message-effect"
|
||||
canSendStickers={false}
|
||||
noContextMenus={false}
|
||||
chatId={chatId}
|
||||
isTranslucent={isTranslucent}
|
||||
onStickerSelect={handleStickerSelect}
|
||||
isForEffects={isForEffects}
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
<CustomEmojiPicker
|
||||
chatId={renderedChatId}
|
||||
idPrefix="message-emoji-set-"
|
||||
isHidden={!isOpen || !(withCustomReactions || renderedStoryId)}
|
||||
loadAndPlay={Boolean(isOpen && withCustomReactions)}
|
||||
isReactionPicker
|
||||
className={!withCustomReactions && !renderedStoryId ? styles.hidden : undefined}
|
||||
selectedReactionIds={selectedReactionIds}
|
||||
isTranslucent={isTranslucent}
|
||||
onCustomEmojiSelect={renderedStoryId ? handleStoryReactionSelect : handleToggleCustomReaction}
|
||||
onReactionSelect={renderedStoryId ? handleStoryReactionSelect : handleToggleReaction}
|
||||
/>
|
||||
{!withCustomReactions && Boolean(renderedChatId) && (
|
||||
<ReactionPickerLimited
|
||||
chatId={renderedChatId}
|
||||
loadAndPlay={isOpen}
|
||||
onReactionSelect={renderedStoryId ? handleStoryReactionSelect : handleToggleReaction}
|
||||
selectedReactionIds={selectedReactionIds}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Menu>
|
||||
);
|
||||
@ -227,8 +264,9 @@ const ReactionPicker: FC<OwnProps & StateProps> = ({
|
||||
|
||||
export default memo(withGlobal<OwnProps>((global): StateProps => {
|
||||
const state = selectTabState(global);
|
||||
const availableEffectById = global.availableEffectById;
|
||||
const {
|
||||
chatId, messageId, storyPeerId, storyId, position, sendAsMessage,
|
||||
chatId, messageId, storyPeerId, storyId, position, sendAsMessage, isForEffects,
|
||||
} = state.reactionPicker || {};
|
||||
const story = storyPeerId && storyId
|
||||
? selectPeerStory(global, storyPeerId, storyId) as ApiStory | ApiStorySkipped
|
||||
@ -251,6 +289,9 @@ export default memo(withGlobal<OwnProps>((global): StateProps => {
|
||||
isTranslucent: selectIsContextMenuTranslucent(global),
|
||||
isCurrentUserPremium: selectIsCurrentUserPremium(global),
|
||||
sendAsMessage,
|
||||
isForEffects,
|
||||
chatId,
|
||||
availableEffectById,
|
||||
};
|
||||
})(ReactionPicker));
|
||||
|
||||
|
||||
@ -27,6 +27,7 @@ type OwnProps = {
|
||||
isPrivate?: boolean;
|
||||
topReactions?: ApiReaction[];
|
||||
defaultTagReactions?: ApiReaction[];
|
||||
effectReactions?: ApiReaction[];
|
||||
allAvailableReactions?: ApiAvailableReaction[];
|
||||
currentReactions?: ApiReactionCount[];
|
||||
maxUniqueReactions?: number;
|
||||
@ -37,6 +38,7 @@ type OwnProps = {
|
||||
className?: string;
|
||||
isInSavedMessages?: boolean;
|
||||
isInStoryViewer?: boolean;
|
||||
isForEffects?: boolean;
|
||||
onClose?: NoneToVoidFunction;
|
||||
onToggleReaction: (reaction: ApiReaction) => void;
|
||||
onShowMore: (position: IAnchorPosition) => void;
|
||||
@ -60,6 +62,8 @@ const ReactionSelector: FC<OwnProps> = ({
|
||||
isCurrentUserPremium,
|
||||
isInSavedMessages,
|
||||
isInStoryViewer,
|
||||
isForEffects,
|
||||
effectReactions,
|
||||
onClose,
|
||||
onToggleReaction,
|
||||
onShowMore,
|
||||
@ -72,12 +76,15 @@ const ReactionSelector: FC<OwnProps> = ({
|
||||
const areReactionsLocked = isInSavedMessages && !isCurrentUserPremium && !isInStoryViewer;
|
||||
|
||||
const availableReactions = useMemo(() => {
|
||||
const reactions = isInSavedMessages ? defaultTagReactions
|
||||
const reactions = isForEffects ? effectReactions : isInSavedMessages ? defaultTagReactions
|
||||
: (enabledReactions?.type === 'some' ? enabledReactions.allowed
|
||||
: allAvailableReactions?.map((reaction) => reaction.reaction));
|
||||
const filteredReactions = reactions?.map((reaction) => {
|
||||
const isCustomReaction = 'documentId' in reaction;
|
||||
const availableReaction = allAvailableReactions?.find((r) => isSameReaction(r.reaction, reaction));
|
||||
|
||||
if (isForEffects) return availableReaction;
|
||||
|
||||
if ((!isCustomReaction && !availableReaction) || availableReaction?.isInactive) return undefined;
|
||||
|
||||
if (!isPrivate && (!enabledReactions || !canSendReaction(reaction, enabledReactions))) {
|
||||
@ -95,7 +102,7 @@ const ReactionSelector: FC<OwnProps> = ({
|
||||
return sortReactions(filteredReactions, topReactions);
|
||||
}, [
|
||||
allAvailableReactions, currentReactions, defaultTagReactions, enabledReactions, isInSavedMessages, isPrivate,
|
||||
maxUniqueReactions, topReactions,
|
||||
maxUniqueReactions, topReactions, isForEffects, effectReactions,
|
||||
]);
|
||||
|
||||
const reactionsToRender = useMemo(() => {
|
||||
@ -151,8 +158,12 @@ const ReactionSelector: FC<OwnProps> = ({
|
||||
return lang('StoryReactionsHint');
|
||||
}
|
||||
|
||||
if (isForEffects) {
|
||||
return lang('AddEffectMessageHint');
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}, [isCurrentUserPremium, isInSavedMessages, isInStoryViewer, lang]);
|
||||
}, [isCurrentUserPremium, isInSavedMessages, isInStoryViewer, lang, isForEffects]);
|
||||
|
||||
if (!reactionsToRender.length) return undefined;
|
||||
|
||||
|
||||
@ -206,6 +206,8 @@ export const TOP_SYMBOL_SET_ID = 'top';
|
||||
export const POPULAR_SYMBOL_SET_ID = 'popular';
|
||||
export const RECENT_SYMBOL_SET_ID = 'recent';
|
||||
export const FAVORITE_SYMBOL_SET_ID = 'favorite';
|
||||
export const EFFECT_STICKERS_SET_ID = 'effectStickers';
|
||||
export const EFFECT_EMOJIS_SET_ID = 'effectEmojis';
|
||||
export const CHAT_STICKER_SET_ID = 'chatStickers';
|
||||
export const DEFAULT_TOPIC_ICON_STICKER_ID = 'topic-default-icon';
|
||||
export const DEFAULT_STATUS_ICON_ID = 'status-default-icon';
|
||||
|
||||
@ -4,7 +4,8 @@ import type {
|
||||
} from '../../../api/types';
|
||||
import type { RequiredGlobalActions } from '../../index';
|
||||
import type {
|
||||
ActionReturnType, ChatListType, GlobalState, TabArgs,
|
||||
ActionReturnType, ApiDraft,
|
||||
ChatListType, GlobalState, TabArgs,
|
||||
} from '../../types';
|
||||
import { MAIN_THREAD_ID } from '../../../api/types';
|
||||
import {
|
||||
@ -2824,9 +2825,18 @@ async function loadChats(
|
||||
|
||||
if (!draft && !thread) return;
|
||||
|
||||
if (!selectDraft(global, chatId, MAIN_THREAD_ID)?.isLocal) {
|
||||
const currentDraft = selectDraft(global, chatId, MAIN_THREAD_ID);
|
||||
|
||||
// Temporary workaround until the layer is updated
|
||||
if (!currentDraft?.isLocal) {
|
||||
const effectId = currentDraft?.effectId;
|
||||
const newDraft: ApiDraft = effectId ? {
|
||||
...draft,
|
||||
effectId,
|
||||
} : draft;
|
||||
|
||||
global = replaceThreadParam(
|
||||
global, chatId, MAIN_THREAD_ID, 'draft', draft,
|
||||
global, chatId, MAIN_THREAD_ID, 'draft', newDraft,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
@ -525,6 +525,7 @@ addActionHandler('saveDraft', (global, actions, payload): ActionReturnType => {
|
||||
const newDraft: ApiDraft = {
|
||||
text,
|
||||
replyInfo: currentDraft?.replyInfo,
|
||||
effectId: currentDraft?.effectId,
|
||||
};
|
||||
|
||||
saveDraft({
|
||||
@ -600,6 +601,23 @@ addActionHandler('resetDraftReplyInfo', (global, actions, payload): ActionReturn
|
||||
});
|
||||
});
|
||||
|
||||
addActionHandler('saveEffectInDraft', (global, actions, payload): ActionReturnType => {
|
||||
const {
|
||||
chatId, threadId, effectId,
|
||||
} = payload;
|
||||
|
||||
const currentDraft = selectDraft(global, chatId, threadId);
|
||||
|
||||
const newDraft = {
|
||||
...currentDraft,
|
||||
effectId,
|
||||
};
|
||||
|
||||
saveDraft({
|
||||
global, chatId, threadId, draft: newDraft, isLocalOnly: true, noLocalTimeUpdate: true,
|
||||
});
|
||||
});
|
||||
|
||||
async function saveDraft<T extends GlobalState>({
|
||||
global, chatId, threadId, draft, isLocalOnly, noLocalTimeUpdate,
|
||||
} : {
|
||||
@ -1410,6 +1428,7 @@ async function sendMessage<T extends GlobalState>(global: T, params: {
|
||||
wasDrafted?: boolean;
|
||||
lastMessageId?: number;
|
||||
isInvertedMedia?: true;
|
||||
effectId?: string;
|
||||
}) {
|
||||
let currentMessageKey: MessageKey | undefined;
|
||||
const progressCallback = params.attachment ? (progress: number, messageKey: MessageKey) => {
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import type { ApiReactionEmoji } from '../../../api/types';
|
||||
import type { ActionReturnType } from '../../types';
|
||||
import { ApiMediaFormat } from '../../../api/types';
|
||||
|
||||
@ -83,12 +84,35 @@ addActionHandler('loadAvailableEffects', async (global): Promise<void> => {
|
||||
return;
|
||||
}
|
||||
|
||||
const effectById = buildCollectionByKey(result, 'id');
|
||||
const { effects, emojis, stickers } = result;
|
||||
const reactions:ApiReactionEmoji[] = [];
|
||||
|
||||
const effectById = buildCollectionByKey(effects, 'id');
|
||||
|
||||
for (const effect of effects) {
|
||||
if (effect.effectAnimationId) {
|
||||
const reaction: ApiReactionEmoji = {
|
||||
emoticon: effect.emoticon,
|
||||
};
|
||||
reactions.push(reaction);
|
||||
}
|
||||
}
|
||||
|
||||
global = getGlobal();
|
||||
global = {
|
||||
...global,
|
||||
availableEffectById: effectById,
|
||||
stickers: {
|
||||
...global.stickers,
|
||||
effect: {
|
||||
stickers,
|
||||
emojis,
|
||||
},
|
||||
},
|
||||
reactions: {
|
||||
...global.reactions,
|
||||
effectReactions: reactions,
|
||||
},
|
||||
};
|
||||
setGlobal(global);
|
||||
});
|
||||
|
||||
@ -34,6 +34,7 @@ import {
|
||||
selectChatMessages,
|
||||
selectCommonBoxChatId,
|
||||
selectCurrentMessageList,
|
||||
selectDraft,
|
||||
selectIsChatListed,
|
||||
selectTabState,
|
||||
selectThreadParam,
|
||||
@ -452,7 +453,15 @@ addActionHandler('apiUpdate', (global, actions, update): ActionReturnType => {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
global = replaceThreadParam(global, chatId, threadId || MAIN_THREAD_ID, 'draft', draft);
|
||||
const currentDraft = selectDraft(global, chatId, threadId ?? MAIN_THREAD_ID);
|
||||
|
||||
// Temporary workaround until the layer is updated
|
||||
const newDraft = currentDraft?.effectId ? {
|
||||
...draft,
|
||||
effectId: currentDraft?.effectId,
|
||||
} : draft;
|
||||
|
||||
global = replaceThreadParam(global, chatId, threadId || MAIN_THREAD_ID, 'draft', newDraft);
|
||||
global = updateChat(global, chatId, { draftDate: draft?.date });
|
||||
return global;
|
||||
}
|
||||
|
||||
@ -36,6 +36,7 @@ addActionHandler('processOpenChatOrThread', (global, actions, payload): ActionRe
|
||||
},
|
||||
}, tabId);
|
||||
}
|
||||
actions.hideEffectInComposer({ tabId });
|
||||
|
||||
if (!currentMessageList || (
|
||||
currentMessageList.chatId !== chatId
|
||||
|
||||
@ -29,6 +29,7 @@ import {
|
||||
selectChatMessage,
|
||||
selectCurrentChat,
|
||||
selectCurrentMessageList,
|
||||
selectIsCurrentUserPremium,
|
||||
selectIsTrustedBot,
|
||||
selectTabState,
|
||||
} from '../../selectors';
|
||||
@ -498,6 +499,51 @@ addActionHandler('updateAttachmentSettings', (global, actions, payload): ActionR
|
||||
};
|
||||
});
|
||||
|
||||
addActionHandler('requestEffectInComposer', (global, actions, payload): ActionReturnType => {
|
||||
const { tabId = getCurrentTabId() } = payload;
|
||||
|
||||
return updateTabState(global, {
|
||||
shouldPlayEffectInComposer: true,
|
||||
}, tabId);
|
||||
});
|
||||
|
||||
addActionHandler('hideEffectInComposer', (global, actions, payload): ActionReturnType => {
|
||||
const { tabId = getCurrentTabId() } = payload;
|
||||
|
||||
return updateTabState(global, {
|
||||
shouldPlayEffectInComposer: undefined,
|
||||
}, tabId);
|
||||
});
|
||||
|
||||
addActionHandler('setReactionEffect', (global, actions, payload): ActionReturnType => {
|
||||
const {
|
||||
chatId, threadId, reaction, tabId = getCurrentTabId(),
|
||||
} = payload;
|
||||
|
||||
const emoticon = reaction && 'emoticon' in reaction && reaction.emoticon;
|
||||
if (!emoticon) return;
|
||||
|
||||
const effect = Object.values(global.availableEffectById)
|
||||
.find((currentEffect) => currentEffect.effectAnimationId && currentEffect.emoticon === emoticon);
|
||||
|
||||
const effectId = effect?.id;
|
||||
|
||||
const isCurrentUserPremium = selectIsCurrentUserPremium(global);
|
||||
if (effect?.isPremium && !isCurrentUserPremium) {
|
||||
actions.openPremiumModal({
|
||||
initialSection: 'animated_emoji',
|
||||
tabId,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (!effectId) return;
|
||||
|
||||
actions.requestEffectInComposer({ tabId });
|
||||
|
||||
actions.saveEffectInDraft({ chatId, threadId, effectId });
|
||||
});
|
||||
|
||||
addActionHandler('openLimitReachedModal', (global, actions, payload): ActionReturnType => {
|
||||
const { limit, tabId = getCurrentTabId() } = payload;
|
||||
|
||||
|
||||
@ -62,6 +62,22 @@ addActionHandler('openStoryReactionPicker', (global, actions, payload): ActionRe
|
||||
}, tabId);
|
||||
});
|
||||
|
||||
addActionHandler('openEffectPicker', (global, actions, payload): ActionReturnType => {
|
||||
const {
|
||||
position,
|
||||
chatId,
|
||||
tabId = getCurrentTabId(),
|
||||
} = payload!;
|
||||
|
||||
return updateTabState(global, {
|
||||
reactionPicker: {
|
||||
position,
|
||||
chatId,
|
||||
isForEffects: true,
|
||||
},
|
||||
}, tabId);
|
||||
});
|
||||
|
||||
addActionHandler('closeReactionPicker', (global, actions, payload): ActionReturnType => {
|
||||
const { tabId = getCurrentTabId() } = payload || {};
|
||||
const tabState = selectTabState(global, tabId);
|
||||
@ -73,6 +89,7 @@ addActionHandler('closeReactionPicker', (global, actions, payload): ActionReturn
|
||||
position: undefined,
|
||||
storyId: undefined,
|
||||
storyPeerId: undefined,
|
||||
isForEffects: undefined,
|
||||
},
|
||||
}, tabId);
|
||||
});
|
||||
|
||||
@ -301,6 +301,7 @@ export function serializeGlobal<T extends GlobalState>(global: T) {
|
||||
'defaultTags',
|
||||
'recentReactions',
|
||||
'topReactions',
|
||||
'effectReactions',
|
||||
'hash',
|
||||
]),
|
||||
availableReactions: reduceAvailableReactions(global.reactions.availableReactions),
|
||||
|
||||
@ -160,6 +160,7 @@ export const INITIAL_GLOBAL_STATE: GlobalState = {
|
||||
defaultTags: [],
|
||||
topReactions: [],
|
||||
recentReactions: [],
|
||||
effectReactions: [],
|
||||
hash: {},
|
||||
},
|
||||
availableEffectById: {},
|
||||
@ -182,6 +183,10 @@ export const INITIAL_GLOBAL_STATE: GlobalState = {
|
||||
featured: {
|
||||
setIds: [],
|
||||
},
|
||||
effect: {
|
||||
stickers: [],
|
||||
emojis: [],
|
||||
},
|
||||
forEmoji: {},
|
||||
},
|
||||
|
||||
|
||||
@ -343,8 +343,11 @@ export type TabState = {
|
||||
storyId?: number;
|
||||
position?: IAnchorPosition;
|
||||
sendAsMessage?: boolean;
|
||||
isForEffects?: boolean;
|
||||
};
|
||||
|
||||
shouldPlayEffectInComposer?: true;
|
||||
|
||||
inlineBots: {
|
||||
isLoading: boolean;
|
||||
byUsername: Record<string, false | InlineBotSettings>;
|
||||
@ -980,6 +983,7 @@ export type GlobalState = {
|
||||
topReactions: ApiReaction[];
|
||||
recentReactions: ApiReaction[];
|
||||
defaultTags: ApiReaction[];
|
||||
effectReactions: ApiReaction[];
|
||||
availableReactions?: ApiAvailableReaction[];
|
||||
hash: {
|
||||
topReactions?: string;
|
||||
@ -1020,6 +1024,10 @@ export type GlobalState = {
|
||||
stickers?: ApiSticker[];
|
||||
hash?: string;
|
||||
};
|
||||
effect: {
|
||||
stickers: ApiSticker[];
|
||||
emojis: ApiSticker[];
|
||||
};
|
||||
};
|
||||
|
||||
customEmojis: {
|
||||
@ -1140,6 +1148,7 @@ export type ApiDraft = {
|
||||
text?: ApiFormattedText;
|
||||
replyInfo?: ApiInputMessageReplyInfo;
|
||||
date?: number;
|
||||
effectId?: string;
|
||||
isLocal?: boolean;
|
||||
};
|
||||
|
||||
@ -1482,6 +1491,7 @@ export interface ActionPayloads {
|
||||
messageList?: MessageList;
|
||||
isReaction?: true; // Reaction to the story are sent in the form of a message
|
||||
isInvertedMedia?: true;
|
||||
effectId?: string;
|
||||
} & WithTabId;
|
||||
sendInviteMessages: {
|
||||
chatId: string;
|
||||
@ -2318,6 +2328,10 @@ export interface ActionPayloads {
|
||||
reaction?: ApiReaction;
|
||||
} & WithTabId;
|
||||
|
||||
openEffectPicker: {
|
||||
chatId: string;
|
||||
position: IAnchorPosition;
|
||||
} & WithTabId;
|
||||
openMessageReactionPicker: {
|
||||
chatId: string;
|
||||
messageId: number;
|
||||
@ -2918,6 +2932,21 @@ export interface ActionPayloads {
|
||||
isInvertedMedia?: true;
|
||||
};
|
||||
|
||||
saveEffectInDraft: {
|
||||
chatId: string;
|
||||
threadId: ThreadId;
|
||||
effectId?: string;
|
||||
};
|
||||
|
||||
setReactionEffect: {
|
||||
chatId: string;
|
||||
threadId: ThreadId;
|
||||
reaction?: ApiReaction;
|
||||
} & WithTabId;
|
||||
|
||||
requestEffectInComposer: WithTabId;
|
||||
hideEffectInComposer: WithTabId;
|
||||
|
||||
updateArchiveSettings: {
|
||||
isMinimized?: boolean;
|
||||
isHidden?: boolean;
|
||||
|
||||
2
src/types/language.d.ts
vendored
2
src/types/language.d.ts
vendored
@ -1511,7 +1511,7 @@ export interface LangPair {
|
||||
'MenuBetaChangelog': undefined;
|
||||
'MenuSwitchToK': undefined;
|
||||
'MenuInstallApp': undefined;
|
||||
|
||||
'RemoveEffect' : undefined;
|
||||
}
|
||||
|
||||
export type LangKey = keyof LangPair;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user