Support Dice (#6572)
This commit is contained in:
parent
049db12d92
commit
1c602dddfa
@ -38,6 +38,10 @@ export interface GramJsAppConfig extends LimitsConfig {
|
|||||||
file_reference_base64: string;
|
file_reference_base64: string;
|
||||||
}>;
|
}>;
|
||||||
emojies_send_dice: string[];
|
emojies_send_dice: string[];
|
||||||
|
emojies_send_dice_success: Record<string, {
|
||||||
|
value: number;
|
||||||
|
frame_start: number;
|
||||||
|
}>;
|
||||||
groupcall_video_participants_max: number;
|
groupcall_video_participants_max: number;
|
||||||
reactions_uniq_max: number;
|
reactions_uniq_max: number;
|
||||||
chat_read_mark_size_threshold: number;
|
chat_read_mark_size_threshold: number;
|
||||||
@ -144,6 +148,17 @@ function buildEmojiSounds(appConfig: GramJsAppConfig) {
|
|||||||
}, {}) : {};
|
}, {}) : {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function buildDiceEmojiesSuccess(appConfig: GramJsAppConfig) {
|
||||||
|
const { emojies_send_dice_success } = appConfig;
|
||||||
|
return emojies_send_dice_success ? Object.entries(emojies_send_dice_success).reduce((acc, [key, value]) => {
|
||||||
|
acc[key] = {
|
||||||
|
value: value.value,
|
||||||
|
frameStart: value.frame_start,
|
||||||
|
};
|
||||||
|
return acc;
|
||||||
|
}, {} as ApiAppConfig['diceEmojiesSuccess']) : {};
|
||||||
|
}
|
||||||
|
|
||||||
function getLimit(appConfig: GramJsAppConfig, key: Limit, fallbackKey: ApiLimitType) {
|
function getLimit(appConfig: GramJsAppConfig, key: Limit, fallbackKey: ApiLimitType) {
|
||||||
const defaultLimit = appConfig[`${key}_default`] || DEFAULT_LIMITS[fallbackKey][0];
|
const defaultLimit = appConfig[`${key}_default`] || DEFAULT_LIMITS[fallbackKey][0];
|
||||||
const premiumLimit = appConfig[`${key}_premium`] || DEFAULT_LIMITS[fallbackKey][1];
|
const premiumLimit = appConfig[`${key}_premium`] || DEFAULT_LIMITS[fallbackKey][1];
|
||||||
@ -251,6 +266,8 @@ export function buildAppConfig(json: GramJs.TypeJSONValue, hash: number): ApiApp
|
|||||||
whitelistedBotIds: appConfig.whitelisted_bots,
|
whitelistedBotIds: appConfig.whitelisted_bots,
|
||||||
arePasskeysAvailable: appConfig.settings_display_passkeys,
|
arePasskeysAvailable: appConfig.settings_display_passkeys,
|
||||||
passkeysMaxCount: appConfig.passkeys_account_passkeys_max,
|
passkeysMaxCount: appConfig.passkeys_account_passkeys_max,
|
||||||
|
diceEmojies: appConfig.emojies_send_dice,
|
||||||
|
diceEmojiesSuccess: buildDiceEmojiesSuccess(appConfig),
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import { Api as GramJs } from '../../../lib/gramjs';
|
|||||||
import type {
|
import type {
|
||||||
ApiAudio,
|
ApiAudio,
|
||||||
ApiContact,
|
ApiContact,
|
||||||
|
ApiDice,
|
||||||
ApiDocument,
|
ApiDocument,
|
||||||
ApiFormattedText,
|
ApiFormattedText,
|
||||||
ApiGame,
|
ApiGame,
|
||||||
@ -179,13 +180,16 @@ export function buildMessageMediaContent(
|
|||||||
const game = buildGameFromMedia(media);
|
const game = buildGameFromMedia(media);
|
||||||
if (game) return { game };
|
if (game) return { game };
|
||||||
|
|
||||||
|
const dice = buildDiceFromMedia(media);
|
||||||
|
if (dice) return { dice };
|
||||||
|
|
||||||
const storyData = buildMessageStoryData(media);
|
const storyData = buildMessageStoryData(media);
|
||||||
if (storyData) return { storyData };
|
if (storyData) return { storyData };
|
||||||
|
|
||||||
const giveaway = buildGiweawayFromMedia(media);
|
const giveaway = buildGiveawayFromMedia(media);
|
||||||
if (giveaway) return { giveaway };
|
if (giveaway) return { giveaway };
|
||||||
|
|
||||||
const giveawayResults = buildGiweawayResultsFromMedia(media);
|
const giveawayResults = buildGiveawayResultsFromMedia(media);
|
||||||
if (giveawayResults) return { giveawayResults };
|
if (giveawayResults) return { giveawayResults };
|
||||||
|
|
||||||
const paidMedia = buildPaidMedia(media);
|
const paidMedia = buildPaidMedia(media);
|
||||||
@ -668,7 +672,24 @@ function buildGame(media: GramJs.MessageMediaGame): ApiGame | undefined {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildGiweawayFromMedia(media: GramJs.TypeMessageMedia): ApiGiveaway | undefined {
|
function buildDiceFromMedia(media: GramJs.TypeMessageMedia): ApiDice | undefined {
|
||||||
|
if (!(media instanceof GramJs.MessageMediaDice)) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return buildDice(media);
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildDice(media: GramJs.MessageMediaDice): ApiDice | undefined {
|
||||||
|
const { value, emoticon } = media;
|
||||||
|
return {
|
||||||
|
mediaType: 'dice',
|
||||||
|
value,
|
||||||
|
emoticon,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildGiveawayFromMedia(media: GramJs.TypeMessageMedia): ApiGiveaway | undefined {
|
||||||
if (!(media instanceof GramJs.MessageMediaGiveaway)) {
|
if (!(media instanceof GramJs.MessageMediaGiveaway)) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
@ -696,7 +717,7 @@ function buildGiveaway(media: GramJs.MessageMediaGiveaway): ApiGiveaway | undefi
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildGiweawayResultsFromMedia(media: GramJs.TypeMessageMedia): ApiGiveawayResults | undefined {
|
function buildGiveawayResultsFromMedia(media: GramJs.TypeMessageMedia): ApiGiveawayResults | undefined {
|
||||||
if (!(media instanceof GramJs.MessageMediaGiveawayResults)) {
|
if (!(media instanceof GramJs.MessageMediaGiveawayResults)) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import type {
|
|||||||
ApiAttachment,
|
ApiAttachment,
|
||||||
ApiChat,
|
ApiChat,
|
||||||
ApiContact,
|
ApiContact,
|
||||||
|
ApiDice,
|
||||||
ApiDraft,
|
ApiDraft,
|
||||||
ApiFactCheck,
|
ApiFactCheck,
|
||||||
ApiInputMessageReplyInfo,
|
ApiInputMessageReplyInfo,
|
||||||
@ -428,29 +429,53 @@ function buildNewTodo(todo: ApiNewMediaTodo): ApiMediaTodo {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function buildLocalMessage(
|
export function buildLocalMessage({
|
||||||
chat: ApiChat,
|
chat,
|
||||||
lastMessageId?: number,
|
lastMessageId,
|
||||||
text?: string,
|
text,
|
||||||
entities?: ApiMessageEntity[],
|
entities,
|
||||||
replyInfo?: ApiInputReplyInfo,
|
replyInfo,
|
||||||
suggestedPostInfo?: ApiInputSuggestedPostInfo,
|
suggestedPostInfo,
|
||||||
attachment?: ApiAttachment,
|
attachment,
|
||||||
sticker?: ApiSticker,
|
sticker,
|
||||||
gif?: ApiVideo,
|
gif,
|
||||||
poll?: ApiNewPoll,
|
poll,
|
||||||
todo?: ApiNewMediaTodo,
|
todo,
|
||||||
contact?: ApiContact,
|
contact,
|
||||||
groupedId?: string,
|
groupedId,
|
||||||
scheduledAt?: number,
|
scheduledAt,
|
||||||
scheduleRepeatPeriod?: number,
|
scheduleRepeatPeriod,
|
||||||
sendAs?: ApiPeer,
|
sendAs,
|
||||||
story?: ApiStory | ApiStorySkipped,
|
story,
|
||||||
isInvertedMedia?: true,
|
isInvertedMedia,
|
||||||
effectId?: string,
|
effectId,
|
||||||
isPending?: true,
|
isPending,
|
||||||
messagePriceInStars?: number,
|
messagePriceInStars,
|
||||||
) {
|
dice,
|
||||||
|
}: {
|
||||||
|
chat: ApiChat;
|
||||||
|
lastMessageId?: number;
|
||||||
|
text?: string;
|
||||||
|
entities?: ApiMessageEntity[];
|
||||||
|
replyInfo?: ApiInputReplyInfo;
|
||||||
|
suggestedPostInfo?: ApiInputSuggestedPostInfo;
|
||||||
|
attachment?: ApiAttachment;
|
||||||
|
sticker?: ApiSticker;
|
||||||
|
gif?: ApiVideo;
|
||||||
|
poll?: ApiNewPoll;
|
||||||
|
todo?: ApiNewMediaTodo;
|
||||||
|
contact?: ApiContact;
|
||||||
|
groupedId?: string;
|
||||||
|
scheduledAt?: number;
|
||||||
|
scheduleRepeatPeriod?: number;
|
||||||
|
sendAs?: ApiPeer;
|
||||||
|
story?: ApiStory | ApiStorySkipped;
|
||||||
|
isInvertedMedia?: true;
|
||||||
|
effectId?: string;
|
||||||
|
isPending?: true;
|
||||||
|
messagePriceInStars?: number;
|
||||||
|
dice?: string;
|
||||||
|
}) {
|
||||||
const localId = getNextLocalMessageId(lastMessageId);
|
const localId = getNextLocalMessageId(lastMessageId);
|
||||||
const media = attachment && buildUploadingMedia(attachment);
|
const media = attachment && buildUploadingMedia(attachment);
|
||||||
const isChannel = chat.type === 'chatTypeChannel';
|
const isChannel = chat.type === 'chatTypeChannel';
|
||||||
@ -460,7 +485,13 @@ export function buildLocalMessage(
|
|||||||
const localPoll = poll && buildNewPoll(poll, localId);
|
const localPoll = poll && buildNewPoll(poll, localId);
|
||||||
const localTodo = todo && buildNewTodo(todo);
|
const localTodo = todo && buildNewTodo(todo);
|
||||||
|
|
||||||
const formattedText = text ? addTimestampEntities(
|
const localDice = dice ? {
|
||||||
|
mediaType: 'dice',
|
||||||
|
value: -1,
|
||||||
|
emoticon: dice,
|
||||||
|
} satisfies ApiDice : undefined;
|
||||||
|
|
||||||
|
const formattedText = text && !dice ? addTimestampEntities(
|
||||||
{ text, entities, emojiOnlyCount: undefined },
|
{ text, entities, emojiOnlyCount: undefined },
|
||||||
) : undefined;
|
) : undefined;
|
||||||
|
|
||||||
@ -476,6 +507,7 @@ export function buildLocalMessage(
|
|||||||
storyData: story && { mediaType: 'storyData', ...story },
|
storyData: story && { mediaType: 'storyData', ...story },
|
||||||
pollId: localPoll?.id,
|
pollId: localPoll?.id,
|
||||||
todo: localTodo,
|
todo: localTodo,
|
||||||
|
dice: localDice,
|
||||||
}),
|
}),
|
||||||
date: scheduledAt || getServerTime(),
|
date: scheduledAt || getServerTime(),
|
||||||
isOutgoing: !isChannel,
|
isOutgoing: !isChannel,
|
||||||
|
|||||||
@ -301,7 +301,7 @@ export function sendMessageLocal(
|
|||||||
const {
|
const {
|
||||||
chat, lastMessageId, text, entities, replyInfo, suggestedPostInfo, attachment, sticker, story, gif, poll, todo,
|
chat, lastMessageId, text, entities, replyInfo, suggestedPostInfo, attachment, sticker, story, gif, poll, todo,
|
||||||
contact, scheduledAt, scheduleRepeatPeriod, groupedId, sendAs, wasDrafted, isInvertedMedia, effectId, isPending,
|
contact, scheduledAt, scheduleRepeatPeriod, groupedId, sendAs, wasDrafted, isInvertedMedia, effectId, isPending,
|
||||||
messagePriceInStars,
|
messagePriceInStars, dice,
|
||||||
} = params;
|
} = params;
|
||||||
|
|
||||||
if (!chat) return undefined;
|
if (!chat) return undefined;
|
||||||
@ -309,7 +309,7 @@ export function sendMessageLocal(
|
|||||||
const {
|
const {
|
||||||
message: localMessage,
|
message: localMessage,
|
||||||
poll: localPoll,
|
poll: localPoll,
|
||||||
} = buildLocalMessage(
|
} = buildLocalMessage({
|
||||||
chat,
|
chat,
|
||||||
lastMessageId,
|
lastMessageId,
|
||||||
text,
|
text,
|
||||||
@ -331,7 +331,8 @@ export function sendMessageLocal(
|
|||||||
effectId,
|
effectId,
|
||||||
isPending,
|
isPending,
|
||||||
messagePriceInStars,
|
messagePriceInStars,
|
||||||
);
|
dice,
|
||||||
|
});
|
||||||
|
|
||||||
sendApiUpdate({
|
sendApiUpdate({
|
||||||
'@type': localMessage.isScheduled ? 'newScheduledMessage' : 'newMessage',
|
'@type': localMessage.isScheduled ? 'newScheduledMessage' : 'newMessage',
|
||||||
@ -352,7 +353,7 @@ export function sendApiMessage(
|
|||||||
) {
|
) {
|
||||||
const {
|
const {
|
||||||
chat, text, entities, replyInfo, suggestedPostInfo, suggestedMedia,
|
chat, text, entities, replyInfo, suggestedPostInfo, suggestedMedia,
|
||||||
attachment, sticker, story, gif, poll, todo, contact,
|
attachment, sticker, story, gif, poll, todo, contact, dice,
|
||||||
|
|
||||||
isSilent, scheduledAt, scheduleRepeatPeriod, groupedId, noWebPage, sendAs, shouldUpdateStickerSetOrder,
|
isSilent, scheduledAt, scheduleRepeatPeriod, groupedId, noWebPage, sendAs, shouldUpdateStickerSetOrder,
|
||||||
isInvertedMedia, effectId, webPageMediaSize, webPageUrl, messagePriceInStars,
|
isInvertedMedia, effectId, webPageMediaSize, webPageUrl, messagePriceInStars,
|
||||||
@ -465,6 +466,10 @@ export function sendApiMessage(
|
|||||||
lastName: contact.lastName,
|
lastName: contact.lastName,
|
||||||
vcard: DEFAULT_PRIMITIVES.STRING,
|
vcard: DEFAULT_PRIMITIVES.STRING,
|
||||||
});
|
});
|
||||||
|
} else if (dice) {
|
||||||
|
media = new GramJs.InputMediaDice({
|
||||||
|
emoticon: dice,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
type SharedKeys<T, U> = {
|
type SharedKeys<T, U> = {
|
||||||
|
|||||||
@ -277,6 +277,27 @@ export async function fetchPremiumGifts() {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function fetchDiceStickers({ emoji }: { emoji: string }) {
|
||||||
|
const result = await invokeRequest(new GramJs.messages.GetStickerSet({
|
||||||
|
stickerset: new GramJs.InputStickerSetDice({
|
||||||
|
emoticon: emoji,
|
||||||
|
}),
|
||||||
|
hash: DEFAULT_PRIMITIVES.INT,
|
||||||
|
}));
|
||||||
|
|
||||||
|
if (!(result instanceof GramJs.messages.StickerSet)) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
localDb.stickerSets[String(result.set.id)] = result.set;
|
||||||
|
|
||||||
|
return {
|
||||||
|
set: buildStickerSet(result.set),
|
||||||
|
stickers: processStickerResult(result.documents),
|
||||||
|
packs: processStickerPackResult(result.packs),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export async function fetchTonGifts() {
|
export async function fetchTonGifts() {
|
||||||
const result = await invokeRequest(new GramJs.messages.GetStickerSet({
|
const result = await invokeRequest(new GramJs.messages.GetStickerSet({
|
||||||
stickerset: new GramJs.InputStickerSetTonGifts(),
|
stickerset: new GramJs.InputStickerSetTonGifts(),
|
||||||
|
|||||||
@ -304,6 +304,12 @@ export type ApiGame = {
|
|||||||
document?: ApiDocument;
|
document?: ApiDocument;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type ApiDice = {
|
||||||
|
mediaType: 'dice';
|
||||||
|
value: number;
|
||||||
|
emoticon: string;
|
||||||
|
};
|
||||||
|
|
||||||
export type ApiGiveaway = {
|
export type ApiGiveaway = {
|
||||||
mediaType: 'giveaway';
|
mediaType: 'giveaway';
|
||||||
quantity: number;
|
quantity: number;
|
||||||
@ -608,6 +614,7 @@ export type MediaContent = {
|
|||||||
giveaway?: ApiGiveaway;
|
giveaway?: ApiGiveaway;
|
||||||
giveawayResults?: ApiGiveawayResults;
|
giveawayResults?: ApiGiveawayResults;
|
||||||
paidMedia?: ApiPaidMedia;
|
paidMedia?: ApiPaidMedia;
|
||||||
|
dice?: ApiDice;
|
||||||
ttlSeconds?: number;
|
ttlSeconds?: number;
|
||||||
};
|
};
|
||||||
export type MediaContainer = {
|
export type MediaContainer = {
|
||||||
|
|||||||
@ -1,9 +1,7 @@
|
|||||||
import type { TeactNode } from '../../lib/teact/teact';
|
|
||||||
|
|
||||||
import type { CallbackAction } from '../../global/types';
|
import type { CallbackAction } from '../../global/types';
|
||||||
import type { IconName } from '../../types/icons';
|
import type { IconName } from '../../types/icons';
|
||||||
import type { RegularLangFnParameters } from '../../util/localization';
|
import type { LangFnParameters, RegularLangFnParameters } from '../../util/localization';
|
||||||
import type { ApiDocument, ApiFormattedText, ApiPhoto, ApiReaction } from './messages';
|
import type { ApiDocument, ApiFormattedText, ApiMessageEntity, ApiPhoto, ApiReaction } from './messages';
|
||||||
import type { ApiPremiumSection } from './payments';
|
import type { ApiPremiumSection } from './payments';
|
||||||
import type { ApiBotVerification } from './peers';
|
import type { ApiBotVerification } from './peers';
|
||||||
import type { ApiStarsSubscriptionPricing } from './stars';
|
import type { ApiStarsSubscriptionPricing } from './stars';
|
||||||
@ -123,8 +121,7 @@ export type ApiNotification = {
|
|||||||
localId: string;
|
localId: string;
|
||||||
containerSelector?: string;
|
containerSelector?: string;
|
||||||
type?: 'paidMessage' | undefined;
|
type?: 'paidMessage' | undefined;
|
||||||
title?: string | RegularLangFnParameters;
|
title?: string | LangFnParameters;
|
||||||
message: TeactNode | RegularLangFnParameters;
|
|
||||||
cacheBreaker?: string;
|
cacheBreaker?: string;
|
||||||
actionText?: string | RegularLangFnParameters;
|
actionText?: string | RegularLangFnParameters;
|
||||||
action?: CallbackAction | CallbackAction[];
|
action?: CallbackAction | CallbackAction[];
|
||||||
@ -136,7 +133,13 @@ export type ApiNotification = {
|
|||||||
customEmojiIconId?: string;
|
customEmojiIconId?: string;
|
||||||
shouldUseCustomIcon?: boolean;
|
shouldUseCustomIcon?: boolean;
|
||||||
dismissAction?: CallbackAction;
|
dismissAction?: CallbackAction;
|
||||||
};
|
} & ({
|
||||||
|
message: string;
|
||||||
|
messageEntities?: ApiMessageEntity[];
|
||||||
|
} | {
|
||||||
|
message: LangFnParameters;
|
||||||
|
messageEntities?: undefined;
|
||||||
|
});
|
||||||
|
|
||||||
export type ApiError = {
|
export type ApiError = {
|
||||||
message: string;
|
message: string;
|
||||||
@ -289,6 +292,11 @@ export interface ApiAppConfig {
|
|||||||
whitelistedBotIds?: string[];
|
whitelistedBotIds?: string[];
|
||||||
arePasskeysAvailable: boolean;
|
arePasskeysAvailable: boolean;
|
||||||
passkeysMaxCount: number;
|
passkeysMaxCount: number;
|
||||||
|
diceEmojies: string[];
|
||||||
|
diceEmojiesSuccess: Record<string, {
|
||||||
|
value: number;
|
||||||
|
frameStart: number;
|
||||||
|
}>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ApiConfig {
|
export interface ApiConfig {
|
||||||
|
|||||||
@ -2557,6 +2557,8 @@
|
|||||||
"BotReadTextFromClipboardTitle" = "Clipboard Access";
|
"BotReadTextFromClipboardTitle" = "Clipboard Access";
|
||||||
"BotReadTextFromClipboardDescription" = "{bot} wants to read the contents of your clipboard. Do you want to continue?";
|
"BotReadTextFromClipboardDescription" = "{bot} wants to read the contents of your clipboard. Do you want to continue?";
|
||||||
"BotReadTextFromClipboardConfirm" = "Allow";
|
"BotReadTextFromClipboardConfirm" = "Allow";
|
||||||
|
"DiceToast" = "Send a {emoji} emoji to try your luck.";
|
||||||
|
"DiceToastSend" = "Send";
|
||||||
"ChatTypePrivate" = "Private Chat";
|
"ChatTypePrivate" = "Private Chat";
|
||||||
"ChatTypeGroup" = "Group";
|
"ChatTypeGroup" = "Group";
|
||||||
"ChatTypeChannel" = "Channel";
|
"ChatTypeChannel" = "Channel";
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { ElementRef, FC } from '../../lib/teact/teact';
|
import type { ElementRef } from '../../lib/teact/teact';
|
||||||
import {
|
import {
|
||||||
getIsHeavyAnimating,
|
getIsHeavyAnimating,
|
||||||
memo,
|
memo,
|
||||||
@ -39,6 +39,7 @@ export type OwnProps = {
|
|||||||
tgsUrl?: string;
|
tgsUrl?: string;
|
||||||
play?: boolean | string;
|
play?: boolean | string;
|
||||||
playSegment?: [number, number];
|
playSegment?: [number, number];
|
||||||
|
seekToEnd?: boolean;
|
||||||
speed?: number;
|
speed?: number;
|
||||||
noLoop?: boolean;
|
noLoop?: boolean;
|
||||||
size: number;
|
size: number;
|
||||||
@ -55,11 +56,12 @@ export type OwnProps = {
|
|||||||
onLoad?: NoneToVoidFunction;
|
onLoad?: NoneToVoidFunction;
|
||||||
onEnded?: NoneToVoidFunction;
|
onEnded?: NoneToVoidFunction;
|
||||||
onLoop?: NoneToVoidFunction;
|
onLoop?: NoneToVoidFunction;
|
||||||
|
onFrame?: (index: number) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
const THROTTLE_MS = 150;
|
const THROTTLE_MS = 150;
|
||||||
|
|
||||||
const AnimatedSticker: FC<OwnProps> = ({
|
const AnimatedSticker = ({
|
||||||
ref,
|
ref,
|
||||||
renderId,
|
renderId,
|
||||||
className,
|
className,
|
||||||
@ -68,6 +70,7 @@ const AnimatedSticker: FC<OwnProps> = ({
|
|||||||
play,
|
play,
|
||||||
playSegment,
|
playSegment,
|
||||||
speed,
|
speed,
|
||||||
|
seekToEnd,
|
||||||
noLoop,
|
noLoop,
|
||||||
size,
|
size,
|
||||||
quality,
|
quality,
|
||||||
@ -83,7 +86,8 @@ const AnimatedSticker: FC<OwnProps> = ({
|
|||||||
onLoad,
|
onLoad,
|
||||||
onEnded,
|
onEnded,
|
||||||
onLoop,
|
onLoop,
|
||||||
}) => {
|
onFrame,
|
||||||
|
}: OwnProps) => {
|
||||||
let containerRef = useRef<HTMLDivElement>();
|
let containerRef = useRef<HTMLDivElement>();
|
||||||
if (ref) {
|
if (ref) {
|
||||||
containerRef = ref;
|
containerRef = ref;
|
||||||
@ -160,12 +164,17 @@ const AnimatedSticker: FC<OwnProps> = ({
|
|||||||
onLoad,
|
onLoad,
|
||||||
onEnded,
|
onEnded,
|
||||||
onLoop,
|
onLoop,
|
||||||
|
onFrame,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (speed) {
|
if (speed) {
|
||||||
newAnimation.setSpeed(speed);
|
newAnimation.setSpeed(speed);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (seekToEnd) {
|
||||||
|
newAnimation.seekToEnd();
|
||||||
|
}
|
||||||
|
|
||||||
setAnimation(newAnimation);
|
setAnimation(newAnimation);
|
||||||
animationRef.current = newAnimation;
|
animationRef.current = newAnimation;
|
||||||
});
|
});
|
||||||
@ -207,6 +216,8 @@ const AnimatedSticker: FC<OwnProps> = ({
|
|||||||
|
|
||||||
if (playSegmentRef.current) {
|
if (playSegmentRef.current) {
|
||||||
animation.playSegment(playSegmentRef.current, shouldRestart, viewId);
|
animation.playSegment(playSegmentRef.current, shouldRestart, viewId);
|
||||||
|
} else if (seekToEnd) {
|
||||||
|
animation.seekToEnd();
|
||||||
} else {
|
} else {
|
||||||
animation.play(shouldRestart, viewId);
|
animation.play(shouldRestart, viewId);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { ElementRef, FC } from '../../lib/teact/teact';
|
import type { ElementRef } from '../../lib/teact/teact';
|
||||||
import { memo, useMemo, useRef } from '../../lib/teact/teact';
|
import { memo, useMemo, useRef } from '../../lib/teact/teact';
|
||||||
import { getGlobal } from '../../global';
|
import { getGlobal } from '../../global';
|
||||||
|
|
||||||
@ -16,6 +16,7 @@ import useColorFilter from '../../hooks/stickers/useColorFilter';
|
|||||||
import useCoordsInSharedCanvas from '../../hooks/useCoordsInSharedCanvas';
|
import useCoordsInSharedCanvas from '../../hooks/useCoordsInSharedCanvas';
|
||||||
import useFlag from '../../hooks/useFlag';
|
import useFlag from '../../hooks/useFlag';
|
||||||
import { useIsIntersecting } from '../../hooks/useIntersectionObserver';
|
import { useIsIntersecting } from '../../hooks/useIntersectionObserver';
|
||||||
|
import useLastCallback from '../../hooks/useLastCallback';
|
||||||
import useMedia from '../../hooks/useMedia';
|
import useMedia from '../../hooks/useMedia';
|
||||||
import useMediaTransition from '../../hooks/useMediaTransition';
|
import useMediaTransition from '../../hooks/useMediaTransition';
|
||||||
import useMountAfterHeavyAnimation from '../../hooks/useMountAfterHeavyAnimation';
|
import useMountAfterHeavyAnimation from '../../hooks/useMountAfterHeavyAnimation';
|
||||||
@ -39,24 +40,28 @@ type OwnProps = {
|
|||||||
loopLimit?: number;
|
loopLimit?: number;
|
||||||
shouldLoop?: boolean;
|
shouldLoop?: boolean;
|
||||||
shouldPreloadPreview?: boolean;
|
shouldPreloadPreview?: boolean;
|
||||||
|
skipPreview?: boolean;
|
||||||
forceAlways?: boolean;
|
forceAlways?: boolean;
|
||||||
forceOnHeavyAnimation?: boolean;
|
forceOnHeavyAnimation?: boolean;
|
||||||
observeIntersectionForLoading?: ObserveFn;
|
observeIntersectionForLoading?: ObserveFn;
|
||||||
observeIntersectionForPlaying?: ObserveFn;
|
observeIntersectionForPlaying?: ObserveFn;
|
||||||
noLoad?: boolean;
|
noLoad?: boolean;
|
||||||
noPlay?: boolean;
|
noPlay?: boolean;
|
||||||
|
forceAnimatedStickerOnEnd?: boolean;
|
||||||
noVideoOnMobile?: boolean;
|
noVideoOnMobile?: boolean;
|
||||||
withSharedAnimation?: boolean;
|
withSharedAnimation?: boolean;
|
||||||
sharedCanvasRef?: ElementRef<HTMLCanvasElement>;
|
sharedCanvasRef?: ElementRef<HTMLCanvasElement>;
|
||||||
withTranslucentThumb?: boolean; // With shared canvas thumbs are opaque by default to provide better transition effect
|
withTranslucentThumb?: boolean; // With shared canvas thumbs are opaque by default to provide better transition effect
|
||||||
onVideoEnded?: AnyToVoidFunction;
|
onVideoEnded?: AnyToVoidFunction;
|
||||||
onAnimatedStickerLoop?: AnyToVoidFunction;
|
onAnimatedStickerLoop?: AnyToVoidFunction;
|
||||||
|
onAnimatedStickerFrame?: (frame: number) => void;
|
||||||
|
onAnimatedStickerLoad?: AnyToVoidFunction;
|
||||||
};
|
};
|
||||||
|
|
||||||
const SHARED_PREFIX = 'shared';
|
const SHARED_PREFIX = 'shared';
|
||||||
const STICKER_SIZE = 24;
|
const STICKER_SIZE = 24;
|
||||||
|
|
||||||
const StickerView: FC<OwnProps> = ({
|
const StickerView = ({
|
||||||
containerRef,
|
containerRef,
|
||||||
sticker,
|
sticker,
|
||||||
thumbClassName,
|
thumbClassName,
|
||||||
@ -68,6 +73,7 @@ const StickerView: FC<OwnProps> = ({
|
|||||||
loopLimit,
|
loopLimit,
|
||||||
shouldLoop = false,
|
shouldLoop = false,
|
||||||
shouldPreloadPreview,
|
shouldPreloadPreview,
|
||||||
|
skipPreview,
|
||||||
forceAlways,
|
forceAlways,
|
||||||
forceOnHeavyAnimation,
|
forceOnHeavyAnimation,
|
||||||
observeIntersectionForLoading,
|
observeIntersectionForLoading,
|
||||||
@ -75,12 +81,15 @@ const StickerView: FC<OwnProps> = ({
|
|||||||
noLoad,
|
noLoad,
|
||||||
noPlay,
|
noPlay,
|
||||||
noVideoOnMobile,
|
noVideoOnMobile,
|
||||||
|
forceAnimatedStickerOnEnd,
|
||||||
withSharedAnimation,
|
withSharedAnimation,
|
||||||
withTranslucentThumb,
|
withTranslucentThumb,
|
||||||
sharedCanvasRef,
|
sharedCanvasRef,
|
||||||
onVideoEnded,
|
onVideoEnded,
|
||||||
onAnimatedStickerLoop,
|
onAnimatedStickerLoop,
|
||||||
}) => {
|
onAnimatedStickerFrame,
|
||||||
|
onAnimatedStickerLoad,
|
||||||
|
}: OwnProps) => {
|
||||||
const {
|
const {
|
||||||
id, isLottie, stickerSetInfo, emoji,
|
id, isLottie, stickerSetInfo, emoji,
|
||||||
} = sticker;
|
} = sticker;
|
||||||
@ -111,10 +120,11 @@ const StickerView: FC<OwnProps> = ({
|
|||||||
|
|
||||||
const cachedPreview = mediaLoader.getFromMemory(previewMediaHash);
|
const cachedPreview = mediaLoader.getFromMemory(previewMediaHash);
|
||||||
const isReadyToMountFullMedia = useMountAfterHeavyAnimation(hasIntersectedForPlayingRef.current);
|
const isReadyToMountFullMedia = useMountAfterHeavyAnimation(hasIntersectedForPlayingRef.current);
|
||||||
const shouldForcePreview = isUnsupportedVideo || (isStatic ? isSmall : noPlay);
|
const shouldForcePreview = !skipPreview && (isUnsupportedVideo || (isStatic ? isSmall : noPlay));
|
||||||
const shouldLoadPreview = !customColor && !cachedPreview && (!isReadyToMountFullMedia || shouldForcePreview);
|
const shouldLoadPreview = !skipPreview && !customColor && !cachedPreview
|
||||||
|
&& (!isReadyToMountFullMedia || shouldForcePreview);
|
||||||
const previewMediaData = useMedia(previewMediaHash, !shouldLoadPreview);
|
const previewMediaData = useMedia(previewMediaHash, !shouldLoadPreview);
|
||||||
const withPreview = shouldLoadPreview || cachedPreview;
|
const withPreview = !skipPreview && (shouldLoadPreview || cachedPreview);
|
||||||
|
|
||||||
const shouldSkipLoadingFullMedia = Boolean(shouldForcePreview || (
|
const shouldSkipLoadingFullMedia = Boolean(shouldForcePreview || (
|
||||||
fullMediaHash === previewMediaHash && (cachedPreview || previewMediaData)
|
fullMediaHash === previewMediaHash && (cachedPreview || previewMediaData)
|
||||||
@ -125,7 +135,7 @@ const StickerView: FC<OwnProps> = ({
|
|||||||
const isFullMediaReady = shouldRenderFullMedia && (isStatic || isPlayerReady);
|
const isFullMediaReady = shouldRenderFullMedia && (isStatic || isPlayerReady);
|
||||||
|
|
||||||
const thumbDataUri = useThumbnail(sticker.thumbnail);
|
const thumbDataUri = useThumbnail(sticker.thumbnail);
|
||||||
const thumbData = cachedPreview || previewMediaData || thumbDataUri;
|
const thumbData = !skipPreview ? (cachedPreview || previewMediaData || thumbDataUri) : undefined;
|
||||||
const isThumbOpaque = sharedCanvasRef && !withTranslucentThumb;
|
const isThumbOpaque = sharedCanvasRef && !withTranslucentThumb;
|
||||||
|
|
||||||
const noCrossTransition = Boolean(isLottie && withPreview);
|
const noCrossTransition = Boolean(isLottie && withPreview);
|
||||||
@ -153,6 +163,11 @@ const StickerView: FC<OwnProps> = ({
|
|||||||
].filter(Boolean).join('_')
|
].filter(Boolean).join('_')
|
||||||
), [id, size, customColor, dpr, withSharedAnimation, randomIdPrefix]);
|
), [id, size, customColor, dpr, withSharedAnimation, randomIdPrefix]);
|
||||||
|
|
||||||
|
const handleAnimatedStickerLoad = useLastCallback(() => {
|
||||||
|
onAnimatedStickerLoad?.();
|
||||||
|
markPlayerReady();
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<img
|
<img
|
||||||
@ -182,15 +197,17 @@ const StickerView: FC<OwnProps> = ({
|
|||||||
)}
|
)}
|
||||||
tgsUrl={fullMediaData}
|
tgsUrl={fullMediaData}
|
||||||
play={shouldPlay}
|
play={shouldPlay}
|
||||||
|
seekToEnd={forceAnimatedStickerOnEnd}
|
||||||
noLoop={!shouldLoop}
|
noLoop={!shouldLoop}
|
||||||
forceOnHeavyAnimation={forceAlways || forceOnHeavyAnimation}
|
forceOnHeavyAnimation={forceAlways || forceOnHeavyAnimation}
|
||||||
forceAlways={forceAlways}
|
forceAlways={forceAlways}
|
||||||
isLowPriority={isSmall && !selectIsAlwaysHighPriorityEmoji(getGlobal(), stickerSetInfo)}
|
isLowPriority={isSmall && !selectIsAlwaysHighPriorityEmoji(getGlobal(), stickerSetInfo)}
|
||||||
sharedCanvas={sharedCanvasRef?.current || undefined}
|
sharedCanvas={sharedCanvasRef?.current || undefined}
|
||||||
sharedCanvasCoords={coords}
|
sharedCanvasCoords={coords}
|
||||||
onLoad={markPlayerReady}
|
onLoad={handleAnimatedStickerLoad}
|
||||||
onLoop={onAnimatedStickerLoop}
|
onLoop={onAnimatedStickerLoop}
|
||||||
onEnded={onAnimatedStickerLoop}
|
onEnded={onAnimatedStickerLoop}
|
||||||
|
onFrame={onAnimatedStickerFrame}
|
||||||
color={customColor}
|
color={customColor}
|
||||||
/>
|
/>
|
||||||
) : isVideo ? (
|
) : isVideo ? (
|
||||||
|
|||||||
@ -146,6 +146,7 @@ type StateProps = {
|
|||||||
isAccountFrozen?: boolean;
|
isAccountFrozen?: boolean;
|
||||||
isAppConfigLoaded?: boolean;
|
isAppConfigLoaded?: boolean;
|
||||||
isFoldersSidebarShown: boolean;
|
isFoldersSidebarShown: boolean;
|
||||||
|
diceEmojies?: string[];
|
||||||
selectedGiftAuction?: ApiStarGiftAuctionState;
|
selectedGiftAuction?: ApiStarGiftAuctionState;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -200,6 +201,7 @@ const Main = ({
|
|||||||
isAccountFrozen,
|
isAccountFrozen,
|
||||||
isAppConfigLoaded,
|
isAppConfigLoaded,
|
||||||
isFoldersSidebarShown,
|
isFoldersSidebarShown,
|
||||||
|
diceEmojies,
|
||||||
selectedGiftAuction,
|
selectedGiftAuction,
|
||||||
}: OwnProps & StateProps) => {
|
}: OwnProps & StateProps) => {
|
||||||
const {
|
const {
|
||||||
@ -216,6 +218,7 @@ const Main = ({
|
|||||||
loadCountryList,
|
loadCountryList,
|
||||||
loadAvailableReactions,
|
loadAvailableReactions,
|
||||||
loadStickerSets,
|
loadStickerSets,
|
||||||
|
loadDiceStickers,
|
||||||
loadPremiumGifts,
|
loadPremiumGifts,
|
||||||
loadTonGifts,
|
loadTonGifts,
|
||||||
loadStarGifts,
|
loadStarGifts,
|
||||||
@ -390,6 +393,12 @@ const Main = ({
|
|||||||
}
|
}
|
||||||
}, [addedSetIds, addedCustomEmojiIds, isMasterTab, isSynced, isAppConfigLoaded, isAccountFrozen]);
|
}, [addedSetIds, addedCustomEmojiIds, isMasterTab, isSynced, isAppConfigLoaded, isAccountFrozen]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isMasterTab && isSynced && isAppConfigLoaded && !isAccountFrozen && diceEmojies) {
|
||||||
|
loadDiceStickers();
|
||||||
|
}
|
||||||
|
}, [isMasterTab, isSynced, isAppConfigLoaded, isAccountFrozen, diceEmojies]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadBotFreezeAppeal();
|
loadBotFreezeAppeal();
|
||||||
}, [isAppConfigLoaded]);
|
}, [isAppConfigLoaded]);
|
||||||
@ -708,6 +717,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
isAccountFrozen,
|
isAccountFrozen,
|
||||||
isAppConfigLoaded: global.isAppConfigLoaded,
|
isAppConfigLoaded: global.isAppConfigLoaded,
|
||||||
isFoldersSidebarShown: foldersPosition === FOLDERS_POSITION_LEFT && !isMobile && selectAreFoldersPresent(global),
|
isFoldersSidebarShown: foldersPosition === FOLDERS_POSITION_LEFT && !isMobile && selectAreFoldersPresent(global),
|
||||||
|
diceEmojies: global.appConfig?.diceEmojies,
|
||||||
selectedGiftAuction,
|
selectedGiftAuction,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|||||||
@ -181,6 +181,7 @@ import AnimatedEmoji from './AnimatedEmoji';
|
|||||||
import CommentButton from './CommentButton';
|
import CommentButton from './CommentButton';
|
||||||
import Contact from './Contact';
|
import Contact from './Contact';
|
||||||
import ContextMenuContainer from './ContextMenuContainer.async';
|
import ContextMenuContainer from './ContextMenuContainer.async';
|
||||||
|
import DiceWrapper from './dice/DiceWrapper';
|
||||||
import FactCheck from './FactCheck';
|
import FactCheck from './FactCheck';
|
||||||
import Game from './Game';
|
import Game from './Game';
|
||||||
import Giveaway from './Giveaway';
|
import Giveaway from './Giveaway';
|
||||||
@ -477,6 +478,7 @@ const Message = ({
|
|||||||
const [isPlayingSnapAnimation, setIsPlayingSnapAnimation] = useState(false);
|
const [isPlayingSnapAnimation, setIsPlayingSnapAnimation] = useState(false);
|
||||||
const [isPlayingDeleteAnimation, setIsPlayingDeleteAnimation] = useState(false);
|
const [isPlayingDeleteAnimation, setIsPlayingDeleteAnimation] = useState(false);
|
||||||
const [shouldPlayEffect, requestEffect, hideEffect] = useFlag();
|
const [shouldPlayEffect, requestEffect, hideEffect] = useFlag();
|
||||||
|
const [shouldPlayDiceEffect, requestDiceEffect, hideDiceEffect] = useFlag();
|
||||||
const [isDeclineDialogOpen, openDeclineDialog, closeDeclineDialog] = useFlag();
|
const [isDeclineDialogOpen, openDeclineDialog, closeDeclineDialog] = useFlag();
|
||||||
const [declineReason, setDeclineReason] = useState('');
|
const [declineReason, setDeclineReason] = useState('');
|
||||||
const { isMobile, isTouchScreen } = useAppLayout();
|
const { isMobile, isTouchScreen } = useAppLayout();
|
||||||
@ -547,7 +549,7 @@ const Message = ({
|
|||||||
voice, document, sticker, contact,
|
voice, document, sticker, contact,
|
||||||
invoice, location,
|
invoice, location,
|
||||||
action, game, storyData, giveaway,
|
action, game, storyData, giveaway,
|
||||||
giveawayResults, todo,
|
giveawayResults, todo, dice,
|
||||||
} = getMessageContent(message);
|
} = getMessageContent(message);
|
||||||
|
|
||||||
const messageReplyInfo = getMessageReplyInfo(message);
|
const messageReplyInfo = getMessageReplyInfo(message);
|
||||||
@ -780,6 +782,14 @@ const Message = ({
|
|||||||
}
|
}
|
||||||
}, [effect, isLocal, memoFirstUnreadIdRef, messageId, sticker?.hasEffect]);
|
}, [effect, isLocal, memoFirstUnreadIdRef, messageId, sticker?.hasEffect]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (dice && ((
|
||||||
|
memoFirstUnreadIdRef?.current && messageId >= memoFirstUnreadIdRef.current
|
||||||
|
) || isLocal)) {
|
||||||
|
requestDiceEffect();
|
||||||
|
}
|
||||||
|
}, [dice, memoFirstUnreadIdRef, messageId, isLocal]);
|
||||||
|
|
||||||
const detectedLanguage = useTextLanguage(
|
const detectedLanguage = useTextLanguage(
|
||||||
text?.text,
|
text?.text,
|
||||||
!(areTranslationsEnabled && shouldDetectChatLanguage) || isTypingDraft,
|
!(areTranslationsEnabled && shouldDetectChatLanguage) || isTypingDraft,
|
||||||
@ -1299,6 +1309,15 @@ const Message = ({
|
|||||||
canAutoLoadMedia={canAutoLoadMedia}
|
canAutoLoadMedia={canAutoLoadMedia}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
{dice && (
|
||||||
|
<DiceWrapper
|
||||||
|
isLocal={isLocal}
|
||||||
|
dice={dice}
|
||||||
|
isOutgoing={isOwn}
|
||||||
|
canPlayWinEffect={shouldPlayDiceEffect}
|
||||||
|
onEffectPlayed={hideDiceEffect}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
{invoice?.extendedMedia && (
|
{invoice?.extendedMedia && (
|
||||||
<InvoiceMediaPreview
|
<InvoiceMediaPreview
|
||||||
message={message}
|
message={message}
|
||||||
|
|||||||
@ -179,7 +179,8 @@ const Poll: FC<OwnProps> = ({
|
|||||||
const showSolution = useLastCallback(() => {
|
const showSolution = useLastCallback(() => {
|
||||||
showNotification({
|
showNotification({
|
||||||
localId: getMessageKey(message),
|
localId: getMessageKey(message),
|
||||||
message: renderTextWithEntities({ text: poll.results.solution!, entities: poll.results.solutionEntities }),
|
message: poll.results.solution!,
|
||||||
|
messageEntities: poll.results.solutionEntities,
|
||||||
duration: SOLUTION_DURATION,
|
duration: SOLUTION_DURATION,
|
||||||
containerSelector: SOLUTION_CONTAINER_ID,
|
containerSelector: SOLUTION_CONTAINER_ID,
|
||||||
});
|
});
|
||||||
|
|||||||
19
src/components/middle/message/dice/Dice.module.scss
Normal file
19
src/components/middle/message/dice/Dice.module.scss
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
.root {
|
||||||
|
cursor: var(--custom-cursor, pointer);
|
||||||
|
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
display: block !important;
|
||||||
|
|
||||||
|
width: var(--_size);
|
||||||
|
height: var(--_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sticker {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hidden {
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
141
src/components/middle/message/dice/Dice.tsx
Normal file
141
src/components/middle/message/dice/Dice.tsx
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
import { memo, useRef, useState } from '@teact';
|
||||||
|
import { getActions, withGlobal } from '../../../../global';
|
||||||
|
|
||||||
|
import type { ApiSticker } from '../../../../api/types';
|
||||||
|
import type { OwnProps } from './DiceWrapper';
|
||||||
|
|
||||||
|
import { selectDiceSticker, selectIdleDiceSticker } from '../../../../global/selectors/symbols';
|
||||||
|
import buildClassName from '../../../../util/buildClassName';
|
||||||
|
import { getStickerDimensions, REM } from '../../../common/helpers/mediaDimensions';
|
||||||
|
|
||||||
|
import useAppLayout from '../../../../hooks/useAppLayout';
|
||||||
|
import useFlag from '../../../../hooks/useFlag';
|
||||||
|
import useLastCallback from '../../../../hooks/useLastCallback';
|
||||||
|
|
||||||
|
import StickerView from '../../../common/StickerView';
|
||||||
|
|
||||||
|
import styles from './Dice.module.scss';
|
||||||
|
|
||||||
|
type StateProps = {
|
||||||
|
idleSticker?: ApiSticker;
|
||||||
|
valueSticker?: ApiSticker;
|
||||||
|
winEffect?: {
|
||||||
|
value: number;
|
||||||
|
frameStart: number;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const FALLBACK_SIZE = 13 * REM;
|
||||||
|
|
||||||
|
const Dice = ({
|
||||||
|
dice,
|
||||||
|
idleSticker,
|
||||||
|
valueSticker,
|
||||||
|
winEffect,
|
||||||
|
canPlayWinEffect,
|
||||||
|
isLocal,
|
||||||
|
isOutgoing,
|
||||||
|
onEffectPlayed,
|
||||||
|
observeIntersectionForLoading,
|
||||||
|
observeIntersectionForPlaying,
|
||||||
|
}: OwnProps & StateProps) => {
|
||||||
|
const { requestConfetti, showNotification } = getActions();
|
||||||
|
const { isMobile } = useAppLayout();
|
||||||
|
const { width } = idleSticker ? getStickerDimensions(idleSticker, isMobile) : { width: FALLBACK_SIZE };
|
||||||
|
|
||||||
|
const shouldSkipToEnd = !canPlayWinEffect && !isLocal;
|
||||||
|
const [isShowingResult, setIsShowingResult] = useState<boolean>(shouldSkipToEnd);
|
||||||
|
const [isValueStickerLoaded, markValueStickerLoaded] = useFlag();
|
||||||
|
|
||||||
|
const idleContainerRef = useRef<HTMLDivElement>();
|
||||||
|
const valueContainerRef = useRef<HTMLDivElement>();
|
||||||
|
|
||||||
|
const onIdleLoop = useLastCallback(() => {
|
||||||
|
setIsShowingResult(isValueStickerLoaded);
|
||||||
|
});
|
||||||
|
|
||||||
|
const onValueFrame = useLastCallback((frame: number) => {
|
||||||
|
if (canPlayWinEffect && isOutgoing && dice.value === winEffect?.value && frame === winEffect?.frameStart) {
|
||||||
|
requestConfetti({});
|
||||||
|
onEffectPlayed?.();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleClick = useLastCallback(() => {
|
||||||
|
showNotification({
|
||||||
|
message: {
|
||||||
|
key: 'DiceToast',
|
||||||
|
variables: {
|
||||||
|
emoji: dice.emoticon,
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
withNodes: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
action: {
|
||||||
|
action: 'sendDiceInCurrentChat',
|
||||||
|
payload: {
|
||||||
|
emoji: dice.emoticon,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
actionText: {
|
||||||
|
key: 'DiceToastSend',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.root} style={`--_size: ${width}px`} onClick={handleClick}>
|
||||||
|
{idleSticker && (
|
||||||
|
<div
|
||||||
|
ref={idleContainerRef}
|
||||||
|
className={buildClassName(styles.sticker, isShowingResult && styles.hidden)}
|
||||||
|
>
|
||||||
|
<StickerView
|
||||||
|
containerRef={idleContainerRef}
|
||||||
|
sticker={idleSticker}
|
||||||
|
size={width}
|
||||||
|
noPlay={isShowingResult}
|
||||||
|
shouldLoop
|
||||||
|
forceAlways
|
||||||
|
skipPreview={isShowingResult}
|
||||||
|
onAnimatedStickerLoop={onIdleLoop}
|
||||||
|
observeIntersectionForLoading={observeIntersectionForLoading}
|
||||||
|
observeIntersectionForPlaying={observeIntersectionForPlaying}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{valueSticker && (
|
||||||
|
<div ref={valueContainerRef} className={buildClassName(styles.sticker, !isShowingResult && styles.hidden)}>
|
||||||
|
<StickerView
|
||||||
|
containerRef={valueContainerRef}
|
||||||
|
sticker={valueSticker}
|
||||||
|
size={width}
|
||||||
|
noPlay={!isShowingResult}
|
||||||
|
skipPreview
|
||||||
|
forceAlways
|
||||||
|
forceAnimatedStickerOnEnd={shouldSkipToEnd}
|
||||||
|
observeIntersectionForLoading={observeIntersectionForLoading}
|
||||||
|
observeIntersectionForPlaying={observeIntersectionForPlaying}
|
||||||
|
onAnimatedStickerLoad={markValueStickerLoaded}
|
||||||
|
onAnimatedStickerFrame={onValueFrame}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(withGlobal<OwnProps>(
|
||||||
|
(global, { dice }): Complete<StateProps> => {
|
||||||
|
const idleSticker = selectIdleDiceSticker(global, dice.emoticon);
|
||||||
|
const valueSticker = selectDiceSticker(global, dice.emoticon, dice.value);
|
||||||
|
|
||||||
|
const winEffect = global.appConfig.diceEmojiesSuccess[dice.emoticon];
|
||||||
|
return {
|
||||||
|
idleSticker,
|
||||||
|
valueSticker,
|
||||||
|
winEffect,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
)(Dice));
|
||||||
29
src/components/middle/message/dice/DiceWrapper.tsx
Normal file
29
src/components/middle/message/dice/DiceWrapper.tsx
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import { memo } from '../../../../lib/teact/teact';
|
||||||
|
|
||||||
|
import type { ApiDice } from '../../../../api/types';
|
||||||
|
import type { ObserveFn } from '../../../../hooks/useIntersectionObserver';
|
||||||
|
|
||||||
|
import { SLOT_MACHINE_EMOJI } from '../../../../config';
|
||||||
|
|
||||||
|
import Dice from './Dice';
|
||||||
|
import SlotMachine from './SlotMachine';
|
||||||
|
|
||||||
|
export type OwnProps = {
|
||||||
|
dice: ApiDice;
|
||||||
|
canPlayWinEffect?: boolean;
|
||||||
|
isLocal?: boolean;
|
||||||
|
isOutgoing?: boolean;
|
||||||
|
onEffectPlayed?: NoneToVoidFunction;
|
||||||
|
observeIntersectionForLoading?: ObserveFn;
|
||||||
|
observeIntersectionForPlaying?: ObserveFn;
|
||||||
|
};
|
||||||
|
|
||||||
|
const DiceWrapper = (props: OwnProps) => {
|
||||||
|
if (props.dice.emoticon === SLOT_MACHINE_EMOJI) {
|
||||||
|
return <SlotMachine {...props} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <Dice {...props} />;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(DiceWrapper);
|
||||||
202
src/components/middle/message/dice/SlotMachine.tsx
Normal file
202
src/components/middle/message/dice/SlotMachine.tsx
Normal file
@ -0,0 +1,202 @@
|
|||||||
|
import { memo, useRef, useState } from '../../../../lib/teact/teact';
|
||||||
|
import { getActions, withGlobal } from '../../../../global';
|
||||||
|
|
||||||
|
import type { ApiStickerSet } from '../../../../api/types';
|
||||||
|
import type { OwnProps } from './DiceWrapper';
|
||||||
|
|
||||||
|
import { SLOT_MACHINE_EMOJI } from '../../../../config';
|
||||||
|
import { getStickerMediaHash } from '../../../../global/helpers';
|
||||||
|
import { selectStickerSet } from '../../../../global/selectors';
|
||||||
|
import buildClassName from '../../../../util/buildClassName';
|
||||||
|
import { getStickerDimensions, REM } from '../../../common/helpers/mediaDimensions';
|
||||||
|
import { prepareSlotMachine } from '../helpers/prepareSlotMachine';
|
||||||
|
|
||||||
|
import useAppLayout from '../../../../hooks/useAppLayout';
|
||||||
|
import useFlag from '../../../../hooks/useFlag';
|
||||||
|
import { useIsIntersecting } from '../../../../hooks/useIntersectionObserver';
|
||||||
|
import useLastCallback from '../../../../hooks/useLastCallback';
|
||||||
|
import useMedia from '../../../../hooks/useMedia';
|
||||||
|
import useMediaTransition from '../../../../hooks/useMediaTransition';
|
||||||
|
|
||||||
|
import AnimatedSticker from '../../../common/AnimatedSticker';
|
||||||
|
|
||||||
|
import styles from './Dice.module.scss';
|
||||||
|
|
||||||
|
type StateProps = {
|
||||||
|
slotsStickerSet?: ApiStickerSet;
|
||||||
|
winEffect?: {
|
||||||
|
value: number;
|
||||||
|
frameStart: number;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const FALLBACK_SIZE = 13 * REM;
|
||||||
|
const STICKER_RENDER_DELAY = 100;
|
||||||
|
const WIN_BACKGROUND_DELAY = 700;
|
||||||
|
|
||||||
|
const SlotMachine = ({
|
||||||
|
dice,
|
||||||
|
canPlayWinEffect,
|
||||||
|
isLocal,
|
||||||
|
isOutgoing,
|
||||||
|
slotsStickerSet,
|
||||||
|
winEffect,
|
||||||
|
onEffectPlayed,
|
||||||
|
observeIntersectionForLoading,
|
||||||
|
}: OwnProps & StateProps) => {
|
||||||
|
const { requestConfetti, showNotification } = getActions();
|
||||||
|
const { isMobile } = useAppLayout();
|
||||||
|
|
||||||
|
const isWin = dice.value === winEffect?.value;
|
||||||
|
const shouldSkipToEnd = !canPlayWinEffect && !isLocal;
|
||||||
|
|
||||||
|
const loadedCountRef = useRef(0);
|
||||||
|
const [isReady, markReady] = useFlag(!shouldSkipToEnd);
|
||||||
|
|
||||||
|
const [backgroundState, setBackgroundState] = useState<'base' | 'win'>(shouldSkipToEnd && isWin ? 'win' : 'base');
|
||||||
|
const [spinState, setSpinState] = useState<'base' | 'result'>(shouldSkipToEnd ? 'result' : 'base');
|
||||||
|
|
||||||
|
const { ref } = useMediaTransition({
|
||||||
|
hasMediaData: isReady,
|
||||||
|
});
|
||||||
|
|
||||||
|
const canLoad = useIsIntersecting(ref, observeIntersectionForLoading);
|
||||||
|
|
||||||
|
const preparedStickers = slotsStickerSet?.stickers && prepareSlotMachine(slotsStickerSet?.stickers, dice.value);
|
||||||
|
const backgroundHash = preparedStickers?.background
|
||||||
|
? getStickerMediaHash(preparedStickers.background, 'full') : undefined;
|
||||||
|
const backgroundData = useMedia(backgroundHash, !canLoad);
|
||||||
|
const frameWinHash = preparedStickers?.frameWin && isWin
|
||||||
|
? getStickerMediaHash(preparedStickers.frameWin, 'full') : undefined;
|
||||||
|
const frameWinData = useMedia(frameWinHash, !canLoad);
|
||||||
|
const frameStartHash = preparedStickers?.frameStart
|
||||||
|
? getStickerMediaHash(preparedStickers.frameStart, 'full') : undefined;
|
||||||
|
const frameStartData = useMedia(frameStartHash, !canLoad);
|
||||||
|
|
||||||
|
const leftSpinHash = preparedStickers?.leftSpin
|
||||||
|
? getStickerMediaHash(preparedStickers.leftSpin, 'full') : undefined;
|
||||||
|
const leftSpinData = useMedia(leftSpinHash, !canLoad);
|
||||||
|
const middleSpinHash = preparedStickers?.middleSpin
|
||||||
|
? getStickerMediaHash(preparedStickers.middleSpin, 'full') : undefined;
|
||||||
|
const middleSpinData = useMedia(middleSpinHash, !canLoad);
|
||||||
|
const rightSpinHash = preparedStickers?.rightSpin
|
||||||
|
? getStickerMediaHash(preparedStickers.rightSpin, 'full') : undefined;
|
||||||
|
const rightSpinData = useMedia(rightSpinHash, !canLoad);
|
||||||
|
|
||||||
|
const leftResultHash = preparedStickers?.leftResult
|
||||||
|
? getStickerMediaHash(preparedStickers.leftResult, 'full') : undefined;
|
||||||
|
const leftResultData = useMedia(leftResultHash, !canLoad);
|
||||||
|
const middleResultHash = preparedStickers?.middleResult
|
||||||
|
? getStickerMediaHash(preparedStickers.middleResult, 'full') : undefined;
|
||||||
|
const middleResultData = useMedia(middleResultHash, !canLoad);
|
||||||
|
const rightResultHash = preparedStickers?.rightResult
|
||||||
|
? getStickerMediaHash(preparedStickers.rightResult, 'full') : undefined;
|
||||||
|
const rightResultData = useMedia(rightResultHash, !canLoad);
|
||||||
|
|
||||||
|
const { width } = preparedStickers ? getStickerDimensions(preparedStickers.background, isMobile)
|
||||||
|
: { width: FALLBACK_SIZE };
|
||||||
|
|
||||||
|
const isWaitingForResults = !leftResultData || !middleResultData || !rightResultData;
|
||||||
|
|
||||||
|
const handleLoaded = useLastCallback(() => {
|
||||||
|
loadedCountRef.current += 1;
|
||||||
|
if (loadedCountRef.current >= 3) {
|
||||||
|
setTimeout(() => {
|
||||||
|
markReady();
|
||||||
|
}, STICKER_RENDER_DELAY);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleSpinEnded = useLastCallback(() => {
|
||||||
|
if (isWaitingForResults) return;
|
||||||
|
setSpinState('result');
|
||||||
|
// Result spin start - too early. Result spin end - too late.
|
||||||
|
if (isWin) setTimeout(() => setBackgroundState('win'), WIN_BACKGROUND_DELAY);
|
||||||
|
});
|
||||||
|
|
||||||
|
const onWinBackgroundFrame = useLastCallback((frame: number) => {
|
||||||
|
if (canPlayWinEffect && isOutgoing && isWin && frame === winEffect?.frameStart) {
|
||||||
|
requestConfetti({});
|
||||||
|
onEffectPlayed?.();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleClick = useLastCallback(() => {
|
||||||
|
showNotification({
|
||||||
|
message: {
|
||||||
|
key: 'DiceToast',
|
||||||
|
variables: {
|
||||||
|
emoji: dice.emoticon,
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
withNodes: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
action: {
|
||||||
|
action: 'sendDiceInCurrentChat',
|
||||||
|
payload: {
|
||||||
|
emoji: dice.emoticon,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
actionText: {
|
||||||
|
key: 'DiceToastSend',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function renderSticker(
|
||||||
|
tgsUrl: string | undefined,
|
||||||
|
isHidden: boolean,
|
||||||
|
shouldLoop?: boolean,
|
||||||
|
noRenderOnHidden?: boolean,
|
||||||
|
onEnded?: NoneToVoidFunction,
|
||||||
|
onFrame?: (frame: number) => void,
|
||||||
|
) {
|
||||||
|
if (noRenderOnHidden && isHidden) return undefined;
|
||||||
|
return (
|
||||||
|
<div className={buildClassName(styles.sticker, isHidden && styles.hidden)}>
|
||||||
|
<AnimatedSticker
|
||||||
|
tgsUrl={tgsUrl}
|
||||||
|
size={width}
|
||||||
|
play={!isHidden}
|
||||||
|
noLoop={!shouldLoop}
|
||||||
|
forceAlways
|
||||||
|
onEnded={onEnded}
|
||||||
|
onFrame={onFrame}
|
||||||
|
onLoad={!isHidden ? handleLoaded : undefined}
|
||||||
|
seekToEnd={shouldSkipToEnd}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div ref={ref} className={styles.root} style={`--_size: ${width}px`} onClick={handleClick}>
|
||||||
|
{renderSticker(backgroundData, backgroundState !== 'base', false, true)}
|
||||||
|
{renderSticker(frameWinData, backgroundState !== 'win', false, false, undefined, onWinBackgroundFrame)}
|
||||||
|
|
||||||
|
{renderSticker(leftSpinData, spinState !== 'base', isWaitingForResults, true)}
|
||||||
|
{renderSticker(middleSpinData, spinState !== 'base', isWaitingForResults, true)}
|
||||||
|
{renderSticker(rightSpinData, spinState !== 'base', isWaitingForResults, true, handleSpinEnded)}
|
||||||
|
|
||||||
|
{renderSticker(leftResultData, spinState !== 'result')}
|
||||||
|
{renderSticker(middleResultData, spinState !== 'result')}
|
||||||
|
{renderSticker(rightResultData, spinState !== 'result')}
|
||||||
|
|
||||||
|
{renderSticker(frameStartData, false)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(withGlobal(
|
||||||
|
(global): Complete<StateProps> => {
|
||||||
|
const stickerSetId = global.stickers.diceSetIdByEmoji?.[SLOT_MACHINE_EMOJI];
|
||||||
|
const slotsStickerSet = stickerSetId ? selectStickerSet(global, stickerSetId) : undefined;
|
||||||
|
const winEffect = global.appConfig.diceEmojiesSuccess[SLOT_MACHINE_EMOJI];
|
||||||
|
|
||||||
|
return {
|
||||||
|
slotsStickerSet,
|
||||||
|
winEffect,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
)(SlotMachine));
|
||||||
@ -58,7 +58,7 @@ export function translateWithYou<K extends LangKey>(
|
|||||||
export function getPinnedMediaValue(lang: LangFn, message: ApiMessage) {
|
export function getPinnedMediaValue(lang: LangFn, message: ApiMessage) {
|
||||||
const {
|
const {
|
||||||
audio, contact, document, game, giveaway, giveawayResults, paidMedia, storyData,
|
audio, contact, document, game, giveaway, giveawayResults, paidMedia, storyData,
|
||||||
invoice, location, photo, pollId, sticker, video, voice,
|
invoice, location, photo, pollId, sticker, video, voice, dice,
|
||||||
} = getMessageContent(message);
|
} = getMessageContent(message);
|
||||||
|
|
||||||
if (message.groupedId || paidMedia) return lang('ActionPinnedMediaAlbum');
|
if (message.groupedId || paidMedia) return lang('ActionPinnedMediaAlbum');
|
||||||
@ -78,6 +78,7 @@ export function getPinnedMediaValue(lang: LangFn, message: ApiMessage) {
|
|||||||
if (pollId) return lang('ActionPinnedMediaPoll');
|
if (pollId) return lang('ActionPinnedMediaPoll');
|
||||||
if (giveaway) return lang('ActionPinnedMediaGiveaway');
|
if (giveaway) return lang('ActionPinnedMediaGiveaway');
|
||||||
if (giveawayResults) return lang('ActionPinnedMediaGiveawayResults');
|
if (giveawayResults) return lang('ActionPinnedMediaGiveawayResults');
|
||||||
|
if (dice) return dice.emoticon;
|
||||||
|
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|||||||
41
src/components/middle/message/helpers/prepareSlotMachine.ts
Normal file
41
src/components/middle/message/helpers/prepareSlotMachine.ts
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
import type { ApiSticker } from '../../../../api/types';
|
||||||
|
|
||||||
|
const SLOT_MAP = [1, 2, 3, 0];
|
||||||
|
export function prepareSlotMachine(stickers: ApiSticker[], value: number) {
|
||||||
|
const isLocal = value === -1;
|
||||||
|
|
||||||
|
const leftSlot = (value - 1) & 0b11;
|
||||||
|
const middleSlot = ((value - 1) >> 2) & 0b11;
|
||||||
|
const rightSlot = ((value - 1) >> 4) & 0b11;
|
||||||
|
|
||||||
|
const bg = stickers[0];
|
||||||
|
const frameWin = stickers[1];
|
||||||
|
const frameStart = stickers[2];
|
||||||
|
|
||||||
|
const leftWin = stickers[3];
|
||||||
|
const leftResult = !isLocal ? stickers[4 + SLOT_MAP[leftSlot]] : undefined;
|
||||||
|
const leftSpin = stickers[8];
|
||||||
|
|
||||||
|
const middleWin = stickers[9];
|
||||||
|
const middleResult = !isLocal ? stickers[10 + SLOT_MAP[middleSlot]] : undefined;
|
||||||
|
const middleSpin = stickers[14];
|
||||||
|
|
||||||
|
const rightWin = stickers[15];
|
||||||
|
const rightResult = !isLocal ? stickers[16 + SLOT_MAP[rightSlot]] : undefined;
|
||||||
|
const rightSpin = stickers[20];
|
||||||
|
|
||||||
|
return {
|
||||||
|
background: bg,
|
||||||
|
frameWin,
|
||||||
|
frameStart,
|
||||||
|
leftWin,
|
||||||
|
leftResult,
|
||||||
|
leftSpin,
|
||||||
|
middleWin,
|
||||||
|
middleResult,
|
||||||
|
middleSpin,
|
||||||
|
rightWin,
|
||||||
|
rightResult,
|
||||||
|
rightSpin,
|
||||||
|
};
|
||||||
|
}
|
||||||
@ -99,13 +99,17 @@ function GiftItemStar({
|
|||||||
|
|
||||||
if (isUserLimitReached) {
|
if (isUserLimitReached) {
|
||||||
showNotification({
|
showNotification({
|
||||||
message: lang('NotificationGiftsLimit2', {
|
message: {
|
||||||
count: perUserTotal,
|
key: 'NotificationGiftsLimit2',
|
||||||
}, {
|
variables: {
|
||||||
pluralValue: perUserTotal!,
|
count: perUserTotal,
|
||||||
withMarkdown: true,
|
},
|
||||||
withNodes: true,
|
options: {
|
||||||
}),
|
pluralValue: perUserTotal!,
|
||||||
|
withMarkdown: true,
|
||||||
|
withNodes: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
import type { FC } from '../../lib/teact/teact';
|
|
||||||
import {
|
import {
|
||||||
useEffect,
|
useEffect,
|
||||||
useMemo,
|
useMemo,
|
||||||
@ -15,6 +14,7 @@ import buildClassName from '../../util/buildClassName';
|
|||||||
import captureEscKeyListener from '../../util/captureEscKeyListener';
|
import captureEscKeyListener from '../../util/captureEscKeyListener';
|
||||||
import { REM } from '../common/helpers/mediaDimensions';
|
import { REM } from '../common/helpers/mediaDimensions';
|
||||||
import renderText from '../common/helpers/renderText';
|
import renderText from '../common/helpers/renderText';
|
||||||
|
import { renderTextWithEntities } from '../common/helpers/renderTextWithEntities';
|
||||||
|
|
||||||
import useLang from '../../hooks/useLang';
|
import useLang from '../../hooks/useLang';
|
||||||
import useLastCallback from '../../hooks/useLastCallback';
|
import useLastCallback from '../../hooks/useLastCallback';
|
||||||
@ -37,9 +37,9 @@ const DEFAULT_DURATION = 3000;
|
|||||||
const ANIMATION_DURATION = 150;
|
const ANIMATION_DURATION = 150;
|
||||||
const CUSTOM_EMOJI_SIZE = 1.75 * REM;
|
const CUSTOM_EMOJI_SIZE = 1.75 * REM;
|
||||||
|
|
||||||
const Notification: FC<OwnProps> = ({
|
const Notification = ({
|
||||||
notification,
|
notification,
|
||||||
}) => {
|
}: OwnProps) => {
|
||||||
const actions = getActions();
|
const actions = getActions();
|
||||||
|
|
||||||
const lang = useLang();
|
const lang = useLang();
|
||||||
@ -62,7 +62,10 @@ const Notification: FC<OwnProps> = ({
|
|||||||
containerSelector,
|
containerSelector,
|
||||||
} = notification;
|
} = notification;
|
||||||
|
|
||||||
|
const isMessageLangFnParam = isLangFnParam(message);
|
||||||
|
|
||||||
const [isOpen, setIsOpen] = useState(true);
|
const [isOpen, setIsOpen] = useState(true);
|
||||||
|
const actionActivationRef = useRef<boolean>(false);
|
||||||
const timerRef = useRef<number | undefined>();
|
const timerRef = useRef<number | undefined>();
|
||||||
const { transitionClassNames } = useShowTransitionDeprecated(isOpen);
|
const { transitionClassNames } = useShowTransitionDeprecated(isOpen);
|
||||||
|
|
||||||
@ -80,8 +83,9 @@ const Notification: FC<OwnProps> = ({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleActionClick = useLastCallback(() => {
|
const handleActionClick = useLastCallback((e: React.MouseEvent<HTMLButtonElement>) => {
|
||||||
if (action) {
|
if (action && !actionActivationRef.current) {
|
||||||
|
actionActivationRef.current = true;
|
||||||
if (Array.isArray(action)) {
|
if (Array.isArray(action)) {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
action.forEach((cb) => actions[cb.action](cb.payload));
|
action.forEach((cb) => actions[cb.action](cb.payload));
|
||||||
@ -98,7 +102,8 @@ const Notification: FC<OwnProps> = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const handleClick = useLastCallback(() => {
|
const handleClick = useLastCallback(() => {
|
||||||
if (action) {
|
if (action && !actionActivationRef.current) {
|
||||||
|
actionActivationRef.current = true;
|
||||||
if (Array.isArray(action)) {
|
if (Array.isArray(action)) {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
action.forEach((cb) => actions[cb.action](cb.payload));
|
action.forEach((cb) => actions[cb.action](cb.payload));
|
||||||
@ -146,19 +151,27 @@ const Notification: FC<OwnProps> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return renderText(title, ['simple_markdown', 'emoji', 'br', 'links']);
|
return renderText(title, ['simple_markdown', 'emoji', 'br', 'links']);
|
||||||
|
// @ts-expect-error -- Lang Parameters are too complex
|
||||||
}, [lang, title]);
|
}, [lang, title]);
|
||||||
|
|
||||||
const renderedMessage = useMemo(() => {
|
const renderedMessage = useMemo(() => {
|
||||||
if (isLangFnParam(message)) {
|
if (isMessageLangFnParam) {
|
||||||
return lang.with(message);
|
return lang.with(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof message === 'string') {
|
if (typeof message === 'string') {
|
||||||
|
if (notification.messageEntities) {
|
||||||
|
return renderTextWithEntities({
|
||||||
|
text: message,
|
||||||
|
entities: notification.messageEntities,
|
||||||
|
});
|
||||||
|
}
|
||||||
return renderText(message, ['simple_markdown', 'emoji', 'br', 'links']);
|
return renderText(message, ['simple_markdown', 'emoji', 'br', 'links']);
|
||||||
}
|
}
|
||||||
|
|
||||||
return message;
|
return message;
|
||||||
}, [lang, message]);
|
// @ts-expect-error -- Lang Parameters are too complex
|
||||||
|
}, [isMessageLangFnParam, lang, message, notification.messageEntities]);
|
||||||
|
|
||||||
const renderedActionText = useMemo(() => {
|
const renderedActionText = useMemo(() => {
|
||||||
if (!actionText) return undefined;
|
if (!actionText) return undefined;
|
||||||
|
|||||||
@ -459,3 +459,5 @@ export const DEFAULT_RESALE_GIFTS_FILTER_OPTIONS: ResaleGiftsFilterOptions = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const ACCOUNT_TTL_OPTIONS = [1, 3, 6, 12, 18, 24];
|
export const ACCOUNT_TTL_OPTIONS = [1, 3, 6, 12, 18, 24];
|
||||||
|
|
||||||
|
export const SLOT_MACHINE_EMOJI = '🎰';
|
||||||
|
|||||||
@ -434,6 +434,12 @@ addActionHandler('sendMessage', async (global, actions, payload): Promise<void>
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const diceEmojies = global.appConfig.diceEmojies;
|
||||||
|
let dice = payload.dice;
|
||||||
|
if (payload.text && !payload.entities?.length && diceEmojies.includes(payload.text)) {
|
||||||
|
dice = payload.text;
|
||||||
|
}
|
||||||
|
|
||||||
const params: SendMessageParams = {
|
const params: SendMessageParams = {
|
||||||
...payload,
|
...payload,
|
||||||
chat,
|
chat,
|
||||||
@ -445,6 +451,8 @@ addActionHandler('sendMessage', async (global, actions, payload): Promise<void>
|
|||||||
lastMessageId,
|
lastMessageId,
|
||||||
messagePriceInStars,
|
messagePriceInStars,
|
||||||
isStoryReply,
|
isStoryReply,
|
||||||
|
dice,
|
||||||
|
text: !dice ? payload.text : undefined,
|
||||||
isPending: messagePriceInStars ? true : undefined,
|
isPending: messagePriceInStars ? true : undefined,
|
||||||
...suggestedMessage && { isInvertedMedia: suggestedMessage?.isInvertedMedia },
|
...suggestedMessage && { isInvertedMedia: suggestedMessage?.isInvertedMedia },
|
||||||
};
|
};
|
||||||
@ -610,6 +618,20 @@ addActionHandler('sendInviteMessages', async (global, actions, payload): Promise
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
addActionHandler('sendDiceInCurrentChat', (global, actions, payload): ActionReturnType => {
|
||||||
|
const { emoji, tabId = getCurrentTabId() } = payload;
|
||||||
|
const messageList = selectCurrentMessageList(global, tabId);
|
||||||
|
if (!messageList) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
actions.sendMessage({
|
||||||
|
messageList,
|
||||||
|
dice: emoji,
|
||||||
|
tabId,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
addActionHandler('editMessage', (global, actions, payload): ActionReturnType => {
|
addActionHandler('editMessage', (global, actions, payload): ActionReturnType => {
|
||||||
const {
|
const {
|
||||||
messageList, text, entities, attachments, tabId = getCurrentTabId(),
|
messageList, text, entities, attachments, tabId = getCurrentTabId(),
|
||||||
|
|||||||
@ -199,6 +199,34 @@ addActionHandler('loadFeaturedStickers', async (global): Promise<void> => {
|
|||||||
setGlobal(global);
|
setGlobal(global);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
addActionHandler('loadDiceStickers', async (global): Promise<void> => {
|
||||||
|
const emojis = global.appConfig.diceEmojies;
|
||||||
|
const promises = emojis.map((emoji) => callApi('fetchDiceStickers', { emoji }));
|
||||||
|
const results = await Promise.all(promises);
|
||||||
|
|
||||||
|
global = getGlobal();
|
||||||
|
results.forEach((result, index) => {
|
||||||
|
if (!result) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const emoji = emojis[index];
|
||||||
|
const { set, stickers, packs } = result;
|
||||||
|
global = updateStickerSet(global, set.id, { ...set, stickers, packs });
|
||||||
|
global = {
|
||||||
|
...global,
|
||||||
|
stickers: {
|
||||||
|
...global.stickers,
|
||||||
|
diceSetIdByEmoji: {
|
||||||
|
...global.stickers.diceSetIdByEmoji,
|
||||||
|
[emoji]: set.id,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
setGlobal(global);
|
||||||
|
});
|
||||||
|
|
||||||
addActionHandler('loadPremiumGifts', async (global): Promise<void> => {
|
addActionHandler('loadPremiumGifts', async (global): Promise<void> => {
|
||||||
const stickerSet = await callApi('fetchPremiumGifts');
|
const stickerSet = await callApi('fetchPremiumGifts');
|
||||||
if (!stickerSet) {
|
if (!stickerSet) {
|
||||||
|
|||||||
@ -443,6 +443,7 @@ function reduceGlobal<T extends GlobalState>(global: T) {
|
|||||||
'availableEffectById',
|
'availableEffectById',
|
||||||
]),
|
]),
|
||||||
lastIsChatInfoShown: !getIsMobile() ? global.lastIsChatInfoShown : undefined,
|
lastIsChatInfoShown: !getIsMobile() ? global.lastIsChatInfoShown : undefined,
|
||||||
|
stickers: reduceStickers(global),
|
||||||
customEmojis: reduceCustomEmojis(global),
|
customEmojis: reduceCustomEmojis(global),
|
||||||
users: reduceUsers(global),
|
users: reduceUsers(global),
|
||||||
chats: reduceChats(global),
|
chats: reduceChats(global),
|
||||||
@ -486,6 +487,15 @@ export function serializeGlobal<T extends GlobalState>(global: T) {
|
|||||||
return JSON.stringify(reduceGlobal(global));
|
return JSON.stringify(reduceGlobal(global));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function reduceStickers<T extends GlobalState>(global: T): GlobalState['stickers'] {
|
||||||
|
const { diceSetIdByEmoji, setsById } = global.stickers;
|
||||||
|
return {
|
||||||
|
...INITIAL_GLOBAL_STATE.stickers,
|
||||||
|
diceSetIdByEmoji,
|
||||||
|
setsById: pickTruthy(setsById, Object.values(diceSetIdByEmoji || {})),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function reduceCustomEmojis<T extends GlobalState>(global: T): GlobalState['customEmojis'] {
|
function reduceCustomEmojis<T extends GlobalState>(global: T): GlobalState['customEmojis'] {
|
||||||
const { lastRendered, byId } = global.customEmojis;
|
const { lastRendered, byId } = global.customEmojis;
|
||||||
const folderEmojiIds = Object.values(global.chatFolders.byId)
|
const folderEmojiIds = Object.values(global.chatFolders.byId)
|
||||||
|
|||||||
@ -152,6 +152,7 @@ function getSummaryDescription(
|
|||||||
giveawayResults,
|
giveawayResults,
|
||||||
paidMedia,
|
paidMedia,
|
||||||
todo,
|
todo,
|
||||||
|
dice,
|
||||||
} = mediaContent;
|
} = mediaContent;
|
||||||
const { poll } = statefulContent || {};
|
const { poll } = statefulContent || {};
|
||||||
|
|
||||||
@ -256,6 +257,10 @@ function getSummaryDescription(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (dice) {
|
||||||
|
summary = dice.emoticon;
|
||||||
|
}
|
||||||
|
|
||||||
return summary || lang('MessageUnsupported');
|
return summary || lang('MessageUnsupported');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -63,13 +63,13 @@ export function getMessageTranscription(message: ApiMessage) {
|
|||||||
|
|
||||||
export function hasMessageText(message: MediaContainer) {
|
export function hasMessageText(message: MediaContainer) {
|
||||||
const {
|
const {
|
||||||
action, text, sticker, photo, video, audio, voice, document, pollId, todo,
|
action, text, sticker, photo, video, audio, voice, document, pollId, todo, dice,
|
||||||
webPage, contact, invoice, location, game, storyData, giveaway, giveawayResults, paidMedia,
|
webPage, contact, invoice, location, game, storyData, giveaway, giveawayResults, paidMedia,
|
||||||
} = message.content;
|
} = message.content;
|
||||||
|
|
||||||
return Boolean(text) || !(
|
return Boolean(text) || !(
|
||||||
sticker || photo || video || audio || voice || document || contact || pollId || todo || webPage
|
sticker || photo || video || audio || voice || document || contact || pollId || todo || webPage
|
||||||
|| invoice || location || game || storyData || giveaway || giveawayResults
|
|| invoice || location || game || storyData || giveaway || giveawayResults || dice
|
||||||
|| paidMedia || action?.type === 'phoneCall'
|
|| paidMedia || action?.type === 'phoneCall'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -112,10 +112,10 @@ export function getMessageCustomShape(message: ApiMessage): boolean {
|
|||||||
const {
|
const {
|
||||||
text, sticker, photo, video, audio, voice,
|
text, sticker, photo, video, audio, voice,
|
||||||
document, pollId, webPage, contact, action,
|
document, pollId, webPage, contact, action,
|
||||||
game, invoice, location, storyData,
|
game, invoice, location, storyData, dice,
|
||||||
} = message.content;
|
} = message.content;
|
||||||
|
|
||||||
if (sticker || (video?.isRound)) {
|
if (sticker || (video?.isRound) || dice) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -726,7 +726,7 @@ export function selectAllowedMessageActionsSlow<T extends GlobalState>(
|
|||||||
) && !(
|
) && !(
|
||||||
content.sticker || content.contact || content.pollId || content.action
|
content.sticker || content.contact || content.pollId || content.action
|
||||||
|| (content.video?.isRound) || content.location || content.invoice || content.giveaway || content.giveawayResults
|
|| (content.video?.isRound) || content.location || content.invoice || content.giveaway || content.giveawayResults
|
||||||
|| isDocumentSticker
|
|| isDocumentSticker || content.dice
|
||||||
)
|
)
|
||||||
&& !isForwarded
|
&& !isForwarded
|
||||||
&& !message.viaBotId
|
&& !message.viaBotId
|
||||||
@ -763,7 +763,7 @@ export function selectAllowedMessageActionsSlow<T extends GlobalState>(
|
|||||||
const canReport = !isPrivate && !isOwn;
|
const canReport = !isPrivate && !isOwn;
|
||||||
|
|
||||||
const canDeleteForAll = canDelete && !chat.isForbidden && (
|
const canDeleteForAll = canDelete && !chat.isForbidden && (
|
||||||
(isPrivate && !isChatWithSelf && !isBotChat)
|
(isPrivate && !isChatWithSelf && !isBotChat && !content.dice)
|
||||||
|| (isBasicGroup && (
|
|| (isBasicGroup && (
|
||||||
isOwn || getHasAdminRight(chat, 'deleteMessages') || chat.isCreator
|
isOwn || getHasAdminRight(chat, 'deleteMessages') || chat.isCreator
|
||||||
))
|
))
|
||||||
|
|||||||
@ -250,3 +250,28 @@ export function selectGiftStickerForTon<T extends GlobalState>(global: T, amount
|
|||||||
export function selectCustomEmoji<T extends GlobalState>(global: T, documentId: string) {
|
export function selectCustomEmoji<T extends GlobalState>(global: T, documentId: string) {
|
||||||
return global.customEmojis.byId[documentId];
|
return global.customEmojis.byId[documentId];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function selectIdleDiceSticker<T extends GlobalState>(global: T, diceEmoji: string) {
|
||||||
|
const { diceSetIdByEmoji } = global.stickers;
|
||||||
|
if (!diceSetIdByEmoji) return undefined;
|
||||||
|
const diceSetId = diceSetIdByEmoji[diceEmoji];
|
||||||
|
if (!diceSetId) return undefined;
|
||||||
|
|
||||||
|
const diceSet = global.stickers.setsById[diceSetId];
|
||||||
|
if (!diceSet) return undefined;
|
||||||
|
|
||||||
|
return diceSet.stickers?.find((sticker) => sticker.emoji === '#\u20E3');
|
||||||
|
}
|
||||||
|
|
||||||
|
export function selectDiceSticker<T extends GlobalState>(global: T, diceEmoji: string, value: number) {
|
||||||
|
const { diceSetIdByEmoji } = global.stickers;
|
||||||
|
if (!diceSetIdByEmoji) return undefined;
|
||||||
|
const diceSetId = diceSetIdByEmoji[diceEmoji];
|
||||||
|
if (!diceSetId) return undefined;
|
||||||
|
|
||||||
|
const diceSet = global.stickers.setsById[diceSetId];
|
||||||
|
if (!diceSet) return undefined;
|
||||||
|
|
||||||
|
const numberEmoji = `${value}\u20E3`;
|
||||||
|
return diceSet.stickers?.find((sticker) => sticker.emoji === numberEmoji);
|
||||||
|
}
|
||||||
|
|||||||
@ -495,6 +495,9 @@ export interface ActionPayloads {
|
|||||||
sendMessages: {
|
sendMessages: {
|
||||||
sendParams: SendMessageParams[];
|
sendParams: SendMessageParams[];
|
||||||
};
|
};
|
||||||
|
sendDiceInCurrentChat: {
|
||||||
|
emoji: string;
|
||||||
|
} & WithTabId;
|
||||||
sendInviteMessages: {
|
sendInviteMessages: {
|
||||||
chatId: string;
|
chatId: string;
|
||||||
userIds: string[];
|
userIds: string[];
|
||||||
@ -2006,6 +2009,7 @@ export interface ActionPayloads {
|
|||||||
loadRecentStickers: undefined;
|
loadRecentStickers: undefined;
|
||||||
loadFavoriteStickers: undefined;
|
loadFavoriteStickers: undefined;
|
||||||
loadFeaturedStickers: undefined;
|
loadFeaturedStickers: undefined;
|
||||||
|
loadDiceStickers: undefined;
|
||||||
|
|
||||||
reorderStickerSets: {
|
reorderStickerSets: {
|
||||||
isCustomEmoji?: boolean;
|
isCustomEmoji?: boolean;
|
||||||
|
|||||||
@ -366,6 +366,7 @@ export type GlobalState = {
|
|||||||
stickers: ApiSticker[];
|
stickers: ApiSticker[];
|
||||||
emojis: ApiSticker[];
|
emojis: ApiSticker[];
|
||||||
};
|
};
|
||||||
|
diceSetIdByEmoji?: Record<string, string>;
|
||||||
};
|
};
|
||||||
|
|
||||||
customEmojis: {
|
customEmojis: {
|
||||||
|
|||||||
@ -22,6 +22,8 @@ type Frame =
|
|||||||
| typeof WAITING
|
| typeof WAITING
|
||||||
| ImageBitmap;
|
| ImageBitmap;
|
||||||
|
|
||||||
|
type FrameCallback = (index: number) => void;
|
||||||
|
|
||||||
const HIGH_PRIORITY_QUALITY = (IS_ANDROID || IS_IOS) ? 0.75 : 1;
|
const HIGH_PRIORITY_QUALITY = (IS_ANDROID || IS_IOS) ? 0.75 : 1;
|
||||||
const LOW_PRIORITY_QUALITY = IS_ANDROID ? 0.5 : 0.75;
|
const LOW_PRIORITY_QUALITY = IS_ANDROID ? 0.5 : 0.75;
|
||||||
const LOW_PRIORITY_QUALITY_SIZE_THRESHOLD = 24;
|
const LOW_PRIORITY_QUALITY_SIZE_THRESHOLD = 24;
|
||||||
@ -47,6 +49,7 @@ class RLottie {
|
|||||||
isSharedCanvas?: boolean;
|
isSharedCanvas?: boolean;
|
||||||
coords?: Params['coords'];
|
coords?: Params['coords'];
|
||||||
onLoad?: NoneToVoidFunction;
|
onLoad?: NoneToVoidFunction;
|
||||||
|
onFrame?: FrameCallback;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
private imgSize!: number;
|
private imgSize!: number;
|
||||||
@ -89,13 +92,19 @@ class RLottie {
|
|||||||
|
|
||||||
private lastRenderAt?: number;
|
private lastRenderAt?: number;
|
||||||
|
|
||||||
|
private requestedSeekToEnd = false;
|
||||||
|
|
||||||
static init(...args: ConstructorParameters<typeof RLottie>) {
|
static init(...args: ConstructorParameters<typeof RLottie>) {
|
||||||
const [
|
const [
|
||||||
, canvas,
|
, canvas,
|
||||||
renderId,
|
renderId,
|
||||||
params,
|
params,
|
||||||
viewId = generateUniqueId(), ,
|
viewId = generateUniqueId(),
|
||||||
|
,
|
||||||
onLoad,
|
onLoad,
|
||||||
|
,
|
||||||
|
,
|
||||||
|
onFrame,
|
||||||
] = args;
|
] = args;
|
||||||
let instance = instancesByRenderId.get(renderId);
|
let instance = instancesByRenderId.get(renderId);
|
||||||
|
|
||||||
@ -103,7 +112,7 @@ class RLottie {
|
|||||||
instance = new RLottie(...args);
|
instance = new RLottie(...args);
|
||||||
instancesByRenderId.set(renderId, instance);
|
instancesByRenderId.set(renderId, instance);
|
||||||
} else {
|
} else {
|
||||||
instance.addView(viewId, canvas, onLoad, params?.coords);
|
instance.addView(viewId, canvas, onLoad, onFrame, params?.coords);
|
||||||
}
|
}
|
||||||
|
|
||||||
return instance;
|
return instance;
|
||||||
@ -111,16 +120,17 @@ class RLottie {
|
|||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private tgsUrl: string,
|
private tgsUrl: string,
|
||||||
private container: HTMLDivElement | HTMLCanvasElement,
|
container: HTMLDivElement | HTMLCanvasElement,
|
||||||
private renderId: string,
|
private renderId: string,
|
||||||
private params: Params,
|
private params: Params,
|
||||||
viewId: string = generateUniqueId(),
|
viewId: string = generateUniqueId(),
|
||||||
private customColor?: [number, number, number],
|
private customColor?: [number, number, number],
|
||||||
private onLoad?: NoneToVoidFunction | undefined,
|
onLoad?: NoneToVoidFunction | undefined,
|
||||||
private onEnded?: (isDestroyed?: boolean) => void,
|
private onEnded?: (isDestroyed?: boolean) => void,
|
||||||
private onLoop?: () => void,
|
private onLoop?: () => void,
|
||||||
|
onFrame?: FrameCallback,
|
||||||
) {
|
) {
|
||||||
this.addView(viewId, container, onLoad, params.coords);
|
this.addView(viewId, container, onLoad, onFrame, params.coords);
|
||||||
this.initConfig();
|
this.initConfig();
|
||||||
this.initRenderer();
|
this.initRenderer();
|
||||||
}
|
}
|
||||||
@ -209,6 +219,11 @@ class RLottie {
|
|||||||
this.doPlay();
|
this.doPlay();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
seekToEnd() {
|
||||||
|
this.requestedSeekToEnd = true;
|
||||||
|
this.doPlay();
|
||||||
|
}
|
||||||
|
|
||||||
setSpeed(speed: number) {
|
setSpeed(speed: number) {
|
||||||
this.speed = speed;
|
this.speed = speed;
|
||||||
}
|
}
|
||||||
@ -257,6 +272,7 @@ class RLottie {
|
|||||||
viewId: string,
|
viewId: string,
|
||||||
container: HTMLDivElement | HTMLCanvasElement,
|
container: HTMLDivElement | HTMLCanvasElement,
|
||||||
onLoad?: NoneToVoidFunction,
|
onLoad?: NoneToVoidFunction,
|
||||||
|
onFrame?: FrameCallback,
|
||||||
coords?: Params['coords'],
|
coords?: Params['coords'],
|
||||||
) {
|
) {
|
||||||
const sizeFactor = this.calcSizeFactor();
|
const sizeFactor = this.calcSizeFactor();
|
||||||
@ -292,7 +308,7 @@ class RLottie {
|
|||||||
container.appendChild(canvas);
|
container.appendChild(canvas);
|
||||||
|
|
||||||
this.views.set(viewId, {
|
this.views.set(viewId, {
|
||||||
canvas, ctx, onLoad,
|
canvas, ctx, onLoad, onFrame,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
@ -442,6 +458,12 @@ class RLottie {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.requestedSeekToEnd) {
|
||||||
|
this.approxFrameIndex = this.framesCount - 1;
|
||||||
|
this.stopFrameIndex = undefined;
|
||||||
|
this.requestedSeekToEnd = false;
|
||||||
|
}
|
||||||
|
|
||||||
if (this.isAnimating) {
|
if (this.isAnimating) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -486,12 +508,13 @@ class RLottie {
|
|||||||
if (frameIndex !== this.prevFrameIndex) {
|
if (frameIndex !== this.prevFrameIndex) {
|
||||||
this.views.forEach((containerData) => {
|
this.views.forEach((containerData) => {
|
||||||
const {
|
const {
|
||||||
ctx, isLoaded, isPaused, coords: { x, y } = {}, onLoad,
|
ctx, isLoaded, isPaused, coords: { x, y } = {}, onLoad, onFrame,
|
||||||
} = containerData;
|
} = containerData;
|
||||||
|
|
||||||
if (!isLoaded || !isPaused) {
|
if (!isLoaded || !isPaused) {
|
||||||
ctx.clearRect(x || 0, y || 0, this.imgSize, this.imgSize);
|
ctx.clearRect(x || 0, y || 0, this.imgSize, this.imgSize);
|
||||||
ctx.drawImage(frame, x || 0, y || 0);
|
ctx.drawImage(frame, x || 0, y || 0);
|
||||||
|
onFrame?.(frameIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isLoaded) {
|
if (!isLoaded) {
|
||||||
|
|||||||
@ -160,4 +160,6 @@ export const DEFAULT_APP_CONFIG: ApiAppConfig = {
|
|||||||
typingDraftTtl: 10,
|
typingDraftTtl: 10,
|
||||||
arePasskeysAvailable: true,
|
arePasskeysAvailable: true,
|
||||||
passkeysMaxCount: 5,
|
passkeysMaxCount: 5,
|
||||||
|
diceEmojies: [],
|
||||||
|
diceEmojiesSuccess: {},
|
||||||
};
|
};
|
||||||
|
|||||||
@ -723,6 +723,7 @@ export type SendMessageParams = {
|
|||||||
gif?: ApiVideo;
|
gif?: ApiVideo;
|
||||||
poll?: ApiNewPoll;
|
poll?: ApiNewPoll;
|
||||||
todo?: ApiNewMediaTodo;
|
todo?: ApiNewMediaTodo;
|
||||||
|
dice?: string;
|
||||||
contact?: ApiContact;
|
contact?: ApiContact;
|
||||||
isSilent?: boolean;
|
isSilent?: boolean;
|
||||||
scheduledAt?: number;
|
scheduledAt?: number;
|
||||||
|
|||||||
4
src/types/language.d.ts
vendored
4
src/types/language.d.ts
vendored
@ -1885,6 +1885,7 @@ export interface LangPair {
|
|||||||
'SettingsBirthday': undefined;
|
'SettingsBirthday': undefined;
|
||||||
'BotReadTextFromClipboardTitle': undefined;
|
'BotReadTextFromClipboardTitle': undefined;
|
||||||
'BotReadTextFromClipboardConfirm': undefined;
|
'BotReadTextFromClipboardConfirm': undefined;
|
||||||
|
'DiceToastSend': undefined;
|
||||||
'ChatTypePrivate': undefined;
|
'ChatTypePrivate': undefined;
|
||||||
'ChatTypeGroup': undefined;
|
'ChatTypeGroup': undefined;
|
||||||
'ChatTypeChannel': undefined;
|
'ChatTypeChannel': undefined;
|
||||||
@ -3329,6 +3330,9 @@ export interface LangPairWithVariables<V = LangVariable> {
|
|||||||
'BotReadTextFromClipboardDescription': {
|
'BotReadTextFromClipboardDescription': {
|
||||||
'bot': V;
|
'bot': V;
|
||||||
};
|
};
|
||||||
|
'DiceToast': {
|
||||||
|
'emoji': V;
|
||||||
|
};
|
||||||
'GroupStatusWithOnline': {
|
'GroupStatusWithOnline': {
|
||||||
'status': V;
|
'status': V;
|
||||||
'onlineCount': V;
|
'onlineCount': V;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user