Media Viewer: Fix document real size (#6177)
This commit is contained in:
parent
eb7dff433c
commit
c7bf1ebd72
@ -136,6 +136,7 @@ export default tseslint.config(
|
|||||||
disallowTypeAnnotations: false,
|
disallowTypeAnnotations: false,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
'@typescript-eslint/no-shadow': 'error',
|
||||||
'@typescript-eslint/no-unsafe-argument': 'off',
|
'@typescript-eslint/no-unsafe-argument': 'off',
|
||||||
'@typescript-eslint/no-unsafe-assignment': 'off',
|
'@typescript-eslint/no-unsafe-assignment': 'off',
|
||||||
'@typescript-eslint/no-unsafe-call': 'off',
|
'@typescript-eslint/no-unsafe-call': 'off',
|
||||||
|
|||||||
@ -153,7 +153,7 @@ export interface ApiDocument {
|
|||||||
previewPhotoSizes?: ApiPhotoSize[];
|
previewPhotoSizes?: ApiPhotoSize[];
|
||||||
previewBlobUrl?: string;
|
previewBlobUrl?: string;
|
||||||
innerMediaType?: 'photo' | 'video';
|
innerMediaType?: 'photo' | 'video';
|
||||||
mediaSize?: ApiDimensions & { fromDocumentAttribute?: boolean };
|
mediaSize?: ApiDimensions & { fromDocumentAttribute?: boolean; fromPreload?: true };
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ApiContact {
|
export interface ApiContact {
|
||||||
|
|||||||
@ -15,6 +15,7 @@ import {
|
|||||||
} from '../../global/helpers';
|
} from '../../global/helpers';
|
||||||
import { isIpRevealingMedia } from '../../util/media/ipRevealingMedia';
|
import { isIpRevealingMedia } from '../../util/media/ipRevealingMedia';
|
||||||
import { getDocumentExtension, getDocumentHasPreview } from './helpers/documentInfo';
|
import { getDocumentExtension, getDocumentHasPreview } from './helpers/documentInfo';
|
||||||
|
import { preloadDocumentMedia } from './helpers/preloadDocumentMedia';
|
||||||
|
|
||||||
import useFlag from '../../hooks/useFlag';
|
import useFlag from '../../hooks/useFlag';
|
||||||
import { useIsIntersecting } from '../../hooks/useIntersectionObserver';
|
import { useIsIntersecting } from '../../hooks/useIntersectionObserver';
|
||||||
@ -119,10 +120,26 @@ const Document = ({
|
|||||||
const localBlobUrl = hasPreview ? document.previewBlobUrl : undefined;
|
const localBlobUrl = hasPreview ? document.previewBlobUrl : undefined;
|
||||||
const previewData = useMedia(getDocumentMediaHash(document, 'pictogram'), !isIntersecting);
|
const previewData = useMedia(getDocumentMediaHash(document, 'pictogram'), !isIntersecting);
|
||||||
|
|
||||||
const shouldForceDownload = document.innerMediaType === 'photo' && !document.mediaSize?.fromDocumentAttribute;
|
const shouldForceDownload = document.innerMediaType === 'photo' && document.mediaSize
|
||||||
|
&& !document.mediaSize.fromDocumentAttribute && !document.mediaSize.fromPreload;
|
||||||
|
|
||||||
const withMediaViewer = onMediaClick && document.innerMediaType && !shouldForceDownload;
|
const withMediaViewer = onMediaClick && document.innerMediaType && !shouldForceDownload;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fileEl = ref.current;
|
||||||
|
if (!withMediaViewer || !fileEl || !message) return;
|
||||||
|
|
||||||
|
const onHover = () => {
|
||||||
|
preloadDocumentMedia(message);
|
||||||
|
};
|
||||||
|
|
||||||
|
fileEl.addEventListener('mouseenter', onHover);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
fileEl.removeEventListener('mouseenter', onHover);
|
||||||
|
};
|
||||||
|
}, [withMediaViewer, message]);
|
||||||
|
|
||||||
const handleDownload = useLastCallback(() => {
|
const handleDownload = useLastCallback(() => {
|
||||||
downloadMedia({ media: document, originMessage: message });
|
downloadMedia({ media: document, originMessage: message });
|
||||||
});
|
});
|
||||||
|
|||||||
@ -225,21 +225,21 @@ const PremiumProgress: FC<OwnProps> = ({
|
|||||||
|
|
||||||
const renderProgressLayer = (
|
const renderProgressLayer = (
|
||||||
isPositive: boolean,
|
isPositive: boolean,
|
||||||
layerProgress: number,
|
currentProgress: number,
|
||||||
layerClassName?: string,
|
layerClassName?: string,
|
||||||
disableTransition?: boolean,
|
disableTransition?: boolean,
|
||||||
) => {
|
) => {
|
||||||
const className = isPositive ? styles.positiveProgress : styles.negativeProgress;
|
const typeClass = isPositive ? styles.positiveProgress : styles.negativeProgress;
|
||||||
const progressVar = '--layer-progress';
|
const progressVar = '--layer-progress';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={buildClassName(
|
className={buildClassName(
|
||||||
className,
|
typeClass,
|
||||||
layerClassName,
|
layerClassName,
|
||||||
disableTransition && styles.noTransition,
|
disableTransition && styles.noTransition,
|
||||||
)}
|
)}
|
||||||
style={`${progressVar}: ${layerProgress}`}
|
style={`${progressVar}: ${currentProgress}`}
|
||||||
>
|
>
|
||||||
<div className={styles.left}>
|
<div className={styles.left}>
|
||||||
<span>{displayLeftText}</span>
|
<span>{displayLeftText}</span>
|
||||||
|
|||||||
70
src/components/common/helpers/preloadDocumentMedia.ts
Normal file
70
src/components/common/helpers/preloadDocumentMedia.ts
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
import { getGlobal, setGlobal } from '../../../global';
|
||||||
|
|
||||||
|
import type { ApiDocument, ApiMessage } from '../../../api/types';
|
||||||
|
|
||||||
|
import {
|
||||||
|
getDocumentMediaHash, getMediaFormat, getMessageDocumentPhoto, getMessageDocumentVideo,
|
||||||
|
} from '../../../global/helpers';
|
||||||
|
import { updateChatMessage } from '../../../global/reducers';
|
||||||
|
import { selectChatMessage } from '../../../global/selectors';
|
||||||
|
import { IS_PROGRESSIVE_SUPPORTED } from '../../../util/browser/windowEnvironment';
|
||||||
|
import { preloadImage, preloadVideo } from '../../../util/files';
|
||||||
|
import { fetch } from '../../../util/mediaLoader';
|
||||||
|
import LimitedMap from '../../../util/primitives/LimitedMap';
|
||||||
|
|
||||||
|
const preloadedHashes = new LimitedMap<string, void>(100);
|
||||||
|
|
||||||
|
export async function preloadDocumentMedia(mediaContainer: ApiMessage) {
|
||||||
|
const video = getMessageDocumentVideo(mediaContainer);
|
||||||
|
const photo = getMessageDocumentPhoto(mediaContainer);
|
||||||
|
|
||||||
|
const media = video || photo;
|
||||||
|
|
||||||
|
// Skip large photos that were not processed by the server
|
||||||
|
const shouldSkipPhoto = photo && photo.mediaSize && !photo.mediaSize.fromDocumentAttribute;
|
||||||
|
if (!media || media.previewBlobUrl || shouldSkipPhoto) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const hash = getDocumentMediaHash(media, 'full');
|
||||||
|
if (!hash || preloadedHashes.has(hash)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
preloadedHashes.set(hash, undefined);
|
||||||
|
|
||||||
|
const url = await fetch(hash, getMediaFormat(media, 'full'));
|
||||||
|
if (!url) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let dimensions: ApiDocument['mediaSize'] | undefined;
|
||||||
|
|
||||||
|
if (video && IS_PROGRESSIVE_SUPPORTED) {
|
||||||
|
const videoEl = await preloadVideo(url);
|
||||||
|
dimensions = { width: videoEl.videoWidth, height: videoEl.videoHeight, fromPreload: true };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (photo) {
|
||||||
|
const img = await preloadImage(url);
|
||||||
|
dimensions = { width: img.naturalWidth, height: img.naturalHeight, fromPreload: true };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!dimensions || dimensions.width <= 0 || dimensions.height <= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let global = getGlobal();
|
||||||
|
const message = selectChatMessage(global, mediaContainer.chatId, mediaContainer.id);
|
||||||
|
if (!message || !message.content.document) return;
|
||||||
|
global = updateChatMessage(global, mediaContainer.chatId, mediaContainer.id, {
|
||||||
|
content: {
|
||||||
|
...message.content,
|
||||||
|
document: {
|
||||||
|
...message.content.document,
|
||||||
|
mediaSize: dimensions,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
setGlobal(global);
|
||||||
|
}
|
||||||
@ -64,6 +64,7 @@ type StateProps = {
|
|||||||
enum ContentType {
|
enum ContentType {
|
||||||
Main,
|
Main,
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-shadow
|
||||||
Settings,
|
Settings,
|
||||||
Archived,
|
Archived,
|
||||||
|
|
||||||
|
|||||||
@ -257,14 +257,14 @@ const LeftMainHeader: FC<OwnProps & StateProps> = ({
|
|||||||
}, [globalSearchChatId, selectedSearchDate]);
|
}, [globalSearchChatId, selectedSearchDate]);
|
||||||
|
|
||||||
const version = useMemo(() => {
|
const version = useMemo(() => {
|
||||||
let version = '';
|
let fullVersion = '';
|
||||||
if (IS_TAURI && window.tauri.version) {
|
if (IS_TAURI && window.tauri.version) {
|
||||||
version = `Tauri ${window.tauri.version} | `;
|
fullVersion = `Tauri ${window.tauri.version} | `;
|
||||||
}
|
}
|
||||||
|
|
||||||
version += `${APP_NAME} ${versionString}`;
|
fullVersion += `${APP_NAME} ${versionString}`;
|
||||||
|
|
||||||
return version;
|
return fullVersion;
|
||||||
}, [versionString]);
|
}, [versionString]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -109,7 +109,7 @@ export const useMediaProps = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (isDocument) {
|
if (isDocument) {
|
||||||
return media.mediaSize!;
|
return media.mediaSize || FALLBACK_DIMENSIONS;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isPhoto) {
|
if (isPhoto) {
|
||||||
|
|||||||
@ -145,12 +145,15 @@ type StateProps = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
enum Content {
|
enum Content {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-shadow
|
||||||
Loading,
|
Loading,
|
||||||
Restricted,
|
Restricted,
|
||||||
StarsRequired,
|
StarsRequired,
|
||||||
PremiumRequired,
|
PremiumRequired,
|
||||||
AccountInfo,
|
AccountInfo,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-shadow
|
||||||
ContactGreeting,
|
ContactGreeting,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-shadow
|
||||||
NoMessages,
|
NoMessages,
|
||||||
MessageList,
|
MessageList,
|
||||||
}
|
}
|
||||||
@ -180,6 +183,7 @@ const MessageList: FC<OwnProps & StateProps> = ({
|
|||||||
isChannelWithAvatars,
|
isChannelWithAvatars,
|
||||||
canPost,
|
canPost,
|
||||||
isSynced,
|
isSynced,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-shadow
|
||||||
isChatMonoforum,
|
isChatMonoforum,
|
||||||
isReady,
|
isReady,
|
||||||
isChatWithSelf,
|
isChatWithSelf,
|
||||||
|
|||||||
@ -478,8 +478,8 @@ const AttachmentModal: FC<OwnProps & StateProps> = ({
|
|||||||
const everyPhoto = renderingAttachments.every((a) => SUPPORTED_PHOTO_CONTENT_TYPES.has(a.mimeType));
|
const everyPhoto = renderingAttachments.every((a) => SUPPORTED_PHOTO_CONTENT_TYPES.has(a.mimeType));
|
||||||
const everyVideo = renderingAttachments.every((a) => SUPPORTED_VIDEO_CONTENT_TYPES.has(a.mimeType));
|
const everyVideo = renderingAttachments.every((a) => SUPPORTED_VIDEO_CONTENT_TYPES.has(a.mimeType));
|
||||||
const everyAudio = renderingAttachments.every((a) => SUPPORTED_AUDIO_CONTENT_TYPES.has(a.mimeType));
|
const everyAudio = renderingAttachments.every((a) => SUPPORTED_AUDIO_CONTENT_TYPES.has(a.mimeType));
|
||||||
const hasAnyPhoto = renderingAttachments.some((a) => SUPPORTED_PHOTO_CONTENT_TYPES.has(a.mimeType));
|
const anyPhoto = renderingAttachments.some((a) => SUPPORTED_PHOTO_CONTENT_TYPES.has(a.mimeType));
|
||||||
return [everyPhoto, everyVideo, everyAudio, hasAnyPhoto];
|
return [everyPhoto, everyVideo, everyAudio, anyPhoto];
|
||||||
}, [renderingAttachments, isQuickGallery]);
|
}, [renderingAttachments, isQuickGallery]);
|
||||||
|
|
||||||
const hasAnySpoilerable = useMemo(() => {
|
const hasAnySpoilerable = useMemo(() => {
|
||||||
|
|||||||
@ -287,13 +287,14 @@ const ToDoListModal = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
function renderHeader() {
|
function renderHeader() {
|
||||||
const title = isAddTaskMode ? 'TitleAppendToDoList' : editingMessage ? 'TitleEditToDoList' : 'TitleNewToDoList';
|
const modalTitle = isAddTaskMode ? 'TitleAppendToDoList'
|
||||||
|
: editingMessage ? 'TitleEditToDoList' : 'TitleNewToDoList';
|
||||||
return (
|
return (
|
||||||
<div className="modal-header-condensed">
|
<div className="modal-header-condensed">
|
||||||
<Button round color="translucent" size="smaller" ariaLabel={lang('AriaToDoCancel')} onClick={onClear}>
|
<Button round color="translucent" size="smaller" ariaLabel={lang('AriaToDoCancel')} onClick={onClear}>
|
||||||
<Icon name="close" />
|
<Icon name="close" />
|
||||||
</Button>
|
</Button>
|
||||||
<div className="modal-title">{lang(title)}</div>
|
<div className="modal-title">{lang(modalTitle)}</div>
|
||||||
<Button
|
<Button
|
||||||
color="primary"
|
color="primary"
|
||||||
size="smaller"
|
size="smaller"
|
||||||
|
|||||||
@ -784,10 +784,6 @@ const ActionMessageText = ({
|
|||||||
case 'suggestedPostRefund': {
|
case 'suggestedPostRefund': {
|
||||||
const { payerInitiated } = action;
|
const { payerInitiated } = action;
|
||||||
|
|
||||||
const replyMessage = message.replyInfo?.type === 'message' && message.replyInfo.replyToMsgId
|
|
||||||
? selectChatMessage(global, chatId, message.replyInfo.replyToMsgId)
|
|
||||||
: undefined;
|
|
||||||
|
|
||||||
const postSender = replyMessage ? selectSender(global, replyMessage) : sender;
|
const postSender = replyMessage ? selectSender(global, replyMessage) : sender;
|
||||||
const postSenderTitle = postSender && getPeerTitle(lang, postSender);
|
const postSenderTitle = postSender && getPeerTitle(lang, postSender);
|
||||||
const postSenderLink = renderPeerLink(postSender?.id, postSenderTitle || userFallbackText, asPreview);
|
const postSenderLink = renderPeerLink(postSender?.id, postSenderTitle || userFallbackText, asPreview);
|
||||||
@ -822,9 +818,6 @@ const ActionMessageText = ({
|
|||||||
const { isRejected, isBalanceTooLow, rejectComment } = action;
|
const { isRejected, isBalanceTooLow, rejectComment } = action;
|
||||||
|
|
||||||
if (isRejected) {
|
if (isRejected) {
|
||||||
const senderTitle = sender && getPeerTitle(lang, sender);
|
|
||||||
const senderLink = renderPeerLink(sender?.id, senderTitle || userFallbackText, asPreview);
|
|
||||||
|
|
||||||
return translateWithYou(
|
return translateWithYou(
|
||||||
lang,
|
lang,
|
||||||
rejectComment ? 'SuggestedPostRejectedWithReason' : 'SuggestedPostRejected',
|
rejectComment ? 'SuggestedPostRejectedWithReason' : 'SuggestedPostRejected',
|
||||||
@ -835,10 +828,6 @@ const ActionMessageText = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (isBalanceTooLow) {
|
if (isBalanceTooLow) {
|
||||||
const replyMessage = message.replyInfo?.type === 'message' && message.replyInfo.replyToMsgId
|
|
||||||
? selectChatMessage(global, chatId, message.replyInfo.replyToMsgId)
|
|
||||||
: undefined;
|
|
||||||
|
|
||||||
const replyMessageSender = replyMessage ? selectSender(global, replyMessage) : sender;
|
const replyMessageSender = replyMessage ? selectSender(global, replyMessage) : sender;
|
||||||
const replyPeerTitle = replyMessageSender && getPeerTitle(lang, replyMessageSender);
|
const replyPeerTitle = replyMessageSender && getPeerTitle(lang, replyMessageSender);
|
||||||
const userLink = renderPeerLink(replyMessageSender?.id, replyPeerTitle || userFallbackText, asPreview);
|
const userLink = renderPeerLink(replyMessageSender?.id, replyPeerTitle || userFallbackText, asPreview);
|
||||||
|
|||||||
@ -42,11 +42,11 @@ function SpeedingDiamond({ onMouseMove }: OwnProps) {
|
|||||||
if (!isAnimating) return false;
|
if (!isAnimating) return false;
|
||||||
|
|
||||||
const t = Math.min((Date.now() - startAt) / SLOWDOWN_DURATION, 1);
|
const t = Math.min((Date.now() - startAt) / SLOWDOWN_DURATION, 1);
|
||||||
const speed = (MAX_SPEED - MIN_SPEED) * (1 - transition(t));
|
const newSpeed = (MAX_SPEED - MIN_SPEED) * (1 - transition(t));
|
||||||
|
|
||||||
setSpeed(speed);
|
setSpeed(newSpeed);
|
||||||
|
|
||||||
isAnimating = t < 1 && speed > 1;
|
isAnimating = t < 1 && newSpeed > 1;
|
||||||
|
|
||||||
return isAnimating;
|
return isAnimating;
|
||||||
}, requestMutation);
|
}, requestMutation);
|
||||||
|
|||||||
@ -136,10 +136,10 @@ const GiftModalResaleScreen: FC<OwnProps & StateProps> = ({
|
|||||||
preloadBackwards={RESALE_GIFTS_LIMIT}
|
preloadBackwards={RESALE_GIFTS_LIMIT}
|
||||||
scrollContainerClosest={`.${styles.resaleScreenRoot}`}
|
scrollContainerClosest={`.${styles.resaleScreenRoot}`}
|
||||||
>
|
>
|
||||||
{resellGifts?.map((gift) => (
|
{resellGifts?.map((g) => (
|
||||||
<GiftItemStar
|
<GiftItemStar
|
||||||
key={gift.id}
|
key={g.id}
|
||||||
gift={gift}
|
gift={g}
|
||||||
observeIntersection={observe}
|
observeIntersection={observe}
|
||||||
isResale
|
isResale
|
||||||
onClick={onGiftClick}
|
onClick={onGiftClick}
|
||||||
|
|||||||
@ -276,7 +276,7 @@ const GiftResaleFilters: FC<StateProps & OwnProps> = ({
|
|||||||
|
|
||||||
const handleModelMenuItemClick = useLastCallback((attribute: ApiStarGiftAttributeModel) => {
|
const handleModelMenuItemClick = useLastCallback((attribute: ApiStarGiftAttributeModel) => {
|
||||||
if (!counters) return;
|
if (!counters) return;
|
||||||
const attributes = filter.modelAttributes || [];
|
const modelAttributes = filter.modelAttributes || [];
|
||||||
const modelAttribute
|
const modelAttribute
|
||||||
= counters.find((counter): counter is ApiStarGiftAttributeCounter<StarGiftAttributeIdModel> =>
|
= counters.find((counter): counter is ApiStarGiftAttributeCounter<StarGiftAttributeIdModel> =>
|
||||||
counter.attribute.type === 'model' && counter.attribute.documentId === attribute.sticker.id,
|
counter.attribute.type === 'model' && counter.attribute.documentId === attribute.sticker.id,
|
||||||
@ -284,10 +284,10 @@ const GiftResaleFilters: FC<StateProps & OwnProps> = ({
|
|||||||
|
|
||||||
if (!modelAttribute) return;
|
if (!modelAttribute) return;
|
||||||
|
|
||||||
const isActive = attributes.some((item) => item.documentId === modelAttribute.documentId);
|
const isActive = modelAttributes.some((item) => item.documentId === modelAttribute.documentId);
|
||||||
const updatedAttributes = isActive
|
const updatedAttributes = isActive
|
||||||
? attributes.filter((item) => item.documentId !== modelAttribute.documentId)
|
? modelAttributes.filter((item) => item.documentId !== modelAttribute.documentId)
|
||||||
: [...attributes, modelAttribute];
|
: [...modelAttributes, modelAttribute];
|
||||||
updateResaleGiftsFilter({ filter: {
|
updateResaleGiftsFilter({ filter: {
|
||||||
...filter,
|
...filter,
|
||||||
modelAttributes: updatedAttributes,
|
modelAttributes: updatedAttributes,
|
||||||
@ -296,7 +296,7 @@ const GiftResaleFilters: FC<StateProps & OwnProps> = ({
|
|||||||
|
|
||||||
const handlePatternMenuItemClick = useLastCallback((attribute: ApiStarGiftAttributePattern) => {
|
const handlePatternMenuItemClick = useLastCallback((attribute: ApiStarGiftAttributePattern) => {
|
||||||
if (!counters) return;
|
if (!counters) return;
|
||||||
const attributes = filter.patternAttributes || [];
|
const patternAttributes = filter.patternAttributes || [];
|
||||||
const patternAttribute = counters.find(
|
const patternAttribute = counters.find(
|
||||||
(counter): counter is ApiStarGiftAttributeCounter<ApiStarGiftAttributeIdPattern> =>
|
(counter): counter is ApiStarGiftAttributeCounter<ApiStarGiftAttributeIdPattern> =>
|
||||||
counter.attribute.type === 'pattern' && counter.attribute.documentId === attribute.sticker.id,
|
counter.attribute.type === 'pattern' && counter.attribute.documentId === attribute.sticker.id,
|
||||||
@ -304,10 +304,10 @@ const GiftResaleFilters: FC<StateProps & OwnProps> = ({
|
|||||||
|
|
||||||
if (!patternAttribute) return;
|
if (!patternAttribute) return;
|
||||||
|
|
||||||
const isActive = attributes.some((item) => item.documentId === patternAttribute.documentId);
|
const isActive = patternAttributes.some((item) => item.documentId === patternAttribute.documentId);
|
||||||
const updatedAttributes = isActive
|
const updatedAttributes = isActive
|
||||||
? attributes.filter((item) => item.documentId !== patternAttribute.documentId)
|
? patternAttributes.filter((item) => item.documentId !== patternAttribute.documentId)
|
||||||
: [...attributes, patternAttribute];
|
: [...patternAttributes, patternAttribute];
|
||||||
updateResaleGiftsFilter({ filter: {
|
updateResaleGiftsFilter({ filter: {
|
||||||
...filter,
|
...filter,
|
||||||
patternAttributes: updatedAttributes,
|
patternAttributes: updatedAttributes,
|
||||||
@ -316,7 +316,7 @@ const GiftResaleFilters: FC<StateProps & OwnProps> = ({
|
|||||||
|
|
||||||
const handleBackdropMenuItemClick = useLastCallback((attribute: ApiStarGiftAttributeBackdrop) => {
|
const handleBackdropMenuItemClick = useLastCallback((attribute: ApiStarGiftAttributeBackdrop) => {
|
||||||
if (!counters) return;
|
if (!counters) return;
|
||||||
const attributes = filter.backdropAttributes || [];
|
const backdropAttributes = filter.backdropAttributes || [];
|
||||||
const backdropAttribute = counters.find(
|
const backdropAttribute = counters.find(
|
||||||
(counter): counter is ApiStarGiftAttributeCounter<ApiStarGiftAttributeIdBackdrop> =>
|
(counter): counter is ApiStarGiftAttributeCounter<ApiStarGiftAttributeIdBackdrop> =>
|
||||||
counter.attribute.type === 'backdrop' && counter.attribute.backdropId === attribute.backdropId,
|
counter.attribute.type === 'backdrop' && counter.attribute.backdropId === attribute.backdropId,
|
||||||
@ -324,10 +324,10 @@ const GiftResaleFilters: FC<StateProps & OwnProps> = ({
|
|||||||
|
|
||||||
if (!backdropAttribute) return;
|
if (!backdropAttribute) return;
|
||||||
|
|
||||||
const isActive = attributes.some((item) => item.backdropId === backdropAttribute.backdropId);
|
const isActive = backdropAttributes.some((item) => item.backdropId === backdropAttribute.backdropId);
|
||||||
const updatedAttributes = isActive
|
const updatedAttributes = isActive
|
||||||
? attributes.filter((item) => item.backdropId !== backdropAttribute.backdropId)
|
? backdropAttributes.filter((item) => item.backdropId !== backdropAttribute.backdropId)
|
||||||
: [...attributes, backdropAttribute];
|
: [...backdropAttributes, backdropAttribute];
|
||||||
updateResaleGiftsFilter({ filter: {
|
updateResaleGiftsFilter({ filter: {
|
||||||
...filter,
|
...filter,
|
||||||
backdropAttributes: updatedAttributes,
|
backdropAttributes: updatedAttributes,
|
||||||
|
|||||||
@ -218,12 +218,12 @@ const GiftInfoModal = ({
|
|||||||
: gift?.ownerId === currentUserId || isSelfUnique
|
: gift?.ownerId === currentUserId || isSelfUnique
|
||||||
);
|
);
|
||||||
|
|
||||||
function getResalePrice(shouldPayInTon?: boolean) {
|
function getResalePrice(isInTon?: boolean) {
|
||||||
if (!isGiftUnique) return undefined;
|
if (!isGiftUnique) return undefined;
|
||||||
const amounts = gift.resellPrice;
|
const amounts = gift.resellPrice;
|
||||||
if (!amounts) return undefined;
|
if (!amounts) return undefined;
|
||||||
|
|
||||||
if (gift?.resaleTonOnly || shouldPayInTon) {
|
if (gift?.resaleTonOnly || isInTon) {
|
||||||
return amounts.find((amount) => amount.currency === TON_CURRENCY_CODE);
|
return amounts.find((amount) => amount.currency === TON_CURRENCY_CODE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -180,9 +180,9 @@ function MessageStatistics({
|
|||||||
|
|
||||||
<div ref={containerRef}>
|
<div ref={containerRef}>
|
||||||
{GRAPHS.map((graph) => {
|
{GRAPHS.map((graph) => {
|
||||||
const isReady = loadedCharts.current.has(graph) && !errorCharts.current.has(graph);
|
const isGraphReady = loadedCharts.current.has(graph) && !errorCharts.current.has(graph);
|
||||||
return (
|
return (
|
||||||
<div className={buildClassName(styles.graph, !isReady && styles.hidden)} />
|
<div className={buildClassName(styles.graph, !isGraphReady && styles.hidden)} />
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -211,9 +211,9 @@ const Statistics = ({
|
|||||||
|
|
||||||
<div ref={containerRef}>
|
<div ref={containerRef}>
|
||||||
{graphs.map((graph) => {
|
{graphs.map((graph) => {
|
||||||
const isReady = loadedCharts.current.has(graph) && !errorCharts.current.has(graph);
|
const isGraphReady = loadedCharts.current.has(graph) && !errorCharts.current.has(graph);
|
||||||
return (
|
return (
|
||||||
<div className={buildClassName(styles.graph, !isReady && styles.hidden)} />
|
<div className={buildClassName(styles.graph, !isGraphReady && styles.hidden)} />
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -185,9 +185,9 @@ function StoryStatistics({
|
|||||||
|
|
||||||
<div ref={containerRef}>
|
<div ref={containerRef}>
|
||||||
{GRAPHS.map((graph) => {
|
{GRAPHS.map((graph) => {
|
||||||
const isReady = loadedCharts.current.has(graph) && !errorCharts.current.has(graph);
|
const isGraphReady = loadedCharts.current.has(graph) && !errorCharts.current.has(graph);
|
||||||
return (
|
return (
|
||||||
<div className={buildClassName(styles.graph, !isReady && styles.hidden)} />
|
<div className={buildClassName(styles.graph, !isGraphReady && styles.hidden)} />
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -9,7 +9,7 @@ import type {
|
|||||||
} from '../../types';
|
} from '../../types';
|
||||||
import { PaymentStep } from '../../../types';
|
import { PaymentStep } from '../../../types';
|
||||||
|
|
||||||
import { DEBUG_PAYMENT_SMART_GLOCAL } from '../../../config';
|
import { DEBUG_PAYMENT_SMART_GLOCAL, STARS_CURRENCY_CODE, TON_CURRENCY_CODE } from '../../../config';
|
||||||
import { getCurrentTabId } from '../../../util/establishMultitabRole';
|
import { getCurrentTabId } from '../../../util/establishMultitabRole';
|
||||||
import * as langProvider from '../../../util/oldLangProvider';
|
import * as langProvider from '../../../util/oldLangProvider';
|
||||||
import { getStripeError } from '../../../util/payments/stripe';
|
import { getStripeError } from '../../../util/payments/stripe';
|
||||||
@ -1085,13 +1085,14 @@ async function payInputStarInvoice<T extends GlobalState>(
|
|||||||
...[tabId = getCurrentTabId()]: TabArgs<T>
|
...[tabId = getCurrentTabId()]: TabArgs<T>
|
||||||
) {
|
) {
|
||||||
const actions = getActions();
|
const actions = getActions();
|
||||||
const isTon = inputInvoice.type === 'stargiftResale' && inputInvoice.currency === 'TON';
|
const isTon = inputInvoice.type === 'stargiftResale' && inputInvoice.currency === TON_CURRENCY_CODE;
|
||||||
const balance = isTon ? global.ton?.balance : global.stars?.balance;
|
const balance = isTon ? global.ton?.balance : global.stars?.balance;
|
||||||
|
const currency = isTon ? TON_CURRENCY_CODE : STARS_CURRENCY_CODE;
|
||||||
|
|
||||||
if (balance === undefined) return;
|
if (balance === undefined) return;
|
||||||
|
|
||||||
if (balance.amount < price) {
|
if (balance.amount < price) {
|
||||||
actions.openStarsBalanceModal({ currency: isTon ? 'TON' : 'XTR', tabId });
|
actions.openStarsBalanceModal({ currency, tabId });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1126,12 +1127,10 @@ async function payInputStarInvoice<T extends GlobalState>(
|
|||||||
|
|
||||||
const formPrice = form.invoice.totalAmount;
|
const formPrice = form.invoice.totalAmount;
|
||||||
if (formPrice !== price) {
|
if (formPrice !== price) {
|
||||||
const isTon = inputInvoice.type === 'stargiftResale' && inputInvoice.currency === 'TON';
|
|
||||||
|
|
||||||
actions.openPriceConfirmModal({
|
actions.openPriceConfirmModal({
|
||||||
originalAmount: price,
|
originalAmount: price,
|
||||||
newAmount: formPrice,
|
newAmount: formPrice,
|
||||||
currency: isTon ? 'TON' : 'XTR',
|
currency,
|
||||||
directInfo: {
|
directInfo: {
|
||||||
inputInvoice,
|
inputInvoice,
|
||||||
formId: form.formId,
|
formId: form.formId,
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import { useEffect } from '../lib/teact/teact';
|
|||||||
import { ApiMediaFormat } from '../api/types';
|
import { ApiMediaFormat } from '../api/types';
|
||||||
|
|
||||||
import { selectIsSynced } from '../global/selectors';
|
import { selectIsSynced } from '../global/selectors';
|
||||||
|
import { IS_PROGRESSIVE_SUPPORTED } from '../util/browser/windowEnvironment';
|
||||||
import * as mediaLoader from '../util/mediaLoader';
|
import * as mediaLoader from '../util/mediaLoader';
|
||||||
import useSelector from './data/useSelector';
|
import useSelector from './data/useSelector';
|
||||||
import useForceUpdate from './useForceUpdate';
|
import useForceUpdate from './useForceUpdate';
|
||||||
@ -13,7 +14,11 @@ const useMedia = (
|
|||||||
mediaFormat = ApiMediaFormat.BlobUrl,
|
mediaFormat = ApiMediaFormat.BlobUrl,
|
||||||
delay?: number | false,
|
delay?: number | false,
|
||||||
) => {
|
) => {
|
||||||
const mediaData = mediaHash ? mediaLoader.getFromMemory(mediaHash) : undefined;
|
const isStreaming = IS_PROGRESSIVE_SUPPORTED && mediaFormat === ApiMediaFormat.Progressive;
|
||||||
|
const mediaData = mediaHash
|
||||||
|
? (isStreaming ? mediaLoader.getProgressiveUrl(mediaHash)
|
||||||
|
: mediaLoader.getFromMemory(mediaHash)) : undefined;
|
||||||
|
|
||||||
const forceUpdate = useForceUpdate();
|
const forceUpdate = useForceUpdate();
|
||||||
const isSynced = useSelector(selectIsSynced);
|
const isSynced = useSelector(selectIsSynced);
|
||||||
|
|
||||||
|
|||||||
@ -23,8 +23,11 @@ export default function useMediaWithLoadProgress(
|
|||||||
delay?: number | false,
|
delay?: number | false,
|
||||||
isHtmlAllowed = false,
|
isHtmlAllowed = false,
|
||||||
) {
|
) {
|
||||||
const mediaData = mediaHash ? mediaLoader.getFromMemory(mediaHash) : undefined;
|
|
||||||
const isStreaming = IS_PROGRESSIVE_SUPPORTED && mediaFormat === ApiMediaFormat.Progressive;
|
const isStreaming = IS_PROGRESSIVE_SUPPORTED && mediaFormat === ApiMediaFormat.Progressive;
|
||||||
|
const mediaData = mediaHash
|
||||||
|
? (isStreaming ? mediaLoader.getProgressiveUrl(mediaHash)
|
||||||
|
: mediaLoader.getFromMemory(mediaHash)) : undefined;
|
||||||
|
|
||||||
const forceUpdate = useForceUpdate();
|
const forceUpdate = useForceUpdate();
|
||||||
const isSynced = useSelector(selectIsSynced);
|
const isSynced = useSelector(selectIsSynced);
|
||||||
const id = useUniqueId();
|
const id = useUniqueId();
|
||||||
|
|||||||
@ -9,7 +9,7 @@ import createMockedChatBannedRights from './createMockedChatBannedRights';
|
|||||||
import { MOCK_STARTING_DATE } from './MockTypes';
|
import { MOCK_STARTING_DATE } from './MockTypes';
|
||||||
|
|
||||||
export default function createMockedChannel(id: string, mockData: MockTypes): Api.Channel {
|
export default function createMockedChannel(id: string, mockData: MockTypes): Api.Channel {
|
||||||
const channel = mockData.channels.find((channel) => channel.id === id);
|
const channel = mockData.channels.find((c) => c.id === id);
|
||||||
|
|
||||||
if (!channel) throw Error('No such channel ' + id);
|
if (!channel) throw Error('No such channel ' + id);
|
||||||
|
|
||||||
|
|||||||
@ -7,7 +7,7 @@ import Api from '../../tl/api';
|
|||||||
import { MOCK_STARTING_DATE } from './MockTypes';
|
import { MOCK_STARTING_DATE } from './MockTypes';
|
||||||
|
|
||||||
export default function createMockedChat(id: string, mockData: MockTypes): Api.Chat {
|
export default function createMockedChat(id: string, mockData: MockTypes): Api.Chat {
|
||||||
const chat = mockData.chats.find((chat) => chat.id === id);
|
const chat = mockData.chats.find((c) => c.id === id);
|
||||||
|
|
||||||
if (!chat) throw Error('No such chat ' + id);
|
if (!chat) throw Error('No such chat ' + id);
|
||||||
|
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import type { MockTypes } from './MockTypes';
|
|||||||
import Api from '../../tl/api';
|
import Api from '../../tl/api';
|
||||||
|
|
||||||
export default function createMockedChatAdminRights(chatId: string, mockData: MockTypes) {
|
export default function createMockedChatAdminRights(chatId: string, mockData: MockTypes) {
|
||||||
const channel = mockData.channels.find((channel) => channel.id === chatId);
|
const channel = mockData.channels.find((c) => c.id === chatId);
|
||||||
|
|
||||||
if (!channel) throw Error('No such channel ' + chatId);
|
if (!channel) throw Error('No such channel ' + chatId);
|
||||||
|
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import type { MockTypes } from './MockTypes';
|
|||||||
import Api from '../../tl/api';
|
import Api from '../../tl/api';
|
||||||
|
|
||||||
export default function createMockedChatBannedRights(chatId: string, mockData: MockTypes) {
|
export default function createMockedChatBannedRights(chatId: string, mockData: MockTypes) {
|
||||||
const channel = mockData.channels.find((channel) => channel.id === chatId);
|
const channel = mockData.channels.find((c) => c.id === chatId);
|
||||||
|
|
||||||
if (!channel) throw Error('No such channel ' + chatId);
|
if (!channel) throw Error('No such channel ' + chatId);
|
||||||
|
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import Api from '../../tl/api';
|
|||||||
import createMockedTypeInputPeer from './createMockedTypeInputPeer';
|
import createMockedTypeInputPeer from './createMockedTypeInputPeer';
|
||||||
|
|
||||||
export default function createMockedDialogFilter(id: number, mockData: MockTypes) {
|
export default function createMockedDialogFilter(id: number, mockData: MockTypes) {
|
||||||
const dialogFilter = mockData.dialogFilters.find((dialogFilter) => dialogFilter.id === id);
|
const dialogFilter = mockData.dialogFilters.find((f) => f.id === id);
|
||||||
|
|
||||||
if (!dialogFilter) throw Error('No such dialog filter ' + id);
|
if (!dialogFilter) throw Error('No such dialog filter ' + id);
|
||||||
|
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import type { MockTypes } from './MockTypes';
|
|||||||
import Api from '../../tl/api';
|
import Api from '../../tl/api';
|
||||||
|
|
||||||
export default function createMockedTypeInputPeer(id: string, mockData: MockTypes): Api.TypeInputPeer {
|
export default function createMockedTypeInputPeer(id: string, mockData: MockTypes): Api.TypeInputPeer {
|
||||||
const user = mockData.users.find((user) => user.id === id);
|
const user = mockData.users.find((u) => u.id === id);
|
||||||
if (user) {
|
if (user) {
|
||||||
return new Api.InputPeerUser({
|
return new Api.InputPeerUser({
|
||||||
userId: BigInt(id),
|
userId: BigInt(id),
|
||||||
@ -13,14 +13,14 @@ export default function createMockedTypeInputPeer(id: string, mockData: MockType
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const chat = mockData.chats.find((chat) => chat.id === id);
|
const chat = mockData.chats.find((c) => c.id === id);
|
||||||
if (chat) {
|
if (chat) {
|
||||||
return new Api.InputPeerChat({
|
return new Api.InputPeerChat({
|
||||||
chatId: BigInt(id),
|
chatId: BigInt(id),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const channel = mockData.channels.find((channel) => channel.id === id);
|
const channel = mockData.channels.find((c) => c.id === id);
|
||||||
if (channel) {
|
if (channel) {
|
||||||
return new Api.InputPeerChannel({
|
return new Api.InputPeerChannel({
|
||||||
channelId: BigInt(Number(id) + 1000000000),
|
channelId: BigInt(Number(id) + 1000000000),
|
||||||
|
|||||||
@ -5,21 +5,21 @@ import type { MockTypes } from './MockTypes';
|
|||||||
import Api from '../../tl/api';
|
import Api from '../../tl/api';
|
||||||
|
|
||||||
export default function createMockedTypePeer(id: string, mockData: MockTypes): Api.TypePeer {
|
export default function createMockedTypePeer(id: string, mockData: MockTypes): Api.TypePeer {
|
||||||
const user = mockData.users.find((user) => user.id === id);
|
const user = mockData.users.find((u) => u.id === id);
|
||||||
if (user) {
|
if (user) {
|
||||||
return new Api.PeerUser({
|
return new Api.PeerUser({
|
||||||
userId: BigInt(id),
|
userId: BigInt(id),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const chat = mockData.chats.find((chat) => chat.id === id);
|
const chat = mockData.chats.find((c) => c.id === id);
|
||||||
if (chat) {
|
if (chat) {
|
||||||
return new Api.PeerChat({
|
return new Api.PeerChat({
|
||||||
chatId: BigInt(id),
|
chatId: BigInt(id),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const channel = mockData.channels.find((channel) => channel.id === id);
|
const channel = mockData.channels.find((c) => c.id === id);
|
||||||
if (channel) {
|
if (channel) {
|
||||||
return new Api.PeerChannel({
|
return new Api.PeerChannel({
|
||||||
channelId: BigInt(Number(id) + 1000000000),
|
channelId: BigInt(Number(id) + 1000000000),
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import type { MockTypes } from './MockTypes';
|
|||||||
import Api from '../../tl/api';
|
import Api from '../../tl/api';
|
||||||
|
|
||||||
export default function createMockedUser(id: string, mockData: MockTypes): Api.User {
|
export default function createMockedUser(id: string, mockData: MockTypes): Api.User {
|
||||||
const user = mockData.users.find((user) => user.id === id);
|
const user = mockData.users.find((u) => u.id === id);
|
||||||
|
|
||||||
if (!user) throw Error('No such user ' + id);
|
if (!user) throw Error('No such user ' + id);
|
||||||
|
|
||||||
|
|||||||
@ -118,7 +118,6 @@ export function removeCallback(url: string, callbackUniqueId: string) {
|
|||||||
export function getProgressiveUrl(url: string) {
|
export function getProgressiveUrl(url: string) {
|
||||||
const base = new URL(`${PROGRESSIVE_URL_PREFIX}${url}`, window.location.href);
|
const base = new URL(`${PROGRESSIVE_URL_PREFIX}${url}`, window.location.href);
|
||||||
if (ACCOUNT_SLOT) base.searchParams.set('account', ACCOUNT_SLOT.toString());
|
if (ACCOUNT_SLOT) base.searchParams.set('account', ACCOUNT_SLOT.toString());
|
||||||
memoryCache.set(url, base.href); // Needed for hooks to detect document as already loaded and apply URL
|
|
||||||
return base.href;
|
return base.href;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -133,7 +133,7 @@ export function fastRaf(callback: NoneToVoidFunction, withTimeoutFallback = fals
|
|||||||
|
|
||||||
if (fastRafCallbacks) {
|
if (fastRafCallbacks) {
|
||||||
const currentCallbacks = fastRafCallbacks;
|
const currentCallbacks = fastRafCallbacks;
|
||||||
currentTimeoutCallbacks.forEach((callback) => currentCallbacks.delete(callback));
|
currentTimeoutCallbacks.forEach((c) => currentCallbacks.delete(c));
|
||||||
}
|
}
|
||||||
fastRafFallbackCallbacks = undefined;
|
fastRafFallbackCallbacks = undefined;
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user