Cleanup - unused files / unused exports / duplicate exports
[ProtonMail-WebClient.git] / packages / components / hooks / useLoadContactImage.ts
blob8e02522a13210f9c196716ff2d61f008a8ae3a69
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';
14 type ImageModel = {
15     src: string;
16     width?: number;
17     height?: number;
18     isSmall?: boolean;
21 interface Props {
22     photo: string;
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);
44     // Show image when :
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
49     const shouldShow =
50         showAnyway ||
51         (hasShowEmbeddedImages && hasShowRemoteImages) ||
52         (isBase64 ? hasShowRemoteImages : hasShowRemoteImages);
54     /**
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
63      * 3. Loading failed
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)
72      */
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 });
77         } else {
78             const resized = await resizeImage({
79                 original: src,
80                 maxWidth: CONTACT_IMG_SIZE,
81                 maxHeight: CONTACT_IMG_SIZE,
82                 bigResize: true,
83                 crossOrigin: useProxy,
84             });
86             setImage({ src: resized });
87         }
88     };
89     const loadImage = async () => {
90         try {
91             const uid = authentication.getUID();
92             const imageURL = getContactImageSource({
93                 apiUrl: API_URL,
94                 url: photo,
95                 uid,
96                 useProxy: !!mailSettings?.ImageProxy,
97                 origin: window.location.origin,
98             });
100             const { src, width, height } = await toImage(imageURL);
102             if (needsResize) {
103                 await handleResizeImage(src, width, height, !!mailSettings?.ImageProxy);
104             } else {
105                 setImage({ src });
106             }
107         } catch (e) {
108             if (!!mailSettings?.ImageProxy) {
109                 onToggleLoadDirectBanner?.(true);
110                 setNeedsLoadDirect(true);
111             }
112             throw new Error('Get image failed');
113         }
114     };
116     const loadImageDirect = async () => {
117         try {
118             const { src, width, height } = await toImage(photo, false);
120             if (needsResize) {
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
126                 try {
127                     await handleResizeImage(src, width, height, false);
128                 } catch (e) {
129                     setImage({ src });
130                 }
131             } else {
132                 setImage({ src });
133             }
135             setNeedsLoadDirect(false);
136             onToggleLoadDirectBanner?.(false);
137         } catch (e) {
138             setLoadDirectFailed(true);
139             onToggleLoadDirectBanner?.(false);
140             throw new Error('Get image failed');
141         }
142     };
144     const handleLoadImageDirect = async () => {
145         void withLoadingResize(loadImageDirect());
146     };
148     useEffect(() => {
149         if (!photo || !shouldShow) {
150             return;
151         }
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));
157         } else {
158             void withLoadingResize(loadImageDirect().catch(noop));
159         }
160     }, [photo, shouldShow]);
162     const display: 'loading' | 'loadDirectFailed' | 'needsLoadDirect' | 'smallImageLoaded' | 'loaded' | 'askLoading' =
163         (() => {
164             if (shouldShow) {
165                 if (loading) {
166                     return 'loading';
167                 }
169                 if (loadDirectFailed) {
170                     return 'loadDirectFailed';
171                 }
173                 if (needsLoadDirect) {
174                     return 'needsLoadDirect';
175                 }
177                 return 'loaded';
178             }
180             return 'askLoading';
181         })();
183     return {
184         loadImage,
185         loadImageDirect,
186         handleLoadImageDirect,
187         image,
188         needsLoadDirect,
189         setShowAnyway,
190         loading,
191         shouldShow,
192         loadDirectFailed,
193         display,
194     };
197 export default useLoadContactImage;