Media Viewer: Support document preview (#1247)
This commit is contained in:
parent
9b522ff50b
commit
265c710d19
@ -395,11 +395,38 @@ export function buildApiDocument(document: GramJs.TypeDocument): ApiDocument | u
|
|||||||
}
|
}
|
||||||
|
|
||||||
const {
|
const {
|
||||||
id, size, mimeType, date, thumbs,
|
id, size, mimeType, date, thumbs, attributes,
|
||||||
} = document;
|
} = document;
|
||||||
|
|
||||||
const thumbnail = thumbs && buildApiThumbnailFromStripped(thumbs);
|
const thumbnail = thumbs && buildApiThumbnailFromStripped(thumbs);
|
||||||
|
|
||||||
|
let mediaType: ApiDocument['mediaType'] | undefined;
|
||||||
|
let mediaSize: ApiDocument['mediaSize'] | undefined;
|
||||||
|
const photoSize = thumbs && thumbs.find((s: any): s is GramJs.PhotoSize => s instanceof GramJs.PhotoSize);
|
||||||
|
if (photoSize) {
|
||||||
|
mediaSize = {
|
||||||
|
width: photoSize.w,
|
||||||
|
height: photoSize.h,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mimeType.startsWith('image/')) {
|
||||||
|
mediaType = 'photo';
|
||||||
|
|
||||||
|
const imageAttribute = attributes
|
||||||
|
.find((a: any): a is GramJs.DocumentAttributeImageSize => a instanceof GramJs.DocumentAttributeImageSize);
|
||||||
|
|
||||||
|
if (imageAttribute) {
|
||||||
|
const { w: width, h: height } = imageAttribute;
|
||||||
|
mediaSize = {
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} else if (mimeType.startsWith('video/')) {
|
||||||
|
mediaType = 'video';
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: String(id),
|
id: String(id),
|
||||||
size,
|
size,
|
||||||
@ -407,6 +434,8 @@ export function buildApiDocument(document: GramJs.TypeDocument): ApiDocument | u
|
|||||||
timestamp: date,
|
timestamp: date,
|
||||||
fileName: getFilenameFromDocument(document),
|
fileName: getFilenameFromDocument(document),
|
||||||
thumbnail,
|
thumbnail,
|
||||||
|
mediaType,
|
||||||
|
mediaSize,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,13 +1,14 @@
|
|||||||
export interface ApiPhotoSize {
|
export interface ApiDimensions {
|
||||||
type: 's' | 'm' | 'x' | 'y' | 'z';
|
|
||||||
width: number;
|
width: number;
|
||||||
height: number;
|
height: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ApiThumbnail {
|
export interface ApiPhotoSize extends ApiDimensions {
|
||||||
|
type: 's' | 'm' | 'x' | 'y' | 'z';
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ApiThumbnail extends ApiDimensions {
|
||||||
dataUri: string;
|
dataUri: string;
|
||||||
height: number;
|
|
||||||
width: number;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ApiPhoto {
|
export interface ApiPhoto {
|
||||||
@ -81,6 +82,8 @@ export interface ApiDocument {
|
|||||||
mimeType: string;
|
mimeType: string;
|
||||||
thumbnail?: ApiThumbnail;
|
thumbnail?: ApiThumbnail;
|
||||||
previewBlobUrl?: string;
|
previewBlobUrl?: string;
|
||||||
|
mediaType?: 'photo' | 'video';
|
||||||
|
mediaSize?: ApiDimensions;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ApiContact {
|
export interface ApiContact {
|
||||||
|
|||||||
@ -5,7 +5,12 @@ import React, {
|
|||||||
import { ApiMessage } from '../../api/types';
|
import { ApiMessage } from '../../api/types';
|
||||||
|
|
||||||
import { getDocumentExtension, getDocumentHasPreview } from './helpers/documentInfo';
|
import { getDocumentExtension, getDocumentHasPreview } from './helpers/documentInfo';
|
||||||
import { getMediaTransferState, getMessageMediaHash, getMessageMediaThumbDataUri } from '../../modules/helpers';
|
import {
|
||||||
|
getMediaTransferState,
|
||||||
|
getMessageMediaHash,
|
||||||
|
getMessageMediaThumbDataUri,
|
||||||
|
isMessageDocumentVideo,
|
||||||
|
} from '../../modules/helpers';
|
||||||
import { ObserveFn, useIsIntersecting } from '../../hooks/useIntersectionObserver';
|
import { ObserveFn, useIsIntersecting } from '../../hooks/useIntersectionObserver';
|
||||||
import useMediaWithDownloadProgress from '../../hooks/useMediaWithDownloadProgress';
|
import useMediaWithDownloadProgress from '../../hooks/useMediaWithDownloadProgress';
|
||||||
import useMedia from '../../hooks/useMedia';
|
import useMedia from '../../hooks/useMedia';
|
||||||
@ -25,6 +30,7 @@ type OwnProps = {
|
|||||||
className?: string;
|
className?: string;
|
||||||
sender?: string;
|
sender?: string;
|
||||||
onCancelUpload?: () => void;
|
onCancelUpload?: () => void;
|
||||||
|
onMediaClick?: () => void;
|
||||||
onDateClick?: (messageId: number, chatId: number) => void;
|
onDateClick?: (messageId: number, chatId: number) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -40,6 +46,7 @@ const Document: FC<OwnProps> = ({
|
|||||||
isSelected,
|
isSelected,
|
||||||
isSelectable,
|
isSelectable,
|
||||||
onCancelUpload,
|
onCancelUpload,
|
||||||
|
onMediaClick,
|
||||||
onDateClick,
|
onDateClick,
|
||||||
}) => {
|
}) => {
|
||||||
// eslint-disable-next-line no-null/no-null
|
// eslint-disable-next-line no-null/no-null
|
||||||
@ -48,6 +55,7 @@ const Document: FC<OwnProps> = ({
|
|||||||
const document = message.content.document!;
|
const document = message.content.document!;
|
||||||
const extension = getDocumentExtension(document) || '';
|
const extension = getDocumentExtension(document) || '';
|
||||||
const { fileName, size, timestamp } = document;
|
const { fileName, size, timestamp } = document;
|
||||||
|
const withMediaViewer = onMediaClick && Boolean(document.mediaType);
|
||||||
|
|
||||||
const isIntersecting = useIsIntersecting(ref, observeIntersection);
|
const isIntersecting = useIsIntersecting(ref, observeIntersection);
|
||||||
|
|
||||||
@ -65,14 +73,16 @@ const Document: FC<OwnProps> = ({
|
|||||||
const previewData = useMedia(getMessageMediaHash(message, 'pictogram'), !isIntersecting);
|
const previewData = useMedia(getMessageMediaHash(message, 'pictogram'), !isIntersecting);
|
||||||
|
|
||||||
const handleClick = useCallback(() => {
|
const handleClick = useCallback(() => {
|
||||||
if (isUploading) {
|
if (withMediaViewer) {
|
||||||
|
onMediaClick!();
|
||||||
|
} else if (isUploading) {
|
||||||
if (onCancelUpload) {
|
if (onCancelUpload) {
|
||||||
onCancelUpload();
|
onCancelUpload();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
setIsDownloadAllowed((isAllowed) => !isAllowed);
|
setIsDownloadAllowed((isAllowed) => !isAllowed);
|
||||||
}
|
}
|
||||||
}, [isUploading, onCancelUpload]);
|
}, [withMediaViewer, isUploading, onCancelUpload, onMediaClick]);
|
||||||
|
|
||||||
const handleDateClick = useCallback(() => {
|
const handleDateClick = useCallback(() => {
|
||||||
onDateClick!(message.id, message.chatId);
|
onDateClick!(message.id, message.chatId);
|
||||||
@ -102,6 +112,7 @@ const Document: FC<OwnProps> = ({
|
|||||||
sender={sender}
|
sender={sender}
|
||||||
isSelectable={isSelectable}
|
isSelectable={isSelectable}
|
||||||
isSelected={isSelected}
|
isSelected={isSelected}
|
||||||
|
actionIcon={withMediaViewer ? (isMessageDocumentVideo(message) ? 'icon-play' : 'icon-eye') : 'icon-download'}
|
||||||
onClick={handleClick}
|
onClick={handleClick}
|
||||||
onDateClick={onDateClick ? handleDateClick : undefined}
|
onDateClick={onDateClick ? handleDateClick : undefined}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -70,7 +70,7 @@
|
|||||||
background: transparent;
|
background: transparent;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
& + .icon-download,
|
& + .action-icon,
|
||||||
& + .file-progress {
|
& + .file-progress {
|
||||||
background: rgba(black, 0.5);
|
background: rgba(black, 0.5);
|
||||||
border-radius: var(--border-radius-messages-small);
|
border-radius: var(--border-radius-messages-small);
|
||||||
@ -81,7 +81,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-download {
|
.action-icon {
|
||||||
color: #fff;
|
color: #fff;
|
||||||
font-size: 1.5rem;
|
font-size: 1.5rem;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@ -104,7 +104,7 @@
|
|||||||
border-width: 0;
|
border-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-download {
|
.action-icon {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
|
|
||||||
&.hidden {
|
&.hidden {
|
||||||
@ -164,7 +164,7 @@
|
|||||||
--background-color: var(--color-background);
|
--background-color: var(--color-background);
|
||||||
--border-radius-messages-small: .3125rem;
|
--border-radius-messages-small: .3125rem;
|
||||||
|
|
||||||
.icon-download,
|
.action-icon,
|
||||||
.file-progress,
|
.file-progress,
|
||||||
.file-icon,
|
.file-icon,
|
||||||
.file-preview {
|
.file-preview {
|
||||||
@ -211,7 +211,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-download {
|
.action-icon {
|
||||||
left: auto;
|
left: auto;
|
||||||
right: 0;
|
right: 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -31,6 +31,7 @@ type OwnProps = {
|
|||||||
isSelectable?: boolean;
|
isSelectable?: boolean;
|
||||||
isSelected?: boolean;
|
isSelected?: boolean;
|
||||||
transferProgress?: number;
|
transferProgress?: number;
|
||||||
|
actionIcon?: string;
|
||||||
onClick?: () => void;
|
onClick?: () => void;
|
||||||
onDateClick?: (e: React.MouseEvent<HTMLAnchorElement>) => void;
|
onDateClick?: (e: React.MouseEvent<HTMLAnchorElement>) => void;
|
||||||
};
|
};
|
||||||
@ -51,6 +52,7 @@ const File: FC<OwnProps> = ({
|
|||||||
isSelectable,
|
isSelectable,
|
||||||
isSelected,
|
isSelected,
|
||||||
transferProgress,
|
transferProgress,
|
||||||
|
actionIcon,
|
||||||
onClick,
|
onClick,
|
||||||
onDateClick,
|
onDateClick,
|
||||||
}) => {
|
}) => {
|
||||||
@ -126,7 +128,15 @@ const File: FC<OwnProps> = ({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{onClick && <i className={buildClassName('icon-download', shouldSpinnerRender && 'hidden')} />}
|
{onClick && (
|
||||||
|
<i
|
||||||
|
className={buildClassName(
|
||||||
|
'action-icon',
|
||||||
|
actionIcon || 'icon-download',
|
||||||
|
shouldSpinnerRender && 'hidden',
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="file-info">
|
<div className="file-info">
|
||||||
<div className="file-title" dir="auto">{renderText(name)}</div>
|
<div className="file-title" dir="auto">{renderText(name)}</div>
|
||||||
|
|||||||
@ -1,16 +1,19 @@
|
|||||||
import { ApiPhoto, ApiVideo, ApiSticker } from '../../../api/types';
|
import {
|
||||||
import { getPhotoInlineDimensions, getVideoDimensions, IDimensions } from '../../../modules/helpers';
|
ApiPhoto, ApiVideo, ApiSticker, ApiDimensions,
|
||||||
import windowSize from '../../../util/windowSize';
|
} from '../../../api/types';
|
||||||
import { IS_SINGLE_COLUMN_LAYOUT } from '../../../util/environment';
|
|
||||||
import { STICKER_SIZE_INLINE_DESKTOP_FACTOR, STICKER_SIZE_INLINE_MOBILE_FACTOR } from '../../../config';
|
import { STICKER_SIZE_INLINE_DESKTOP_FACTOR, STICKER_SIZE_INLINE_MOBILE_FACTOR } from '../../../config';
|
||||||
|
import { IS_SINGLE_COLUMN_LAYOUT } from '../../../util/environment';
|
||||||
|
import windowSize from '../../../util/windowSize';
|
||||||
|
import { getPhotoInlineDimensions, getVideoDimensions } from '../../../modules/helpers';
|
||||||
|
|
||||||
export const MEDIA_VIEWER_MEDIA_QUERY = '(max-height: 640px)';
|
export const MEDIA_VIEWER_MEDIA_QUERY = '(max-height: 640px)';
|
||||||
export const REM = parseInt(getComputedStyle(document.documentElement).fontSize, 10);
|
export const REM = parseInt(getComputedStyle(document.documentElement).fontSize, 10);
|
||||||
export const ROUND_VIDEO_DIMENSIONS = 200;
|
export const ROUND_VIDEO_DIMENSIONS = 200;
|
||||||
export const AVATAR_FULL_DIMENSIONS = { width: 640, height: 640 };
|
export const AVATAR_FULL_DIMENSIONS = { width: 640, height: 640 };
|
||||||
|
|
||||||
const DEFAULT_MEDIA_DIMENSIONS: IDimensions = { width: 100, height: 100 };
|
|
||||||
export const LIKE_STICKER_ID = '1258816259753933';
|
export const LIKE_STICKER_ID = '1258816259753933';
|
||||||
|
|
||||||
|
const DEFAULT_MEDIA_DIMENSIONS: ApiDimensions = { width: 100, height: 100 };
|
||||||
const MOBILE_SCREEN_NO_AVATARS_MESSAGE_EXTRA_WIDTH_REM = 4.5;
|
const MOBILE_SCREEN_NO_AVATARS_MESSAGE_EXTRA_WIDTH_REM = 4.5;
|
||||||
const MOBILE_SCREEN_MESSAGE_EXTRA_WIDTH_REM = 7;
|
const MOBILE_SCREEN_MESSAGE_EXTRA_WIDTH_REM = 7;
|
||||||
const MESSAGE_MAX_WIDTH_REM = 29;
|
const MESSAGE_MAX_WIDTH_REM = 29;
|
||||||
@ -92,7 +95,7 @@ function calculateDimensionsForMessageMedia({
|
|||||||
isWebPagePhoto?: boolean;
|
isWebPagePhoto?: boolean;
|
||||||
isGif?: boolean;
|
isGif?: boolean;
|
||||||
noAvatars?: boolean;
|
noAvatars?: boolean;
|
||||||
}): IDimensions {
|
}): ApiDimensions {
|
||||||
const aspectRatio = height / width;
|
const aspectRatio = height / width;
|
||||||
const availableWidth = getAvailableWidth(fromOwnMessage, isForwarded, isWebPagePhoto, noAvatars);
|
const availableWidth = getAvailableWidth(fromOwnMessage, isForwarded, isWebPagePhoto, noAvatars);
|
||||||
const availableHeight = getAvailableHeight(isGif, aspectRatio);
|
const availableHeight = getAvailableHeight(isGif, aspectRatio);
|
||||||
@ -100,7 +103,7 @@ function calculateDimensionsForMessageMedia({
|
|||||||
return calculateDimensions(availableWidth, availableHeight, width, height);
|
return calculateDimensions(availableWidth, availableHeight, width, height);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getMediaViewerAvailableDimensions(withFooter: boolean, isVideo: boolean): IDimensions {
|
export function getMediaViewerAvailableDimensions(withFooter: boolean, isVideo: boolean): ApiDimensions {
|
||||||
const mql = window.matchMedia(MEDIA_VIEWER_MEDIA_QUERY);
|
const mql = window.matchMedia(MEDIA_VIEWER_MEDIA_QUERY);
|
||||||
const { width: windowWidth, height: windowHeight } = windowSize.get();
|
const { width: windowWidth, height: windowHeight } = windowSize.get();
|
||||||
let occupiedHeightRem = isVideo && mql.matches ? 10 : 8.25;
|
let occupiedHeightRem = isVideo && mql.matches ? 10 : 8.25;
|
||||||
@ -151,14 +154,14 @@ export function calculateVideoDimensions(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getPictogramDimensions(): IDimensions {
|
export function getPictogramDimensions(): ApiDimensions {
|
||||||
return {
|
return {
|
||||||
width: 2 * REM,
|
width: 2 * REM,
|
||||||
height: 2 * REM,
|
height: 2 * REM,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getDocumentThumbnailDimensions(smaller?: boolean): IDimensions {
|
export function getDocumentThumbnailDimensions(smaller?: boolean): ApiDimensions {
|
||||||
if (smaller) {
|
if (smaller) {
|
||||||
return {
|
return {
|
||||||
width: 3 * REM,
|
width: 3 * REM,
|
||||||
@ -172,7 +175,7 @@ export function getDocumentThumbnailDimensions(smaller?: boolean): IDimensions {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getStickerDimensions(sticker: ApiSticker): IDimensions {
|
export function getStickerDimensions(sticker: ApiSticker): ApiDimensions {
|
||||||
const { width } = sticker;
|
const { width } = sticker;
|
||||||
let { height } = sticker;
|
let { height } = sticker;
|
||||||
|
|
||||||
@ -203,8 +206,8 @@ export function getStickerDimensions(sticker: ApiSticker): IDimensions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function calculateMediaViewerDimensions(
|
export function calculateMediaViewerDimensions(
|
||||||
{ width, height }: IDimensions, withFooter: boolean, isVideo: boolean = false,
|
{ width, height }: ApiDimensions, withFooter: boolean, isVideo: boolean = false,
|
||||||
): IDimensions {
|
): ApiDimensions {
|
||||||
const { width: availableWidth, height: availableHeight } = getMediaViewerAvailableDimensions(withFooter, isVideo);
|
const { width: availableWidth, height: availableHeight } = getMediaViewerAvailableDimensions(withFooter, isVideo);
|
||||||
|
|
||||||
return calculateDimensions(availableWidth, availableHeight, width, height);
|
return calculateDimensions(availableWidth, availableHeight, width, height);
|
||||||
@ -215,7 +218,7 @@ export function calculateDimensions(
|
|||||||
availableHeight: number,
|
availableHeight: number,
|
||||||
mediaWidth: number,
|
mediaWidth: number,
|
||||||
mediaHeight: number,
|
mediaHeight: number,
|
||||||
): IDimensions {
|
): ApiDimensions {
|
||||||
const aspectRatio = mediaHeight / mediaWidth;
|
const aspectRatio = mediaHeight / mediaWidth;
|
||||||
const calculatedWidth = Math.min(mediaWidth, availableWidth);
|
const calculatedWidth = Math.min(mediaWidth, availableWidth);
|
||||||
const calculatedHeight = Math.round(calculatedWidth * aspectRatio);
|
const calculatedHeight = Math.round(calculatedWidth * aspectRatio);
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import { withGlobal } from '../../lib/teact/teactn';
|
|||||||
|
|
||||||
import { GlobalActions } from '../../global/types';
|
import { GlobalActions } from '../../global/types';
|
||||||
import {
|
import {
|
||||||
ApiChat, ApiMediaFormat, ApiMessage, ApiUser,
|
ApiChat, ApiMediaFormat, ApiMessage, ApiUser, ApiDimensions,
|
||||||
} from '../../api/types';
|
} from '../../api/types';
|
||||||
import { MediaViewerOrigin } from '../../types';
|
import { MediaViewerOrigin } from '../../types';
|
||||||
|
|
||||||
@ -30,17 +30,20 @@ import {
|
|||||||
import {
|
import {
|
||||||
getChatAvatarHash,
|
getChatAvatarHash,
|
||||||
getChatMediaMessageIds,
|
getChatMediaMessageIds,
|
||||||
getMessageMediaFilename,
|
getMessageFileName,
|
||||||
getMessageMediaFormat,
|
getMessageMediaFormat,
|
||||||
getMessageMediaHash,
|
getMessageMediaHash,
|
||||||
getMessageMediaThumbDataUri,
|
getMessageMediaThumbDataUri,
|
||||||
getMessagePhoto,
|
getMessagePhoto,
|
||||||
getMessageVideo,
|
getMessageVideo,
|
||||||
|
getMessageDocument,
|
||||||
|
isMessageDocumentPhoto,
|
||||||
|
isMessageDocumentVideo,
|
||||||
getMessageWebPagePhoto,
|
getMessageWebPagePhoto,
|
||||||
getMessageWebPageVideo,
|
getMessageWebPageVideo,
|
||||||
getPhotoFullDimensions,
|
getPhotoFullDimensions,
|
||||||
getVideoDimensions,
|
getVideoDimensions, getMessageFileSize,
|
||||||
IDimensions,
|
|
||||||
} from '../../modules/helpers';
|
} from '../../modules/helpers';
|
||||||
import { pick } from '../../util/iteratees';
|
import { pick } from '../../util/iteratees';
|
||||||
import { captureEvents, SwipeDirection } from '../../util/captureEvents';
|
import { captureEvents, SwipeDirection } from '../../util/captureEvents';
|
||||||
@ -104,56 +107,62 @@ const MediaViewer: FC<StateProps & DispatchProps> = ({
|
|||||||
focusMessage,
|
focusMessage,
|
||||||
animationLevel,
|
animationLevel,
|
||||||
}) => {
|
}) => {
|
||||||
// eslint-disable-next-line no-null/no-null
|
|
||||||
const animationKey = useRef<number>(null);
|
|
||||||
const isOpen = Boolean(avatarOwner || messageId);
|
const isOpen = Boolean(avatarOwner || messageId);
|
||||||
const webPagePhoto = message ? getMessageWebPagePhoto(message) : undefined;
|
|
||||||
const webPageVideo = message ? getMessageWebPageVideo(message) : undefined;
|
|
||||||
const photo = message ? getMessagePhoto(message) : undefined;
|
|
||||||
const video = message ? getMessageVideo(message) : undefined;
|
|
||||||
const isWebPagePhoto = Boolean(webPagePhoto);
|
|
||||||
const isWebPageVideo = Boolean(webPageVideo);
|
|
||||||
const messageVideo = video || webPageVideo;
|
|
||||||
const isVideo = Boolean(messageVideo);
|
|
||||||
const isPhoto = Boolean(!isVideo && (photo || webPagePhoto));
|
|
||||||
const isGif = (messageVideo) ? messageVideo.isGif : undefined;
|
|
||||||
const isFromSharedMedia = origin === MediaViewerOrigin.SharedMedia;
|
const isFromSharedMedia = origin === MediaViewerOrigin.SharedMedia;
|
||||||
const isFromSearch = origin === MediaViewerOrigin.SearchResult;
|
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);
|
|
||||||
|
|
||||||
|
/* Content */
|
||||||
|
const photo = message ? getMessagePhoto(message) : undefined;
|
||||||
|
const video = message ? getMessageVideo(message) : undefined;
|
||||||
|
const webPagePhoto = message ? getMessageWebPagePhoto(message) : undefined;
|
||||||
|
const webPageVideo = message ? getMessageWebPageVideo(message) : undefined;
|
||||||
|
const isDocumentPhoto = message ? isMessageDocumentPhoto(message) : false;
|
||||||
|
const isDocumentVideo = message ? isMessageDocumentVideo(message) : false;
|
||||||
|
const isVideo = Boolean(video || webPageVideo || isDocumentVideo);
|
||||||
|
const isPhoto = Boolean(!isVideo && (photo || webPagePhoto || isDocumentPhoto));
|
||||||
|
const { isGif } = video || webPageVideo || {};
|
||||||
|
const isAvatar = Boolean(avatarOwner);
|
||||||
|
|
||||||
|
/* Navigation */
|
||||||
|
const isSingleSlide = Boolean(webPagePhoto || webPageVideo);
|
||||||
const messageIds = useMemo(() => {
|
const messageIds = useMemo(() => {
|
||||||
return (isWebPagePhoto || isWebPageVideo) && messageId
|
return isSingleSlide && messageId
|
||||||
? [messageId]
|
? [messageId]
|
||||||
: getChatMediaMessageIds(chatMessages || {}, collectionIds || [], isFromSharedMedia);
|
: getChatMediaMessageIds(chatMessages || {}, collectionIds || [], isFromSharedMedia);
|
||||||
}, [isWebPagePhoto, isWebPageVideo, messageId, chatMessages, collectionIds, isFromSharedMedia]);
|
}, [isSingleSlide, messageId, chatMessages, collectionIds, isFromSharedMedia]);
|
||||||
|
|
||||||
const selectedMediaMessageIndex = messageId ? messageIds.indexOf(messageId) : -1;
|
const selectedMediaMessageIndex = messageId ? messageIds.indexOf(messageId) : -1;
|
||||||
const isFirst = selectedMediaMessageIndex === 0 || selectedMediaMessageIndex === -1;
|
const isFirst = selectedMediaMessageIndex === 0 || selectedMediaMessageIndex === -1;
|
||||||
const isLast = selectedMediaMessageIndex === messageIds.length - 1 || selectedMediaMessageIndex === -1;
|
const isLast = selectedMediaMessageIndex === messageIds.length - 1 || selectedMediaMessageIndex === -1;
|
||||||
|
|
||||||
|
/* Animation */
|
||||||
|
const animationKey = useRef<number>();
|
||||||
|
const prevSenderId = usePrevious<number | undefined>(senderId);
|
||||||
if (isOpen && (!prevSenderId || prevSenderId !== senderId || !animationKey.current)) {
|
if (isOpen && (!prevSenderId || prevSenderId !== senderId || !animationKey.current)) {
|
||||||
animationKey.current = selectedMediaMessageIndex;
|
animationKey.current = selectedMediaMessageIndex;
|
||||||
}
|
}
|
||||||
|
const slideAnimation = animationLevel >= 1 ? 'mv-slide' : 'none';
|
||||||
|
const headerAnimation = animationLevel === 2 ? 'slide-fade' : 'none';
|
||||||
|
const isGhostAnimation = animationLevel === 2;
|
||||||
|
|
||||||
function getMediaHash(full?: boolean) {
|
/* Controls */
|
||||||
if (avatarOwner && profilePhotoIndex !== undefined) {
|
const [isFooterHidden, setIsFooterHidden] = useState<boolean>(false);
|
||||||
const { photos } = avatarOwner;
|
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 });
|
||||||
|
|
||||||
|
/* Media data */
|
||||||
|
function getMediaHash(isFull?: boolean) {
|
||||||
|
if (isAvatar && profilePhotoIndex !== undefined) {
|
||||||
|
const { photos } = avatarOwner!;
|
||||||
return photos && photos[profilePhotoIndex]
|
return photos && photos[profilePhotoIndex]
|
||||||
? `photo${photos[profilePhotoIndex].id}?size=c`
|
? `photo${photos[profilePhotoIndex].id}?size=c`
|
||||||
: getChatAvatarHash(avatarOwner, full ? 'big' : 'normal');
|
: getChatAvatarHash(avatarOwner!, isFull ? 'big' : 'normal');
|
||||||
}
|
}
|
||||||
|
|
||||||
return message && getMessageMediaHash(message, full ? 'viewerFull' : 'viewerPreview');
|
return message && getMessageMediaHash(message, isFull ? 'viewerFull' : 'viewerPreview');
|
||||||
}
|
}
|
||||||
|
|
||||||
const blobUrlPictogram = useMedia(
|
const blobUrlPictogram = useMedia(
|
||||||
@ -167,7 +176,7 @@ const MediaViewer: FC<StateProps & DispatchProps> = ({
|
|||||||
const blobUrlPreview = useMedia(
|
const blobUrlPreview = useMedia(
|
||||||
previewMediaHash,
|
previewMediaHash,
|
||||||
undefined,
|
undefined,
|
||||||
avatarOwner && previewMediaHash && previewMediaHash.startsWith('profilePhoto')
|
isAvatar && previewMediaHash && previewMediaHash.startsWith('profilePhoto')
|
||||||
? ApiMediaFormat.DataUri
|
? ApiMediaFormat.DataUri
|
||||||
: ApiMediaFormat.BlobUrl,
|
: ApiMediaFormat.BlobUrl,
|
||||||
undefined,
|
undefined,
|
||||||
@ -188,12 +197,25 @@ const MediaViewer: FC<StateProps & DispatchProps> = ({
|
|||||||
bestImageData = thumbDataUri;
|
bestImageData = thumbDataUri;
|
||||||
}
|
}
|
||||||
|
|
||||||
const photoDimensions = isPhoto ? getPhotoFullDimensions((
|
const videoSize = message ? getMessageFileSize(message) : undefined;
|
||||||
isWebPagePhoto ? webPagePhoto : photo
|
const fileName = message
|
||||||
)!) : undefined;
|
? getMessageFileName(message)
|
||||||
const videoDimensions = isVideo ? getVideoDimensions((
|
: isAvatar
|
||||||
isWebPageVideo ? webPageVideo : video
|
? `avatar${avatarOwner!.id}-${profilePhotoIndex}.jpg`
|
||||||
)!) : undefined;
|
: undefined;
|
||||||
|
|
||||||
|
let dimensions!: ApiDimensions;
|
||||||
|
if (message) {
|
||||||
|
if (isDocumentPhoto || isDocumentVideo) {
|
||||||
|
dimensions = getMessageDocument(message)!.mediaSize!;
|
||||||
|
} else if (photo || webPagePhoto) {
|
||||||
|
dimensions = getPhotoFullDimensions((photo || webPagePhoto)!)!;
|
||||||
|
} else if (video || webPageVideo) {
|
||||||
|
dimensions = getVideoDimensions((video || webPageVideo)!)!;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
dimensions = AVATAR_FULL_DIMENSIONS;
|
||||||
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!IS_SINGLE_COLUMN_LAYOUT) {
|
if (!IS_SINGLE_COLUMN_LAYOUT) {
|
||||||
@ -230,7 +252,7 @@ const MediaViewer: FC<StateProps & DispatchProps> = ({
|
|||||||
dispatchHeavyAnimationEvent(ANIMATION_DURATION + ANIMATION_END_DELAY);
|
dispatchHeavyAnimationEvent(ANIMATION_DURATION + ANIMATION_END_DELAY);
|
||||||
const textParts = message ? renderMessageText(message) : undefined;
|
const textParts = message ? renderMessageText(message) : undefined;
|
||||||
const hasFooter = Boolean(textParts);
|
const hasFooter = Boolean(textParts);
|
||||||
animateOpening(hasFooter, origin!, bestImageData!, message);
|
animateOpening(hasFooter, origin!, bestImageData!, dimensions, isVideo, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isGhostAnimation && !isOpen && (prevMessage || prevAvatarOwner)) {
|
if (isGhostAnimation && !isOpen && (prevMessage || prevAvatarOwner)) {
|
||||||
@ -238,8 +260,8 @@ const MediaViewer: FC<StateProps & DispatchProps> = ({
|
|||||||
animateClosing(prevOrigin!, prevBestImageData!, prevMessage || undefined);
|
animateClosing(prevOrigin!, prevBestImageData!, prevMessage || undefined);
|
||||||
}
|
}
|
||||||
}, [
|
}, [
|
||||||
isGhostAnimation, isOpen, origin, prevOrigin,
|
isGhostAnimation, isOpen, origin, prevOrigin, message, prevMessage, prevAvatarOwner,
|
||||||
message, prevMessage, prevAvatarOwner, bestImageData, prevBestImageData,
|
bestImageData, prevBestImageData, dimensions, isVideo,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -409,7 +431,7 @@ const MediaViewer: FC<StateProps & DispatchProps> = ({
|
|||||||
const lang = useLang();
|
const lang = useLang();
|
||||||
|
|
||||||
function renderSlide(isActive: boolean) {
|
function renderSlide(isActive: boolean) {
|
||||||
if (avatarOwner) {
|
if (isAvatar) {
|
||||||
return (
|
return (
|
||||||
<div key={chatId} className="media-viewer-content">
|
<div key={chatId} className="media-viewer-content">
|
||||||
{renderPhoto(
|
{renderPhoto(
|
||||||
@ -431,7 +453,7 @@ const MediaViewer: FC<StateProps & DispatchProps> = ({
|
|||||||
>
|
>
|
||||||
{isPhoto && renderPhoto(
|
{isPhoto && renderPhoto(
|
||||||
localBlobUrl || fullMediaData || blobUrlPreview || blobUrlPictogram,
|
localBlobUrl || fullMediaData || blobUrlPreview || blobUrlPictogram,
|
||||||
message && calculateMediaViewerDimensions(photoDimensions!, hasFooter),
|
message && calculateMediaViewerDimensions(dimensions!, hasFooter),
|
||||||
!IS_SINGLE_COLUMN_LAYOUT && !isZoomed,
|
!IS_SINGLE_COLUMN_LAYOUT && !isZoomed,
|
||||||
)}
|
)}
|
||||||
{isVideo && (
|
{isVideo && (
|
||||||
@ -440,9 +462,9 @@ const MediaViewer: FC<StateProps & DispatchProps> = ({
|
|||||||
url={localBlobUrl || fullMediaData}
|
url={localBlobUrl || fullMediaData}
|
||||||
isGif={isGif}
|
isGif={isGif}
|
||||||
posterData={bestImageData}
|
posterData={bestImageData}
|
||||||
posterSize={message && calculateMediaViewerDimensions(videoDimensions!, hasFooter, true)}
|
posterSize={message && calculateMediaViewerDimensions(dimensions!, hasFooter, true)}
|
||||||
downloadProgress={downloadProgress}
|
downloadProgress={downloadProgress}
|
||||||
fileSize={messageVideo!.size}
|
fileSize={videoSize!}
|
||||||
isMediaViewerOpen={isOpen}
|
isMediaViewerOpen={isOpen}
|
||||||
noPlay={!isActive}
|
noPlay={!isActive}
|
||||||
onClose={close}
|
onClose={close}
|
||||||
@ -464,12 +486,17 @@ const MediaViewer: FC<StateProps & DispatchProps> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
function renderSenderInfo() {
|
function renderSenderInfo() {
|
||||||
return (
|
return isAvatar ? (
|
||||||
<SenderInfo
|
<SenderInfo
|
||||||
key={avatarOwner ? avatarOwner.id : messageId}
|
key={avatarOwner!.id}
|
||||||
chatId={avatarOwner ? avatarOwner.id : chatId}
|
chatId={avatarOwner!.id}
|
||||||
|
isAvatar
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<SenderInfo
|
||||||
|
key={messageId}
|
||||||
|
chatId={chatId}
|
||||||
messageId={messageId}
|
messageId={messageId}
|
||||||
isAvatar={Boolean(avatarOwner)}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -507,7 +534,7 @@ const MediaViewer: FC<StateProps & DispatchProps> = ({
|
|||||||
onCloseMediaViewer={close}
|
onCloseMediaViewer={close}
|
||||||
onForward={handleForward}
|
onForward={handleForward}
|
||||||
onZoomToggle={handleZoomToggle}
|
onZoomToggle={handleZoomToggle}
|
||||||
isAvatar={Boolean(avatarOwner)}
|
isAvatar={isAvatar}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<PanZoom
|
<PanZoom
|
||||||
@ -554,7 +581,7 @@ const MediaViewer: FC<StateProps & DispatchProps> = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
function renderPhoto(blobUrl?: string, imageSize?: IDimensions, canDrag?: boolean) {
|
function renderPhoto(blobUrl?: string, imageSize?: ApiDimensions, canDrag?: boolean) {
|
||||||
return blobUrl
|
return blobUrl
|
||||||
? (
|
? (
|
||||||
<img
|
<img
|
||||||
|
|||||||
@ -5,12 +5,12 @@ import { ApiMessage } from '../../api/types';
|
|||||||
import { IS_SINGLE_COLUMN_LAYOUT } from '../../util/environment';
|
import { IS_SINGLE_COLUMN_LAYOUT } from '../../util/environment';
|
||||||
import { getMessageMediaHash } from '../../modules/helpers';
|
import { getMessageMediaHash } from '../../modules/helpers';
|
||||||
import useLang from '../../hooks/useLang';
|
import useLang from '../../hooks/useLang';
|
||||||
|
import useMediaDownload from '../../hooks/useMediaDownload';
|
||||||
|
|
||||||
import Button from '../ui/Button';
|
import Button from '../ui/Button';
|
||||||
import DropdownMenu from '../ui/DropdownMenu';
|
import DropdownMenu from '../ui/DropdownMenu';
|
||||||
import MenuItem from '../ui/MenuItem';
|
import MenuItem from '../ui/MenuItem';
|
||||||
import ProgressSpinner from '../ui/ProgressSpinner';
|
import ProgressSpinner from '../ui/ProgressSpinner';
|
||||||
import useMediaDownload from '../../hooks/useMediaDownload';
|
|
||||||
|
|
||||||
import './MediaViewerActions.scss';
|
import './MediaViewerActions.scss';
|
||||||
|
|
||||||
@ -41,7 +41,10 @@ const MediaViewerActions: FC<OwnProps> = ({
|
|||||||
isDownloadStarted,
|
isDownloadStarted,
|
||||||
downloadProgress,
|
downloadProgress,
|
||||||
handleDownloadClick,
|
handleDownloadClick,
|
||||||
} = useMediaDownload(message && isVideo ? getMessageMediaHash(message, 'download') : undefined);
|
} = useMediaDownload(
|
||||||
|
message && isVideo ? getMessageMediaHash(message, 'download') : undefined,
|
||||||
|
fileName,
|
||||||
|
);
|
||||||
|
|
||||||
const lang = useLang();
|
const lang = useLang();
|
||||||
|
|
||||||
|
|||||||
@ -2,8 +2,6 @@ import React, {
|
|||||||
FC, memo, useCallback, useEffect, useRef, useState,
|
FC, memo, useCallback, useEffect, useRef, useState,
|
||||||
} from '../../lib/teact/teact';
|
} from '../../lib/teact/teact';
|
||||||
|
|
||||||
import { IDimensions } from '../../modules/helpers';
|
|
||||||
|
|
||||||
import { IS_IOS, IS_SINGLE_COLUMN_LAYOUT, IS_TOUCH_ENV } from '../../util/environment';
|
import { IS_IOS, IS_SINGLE_COLUMN_LAYOUT, IS_TOUCH_ENV } from '../../util/environment';
|
||||||
import useShowTransition from '../../hooks/useShowTransition';
|
import useShowTransition from '../../hooks/useShowTransition';
|
||||||
import useBuffering from '../../hooks/useBuffering';
|
import useBuffering from '../../hooks/useBuffering';
|
||||||
@ -15,12 +13,13 @@ import VideoPlayerControls from './VideoPlayerControls';
|
|||||||
import ProgressSpinner from '../ui/ProgressSpinner';
|
import ProgressSpinner from '../ui/ProgressSpinner';
|
||||||
|
|
||||||
import './VideoPlayer.scss';
|
import './VideoPlayer.scss';
|
||||||
|
import { ApiDimensions } from '../../api/types';
|
||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
url?: string;
|
url?: string;
|
||||||
isGif?: boolean;
|
isGif?: boolean;
|
||||||
posterData?: string;
|
posterData?: string;
|
||||||
posterSize?: IDimensions;
|
posterSize?: ApiDimensions;
|
||||||
downloadProgress?: number;
|
downloadProgress?: number;
|
||||||
fileSize: number;
|
fileSize: number;
|
||||||
isMediaViewerOpen?: boolean;
|
isMediaViewerOpen?: boolean;
|
||||||
|
|||||||
@ -1,28 +1,25 @@
|
|||||||
import { ApiMessage } from '../../../api/types';
|
import { ApiMessage, ApiDimensions } from '../../../api/types';
|
||||||
|
|
||||||
import { MediaViewerOrigin } from '../../../types';
|
import { MediaViewerOrigin } from '../../../types';
|
||||||
|
|
||||||
import { ANIMATION_END_DELAY } from '../../../config';
|
import { ANIMATION_END_DELAY } from '../../../config';
|
||||||
import {
|
import {
|
||||||
getMessageContent,
|
|
||||||
getMessageWebPagePhoto,
|
|
||||||
getMessageWebPageVideo,
|
|
||||||
getPhotoFullDimensions,
|
|
||||||
getVideoDimensions,
|
|
||||||
} from '../../../modules/helpers';
|
|
||||||
import {
|
|
||||||
AVATAR_FULL_DIMENSIONS,
|
|
||||||
calculateDimensions,
|
calculateDimensions,
|
||||||
getMediaViewerAvailableDimensions,
|
getMediaViewerAvailableDimensions,
|
||||||
MEDIA_VIEWER_MEDIA_QUERY,
|
MEDIA_VIEWER_MEDIA_QUERY,
|
||||||
REM,
|
REM,
|
||||||
} from '../../common/helpers/mediaDimensions';
|
} from '../../common/helpers/mediaDimensions';
|
||||||
|
|
||||||
import windowSize from '../../../util/windowSize';
|
import windowSize from '../../../util/windowSize';
|
||||||
|
|
||||||
const ANIMATION_DURATION = 200;
|
const ANIMATION_DURATION = 200;
|
||||||
|
|
||||||
export function animateOpening(
|
export function animateOpening(
|
||||||
hasFooter: boolean, origin: MediaViewerOrigin, bestImageData: string, message?: ApiMessage,
|
hasFooter: boolean,
|
||||||
|
origin: MediaViewerOrigin,
|
||||||
|
bestImageData: string,
|
||||||
|
dimensions: ApiDimensions,
|
||||||
|
isVideo: boolean,
|
||||||
|
message?: ApiMessage,
|
||||||
) {
|
) {
|
||||||
const { mediaEl: fromImage } = getNodes(origin, message);
|
const { mediaEl: fromImage } = getNodes(origin, message);
|
||||||
if (!fromImage) {
|
if (!fromImage) {
|
||||||
@ -30,27 +27,11 @@ export function animateOpening(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const { width: windowWidth } = windowSize.get();
|
const { width: windowWidth } = windowSize.get();
|
||||||
|
|
||||||
let isVideo = false;
|
|
||||||
let mediaSize;
|
|
||||||
if (message) {
|
|
||||||
const { photo, video } = getMessageContent(message);
|
|
||||||
const webPagePhoto = getMessageWebPagePhoto(message);
|
|
||||||
const webPageVideo = getMessageWebPageVideo(message);
|
|
||||||
isVideo = Boolean(video || webPageVideo);
|
|
||||||
mediaSize = isVideo
|
|
||||||
? getVideoDimensions((video || webPageVideo)!)!
|
|
||||||
: getPhotoFullDimensions((photo || webPagePhoto)!)!;
|
|
||||||
} else {
|
|
||||||
mediaSize = AVATAR_FULL_DIMENSIONS;
|
|
||||||
}
|
|
||||||
|
|
||||||
// eslint-disable-next-line max-len
|
|
||||||
const {
|
const {
|
||||||
width: availableWidth, height: availableHeight,
|
width: availableWidth, height: availableHeight,
|
||||||
} = getMediaViewerAvailableDimensions(hasFooter, isVideo);
|
} = getMediaViewerAvailableDimensions(hasFooter, isVideo);
|
||||||
const { width: toWidth, height: toHeight } = calculateDimensions(
|
const { width: toWidth, height: toHeight } = calculateDimensions(
|
||||||
availableWidth, availableHeight, mediaSize.width, mediaSize.height,
|
availableWidth, availableHeight, dimensions.width, dimensions.height,
|
||||||
);
|
);
|
||||||
const toLeft = (windowWidth - toWidth) / 2;
|
const toLeft = (windowWidth - toWidth) / 2;
|
||||||
const toTop = getTopOffset(hasFooter) + (availableHeight - toHeight) / 2;
|
const toTop = getTopOffset(hasFooter) + (availableHeight - toHeight) / 2;
|
||||||
|
|||||||
@ -639,6 +639,7 @@ const Message: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
uploadProgress={uploadProgress}
|
uploadProgress={uploadProgress}
|
||||||
isSelectable={isInDocumentGroup}
|
isSelectable={isInDocumentGroup}
|
||||||
isSelected={isSelected}
|
isSelected={isSelected}
|
||||||
|
onMediaClick={handleMediaClick}
|
||||||
onCancelUpload={handleCancelUpload}
|
onCancelUpload={handleCancelUpload}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -4,8 +4,7 @@
|
|||||||
// https://github.com/overtake/TelegramSwift/blob/master/Telegram-Mac/GroupedLayout.swift#L83
|
// https://github.com/overtake/TelegramSwift/blob/master/Telegram-Mac/GroupedLayout.swift#L83
|
||||||
|
|
||||||
import { IAlbum } from '../../../../types';
|
import { IAlbum } from '../../../../types';
|
||||||
import { ApiMessage } from '../../../../api/types';
|
import { ApiMessage, ApiDimensions } from '../../../../api/types';
|
||||||
import { IDimensions } from '../../../../modules/helpers';
|
|
||||||
|
|
||||||
import { getAvailableWidth, REM } from '../../../common/helpers/mediaDimensions';
|
import { getAvailableWidth, REM } from '../../../common/helpers/mediaDimensions';
|
||||||
import { calculateMediaDimensions } from './mediaDimensions';
|
import { calculateMediaDimensions } from './mediaDimensions';
|
||||||
@ -43,13 +42,13 @@ type ILayoutParams = {
|
|||||||
};
|
};
|
||||||
export type IAlbumLayout = {
|
export type IAlbumLayout = {
|
||||||
layout: IMediaLayout[];
|
layout: IMediaLayout[];
|
||||||
containerStyle: IDimensions;
|
containerStyle: ApiDimensions;
|
||||||
};
|
};
|
||||||
|
|
||||||
function getRatios(messages: ApiMessage[]) {
|
function getRatios(messages: ApiMessage[]) {
|
||||||
return messages.map(
|
return messages.map(
|
||||||
(message) => {
|
(message) => {
|
||||||
const dimensions = calculateMediaDimensions(message) as IDimensions;
|
const dimensions = calculateMediaDimensions(message) as ApiDimensions;
|
||||||
|
|
||||||
return dimensions.width / dimensions.height;
|
return dimensions.width / dimensions.height;
|
||||||
},
|
},
|
||||||
@ -77,7 +76,7 @@ function cropRatios(ratios: number[], averageRatio: number) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function calculateContainerSize(layout: IMediaLayout[]) {
|
function calculateContainerSize(layout: IMediaLayout[]) {
|
||||||
const styles: IDimensions = { width: 0, height: 0 };
|
const styles: ApiDimensions = { width: 0, height: 0 };
|
||||||
layout.forEach(({
|
layout.forEach(({
|
||||||
dimensions,
|
dimensions,
|
||||||
sides,
|
sides,
|
||||||
|
|||||||
@ -1,14 +1,13 @@
|
|||||||
import { useEffect, useState } from '../lib/teact/teact';
|
import { useEffect, useState } from '../lib/teact/teact';
|
||||||
|
|
||||||
import { IDimensions } from '../modules/helpers';
|
|
||||||
|
|
||||||
import { throttle } from '../util/schedulers';
|
import { throttle } from '../util/schedulers';
|
||||||
import windowSize from '../util/windowSize';
|
import windowSize from '../util/windowSize';
|
||||||
|
import { ApiDimensions } from '../api/types';
|
||||||
|
|
||||||
const THROTTLE = 250;
|
const THROTTLE = 250;
|
||||||
|
|
||||||
export default () => {
|
export default () => {
|
||||||
const [size, setSize] = useState<IDimensions>(windowSize.get());
|
const [size, setSize] = useState<ApiDimensions>(windowSize.get());
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleResize = throttle(() => {
|
const handleResize = throttle(() => {
|
||||||
|
|||||||
@ -1,17 +1,18 @@
|
|||||||
import {
|
import {
|
||||||
ApiAudio, ApiMediaFormat, ApiMessage, ApiMessageSearchType, ApiPhoto, ApiVideo,
|
ApiAudio, ApiMediaFormat, ApiMessage, ApiMessageSearchType, ApiPhoto, ApiVideo, ApiDimensions,
|
||||||
} from '../../api/types';
|
} from '../../api/types';
|
||||||
|
|
||||||
import { IS_OPUS_SUPPORTED, IS_PROGRESSIVE_SUPPORTED, IS_SAFARI } from '../../util/environment';
|
import { IS_OPUS_SUPPORTED, IS_PROGRESSIVE_SUPPORTED, IS_SAFARI } from '../../util/environment';
|
||||||
import { getMessageKey, isMessageLocal, matchLinkInMessageText } from './messages';
|
import { getMessageKey, isMessageLocal, matchLinkInMessageText } from './messages';
|
||||||
import { getDocumentHasPreview } from '../../components/common/helpers/documentInfo';
|
import { getDocumentHasPreview } from '../../components/common/helpers/documentInfo';
|
||||||
|
|
||||||
export type IDimensions = {
|
type Target =
|
||||||
width: number;
|
'micro'
|
||||||
height: number;
|
| 'pictogram'
|
||||||
};
|
| 'inline'
|
||||||
|
| 'viewerPreview'
|
||||||
type Target = 'micro' | 'pictogram' | 'inline' | 'viewerPreview' | 'viewerFull' | 'download';
|
| 'viewerFull'
|
||||||
|
| 'download';
|
||||||
|
|
||||||
|
|
||||||
export function getMessageContent(message: ApiMessage) {
|
export function getMessageContent(message: ApiMessage) {
|
||||||
@ -66,6 +67,16 @@ export function getMessageDocument(message: ApiMessage) {
|
|||||||
return message.content.document;
|
return message.content.document;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isMessageDocumentPhoto(message: ApiMessage) {
|
||||||
|
const document = getMessageDocument(message);
|
||||||
|
return document ? document.mediaType === 'photo' : undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isMessageDocumentVideo(message: ApiMessage) {
|
||||||
|
const document = getMessageDocument(message);
|
||||||
|
return document ? document.mediaType === 'video' : undefined;
|
||||||
|
}
|
||||||
|
|
||||||
export function getMessageContact(message: ApiMessage) {
|
export function getMessageContact(message: ApiMessage) {
|
||||||
return message.content.contact;
|
return message.content.contact;
|
||||||
}
|
}
|
||||||
@ -177,6 +188,7 @@ export function getMessageMediaHash(
|
|||||||
case 'micro':
|
case 'micro':
|
||||||
case 'pictogram':
|
case 'pictogram':
|
||||||
case 'inline':
|
case 'inline':
|
||||||
|
case 'viewerPreview':
|
||||||
if (!getDocumentHasPreview(document) || hasMessageLocalBlobUrl(message)) {
|
if (!getDocumentHasPreview(document) || hasMessageLocalBlobUrl(message)) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
@ -262,8 +274,10 @@ export function getMessageMediaFormat(
|
|||||||
return ApiMediaFormat.BlobUrl;
|
return ApiMediaFormat.BlobUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getMessageMediaFilename(message: ApiMessage) {
|
export function getMessageFileName(message: ApiMessage) {
|
||||||
const { photo, video } = message.content;
|
const {
|
||||||
|
photo, video, document,
|
||||||
|
} = message.content;
|
||||||
const webPagePhoto = getMessageWebPagePhoto(message);
|
const webPagePhoto = getMessageWebPagePhoto(message);
|
||||||
const webPageVideo = getMessageWebPageVideo(message);
|
const webPageVideo = getMessageWebPageVideo(message);
|
||||||
|
|
||||||
@ -271,15 +285,17 @@ export function getMessageMediaFilename(message: ApiMessage) {
|
|||||||
return `photo${message.date}.jpeg`;
|
return `photo${message.date}.jpeg`;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (webPageVideo) {
|
const { fileName } = video || webPageVideo || document || {};
|
||||||
return webPageVideo.fileName;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (video) {
|
return fileName;
|
||||||
return video.fileName;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return undefined;
|
export function getMessageFileSize(message: ApiMessage) {
|
||||||
|
const { video, document } = message.content;
|
||||||
|
const webPageVideo = getMessageWebPageVideo(message);
|
||||||
|
const { size } = video || webPageVideo || document || {};
|
||||||
|
|
||||||
|
return size;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function hasMessageLocalBlobUrl(message: ApiMessage) {
|
export function hasMessageLocalBlobUrl(message: ApiMessage) {
|
||||||
@ -289,14 +305,14 @@ export function hasMessageLocalBlobUrl(message: ApiMessage) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function getChatMediaMessageIds(
|
export function getChatMediaMessageIds(
|
||||||
messages: Record<number, ApiMessage>, listedIds: number[], reverseOrder = false,
|
messages: Record<number, ApiMessage>, listedIds: number[], isFromSharedMedia = false,
|
||||||
) {
|
) {
|
||||||
const ids = getMessageContentIds(messages, listedIds, 'media');
|
const ids = getMessageContentIds(messages, listedIds, isFromSharedMedia ? 'media' : 'inlineMedia');
|
||||||
|
|
||||||
return reverseOrder ? ids.reverse() : ids;
|
return isFromSharedMedia ? ids.reverse() : ids;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getPhotoFullDimensions(photo: ApiPhoto): IDimensions | undefined {
|
export function getPhotoFullDimensions(photo: ApiPhoto): ApiDimensions | undefined {
|
||||||
return (
|
return (
|
||||||
photo.sizes.find((size) => size.type === 'z')
|
photo.sizes.find((size) => size.type === 'z')
|
||||||
|| photo.sizes.find((size) => size.type === 'y')
|
|| photo.sizes.find((size) => size.type === 'y')
|
||||||
@ -304,7 +320,7 @@ export function getPhotoFullDimensions(photo: ApiPhoto): IDimensions | undefined
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getPhotoInlineDimensions(photo: ApiPhoto): IDimensions | undefined {
|
export function getPhotoInlineDimensions(photo: ApiPhoto): ApiDimensions | undefined {
|
||||||
return (
|
return (
|
||||||
photo.sizes.find((size) => size.type === 'x')
|
photo.sizes.find((size) => size.type === 'x')
|
||||||
|| photo.sizes.find((size) => size.type === 'm')
|
|| photo.sizes.find((size) => size.type === 'm')
|
||||||
@ -313,9 +329,9 @@ export function getPhotoInlineDimensions(photo: ApiPhoto): IDimensions | undefin
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getVideoDimensions(video: ApiVideo): IDimensions | undefined {
|
export function getVideoDimensions(video: ApiVideo): ApiDimensions | undefined {
|
||||||
if (video.width && video.height) {
|
if (video.width && video.height) {
|
||||||
return video as IDimensions;
|
return video as ApiDimensions;
|
||||||
}
|
}
|
||||||
|
|
||||||
return undefined;
|
return undefined;
|
||||||
@ -332,7 +348,7 @@ export function getMediaTransferState(message: ApiMessage, progress?: number, is
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function getMessageContentIds(
|
export function getMessageContentIds(
|
||||||
messages: Record<number, ApiMessage>, messageIds: number[], contentType: ApiMessageSearchType,
|
messages: Record<number, ApiMessage>, messageIds: number[], contentType: ApiMessageSearchType | 'inlineMedia',
|
||||||
) {
|
) {
|
||||||
let validator: Function;
|
let validator: Function;
|
||||||
|
|
||||||
@ -356,6 +372,18 @@ export function getMessageContentIds(
|
|||||||
validator = getMessageAudio;
|
validator = getMessageAudio;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case 'inlineMedia':
|
||||||
|
validator = (message: ApiMessage) => {
|
||||||
|
const video = getMessageVideo(message);
|
||||||
|
return (
|
||||||
|
getMessagePhoto(message)
|
||||||
|
|| (video && !video.isRound && !video.isGif)
|
||||||
|
|| isMessageDocumentPhoto(message)
|
||||||
|
|| isMessageDocumentVideo(message)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return [] as Array<number>;
|
return [] as Array<number>;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import {
|
import {
|
||||||
ApiLanguage, ApiMessage, ApiStickerSet, ApiShippingAddress,
|
ApiLanguage, ApiMessage, ApiShippingAddress, ApiStickerSet,
|
||||||
} from '../api/types';
|
} from '../api/types';
|
||||||
|
|
||||||
export enum LoadMoreDirection {
|
export enum LoadMoreDirection {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user