-
Notifications
You must be signed in to change notification settings - Fork 96
Expand file tree
/
Copy pathThumbnailContent.tsx
More file actions
85 lines (75 loc) · 3.26 KB
/
Copy pathThumbnailContent.tsx
File metadata and controls
85 lines (75 loc) · 3.26 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
import type { ReactNode } from 'react';
import { useCallback, useEffect, useMemo } from 'react';
import { Box, Spinner } from 'folds';
import type { IThumbnailContent } from '$types/matrix/common';
import { useMatrixClient } from '$hooks/useMatrixClient';
import { AsyncStatus, useAsyncCallback } from '$hooks/useAsyncCallback';
import {
decryptFile,
downloadEncryptedMedia,
mxcUrlToHttp,
rewriteAuthenticatedMediaUrl,
} from '$utils/matrix';
import { prepareLoopbackImageSource } from '$utils/mediaUrl';
import { setMediaEncryption } from '$utils/tauriMediaEncryption';
import { isTauri } from '@tauri-apps/api/core';
import { useMediaAuthentication } from '$hooks/useMediaAuthentication';
import { useRenderableMediaUrl } from '$hooks/useRenderableMediaUrl';
import { useCreateObjectURL } from '$hooks/useObjectURL';
import { FALLBACK_MIMETYPE } from '$utils/mimeTypes';
export type ThumbnailContentProps = {
info: IThumbnailContent;
renderImage: (src: string) => ReactNode;
};
export function ThumbnailContent({ info, renderImage }: ThumbnailContentProps) {
const mx = useMatrixClient();
const useAuthentication = useMediaAuthentication();
const encInfo = info.thumbnail_file;
const thumbMxcUrl = encInfo?.url ?? info.thumbnail_url;
const rawMediaUrl = useMemo(() => {
if (typeof thumbMxcUrl !== 'string') return undefined;
return mxcUrlToHttp(mx, thumbMxcUrl, useAuthentication) ?? undefined;
}, [mx, thumbMxcUrl, useAuthentication]);
const tauri = isTauri();
// Tauri resolves the source inside `loadThumbSrc` instead.
const resolvedMediaUrl = useRenderableMediaUrl(encInfo || tauri ? undefined : rawMediaUrl);
const createObjectURL = useCreateObjectURL();
const [thumbSrcState, loadThumbSrc] = useAsyncCallback(
useCallback(async () => {
const thumbInfo = info.thumbnail_info;
// Only the URL is required: `mimetype` is a decryption hint that already falls back,
// and other clients routinely omit it.
if (typeof thumbMxcUrl !== 'string') {
throw new Error('Failed to load thumbnail');
}
if (encInfo) {
if (!rawMediaUrl) throw new Error('Invalid media URL');
if (tauri) {
await setMediaEncryption(rawMediaUrl, encInfo, thumbInfo?.mimetype ?? FALLBACK_MIMETYPE);
return rewriteAuthenticatedMediaUrl(rawMediaUrl)!;
}
return createObjectURL(
downloadEncryptedMedia(rawMediaUrl, (encBuf) =>
decryptFile(encBuf, thumbInfo?.mimetype ?? FALLBACK_MIMETYPE, encInfo)
)
);
}
if (tauri && rawMediaUrl) return prepareLoopbackImageSource(rawMediaUrl);
return resolvedMediaUrl ?? rawMediaUrl ?? thumbMxcUrl;
}, [info, thumbMxcUrl, rawMediaUrl, resolvedMediaUrl, tauri, encInfo, createObjectURL])
);
useEffect(() => {
// The failure is already reflected in `thumbSrcState`; swallow the rejection so it
// does not surface as an unhandled promise rejection.
loadThumbSrc().catch(() => undefined);
}, [loadThumbSrc]);
if (thumbSrcState.status === AsyncStatus.Success) return renderImage(thumbSrcState.data);
if (thumbSrcState.status === AsyncStatus.Loading) {
return (
<Box alignItems="Center" justifyContent="Center">
<Spinner variant="Secondary" />
</Box>
);
}
return null;
}