1 import { useEffect, useState } from 'react';
3 import useAuthentication from '@proton/components/hooks/useAuthentication';
4 import useConfig from '@proton/components/hooks/useConfig';
5 import { useLoading } from '@proton/hooks';
6 import { useMailSettings } from '@proton/mail/mailSettings/hooks';
7 import { CONTACT_IMG_SIZE } from '@proton/shared/lib/contacts/constants';
8 import { getContactImageSource } from '@proton/shared/lib/helpers/contacts';
9 import { resizeImage, toImage } from '@proton/shared/lib/helpers/image';
10 import { isBase64Image } from '@proton/shared/lib/helpers/validators';
11 import { hasShowEmbedded, hasShowRemote } from '@proton/shared/lib/mail/images';
12 import noop from '@proton/utils/noop';
23 needsResize?: boolean;
24 onToggleLoadDirectBanner?: (show: boolean) => void;
27 const useLoadContactImage = ({ photo, onToggleLoadDirectBanner, needsResize = false }: Props) => {
28 const authentication = useAuthentication();
29 const { API_URL } = useConfig();
30 const [mailSettings, loadingMailSettings] = useMailSettings();
32 const [image, setImage] = useState<ImageModel>({ src: '' });
33 const [loadingResize, withLoadingResize] = useLoading(true);
34 const [needsLoadDirect, setNeedsLoadDirect] = useState(false);
35 const [showAnyway, setShowAnyway] = useState(false);
36 const [loadDirectFailed, setLoadDirectFailed] = useState(false);
38 const loading = loadingMailSettings || loadingResize;
39 const hasShowRemoteImages = hasShowRemote(mailSettings);
40 const hasShowEmbeddedImages = hasShowEmbedded(mailSettings);
42 const isBase64 = isBase64Image(photo);
45 // - User requested by clicking on the load button
46 // - Both Auto show settings are ON
47 // - If image is embedded, check embedded setting
48 // - If image is remote, check remote setting
51 (hasShowEmbeddedImages && hasShowRemoteImages) ||
52 (isBase64 ? hasShowRemoteImages : hasShowRemoteImages);
55 * How image loading works:
56 * 1. If shouldShow = false (e.g. Auto show setting is OFF)
57 * => Nothing is done, we should display a "Load" button on the component calling this hook
58 * 2. Load is possible (Auto show ON or user clicked on load)
59 * a. User is using the proxy OR the image is b64
60 * => We pass in loadImage function. We will try to load the image using the Proton image proxy
61 * b. User is not using the proxy
62 * => We pass in loadImageDirect. We will try to load the image using the default URL
64 * a. Loading with proxy failed
65 * A banner should be displayed to the user (from the component calling this hook)
66 * informing that the image could not be loaded using Proton image proxy.
67 * The user has the possibility to load the image with its default url
68 * => We pass in loadImageDirect
69 * b. Loading without proxy failed
70 * => A placeholder should be displayed in the component calling this hook to inform
71 * the user that the image could not be loaded (+ potential load direct banner is removed)
74 const handleResizeImage = async (src: string, width: number, height: number, useProxy: boolean) => {
75 if (width <= CONTACT_IMG_SIZE && height <= CONTACT_IMG_SIZE) {
76 setImage({ src, width, height, isSmall: true });
78 const resized = await resizeImage({
80 maxWidth: CONTACT_IMG_SIZE,
81 maxHeight: CONTACT_IMG_SIZE,
83 crossOrigin: useProxy,
86 setImage({ src: resized });
89 const loadImage = async () => {
91 const uid = authentication.getUID();
92 const imageURL = getContactImageSource({
96 useProxy: !!mailSettings?.ImageProxy,
97 origin: window.location.origin,
100 const { src, width, height } = await toImage(imageURL);
103 await handleResizeImage(src, width, height, !!mailSettings?.ImageProxy);
108 if (!!mailSettings?.ImageProxy) {
109 onToggleLoadDirectBanner?.(true);
110 setNeedsLoadDirect(true);
112 throw new Error('Get image failed');
116 const loadImageDirect = async () => {
118 const { src, width, height } = await toImage(photo, false);
121 // In some cases resizing the image will not work (e.g. images using .ppm formats)
122 // So the loading from handleResizeImage will fail.
123 // In this case, we catch the error and set the Image using the image src
124 // At this point if the image necessarily loaded,
125 // otherwise we would have passed in the function catch block where we set loadDirectFailed
127 await handleResizeImage(src, width, height, false);
135 setNeedsLoadDirect(false);
136 onToggleLoadDirectBanner?.(false);
138 setLoadDirectFailed(true);
139 onToggleLoadDirectBanner?.(false);
140 throw new Error('Get image failed');
144 const handleLoadImageDirect = async () => {
145 void withLoadingResize(loadImageDirect());
149 if (!photo || !shouldShow) {
153 if (mailSettings?.ImageProxy || isBase64) {
154 // if resize fails (e.g. toImage will throw if the requested resource hasn't specified a CORS policy),
155 // fallback to the original src
156 void withLoadingResize(loadImage().catch(noop));
158 void withLoadingResize(loadImageDirect().catch(noop));
160 }, [photo, shouldShow]);
162 const display: 'loading' | 'loadDirectFailed' | 'needsLoadDirect' | 'smallImageLoaded' | 'loaded' | 'askLoading' =
169 if (loadDirectFailed) {
170 return 'loadDirectFailed';
173 if (needsLoadDirect) {
174 return 'needsLoadDirect';
186 handleLoadImageDirect,
197 export default useLoadContactImage;