663 lines
20 KiB
TypeScript
663 lines
20 KiB
TypeScript
import React, {
|
|
FC, memo, useCallback, useEffect, useMemo, useRef, useState,
|
|
} from '../../lib/teact/teact';
|
|
import { withGlobal } from '../../lib/teact/teactn';
|
|
|
|
import { GlobalActions } from '../../global/types';
|
|
import {
|
|
ApiChat, ApiMediaFormat, ApiMessage, ApiUser,
|
|
} from '../../api/types';
|
|
import { MediaViewerOrigin } from '../../types';
|
|
|
|
import { ANIMATION_END_DELAY } from '../../config';
|
|
import { IS_IOS, IS_SINGLE_COLUMN_LAYOUT, IS_TOUCH_ENV } from '../../util/environment';
|
|
import {
|
|
AVATAR_FULL_DIMENSIONS,
|
|
MEDIA_VIEWER_MEDIA_QUERY,
|
|
calculateMediaViewerDimensions,
|
|
} from '../common/helpers/mediaDimensions';
|
|
import {
|
|
selectChat,
|
|
selectChatMessage,
|
|
selectChatMessages,
|
|
selectCurrentMediaSearch,
|
|
selectListedIds,
|
|
selectOutlyingIds,
|
|
selectScheduledMessage,
|
|
selectScheduledMessages,
|
|
selectUser,
|
|
} from '../../modules/selectors';
|
|
import {
|
|
getChatAvatarHash,
|
|
getChatMediaMessageIds,
|
|
getMessageMediaFilename,
|
|
getMessageMediaFormat,
|
|
getMessageMediaHash,
|
|
getMessageMediaThumbDataUri,
|
|
getMessagePhoto,
|
|
getMessageVideo,
|
|
getMessageWebPagePhoto,
|
|
getPhotoFullDimensions,
|
|
getVideoDimensions,
|
|
IDimensions,
|
|
} from '../../modules/helpers';
|
|
import { pick } from '../../util/iteratees';
|
|
import { captureEvents, SwipeDirection } from '../../util/captureEvents';
|
|
import captureEscKeyListener from '../../util/captureEscKeyListener';
|
|
import { stopCurrentAudio } from '../../util/audioPlayer';
|
|
import useForceUpdate from '../../hooks/useForceUpdate';
|
|
import useMedia from '../../hooks/useMedia';
|
|
import useMediaWithDownloadProgress from '../../hooks/useMediaWithDownloadProgress';
|
|
import useBlurSync from '../../hooks/useBlurSync';
|
|
import usePrevious from '../../hooks/usePrevious';
|
|
import { dispatchHeavyAnimationEvent } from '../../hooks/useHeavyAnimationCheck';
|
|
import { renderMessageText } from '../common/helpers/renderMessageText';
|
|
import { animateClosing, animateOpening } from './helpers/ghostAnimation';
|
|
import useLang from '../../hooks/useLang';
|
|
|
|
import Spinner from '../ui/Spinner';
|
|
import ShowTransition from '../ui/ShowTransition';
|
|
import Transition from '../ui/Transition';
|
|
import Button from '../ui/Button';
|
|
import SenderInfo from './SenderInfo';
|
|
import MediaViewerActions from './MediaViewerActions';
|
|
import MediaViewerFooter from './MediaViewerFooter';
|
|
import VideoPlayer from './VideoPlayer';
|
|
import ZoomControls from './ZoomControls';
|
|
import PanZoom from './PanZoom';
|
|
|
|
import './MediaViewer.scss';
|
|
|
|
type StateProps = {
|
|
chatId?: number;
|
|
threadId?: number;
|
|
messageId?: number;
|
|
senderId?: number;
|
|
origin?: MediaViewerOrigin;
|
|
avatarOwner?: ApiChat | ApiUser;
|
|
profilePhotoIndex?: number;
|
|
message?: ApiMessage;
|
|
chatMessages?: Record<number, ApiMessage>;
|
|
collectionIds?: number[];
|
|
animationLevel: 0 | 1 | 2;
|
|
};
|
|
|
|
type DispatchProps = Pick<GlobalActions, 'openMediaViewer' | 'closeMediaViewer' | 'openForwardMenu' | 'focusMessage'>;
|
|
|
|
const ANIMATION_DURATION = 350;
|
|
|
|
const MediaViewer: FC<StateProps & DispatchProps> = ({
|
|
chatId,
|
|
threadId,
|
|
messageId,
|
|
senderId,
|
|
origin,
|
|
avatarOwner,
|
|
profilePhotoIndex,
|
|
message,
|
|
chatMessages,
|
|
collectionIds,
|
|
openMediaViewer,
|
|
closeMediaViewer,
|
|
openForwardMenu,
|
|
focusMessage,
|
|
animationLevel,
|
|
}) => {
|
|
// eslint-disable-next-line no-null/no-null
|
|
const animationKey = useRef<number>(null);
|
|
const isOpen = Boolean(avatarOwner || messageId);
|
|
const webPagePhoto = message ? getMessageWebPagePhoto(message) : undefined;
|
|
const photo = message ? getMessagePhoto(message) : undefined;
|
|
const video = message ? getMessageVideo(message) : undefined;
|
|
const isWebPagePhoto = Boolean(webPagePhoto);
|
|
const isPhoto = Boolean(photo || webPagePhoto);
|
|
const isVideo = Boolean(video);
|
|
const isGif = video ? video.isGif : undefined;
|
|
const isFromSharedMedia = origin === MediaViewerOrigin.SharedMedia;
|
|
const isFromSearch = origin === MediaViewerOrigin.SearchResult;
|
|
const slideAnimation = animationLevel >= 1 ? 'mv-slide' : 'none';
|
|
const headerAnimation = animationLevel === 2 ? 'slide-fade' : 'none';
|
|
const isGhostAnimation = animationLevel === 2;
|
|
const fileName = avatarOwner
|
|
? `avatar${avatarOwner.id}-${profilePhotoIndex}.jpg`
|
|
: message && getMessageMediaFilename(message);
|
|
const prevSenderId = usePrevious<number | undefined>(senderId);
|
|
const [canPanZoomWrap, setCanPanZoomWrap] = useState(false);
|
|
const [isZoomed, setIsZoomed] = useState<boolean>(false);
|
|
const [zoomLevel, setZoomLevel] = useState<number>(1);
|
|
const [panDelta, setPanDelta] = useState({ x: 0, y: 0 });
|
|
const [isFooterHidden, setIsFooterHidden] = useState<boolean>(false);
|
|
|
|
const messageIds = useMemo(() => {
|
|
return isWebPagePhoto && messageId
|
|
? [messageId]
|
|
: getChatMediaMessageIds(chatMessages || {}, collectionIds || [], isFromSharedMedia);
|
|
}, [isWebPagePhoto, messageId, chatMessages, collectionIds, isFromSharedMedia]);
|
|
|
|
const selectedMediaMessageIndex = messageId ? messageIds.indexOf(messageId) : -1;
|
|
const isFirst = selectedMediaMessageIndex === 0 || selectedMediaMessageIndex === -1;
|
|
const isLast = selectedMediaMessageIndex === messageIds.length - 1 || selectedMediaMessageIndex === -1;
|
|
if (isOpen && (!prevSenderId || prevSenderId !== senderId || !animationKey.current)) {
|
|
animationKey.current = selectedMediaMessageIndex;
|
|
}
|
|
|
|
function getMediaHash(full?: boolean) {
|
|
if (avatarOwner && profilePhotoIndex !== undefined) {
|
|
const { photos } = avatarOwner;
|
|
return photos && photos[profilePhotoIndex]
|
|
? `photo${photos[profilePhotoIndex].id}?size=c`
|
|
: getChatAvatarHash(avatarOwner, full ? 'big' : 'normal');
|
|
}
|
|
|
|
return message && getMessageMediaHash(message, full ? 'viewerFull' : 'viewerPreview');
|
|
}
|
|
|
|
const blobUrlPictogram = useMedia(
|
|
message && (isFromSharedMedia || isFromSearch) && getMessageMediaHash(message, 'pictogram'),
|
|
undefined,
|
|
ApiMediaFormat.BlobUrl,
|
|
undefined,
|
|
isGhostAnimation && ANIMATION_DURATION,
|
|
);
|
|
const previewMediaHash = getMediaHash();
|
|
const blobUrlPreview = useMedia(
|
|
previewMediaHash,
|
|
undefined,
|
|
avatarOwner && previewMediaHash && previewMediaHash.startsWith('profilePhoto')
|
|
? ApiMediaFormat.DataUri
|
|
: ApiMediaFormat.BlobUrl,
|
|
undefined,
|
|
isGhostAnimation && ANIMATION_DURATION,
|
|
);
|
|
const { mediaData: fullMediaData, downloadProgress } = useMediaWithDownloadProgress(
|
|
getMediaHash(true),
|
|
undefined,
|
|
message && getMessageMediaFormat(message, 'viewerFull'),
|
|
undefined,
|
|
isGhostAnimation && ANIMATION_DURATION,
|
|
);
|
|
|
|
const localBlobUrl = (photo || video) ? (photo || video)!.blobUrl : undefined;
|
|
let bestImageData = (!isVideo && (localBlobUrl || fullMediaData)) || blobUrlPreview || blobUrlPictogram;
|
|
const thumbDataUri = useBlurSync(!bestImageData && message && getMessageMediaThumbDataUri(message));
|
|
if (!bestImageData && origin !== MediaViewerOrigin.SearchResult) {
|
|
bestImageData = thumbDataUri;
|
|
}
|
|
|
|
const photoDimensions = isPhoto ? getPhotoFullDimensions((
|
|
isWebPagePhoto ? getMessageWebPagePhoto(message!) : getMessagePhoto(message!)
|
|
)!) : undefined;
|
|
const videoDimensions = isVideo ? getVideoDimensions(getMessageVideo(message!)!) : undefined;
|
|
|
|
useEffect(() => {
|
|
if (!IS_SINGLE_COLUMN_LAYOUT) {
|
|
return;
|
|
}
|
|
|
|
document.body.classList.toggle('is-media-viewer-open', isOpen);
|
|
}, [isOpen]);
|
|
|
|
const forceUpdate = useForceUpdate();
|
|
useEffect(() => {
|
|
const mql = window.matchMedia(MEDIA_VIEWER_MEDIA_QUERY);
|
|
if (typeof mql.addEventListener === 'function') {
|
|
mql.addEventListener('change', forceUpdate);
|
|
} else if (typeof mql.addListener === 'function') {
|
|
mql.addListener(forceUpdate);
|
|
}
|
|
|
|
return () => {
|
|
if (typeof mql.removeEventListener === 'function') {
|
|
mql.removeEventListener('change', forceUpdate);
|
|
} else if (typeof mql.removeListener === 'function') {
|
|
mql.removeListener(forceUpdate);
|
|
}
|
|
};
|
|
}, [forceUpdate]);
|
|
|
|
const prevMessage = usePrevious<ApiMessage | undefined>(message);
|
|
const prevOrigin = usePrevious(origin);
|
|
const prevAvatarOwner = usePrevious<ApiChat | ApiUser | undefined>(avatarOwner);
|
|
const prevBestImageData = usePrevious(bestImageData);
|
|
useEffect(() => {
|
|
if (isGhostAnimation && isOpen && !prevMessage && !prevAvatarOwner) {
|
|
dispatchHeavyAnimationEvent(ANIMATION_DURATION + ANIMATION_END_DELAY);
|
|
const textParts = message ? renderMessageText(message) : undefined;
|
|
const hasFooter = Boolean(textParts);
|
|
animateOpening(hasFooter, origin!, bestImageData!, message);
|
|
}
|
|
|
|
if (isGhostAnimation && !isOpen && (prevMessage || prevAvatarOwner)) {
|
|
dispatchHeavyAnimationEvent(ANIMATION_DURATION + ANIMATION_END_DELAY);
|
|
animateClosing(prevOrigin!, prevBestImageData!, prevMessage || undefined);
|
|
}
|
|
}, [
|
|
isGhostAnimation, isOpen, origin, prevOrigin,
|
|
message, prevMessage, prevAvatarOwner, bestImageData, prevBestImageData,
|
|
]);
|
|
|
|
useEffect(() => {
|
|
let timer: number | undefined;
|
|
|
|
if (isZoomed) {
|
|
setCanPanZoomWrap(true);
|
|
} else {
|
|
timer = window.setTimeout(() => {
|
|
setCanPanZoomWrap(false);
|
|
}, ANIMATION_DURATION);
|
|
}
|
|
|
|
return () => {
|
|
if (timer) {
|
|
window.clearTimeout(timer);
|
|
}
|
|
};
|
|
}, [isZoomed]);
|
|
|
|
const closeZoom = () => {
|
|
setIsZoomed(false);
|
|
setZoomLevel(1);
|
|
setPanDelta({ x: 0, y: 0 });
|
|
};
|
|
|
|
const handleZoomToggle = useCallback(() => {
|
|
setIsZoomed(!isZoomed);
|
|
setZoomLevel(!isZoomed ? 1.5 : 1);
|
|
if (isZoomed) {
|
|
setPanDelta({ x: 0, y: 0 });
|
|
}
|
|
}, [isZoomed]);
|
|
|
|
const handleZoomValue = useCallback((level: number, canCloseZoom = false) => {
|
|
setZoomLevel(level);
|
|
if (level === 1 && canCloseZoom) {
|
|
closeZoom();
|
|
}
|
|
}, []);
|
|
|
|
const close = useCallback(() => {
|
|
closeMediaViewer();
|
|
closeZoom();
|
|
}, [closeMediaViewer]);
|
|
|
|
const handleFooterClick = useCallback(() => {
|
|
close();
|
|
focusMessage({ chatId, threadId, messageId });
|
|
}, [close, chatId, threadId, focusMessage, messageId]);
|
|
|
|
const handleForward = useCallback(() => {
|
|
openForwardMenu({ fromChatId: chatId, messageIds: [messageId] });
|
|
closeZoom();
|
|
}, [openForwardMenu, chatId, messageId]);
|
|
|
|
useEffect(() => (isOpen ? captureEscKeyListener(() => {
|
|
if (isZoomed) {
|
|
closeZoom();
|
|
} else {
|
|
close();
|
|
}
|
|
}) : undefined), [close, isOpen, isZoomed]);
|
|
|
|
useEffect(() => {
|
|
if (isVideo && !isGif) {
|
|
stopCurrentAudio();
|
|
}
|
|
}, [isGif, isVideo]);
|
|
|
|
const getMessageId = useCallback((fromId: number, direction: number): number => {
|
|
let index = messageIds.indexOf(fromId);
|
|
if ((direction === -1 && index > 0) || (direction === 1 && index < messageIds.length - 1)) {
|
|
index += direction;
|
|
}
|
|
|
|
return messageIds[index];
|
|
}, [messageIds]);
|
|
|
|
const selectPreviousMedia = useCallback(() => {
|
|
if (isFirst) {
|
|
return;
|
|
}
|
|
|
|
openMediaViewer({
|
|
chatId,
|
|
threadId,
|
|
messageId: messageId ? getMessageId(messageId, -1) : undefined,
|
|
origin,
|
|
});
|
|
}, [chatId, threadId, getMessageId, isFirst, messageId, openMediaViewer, origin]);
|
|
|
|
const selectNextMedia = useCallback(() => {
|
|
if (isLast) {
|
|
return;
|
|
}
|
|
|
|
openMediaViewer({
|
|
chatId,
|
|
threadId,
|
|
messageId: messageId ? getMessageId(messageId, 1) : undefined,
|
|
origin,
|
|
});
|
|
}, [chatId, threadId, getMessageId, isLast, messageId, openMediaViewer, origin]);
|
|
|
|
useEffect(() => {
|
|
const handleKeyDown = (e: KeyboardEvent) => {
|
|
switch (e.key) {
|
|
case 'Left': // IE/Edge specific value
|
|
case 'ArrowLeft':
|
|
selectPreviousMedia();
|
|
break;
|
|
|
|
case 'Right': // IE/Edge specific value
|
|
case 'ArrowRight':
|
|
selectNextMedia();
|
|
break;
|
|
}
|
|
};
|
|
|
|
document.addEventListener('keydown', handleKeyDown, false);
|
|
|
|
return () => {
|
|
document.removeEventListener('keydown', handleKeyDown, false);
|
|
};
|
|
});
|
|
|
|
// Support for swipe gestures and closing on click
|
|
useEffect(() => {
|
|
const element = document.querySelector<HTMLDivElement>('.slide-container > .active, .slide-container > .to');
|
|
if (!element) {
|
|
return undefined;
|
|
}
|
|
|
|
const shouldCloseOnVideo = isGif && !IS_IOS;
|
|
|
|
return captureEvents(element, {
|
|
// eslint-disable-next-line max-len
|
|
excludedClosestSelector: `.backdrop, .navigation, .media-viewer-head, .media-viewer-footer${!shouldCloseOnVideo ? ', .VideoPlayer' : ''}`,
|
|
onClick: () => {
|
|
if (!isZoomed && !IS_TOUCH_ENV) {
|
|
close();
|
|
}
|
|
},
|
|
onSwipe: IS_TOUCH_ENV ? (e, direction) => {
|
|
if (direction === SwipeDirection.Right) {
|
|
selectPreviousMedia();
|
|
} else if (direction === SwipeDirection.Left) {
|
|
selectNextMedia();
|
|
} else if (!(e.target && (e.target as HTMLElement).closest('.MediaViewerFooter'))) {
|
|
close();
|
|
}
|
|
} : undefined,
|
|
});
|
|
}, [close, isFooterHidden, isGif, isPhoto, isZoomed, selectNextMedia, selectPreviousMedia]);
|
|
|
|
const handlePan = useCallback((x: number, y: number) => {
|
|
setPanDelta({ x, y });
|
|
}, []);
|
|
|
|
const handleToggleFooterVisibility = useCallback(() => {
|
|
if (IS_TOUCH_ENV && (isPhoto || isGif)) {
|
|
setIsFooterHidden(!isFooterHidden);
|
|
}
|
|
}, [isFooterHidden, isGif, isPhoto]);
|
|
|
|
const lang = useLang();
|
|
|
|
function renderSlide(isActive: boolean) {
|
|
if (avatarOwner) {
|
|
return (
|
|
<div key={chatId} className="media-viewer-content">
|
|
{renderPhoto(
|
|
fullMediaData || blobUrlPreview,
|
|
calculateMediaViewerDimensions(AVATAR_FULL_DIMENSIONS, false),
|
|
!IS_SINGLE_COLUMN_LAYOUT && !isZoomed,
|
|
)}
|
|
</div>
|
|
);
|
|
} else if (message) {
|
|
const textParts = renderMessageText(message);
|
|
const hasFooter = Boolean(textParts);
|
|
|
|
return (
|
|
<div
|
|
key={messageId}
|
|
className={`media-viewer-content ${hasFooter ? 'has-footer' : ''}`}
|
|
onClick={handleToggleFooterVisibility}
|
|
>
|
|
{isPhoto && renderPhoto(
|
|
localBlobUrl || fullMediaData || blobUrlPreview || blobUrlPictogram,
|
|
message && calculateMediaViewerDimensions(photoDimensions!, hasFooter),
|
|
!IS_SINGLE_COLUMN_LAYOUT && !isZoomed,
|
|
)}
|
|
{isVideo && (
|
|
<VideoPlayer
|
|
key={messageId}
|
|
url={localBlobUrl || fullMediaData}
|
|
isGif={isGif}
|
|
posterData={bestImageData}
|
|
posterSize={message && calculateMediaViewerDimensions(videoDimensions!, hasFooter, true)}
|
|
downloadProgress={downloadProgress}
|
|
fileSize={video!.size}
|
|
isMediaViewerOpen={isOpen}
|
|
noPlay={!isActive}
|
|
onClose={close}
|
|
/>
|
|
)}
|
|
{textParts && (
|
|
<MediaViewerFooter
|
|
text={textParts}
|
|
onClick={handleFooterClick}
|
|
isHidden={isFooterHidden && (!isVideo || isGif)}
|
|
isForVideo={isVideo && !isGif}
|
|
/>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return undefined;
|
|
}
|
|
|
|
function renderSenderInfo() {
|
|
return (
|
|
<SenderInfo
|
|
key={avatarOwner ? avatarOwner.id : messageId}
|
|
chatId={avatarOwner ? avatarOwner.id : chatId}
|
|
messageId={messageId}
|
|
isAvatar={Boolean(avatarOwner)}
|
|
/>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<ShowTransition
|
|
id="MediaViewer"
|
|
className={isZoomed ? 'zoomed' : ''}
|
|
isOpen={isOpen}
|
|
>
|
|
{() => (
|
|
<>
|
|
<div className="media-viewer-head" dir={lang.isRtl ? 'rtl' : undefined}>
|
|
{IS_SINGLE_COLUMN_LAYOUT && (
|
|
<Button
|
|
className="media-viewer-close"
|
|
round
|
|
size="smaller"
|
|
color="translucent-white"
|
|
ariaLabel={lang('Close')}
|
|
onClick={close}
|
|
>
|
|
<i className="icon-close" />
|
|
</Button>
|
|
)}
|
|
<Transition activeKey={animationKey.current!} name={headerAnimation}>
|
|
{renderSenderInfo}
|
|
</Transition>
|
|
<MediaViewerActions
|
|
mediaData={fullMediaData || blobUrlPreview}
|
|
isVideo={isVideo}
|
|
isZoomed={isZoomed}
|
|
message={message}
|
|
fileName={fileName}
|
|
onCloseMediaViewer={close}
|
|
onForward={handleForward}
|
|
onZoomToggle={handleZoomToggle}
|
|
isAvatar={Boolean(avatarOwner)}
|
|
/>
|
|
</div>
|
|
<PanZoom
|
|
noWrap={!canPanZoomWrap}
|
|
canPan={isZoomed}
|
|
panDeltaX={panDelta.x}
|
|
panDeltaY={panDelta.y}
|
|
zoomLevel={zoomLevel}
|
|
onPan={handlePan}
|
|
>
|
|
<Transition
|
|
className="slide-container"
|
|
activeKey={selectedMediaMessageIndex}
|
|
name={slideAnimation}
|
|
>
|
|
{renderSlide}
|
|
</Transition>
|
|
</PanZoom>
|
|
{!isFirst && (
|
|
<button
|
|
type="button"
|
|
className={`navigation prev ${isVideo && !isGif && 'inline'}`}
|
|
aria-label={lang('AccDescrPrevious')}
|
|
dir={lang.isRtl ? 'rtl' : undefined}
|
|
onClick={selectPreviousMedia}
|
|
/>
|
|
)}
|
|
{!isLast && (
|
|
<button
|
|
type="button"
|
|
className={`navigation next ${isVideo && !isGif && 'inline'}`}
|
|
aria-label={lang('Next')}
|
|
dir={lang.isRtl ? 'rtl' : undefined}
|
|
onClick={selectNextMedia}
|
|
/>
|
|
)}
|
|
<ZoomControls
|
|
isShown={isZoomed}
|
|
onChangeZoom={handleZoomValue}
|
|
/>
|
|
</>
|
|
)}
|
|
</ShowTransition>
|
|
);
|
|
};
|
|
|
|
function renderPhoto(blobUrl?: string, imageSize?: IDimensions, canDrag?: boolean) {
|
|
return blobUrl
|
|
? (
|
|
<img
|
|
src={blobUrl}
|
|
alt=""
|
|
// @ts-ignore teact feature
|
|
style={imageSize ? `width: ${imageSize.width}px` : ''}
|
|
draggable={Boolean(canDrag)}
|
|
/>
|
|
)
|
|
: (
|
|
<div
|
|
className="spinner-wrapper"
|
|
// @ts-ignore teact feature
|
|
style={imageSize ? `width: ${imageSize.width}px` : ''}
|
|
>
|
|
<Spinner color="white" />
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export default memo(withGlobal(
|
|
(global): StateProps => {
|
|
const {
|
|
chatId, threadId, messageId, avatarOwnerId, profilePhotoIndex, origin,
|
|
} = global.mediaViewer;
|
|
const {
|
|
animationLevel,
|
|
} = global.settings.byKey;
|
|
|
|
if (origin === MediaViewerOrigin.SearchResult) {
|
|
if (!(chatId && messageId)) {
|
|
return { animationLevel };
|
|
}
|
|
|
|
const message = selectChatMessage(global, chatId, messageId);
|
|
if (!message) {
|
|
return { animationLevel };
|
|
}
|
|
|
|
return {
|
|
chatId,
|
|
messageId,
|
|
senderId: message.senderId,
|
|
origin,
|
|
message,
|
|
animationLevel,
|
|
};
|
|
}
|
|
|
|
if (avatarOwnerId) {
|
|
const sender = selectUser(global, avatarOwnerId) || selectChat(global, avatarOwnerId);
|
|
|
|
return {
|
|
messageId: -1,
|
|
senderId: avatarOwnerId,
|
|
avatarOwner: sender,
|
|
profilePhotoIndex: profilePhotoIndex || 0,
|
|
animationLevel,
|
|
origin,
|
|
};
|
|
}
|
|
|
|
if (!(chatId && threadId && messageId)) {
|
|
return { animationLevel };
|
|
}
|
|
|
|
let message: ApiMessage | undefined;
|
|
if (origin && [MediaViewerOrigin.ScheduledAlbum, MediaViewerOrigin.ScheduledInline].includes(origin)) {
|
|
message = selectScheduledMessage(global, chatId, messageId);
|
|
} else {
|
|
message = selectChatMessage(global, chatId, messageId);
|
|
}
|
|
|
|
if (!message) {
|
|
return { animationLevel };
|
|
}
|
|
|
|
let chatMessages: Record<number, ApiMessage> | undefined;
|
|
|
|
if (origin && [MediaViewerOrigin.ScheduledAlbum, MediaViewerOrigin.ScheduledInline].includes(origin)) {
|
|
chatMessages = selectScheduledMessages(global, chatId);
|
|
} else {
|
|
chatMessages = selectChatMessages(global, chatId);
|
|
}
|
|
let collectionIds: number[] | undefined;
|
|
|
|
if (origin === MediaViewerOrigin.Inline || origin === MediaViewerOrigin.Album) {
|
|
collectionIds = selectOutlyingIds(global, chatId, threadId) || selectListedIds(global, chatId, threadId);
|
|
} else if (origin === MediaViewerOrigin.SharedMedia) {
|
|
const currentSearch = selectCurrentMediaSearch(global);
|
|
const { foundIds } = (currentSearch && currentSearch.resultsByType && currentSearch.resultsByType.media) || {};
|
|
collectionIds = foundIds;
|
|
}
|
|
|
|
return {
|
|
chatId,
|
|
threadId,
|
|
messageId,
|
|
senderId: message.senderId,
|
|
origin,
|
|
message,
|
|
chatMessages,
|
|
collectionIds,
|
|
animationLevel,
|
|
};
|
|
},
|
|
(setGlobal, actions): DispatchProps => pick(actions, [
|
|
'openMediaViewer', 'closeMediaViewer', 'openForwardMenu', 'focusMessage',
|
|
]),
|
|
)(MediaViewer));
|