WebPage: Render preview and audio document (#4345)

This commit is contained in:
Alexander Zinchuk 2024-03-08 12:48:52 +01:00
parent 70dfa2e160
commit f8bcd32865
8 changed files with 96 additions and 21 deletions

View File

@ -216,6 +216,39 @@ export function buildVideoFromDocument(document: GramJs.Document, isSpoiler?: bo
}; };
} }
export function buildAudioFromDocument(document: GramJs.Document): ApiAudio | undefined {
if (document instanceof GramJs.DocumentEmpty) {
return undefined;
}
const {
id, mimeType, size, attributes,
} = document;
const audioAttributes = attributes
.find((a: any): a is GramJs.DocumentAttributeAudio => a instanceof GramJs.DocumentAttributeAudio);
if (!audioAttributes) {
return undefined;
}
const {
duration,
title,
performer,
} = audioAttributes;
return {
id: String(id),
mimeType,
duration,
fileName: getFilenameFromDocument(document, 'audio'),
title,
performer,
size: size.toJSNumber(),
};
}
function buildVideo(media: GramJs.TypeMessageMedia): ApiVideo | undefined { function buildVideo(media: GramJs.TypeMessageMedia): ApiVideo | undefined {
if ( if (
!(media instanceof GramJs.MessageMediaDocument) !(media instanceof GramJs.MessageMediaDocument)
@ -653,9 +686,13 @@ export function buildWebPage(media: GramJs.TypeMessageMedia): ApiWebPage | undef
} = media.webpage; } = media.webpage;
let video; let video;
let audio;
if (document instanceof GramJs.Document && document.mimeType.startsWith('video/')) { if (document instanceof GramJs.Document && document.mimeType.startsWith('video/')) {
video = buildVideoFromDocument(document); video = buildVideoFromDocument(document);
} }
if (document instanceof GramJs.Document && document.mimeType.startsWith('audio/')) {
audio = buildAudioFromDocument(document);
}
let story: ApiWebPageStoryData | undefined; let story: ApiWebPageStoryData | undefined;
const attributeStory = attributes const attributeStory = attributes
?.find((a: any): a is GramJs.WebPageAttributeStory => a instanceof GramJs.WebPageAttributeStory); ?.find((a: any): a is GramJs.WebPageAttributeStory => a instanceof GramJs.WebPageAttributeStory);
@ -683,8 +720,9 @@ export function buildWebPage(media: GramJs.TypeMessageMedia): ApiWebPage | undef
'duration', 'duration',
]), ]),
photo: photo instanceof GramJs.Photo ? buildApiPhoto(photo) : undefined, photo: photo instanceof GramJs.Photo ? buildApiPhoto(photo) : undefined,
document: !video && document ? buildApiDocument(document) : undefined, document: !video && !audio && document ? buildApiDocument(document) : undefined,
video, video,
audio,
story, story,
}; };
} }

View File

@ -329,6 +329,7 @@ export interface ApiWebPage {
title?: string; title?: string;
description?: string; description?: string;
photo?: ApiPhoto; photo?: ApiPhoto;
audio?: ApiAudio;
duration?: number; duration?: number;
document?: ApiDocument; document?: ApiDocument;
video?: ApiVideo; video?: ApiVideo;

View File

@ -16,6 +16,7 @@ import {
getMediaTransferState, getMediaTransferState,
getMessageMediaFormat, getMessageMediaFormat,
getMessageMediaHash, getMessageMediaHash,
getMessageWebPageAudio,
hasMessageTtl, hasMessageTtl,
isMessageLocal, isMessageLocal,
isOwnMessage, isOwnMessage,
@ -67,7 +68,7 @@ type OwnProps = {
isTranscriptionError?: boolean; isTranscriptionError?: boolean;
autoPlay?: boolean; autoPlay?: boolean;
onHideTranscription?: (isHidden: boolean) => void; onHideTranscription?: (isHidden: boolean) => void;
onPlay: (messageId: number, chatId: string) => void; onPlay?: (messageId: number, chatId: string) => void;
onPause?: NoneToVoidFunction; onPause?: NoneToVoidFunction;
onReadMedia?: () => void; onReadMedia?: () => void;
onCancelUpload?: () => void; onCancelUpload?: () => void;
@ -112,9 +113,10 @@ const Audio: FC<OwnProps> = ({
const { const {
content: { content: {
audio, voice, video, audio: contentAudio, voice, video,
}, isMediaUnread, }, isMediaUnread,
} = message; } = message;
const audio = contentAudio || getMessageWebPageAudio(message);
const isVoice = Boolean(voice || video); const isVoice = Boolean(voice || video);
const isSeeking = useRef<boolean>(false); const isSeeking = useRef<boolean>(false);
// eslint-disable-next-line no-null/no-null // eslint-disable-next-line no-null/no-null
@ -145,7 +147,7 @@ const Audio: FC<OwnProps> = ({
const handleForcePlay = useLastCallback(() => { const handleForcePlay = useLastCallback(() => {
setIsActivated(true); setIsActivated(true);
onPlay(message.id, message.chatId); onPlay?.(message.id, message.chatId);
}); });
const handleTrackChange = useLastCallback(() => { const handleTrackChange = useLastCallback(() => {
@ -228,7 +230,7 @@ const Audio: FC<OwnProps> = ({
} }
if (!isPlaying) { if (!isPlaying) {
onPlay(message.id, message.chatId); onPlay?.(message.id, message.chatId);
} }
getActions().setAudioPlayerOrigin({ origin }); getActions().setAudioPlayerOrigin({ origin });

View File

@ -642,7 +642,7 @@
} }
&.has-replies:not(.custom-shape), &.has-replies:not(.custom-shape),
&.text { &.text:not(.web-page) {
.media-inner, .media-inner,
.Album { .Album {
--border-bottom-left-radius: 0; --border-bottom-left-radius: 0;

View File

@ -1228,6 +1228,7 @@ const Message: FC<OwnProps & StateProps> = ({
backgroundEmojiId={sender?.color?.backgroundEmojiId} backgroundEmojiId={sender?.color?.backgroundEmojiId}
shouldWarnAboutSvg={shouldWarnAboutSvg} shouldWarnAboutSvg={shouldWarnAboutSvg}
autoLoadFileMaxSizeMb={autoLoadFileMaxSizeMb} autoLoadFileMaxSizeMb={autoLoadFileMaxSizeMb}
onAudioPlay={handleAudioPlay}
onMediaClick={handleMediaClick} onMediaClick={handleMediaClick}
onCancelMediaTransfer={handleCancelUpload} onCancelMediaTransfer={handleCancelUpload}
/> />

View File

@ -10,6 +10,10 @@
position: relative; position: relative;
overflow: hidden; overflow: hidden;
&.with-video {
padding: 0.1875rem 0.375rem;
}
&.with-document { &.with-document {
--file-icon-border-color: var(--accent-background-color); --file-icon-border-color: var(--accent-background-color);
} }
@ -77,13 +81,15 @@
.media-inner { .media-inner {
margin: 0 !important; margin: 0 !important;
margin-bottom: 0.375rem !important;
&, &,
& img, & img,
& canvas, & canvas,
&.small-image img { &.small-image img {
border-radius: var(--border-radius-messages-small) !important; --border-top-left-radius: var(--border-radius-messages-small);
--border-top-right-radius: var(--border-radius-messages-small);
--border-bottom-left-radius: var(--border-radius-messages-small);
--border-bottom-right-radius: var(--border-radius-messages-small);
} }
&.square-image { &.square-image {
@ -100,11 +106,6 @@
} }
} }
&.with-video .media-inner {
margin-top: 0.5rem !important;
margin-bottom: 1rem !important;
}
.message-content:not(.has-reactions) & { .message-content:not(.has-reactions) & {
margin-bottom: calc(var(--message-meta-height)) !important; margin-bottom: calc(var(--message-meta-height)) !important;
} }

View File

@ -4,7 +4,7 @@ import { getActions } from '../../../global';
import type { ApiMessage, ApiTypeStory } from '../../../api/types'; import type { ApiMessage, ApiTypeStory } from '../../../api/types';
import type { ObserveFn } from '../../../hooks/useIntersectionObserver'; import type { ObserveFn } from '../../../hooks/useIntersectionObserver';
import type { ISettings } from '../../../types'; import { AudioOrigin, type ISettings } from '../../../types';
import { getMessageWebPage } from '../../../global/helpers'; import { getMessageWebPage } from '../../../global/helpers';
import buildClassName from '../../../util/buildClassName'; import buildClassName from '../../../util/buildClassName';
@ -18,6 +18,7 @@ import useEnsureStory from '../../../hooks/useEnsureStory';
import useLang from '../../../hooks/useLang'; import useLang from '../../../hooks/useLang';
import useLastCallback from '../../../hooks/useLastCallback'; import useLastCallback from '../../../hooks/useLastCallback';
import Audio from '../../common/Audio';
import Document from '../../common/Document'; import Document from '../../common/Document';
import EmojiIconBackground from '../../common/embedded/EmojiIconBackground'; import EmojiIconBackground from '../../common/embedded/EmojiIconBackground';
import SafeLink from '../../common/SafeLink'; import SafeLink from '../../common/SafeLink';
@ -47,8 +48,9 @@ type OwnProps = {
story?: ApiTypeStory; story?: ApiTypeStory;
shouldWarnAboutSvg?: boolean; shouldWarnAboutSvg?: boolean;
autoLoadFileMaxSizeMb?: number; autoLoadFileMaxSizeMb?: number;
onMediaClick?: () => void; onAudioPlay?: NoneToVoidFunction;
onCancelMediaTransfer?: () => void; onMediaClick?: NoneToVoidFunction;
onCancelMediaTransfer?: NoneToVoidFunction;
}; };
const WebPage: FC<OwnProps> = ({ const WebPage: FC<OwnProps> = ({
@ -68,6 +70,7 @@ const WebPage: FC<OwnProps> = ({
shouldWarnAboutSvg, shouldWarnAboutSvg,
autoLoadFileMaxSizeMb, autoLoadFileMaxSizeMb,
onMediaClick, onMediaClick,
onAudioPlay,
onCancelMediaTransfer, onCancelMediaTransfer,
}) => { }) => {
const { openTelegramLink } = getActions(); const { openTelegramLink } = getActions();
@ -103,6 +106,7 @@ const WebPage: FC<OwnProps> = ({
description, description,
photo, photo,
video, video,
audio,
type, type,
document, document,
} = webPage; } = webPage;
@ -200,6 +204,17 @@ const WebPage: FC<OwnProps> = ({
onCancelUpload={onCancelMediaTransfer} onCancelUpload={onCancelMediaTransfer}
/> />
)} )}
{!inPreview && audio && (
<Audio
theme={theme}
message={message}
origin={AudioOrigin.Inline}
noAvatars={noAvatars}
isDownloading={isDownloading}
onPlay={onAudioPlay}
onCancelUpload={onCancelMediaTransfer}
/>
)}
{!inPreview && document && ( {!inPreview && document && (
<Document <Document
message={message} message={message}
@ -211,6 +226,18 @@ const WebPage: FC<OwnProps> = ({
shouldWarnAboutSvg={shouldWarnAboutSvg} shouldWarnAboutSvg={shouldWarnAboutSvg}
/> />
)} )}
{inPreview && displayUrl && !isArticle && (
<div className="WebPage-text">
{backgroundEmojiId && (
<EmojiIconBackground
emojiDocumentId={backgroundEmojiId}
className="WebPage--background-icons"
/>
)}
<p className="site-name">{displayUrl}</p>
<p className="site-description">{lang('Chat.Empty.LinkPreview')}</p>
</div>
)}
</div> </div>
{quickButtonLangKey && renderQuickButton(quickButtonLangKey)} {quickButtonLangKey && renderQuickButton(quickButtonLangKey)}
</div> </div>

View File

@ -155,6 +155,10 @@ export function getMessageWebPageVideo(message: MediaContainer) {
return getMessageWebPage(message)?.video; return getMessageWebPage(message)?.video;
} }
export function getMessageWebPageAudio(message: MediaContainer) {
return getMessageWebPage(message)?.audio;
}
export function getMessageDocumentVideo(message: MediaContainer) { export function getMessageDocumentVideo(message: MediaContainer) {
return isMessageDocumentVideo(message) ? getMessageDocument(message) : undefined; return isMessageDocumentVideo(message) ? getMessageDocument(message) : undefined;
} }
@ -218,8 +222,9 @@ export function getMessageMediaHash(
const actionPhoto = getMessageActionPhoto(message); const actionPhoto = getMessageActionPhoto(message);
const messageVideo = video || getMessageWebPageVideo(message) || getMessageDocumentVideo(message); const messageVideo = video || getMessageWebPageVideo(message) || getMessageDocumentVideo(message);
const messageDocument = document || getMessageWebPageDocument(message); const messageDocument = document || getMessageWebPageDocument(message);
const messageAudio = audio || getMessageWebPageAudio(message);
const content = actionPhoto || messagePhoto || messageVideo || sticker || audio || voice || messageDocument; const content = actionPhoto || messagePhoto || messageVideo || sticker || messageAudio || voice || messageDocument;
if (!content) { if (!content) {
return undefined; return undefined;
} }
@ -288,13 +293,13 @@ export function getMessageMediaHash(
} }
} }
if (audio) { if (messageAudio) {
switch (target) { switch (target) {
case 'micro': case 'micro':
case 'pictogram': case 'pictogram':
return getAudioHasCover(audio) ? `${base}?size=m` : undefined; return getAudioHasCover(messageAudio) ? `${base}?size=m` : undefined;
case 'inline': case 'inline':
return getVideoOrAudioBaseHash(audio, base); return getVideoOrAudioBaseHash(messageAudio, base);
case 'download': case 'download':
return `${base}?download`; return `${base}?download`;
} }
@ -517,7 +522,7 @@ export function getMessageContentIds(
export function getMediaDuration(message: ApiMessage) { export function getMediaDuration(message: ApiMessage) {
const { audio, voice, video } = getMessageContent(message); const { audio, voice, video } = getMessageContent(message);
const media = audio || voice || video || getMessageWebPageVideo(message); const media = audio || voice || video || getMessageWebPageVideo(message) || getMessageWebPageAudio(message);
if (!media) { if (!media) {
return undefined; return undefined;
} }