fix(app): keep animated embed images animated (#1892)

This commit is contained in:
Hampus authored and GitHub committed 2026-08-24 12:40:41 +02:00
1 parent 50a947a722
commit 3fe6217809
2 files changed
+47 -24

No files matched your search

@@ -2,7 +2,13 @@
import type {Message} from '@app/features/messaging/models/MessagingMessage';
import {getEmbedMediaDimensions} from '@app/features/messaging/utils/MediaDimensionConfig';
import {buildMediaProxyURL, resolvePreferredImageFormat} from '@app/features/messaging/utils/MediaProxyUtils';
import {
buildAnimatedImageProxyURL,
buildMediaProxyURL,
mediaDevicePixelRatio,
resolvePreferredImageFormat,
stripMediaProxyParams,
} from '@app/features/messaging/utils/MediaProxyUtils';
import {Logger} from '@app/features/platform/utils/AppLogger';
import * as ColorUtils from '@app/features/theme/utils/ColorUtils';
import {createCalculator, type MediaDimensionCalculator} from '@app/features/ui/utils/DimensionUtils';
@@ -128,15 +134,40 @@ export const calculateMediaDimensions = (media: Required<EmbedMedia>): MediaDime
const {dimensions} = mediaCalculator.calculate({width: media.width, height: media.height});
return dimensions;
};
const toEmbedMediaPixels = (layoutWidth: number, layoutHeight: number): MediaDimensions => {
const ratio = mediaDevicePixelRatio();
return {width: Math.round(layoutWidth * ratio), height: Math.round(layoutHeight * ratio)};
};
export const getOptimizedMediaURL = (proxyURL: string, width: number, height: number, contentType?: string): string => {
const targetWidth = Math.round(width * 2);
const targetHeight = Math.round(height * 2);
const target = toEmbedMediaPixels(width, height);
return buildMediaProxyURL(proxyURL, {
format: resolvePreferredImageFormat(contentType),
width: targetWidth,
height: targetHeight,
width: target.width,
height: target.height,
});
};
export const getOptimizedAnimatedMediaURL = (proxyURL: string, width: number, height: number): string => {
if (!proxyURL) return proxyURL;
const target = toEmbedMediaPixels(width, height);
return buildAnimatedImageProxyURL(stripMediaProxyParams(proxyURL), target.width, target.height);
};
export const isAnimatedEmbedMedia = (media: Pick<EmbedMedia, 'content_type' | 'flags'>): boolean => {
if (media.content_type === 'image/gif') return true;
return ((media.flags ?? 0) & MessageAttachmentFlags.IS_ANIMATED) === MessageAttachmentFlags.IS_ANIMATED;
};
export const resolveEmbedImageSource = (
media: Pick<EmbedMedia, 'content_type' | 'flags'> & {proxy_url: string},
width: number,
height: number,
): {src: string; animated: boolean} => {
const animated = isAnimatedEmbedMedia(media);
return {
src: animated
? getOptimizedAnimatedMediaURL(media.proxy_url, width, height)
: getOptimizedMediaURL(media.proxy_url, width, height, media.content_type),
animated,
};
};
export const mediaIdentityKey = (media?: EmbedMedia): string => {
if (!media) return '';
return [
@@ -3,11 +3,11 @@
import {
calculateMediaDimensions,
type EmbedMediaRendererProps,
getOptimizedMediaURL,
getUrlHostname,
isMediaMatureContent,
isValidMedia,
mediaPropsEqual,
resolveEmbedImageSource,
THUMBNAIL_SIZE,
} from '@app/features/channel/components/embeds/channel_embed/ChannelEmbedShared';
import {EmbedGif} from '@app/features/channel/components/embeds/media/EmbedGifv';
@@ -16,10 +16,9 @@ import EmbedVideo from '@app/features/channel/components/embeds/media/EmbedVideo
import {EmbedYouTube} from '@app/features/channel/components/embeds/media/EmbedYouTube';
import {getInlineVideoLayoutConstraints} from '@app/features/channel/components/embeds/media/VideoDimensionUtils';
import {getEmbedMediaDimensions} from '@app/features/messaging/utils/MediaDimensionConfig';
import {buildAnimatedImageProxyURL, buildMediaProxyURL} from '@app/features/messaging/utils/MediaProxyUtils';
import {buildMediaProxyURL} from '@app/features/messaging/utils/MediaProxyUtils';
import messageStyles from '@app/features/theme/styles/Message.module.css';
import FocusRing from '@app/features/ui/focus_ring/FocusRing';
import {MessageAttachmentFlags} from '@fluxer/constants/src/ChannelConstants';
import {observer} from 'mobx-react-lite';
import {type FC, memo} from 'react';
@@ -76,7 +75,6 @@ const EmbedMediaRendererInner: FC<EmbedMediaRendererProps> = observer(
if (isValidMedia(image)) {
const {width, height} = calculateMediaDimensions(image);
const isGif = image.content_type === 'image/gif' || image.url.toLowerCase().endsWith('.gif');
const imageIsAnimated = (image.flags & MessageAttachmentFlags.IS_ANIMATED) === MessageAttachmentFlags.IS_ANIMATED;
if (isGif) {
return (
<FocusRing
@@ -105,6 +103,7 @@ const EmbedMediaRendererInner: FC<EmbedMediaRendererProps> = observer(
</FocusRing>
);
}
const resolvedImageSource = resolveEmbedImageSource(image, width, height);
return (
<FocusRing
within
@@ -112,7 +111,7 @@ const EmbedMediaRendererInner: FC<EmbedMediaRendererProps> = observer(
data-flx="channel.embeds.embed.embed-media-renderer-inner.focus-ring--4"
>
<EmbedImage
src={getOptimizedMediaURL(image.proxy_url, width, height, image.content_type)}
src={resolvedImageSource.src}
originalSrc={image.url}
naturalWidth={image.width}
naturalHeight={image.height}
@@ -128,7 +127,7 @@ const EmbedMediaRendererInner: FC<EmbedMediaRendererProps> = observer(
embedIndex={embedIndex}
onDelete={onDelete}
isPreview={isPreview}
animated={imageIsAnimated}
animated={resolvedImageSource.animated}
alt={image.description ?? undefined}
data-flx="channel.embeds.embed.embed-media-renderer-inner.embed-image"
/>
@@ -138,8 +137,6 @@ const EmbedMediaRendererInner: FC<EmbedMediaRendererProps> = observer(
if (isValidMedia(thumbnail)) {
const {width, height} = calculateMediaDimensions(thumbnail);
const isGif = thumbnail.content_type === 'image/gif' || thumbnail.url.toLowerCase().endsWith('.gif');
const thumbnailIsAnimated =
(thumbnail.flags & MessageAttachmentFlags.IS_ANIMATED) === MessageAttachmentFlags.IS_ANIMATED;
if (isGif) {
return (
<FocusRing
@@ -167,6 +164,7 @@ const EmbedMediaRendererInner: FC<EmbedMediaRendererProps> = observer(
</FocusRing>
);
}
const thumbnailSource = resolveEmbedImageSource(thumbnail, width, height);
return (
<FocusRing
within
@@ -174,7 +172,7 @@ const EmbedMediaRendererInner: FC<EmbedMediaRendererProps> = observer(
data-flx="channel.embeds.embed.embed-media-renderer-inner.focus-ring--6"
>
<EmbedImage
src={getOptimizedMediaURL(thumbnail.proxy_url, width, height, thumbnail.content_type)}
src={thumbnailSource.src}
originalSrc={thumbnail.url}
naturalWidth={thumbnail.width}
naturalHeight={thumbnail.height}
@@ -189,7 +187,7 @@ const EmbedMediaRendererInner: FC<EmbedMediaRendererProps> = observer(
contentHash={thumbnail.content_hash}
embedIndex={embedIndex}
onDelete={onDelete}
animated={thumbnailIsAnimated}
animated={thumbnailSource.animated}
alt={thumbnail.description ?? undefined}
data-flx="channel.embeds.embed.embed-media-renderer-inner.embed-image--2"
/>
@@ -205,9 +203,7 @@ const InlineThumbnailRendererInner: FC<EmbedMediaRendererProps> = observer(
if (!embed.thumbnail || !isValidMedia(embed.thumbnail)) return null;
const thumbnail = embed.thumbnail;
const width = Math.min(THUMBNAIL_SIZE, Math.round((THUMBNAIL_SIZE * thumbnail.width) / thumbnail.height));
const thumbnailIsAnimated =
thumbnail.content_type === 'image/gif' ||
(thumbnail.flags & MessageAttachmentFlags.IS_ANIMATED) === MessageAttachmentFlags.IS_ANIMATED;
const thumbnailSource = resolveEmbedImageSource(thumbnail, width, THUMBNAIL_SIZE);
return (
<FocusRing
within
@@ -215,11 +211,7 @@ const InlineThumbnailRendererInner: FC<EmbedMediaRendererProps> = observer(
data-flx="channel.embeds.embed.inline-thumbnail-renderer-inner.focus-ring"
>
<EmbedImage
src={
thumbnail.content_type === 'image/gif'
? buildAnimatedImageProxyURL(thumbnail.proxy_url, width * 2, THUMBNAIL_SIZE * 2)
: getOptimizedMediaURL(thumbnail.proxy_url, width, THUMBNAIL_SIZE, thumbnail.content_type)
}
src={thumbnailSource.src}
originalSrc={thumbnail.url}
naturalWidth={thumbnail.width}
naturalHeight={thumbnail.height}
@@ -236,7 +228,7 @@ const InlineThumbnailRendererInner: FC<EmbedMediaRendererProps> = observer(
embedIndex={embedIndex}
onDelete={onDelete}
isPreview={isPreview}
animated={thumbnailIsAnimated}
animated={thumbnailSource.animated}
alt={thumbnail.description ?? undefined}
data-flx="channel.embeds.embed.inline-thumbnail-renderer-inner.embed-image"
/>