Implement Games (#1797)
This commit is contained in:
parent
cd4e9077d5
commit
d7226fded9
@ -29,6 +29,7 @@ import {
|
|||||||
ApiSponsoredMessage,
|
ApiSponsoredMessage,
|
||||||
ApiUser,
|
ApiUser,
|
||||||
ApiLocation,
|
ApiLocation,
|
||||||
|
ApiGame,
|
||||||
} from '../../types';
|
} from '../../types';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@ -327,6 +328,9 @@ export function buildMessageMediaContent(media: GramJs.TypeMessageMedia): ApiMes
|
|||||||
const location = buildLocationFromMedia(media);
|
const location = buildLocationFromMedia(media);
|
||||||
if (location) return { location };
|
if (location) return { location };
|
||||||
|
|
||||||
|
const game = buildGameFromMedia(media);
|
||||||
|
if (game) return { game };
|
||||||
|
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -532,6 +536,16 @@ export function buildApiDocument(document: GramJs.TypeDocument): ApiDocument | u
|
|||||||
}
|
}
|
||||||
} else if (SUPPORTED_VIDEO_CONTENT_TYPES.has(mimeType)) {
|
} else if (SUPPORTED_VIDEO_CONTENT_TYPES.has(mimeType)) {
|
||||||
mediaType = 'video';
|
mediaType = 'video';
|
||||||
|
const videoAttribute = attributes
|
||||||
|
.find((a: any): a is GramJs.DocumentAttributeVideo => a instanceof GramJs.DocumentAttributeVideo);
|
||||||
|
|
||||||
|
if (videoAttribute) {
|
||||||
|
const { w: width, h: height } = videoAttribute;
|
||||||
|
mediaSize = {
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -638,6 +652,33 @@ function buildGeoPoint(geo: GramJs.TypeGeoPoint): ApiLocation['geo'] | undefined
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function buildGameFromMedia(media: GramJs.TypeMessageMedia): ApiGame | undefined {
|
||||||
|
if (!(media instanceof GramJs.MessageMediaGame)) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return buildGame(media);
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildGame(media: GramJs.MessageMediaGame): ApiGame | undefined {
|
||||||
|
const {
|
||||||
|
id, accessHash, shortName, title, description, photo: apiPhoto, document: apiDocument,
|
||||||
|
} = media.game;
|
||||||
|
|
||||||
|
const photo = apiPhoto instanceof GramJs.Photo ? buildApiPhoto(apiPhoto) : undefined;
|
||||||
|
const document = apiDocument instanceof GramJs.Document ? buildApiDocument(apiDocument) : undefined;
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: id.toString(),
|
||||||
|
accessHash: accessHash.toString(),
|
||||||
|
shortName,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
photo,
|
||||||
|
document,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export function buildPoll(poll: GramJs.Poll, pollResults: GramJs.PollResults): ApiPoll {
|
export function buildPoll(poll: GramJs.Poll, pollResults: GramJs.PollResults): ApiPoll {
|
||||||
const { id, answers: rawAnswers } = poll;
|
const { id, answers: rawAnswers } = poll;
|
||||||
const answers = rawAnswers.map((answer) => ({
|
const answers = rawAnswers.map((answer) => ({
|
||||||
@ -751,6 +792,7 @@ function buildAction(
|
|||||||
const translationValues = [];
|
const translationValues = [];
|
||||||
let type: ApiAction['type'] = 'other';
|
let type: ApiAction['type'] = 'other';
|
||||||
let photo: ApiPhoto | undefined;
|
let photo: ApiPhoto | undefined;
|
||||||
|
let score: number | undefined;
|
||||||
|
|
||||||
const targetUserIds = 'users' in action
|
const targetUserIds = 'users' in action
|
||||||
? action.users && action.users.map((id) => buildApiPeerId(id, 'user'))
|
? action.users && action.users.map((id) => buildApiPeerId(id, 'user'))
|
||||||
@ -868,6 +910,10 @@ function buildAction(
|
|||||||
} else if (action instanceof GramJs.MessageActionChatJoinedByRequest) {
|
} else if (action instanceof GramJs.MessageActionChatJoinedByRequest) {
|
||||||
text = 'ChatService.UserJoinedGroupByRequest';
|
text = 'ChatService.UserJoinedGroupByRequest';
|
||||||
translationValues.push('%action_origin%');
|
translationValues.push('%action_origin%');
|
||||||
|
} else if (action instanceof GramJs.MessageActionGameScore) {
|
||||||
|
text = senderId === currentUserId ? 'ActionYouScoredInGame' : 'ActionUserScoredInGame';
|
||||||
|
translationValues.push('%score%');
|
||||||
|
score = action.score;
|
||||||
} else {
|
} else {
|
||||||
text = 'ChatList.UnsupportedMessage';
|
text = 'ChatList.UnsupportedMessage';
|
||||||
}
|
}
|
||||||
@ -887,21 +933,22 @@ function buildAction(
|
|||||||
currency,
|
currency,
|
||||||
translationValues,
|
translationValues,
|
||||||
call,
|
call,
|
||||||
|
score,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildReplyButtons(message: UniversalMessage): ApiReplyKeyboard | undefined {
|
function buildReplyButtons(message: UniversalMessage): ApiReplyKeyboard | undefined {
|
||||||
const { id: messageId, replyMarkup, media } = message;
|
const { replyMarkup, media } = message;
|
||||||
|
|
||||||
|
// TODO Move to the proper button inside preview
|
||||||
if (!replyMarkup) {
|
if (!replyMarkup) {
|
||||||
if (media instanceof GramJs.MessageMediaWebPage && media.webpage instanceof GramJs.WebPage) {
|
if (media instanceof GramJs.MessageMediaWebPage && media.webpage instanceof GramJs.WebPage) {
|
||||||
if (media.webpage.type === 'telegram_message') {
|
if (media.webpage.type === 'telegram_message') {
|
||||||
return {
|
return {
|
||||||
inlineButtons: [[{
|
inlineButtons: [[{
|
||||||
type: 'url' as const,
|
type: 'url',
|
||||||
text: 'Show Message',
|
text: 'Show Message',
|
||||||
messageId,
|
url: media.webpage.url,
|
||||||
value: media.webpage.url,
|
|
||||||
}]],
|
}]],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -916,40 +963,103 @@ function buildReplyButtons(message: UniversalMessage): ApiReplyKeyboard | undefi
|
|||||||
}
|
}
|
||||||
|
|
||||||
const markup = replyMarkup.rows.map(({ buttons }) => {
|
const markup = replyMarkup.rows.map(({ buttons }) => {
|
||||||
return buttons.map((button) => {
|
return buttons.map((button): ApiKeyboardButton => {
|
||||||
let { text } = button;
|
const { text } = button;
|
||||||
|
|
||||||
let type;
|
|
||||||
let value;
|
|
||||||
if (button instanceof GramJs.KeyboardButton) {
|
if (button instanceof GramJs.KeyboardButton) {
|
||||||
type = 'command';
|
return {
|
||||||
value = text;
|
type: 'command',
|
||||||
} else if (button instanceof GramJs.KeyboardButtonUrl) {
|
text,
|
||||||
type = 'url';
|
};
|
||||||
value = button.url;
|
}
|
||||||
} else if (button instanceof GramJs.KeyboardButtonCallback) {
|
|
||||||
type = 'callback';
|
if (button instanceof GramJs.KeyboardButtonUrl) {
|
||||||
value = serializeBytes(button.data);
|
if (button.url.includes('?startgroup=')) {
|
||||||
} else if (button instanceof GramJs.KeyboardButtonRequestPoll) {
|
return {
|
||||||
type = 'requestPoll';
|
type: 'unsupported',
|
||||||
} else if (button instanceof GramJs.KeyboardButtonRequestPhone) {
|
text,
|
||||||
type = 'requestSelfContact';
|
};
|
||||||
} else if (button instanceof GramJs.KeyboardButtonBuy) {
|
|
||||||
if (media instanceof GramJs.MessageMediaInvoice && media.receiptMsgId) {
|
|
||||||
text = 'PaymentReceipt';
|
|
||||||
value = media.receiptMsgId;
|
|
||||||
}
|
}
|
||||||
type = 'buy';
|
|
||||||
} else {
|
return {
|
||||||
type = 'NOT_SUPPORTED';
|
type: 'url',
|
||||||
|
text,
|
||||||
|
url: button.url,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (button instanceof GramJs.KeyboardButtonCallback) {
|
||||||
|
if (button.requiresPassword) {
|
||||||
|
return {
|
||||||
|
type: 'unsupported',
|
||||||
|
text,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
type: 'callback',
|
||||||
|
text,
|
||||||
|
data: serializeBytes(button.data),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (button instanceof GramJs.KeyboardButtonRequestPoll) {
|
||||||
|
return {
|
||||||
|
type: 'requestPoll',
|
||||||
|
text,
|
||||||
|
isQuiz: button.quiz,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (button instanceof GramJs.KeyboardButtonRequestPhone) {
|
||||||
|
return {
|
||||||
|
type: 'requestPhone',
|
||||||
|
text,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (button instanceof GramJs.KeyboardButtonBuy) {
|
||||||
|
if (media instanceof GramJs.MessageMediaInvoice && media.receiptMsgId) {
|
||||||
|
return {
|
||||||
|
type: 'receipt',
|
||||||
|
text: 'PaymentReceipt',
|
||||||
|
receiptMessageId: media.receiptMsgId,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
type: 'buy',
|
||||||
|
text,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (button instanceof GramJs.KeyboardButtonGame) {
|
||||||
|
return {
|
||||||
|
type: 'game',
|
||||||
|
text,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (button instanceof GramJs.KeyboardButtonSwitchInline) {
|
||||||
|
return {
|
||||||
|
type: 'switchBotInline',
|
||||||
|
text,
|
||||||
|
query: button.query,
|
||||||
|
isSamePeer: button.samePeer,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (button instanceof GramJs.KeyboardButtonUserProfile) {
|
||||||
|
return {
|
||||||
|
type: 'userProfile',
|
||||||
|
text,
|
||||||
|
userId: button.userId.toString(),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
type,
|
type: 'unsupported',
|
||||||
text,
|
text,
|
||||||
messageId,
|
};
|
||||||
value,
|
|
||||||
} as ApiKeyboardButton;
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -319,6 +319,9 @@ export function isMessageWithMedia(message: GramJs.Message | GramJs.UpdateServic
|
|||||||
&& media.webpage.document.mimeType.startsWith('video')
|
&& media.webpage.document.mimeType.startsWith('video')
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
) || (
|
||||||
|
media instanceof GramJs.MessageMediaGame
|
||||||
|
&& (media.game.document instanceof GramJs.Document || media.game.photo instanceof GramJs.Photo)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -441,6 +444,8 @@ export function buildSendMessageAction(action: ApiSendMessageAction) {
|
|||||||
return new GramJs.SendMessageRecordAudioAction();
|
return new GramJs.SendMessageRecordAudioAction();
|
||||||
case 'chooseSticker':
|
case 'chooseSticker':
|
||||||
return new GramJs.SendMessageChooseStickerAction();
|
return new GramJs.SendMessageChooseStickerAction();
|
||||||
|
case 'playingGame':
|
||||||
|
return new GramJs.SendMessageGamePlayAction();
|
||||||
}
|
}
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -31,6 +31,15 @@ export function addMessageToLocalDb(message: GramJs.Message | GramJs.MessageServ
|
|||||||
localDb.documents[String(message.media.webpage.document.id)] = message.media.webpage.document;
|
localDb.documents[String(message.media.webpage.document.id)] = message.media.webpage.document;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (message instanceof GramJs.Message
|
||||||
|
&& message.media instanceof GramJs.MessageMediaGame
|
||||||
|
) {
|
||||||
|
if (message.media.game.document instanceof GramJs.Document) {
|
||||||
|
localDb.documents[String(message.media.game.document.id)] = message.media.game.document;
|
||||||
|
}
|
||||||
|
addPhotoToLocalDb(message.media.game.photo);
|
||||||
|
}
|
||||||
|
|
||||||
if (message instanceof GramJs.MessageService && 'photo' in message.action) {
|
if (message instanceof GramJs.MessageService && 'photo' in message.action) {
|
||||||
addPhotoToLocalDb(message.action.photo);
|
addPhotoToLocalDb(message.action.photo);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -16,14 +16,15 @@ export function init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function answerCallbackButton({
|
export async function answerCallbackButton({
|
||||||
chatId, accessHash, messageId, data,
|
chatId, accessHash, messageId, data, isGame,
|
||||||
}: {
|
}: {
|
||||||
chatId: string; accessHash?: string; messageId: number; data: string;
|
chatId: string; accessHash?: string; messageId: number; data?: string; isGame?: boolean;
|
||||||
}) {
|
}) {
|
||||||
const result = await invokeRequest(new GramJs.messages.GetBotCallbackAnswer({
|
const result = await invokeRequest(new GramJs.messages.GetBotCallbackAnswer({
|
||||||
peer: buildInputPeer(chatId, accessHash),
|
peer: buildInputPeer(chatId, accessHash),
|
||||||
msgId: messageId,
|
msgId: messageId,
|
||||||
data: deserializeBytes(data),
|
data: data ? deserializeBytes(data) : undefined,
|
||||||
|
game: isGame || undefined,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
return result ? omitVirtualClassFields(result) : undefined;
|
return result ? omitVirtualClassFields(result) : undefined;
|
||||||
|
|||||||
@ -1102,6 +1102,7 @@ export async function forwardMessages({
|
|||||||
isSilent,
|
isSilent,
|
||||||
scheduledAt,
|
scheduledAt,
|
||||||
sendAs,
|
sendAs,
|
||||||
|
withMyScore,
|
||||||
}: {
|
}: {
|
||||||
fromChat: ApiChat;
|
fromChat: ApiChat;
|
||||||
toChat: ApiChat;
|
toChat: ApiChat;
|
||||||
@ -1110,6 +1111,7 @@ export async function forwardMessages({
|
|||||||
isSilent?: boolean;
|
isSilent?: boolean;
|
||||||
scheduledAt?: number;
|
scheduledAt?: number;
|
||||||
sendAs?: ApiUser | ApiChat;
|
sendAs?: ApiUser | ApiChat;
|
||||||
|
withMyScore?: boolean;
|
||||||
}) {
|
}) {
|
||||||
const messageIds = messages.map(({ id }) => id);
|
const messageIds = messages.map(({ id }) => id);
|
||||||
const randomIds = messages.map(generateRandomBigInt);
|
const randomIds = messages.map(generateRandomBigInt);
|
||||||
@ -1131,7 +1133,8 @@ export async function forwardMessages({
|
|||||||
toPeer: buildInputPeer(toChat.id, toChat.accessHash),
|
toPeer: buildInputPeer(toChat.id, toChat.accessHash),
|
||||||
randomId: randomIds,
|
randomId: randomIds,
|
||||||
id: messageIds,
|
id: messageIds,
|
||||||
...(isSilent && { sil2ent: isSilent }),
|
withMyScore: withMyScore || undefined,
|
||||||
|
silent: isSilent || undefined,
|
||||||
...(scheduledAt && { scheduleDate: scheduledAt }),
|
...(scheduledAt && { scheduleDate: scheduledAt }),
|
||||||
...(sendAs && { sendAs: buildInputPeer(sendAs.id, sendAs.accessHash) }),
|
...(sendAs && { sendAs: buildInputPeer(sendAs.id, sendAs.accessHash) }),
|
||||||
}), true);
|
}), true);
|
||||||
|
|||||||
@ -174,6 +174,16 @@ interface ApiGeoLive {
|
|||||||
|
|
||||||
export type ApiLocation = ApiGeo | ApiVenue | ApiGeoLive;
|
export type ApiLocation = ApiGeo | ApiVenue | ApiGeoLive;
|
||||||
|
|
||||||
|
export type ApiGame = {
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
photo?: ApiPhoto;
|
||||||
|
shortName: string;
|
||||||
|
id: string;
|
||||||
|
accessHash: string;
|
||||||
|
document?: ApiDocument;
|
||||||
|
};
|
||||||
|
|
||||||
export type ApiNewPoll = {
|
export type ApiNewPoll = {
|
||||||
summary: ApiPoll['summary'];
|
summary: ApiPoll['summary'];
|
||||||
quiz?: {
|
quiz?: {
|
||||||
@ -193,6 +203,7 @@ export interface ApiAction {
|
|||||||
currency?: string;
|
currency?: string;
|
||||||
translationValues: string[];
|
translationValues: string[];
|
||||||
call?: Partial<ApiGroupCall>;
|
call?: Partial<ApiGroupCall>;
|
||||||
|
score?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ApiWebPage {
|
export interface ApiWebPage {
|
||||||
@ -272,6 +283,7 @@ export interface ApiMessage {
|
|||||||
voice?: ApiVoice;
|
voice?: ApiVoice;
|
||||||
invoice?: ApiInvoice;
|
invoice?: ApiInvoice;
|
||||||
location?: ApiLocation;
|
location?: ApiLocation;
|
||||||
|
game?: ApiGame;
|
||||||
};
|
};
|
||||||
date: number;
|
date: number;
|
||||||
isOutgoing: boolean;
|
isOutgoing: boolean;
|
||||||
@ -363,13 +375,60 @@ export type ApiSponsoredMessage = {
|
|||||||
expiresAt: number;
|
expiresAt: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface ApiKeyboardButton {
|
// KeyboardButtons
|
||||||
type: 'command' | 'url' | 'callback' | 'requestPoll' | 'requestSelfContact' | 'buy' | 'NOT_SUPPORTED';
|
|
||||||
|
interface ApiKeyboardButtonSimple {
|
||||||
|
type: 'unsupported' | 'buy' | 'command' | 'requestPhone' | 'game';
|
||||||
text: string;
|
text: string;
|
||||||
messageId: number;
|
|
||||||
value?: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface ApiKeyboardButtonReceipt {
|
||||||
|
type: 'receipt';
|
||||||
|
text: string;
|
||||||
|
receiptMessageId: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ApiKeyboardButtonUrl {
|
||||||
|
type: 'url';
|
||||||
|
text: string;
|
||||||
|
url: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ApiKeyboardButtonCallback {
|
||||||
|
type: 'callback';
|
||||||
|
text: string;
|
||||||
|
data: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ApiKeyboardButtonRequestPoll {
|
||||||
|
type: 'requestPoll';
|
||||||
|
text: string;
|
||||||
|
isQuiz?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ApiKeyboardButtonSwitchInline {
|
||||||
|
type: 'switchBotInline';
|
||||||
|
text: string;
|
||||||
|
query: string;
|
||||||
|
isSamePeer?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ApiKeyboardButtonUserProfile {
|
||||||
|
type: 'userProfile';
|
||||||
|
text: string;
|
||||||
|
userId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ApiKeyboardButton = (
|
||||||
|
ApiKeyboardButtonSimple
|
||||||
|
| ApiKeyboardButtonReceipt
|
||||||
|
| ApiKeyboardButtonUrl
|
||||||
|
| ApiKeyboardButtonCallback
|
||||||
|
| ApiKeyboardButtonRequestPoll
|
||||||
|
| ApiKeyboardButtonSwitchInline
|
||||||
|
| ApiKeyboardButtonUserProfile
|
||||||
|
);
|
||||||
|
|
||||||
export type ApiKeyboardButtons = ApiKeyboardButton[][];
|
export type ApiKeyboardButtons = ApiKeyboardButton[][];
|
||||||
export type ApiReplyKeyboard = {
|
export type ApiReplyKeyboard = {
|
||||||
keyboardPlaceholder?: string;
|
keyboardPlaceholder?: string;
|
||||||
@ -385,7 +444,7 @@ export type ApiReportReason = 'spam' | 'violence' | 'pornography' | 'childAbuse'
|
|||||||
| 'copyright' | 'geoIrrelevant' | 'fake' | 'illegalDrugs' | 'personalDetails' | 'other';
|
| 'copyright' | 'geoIrrelevant' | 'fake' | 'illegalDrugs' | 'personalDetails' | 'other';
|
||||||
|
|
||||||
export type ApiSendMessageAction = {
|
export type ApiSendMessageAction = {
|
||||||
type: 'cancel' | 'typing' | 'recordAudio' | 'chooseSticker';
|
type: 'cancel' | 'typing' | 'recordAudio' | 'chooseSticker' | 'playingGame';
|
||||||
};
|
};
|
||||||
|
|
||||||
export const MAIN_THREAD_ID = -1;
|
export const MAIN_THREAD_ID = -1;
|
||||||
|
|||||||
@ -42,15 +42,17 @@ export function renderActionMessageText(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const {
|
const {
|
||||||
text, translationValues, amount, currency, call,
|
text, translationValues, amount, currency, call, score,
|
||||||
} = message.content.action;
|
} = message.content.action;
|
||||||
const content: TextPart[] = [];
|
const content: TextPart[] = [];
|
||||||
const noLinks = options.asPlainText || options.asTextWithSpoilers;
|
const noLinks = options.asPlainText || options.asTextWithSpoilers;
|
||||||
const translationKey = text === 'Chat.Service.Group.UpdatedPinnedMessage1' && !targetMessage
|
const translationKey = text === 'Chat.Service.Group.UpdatedPinnedMessage1' && !targetMessage
|
||||||
? 'Message.PinnedGenericMessage'
|
? 'Message.PinnedGenericMessage'
|
||||||
: text;
|
: text;
|
||||||
|
|
||||||
let unprocessed = lang(translationKey, translationValues?.length ? translationValues : undefined);
|
let unprocessed = lang(translationKey, translationValues?.length ? translationValues : undefined);
|
||||||
|
if (translationKey.includes('ScoredInGame')) { // Translation hack for games
|
||||||
|
unprocessed = unprocessed.replace('un1', '%action_origin%').replace('un2', '%message%');
|
||||||
|
}
|
||||||
let processed: TextPart[];
|
let processed: TextPart[];
|
||||||
|
|
||||||
if (unprocessed.includes('%payment_amount%')) {
|
if (unprocessed.includes('%payment_amount%')) {
|
||||||
@ -76,6 +78,16 @@ export function renderActionMessageText(
|
|||||||
unprocessed = processed.pop() as string;
|
unprocessed = processed.pop() as string;
|
||||||
content.push(...processed);
|
content.push(...processed);
|
||||||
|
|
||||||
|
if (unprocessed.includes('%score%')) {
|
||||||
|
processed = processPlaceholder(
|
||||||
|
unprocessed,
|
||||||
|
'%score%',
|
||||||
|
score!.toString(),
|
||||||
|
);
|
||||||
|
unprocessed = processed.pop() as string;
|
||||||
|
content.push(...processed);
|
||||||
|
}
|
||||||
|
|
||||||
processed = processPlaceholder(
|
processed = processPlaceholder(
|
||||||
unprocessed,
|
unprocessed,
|
||||||
'%target_user%',
|
'%target_user%',
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import React, {
|
|||||||
import { getActions, getGlobal, withGlobal } from '../../global';
|
import { getActions, getGlobal, withGlobal } from '../../global';
|
||||||
|
|
||||||
import { ApiChat, MAIN_THREAD_ID } from '../../api/types';
|
import { ApiChat, MAIN_THREAD_ID } from '../../api/types';
|
||||||
|
import { GlobalState } from '../../global/types';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
filterChatsByName,
|
filterChatsByName,
|
||||||
@ -29,6 +30,7 @@ type StateProps = {
|
|||||||
pinnedIds?: string[];
|
pinnedIds?: string[];
|
||||||
contactIds?: string[];
|
contactIds?: string[];
|
||||||
currentUserId?: string;
|
currentUserId?: string;
|
||||||
|
switchBotInline?: GlobalState['switchBotInline'];
|
||||||
};
|
};
|
||||||
|
|
||||||
const ForwardPicker: FC<OwnProps & StateProps> = ({
|
const ForwardPicker: FC<OwnProps & StateProps> = ({
|
||||||
@ -39,10 +41,13 @@ const ForwardPicker: FC<OwnProps & StateProps> = ({
|
|||||||
contactIds,
|
contactIds,
|
||||||
currentUserId,
|
currentUserId,
|
||||||
isOpen,
|
isOpen,
|
||||||
|
switchBotInline,
|
||||||
}) => {
|
}) => {
|
||||||
const {
|
const {
|
||||||
setForwardChatId,
|
setForwardChatId,
|
||||||
exitForwardMode,
|
exitForwardMode,
|
||||||
|
openChatWithText,
|
||||||
|
resetSwitchBotInline,
|
||||||
} = getActions();
|
} = getActions();
|
||||||
|
|
||||||
const lang = useLang();
|
const lang = useLang();
|
||||||
@ -86,8 +91,14 @@ const ForwardPicker: FC<OwnProps & StateProps> = ({
|
|||||||
}, [activeListIds, archivedListIds, chatsById, contactIds, currentUserId, filter, isOpen, lang, pinnedIds]);
|
}, [activeListIds, archivedListIds, chatsById, contactIds, currentUserId, filter, isOpen, lang, pinnedIds]);
|
||||||
|
|
||||||
const handleSelectUser = useCallback((userId: string) => {
|
const handleSelectUser = useCallback((userId: string) => {
|
||||||
setForwardChatId({ id: userId });
|
if (switchBotInline) {
|
||||||
}, [setForwardChatId]);
|
const text = `@${switchBotInline.botUsername} ${switchBotInline.query}`;
|
||||||
|
openChatWithText({ chatId: userId, text });
|
||||||
|
resetSwitchBotInline();
|
||||||
|
} else {
|
||||||
|
setForwardChatId({ id: userId });
|
||||||
|
}
|
||||||
|
}, [openChatWithText, resetSwitchBotInline, setForwardChatId, switchBotInline]);
|
||||||
|
|
||||||
const renderingChatAndContactIds = useCurrentOrPrev(chatAndContactIds, true)!;
|
const renderingChatAndContactIds = useCurrentOrPrev(chatAndContactIds, true)!;
|
||||||
|
|
||||||
@ -120,6 +131,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
orderedPinnedIds,
|
orderedPinnedIds,
|
||||||
},
|
},
|
||||||
currentUserId,
|
currentUserId,
|
||||||
|
switchBotInline,
|
||||||
} = global;
|
} = global;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -129,6 +141,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
pinnedIds: orderedPinnedIds.active,
|
pinnedIds: orderedPinnedIds.active,
|
||||||
contactIds: global.contactList?.userIds,
|
contactIds: global.contactList?.userIds,
|
||||||
currentUserId,
|
currentUserId,
|
||||||
|
switchBotInline,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
)(ForwardPicker));
|
)(ForwardPicker));
|
||||||
|
|||||||
48
src/components/main/GameModal.scss
Normal file
48
src/components/main/GameModal.scss
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
.GameModal {
|
||||||
|
.modal-dialog {
|
||||||
|
max-width: 80%;
|
||||||
|
height: 100%;
|
||||||
|
justify-content: center;
|
||||||
|
background-color: transparent;
|
||||||
|
border: none;
|
||||||
|
box-shadow: none;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-header {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-content {
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.game-frame {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
border: 0;
|
||||||
|
border-radius: var(--border-radius-default);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 600px) {
|
||||||
|
.modal-dialog {
|
||||||
|
background-color: var(--color-background);
|
||||||
|
max-width: 100% !important;
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-header {
|
||||||
|
display: flex;
|
||||||
|
padding: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-content {
|
||||||
|
max-height: none;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.game-frame {
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
95
src/components/main/GameModal.tsx
Normal file
95
src/components/main/GameModal.tsx
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
import React, {
|
||||||
|
FC, memo, useCallback, useEffect,
|
||||||
|
} from '../../lib/teact/teact';
|
||||||
|
import { getActions } from '../../lib/teact/teactn';
|
||||||
|
import { GlobalState } from '../../global/types';
|
||||||
|
|
||||||
|
import windowSize from '../../util/windowSize';
|
||||||
|
|
||||||
|
import useLang from '../../hooks/useLang';
|
||||||
|
import useSendMessageAction from '../../hooks/useSendMessageAction';
|
||||||
|
import useInterval from '../../hooks/useInterval';
|
||||||
|
|
||||||
|
import Modal from '../ui/Modal';
|
||||||
|
|
||||||
|
import './GameModal.scss';
|
||||||
|
|
||||||
|
type GameEvents = { eventType: 'share_score' | 'share_game' };
|
||||||
|
|
||||||
|
const PLAY_GAME_ACTION_INTERVAL = 5000;
|
||||||
|
|
||||||
|
type OwnProps = {
|
||||||
|
openedGame?: GlobalState['openedGame'];
|
||||||
|
gameTitle?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const GameModal: FC<OwnProps> = ({ openedGame, gameTitle }) => {
|
||||||
|
const { closeGame, showNotification, openForwardMenu } = getActions();
|
||||||
|
const lang = useLang();
|
||||||
|
const { url, chatId, messageId } = openedGame || {};
|
||||||
|
const isOpen = Boolean(url);
|
||||||
|
|
||||||
|
const sendMessageAction = useSendMessageAction(chatId);
|
||||||
|
useInterval(() => {
|
||||||
|
sendMessageAction({ type: 'playingGame' });
|
||||||
|
}, isOpen ? PLAY_GAME_ACTION_INTERVAL : undefined);
|
||||||
|
|
||||||
|
const handleMessage = useCallback((event: MessageEvent<string>) => {
|
||||||
|
try {
|
||||||
|
const data = JSON.parse(event.data) as GameEvents;
|
||||||
|
if (data.eventType === 'share_score') {
|
||||||
|
openForwardMenu({ fromChatId: chatId, messageIds: [messageId], withMyScore: true });
|
||||||
|
closeGame();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.eventType === 'share_game') {
|
||||||
|
showNotification({ message: 'Unsupported game action' });
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// Ignore messages from other origins
|
||||||
|
}
|
||||||
|
}, [chatId, closeGame, messageId, openForwardMenu, showNotification]);
|
||||||
|
|
||||||
|
const handleLoad = useCallback((event: React.SyntheticEvent<HTMLIFrameElement>) => {
|
||||||
|
event.currentTarget.focus();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
window.addEventListener('message', handleMessage);
|
||||||
|
return () => window.removeEventListener('message', handleMessage);
|
||||||
|
}, [handleMessage]);
|
||||||
|
|
||||||
|
// Prevent refresh when rotating device
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isOpen) return undefined;
|
||||||
|
|
||||||
|
windowSize.disableRefresh();
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
windowSize.enableRefresh();
|
||||||
|
};
|
||||||
|
}, [isOpen]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
className="GameModal"
|
||||||
|
isOpen={isOpen}
|
||||||
|
onClose={closeGame}
|
||||||
|
title={gameTitle}
|
||||||
|
hasCloseButton
|
||||||
|
>
|
||||||
|
{isOpen && (
|
||||||
|
<iframe
|
||||||
|
className="game-frame"
|
||||||
|
onLoad={handleLoad}
|
||||||
|
src={url}
|
||||||
|
title={lang('AttachGame')}
|
||||||
|
sandbox="allow-scripts allow-same-origin allow-orientation-lock"
|
||||||
|
allow="fullscreen"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(GameModal);
|
||||||
@ -5,6 +5,7 @@ import { getActions, withGlobal } from '../../global';
|
|||||||
|
|
||||||
import { LangCode } from '../../types';
|
import { LangCode } from '../../types';
|
||||||
import { ApiMessage, ApiUpdateAuthorizationStateType, ApiUpdateConnectionStateType } from '../../api/types';
|
import { ApiMessage, ApiUpdateAuthorizationStateType, ApiUpdateConnectionStateType } from '../../api/types';
|
||||||
|
import { GlobalState } from '../../global/types';
|
||||||
|
|
||||||
import '../../global/actions/all';
|
import '../../global/actions/all';
|
||||||
import {
|
import {
|
||||||
@ -40,6 +41,7 @@ import RightColumn from '../right/RightColumn';
|
|||||||
import MediaViewer from '../mediaViewer/MediaViewer.async';
|
import MediaViewer from '../mediaViewer/MediaViewer.async';
|
||||||
import AudioPlayer from '../middle/AudioPlayer';
|
import AudioPlayer from '../middle/AudioPlayer';
|
||||||
import DownloadManager from './DownloadManager';
|
import DownloadManager from './DownloadManager';
|
||||||
|
import GameModal from './GameModal';
|
||||||
import Notifications from './Notifications.async';
|
import Notifications from './Notifications.async';
|
||||||
import Dialogs from './Dialogs.async';
|
import Dialogs from './Dialogs.async';
|
||||||
import ForwardPicker from './ForwardPicker.async';
|
import ForwardPicker from './ForwardPicker.async';
|
||||||
@ -76,6 +78,8 @@ type StateProps = {
|
|||||||
addedSetIds?: string[];
|
addedSetIds?: string[];
|
||||||
newContactUserId?: string;
|
newContactUserId?: string;
|
||||||
newContactByPhoneNumber?: boolean;
|
newContactByPhoneNumber?: boolean;
|
||||||
|
openedGame?: GlobalState['openedGame'];
|
||||||
|
gameTitle?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const NOTIFICATION_INTERVAL = 1000;
|
const NOTIFICATION_INTERVAL = 1000;
|
||||||
@ -109,6 +113,8 @@ const Main: FC<StateProps> = ({
|
|||||||
addedSetIds,
|
addedSetIds,
|
||||||
newContactUserId,
|
newContactUserId,
|
||||||
newContactByPhoneNumber,
|
newContactByPhoneNumber,
|
||||||
|
openedGame,
|
||||||
|
gameTitle,
|
||||||
}) => {
|
}) => {
|
||||||
const {
|
const {
|
||||||
sync,
|
sync,
|
||||||
@ -340,6 +346,7 @@ const Main: FC<StateProps> = ({
|
|||||||
userId={newContactUserId}
|
userId={newContactUserId}
|
||||||
isByPhoneNumber={newContactByPhoneNumber}
|
isByPhoneNumber={newContactByPhoneNumber}
|
||||||
/>
|
/>
|
||||||
|
<GameModal openedGame={openedGame} gameTitle={gameTitle} />
|
||||||
<DownloadManager />
|
<DownloadManager />
|
||||||
<CallFallbackConfirm isOpen={isCallFallbackConfirmOpen} />
|
<CallFallbackConfirm isOpen={isCallFallbackConfirmOpen} />
|
||||||
<UnreadCount isForAppBadge />
|
<UnreadCount isForAppBadge />
|
||||||
@ -375,6 +382,9 @@ export default memo(withGlobal(
|
|||||||
const audioMessage = audioChatId && audioMessageId
|
const audioMessage = audioChatId && audioMessageId
|
||||||
? selectChatMessage(global, audioChatId, audioMessageId)
|
? selectChatMessage(global, audioChatId, audioMessageId)
|
||||||
: undefined;
|
: undefined;
|
||||||
|
const openedGame = global.openedGame;
|
||||||
|
const gameMessage = openedGame && selectChatMessage(global, openedGame.chatId, openedGame.messageId);
|
||||||
|
const gameTitle = gameMessage?.content.game?.title;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
connectionState: global.connectionState,
|
connectionState: global.connectionState,
|
||||||
@ -400,6 +410,8 @@ export default memo(withGlobal(
|
|||||||
addedSetIds: global.stickers.added.setIds,
|
addedSetIds: global.stickers.added.setIds,
|
||||||
newContactUserId: global.newContact?.userId,
|
newContactUserId: global.newContact?.userId,
|
||||||
newContactByPhoneNumber: global.newContact?.isByPhoneNumber,
|
newContactByPhoneNumber: global.newContact?.isByPhoneNumber,
|
||||||
|
openedGame,
|
||||||
|
gameTitle,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
)(Main));
|
)(Main));
|
||||||
|
|||||||
@ -28,7 +28,7 @@ type StateProps = {
|
|||||||
const BotKeyboardMenu: FC<OwnProps & StateProps> = ({
|
const BotKeyboardMenu: FC<OwnProps & StateProps> = ({
|
||||||
isOpen, message, onClose,
|
isOpen, message, onClose,
|
||||||
}) => {
|
}) => {
|
||||||
const { clickInlineButton } = getActions();
|
const { clickBotInlineButton } = getActions();
|
||||||
|
|
||||||
const [handleMouseEnter, handleMouseLeave] = useMouseInside(isOpen, onClose);
|
const [handleMouseEnter, handleMouseLeave] = useMouseInside(isOpen, onClose);
|
||||||
const { isKeyboardSingleUse } = message || {};
|
const { isKeyboardSingleUse } = message || {};
|
||||||
@ -66,9 +66,9 @@ const BotKeyboardMenu: FC<OwnProps & StateProps> = ({
|
|||||||
{row.map((button) => (
|
{row.map((button) => (
|
||||||
<Button
|
<Button
|
||||||
ripple
|
ripple
|
||||||
disabled={button.type === 'NOT_SUPPORTED'}
|
disabled={button.type === 'unsupported'}
|
||||||
// eslint-disable-next-line react/jsx-no-bind
|
// eslint-disable-next-line react/jsx-no-bind
|
||||||
onClick={() => clickInlineButton({ button })}
|
onClick={() => clickBotInlineButton({ messageId: message.id, button })}
|
||||||
>
|
>
|
||||||
{button.text}
|
{button.text}
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@ -42,6 +42,7 @@ import {
|
|||||||
selectCanScheduleUntilOnline,
|
selectCanScheduleUntilOnline,
|
||||||
selectEditingScheduledDraft,
|
selectEditingScheduledDraft,
|
||||||
selectEditingDraft,
|
selectEditingDraft,
|
||||||
|
selectRequestedText,
|
||||||
} from '../../../global/selectors';
|
} from '../../../global/selectors';
|
||||||
import {
|
import {
|
||||||
getAllowedAttachmentOptions,
|
getAllowedAttachmentOptions,
|
||||||
@ -127,7 +128,7 @@ type StateProps =
|
|||||||
isRightColumnShown?: boolean;
|
isRightColumnShown?: boolean;
|
||||||
isSelectModeActive?: boolean;
|
isSelectModeActive?: boolean;
|
||||||
isForwarding?: boolean;
|
isForwarding?: boolean;
|
||||||
isPollModalOpen?: boolean;
|
pollModal: GlobalState['pollModal'];
|
||||||
botKeyboardMessageId?: number;
|
botKeyboardMessageId?: number;
|
||||||
botKeyboardPlaceholder?: string;
|
botKeyboardPlaceholder?: string;
|
||||||
withScheduledButton?: boolean;
|
withScheduledButton?: boolean;
|
||||||
@ -151,6 +152,7 @@ type StateProps =
|
|||||||
sendAsChat?: ApiChat;
|
sendAsChat?: ApiChat;
|
||||||
sendAsId?: string;
|
sendAsId?: string;
|
||||||
editingDraft?: ApiFormattedText;
|
editingDraft?: ApiFormattedText;
|
||||||
|
requestedText?: string;
|
||||||
}
|
}
|
||||||
& Pick<GlobalState, 'connectionState'>;
|
& Pick<GlobalState, 'connectionState'>;
|
||||||
|
|
||||||
@ -196,7 +198,7 @@ const Composer: FC<OwnProps & StateProps> = ({
|
|||||||
isRightColumnShown,
|
isRightColumnShown,
|
||||||
isSelectModeActive,
|
isSelectModeActive,
|
||||||
isForwarding,
|
isForwarding,
|
||||||
isPollModalOpen,
|
pollModal,
|
||||||
botKeyboardMessageId,
|
botKeyboardMessageId,
|
||||||
botKeyboardPlaceholder,
|
botKeyboardPlaceholder,
|
||||||
withScheduledButton,
|
withScheduledButton,
|
||||||
@ -218,6 +220,7 @@ const Composer: FC<OwnProps & StateProps> = ({
|
|||||||
sendAsChat,
|
sendAsChat,
|
||||||
sendAsId,
|
sendAsId,
|
||||||
editingDraft,
|
editingDraft,
|
||||||
|
requestedText,
|
||||||
}) => {
|
}) => {
|
||||||
const {
|
const {
|
||||||
sendMessage,
|
sendMessage,
|
||||||
@ -234,6 +237,7 @@ const Composer: FC<OwnProps & StateProps> = ({
|
|||||||
sendInlineBotResult,
|
sendInlineBotResult,
|
||||||
loadSendAs,
|
loadSendAs,
|
||||||
loadFullChat,
|
loadFullChat,
|
||||||
|
resetOpenChatWithText,
|
||||||
} = getActions();
|
} = getActions();
|
||||||
const lang = useLang();
|
const lang = useLang();
|
||||||
|
|
||||||
@ -657,6 +661,17 @@ const Composer: FC<OwnProps & StateProps> = ({
|
|||||||
}
|
}
|
||||||
}, [contentToBeScheduled, handleMessageSchedule, requestCalendar]);
|
}, [contentToBeScheduled, handleMessageSchedule, requestCalendar]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (requestedText) {
|
||||||
|
setHtml(requestedText);
|
||||||
|
resetOpenChatWithText();
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
const messageInput = document.getElementById(EDITABLE_INPUT_ID)!;
|
||||||
|
focusEditableElement(messageInput, true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [requestedText, resetOpenChatWithText]);
|
||||||
|
|
||||||
const handleStickerSelect = useCallback((
|
const handleStickerSelect = useCallback((
|
||||||
sticker: ApiSticker, isSilent?: boolean, isScheduleRequested?: boolean, shouldPreserveInput = false,
|
sticker: ApiSticker, isSilent?: boolean, isScheduleRequested?: boolean, shouldPreserveInput = false,
|
||||||
) => {
|
) => {
|
||||||
@ -945,7 +960,8 @@ const Composer: FC<OwnProps & StateProps> = ({
|
|||||||
onClear={handleClearAttachment}
|
onClear={handleClearAttachment}
|
||||||
/>
|
/>
|
||||||
<PollModal
|
<PollModal
|
||||||
isOpen={Boolean(isPollModalOpen)}
|
isOpen={pollModal.isOpen}
|
||||||
|
isQuiz={pollModal.isQuiz}
|
||||||
shouldBeAnonimous={isChannel}
|
shouldBeAnonimous={isChannel}
|
||||||
onClear={closePollModal}
|
onClear={closePollModal}
|
||||||
onSend={handlePollSend}
|
onSend={handlePollSend}
|
||||||
@ -1213,6 +1229,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
: (chat?.adminRights?.anonymous ? chat?.id : undefined);
|
: (chat?.adminRights?.anonymous ? chat?.id : undefined);
|
||||||
const sendAsUser = sendAsId ? selectUser(global, sendAsId) : undefined;
|
const sendAsUser = sendAsId ? selectUser(global, sendAsId) : undefined;
|
||||||
const sendAsChat = !sendAsUser && sendAsId ? selectChat(global, sendAsId) : undefined;
|
const sendAsChat = !sendAsUser && sendAsId ? selectChat(global, sendAsId) : undefined;
|
||||||
|
const requestedText = selectRequestedText(global, chatId);
|
||||||
|
|
||||||
const editingDraft = messageListType === 'scheduled'
|
const editingDraft = messageListType === 'scheduled'
|
||||||
? selectEditingScheduledDraft(global, chatId)
|
? selectEditingScheduledDraft(global, chatId)
|
||||||
@ -1238,7 +1255,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
botKeyboardMessageId,
|
botKeyboardMessageId,
|
||||||
botKeyboardPlaceholder: keyboardMessage?.keyboardPlaceholder,
|
botKeyboardPlaceholder: keyboardMessage?.keyboardPlaceholder,
|
||||||
isForwarding: chatId === global.forwardMessages.toChatId,
|
isForwarding: chatId === global.forwardMessages.toChatId,
|
||||||
isPollModalOpen: global.isPollModalOpen,
|
pollModal: global.pollModal,
|
||||||
stickersForEmoji: global.stickers.forEmoji.stickers,
|
stickersForEmoji: global.stickers.forEmoji.stickers,
|
||||||
groupChatMembers: chat?.fullInfo?.members,
|
groupChatMembers: chat?.fullInfo?.members,
|
||||||
topInlineBotIds: global.topInlineBots?.userIds,
|
topInlineBotIds: global.topInlineBots?.userIds,
|
||||||
@ -1257,6 +1274,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
sendAsChat,
|
sendAsChat,
|
||||||
sendAsId,
|
sendAsId,
|
||||||
editingDraft,
|
editingDraft,
|
||||||
|
requestedText,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
)(Composer));
|
)(Composer));
|
||||||
|
|||||||
@ -425,7 +425,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
return {
|
return {
|
||||||
messageSendKeyCombo,
|
messageSendKeyCombo,
|
||||||
replyingToId: chatId && threadId ? selectReplyingToId(global, chatId, threadId) : undefined,
|
replyingToId: chatId && threadId ? selectReplyingToId(global, chatId, threadId) : undefined,
|
||||||
noTabCapture: global.isPollModalOpen || global.payment.isPaymentModalOpen,
|
noTabCapture: global.pollModal.isOpen || global.payment.isPaymentModalOpen,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
)(MessageInput));
|
)(MessageInput));
|
||||||
|
|||||||
@ -20,6 +20,7 @@ import './PollModal.scss';
|
|||||||
export type OwnProps = {
|
export type OwnProps = {
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
shouldBeAnonimous?: boolean;
|
shouldBeAnonimous?: boolean;
|
||||||
|
isQuiz?: boolean;
|
||||||
onSend: (pollSummary: ApiNewPoll) => void;
|
onSend: (pollSummary: ApiNewPoll) => void;
|
||||||
onClear: () => void;
|
onClear: () => void;
|
||||||
};
|
};
|
||||||
@ -31,7 +32,7 @@ const MAX_QUESTION_LENGTH = 255;
|
|||||||
const MAX_SOLUTION_LENGTH = 200;
|
const MAX_SOLUTION_LENGTH = 200;
|
||||||
|
|
||||||
const PollModal: FC<OwnProps> = ({
|
const PollModal: FC<OwnProps> = ({
|
||||||
isOpen, shouldBeAnonimous, onSend, onClear,
|
isOpen, isQuiz, shouldBeAnonimous, onSend, onClear,
|
||||||
}) => {
|
}) => {
|
||||||
// eslint-disable-next-line no-null/no-null
|
// eslint-disable-next-line no-null/no-null
|
||||||
const questionInputRef = useRef<HTMLInputElement>(null);
|
const questionInputRef = useRef<HTMLInputElement>(null);
|
||||||
@ -44,8 +45,8 @@ const PollModal: FC<OwnProps> = ({
|
|||||||
const [options, setOptions] = useState<string[]>(['']);
|
const [options, setOptions] = useState<string[]>(['']);
|
||||||
const [isAnonymous, setIsAnonymous] = useState(true);
|
const [isAnonymous, setIsAnonymous] = useState(true);
|
||||||
const [isMultipleAnswers, setIsMultipleAnswers] = useState(false);
|
const [isMultipleAnswers, setIsMultipleAnswers] = useState(false);
|
||||||
const [isQuizMode, setIsQuizMode] = useState(false);
|
const [isQuizMode, setIsQuizMode] = useState(isQuiz || false);
|
||||||
const [solution, setSolution] = useState<string>();
|
const [solution, setSolution] = useState<string>('');
|
||||||
const [correctOption, setCorrectOption] = useState<string>();
|
const [correctOption, setCorrectOption] = useState<string>();
|
||||||
const [hasErrors, setHasErrors] = useState<boolean>(false);
|
const [hasErrors, setHasErrors] = useState<boolean>(false);
|
||||||
|
|
||||||
@ -64,12 +65,12 @@ const PollModal: FC<OwnProps> = ({
|
|||||||
setOptions(['']);
|
setOptions(['']);
|
||||||
setIsAnonymous(true);
|
setIsAnonymous(true);
|
||||||
setIsMultipleAnswers(false);
|
setIsMultipleAnswers(false);
|
||||||
setIsQuizMode(false);
|
setIsQuizMode(isQuiz || false);
|
||||||
setSolution('');
|
setSolution('');
|
||||||
setCorrectOption('');
|
setCorrectOption('');
|
||||||
setHasErrors(false);
|
setHasErrors(false);
|
||||||
}
|
}
|
||||||
}, [isOpen]);
|
}, [isQuiz, isOpen]);
|
||||||
|
|
||||||
useEffect(() => focusInput(questionInputRef), [focusInput, isOpen]);
|
useEffect(() => focusInput(questionInputRef), [focusInput, isOpen]);
|
||||||
|
|
||||||
@ -338,7 +339,7 @@ const PollModal: FC<OwnProps> = ({
|
|||||||
<Checkbox
|
<Checkbox
|
||||||
label={lang('PollQuiz')}
|
label={lang('PollQuiz')}
|
||||||
checked={isQuizMode}
|
checked={isQuizMode}
|
||||||
disabled={isMultipleAnswers}
|
disabled={isMultipleAnswers || isQuiz !== undefined}
|
||||||
onChange={handleQuizModeChange}
|
onChange={handleQuizModeChange}
|
||||||
/>
|
/>
|
||||||
{isQuizMode && (
|
{isQuizMode && (
|
||||||
|
|||||||
34
src/components/middle/message/Game.scss
Normal file
34
src/components/middle/message/Game.scss
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
.Game {
|
||||||
|
.title {
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.description {
|
||||||
|
line-height: 1.2;
|
||||||
|
margin-bottom: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview {
|
||||||
|
position: relative;
|
||||||
|
max-width: 100%;
|
||||||
|
margin-bottom: 0.25rem;
|
||||||
|
margin-top: 0.25rem;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-content {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
object-fit: cover;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.skeleton {
|
||||||
|
aspect-ratio: 16/9;
|
||||||
|
}
|
||||||
|
}
|
||||||
84
src/components/middle/message/Game.tsx
Normal file
84
src/components/middle/message/Game.tsx
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
import React, {
|
||||||
|
FC, memo,
|
||||||
|
} from '../../../lib/teact/teact';
|
||||||
|
|
||||||
|
import { ApiMessage } from '../../../api/types';
|
||||||
|
|
||||||
|
import { getActions } from '../../../global';
|
||||||
|
import { getGamePreviewPhotoHash, getGamePreviewVideoHash, getMessageText } from '../../../global/helpers';
|
||||||
|
|
||||||
|
import useMedia from '../../../hooks/useMedia';
|
||||||
|
|
||||||
|
import Skeleton from '../../ui/Skeleton';
|
||||||
|
|
||||||
|
import './Game.scss';
|
||||||
|
|
||||||
|
const DEFAULT_PREVIEW_DIMENSIONS = {
|
||||||
|
width: 480,
|
||||||
|
height: 270,
|
||||||
|
};
|
||||||
|
|
||||||
|
type OwnProps = {
|
||||||
|
message: ApiMessage;
|
||||||
|
canAutoLoadMedia?: boolean;
|
||||||
|
lastSyncTime?: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
const Game: FC<OwnProps> = ({
|
||||||
|
message,
|
||||||
|
canAutoLoadMedia,
|
||||||
|
lastSyncTime,
|
||||||
|
}) => {
|
||||||
|
const { clickBotInlineButton } = getActions();
|
||||||
|
const game = message.content.game!;
|
||||||
|
const {
|
||||||
|
title, description,
|
||||||
|
} = game;
|
||||||
|
|
||||||
|
const photoHash = Boolean(lastSyncTime) && getGamePreviewPhotoHash(game);
|
||||||
|
const videoHash = Boolean(lastSyncTime) && getGamePreviewVideoHash(game);
|
||||||
|
const photoBlobUrl = useMedia(photoHash, !canAutoLoadMedia);
|
||||||
|
const videoBlobUrl = useMedia(videoHash, !canAutoLoadMedia);
|
||||||
|
|
||||||
|
const handleGameClick = () => {
|
||||||
|
clickBotInlineButton({
|
||||||
|
messageId: message.id,
|
||||||
|
button: message.inlineButtons![0][0],
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="Game">
|
||||||
|
<div
|
||||||
|
className="preview"
|
||||||
|
style={`width: ${DEFAULT_PREVIEW_DIMENSIONS.width}px; height: ${DEFAULT_PREVIEW_DIMENSIONS.height}px`}
|
||||||
|
onClick={handleGameClick}
|
||||||
|
>
|
||||||
|
{!photoBlobUrl && !videoBlobUrl && (
|
||||||
|
<Skeleton className="skeleton preview-content" />
|
||||||
|
)}
|
||||||
|
{photoBlobUrl && (
|
||||||
|
<img
|
||||||
|
className="preview-content"
|
||||||
|
src={photoBlobUrl}
|
||||||
|
alt={title}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{videoBlobUrl && (
|
||||||
|
<video
|
||||||
|
className="preview-content"
|
||||||
|
playsInline
|
||||||
|
muted
|
||||||
|
autoPlay
|
||||||
|
loop
|
||||||
|
src={videoBlobUrl}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="title">{title}</div>
|
||||||
|
{!getMessageText(message) && <div className="description">{description}</div>}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(Game);
|
||||||
@ -12,12 +12,11 @@ import './InlineButtons.scss';
|
|||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
message: ApiMessage;
|
message: ApiMessage;
|
||||||
onClick: ({ button }: { button: ApiKeyboardButton }) => void;
|
onClick: ({ messageId, button }: { messageId: number; button: ApiKeyboardButton }) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
const InlineButtons: FC<OwnProps> = ({ message, onClick }) => {
|
const InlineButtons: FC<OwnProps> = ({ message, onClick }) => {
|
||||||
const lang = useLang();
|
const lang = useLang();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="InlineButtons">
|
<div className="InlineButtons">
|
||||||
{message.inlineButtons!.map((row) => (
|
{message.inlineButtons!.map((row) => (
|
||||||
@ -26,13 +25,14 @@ const InlineButtons: FC<OwnProps> = ({ message, onClick }) => {
|
|||||||
<Button
|
<Button
|
||||||
size="tiny"
|
size="tiny"
|
||||||
ripple
|
ripple
|
||||||
disabled={button.type === 'NOT_SUPPORTED'}
|
disabled={button.type === 'unsupported'}
|
||||||
// eslint-disable-next-line react/jsx-no-bind
|
// eslint-disable-next-line react/jsx-no-bind
|
||||||
onClick={() => onClick({ button })}
|
onClick={() => onClick({ messageId: message.id, button })}
|
||||||
>
|
>
|
||||||
{renderText(lang(button.text))}
|
{renderText(lang(button.text))}
|
||||||
{button.type === 'buy' && <i className="icon-card" />}
|
{['buy', 'receipt'].includes(button.type) && <i className="icon-card" />}
|
||||||
{button.type === 'url' && !button.value!.match(RE_TME_LINK) && <i className="icon-arrow-right" />}
|
{button.type === 'url' && !RE_TME_LINK.test(button.url) && <i className="icon-arrow-right" />}
|
||||||
|
{button.type === 'switchBotInline' && <i className="icon-share-filled" />}
|
||||||
</Button>
|
</Button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -106,6 +106,7 @@ import Poll from './Poll';
|
|||||||
import WebPage from './WebPage';
|
import WebPage from './WebPage';
|
||||||
import Invoice from './Invoice';
|
import Invoice from './Invoice';
|
||||||
import Location from './Location';
|
import Location from './Location';
|
||||||
|
import Game from './Game';
|
||||||
import Album from './Album';
|
import Album from './Album';
|
||||||
import RoundVideo from './RoundVideo';
|
import RoundVideo from './RoundVideo';
|
||||||
import InlineButtons from './InlineButtons';
|
import InlineButtons from './InlineButtons';
|
||||||
@ -281,7 +282,7 @@ const Message: FC<OwnProps & StateProps> = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const {
|
const {
|
||||||
toggleMessageSelection,
|
toggleMessageSelection,
|
||||||
clickInlineButton,
|
clickBotInlineButton,
|
||||||
disableContextMenuHint,
|
disableContextMenuHint,
|
||||||
} = getActions();
|
} = getActions();
|
||||||
|
|
||||||
@ -461,7 +462,7 @@ const Message: FC<OwnProps & StateProps> = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
text, photo, video, audio, voice, document, sticker, contact, poll, webPage, invoice, location,
|
text, photo, video, audio, voice, document, sticker, contact, poll, webPage, invoice, location, game,
|
||||||
} = getMessageContent(message);
|
} = getMessageContent(message);
|
||||||
|
|
||||||
const contentClassName = buildContentClassName(message, {
|
const contentClassName = buildContentClassName(message, {
|
||||||
@ -753,6 +754,13 @@ const Message: FC<OwnProps & StateProps> = ({
|
|||||||
{poll && (
|
{poll && (
|
||||||
<Poll message={message} poll={poll} onSendVote={handleVoteSend} />
|
<Poll message={message} poll={poll} onSendVote={handleVoteSend} />
|
||||||
)}
|
)}
|
||||||
|
{game && (
|
||||||
|
<Game
|
||||||
|
message={message}
|
||||||
|
canAutoLoadMedia={canAutoLoadMedia}
|
||||||
|
lastSyncTime={lastSyncTime}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
{!hasAnimatedEmoji && textParts && (
|
{!hasAnimatedEmoji && textParts && (
|
||||||
<p className={textContentClass} dir="auto">
|
<p className={textContentClass} dir="auto">
|
||||||
{textParts}
|
{textParts}
|
||||||
@ -935,7 +943,7 @@ const Message: FC<OwnProps & StateProps> = ({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{message.inlineButtons && (
|
{message.inlineButtons && (
|
||||||
<InlineButtons message={message} onClick={clickInlineButton} />
|
<InlineButtons message={message} onClick={clickBotInlineButton} />
|
||||||
)}
|
)}
|
||||||
{reactionsPosition === 'outside' && (
|
{reactionsPosition === 'outside' && (
|
||||||
<Reactions
|
<Reactions
|
||||||
|
|||||||
@ -1,6 +1,9 @@
|
|||||||
.Skeleton {
|
.Skeleton {
|
||||||
position: relative;
|
position: relative;
|
||||||
background-color: var(--color-skeleton-background);
|
background-color: var(--color-skeleton-background);
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
&.round {
|
&.round {
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -13,7 +13,11 @@ type OwnProps = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const Skeleton: FC<OwnProps> = ({
|
const Skeleton: FC<OwnProps> = ({
|
||||||
variant = 'rectangular', animation = 'wave', width, height, className,
|
variant = 'rectangular',
|
||||||
|
animation = 'wave',
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
className,
|
||||||
}) => {
|
}) => {
|
||||||
const classNames = buildClassName('Skeleton', variant, animation, className);
|
const classNames = buildClassName('Skeleton', variant, animation, className);
|
||||||
const style = (width ? `width: ${width}px;` : '') + (height ? `height: ${height}px;` : '');
|
const style = (width ? `width: ${width}px;` : '') + (height ? `height: ${height}px;` : '');
|
||||||
|
|||||||
@ -18,37 +18,41 @@ import { buildCollectionByKey } from '../../../util/iteratees';
|
|||||||
import { debounce } from '../../../util/schedulers';
|
import { debounce } from '../../../util/schedulers';
|
||||||
import { replaceInlineBotSettings, replaceInlineBotsIsLoading } from '../../reducers/bots';
|
import { replaceInlineBotSettings, replaceInlineBotsIsLoading } from '../../reducers/bots';
|
||||||
import { getServerTime } from '../../../util/serverTime';
|
import { getServerTime } from '../../../util/serverTime';
|
||||||
|
import PopupManager from '../../../util/PopupManager';
|
||||||
|
|
||||||
|
const GAMEE_URL = 'https://prizes.gamee.com/';
|
||||||
const TOP_PEERS_REQUEST_COOLDOWN = 60; // 1 min
|
const TOP_PEERS_REQUEST_COOLDOWN = 60; // 1 min
|
||||||
const runDebouncedForSearch = debounce((cb) => cb(), 500, false);
|
const runDebouncedForSearch = debounce((cb) => cb(), 500, false);
|
||||||
|
|
||||||
addActionHandler('clickInlineButton', (global, actions, payload) => {
|
addActionHandler('clickBotInlineButton', (global, actions, payload) => {
|
||||||
const { button } = payload;
|
const { messageId, button } = payload;
|
||||||
|
|
||||||
switch (button.type) {
|
switch (button.type) {
|
||||||
case 'command':
|
case 'command':
|
||||||
actions.sendBotCommand({ command: button.value });
|
actions.sendBotCommand({ command: button.text });
|
||||||
break;
|
break;
|
||||||
case 'url':
|
case 'url': {
|
||||||
if (button.value.match(RE_TME_LINK) || button.value.match(RE_TG_LINK)) {
|
const { url } = button;
|
||||||
actions.openTelegramLink({ url: button.value });
|
if (url.match(RE_TME_LINK) || url.match(RE_TG_LINK)) {
|
||||||
|
actions.openTelegramLink({ url });
|
||||||
} else {
|
} else {
|
||||||
actions.toggleSafeLinkModal({ url: button.value });
|
actions.toggleSafeLinkModal({ url });
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
case 'callback': {
|
case 'callback': {
|
||||||
const chat = selectCurrentChat(global);
|
const chat = selectCurrentChat(global);
|
||||||
if (!chat) {
|
if (!chat) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
void answerCallbackButton(chat, button.messageId, button.value);
|
void answerCallbackButton(chat, messageId, button.data);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'requestPoll':
|
case 'requestPoll':
|
||||||
actions.openPollModal();
|
actions.openPollModal({ isQuiz: button.isQuiz });
|
||||||
break;
|
break;
|
||||||
case 'requestSelfContact': {
|
case 'requestPhone': {
|
||||||
const user = global.currentUserId ? selectUser(global, global.currentUserId) : undefined;
|
const user = global.currentUserId ? selectUser(global, global.currentUserId) : undefined;
|
||||||
if (!user) {
|
if (!user) {
|
||||||
return;
|
return;
|
||||||
@ -63,20 +67,44 @@ addActionHandler('clickInlineButton', (global, actions, payload) => {
|
|||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case 'receipt': {
|
||||||
|
const chat = selectCurrentChat(global);
|
||||||
|
if (!chat) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const { receiptMessageId } = button;
|
||||||
|
actions.getReceipt({ receiptMessageId, chatId: chat.id, messageId });
|
||||||
|
break;
|
||||||
|
}
|
||||||
case 'buy': {
|
case 'buy': {
|
||||||
const chat = selectCurrentChat(global);
|
const chat = selectCurrentChat(global);
|
||||||
const { messageId, value } = button;
|
|
||||||
if (!chat) {
|
if (!chat) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (value) {
|
actions.getPaymentForm({ chat, messageId });
|
||||||
actions.getReceipt({ receiptMessageId: value, chatId: chat.id, messageId });
|
actions.setInvoiceMessageInfo(selectChatMessage(global, chat.id, messageId));
|
||||||
} else {
|
actions.openPaymentModal({ chatId: chat.id, messageId });
|
||||||
actions.getPaymentForm({ chat, messageId });
|
break;
|
||||||
actions.setInvoiceMessageInfo(selectChatMessage(global, chat.id, messageId));
|
}
|
||||||
actions.openPaymentModal({ chatId: chat.id, messageId });
|
case 'game': {
|
||||||
|
const chat = selectCurrentChat(global);
|
||||||
|
if (!chat) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void answerCallbackButton(chat, messageId, undefined, true);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'switchBotInline': {
|
||||||
|
const { query, isSamePeer } = button;
|
||||||
|
actions.switchBotInline({ query, isSamePeer, messageId });
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'userProfile': {
|
||||||
|
const { userId } = button;
|
||||||
|
actions.openChatWithInfo({ id: userId });
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -193,6 +221,45 @@ addActionHandler('queryInlineBot', async (global, actions, payload) => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
addActionHandler('switchBotInline', (global, actions, payload) => {
|
||||||
|
const { query, isSamePeer, messageId } = payload;
|
||||||
|
const chat = selectCurrentChat(global);
|
||||||
|
if (!chat) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
const message = selectChatMessage(global, chat.id, messageId);
|
||||||
|
if (!message) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const botSender = selectChatBot(global, message.senderId!);
|
||||||
|
if (!botSender) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const text = `@${botSender.username} ${query}`;
|
||||||
|
|
||||||
|
if (isSamePeer) {
|
||||||
|
actions.openChatWithText({ chatId: chat.id, text });
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...global,
|
||||||
|
switchBotInline: {
|
||||||
|
query,
|
||||||
|
botUsername: botSender.username,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
addActionHandler('resetSwitchBotInline', (global) => {
|
||||||
|
return {
|
||||||
|
...global,
|
||||||
|
switchBotInline: undefined,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
addActionHandler('sendInlineBotResult', (global, actions, payload) => {
|
addActionHandler('sendInlineBotResult', (global, actions, payload) => {
|
||||||
const {
|
const {
|
||||||
id, queryId, isSilent, scheduledAt,
|
id, queryId, isSilent, scheduledAt,
|
||||||
@ -327,19 +394,34 @@ async function sendBotCommand(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function answerCallbackButton(chat: ApiChat, messageId: number, data: string) {
|
let gameePopups: PopupManager | undefined;
|
||||||
|
|
||||||
|
async function answerCallbackButton(chat: ApiChat, messageId: number, data?: string, isGame = false) {
|
||||||
|
const {
|
||||||
|
showDialog, showNotification, toggleSafeLinkModal, openGame,
|
||||||
|
} = getActions();
|
||||||
|
|
||||||
|
if (isGame) {
|
||||||
|
if (!gameePopups) {
|
||||||
|
gameePopups = new PopupManager('popup,width=800,height=600', () => {
|
||||||
|
showNotification({ message: 'Allow browser to open popup window' });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
gameePopups.preOpenIfNeeded();
|
||||||
|
}
|
||||||
|
|
||||||
const result = await callApi('answerCallbackButton', {
|
const result = await callApi('answerCallbackButton', {
|
||||||
chatId: chat.id,
|
chatId: chat.id,
|
||||||
accessHash: chat.accessHash,
|
accessHash: chat.accessHash,
|
||||||
messageId,
|
messageId,
|
||||||
data,
|
data,
|
||||||
|
isGame,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!result) {
|
if (!result) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { showDialog, showNotification, toggleSafeLinkModal } = getActions();
|
|
||||||
const { message, alert: isError, url } = result;
|
const { message, alert: isError, url } = result;
|
||||||
|
|
||||||
if (isError) {
|
if (isError) {
|
||||||
@ -347,6 +429,16 @@ async function answerCallbackButton(chat: ApiChat, messageId: number, data: stri
|
|||||||
} else if (message) {
|
} else if (message) {
|
||||||
showNotification({ message });
|
showNotification({ message });
|
||||||
} else if (url) {
|
} else if (url) {
|
||||||
toggleSafeLinkModal({ url });
|
if (isGame) {
|
||||||
|
// Workaround for Gamee embedding bug
|
||||||
|
if (url.includes(GAMEE_URL)) {
|
||||||
|
gameePopups!.open(url);
|
||||||
|
} else {
|
||||||
|
gameePopups!.cancelPreOpen();
|
||||||
|
openGame({ url, chatId: chat.id, messageId });
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
toggleSafeLinkModal({ url });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -905,6 +905,30 @@ addActionHandler('setActiveChatFolder', (global, actions, payload) => {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
addActionHandler('openChatWithText', (global, actions, payload) => {
|
||||||
|
const { chatId, text } = payload;
|
||||||
|
|
||||||
|
actions.openChat({ id: chatId });
|
||||||
|
actions.exitMessageSelectMode();
|
||||||
|
|
||||||
|
global = getGlobal();
|
||||||
|
|
||||||
|
return {
|
||||||
|
...global,
|
||||||
|
openChatWithText: {
|
||||||
|
chatId,
|
||||||
|
text,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
addActionHandler('resetOpenChatWithText', (global) => {
|
||||||
|
return {
|
||||||
|
...global,
|
||||||
|
openChatWithText: undefined,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
addActionHandler('loadMoreMembers', async (global) => {
|
addActionHandler('loadMoreMembers', async (global) => {
|
||||||
const { chatId } = selectCurrentMessageList(global) || {};
|
const { chatId } = selectCurrentMessageList(global) || {};
|
||||||
const chat = chatId ? selectChat(global, chatId) : undefined;
|
const chat = chatId ? selectChat(global, chatId) : undefined;
|
||||||
|
|||||||
@ -574,7 +574,9 @@ addActionHandler('loadPollOptionResults', (global, actions, payload) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
addActionHandler('forwardMessages', (global, action, payload) => {
|
addActionHandler('forwardMessages', (global, action, payload) => {
|
||||||
const { fromChatId, messageIds, toChatId } = global.forwardMessages;
|
const {
|
||||||
|
fromChatId, messageIds, toChatId, withMyScore,
|
||||||
|
} = global.forwardMessages;
|
||||||
const fromChat = fromChatId ? selectChat(global, fromChatId) : undefined;
|
const fromChat = fromChatId ? selectChat(global, fromChatId) : undefined;
|
||||||
const toChat = toChatId ? selectChat(global, toChatId) : undefined;
|
const toChat = toChatId ? selectChat(global, toChatId) : undefined;
|
||||||
const messages = fromChatId && messageIds
|
const messages = fromChatId && messageIds
|
||||||
@ -600,6 +602,7 @@ addActionHandler('forwardMessages', (global, action, payload) => {
|
|||||||
isSilent,
|
isSilent,
|
||||||
scheduledAt,
|
scheduledAt,
|
||||||
sendAs,
|
sendAs,
|
||||||
|
withMyScore,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -410,7 +410,9 @@ addActionHandler('focusMessage', (global, actions, payload) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
addActionHandler('openForwardMenu', (global, actions, payload) => {
|
addActionHandler('openForwardMenu', (global, actions, payload) => {
|
||||||
const { fromChatId, messageIds, groupedId } = payload!;
|
const {
|
||||||
|
fromChatId, messageIds, groupedId, withMyScore,
|
||||||
|
} = payload!;
|
||||||
let groupedMessageIds;
|
let groupedMessageIds;
|
||||||
if (groupedId) {
|
if (groupedId) {
|
||||||
groupedMessageIds = selectMessageIdsByGroupId(global, fromChatId, groupedId);
|
groupedMessageIds = selectMessageIdsByGroupId(global, fromChatId, groupedId);
|
||||||
@ -421,6 +423,7 @@ addActionHandler('openForwardMenu', (global, actions, payload) => {
|
|||||||
fromChatId,
|
fromChatId,
|
||||||
messageIds: groupedMessageIds || messageIds,
|
messageIds: groupedMessageIds || messageIds,
|
||||||
isModalShown: true,
|
isModalShown: true,
|
||||||
|
withMyScore,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
@ -573,17 +576,23 @@ addActionHandler('disableContextMenuHint', (global) => {
|
|||||||
|
|
||||||
addActionHandler('exitMessageSelectMode', exitMessageSelectMode);
|
addActionHandler('exitMessageSelectMode', exitMessageSelectMode);
|
||||||
|
|
||||||
addActionHandler('openPollModal', (global) => {
|
addActionHandler('openPollModal', (global, actions, payload) => {
|
||||||
|
const { isQuiz } = payload || {};
|
||||||
return {
|
return {
|
||||||
...global,
|
...global,
|
||||||
isPollModalOpen: true,
|
pollModal: {
|
||||||
|
isOpen: true,
|
||||||
|
isQuiz,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
addActionHandler('closePollModal', (global) => {
|
addActionHandler('closePollModal', (global) => {
|
||||||
return {
|
return {
|
||||||
...global,
|
...global,
|
||||||
isPollModalOpen: false,
|
pollModal: {
|
||||||
|
isOpen: false,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -261,3 +261,22 @@ addActionHandler('closeHistoryCalendar', (global) => {
|
|||||||
historyCalendarSelectedAt: undefined,
|
historyCalendarSelectedAt: undefined,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
addActionHandler('openGame', (global, actions, payload) => {
|
||||||
|
const { url, chatId, messageId } = payload;
|
||||||
|
return {
|
||||||
|
...global,
|
||||||
|
openedGame: {
|
||||||
|
url,
|
||||||
|
chatId,
|
||||||
|
messageId,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
addActionHandler('closeGame', (global) => {
|
||||||
|
return {
|
||||||
|
...global,
|
||||||
|
openedGame: undefined,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|||||||
@ -219,6 +219,12 @@ function migrateCache(cached: GlobalState, initialState: GlobalState) {
|
|||||||
if (!cached.activeReactions) {
|
if (!cached.activeReactions) {
|
||||||
cached.activeReactions = {};
|
cached.activeReactions = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!cached.pollModal) {
|
||||||
|
cached.pollModal = {
|
||||||
|
isOpen: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateCache() {
|
function updateCache() {
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import {
|
import {
|
||||||
ApiAudio, ApiMediaFormat, ApiMessage, ApiMessageSearchType, ApiPhoto, ApiVideo, ApiDimensions, ApiLocation,
|
ApiAudio, ApiMediaFormat, ApiMessage, ApiMessageSearchType, ApiPhoto, ApiVideo, ApiDimensions, ApiLocation, ApiGame,
|
||||||
} from '../../api/types';
|
} from '../../api/types';
|
||||||
|
|
||||||
import { IS_OPUS_SUPPORTED, IS_PROGRESSIVE_SUPPORTED, IS_SAFARI } from '../../util/environment';
|
import { IS_OPUS_SUPPORTED, IS_PROGRESSIVE_SUPPORTED, IS_SAFARI } from '../../util/environment';
|
||||||
@ -252,6 +252,26 @@ export function getMessageMediaHash(
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getGamePreviewPhotoHash(game: ApiGame) {
|
||||||
|
const { photo } = game;
|
||||||
|
|
||||||
|
if (photo) {
|
||||||
|
return `photo${photo.id}?size=x`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getGamePreviewVideoHash(game: ApiGame) {
|
||||||
|
const { document } = game;
|
||||||
|
|
||||||
|
if (document) {
|
||||||
|
return `document${document.id}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
function getVideoOrAudioBaseHash(media: ApiAudio | ApiVideo, base: string) {
|
function getVideoOrAudioBaseHash(media: ApiAudio | ApiVideo, base: string) {
|
||||||
if (IS_PROGRESSIVE_SUPPORTED && IS_SAFARI) {
|
if (IS_PROGRESSIVE_SUPPORTED && IS_SAFARI) {
|
||||||
return `${base}?fileSize=${media.size}&mimeType=${media.mimeType}`;
|
return `${base}?fileSize=${media.size}&mimeType=${media.mimeType}`;
|
||||||
|
|||||||
@ -118,6 +118,7 @@ export function getMessageSummaryDescription(
|
|||||||
poll,
|
poll,
|
||||||
invoice,
|
invoice,
|
||||||
location,
|
location,
|
||||||
|
game,
|
||||||
} = message.content;
|
} = message.content;
|
||||||
|
|
||||||
let summary: string | TextPart[] | undefined;
|
let summary: string | TextPart[] | undefined;
|
||||||
@ -178,6 +179,10 @@ export function getMessageSummaryDescription(
|
|||||||
summary = lang('Message.LiveLocation');
|
summary = lang('Message.LiveLocation');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (game) {
|
||||||
|
summary = `🎮 ${game.title}`;
|
||||||
|
}
|
||||||
|
|
||||||
const reaction = !noReactions && getMessageRecentReaction(message);
|
const reaction = !noReactions && getMessageRecentReaction(message);
|
||||||
if (summary && reaction) {
|
if (summary && reaction) {
|
||||||
summary = `to your "${summary}"`;
|
summary = `to your "${summary}"`;
|
||||||
|
|||||||
@ -44,7 +44,7 @@ export function getMessageOriginalId(message: ApiMessage) {
|
|||||||
|
|
||||||
export function getMessageText(message: ApiMessage) {
|
export function getMessageText(message: ApiMessage) {
|
||||||
const {
|
const {
|
||||||
text, sticker, photo, video, audio, voice, document, poll, webPage, contact, invoice, location,
|
text, sticker, photo, video, audio, voice, document, poll, webPage, contact, invoice, location, game,
|
||||||
} = message.content;
|
} = message.content;
|
||||||
|
|
||||||
if (text) {
|
if (text) {
|
||||||
@ -52,7 +52,7 @@ export function getMessageText(message: ApiMessage) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (sticker || photo || video || audio || voice || document
|
if (sticker || photo || video || audio || voice || document
|
||||||
|| contact || poll || webPage || invoice || location) {
|
|| contact || poll || webPage || invoice || location || game) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -202,4 +202,8 @@ export const INITIAL_STATE: GlobalState = {
|
|||||||
statistics: {
|
statistics: {
|
||||||
byChatId: {},
|
byChatId: {},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
pollModal: {
|
||||||
|
isOpen: false,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@ -165,3 +165,10 @@ export function selectSendAs(global: GlobalState, chatId: string) {
|
|||||||
|
|
||||||
return selectUser(global, id) || selectChat(global, id);
|
return selectUser(global, id) || selectChat(global, id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function selectRequestedText(global: GlobalState, chatId: string) {
|
||||||
|
if (global.openChatWithText?.chatId === chatId) {
|
||||||
|
return global.openChatWithText.text;
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|||||||
@ -642,8 +642,8 @@ export function selectIsPollResultsOpen(global: GlobalState) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function selectIsForwardModalOpen(global: GlobalState) {
|
export function selectIsForwardModalOpen(global: GlobalState) {
|
||||||
const { forwardMessages } = global;
|
const { forwardMessages, switchBotInline } = global;
|
||||||
return Boolean(forwardMessages.isModalShown);
|
return Boolean(switchBotInline || forwardMessages.isModalShown);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function selectCommonBoxChatId(global: GlobalState, messageId: number) {
|
export function selectCommonBoxChatId(global: GlobalState, messageId: number) {
|
||||||
|
|||||||
@ -28,7 +28,9 @@ import {
|
|||||||
ApiSponsoredMessage,
|
ApiSponsoredMessage,
|
||||||
ApiChannelStatistics,
|
ApiChannelStatistics,
|
||||||
ApiGroupStatistics,
|
ApiGroupStatistics,
|
||||||
ApiPaymentFormNativeParams, ApiUpdate,
|
ApiPaymentFormNativeParams,
|
||||||
|
ApiUpdate,
|
||||||
|
ApiKeyboardButton,
|
||||||
} from '../api/types';
|
} from '../api/types';
|
||||||
import {
|
import {
|
||||||
FocusDirection,
|
FocusDirection,
|
||||||
@ -114,7 +116,6 @@ export type GlobalState = {
|
|||||||
isChatInfoShown: boolean;
|
isChatInfoShown: boolean;
|
||||||
isStatisticsShown?: boolean;
|
isStatisticsShown?: boolean;
|
||||||
isLeftColumnShown: boolean;
|
isLeftColumnShown: boolean;
|
||||||
isPollModalOpen?: boolean;
|
|
||||||
newChatMembersProgress?: NewChatMembersProgress;
|
newChatMembersProgress?: NewChatMembersProgress;
|
||||||
uiReadyState: 0 | 1 | 2;
|
uiReadyState: 0 | 1 | 2;
|
||||||
shouldSkipHistoryAnimations?: boolean;
|
shouldSkipHistoryAnimations?: boolean;
|
||||||
@ -407,6 +408,7 @@ export type GlobalState = {
|
|||||||
fromChatId?: string;
|
fromChatId?: string;
|
||||||
messageIds?: number[];
|
messageIds?: number[];
|
||||||
toChatId?: string;
|
toChatId?: string;
|
||||||
|
withMyScore?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
pollResults: {
|
pollResults: {
|
||||||
@ -516,6 +518,27 @@ export type GlobalState = {
|
|||||||
userId?: string;
|
userId?: string;
|
||||||
isByPhoneNumber?: boolean;
|
isByPhoneNumber?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
openedGame?: {
|
||||||
|
url: string;
|
||||||
|
chatId: string;
|
||||||
|
messageId: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
switchBotInline?: {
|
||||||
|
query: string;
|
||||||
|
botUsername: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
openChatWithText?: {
|
||||||
|
chatId: string;
|
||||||
|
text: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
pollModal: {
|
||||||
|
isOpen: boolean;
|
||||||
|
isQuiz?: boolean;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface ActionPayloads {
|
export interface ActionPayloads {
|
||||||
@ -531,6 +554,13 @@ export interface ActionPayloads {
|
|||||||
shouldReplaceHistory?: boolean;
|
shouldReplaceHistory?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
openChatWithText: {
|
||||||
|
chatId: string;
|
||||||
|
text: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
resetOpenChatWithText: {};
|
||||||
|
|
||||||
// Messages
|
// Messages
|
||||||
setEditingDraft: {
|
setEditingDraft: {
|
||||||
text?: ApiFormattedText;
|
text?: ApiFormattedText;
|
||||||
@ -615,6 +645,34 @@ export interface ActionPayloads {
|
|||||||
isMuted?: boolean;
|
isMuted?: boolean;
|
||||||
shouldSharePhoneNumber?: boolean;
|
shouldSharePhoneNumber?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Bots
|
||||||
|
|
||||||
|
clickBotInlineButton: {
|
||||||
|
messageId: number;
|
||||||
|
button: ApiKeyboardButton;
|
||||||
|
};
|
||||||
|
|
||||||
|
switchBotInline: {
|
||||||
|
messageId: number;
|
||||||
|
query: string;
|
||||||
|
isSamePeer?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
resetSwitchBotInline: {};
|
||||||
|
|
||||||
|
// Misc
|
||||||
|
openGame: {
|
||||||
|
url: string;
|
||||||
|
chatId: string;
|
||||||
|
messageId: number;
|
||||||
|
};
|
||||||
|
closeGame: {};
|
||||||
|
|
||||||
|
openPollModal: {
|
||||||
|
isQuiz?: boolean;
|
||||||
|
};
|
||||||
|
closePollModal: {};
|
||||||
}
|
}
|
||||||
|
|
||||||
export type NonTypedActionNames = (
|
export type NonTypedActionNames = (
|
||||||
@ -697,10 +755,9 @@ export type NonTypedActionNames = (
|
|||||||
'loadStickersForEmoji' | 'clearStickersForEmoji' | 'loadEmojiKeywords' | 'loadGreetingStickers' |
|
'loadStickersForEmoji' | 'clearStickersForEmoji' | 'loadEmojiKeywords' | 'loadGreetingStickers' |
|
||||||
'openStickerSetShortName' |
|
'openStickerSetShortName' |
|
||||||
// bots
|
// bots
|
||||||
'clickInlineButton' | 'sendBotCommand' | 'loadTopInlineBots' | 'queryInlineBot' | 'sendInlineBotResult' |
|
'sendBotCommand' | 'loadTopInlineBots' | 'queryInlineBot' | 'sendInlineBotResult' |
|
||||||
'resetInlineBot' | 'restartBot' | 'startBot' |
|
'resetInlineBot' | 'restartBot' | 'startBot' |
|
||||||
// misc
|
// misc
|
||||||
'openPollModal' | 'closePollModal' |
|
|
||||||
'loadWebPagePreview' | 'clearWebPagePreview' | 'loadWallpapers' | 'uploadWallpaper' |
|
'loadWebPagePreview' | 'clearWebPagePreview' | 'loadWallpapers' | 'uploadWallpaper' |
|
||||||
'setDeviceToken' | 'deleteDeviceToken' |
|
'setDeviceToken' | 'deleteDeviceToken' |
|
||||||
'checkVersionNotification' | 'createServiceNotification' |
|
'checkVersionNotification' | 'createServiceNotification' |
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import { ApiSendMessageAction } from '../api/types';
|
|||||||
import { SEND_MESSAGE_ACTION_INTERVAL } from '../config';
|
import { SEND_MESSAGE_ACTION_INTERVAL } from '../config';
|
||||||
import { throttle } from '../util/schedulers';
|
import { throttle } from '../util/schedulers';
|
||||||
|
|
||||||
const useSendMessageAction = (chatId: string, threadId?: number) => {
|
const useSendMessageAction = (chatId?: string, threadId?: number) => {
|
||||||
return useMemo(() => {
|
return useMemo(() => {
|
||||||
return throttle((action: ApiSendMessageAction) => {
|
return throttle((action: ApiSendMessageAction) => {
|
||||||
getActions().sendMessageAction({ chatId, threadId, action });
|
getActions().sendMessageAction({ chatId, threadId, action });
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
// eslint-disable-next-line import/no-named-default
|
|
||||||
import { default as Api } from '../tl/api';
|
import { default as Api } from '../tl/api';
|
||||||
import TelegramClient from './TelegramClient';
|
import TelegramClient from './TelegramClient';
|
||||||
import utils from '../Utils';
|
import utils from '../Utils';
|
||||||
|
|||||||
45
src/util/PopupManager.ts
Normal file
45
src/util/PopupManager.ts
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
import { IS_ANDROID, IS_IOS } from './environment';
|
||||||
|
|
||||||
|
const SHOULD_PRE_OPEN = IS_IOS || IS_ANDROID;
|
||||||
|
|
||||||
|
export default class PopupManager {
|
||||||
|
private preOpened?: WindowProxy | null;
|
||||||
|
|
||||||
|
constructor(private features?: string, private onFail?: NoneToVoidFunction) {
|
||||||
|
}
|
||||||
|
|
||||||
|
preOpenIfNeeded() {
|
||||||
|
if (!SHOULD_PRE_OPEN) return;
|
||||||
|
|
||||||
|
this.preOpened = window.open('about:blank', undefined, this.features);
|
||||||
|
if (this.preOpened) {
|
||||||
|
this.preOpened.blur();
|
||||||
|
} else {
|
||||||
|
this.onFail?.();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
open(url: string) {
|
||||||
|
if (this.preOpened) {
|
||||||
|
this.preOpened!.location.href = url;
|
||||||
|
this.preOpened!.focus();
|
||||||
|
this.preOpened = undefined;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!SHOULD_PRE_OPEN) {
|
||||||
|
const popup = window.open(url, undefined, this.features);
|
||||||
|
if (popup) {
|
||||||
|
popup.focus();
|
||||||
|
} else {
|
||||||
|
this.onFail?.();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cancelPreOpen() {
|
||||||
|
this.preOpened?.close();
|
||||||
|
this.preOpened = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user