Media Viewer: Fix document real size (#6177)

This commit is contained in:
zubiden 2025-09-19 14:35:13 +02:00 committed by Alexander Zinchuk
parent eb7dff433c
commit c7bf1ebd72
32 changed files with 163 additions and 74 deletions

View File

@ -136,6 +136,7 @@ export default tseslint.config(
disallowTypeAnnotations: false,
},
],
'@typescript-eslint/no-shadow': 'error',
'@typescript-eslint/no-unsafe-argument': 'off',
'@typescript-eslint/no-unsafe-assignment': 'off',
'@typescript-eslint/no-unsafe-call': 'off',

View File

@ -153,7 +153,7 @@ export interface ApiDocument {
previewPhotoSizes?: ApiPhotoSize[];
previewBlobUrl?: string;
innerMediaType?: 'photo' | 'video';
mediaSize?: ApiDimensions & { fromDocumentAttribute?: boolean };
mediaSize?: ApiDimensions & { fromDocumentAttribute?: boolean; fromPreload?: true };
}
export interface ApiContact {

View File

@ -15,6 +15,7 @@ import {
} from '../../global/helpers';
import { isIpRevealingMedia } from '../../util/media/ipRevealingMedia';
import { getDocumentExtension, getDocumentHasPreview } from './helpers/documentInfo';
import { preloadDocumentMedia } from './helpers/preloadDocumentMedia';
import useFlag from '../../hooks/useFlag';
import { useIsIntersecting } from '../../hooks/useIntersectionObserver';
@ -119,10 +120,26 @@ const Document = ({
const localBlobUrl = hasPreview ? document.previewBlobUrl : undefined;
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;
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(() => {
downloadMedia({ media: document, originMessage: message });
});

View File

@ -225,21 +225,21 @@ const PremiumProgress: FC<OwnProps> = ({
const renderProgressLayer = (
isPositive: boolean,
layerProgress: number,
currentProgress: number,
layerClassName?: string,
disableTransition?: boolean,
) => {
const className = isPositive ? styles.positiveProgress : styles.negativeProgress;
const typeClass = isPositive ? styles.positiveProgress : styles.negativeProgress;
const progressVar = '--layer-progress';
return (
<div
className={buildClassName(
className,
typeClass,
layerClassName,
disableTransition && styles.noTransition,
)}
style={`${progressVar}: ${layerProgress}`}
style={`${progressVar}: ${currentProgress}`}
>
<div className={styles.left}>
<span>{displayLeftText}</span>

View 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);
}

View File

@ -64,6 +64,7 @@ type StateProps = {
enum ContentType {
Main,
// eslint-disable-next-line @typescript-eslint/no-shadow
Settings,
Archived,

View File

@ -257,14 +257,14 @@ const LeftMainHeader: FC<OwnProps & StateProps> = ({
}, [globalSearchChatId, selectedSearchDate]);
const version = useMemo(() => {
let version = '';
let fullVersion = '';
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]);
return (

View File

@ -109,7 +109,7 @@ export const useMediaProps = ({
}
if (isDocument) {
return media.mediaSize!;
return media.mediaSize || FALLBACK_DIMENSIONS;
}
if (isPhoto) {

View File

@ -145,12 +145,15 @@ type StateProps = {
};
enum Content {
// eslint-disable-next-line @typescript-eslint/no-shadow
Loading,
Restricted,
StarsRequired,
PremiumRequired,
AccountInfo,
// eslint-disable-next-line @typescript-eslint/no-shadow
ContactGreeting,
// eslint-disable-next-line @typescript-eslint/no-shadow
NoMessages,
MessageList,
}
@ -180,6 +183,7 @@ const MessageList: FC<OwnProps & StateProps> = ({
isChannelWithAvatars,
canPost,
isSynced,
// eslint-disable-next-line @typescript-eslint/no-shadow
isChatMonoforum,
isReady,
isChatWithSelf,

View File

@ -478,8 +478,8 @@ const AttachmentModal: FC<OwnProps & StateProps> = ({
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 everyAudio = renderingAttachments.every((a) => SUPPORTED_AUDIO_CONTENT_TYPES.has(a.mimeType));
const hasAnyPhoto = renderingAttachments.some((a) => SUPPORTED_PHOTO_CONTENT_TYPES.has(a.mimeType));
return [everyPhoto, everyVideo, everyAudio, hasAnyPhoto];
const anyPhoto = renderingAttachments.some((a) => SUPPORTED_PHOTO_CONTENT_TYPES.has(a.mimeType));
return [everyPhoto, everyVideo, everyAudio, anyPhoto];
}, [renderingAttachments, isQuickGallery]);
const hasAnySpoilerable = useMemo(() => {

View File

@ -287,13 +287,14 @@ const ToDoListModal = ({
});
function renderHeader() {
const title = isAddTaskMode ? 'TitleAppendToDoList' : editingMessage ? 'TitleEditToDoList' : 'TitleNewToDoList';
const modalTitle = isAddTaskMode ? 'TitleAppendToDoList'
: editingMessage ? 'TitleEditToDoList' : 'TitleNewToDoList';
return (
<div className="modal-header-condensed">
<Button round color="translucent" size="smaller" ariaLabel={lang('AriaToDoCancel')} onClick={onClear}>
<Icon name="close" />
</Button>
<div className="modal-title">{lang(title)}</div>
<div className="modal-title">{lang(modalTitle)}</div>
<Button
color="primary"
size="smaller"

View File

@ -784,10 +784,6 @@ const ActionMessageText = ({
case 'suggestedPostRefund': {
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 postSenderTitle = postSender && getPeerTitle(lang, postSender);
const postSenderLink = renderPeerLink(postSender?.id, postSenderTitle || userFallbackText, asPreview);
@ -822,9 +818,6 @@ const ActionMessageText = ({
const { isRejected, isBalanceTooLow, rejectComment } = action;
if (isRejected) {
const senderTitle = sender && getPeerTitle(lang, sender);
const senderLink = renderPeerLink(sender?.id, senderTitle || userFallbackText, asPreview);
return translateWithYou(
lang,
rejectComment ? 'SuggestedPostRejectedWithReason' : 'SuggestedPostRejected',
@ -835,10 +828,6 @@ const ActionMessageText = ({
}
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 replyPeerTitle = replyMessageSender && getPeerTitle(lang, replyMessageSender);
const userLink = renderPeerLink(replyMessageSender?.id, replyPeerTitle || userFallbackText, asPreview);

View File

@ -42,11 +42,11 @@ function SpeedingDiamond({ onMouseMove }: OwnProps) {
if (!isAnimating) return false;
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;
}, requestMutation);

View File

@ -136,10 +136,10 @@ const GiftModalResaleScreen: FC<OwnProps & StateProps> = ({
preloadBackwards={RESALE_GIFTS_LIMIT}
scrollContainerClosest={`.${styles.resaleScreenRoot}`}
>
{resellGifts?.map((gift) => (
{resellGifts?.map((g) => (
<GiftItemStar
key={gift.id}
gift={gift}
key={g.id}
gift={g}
observeIntersection={observe}
isResale
onClick={onGiftClick}

View File

@ -276,7 +276,7 @@ const GiftResaleFilters: FC<StateProps & OwnProps> = ({
const handleModelMenuItemClick = useLastCallback((attribute: ApiStarGiftAttributeModel) => {
if (!counters) return;
const attributes = filter.modelAttributes || [];
const modelAttributes = filter.modelAttributes || [];
const modelAttribute
= counters.find((counter): counter is ApiStarGiftAttributeCounter<StarGiftAttributeIdModel> =>
counter.attribute.type === 'model' && counter.attribute.documentId === attribute.sticker.id,
@ -284,10 +284,10 @@ const GiftResaleFilters: FC<StateProps & OwnProps> = ({
if (!modelAttribute) return;
const isActive = attributes.some((item) => item.documentId === modelAttribute.documentId);
const isActive = modelAttributes.some((item) => item.documentId === modelAttribute.documentId);
const updatedAttributes = isActive
? attributes.filter((item) => item.documentId !== modelAttribute.documentId)
: [...attributes, modelAttribute];
? modelAttributes.filter((item) => item.documentId !== modelAttribute.documentId)
: [...modelAttributes, modelAttribute];
updateResaleGiftsFilter({ filter: {
...filter,
modelAttributes: updatedAttributes,
@ -296,7 +296,7 @@ const GiftResaleFilters: FC<StateProps & OwnProps> = ({
const handlePatternMenuItemClick = useLastCallback((attribute: ApiStarGiftAttributePattern) => {
if (!counters) return;
const attributes = filter.patternAttributes || [];
const patternAttributes = filter.patternAttributes || [];
const patternAttribute = counters.find(
(counter): counter is ApiStarGiftAttributeCounter<ApiStarGiftAttributeIdPattern> =>
counter.attribute.type === 'pattern' && counter.attribute.documentId === attribute.sticker.id,
@ -304,10 +304,10 @@ const GiftResaleFilters: FC<StateProps & OwnProps> = ({
if (!patternAttribute) return;
const isActive = attributes.some((item) => item.documentId === patternAttribute.documentId);
const isActive = patternAttributes.some((item) => item.documentId === patternAttribute.documentId);
const updatedAttributes = isActive
? attributes.filter((item) => item.documentId !== patternAttribute.documentId)
: [...attributes, patternAttribute];
? patternAttributes.filter((item) => item.documentId !== patternAttribute.documentId)
: [...patternAttributes, patternAttribute];
updateResaleGiftsFilter({ filter: {
...filter,
patternAttributes: updatedAttributes,
@ -316,7 +316,7 @@ const GiftResaleFilters: FC<StateProps & OwnProps> = ({
const handleBackdropMenuItemClick = useLastCallback((attribute: ApiStarGiftAttributeBackdrop) => {
if (!counters) return;
const attributes = filter.backdropAttributes || [];
const backdropAttributes = filter.backdropAttributes || [];
const backdropAttribute = counters.find(
(counter): counter is ApiStarGiftAttributeCounter<ApiStarGiftAttributeIdBackdrop> =>
counter.attribute.type === 'backdrop' && counter.attribute.backdropId === attribute.backdropId,
@ -324,10 +324,10 @@ const GiftResaleFilters: FC<StateProps & OwnProps> = ({
if (!backdropAttribute) return;
const isActive = attributes.some((item) => item.backdropId === backdropAttribute.backdropId);
const isActive = backdropAttributes.some((item) => item.backdropId === backdropAttribute.backdropId);
const updatedAttributes = isActive
? attributes.filter((item) => item.backdropId !== backdropAttribute.backdropId)
: [...attributes, backdropAttribute];
? backdropAttributes.filter((item) => item.backdropId !== backdropAttribute.backdropId)
: [...backdropAttributes, backdropAttribute];
updateResaleGiftsFilter({ filter: {
...filter,
backdropAttributes: updatedAttributes,

View File

@ -218,12 +218,12 @@ const GiftInfoModal = ({
: gift?.ownerId === currentUserId || isSelfUnique
);
function getResalePrice(shouldPayInTon?: boolean) {
function getResalePrice(isInTon?: boolean) {
if (!isGiftUnique) return undefined;
const amounts = gift.resellPrice;
if (!amounts) return undefined;
if (gift?.resaleTonOnly || shouldPayInTon) {
if (gift?.resaleTonOnly || isInTon) {
return amounts.find((amount) => amount.currency === TON_CURRENCY_CODE);
}

View File

@ -180,9 +180,9 @@ function MessageStatistics({
<div ref={containerRef}>
{GRAPHS.map((graph) => {
const isReady = loadedCharts.current.has(graph) && !errorCharts.current.has(graph);
const isGraphReady = loadedCharts.current.has(graph) && !errorCharts.current.has(graph);
return (
<div className={buildClassName(styles.graph, !isReady && styles.hidden)} />
<div className={buildClassName(styles.graph, !isGraphReady && styles.hidden)} />
);
})}
</div>

View File

@ -211,9 +211,9 @@ const Statistics = ({
<div ref={containerRef}>
{graphs.map((graph) => {
const isReady = loadedCharts.current.has(graph) && !errorCharts.current.has(graph);
const isGraphReady = loadedCharts.current.has(graph) && !errorCharts.current.has(graph);
return (
<div className={buildClassName(styles.graph, !isReady && styles.hidden)} />
<div className={buildClassName(styles.graph, !isGraphReady && styles.hidden)} />
);
})}
</div>

View File

@ -185,9 +185,9 @@ function StoryStatistics({
<div ref={containerRef}>
{GRAPHS.map((graph) => {
const isReady = loadedCharts.current.has(graph) && !errorCharts.current.has(graph);
const isGraphReady = loadedCharts.current.has(graph) && !errorCharts.current.has(graph);
return (
<div className={buildClassName(styles.graph, !isReady && styles.hidden)} />
<div className={buildClassName(styles.graph, !isGraphReady && styles.hidden)} />
);
})}
</div>

View File

@ -9,7 +9,7 @@ import type {
} 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 * as langProvider from '../../../util/oldLangProvider';
import { getStripeError } from '../../../util/payments/stripe';
@ -1085,13 +1085,14 @@ async function payInputStarInvoice<T extends GlobalState>(
...[tabId = getCurrentTabId()]: TabArgs<T>
) {
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 currency = isTon ? TON_CURRENCY_CODE : STARS_CURRENCY_CODE;
if (balance === undefined) return;
if (balance.amount < price) {
actions.openStarsBalanceModal({ currency: isTon ? 'TON' : 'XTR', tabId });
actions.openStarsBalanceModal({ currency, tabId });
return;
}
@ -1126,12 +1127,10 @@ async function payInputStarInvoice<T extends GlobalState>(
const formPrice = form.invoice.totalAmount;
if (formPrice !== price) {
const isTon = inputInvoice.type === 'stargiftResale' && inputInvoice.currency === 'TON';
actions.openPriceConfirmModal({
originalAmount: price,
newAmount: formPrice,
currency: isTon ? 'TON' : 'XTR',
currency,
directInfo: {
inputInvoice,
formId: form.formId,

View File

@ -3,6 +3,7 @@ import { useEffect } from '../lib/teact/teact';
import { ApiMediaFormat } from '../api/types';
import { selectIsSynced } from '../global/selectors';
import { IS_PROGRESSIVE_SUPPORTED } from '../util/browser/windowEnvironment';
import * as mediaLoader from '../util/mediaLoader';
import useSelector from './data/useSelector';
import useForceUpdate from './useForceUpdate';
@ -13,7 +14,11 @@ const useMedia = (
mediaFormat = ApiMediaFormat.BlobUrl,
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 isSynced = useSelector(selectIsSynced);

View File

@ -23,8 +23,11 @@ export default function useMediaWithLoadProgress(
delay?: number | false,
isHtmlAllowed = 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 isSynced = useSelector(selectIsSynced);
const id = useUniqueId();

View File

@ -9,7 +9,7 @@ import createMockedChatBannedRights from './createMockedChatBannedRights';
import { MOCK_STARTING_DATE } from './MockTypes';
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);

View File

@ -7,7 +7,7 @@ import Api from '../../tl/api';
import { MOCK_STARTING_DATE } from './MockTypes';
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);

View File

@ -3,7 +3,7 @@ import type { MockTypes } from './MockTypes';
import Api from '../../tl/api';
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);

View File

@ -3,7 +3,7 @@ import type { MockTypes } from './MockTypes';
import Api from '../../tl/api';
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);

View File

@ -4,7 +4,7 @@ import Api from '../../tl/api';
import createMockedTypeInputPeer from './createMockedTypeInputPeer';
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);

View File

@ -5,7 +5,7 @@ import type { MockTypes } from './MockTypes';
import Api from '../../tl/api';
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) {
return new Api.InputPeerUser({
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) {
return new Api.InputPeerChat({
chatId: BigInt(id),
});
}
const channel = mockData.channels.find((channel) => channel.id === id);
const channel = mockData.channels.find((c) => c.id === id);
if (channel) {
return new Api.InputPeerChannel({
channelId: BigInt(Number(id) + 1000000000),

View File

@ -5,21 +5,21 @@ import type { MockTypes } from './MockTypes';
import Api from '../../tl/api';
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) {
return new Api.PeerUser({
userId: BigInt(id),
});
}
const chat = mockData.chats.find((chat) => chat.id === id);
const chat = mockData.chats.find((c) => c.id === id);
if (chat) {
return new Api.PeerChat({
chatId: BigInt(id),
});
}
const channel = mockData.channels.find((channel) => channel.id === id);
const channel = mockData.channels.find((c) => c.id === id);
if (channel) {
return new Api.PeerChannel({
channelId: BigInt(Number(id) + 1000000000),

View File

@ -5,7 +5,7 @@ import type { MockTypes } from './MockTypes';
import Api from '../../tl/api';
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);

View File

@ -118,7 +118,6 @@ export function removeCallback(url: string, callbackUniqueId: string) {
export function getProgressiveUrl(url: string) {
const base = new URL(`${PROGRESSIVE_URL_PREFIX}${url}`, window.location.href);
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;
}

View File

@ -133,7 +133,7 @@ export function fastRaf(callback: NoneToVoidFunction, withTimeoutFallback = fals
if (fastRafCallbacks) {
const currentCallbacks = fastRafCallbacks;
currentTimeoutCallbacks.forEach((callback) => currentCallbacks.delete(callback));
currentTimeoutCallbacks.forEach((c) => currentCallbacks.delete(c));
}
fastRafFallbackCallbacks = undefined;