Show image viewer when clicking url preview thumbnail (#2309)

* Show large image overlay when clicking url preview thumbnail

* Move image overlay into its own component

* Move ImageOverlay props into extended type

* Remove export for internal type
This commit is contained in:
LeaPhant
2026-03-14 06:34:55 +01:00
committed by GitHub
parent 6a05ff5840
commit 7570a84dfd
3 changed files with 135 additions and 62 deletions

View File

@@ -1,6 +1,7 @@
import React, { useCallback, useEffect, useRef, useState } from 'react';
import React, { ReactNode, useCallback, useEffect, useRef, useState } from 'react';
import { IPreviewUrlResponse } from 'matrix-js-sdk';
import { Box, Icon, IconButton, Icons, Scroll, Spinner, Text, as, color, config } from 'folds';
import { RenderViewerProps, ImageOverlay } from '../ImageOverlay';
import { AsyncStatus, useAsyncCallback } from '../../hooks/useAsyncCallback';
import { useMatrixClient } from '../../hooks/useMatrixClient';
import { UrlPreview, UrlPreviewContent, UrlPreviewDescription, UrlPreviewImg } from './UrlPreview';
@@ -15,72 +16,94 @@ import { useMediaAuthentication } from '../../hooks/useMediaAuthentication';
const linkStyles = { color: color.Success.Main };
export const UrlPreviewCard = as<'div', { url: string; ts: number }>(
({ url, ts, ...props }, ref) => {
const mx = useMatrixClient();
const useAuthentication = useMediaAuthentication();
const [previewStatus, loadPreview] = useAsyncCallback(
useCallback(() => mx.getUrlPreview(url, ts), [url, ts, mx])
export const UrlPreviewCard = as<
'div',
{ url: string; ts: number; renderViewer: (props: RenderViewerProps) => ReactNode }
>(({ url, ts, renderViewer, ...props }, ref) => {
const mx = useMatrixClient();
const useAuthentication = useMediaAuthentication();
const [viewer, setViewer] = useState(false);
const [previewStatus, loadPreview] = useAsyncCallback(
useCallback(() => mx.getUrlPreview(url, ts), [url, ts, mx])
);
useEffect(() => {
loadPreview();
}, [loadPreview]);
if (previewStatus.status === AsyncStatus.Error) return null;
const renderContent = (prev: IPreviewUrlResponse) => {
const thumbUrl = mxcUrlToHttp(
mx,
prev['og:image'] || '',
useAuthentication,
256,
256,
'scale',
false
);
useEffect(() => {
loadPreview();
}, [loadPreview]);
if (previewStatus.status === AsyncStatus.Error) return null;
const renderContent = (prev: IPreviewUrlResponse) => {
const imgUrl = mxcUrlToHttp(
mx,
prev['og:image'] || '',
useAuthentication,
256,
256,
'scale',
false
);
return (
<>
{imgUrl && <UrlPreviewImg src={imgUrl} alt={prev['og:title']} title={prev['og:title']} />}
<UrlPreviewContent>
<Text
style={linkStyles}
truncate
as="a"
href={url}
target="_blank"
rel="noreferrer"
size="T200"
priority="300"
>
{typeof prev['og:site_name'] === 'string' && `${prev['og:site_name']} | `}
{tryDecodeURIComponent(url)}
</Text>
<Text truncate priority="400">
<b>{prev['og:title']}</b>
</Text>
<Text size="T200" priority="300">
<UrlPreviewDescription>{prev['og:description']}</UrlPreviewDescription>
</Text>
</UrlPreviewContent>
</>
);
};
const imgUrl = mxcUrlToHttp(mx, prev['og:image'] || '', useAuthentication);
return (
<UrlPreview {...props} ref={ref}>
{previewStatus.status === AsyncStatus.Success ? (
renderContent(previewStatus.data)
) : (
<Box grow="Yes" alignItems="Center" justifyContent="Center">
<Spinner variant="Secondary" size="400" />
</Box>
<>
{thumbUrl && (
<UrlPreviewImg
src={thumbUrl}
alt={prev['og:title']}
title={prev['og:title']}
onClick={() => setViewer(true)}
/>
)}
</UrlPreview>
{imgUrl && (
<ImageOverlay
src={imgUrl}
alt={prev['og:title']}
viewer={viewer}
requestClose={() => {
setViewer(false);
}}
renderViewer={renderViewer}
/>
)}
<UrlPreviewContent>
<Text
style={linkStyles}
truncate
as="a"
href={url}
target="_blank"
rel="noreferrer"
size="T200"
priority="300"
>
{typeof prev['og:site_name'] === 'string' && `${prev['og:site_name']} | `}
{tryDecodeURIComponent(url)}
</Text>
<Text truncate priority="400">
<b>{prev['og:title']}</b>
</Text>
<Text size="T200" priority="300">
<UrlPreviewDescription>{prev['og:description']}</UrlPreviewDescription>
</Text>
</UrlPreviewContent>
</>
);
}
);
};
return (
<UrlPreview {...props} ref={ref}>
{previewStatus.status === AsyncStatus.Success ? (
renderContent(previewStatus.data)
) : (
<Box grow="Yes" alignItems="Center" justifyContent="Center">
<Spinner variant="Secondary" size="400" />
</Box>
)}
</UrlPreview>
);
});
export const UrlPreviewHolder = as<'div'>(({ children, ...props }, ref) => {
const scrollRef = useRef<HTMLDivElement>(null);