fix(app): stop overlaying a sharpening canvas in the media viewer (#1875)

This commit is contained in:
Hampus
2026-08-24 12:36:15 +02:00
committed by GitHub
parent 44869b0ce4
commit 61ce8729de
5 changed files with 148 additions and 356 deletions
@@ -20,7 +20,7 @@
pointer-events: none;
}
.imageSpinnerOverlay {
.placeholder {
position: absolute;
inset: 0;
z-index: 1;
@@ -35,18 +35,6 @@
opacity: 0;
}
.sharpCanvasLayer {
position: fixed;
z-index: 2147483000;
background: transparent;
pointer-events: none;
contain: strict;
}
.sharpCanvasSourceHidden {
opacity: 0;
}
.audioPlayerContainer {
width: 100%;
max-width: 42rem;
@@ -21,11 +21,13 @@ import {openExternalUrlWithWarning} from '@app/features/messaging/utils/External
import {createDownloadHandler} from '@app/features/messaging/utils/FileDownloadUtils';
import {formatFileSize} from '@app/features/messaging/utils/FileUtils';
import * as ImageCacheUtils from '@app/features/messaging/utils/ImageCacheUtils';
import {buildStaticGifPreviewURL} from '@app/features/messaging/utils/MediaProxyUtils';
import {
buildMediaProxyURL,
resolvePreferredImageFormat,
stripMediaProxyParams,
} from '@app/features/messaging/utils/MediaProxyUtils';
buildViewerMediaURL,
getBaseProxyURL,
isGifvRenderedAsImage,
isViewerImageItem,
} from '@app/features/messaging/utils/MediaViewerItemUtils';
import {
copyMediaLinkToClipboard,
copyMediaToClipboard,
@@ -40,10 +42,9 @@ import {PortalHostContext} from '@app/features/ui/overlay/PortalHostContext';
import LayerManager from '@app/features/ui/state/LayerManager';
import MediaViewer, {type MediaViewerItem} from '@app/features/ui/state/MediaViewer';
import MobileLayout from '@app/features/ui/state/MobileLayout';
import {getNativePlatformSync, isNativeWindows} from '@app/features/ui/utils/NativeUtils';
import {snapMediaProxyImageSize} from '@app/features/user/utils/AvatarUtils';
import {AudioPlayer} from '@app/features/voice/components/media_player/components/AudioPlayer';
import {VideoPlayer} from '@app/features/voice/components/media_player/components/VideoPlayer';
import {resolveDevicePixelRatio} from '@app/features/voice/DevicePixelRatio';
import {msg} from '@lingui/core/macro';
import {useLingui} from '@lingui/react/macro';
import {untracked} from 'mobx';
@@ -122,111 +123,29 @@ interface MobileMediaOptionsSheetProps {
sourceChannel?: Channel | null;
}
function getBaseProxyURL(src: string): string {
if (src.startsWith('blob:')) {
return src;
}
return stripMediaProxyParams(src);
}
const MEDIA_VIEWER_THUMBNAIL_SIZE = 44;
function normalizeContentType(contentType?: string): string | undefined {
return contentType?.toLowerCase().split(';')[0]?.trim() || undefined;
}
type RenderReadyImageProps = ImgHTMLAttributes<HTMLImageElement>;
function inferImageContentTypeFromURL(src: string): string | undefined {
try {
const path = new URL(src).pathname.toLowerCase();
if (path.endsWith('.png')) return 'image/png';
if (path.endsWith('.jpg') || path.endsWith('.jpeg')) return 'image/jpeg';
if (path.endsWith('.webp')) return 'image/webp';
if (path.endsWith('.gif')) return 'image/gif';
if (path.endsWith('.svg')) return 'image/svg+xml';
if (path.endsWith('.avif')) return 'image/avif';
if (path.endsWith('.jxl')) return 'image/jxl';
} catch {
return undefined;
}
return undefined;
}
function resolveViewerStaticImageFormat(contentType: string | undefined, src: string): 'webp' | undefined {
const normalizedContentType = normalizeContentType(contentType) ?? inferImageContentTypeFromURL(src);
switch (normalizedContentType) {
case 'image/png':
case 'image/jpeg':
case 'image/webp':
case 'image/gif':
case 'image/svg+xml':
return undefined;
default:
return resolvePreferredImageFormat(normalizedContentType);
}
}
function buildViewerStaticImageURL(item: MediaViewerItem): string {
if (item.src.startsWith('blob:')) {
return item.src;
}
const baseProxyURL = getBaseProxyURL(item.src);
return buildMediaProxyURL(baseProxyURL, {
format: resolveViewerStaticImageFormat(item.contentType, baseProxyURL),
});
}
const SHARP_CANVAS_MAX_SIDE = 8192;
const SHARP_CANVAS_MAX_PIXELS = 32_000_000;
const USE_SHARP_CANVAS_FOR_PLATFORM = isNativeWindows(getNativePlatformSync());
const MEDIA_VIEWER_ANIMATION_SESSION_PARAM = 'flx_viewer_session';
type RenderReadyImageProps = ImgHTMLAttributes<HTMLImageElement> & {
sharpenWhenZoomed?: boolean;
};
function shouldUseSharpCanvasForItem(item: MediaViewerItem, src: string): boolean {
const contentType = normalizeContentType(item.contentType) ?? inferImageContentTypeFromURL(src);
return USE_SHARP_CANVAS_FOR_PLATFORM && item.type === 'image' && !item.animated && contentType !== 'image/svg+xml';
}
function appendMediaViewerAnimationSession(src: string, sessionId: number): string {
if (!src || src.startsWith('blob:')) {
return src;
}
try {
const url = new URL(src);
url.searchParams.set(MEDIA_VIEWER_ANIMATION_SESSION_PARAM, sessionId.toString());
return url.toString();
} catch {
return src;
}
}
function getSharpCanvasSize(width: number, height: number, dpr: number): {height: number; width: number} | null {
const canvasWidth = Math.max(1, Math.round(width * dpr));
const canvasHeight = Math.max(1, Math.round(height * dpr));
if (
canvasWidth > SHARP_CANVAS_MAX_SIDE ||
canvasHeight > SHARP_CANVAS_MAX_SIDE ||
canvasWidth * canvasHeight > SHARP_CANVAS_MAX_PIXELS
) {
return null;
}
return {height: canvasHeight, width: canvasWidth};
}
const isImagePainted = (image: HTMLImageElement | null): image is HTMLImageElement =>
image?.complete === true && image.naturalWidth > 0;
const RenderReadyImage: FC<RenderReadyImageProps> = ({
className,
onError,
onLoad,
sharpenWhenZoomed = false,
src,
...imageProps
}: RenderReadyImageProps) => {
const [isReady, setIsReady] = useState(false);
const [canvasPortalTarget, setCanvasPortalTarget] = useState<HTMLElement | null>(null);
const [isSharpCanvasActive, setIsSharpCanvasActive] = useState(false);
const [isSharpCanvasVisible, setIsSharpCanvasVisible] = useState(false);
const [renderedSource, setRenderedSource] = useState(src);
const [decodedBeforeSwap, setDecodedBeforeSwap] = useState(() => ImageCacheUtils.hasImage(src));
if (renderedSource !== src) {
setRenderedSource(src);
setDecodedBeforeSwap(ImageCacheUtils.hasImage(src));
}
const shouldMountPlaceholder = !decodedBeforeSwap && !isReady;
const imageRef = useRef<HTMLImageElement | null>(null);
const canvasRef = useRef<HTMLCanvasElement | null>(null);
const readyTokenRef = useRef(0);
const markReady = useCallback((image: HTMLImageElement) => {
const token = ++readyTokenRef.current;
@@ -248,8 +167,10 @@ const RenderReadyImage: FC<RenderReadyImageProps> = ({
}, []);
const handleLoad = useCallback(
(event: SyntheticEvent<HTMLImageElement>) => {
const image = event.currentTarget;
ImageCacheUtils.rememberImage(image.getAttribute('src'), image);
onLoad?.(event);
markReady(event.currentTarget);
markReady(image);
},
[markReady, onLoad],
);
@@ -260,170 +181,34 @@ const RenderReadyImage: FC<RenderReadyImageProps> = ({
},
[onError],
);
useEffect(() => {
useLayoutEffect(() => {
readyTokenRef.current += 1;
setIsReady(false);
const image = imageRef.current;
if (image?.complete && image.naturalWidth > 0) {
markReady(image);
if (!isImagePainted(image)) {
setIsReady(false);
return () => {
readyTokenRef.current += 1;
};
}
ImageCacheUtils.rememberImage(image.getAttribute('src'), image);
setIsReady(true);
markReady(image);
return () => {
readyTokenRef.current += 1;
};
}, [markReady, src]);
const drawSharpCanvas = useCallback((): boolean => {
const image = imageRef.current;
const canvas = canvasRef.current;
if (!image || !canvas || !image.complete || image.naturalWidth <= 0 || image.naturalHeight <= 0) return false;
const ownerWindow = image.ownerDocument.defaultView;
if (!ownerWindow) return false;
const imageRect = image.getBoundingClientRect();
const clipElement = image.closest('[data-flx*="pan-zoom-surface"]') as HTMLElement | null;
const clipRect = clipElement?.getBoundingClientRect();
const visibleLeft = Math.max(imageRect.left, clipRect?.left ?? imageRect.left);
const visibleTop = Math.max(imageRect.top, clipRect?.top ?? imageRect.top);
const visibleRight = Math.min(imageRect.right, clipRect?.right ?? imageRect.right);
const visibleBottom = Math.min(imageRect.bottom, clipRect?.bottom ?? imageRect.bottom);
const visibleWidth = visibleRight - visibleLeft;
const visibleHeight = visibleBottom - visibleTop;
if (imageRect.width <= 0 || imageRect.height <= 0 || visibleWidth <= 0 || visibleHeight <= 0) return false;
const canvasSize = getSharpCanvasSize(visibleWidth, visibleHeight, resolveDevicePixelRatio(ownerWindow));
if (!canvasSize) return false;
canvas.style.left = `${visibleLeft}px`;
canvas.style.top = `${visibleTop}px`;
canvas.style.width = `${visibleWidth}px`;
canvas.style.height = `${visibleHeight}px`;
if (canvas.width !== canvasSize.width) canvas.width = canvasSize.width;
if (canvas.height !== canvasSize.height) canvas.height = canvasSize.height;
const context = canvas.getContext('2d');
if (!context) return false;
const sourceX = ((visibleLeft - imageRect.left) / imageRect.width) * image.naturalWidth;
const sourceY = ((visibleTop - imageRect.top) / imageRect.height) * image.naturalHeight;
const sourceWidth = (visibleWidth / imageRect.width) * image.naturalWidth;
const sourceHeight = (visibleHeight / imageRect.height) * image.naturalHeight;
context.imageSmoothingEnabled = true;
context.imageSmoothingQuality = 'high';
context.clearRect(0, 0, canvas.width, canvas.height);
try {
context.drawImage(image, sourceX, sourceY, sourceWidth, sourceHeight, 0, 0, canvas.width, canvas.height);
} catch {
return false;
}
return true;
}, []);
useEffect(() => {
if (!sharpenWhenZoomed) {
setCanvasPortalTarget(null);
return;
}
setCanvasPortalTarget(imageRef.current?.ownerDocument.body ?? null);
}, [sharpenWhenZoomed, src]);
useEffect(() => {
if (!sharpenWhenZoomed || !isReady) {
setIsSharpCanvasActive(false);
return;
}
const image = imageRef.current;
const ownerWindow = image?.ownerDocument.defaultView;
if (!image || !ownerWindow) {
setIsSharpCanvasActive(false);
return;
}
const surface = image.closest('[data-flx*="pan-zoom-surface"], [data-zoom-state]') as HTMLElement | null;
const mediaContainer = image.closest(
'[data-flx="messaging.media-modal.wrapped-children.media-container"]',
) as HTMLElement | null;
const updateActiveState = () => {
setIsSharpCanvasActive(
surface?.getAttribute('data-zoom-state') === 'zoomed' &&
mediaContainer?.getAttribute('data-media-rotation-active') !== 'true',
);
};
updateActiveState();
const observer = new ownerWindow.MutationObserver(updateActiveState);
if (surface) observer.observe(surface, {attributes: true, attributeFilter: ['data-zoom-state']});
if (mediaContainer) {
observer.observe(mediaContainer, {attributes: true, attributeFilter: ['data-media-rotation-active']});
}
return () => observer.disconnect();
}, [isReady, sharpenWhenZoomed, src]);
useLayoutEffect(() => {
if (!isReady || !isSharpCanvasActive || !canvasPortalTarget) {
setIsSharpCanvasVisible(false);
return;
}
const image = imageRef.current;
const canvas = canvasRef.current;
const ownerWindow = image?.ownerDocument.defaultView;
if (!image || !canvas || !ownerWindow) {
setIsSharpCanvasVisible(false);
return;
}
let animationFrame: number | null = null;
let disposed = false;
const scheduleDraw = () => {
if (disposed || animationFrame != null) return;
animationFrame = ownerWindow.requestAnimationFrame(() => {
animationFrame = null;
if (disposed) return;
setIsSharpCanvasVisible(drawSharpCanvas());
});
};
const surface = image.closest('[data-flx*="pan-zoom-surface"], [data-zoom-state]') as HTMLElement | null;
const mutationObserver = new ownerWindow.MutationObserver(scheduleDraw);
if (surface) {
mutationObserver.observe(surface, {
subtree: true,
attributes: true,
attributeFilter: ['style', 'class', 'data-zoom-state', 'data-dragging'],
});
}
const ResizeObserverConstructor = ownerWindow.ResizeObserver;
const resizeObserver = ResizeObserverConstructor ? new ResizeObserverConstructor(scheduleDraw) : null;
resizeObserver?.observe(image);
if (surface) resizeObserver?.observe(surface);
ownerWindow.addEventListener('resize', scheduleDraw);
scheduleDraw();
return () => {
disposed = true;
if (animationFrame != null) {
ownerWindow.cancelAnimationFrame(animationFrame);
}
mutationObserver.disconnect();
resizeObserver?.disconnect();
ownerWindow.removeEventListener('resize', scheduleDraw);
};
}, [canvasPortalTarget, drawSharpCanvas, isReady, isSharpCanvasActive, src]);
const imageClassName =
[
className,
isReady ? undefined : styles.imagePending,
isReady && isSharpCanvasActive && isSharpCanvasVisible ? styles.sharpCanvasSourceHidden : undefined,
]
.filter(Boolean)
.join(' ') || undefined;
const imageClassName = [className, isReady ? undefined : styles.imagePending].filter(Boolean).join(' ') || undefined;
return (
<>
{!isReady && (
{shouldMountPlaceholder && (
<span
className={styles.imageSpinnerOverlay}
className={styles.placeholder}
aria-hidden="true"
data-flx="messaging.media-viewer-modal.render-ready-image.image-spinner-overlay"
data-flx="messaging.media-viewer-modal.render-ready-image.placeholder"
>
<Spinner size="large" data-flx="messaging.media-viewer-modal.render-ready-image.spinner" />
</span>
)}
{isReady &&
isSharpCanvasActive &&
canvasPortalTarget &&
createPortal(
<canvas
ref={canvasRef}
className={styles.sharpCanvasLayer}
data-flx="messaging.media-viewer-modal.render-ready-image.sharp-canvas"
/>,
canvasPortalTarget,
)}
<img
data-flx="messaging.media-viewer-modal.render-ready-image.img"
{...imageProps}
@@ -488,12 +273,11 @@ const MobileMediaOptionsSheet: FC<MobileMediaOptionsSheetProps> = observer(funct
});
const MediaViewerModalComponent: FC = observer(() => {
const {i18n} = useLingui();
const {isOpen, items, currentIndex, sessionId, channelId, messageId, message, sourceChannel} = MediaViewer;
const {isOpen, items, currentIndex, channelId, messageId, message, sourceChannel} = MediaViewer;
const {enabled: isMobile} = MobileLayout;
const [isMediaMenuOpen, setIsMediaMenuOpen] = useState(false);
const currentItem = items[currentIndex];
const currentGifvIsActualGif =
currentItem?.type === 'gifv' && (currentItem.src.endsWith('.gif') || currentItem.originalSrc.endsWith('.gif'));
const currentGifvIsActualGif = currentItem != null && isGifvRenderedAsImage(currentItem);
useBottomSheetBackHandler(isOpen, MediaViewerCommands.closeMediaViewer);
useEffect(() => {
if (!isOpen) return;
@@ -504,36 +288,17 @@ const MediaViewerModalComponent: FC = observer(() => {
}, [isOpen]);
useEffect(() => {
if (!isOpen || items.length <= 1) return;
const preloadIndices = [currentIndex - 1, currentIndex + 1].filter(
(i) => i >= 0 && i < items.length && i !== currentIndex,
);
for (const idx of preloadIndices) {
const item = items[idx];
if (!item) continue;
if (item.type === 'image' || item.type === 'gif') {
const isItemBlob = item.src.startsWith('blob:');
if (isItemBlob) continue;
const baseProxyURL = getBaseProxyURL(item.src);
const shouldRequestAnimated = item.animated || item.type === 'gif';
let preloadSrc: string;
if (shouldRequestAnimated) {
preloadSrc = buildMediaProxyURL(baseProxyURL, {
format: resolvePreferredImageFormat(item.contentType),
animated: true,
});
} else {
preloadSrc = buildViewerStaticImageURL(item);
}
if (!ImageCacheUtils.hasImage(preloadSrc)) {
ImageCacheUtils.loadImage(preloadSrc, () => {});
}
} else if (item.type === 'gifv' || item.type === 'video') {
const video = document.createElement('video');
video.preload = 'metadata';
video.src = item.src;
video.load();
}
}
const count = items.length;
const adjacentIndices =
count === 2 ? [(currentIndex + 1) % count] : [(currentIndex - 1 + count) % count, (currentIndex + 1) % count];
const unpinCallbacks = adjacentIndices.map((index) => {
const item = items[index];
if (!item || !isViewerImageItem(item) || item.src.startsWith('blob:')) return () => {};
return ImageCacheUtils.pinImage(buildViewerMediaURL(item));
});
return () => {
for (const unpin of unpinCallbacks) unpin();
};
}, [isOpen, currentIndex, items]);
const defaultName = useMemo(() => {
if (!currentItem) return '';
@@ -705,58 +470,28 @@ const MediaViewerModalComponent: FC = observer(() => {
onForwardSuccess: handleForwardSuccess,
});
}, [forwardMediaSelection, message, sourceChannel]);
const isBlob = currentItem?.src.startsWith('blob:');
const imageSrc = useMemo(() => {
if (!currentItem) return '';
if (isBlob) {
return currentItem.src;
}
const baseProxyURL = getBaseProxyURL(currentItem.src);
const shouldRequestAnimated = currentItem.animated || currentItem.type === 'gif';
const shouldStartAnimationFromViewerOpen =
currentItem.type === 'gif' || currentGifvIsActualGif || (currentItem.type === 'image' && currentItem.animated);
if (shouldRequestAnimated) {
const animatedSrc = buildMediaProxyURL(baseProxyURL, {
format: resolvePreferredImageFormat(currentItem.contentType),
animated: true,
});
return shouldStartAnimationFromViewerOpen
? appendMediaViewerAnimationSession(animatedSrc, sessionId)
: animatedSrc;
}
if (currentItem.type === 'gifv' || currentItem.type === 'video' || currentItem.type === 'audio') {
return baseProxyURL;
}
return buildViewerStaticImageURL(currentItem);
}, [currentGifvIsActualGif, currentItem, isBlob, sessionId]);
const thumbnails = useMemo(
() =>
items.map((item, index) => {
const name =
item.filename ||
item.originalSrc.split('/').pop()?.split('?')[0] ||
i18n._(ATTACHMENT_DESCRIPTOR, {index1: index + 1});
if ((item.type === 'image' || item.type === 'gif' || item.animated) && !item.src.startsWith('blob:')) {
const baseProxyURL = getBaseProxyURL(item.src);
return {
src: buildMediaProxyURL(baseProxyURL, {
format: resolvePreferredImageFormat(item.contentType),
width: 320,
height: 320,
animated: Boolean(item.animated || item.type === 'gif'),
}),
alt: name,
type: item.type,
};
}
const imageSrc = useMemo(() => (currentItem ? buildViewerMediaURL(currentItem) : ''), [currentItem]);
const thumbnails = useMemo(() => {
const snappedThumbnailSize = snapMediaProxyImageSize(MEDIA_VIEWER_THUMBNAIL_SIZE);
return items.map((item, index) => {
const name =
item.filename ||
item.originalSrc.split('/').pop()?.split('?')[0] ||
i18n._(ATTACHMENT_DESCRIPTOR, {index1: index + 1});
if ((item.type === 'image' || item.type === 'gif' || item.animated) && !item.src.startsWith('blob:')) {
return {
src: item.src,
src: buildStaticGifPreviewURL(item.src, snappedThumbnailSize, snappedThumbnailSize),
alt: name,
type: item.type,
};
}),
[items, i18n.locale],
);
}
return {
src: item.src,
alt: name,
type: item.type,
};
});
}, [items, i18n.locale]);
if (!isOpen || !currentItem) {
return null;
}
@@ -854,6 +589,7 @@ const MediaViewerModalComponent: FC = observer(() => {
width={currentItem.naturalWidth}
height={currentItem.naturalHeight}
duration={currentItem.duration}
initialTime={currentItem.initialTime}
autoPlay
fillContainer
isMobile={isMobile}
@@ -895,7 +631,6 @@ const MediaViewerModalComponent: FC = observer(() => {
width={currentItem.naturalWidth}
height={currentItem.naturalHeight}
className={styles.image}
sharpenWhenZoomed={shouldUseSharpCanvasForItem(currentItem, imageSrc)}
style={{
width: 'auto',
height: 'auto',
@@ -1,5 +1,10 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import {
buildMediaProxyURL,
resolvePreferredImageFormat,
stripMediaProxyParams,
} from '@app/features/messaging/utils/MediaProxyUtils';
import type {MediaViewerItem} from '@app/features/ui/state/MediaViewer';
import {MessageAttachmentFlags} from '@fluxer/constants/src/ChannelConstants';
import type {MessageAttachment} from '@fluxer/schema/src/domains/message/MessageResponseSchemas';
@@ -71,3 +76,79 @@ export function findViewerItemIndex(items: ReadonlyArray<MediaViewerItem>, attac
items.findIndex((item) => item.attachmentId === attachmentId),
);
}
export function getBaseProxyURL(src: string): string {
if (src.startsWith('blob:')) {
return src;
}
return stripMediaProxyParams(src);
}
function normalizeContentType(contentType?: string): string | undefined {
return contentType?.toLowerCase().split(';')[0]?.trim() || undefined;
}
function inferImageContentTypeFromURL(src: string): string | undefined {
try {
const path = new URL(src).pathname.toLowerCase();
if (path.endsWith('.png')) return 'image/png';
if (path.endsWith('.jpg') || path.endsWith('.jpeg')) return 'image/jpeg';
if (path.endsWith('.webp')) return 'image/webp';
if (path.endsWith('.gif')) return 'image/gif';
if (path.endsWith('.svg')) return 'image/svg+xml';
if (path.endsWith('.avif')) return 'image/avif';
if (path.endsWith('.jxl')) return 'image/jxl';
} catch {
return undefined;
}
return undefined;
}
function resolveViewerStaticImageFormat(contentType: string | undefined, src: string): 'webp' | undefined {
const normalizedContentType = normalizeContentType(contentType) ?? inferImageContentTypeFromURL(src);
switch (normalizedContentType) {
case 'image/png':
case 'image/jpeg':
case 'image/webp':
case 'image/gif':
case 'image/svg+xml':
return undefined;
default:
return resolvePreferredImageFormat(normalizedContentType);
}
}
function buildViewerStaticImageURL(item: MediaViewerItem): string {
if (item.src.startsWith('blob:')) {
return item.src;
}
const baseProxyURL = getBaseProxyURL(item.src);
return buildMediaProxyURL(baseProxyURL, {
format: resolveViewerStaticImageFormat(item.contentType, baseProxyURL),
});
}
export function isGifvRenderedAsImage(item: MediaViewerItem): boolean {
return item.type === 'gifv' && (item.src.endsWith('.gif') || item.originalSrc.endsWith('.gif'));
}
export function isViewerImageItem(item: MediaViewerItem): boolean {
return item.type === 'image' || item.type === 'gif' || isGifvRenderedAsImage(item);
}
export function buildViewerMediaURL(item: MediaViewerItem): string {
if (item.src.startsWith('blob:')) {
return item.src;
}
const baseProxyURL = getBaseProxyURL(item.src);
if (item.animated || item.type === 'gif') {
return buildMediaProxyURL(baseProxyURL, {
format: resolvePreferredImageFormat(item.contentType),
animated: true,
});
}
if (item.type === 'gifv' || item.type === 'video' || item.type === 'audio') {
return baseProxyURL;
}
return buildViewerStaticImageURL(item);
}
@@ -28,14 +28,4 @@ describe('MediaViewer state', () => {
expect(Object.isFrozen(MediaViewer.items)).toBe(true);
expect(Object.isFrozen(MediaViewer.items[0])).toBe(true);
});
it('increments the viewer session for every open', () => {
const initialSessionId = MediaViewer.sessionId;
MediaViewer.open([createItem()], 0);
const firstSessionId = MediaViewer.sessionId;
MediaViewer.close();
MediaViewer.open([createItem()], 0);
expect(firstSessionId).toBe(initialSessionId + 1);
expect(MediaViewer.sessionId).toBe(firstSessionId + 1);
});
});
@@ -32,7 +32,6 @@ class MediaViewer {
isOpen: boolean = false;
items: ReadonlyArray<MediaViewerItem> = [];
currentIndex: number = 0;
sessionId: number = 0;
channelId?: string = undefined;
messageId?: string = undefined;
message?: Message = undefined;
@@ -59,7 +58,6 @@ class MediaViewer {
sourceChannel?: Channel | null,
): void {
this.isOpen = true;
this.sessionId += 1;
this.items = copyMediaViewerItems(items);
this.currentIndex = currentIndex;
this.channelId = channelId;