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