Message: Fix narrow media in messages (#2868)

This commit is contained in:
Alexander Zinchuk 2023-03-30 18:25:24 -05:00
parent 6176ef1a98
commit 4390800a46
10 changed files with 120 additions and 28 deletions

View File

@ -335,7 +335,7 @@ function getNodes(origin: MediaViewerOrigin, message?: ApiMessage) {
case MediaViewerOrigin.Inline:
default:
containerSelector = `.Transition__slide--active > .MessageList #${getMessageHtmlId(message!.id)}`;
mediaSelector = `${MESSAGE_CONTENT_SELECTOR} .full-media, ${MESSAGE_CONTENT_SELECTOR} .thumbnail`;
mediaSelector = `${MESSAGE_CONTENT_SELECTOR} .full-media,${MESSAGE_CONTENT_SELECTOR} .thumbnail:not(.blurred-bg)`;
}
const container = document.querySelector<HTMLElement>(containerSelector)!;

View File

@ -1,6 +1,7 @@
.Album {
position: relative;
overflow: hidden;
margin: auto;
.message-content.media.text & {
margin: -0.3125rem -0.5rem 0.3125rem;

View File

@ -14,6 +14,7 @@
}
.invoice-image {
position: relative;
width: 100%;
max-height: 30rem;
object-fit: cover;
@ -42,4 +43,20 @@
}
}
}
.invoice-image-container {
position: relative;
display: flex;
align-items: center;
justify-content: center;
}
.thumbnail {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
object-fit: cover;
}
}

View File

@ -13,6 +13,7 @@ import getCustomAppendixBg from './helpers/getCustomAppendixBg';
import useLayoutEffectWithPrevDeps from '../../../hooks/useLayoutEffectWithPrevDeps';
import useLang from '../../../hooks/useLang';
import useMedia from '../../../hooks/useMedia';
import useBlurredMediaThumbRef from './hooks/useBlurredMediaThumbRef';
import Skeleton from '../../ui/Skeleton';
@ -24,6 +25,7 @@ type OwnProps = {
isInSelectMode?: boolean;
isSelected?: boolean;
theme: ISettings['theme'];
forcedWidth?: number;
};
const Invoice: FC<OwnProps> = ({
@ -32,6 +34,7 @@ const Invoice: FC<OwnProps> = ({
isInSelectMode,
isSelected,
theme,
forcedWidth,
}) => {
// eslint-disable-next-line no-null/no-null
const ref = useRef<HTMLDivElement>(null);
@ -49,6 +52,8 @@ const Invoice: FC<OwnProps> = ({
} = invoice!;
const photoUrl = useMedia(getWebDocumentHash(photo));
const withBlurredBackground = Boolean(forcedWidth);
const blurredBackgroundRef = useBlurredMediaThumbRef(message, !withBlurredBackground, photoUrl);
useLayoutEffectWithPrevDeps(([prevShouldAffectAppendix]) => {
if (!shouldAffectAppendix) {
@ -79,16 +84,26 @@ const Invoice: FC<OwnProps> = ({
<div>{renderText(text, ['emoji', 'br'])}</div>
)}
<div className={`description ${photo ? 'has-image' : ''}`}>
{photoUrl && (
<img
className="invoice-image"
src={photoUrl}
alt=""
crossOrigin="anonymous"
/>
)}
{!photoUrl && photo && (
<Skeleton width={photo.dimensions?.width} height={photo.dimensions?.height} forceAspectRatio />
{Boolean(photo) && (
<div className="invoice-image-container">
{withBlurredBackground && <canvas ref={blurredBackgroundRef} className="thumbnail blurred-bg" />}
{photoUrl && (
<img
className="invoice-image"
src={photoUrl}
alt=""
style={forcedWidth ? `width: ${forcedWidth}px` : undefined}
crossOrigin="anonymous"
/>
)}
{!photoUrl && photo && (
<Skeleton
width={forcedWidth || photo.dimensions?.width}
height={photo.dimensions?.height}
forceAspectRatio
/>
)}
</div>
)}
<p className="description-text">
{formatCurrency(amount, currency, lang.code)}

View File

@ -96,7 +96,12 @@ import {
ROUND_VIDEO_DIMENSIONS_PX,
} from '../../common/helpers/mediaDimensions';
import { buildContentClassName } from './helpers/buildContentClassName';
import { getMinMediaWidth, calculateMediaDimensions } from './helpers/mediaDimensions';
import {
getMinMediaWidth,
calculateMediaDimensions,
MIN_MEDIA_WIDTH_WITH_COMMENTS,
MIN_MEDIA_WIDTH_WITH_TEXT,
} from './helpers/mediaDimensions';
import { calculateAlbumLayout } from './helpers/calculateAlbumLayout';
import renderText from '../../common/helpers/renderText';
import calculateAuthorWidth from './helpers/calculateAuthorWidth';
@ -223,6 +228,7 @@ type StateProps = {
isPinnedList?: boolean;
canAutoLoadMedia?: boolean;
canAutoPlayMedia?: boolean;
hasLinkedChat?: boolean;
shouldLoopStickers?: boolean;
autoLoadFileMaxSizeMb: number;
repliesThreadInfo?: ApiThreadInfo;
@ -317,6 +323,7 @@ const Message: FC<OwnProps & StateProps> = ({
animatedEmoji,
animatedCustomEmoji,
genericEffects,
hasLinkedChat,
isInSelectMode,
isSelected,
isGroupSelected,
@ -565,6 +572,10 @@ const Message: FC<OwnProps & StateProps> = ({
const { phoneCall } = action || {};
const isMediaWidthWithCommentButton = (repliesThreadInfo || (hasLinkedChat && isChannel && isLocal))
&& !isInDocumentGroupNotLast
&& messageListType === 'thread'
&& !noComments;
const withCommentButton = repliesThreadInfo && !isInDocumentGroupNotLast && messageListType === 'thread'
&& !noComments;
const withQuickReactionButton = !IS_TOUCH_ENV && !phoneCall && !isInSelectMode && defaultReaction
@ -657,6 +668,7 @@ const Message: FC<OwnProps & StateProps> = ({
let style = '';
let calculatedWidth;
let reactionsMaxWidth;
let contentWidth: number | undefined;
let noMediaCorners = false;
const albumLayout = useMemo(() => {
return isAlbum
@ -690,14 +702,17 @@ const Message: FC<OwnProps & StateProps> = ({
}
if (width) {
calculatedWidth = Math.max(getMinMediaWidth(Boolean(currentText), withCommentButton), width);
if (width < (isMediaWidthWithCommentButton ? MIN_MEDIA_WIDTH_WITH_COMMENTS : MIN_MEDIA_WIDTH_WITH_TEXT)) {
contentWidth = width;
}
calculatedWidth = Math.max(getMinMediaWidth(Boolean(currentText), isMediaWidthWithCommentButton), width);
if (invoice?.extendedMedia && calculatedWidth - width > NO_MEDIA_CORNERS_THRESHOLD) {
noMediaCorners = true;
}
}
} else if (albumLayout) {
calculatedWidth = Math.max(
getMinMediaWidth(Boolean(currentText), withCommentButton), albumLayout.containerStyle.width,
getMinMediaWidth(Boolean(currentText), isMediaWidthWithCommentButton), albumLayout.containerStyle.width,
);
if (calculatedWidth - albumLayout.containerStyle.width > NO_MEDIA_CORNERS_THRESHOLD) {
noMediaCorners = true;
@ -900,6 +915,7 @@ const Message: FC<OwnProps & StateProps> = ({
isProtected={isProtected}
asForwarded={asForwarded}
theme={theme}
forcedWidth={contentWidth}
onClick={handleMediaClick}
onCancelUpload={handleCancelUpload}
/>
@ -918,6 +934,7 @@ const Message: FC<OwnProps & StateProps> = ({
message={message}
observeIntersectionForLoading={observeIntersectionForLoading}
observeIntersectionForPlaying={observeIntersectionForPlaying}
forcedWidth={contentWidth}
noAvatars={noAvatars}
canAutoLoad={canAutoLoadMedia}
canAutoPlay={canAutoPlayMedia}
@ -1038,6 +1055,7 @@ const Message: FC<OwnProps & StateProps> = ({
isInSelectMode={isInSelectMode}
isSelected={isSelected}
theme={theme}
forcedWidth={contentWidth}
/>
)}
{location && (
@ -1410,6 +1428,7 @@ export default memo(withGlobal<OwnProps>(
chatTranslations,
areTranslationsEnabled: global.settings.byKey.canTranslate,
requestedTranslationLanguage,
hasLinkedChat: Boolean(chat?.fullInfo?.linkedChatId),
...((canShowSender || isLocation) && { sender }),
...(isOutgoing && { outgoingStatus: selectOutgoingStatus(global, message, messageListType === 'scheduled') }),
...(typeof uploadProgress === 'number' && { uploadProgress }),

View File

@ -20,7 +20,7 @@ import {
} from '../../../global/helpers';
import buildClassName from '../../../util/buildClassName';
import getCustomAppendixBg from './helpers/getCustomAppendixBg';
import { calculateMediaDimensions } from './helpers/mediaDimensions';
import { calculateMediaDimensions, MIN_MEDIA_HEIGHT } from './helpers/mediaDimensions';
import { useIsIntersecting } from '../../../hooks/useIntersectionObserver';
import useMediaWithLoadProgress from '../../../hooks/useMediaWithLoadProgress';
@ -44,6 +44,7 @@ export type OwnProps = {
isInSelectMode?: boolean;
isSelected?: boolean;
uploadProgress?: number;
forcedWidth?: number;
size?: 'inline' | 'pictogram';
shouldAffectAppendix?: boolean;
dimensions?: IMediaDimensions & { isSmall?: boolean };
@ -65,6 +66,7 @@ const Photo: FC<OwnProps> = ({
isInSelectMode,
isSelected,
uploadProgress,
forcedWidth,
size = 'inline',
dimensions,
asForwarded,
@ -92,9 +94,11 @@ const Photo: FC<OwnProps> = ({
} = useMediaWithLoadProgress(getMessageMediaHash(message, size), !shouldLoad);
const fullMediaData = localBlobUrl || mediaData;
const withBlurredBackground = Boolean(forcedWidth);
const [withThumb] = useState(!fullMediaData);
const noThumb = Boolean(fullMediaData);
const thumbRef = useBlurredMediaThumbRef(message, noThumb);
const blurredBackgroundRef = useBlurredMediaThumbRef(message, !withBlurredBackground);
const thumbClassNames = useMediaTransition(!noThumb);
const thumbDataUri = getMessageMediaThumbDataUri(message);
@ -170,6 +174,7 @@ const Photo: FC<OwnProps> = ({
!isUploading && !nonInteractive && 'interactive',
isSmall && 'small-image',
width === height && 'square-image',
height < MIN_MEDIA_HEIGHT && 'fix-min-height',
);
const dimensionsStyle = dimensions ? ` width: ${width}px; left: ${dimensions.x}px; top: ${dimensions.y}px;` : '';
@ -183,10 +188,12 @@ const Photo: FC<OwnProps> = ({
style={style}
onClick={isUploading ? undefined : handleClick}
>
{withBlurredBackground && <canvas ref={blurredBackgroundRef} className="thumbnail blurred-bg" />}
<img
src={fullMediaData}
className="full-media"
className={buildClassName('full-media', withBlurredBackground && 'with-blurred-bg')}
alt=""
style={forcedWidth ? `width: ${forcedWidth}px` : undefined}
draggable={!isProtected}
/>
{withThumb && (

View File

@ -1,11 +1,12 @@
import type { FC } from '../../../lib/teact/teact';
import React, { useCallback, useRef, useState } from '../../../lib/teact/teact';
import { getActions } from '../../../global';
import type { FC } from '../../../lib/teact/teact';
import type { ApiMessage } from '../../../api/types';
import type { IMediaDimensions } from './helpers/calculateAlbumLayout';
import type { ObserveFn } from '../../../hooks/useIntersectionObserver';
import { MIN_MEDIA_HEIGHT } from './helpers/mediaDimensions';
import { formatMediaDuration } from '../../../util/dateFormat';
import buildClassName from '../../../util/buildClassName';
import { calculateVideoDimensions } from '../../common/helpers/mediaDimensions';
@ -42,6 +43,7 @@ export type OwnProps = {
canAutoLoad?: boolean;
canAutoPlay?: boolean;
uploadProgress?: number;
forcedWidth?: number;
dimensions?: IMediaDimensions;
asForwarded?: boolean;
lastSyncTime?: number;
@ -60,6 +62,7 @@ const Video: FC<OwnProps> = ({
canAutoLoad,
canAutoPlay,
uploadProgress,
forcedWidth,
lastSyncTime,
dimensions,
asForwarded,
@ -103,6 +106,7 @@ const Video: FC<OwnProps> = ({
const thumbDataUri = getMessageMediaThumbDataUri(message);
const hasThumb = Boolean(thumbDataUri);
const withBlurredBackground = Boolean(forcedWidth);
const previewMediaHash = getMessageMediaHash(message, 'preview');
const [isPreviewPreloaded] = useState(Boolean(previewMediaHash && mediaLoader.getFromMemory(previewMediaHash)));
@ -112,6 +116,7 @@ const Video: FC<OwnProps> = ({
const noThumb = !hasThumb || previewBlobUrl || isPlayerReady;
const thumbRef = useBlurredMediaThumbRef(message, noThumb);
const blurredBackgroundRef = useBlurredMediaThumbRef(message, !withBlurredBackground);
const thumbClassNames = useMediaTransition(!noThumb);
const isInline = fullMediaData && wasIntersectedRef.current;
@ -182,7 +187,11 @@ const Video: FC<OwnProps> = ({
hideSpoiler,
]);
const className = buildClassName('media-inner dark', !isUploading && 'interactive');
const className = buildClassName(
'media-inner dark',
!isUploading && 'interactive',
height < MIN_MEDIA_HEIGHT && 'fix-min-height',
);
const dimensionsStyle = dimensions ? ` width: ${width}px; left: ${dimensions.x}px; top: ${dimensions.y}px;` : '';
const style = `height: ${height}px;${dimensionsStyle}`;
@ -195,11 +204,12 @@ const Video: FC<OwnProps> = ({
style={style}
onClick={isUploading ? undefined : handleClick}
>
{withBlurredBackground && <canvas ref={blurredBackgroundRef} className="thumbnail blurred-bg" />}
{isInline && (
<OptimizedVideo
ref={videoRef}
src={fullMediaData}
className="full-media"
className={buildClassName('full-media', withBlurredBackground && 'with-blurred-bg')}
canPlay={isPlayAllowed && isIntersectingForPlaying}
muted
loop
@ -207,12 +217,14 @@ const Video: FC<OwnProps> = ({
draggable={!isProtected}
onTimeUpdate={handleTimeUpdate}
onReady={markPlayerReady}
style={forcedWidth ? `width: ${forcedWidth}px` : undefined}
/>
)}
<img
src={previewBlobUrl}
className={buildClassName('thumbnail', previewClassNames)}
className={buildClassName('thumbnail', previewClassNames, withBlurredBackground && 'with-blurred-bg')}
alt=""
style={forcedWidth ? `width: ${forcedWidth}px;` : undefined}
draggable={!isProtected}
/>
{hasThumb && !isPreviewPreloaded && (

View File

@ -1,5 +1,5 @@
import type { ApiMessage } from '../../../../api/types';
import { calculateInlineImageDimensions, calculateVideoDimensions } from '../../../common/helpers/mediaDimensions';
import { calculateInlineImageDimensions, calculateVideoDimensions, REM } from '../../../common/helpers/mediaDimensions';
import {
getMessageText,
getMessagePhoto,
@ -9,11 +9,11 @@ import {
getMessageWebPageVideo,
} from '../../../../global/helpers';
const MIN_MEDIA_WIDTH = 100;
const MIN_MEDIA_WIDTH_WITH_COMMENTS = 238;
const MIN_MEDIA_WIDTH_WITH_TEXT = 175;
const MIN_MEDIA_WIDTH_WITH_TEXT_AND_COMMENTS = 238;
const MIN_MEDIA_HEIGHT = 90;
export const MIN_MEDIA_WIDTH_WITH_COMMENTS = 20 * REM;
export const MIN_MEDIA_WIDTH_WITH_TEXT = 15 * REM;
const MIN_MEDIA_WIDTH_WITH_TEXT_AND_COMMENTS = 20 * REM;
const MIN_MEDIA_WIDTH = 7 * REM;
export const MIN_MEDIA_HEIGHT = 5 * REM;
const SMALL_IMAGE_THRESHOLD = 12;
export function getMinMediaWidth(hasText?: boolean, hasCommentButton?: boolean) {

View File

@ -5,11 +5,17 @@ import { getMessageMediaThumbDataUri } from '../../../../global/helpers';
import useCanvasBlur from '../../../../hooks/useCanvasBlur';
import useAppLayout from '../../../../hooks/useAppLayout';
export default function useBlurredMediaThumbRef(message: ApiMessage, isDisabled?: boolean | string) {
export default function useBlurredMediaThumbRef(
message: ApiMessage,
isDisabled?: boolean | string,
forcedUri?: string,
) {
const { isMobile } = useAppLayout();
const dataUri = forcedUri || getMessageMediaThumbDataUri(message);
return useCanvasBlur(
getMessageMediaThumbDataUri(message),
dataUri,
Boolean(isDisabled),
isMobile && !IS_CANVAS_FILTER_SUPPORTED,
);

View File

@ -14,6 +14,11 @@
object-fit: cover;
}
.full-media.with-blurred-bg {
position: relative;
border-radius: 0 !important;
}
video {
background: no-repeat 50% 50%;
background-size: contain;
@ -23,11 +28,21 @@
position: absolute;
top: 0;
left: 0;
&.with-blurred-bg {
border-radius: 0 !important;
left: 50%;
transform: translateX(-50%);
}
}
.media-loading {
position: absolute;
}
&.fix-min-height {
min-height: 5rem;
}
}
.animated-close-icon {