Media: Allow sending messages while uploading (#3170)
This commit is contained in:
parent
d25892caa4
commit
a953869683
@ -43,7 +43,6 @@ import {
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
DELETED_COMMENTS_CHANNEL_ID,
|
DELETED_COMMENTS_CHANNEL_ID,
|
||||||
LOCAL_MESSAGE_MIN_ID,
|
|
||||||
SERVICE_NOTIFICATIONS_USER_ID,
|
SERVICE_NOTIFICATIONS_USER_ID,
|
||||||
SPONSORED_MESSAGE_CACHE_MS,
|
SPONSORED_MESSAGE_CACHE_MS,
|
||||||
SUPPORTED_AUDIO_CONTENT_TYPES,
|
SUPPORTED_AUDIO_CONTENT_TYPES,
|
||||||
@ -65,22 +64,14 @@ import { buildApiCallDiscardReason } from './calls';
|
|||||||
import { getEmojiOnlyCountForMessage } from '../../../global/helpers/getEmojiOnlyCountForMessage';
|
import { getEmojiOnlyCountForMessage } from '../../../global/helpers/getEmojiOnlyCountForMessage';
|
||||||
import { getServerTimeOffset } from '../../../util/serverTime';
|
import { getServerTimeOffset } from '../../../util/serverTime';
|
||||||
|
|
||||||
const TIMESTAMP_BASE = 1676e9; // 2023-02-10
|
|
||||||
const TIMESTAMP_PRECISION = 1e2; // 0.1s
|
|
||||||
const LOCAL_MESSAGES_LIMIT = 1e6; // 1M
|
const LOCAL_MESSAGES_LIMIT = 1e6; // 1M
|
||||||
|
|
||||||
const LOCAL_MEDIA_UPLOADING_TEMP_ID = 'temp';
|
const LOCAL_MEDIA_UPLOADING_TEMP_ID = 'temp';
|
||||||
const INPUT_WAVEFORM_LENGTH = 63;
|
const INPUT_WAVEFORM_LENGTH = 63;
|
||||||
|
|
||||||
let localMessageCounter = LOCAL_MESSAGE_MIN_ID;
|
let localMessageCounter = 0;
|
||||||
|
function getNextLocalMessageId(lastMessageId = 0) {
|
||||||
// Local IDs need to be fractional to allow service notifications to be placed between real messages.
|
return lastMessageId + (++localMessageCounter / LOCAL_MESSAGES_LIMIT);
|
||||||
// It also allows to avoid collisions when sending messages from multiple tabs due to timestamp-based whole part.
|
|
||||||
// To support up to 1M local messages, the whole part must be below 8.5B (https://stackoverflow.com/a/57225494/903919).
|
|
||||||
// The overflow will happen when `datePart` is >3.59B which will be in June 2034.
|
|
||||||
function getNextLocalMessageId() {
|
|
||||||
const datePart = Math.round((Date.now() - TIMESTAMP_BASE) / TIMESTAMP_PRECISION);
|
|
||||||
return LOCAL_MESSAGE_MIN_ID + datePart + (++localMessageCounter / LOCAL_MESSAGES_LIMIT);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let currentUserId!: string;
|
let currentUserId!: string;
|
||||||
@ -1308,7 +1299,7 @@ export function buildLocalMessage(
|
|||||||
scheduledAt?: number,
|
scheduledAt?: number,
|
||||||
sendAs?: ApiChat | ApiUser,
|
sendAs?: ApiChat | ApiUser,
|
||||||
): ApiMessage {
|
): ApiMessage {
|
||||||
const localId = getNextLocalMessageId();
|
const localId = getNextLocalMessageId(chat.lastMessage?.id);
|
||||||
const media = attachment && buildUploadingMedia(attachment);
|
const media = attachment && buildUploadingMedia(attachment);
|
||||||
const isChannel = chat.type === 'chatTypeChannel';
|
const isChannel = chat.type === 'chatTypeChannel';
|
||||||
const isForum = chat.isForum;
|
const isForum = chat.isForum;
|
||||||
|
|||||||
@ -214,7 +214,7 @@ export async function fetchMessage({ chat, messageId }: { chat: ApiChat; message
|
|||||||
return { message, users };
|
return { message, users };
|
||||||
}
|
}
|
||||||
|
|
||||||
let queue = Promise.resolve();
|
let mediaQueue = Promise.resolve();
|
||||||
|
|
||||||
export function sendMessage(
|
export function sendMessage(
|
||||||
{
|
{
|
||||||
@ -236,6 +236,7 @@ export function sendMessage(
|
|||||||
shouldUpdateStickerSetOrder,
|
shouldUpdateStickerSetOrder,
|
||||||
}: {
|
}: {
|
||||||
chat: ApiChat;
|
chat: ApiChat;
|
||||||
|
lastMessageId?: number;
|
||||||
text?: string;
|
text?: string;
|
||||||
entities?: ApiMessageEntity[];
|
entities?: ApiMessageEntity[];
|
||||||
replyingTo?: number;
|
replyingTo?: number;
|
||||||
@ -306,8 +307,7 @@ export function sendMessage(
|
|||||||
}, randomId, localMessage, onProgress);
|
}, randomId, localMessage, onProgress);
|
||||||
}
|
}
|
||||||
|
|
||||||
const prevQueue = queue;
|
const sendPromise = (async () => {
|
||||||
queue = (async () => {
|
|
||||||
let media: GramJs.TypeInputMedia | undefined;
|
let media: GramJs.TypeInputMedia | undefined;
|
||||||
if (attachment) {
|
if (attachment) {
|
||||||
try {
|
try {
|
||||||
@ -318,7 +318,7 @@ export function sendMessage(
|
|||||||
console.warn(err);
|
console.warn(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
await prevQueue;
|
await mediaQueue;
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -337,8 +337,6 @@ export function sendMessage(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
await prevQueue;
|
|
||||||
|
|
||||||
const RequestClass = media ? GramJs.messages.SendMedia : GramJs.messages.SendMessage;
|
const RequestClass = media ? GramJs.messages.SendMedia : GramJs.messages.SendMessage;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -369,7 +367,7 @@ export function sendMessage(
|
|||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
|
||||||
return queue;
|
return sendPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
const groupedUploads: Record<string, {
|
const groupedUploads: Record<string, {
|
||||||
@ -417,8 +415,8 @@ function sendGroupedMedia(
|
|||||||
|
|
||||||
groupIndex = groupedUploads[groupedId].counter++;
|
groupIndex = groupedUploads[groupedId].counter++;
|
||||||
|
|
||||||
const prevQueue = queue;
|
const prevMediaQueue = mediaQueue;
|
||||||
queue = (async () => {
|
mediaQueue = (async () => {
|
||||||
let media;
|
let media;
|
||||||
try {
|
try {
|
||||||
media = await uploadMedia(localMessage, attachment, onProgress!);
|
media = await uploadMedia(localMessage, attachment, onProgress!);
|
||||||
@ -430,7 +428,7 @@ function sendGroupedMedia(
|
|||||||
|
|
||||||
groupedUploads[groupedId].counter--;
|
groupedUploads[groupedId].counter--;
|
||||||
|
|
||||||
await prevQueue;
|
await prevMediaQueue;
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -440,7 +438,7 @@ function sendGroupedMedia(
|
|||||||
media as GramJs.InputMediaUploadedPhoto | GramJs.InputMediaUploadedDocument,
|
media as GramJs.InputMediaUploadedPhoto | GramJs.InputMediaUploadedDocument,
|
||||||
);
|
);
|
||||||
|
|
||||||
await prevQueue;
|
await prevMediaQueue;
|
||||||
|
|
||||||
if (!inputMedia) {
|
if (!inputMedia) {
|
||||||
groupedUploads[groupedId].counter--;
|
groupedUploads[groupedId].counter--;
|
||||||
@ -482,7 +480,7 @@ function sendGroupedMedia(
|
|||||||
if (update) handleMultipleLocalMessagesUpdate(localMessages, update);
|
if (update) handleMultipleLocalMessagesUpdate(localMessages, update);
|
||||||
})();
|
})();
|
||||||
|
|
||||||
return queue;
|
return mediaQueue;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function fetchInputMedia(
|
async function fetchInputMedia(
|
||||||
|
|||||||
@ -1,6 +1,8 @@
|
|||||||
import type { RefObject } from 'react';
|
import React, {
|
||||||
|
memo, useRef, useState, useMemo,
|
||||||
|
} from '../../lib/teact/teact';
|
||||||
|
|
||||||
import type { FC } from '../../lib/teact/teact';
|
import type { FC } from '../../lib/teact/teact';
|
||||||
import React, { memo, useRef, useState } from '../../lib/teact/teact';
|
|
||||||
|
|
||||||
import { IS_CANVAS_FILTER_SUPPORTED } from '../../util/windowEnvironment';
|
import { IS_CANVAS_FILTER_SUPPORTED } from '../../util/windowEnvironment';
|
||||||
import buildClassName from '../../util/buildClassName';
|
import buildClassName from '../../util/buildClassName';
|
||||||
@ -20,7 +22,7 @@ import Link from '../ui/Link';
|
|||||||
import './File.scss';
|
import './File.scss';
|
||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
ref?: RefObject<HTMLDivElement>;
|
ref?: React.RefObject<HTMLDivElement>;
|
||||||
name: string;
|
name: string;
|
||||||
extension?: string;
|
extension?: string;
|
||||||
size: number;
|
size: number;
|
||||||
@ -80,6 +82,10 @@ const File: FC<OwnProps> = ({
|
|||||||
|
|
||||||
const color = getColorFromExtension(extension);
|
const color = getColorFromExtension(extension);
|
||||||
const sizeString = getFileSizeString(size);
|
const sizeString = getFileSizeString(size);
|
||||||
|
const subtitle = useMemo(() => {
|
||||||
|
if (!isTransferring || !transferProgress) return sizeString;
|
||||||
|
return `${getFileSizeString(size * transferProgress)} / ${sizeString}`;
|
||||||
|
}, [isTransferring, size, sizeString, transferProgress]);
|
||||||
|
|
||||||
const { width, height } = getDocumentThumbnailDimensions(smaller);
|
const { width, height } = getDocumentThumbnailDimensions(smaller);
|
||||||
|
|
||||||
@ -146,7 +152,7 @@ const File: FC<OwnProps> = ({
|
|||||||
<div className="file-title" dir="auto" title={name}>{renderText(name)}</div>
|
<div className="file-title" dir="auto" title={name}>{renderText(name)}</div>
|
||||||
<div className="file-subtitle" dir="auto">
|
<div className="file-subtitle" dir="auto">
|
||||||
<span>
|
<span>
|
||||||
{isTransferring && transferProgress ? `${Math.round(transferProgress * 100)}%` : sizeString}
|
{subtitle}
|
||||||
</span>
|
</span>
|
||||||
{sender && <span className="file-sender">{renderText(sender)}</span>}
|
{sender && <span className="file-sender">{renderText(sender)}</span>}
|
||||||
{!sender && Boolean(timestamp) && (
|
{!sender && Boolean(timestamp) && (
|
||||||
|
|||||||
@ -22,7 +22,6 @@ import { LoadMoreDirection } from '../../types';
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
ANIMATION_END_DELAY,
|
ANIMATION_END_DELAY,
|
||||||
LOCAL_MESSAGE_MIN_ID,
|
|
||||||
MESSAGE_LIST_SLICE,
|
MESSAGE_LIST_SLICE,
|
||||||
SERVICE_NOTIFICATIONS_USER_ID,
|
SERVICE_NOTIFICATIONS_USER_ID,
|
||||||
} from '../../config';
|
} from '../../config';
|
||||||
@ -58,6 +57,7 @@ import {
|
|||||||
getDocumentMediaHash,
|
getDocumentMediaHash,
|
||||||
getVideoDimensions,
|
getVideoDimensions,
|
||||||
getPhotoFullDimensions,
|
getPhotoFullDimensions,
|
||||||
|
isLocalMessageId,
|
||||||
} from '../../global/helpers';
|
} from '../../global/helpers';
|
||||||
import { orderBy } from '../../util/iteratees';
|
import { orderBy } from '../../util/iteratees';
|
||||||
import { DPR } from '../../util/windowEnvironment';
|
import { DPR } from '../../util/windowEnvironment';
|
||||||
@ -366,7 +366,7 @@ const MessageList: FC<OwnProps & StateProps> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Loading history while sending a message can return the same message and cause ambiguity
|
// Loading history while sending a message can return the same message and cause ambiguity
|
||||||
const isLastMessageLocal = messageIds && messageIds[messageIds.length - 1] > LOCAL_MESSAGE_MIN_ID;
|
const isLastMessageLocal = messageIds && isLocalMessageId(messageIds[messageIds.length - 1]);
|
||||||
if (isLastMessageLocal) {
|
if (isLastMessageLocal) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,9 +9,10 @@ import { LoadMoreDirection } from '../../../types';
|
|||||||
import type { MessageListType } from '../../../global/types';
|
import type { MessageListType } from '../../../global/types';
|
||||||
import type { Signal } from '../../../util/signals';
|
import type { Signal } from '../../../util/signals';
|
||||||
|
|
||||||
import { LOCAL_MESSAGE_MIN_ID } from '../../../config';
|
|
||||||
import { MESSAGE_LIST_SENSITIVE_AREA } from '../../../util/windowEnvironment';
|
import { MESSAGE_LIST_SENSITIVE_AREA } from '../../../util/windowEnvironment';
|
||||||
import { debounce } from '../../../util/schedulers';
|
import { debounce } from '../../../util/schedulers';
|
||||||
|
import { isLocalMessageId } from '../../../global/helpers';
|
||||||
|
|
||||||
import { useIntersectionObserver, useOnIntersect } from '../../../hooks/useIntersectionObserver';
|
import { useIntersectionObserver, useOnIntersect } from '../../../hooks/useIntersectionObserver';
|
||||||
import useSyncEffect from '../../../hooks/useSyncEffect';
|
import useSyncEffect from '../../../hooks/useSyncEffect';
|
||||||
import { useStateRef } from '../../../hooks/useStateRef';
|
import { useStateRef } from '../../../hooks/useStateRef';
|
||||||
@ -94,7 +95,7 @@ export default function useScrollHooks(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Loading history while sending a message can return the same message and cause ambiguity
|
// Loading history while sending a message can return the same message and cause ambiguity
|
||||||
const isFirstMessageLocal = messageIds[0] > LOCAL_MESSAGE_MIN_ID;
|
const isFirstMessageLocal = isLocalMessageId(messageIds[0]);
|
||||||
if (isFirstMessageLocal) {
|
if (isFirstMessageLocal) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -139,7 +139,6 @@ export const MOBILE_SCREEN_MAX_WIDTH = 600; // px
|
|||||||
export const MOBILE_SCREEN_LANDSCAPE_MAX_WIDTH = 950; // px
|
export const MOBILE_SCREEN_LANDSCAPE_MAX_WIDTH = 950; // px
|
||||||
export const MOBILE_SCREEN_LANDSCAPE_MAX_HEIGHT = 450; // px
|
export const MOBILE_SCREEN_LANDSCAPE_MAX_HEIGHT = 450; // px
|
||||||
|
|
||||||
export const LOCAL_MESSAGE_MIN_ID = 5e9;
|
|
||||||
export const MAX_INT_32 = 2 ** 31 - 1;
|
export const MAX_INT_32 = 2 ** 31 - 1;
|
||||||
export const TMP_CHAT_ID = '0';
|
export const TMP_CHAT_ID = '0';
|
||||||
|
|
||||||
|
|||||||
@ -6,7 +6,6 @@ import type { LangFn } from '../../hooks/useLang';
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
CONTENT_NOT_SUPPORTED,
|
CONTENT_NOT_SUPPORTED,
|
||||||
LOCAL_MESSAGE_MIN_ID,
|
|
||||||
RE_LINK_TEMPLATE,
|
RE_LINK_TEMPLATE,
|
||||||
SERVICE_NOTIFICATIONS_USER_ID,
|
SERVICE_NOTIFICATIONS_USER_ID,
|
||||||
} from '../../config';
|
} from '../../config';
|
||||||
@ -199,7 +198,7 @@ export function isMessageLocal(message: ApiMessage) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function isLocalMessageId(id: number) {
|
export function isLocalMessageId(id: number) {
|
||||||
return id > LOCAL_MESSAGE_MIN_ID;
|
return !Number.isInteger(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isHistoryClearMessage(message: ApiMessage) {
|
export function isHistoryClearMessage(message: ApiMessage) {
|
||||||
|
|||||||
@ -12,7 +12,7 @@ import type {
|
|||||||
import { ApiMessageEntityTypes, MAIN_THREAD_ID } from '../../api/types';
|
import { ApiMessageEntityTypes, MAIN_THREAD_ID } from '../../api/types';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
GENERAL_TOPIC_ID, LOCAL_MESSAGE_MIN_ID, REPLIES_USER_ID, SERVICE_NOTIFICATIONS_USER_ID,
|
GENERAL_TOPIC_ID, REPLIES_USER_ID, SERVICE_NOTIFICATIONS_USER_ID,
|
||||||
} from '../../config';
|
} from '../../config';
|
||||||
import {
|
import {
|
||||||
selectChat, selectChatBot, selectChatFullInfo, selectIsChatWithSelf,
|
selectChat, selectChatBot, selectChatFullInfo, selectIsChatWithSelf,
|
||||||
@ -46,6 +46,7 @@ import {
|
|||||||
isUserRightBanned,
|
isUserRightBanned,
|
||||||
canSendReaction,
|
canSendReaction,
|
||||||
getAllowedAttachmentOptions,
|
getAllowedAttachmentOptions,
|
||||||
|
isLocalMessageId,
|
||||||
} from '../helpers';
|
} from '../helpers';
|
||||||
import { findLast } from '../../util/iteratees';
|
import { findLast } from '../../util/iteratees';
|
||||||
import { selectIsStickerFavorite } from './symbols';
|
import { selectIsStickerFavorite } from './symbols';
|
||||||
@ -324,7 +325,7 @@ export function selectIsViewportNewest<T extends GlobalState>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Edge case: outgoing `lastMessage` is updated with a delay to optimize animation
|
// Edge case: outgoing `lastMessage` is updated with a delay to optimize animation
|
||||||
if (lastMessageId > LOCAL_MESSAGE_MIN_ID && !selectChatMessage(global, chatId, lastMessageId)) {
|
if (isLocalMessageId(lastMessageId) && !selectChatMessage(global, chatId, lastMessageId)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user