From 4390800a46c5a8eced220bba051fae8d92753a36 Mon Sep 17 00:00:00 2001 From: Alexander Zinchuk Date: Thu, 30 Mar 2023 18:25:24 -0500 Subject: [PATCH] Message: Fix narrow media in messages (#2868) --- .../mediaViewer/helpers/ghostAnimation.ts | 2 +- src/components/middle/message/Album.scss | 1 + src/components/middle/message/Invoice.scss | 17 +++++++++ src/components/middle/message/Invoice.tsx | 35 +++++++++++++------ src/components/middle/message/Message.tsx | 25 +++++++++++-- src/components/middle/message/Photo.tsx | 11 ++++-- src/components/middle/message/Video.tsx | 20 ++++++++--- .../middle/message/helpers/mediaDimensions.ts | 12 +++---- .../message/hooks/useBlurredMediaThumbRef.ts | 10 ++++-- src/styles/_common.scss | 15 ++++++++ 10 files changed, 120 insertions(+), 28 deletions(-) diff --git a/src/components/mediaViewer/helpers/ghostAnimation.ts b/src/components/mediaViewer/helpers/ghostAnimation.ts index f1efd934b..7e64644c0 100644 --- a/src/components/mediaViewer/helpers/ghostAnimation.ts +++ b/src/components/mediaViewer/helpers/ghostAnimation.ts @@ -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(containerSelector)!; diff --git a/src/components/middle/message/Album.scss b/src/components/middle/message/Album.scss index 96832e17f..bc1bf7251 100644 --- a/src/components/middle/message/Album.scss +++ b/src/components/middle/message/Album.scss @@ -1,6 +1,7 @@ .Album { position: relative; overflow: hidden; + margin: auto; .message-content.media.text & { margin: -0.3125rem -0.5rem 0.3125rem; diff --git a/src/components/middle/message/Invoice.scss b/src/components/middle/message/Invoice.scss index 37645a1ff..ae4c1b415 100644 --- a/src/components/middle/message/Invoice.scss +++ b/src/components/middle/message/Invoice.scss @@ -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; + } } diff --git a/src/components/middle/message/Invoice.tsx b/src/components/middle/message/Invoice.tsx index 2d4b11fef..bf58db010 100644 --- a/src/components/middle/message/Invoice.tsx +++ b/src/components/middle/message/Invoice.tsx @@ -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 = ({ @@ -32,6 +34,7 @@ const Invoice: FC = ({ isInSelectMode, isSelected, theme, + forcedWidth, }) => { // eslint-disable-next-line no-null/no-null const ref = useRef(null); @@ -49,6 +52,8 @@ const Invoice: FC = ({ } = 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 = ({
{renderText(text, ['emoji', 'br'])}
)}
- {photoUrl && ( - - )} - {!photoUrl && photo && ( - + {Boolean(photo) && ( +
+ {withBlurredBackground && } + {photoUrl && ( + + )} + {!photoUrl && photo && ( + + )} +
)}

{formatCurrency(amount, currency, lang.code)} diff --git a/src/components/middle/message/Message.tsx b/src/components/middle/message/Message.tsx index d8909fd2b..4a0bb0af4 100644 --- a/src/components/middle/message/Message.tsx +++ b/src/components/middle/message/Message.tsx @@ -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 = ({ animatedEmoji, animatedCustomEmoji, genericEffects, + hasLinkedChat, isInSelectMode, isSelected, isGroupSelected, @@ -565,6 +572,10 @@ const Message: FC = ({ 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 = ({ 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 = ({ } 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 = ({ isProtected={isProtected} asForwarded={asForwarded} theme={theme} + forcedWidth={contentWidth} onClick={handleMediaClick} onCancelUpload={handleCancelUpload} /> @@ -918,6 +934,7 @@ const Message: FC = ({ message={message} observeIntersectionForLoading={observeIntersectionForLoading} observeIntersectionForPlaying={observeIntersectionForPlaying} + forcedWidth={contentWidth} noAvatars={noAvatars} canAutoLoad={canAutoLoadMedia} canAutoPlay={canAutoPlayMedia} @@ -1038,6 +1055,7 @@ const Message: FC = ({ isInSelectMode={isInSelectMode} isSelected={isSelected} theme={theme} + forcedWidth={contentWidth} /> )} {location && ( @@ -1410,6 +1428,7 @@ export default memo(withGlobal( 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 }), diff --git a/src/components/middle/message/Photo.tsx b/src/components/middle/message/Photo.tsx index 21d4c9948..27ff25497 100644 --- a/src/components/middle/message/Photo.tsx +++ b/src/components/middle/message/Photo.tsx @@ -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 = ({ isInSelectMode, isSelected, uploadProgress, + forcedWidth, size = 'inline', dimensions, asForwarded, @@ -92,9 +94,11 @@ const Photo: FC = ({ } = 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 = ({ !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 = ({ style={style} onClick={isUploading ? undefined : handleClick} > + {withBlurredBackground && } {withThumb && ( diff --git a/src/components/middle/message/Video.tsx b/src/components/middle/message/Video.tsx index a0bb08f7f..17ec5ee9c 100644 --- a/src/components/middle/message/Video.tsx +++ b/src/components/middle/message/Video.tsx @@ -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 = ({ canAutoLoad, canAutoPlay, uploadProgress, + forcedWidth, lastSyncTime, dimensions, asForwarded, @@ -103,6 +106,7 @@ const Video: FC = ({ 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 = ({ 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 = ({ 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 = ({ style={style} onClick={isUploading ? undefined : handleClick} > + {withBlurredBackground && } {isInline && ( = ({ draggable={!isProtected} onTimeUpdate={handleTimeUpdate} onReady={markPlayerReady} + style={forcedWidth ? `width: ${forcedWidth}px` : undefined} /> )} {hasThumb && !isPreviewPreloaded && ( diff --git a/src/components/middle/message/helpers/mediaDimensions.ts b/src/components/middle/message/helpers/mediaDimensions.ts index 47910a7ff..ade64b617 100644 --- a/src/components/middle/message/helpers/mediaDimensions.ts +++ b/src/components/middle/message/helpers/mediaDimensions.ts @@ -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) { diff --git a/src/components/middle/message/hooks/useBlurredMediaThumbRef.ts b/src/components/middle/message/hooks/useBlurredMediaThumbRef.ts index c415f4ebe..105fbd373 100644 --- a/src/components/middle/message/hooks/useBlurredMediaThumbRef.ts +++ b/src/components/middle/message/hooks/useBlurredMediaThumbRef.ts @@ -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, ); diff --git a/src/styles/_common.scss b/src/styles/_common.scss index de70b2e46..f5a78acc2 100644 --- a/src/styles/_common.scss +++ b/src/styles/_common.scss @@ -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 {