Cleanup - unused files / unused exports / duplicate exports
[ProtonMail-WebClient.git] / applications / drive / src / app / store / _views / usePhotosView.ts
blob5e64507aeb195cb3e87c01c5e7db0bd9b03945e3
1 import type React from 'react';
2 import { useEffect, useMemo } from 'react';
4 import { EVENT_TYPES } from '@proton/shared/lib/drive/constants';
6 import { sendErrorReport } from '../../utils/errorHandling';
7 import { EnrichedError } from '../../utils/errorHandling/EnrichedError';
8 import type { LinkDownload } from '../_downloads';
9 import { useDownloadProvider } from '../_downloads';
10 import type { DriveEvent, DriveEvents } from '../_events';
11 import { useDriveEventManager } from '../_events';
12 import { useLinksListing, useLinksQueue } from '../_links';
13 import { isPhotoGroup, sortWithCategories, usePhotos } from '../_photos';
14 import type { PhotoLink } from '../_photos';
15 import { useAbortSignal, useMemoArrayNoMatterTheOrder } from './utils';
17 /**
18  * For Photos, we listen for delete and move events
19  * to update our internal state which is not linked to the global state.
20  */
21 export function updateByEvents(
22     { events, eventId }: DriveEvents,
23     shareId: string,
24     removePhotosFromCache: (linkIds: string[]) => void,
25     processedEventCounter: (eventId: string, event: DriveEvent) => void
26 ) {
27     const linksToRemove = events
28         .filter(
29             (event) =>
30                 event.eventType === EVENT_TYPES.DELETE ||
31                 (event.originShareId === shareId && event.encryptedLink.rootShareId !== event.originShareId)
32         )
33         .map((event) => {
34             processedEventCounter(eventId, event);
35             return event.encryptedLink.linkId;
36         });
38     removePhotosFromCache(linksToRemove);
41 export const usePhotosView = () => {
42     const eventsManager = useDriveEventManager();
43     const { getCachedChildren, loadLinksMeta } = useLinksListing();
44     const { shareId, linkId, isLoading, volumeId, photos, loadPhotos, removePhotosFromCache } = usePhotos();
45     const { addToQueue } = useLinksQueue({ loadThumbnails: true });
46     const { download } = useDownloadProvider();
48     const abortSignal = useAbortSignal([shareId, linkId]);
49     const cache = shareId && linkId ? getCachedChildren(abortSignal, shareId, linkId) : undefined;
50     const cachedLinks = useMemoArrayNoMatterTheOrder(cache?.links || []);
52     // This will be flattened to contain categories and links
53     const { photosViewData, photoLinkIdToIndexMap, photoLinkIds } = useMemo(() => {
54         if (!shareId || !linkId) {
55             return {
56                 photosViewData: [],
57                 photoLinkIdToIndexMap: {},
58                 photoLinkIds: [],
59             };
60         }
62         const result: Record<string, PhotoLink> = {};
64         // We create "fake" links to avoid complicating the rest of the code
65         photos.forEach((photo) => {
66             result[photo.linkId] = {
67                 linkId: photo.linkId,
68                 rootShareId: shareId,
69                 parentLinkId: linkId,
70                 isFile: true,
71                 activeRevision: {
72                     photo,
73                 },
74             };
75         });
77         // Add data from cache
78         cachedLinks.forEach((link) => {
79             // If this link is not a photo, ignore it
80             if (!link.activeRevision?.photo) {
81                 return;
82             }
84             // Related photos are not supported by the web client for now
85             if (link.activeRevision.photo.mainPhotoLinkId) {
86                 return;
87             }
89             result[link.linkId] = link;
90         });
92         const photosViewData = sortWithCategories(Object.values(result));
94         // To improve performance, let's build some maps ahead of time
95         // For previews and selection, we need these maps to know where
96         // each link is located in the data array.
97         let photoLinkIdToIndexMap: Record<string, number> = {};
99         // We also provide a list of linkIds for the preview navigation,
100         // so it's important that this array follows the sorted view order.
101         let photoLinkIds: string[] = [];
103         photosViewData.forEach((item, index) => {
104             if (!isPhotoGroup(item)) {
105                 photoLinkIdToIndexMap[item.linkId] = index;
106                 photoLinkIds.push(item.linkId);
107             }
108         });
110         return {
111             photosViewData,
112             photoLinkIdToIndexMap,
113             photoLinkIds,
114         };
115     }, [photos, cachedLinks, linkId, shareId]);
117     useEffect(() => {
118         if (!volumeId || !shareId) {
119             return;
120         }
121         const abortController = new AbortController();
123         loadPhotos(abortController.signal, volumeId);
125         const callbackId = eventsManager.eventHandlers.register((eventVolumeId, events, processedEventCounter) => {
126             if (eventVolumeId === volumeId) {
127                 updateByEvents(events, shareId, removePhotosFromCache, processedEventCounter);
128             }
129         });
131         return () => {
132             eventsManager.eventHandlers.unregister(callbackId);
133             abortController.abort();
134         };
135     }, [volumeId, shareId]);
137     const loadPhotoLink = (linkId: string, domRef?: React.MutableRefObject<unknown>) => {
138         if (!shareId) {
139             return;
140         }
142         addToQueue(shareId, linkId, domRef);
143     };
145     /**
146      * A `PhotoLink` may not be fully loaded, so we need to preload all links in the cache
147      * first to request a download.
148      *
149      * @param linkIds List of Link IDs to preload
150      */
151     const requestDownload = async (linkIds: string[]) => {
152         if (!shareId) {
153             return;
154         }
156         const ac = new AbortController();
157         const meta = await loadLinksMeta(ac.signal, 'photos-download', shareId, linkIds);
159         if (meta.links.length === 0) {
160             return;
161         }
163         if (meta.errors.length > 0) {
164             sendErrorReport(
165                 new EnrichedError('Failed to load links meta for download', {
166                     tags: {
167                         shareId,
168                     },
169                     extra: {
170                         linkIds: linkIds.filter((id) => !meta.links.find((link) => link.linkId === id)),
171                         errors: meta.errors,
172                     },
173                 })
174             );
176             return;
177         }
179         const relatedLinkIds = meta.links.flatMap((link) => link.activeRevision?.photo?.relatedPhotosLinkIds || []);
181         const relatedMeta = await loadLinksMeta(ac.signal, 'photos-related-download', shareId, relatedLinkIds);
183         if (relatedMeta.errors.length > 0) {
184             sendErrorReport(
185                 new EnrichedError('Failed to load links meta for download', {
186                     tags: {
187                         shareId,
188                     },
189                     extra: {
190                         linkIds: linkIds.filter((id) => !relatedMeta.links.find((link) => link.linkId === id)),
191                         errors: relatedMeta.errors,
192                     },
193                 })
194             );
196             return;
197         }
199         const links: LinkDownload[] = [...meta.links, ...relatedMeta.links].map(
200             (link) =>
201                 ({
202                     ...link,
203                     shareId: link.rootShareId,
204                 }) satisfies LinkDownload
205         );
207         await download(links);
208     };
210     return {
211         shareId,
212         linkId,
213         photos: photosViewData,
214         photoLinkIdToIndexMap,
215         photoLinkIds,
216         removePhotosFromCache,
217         loadPhotoLink,
218         requestDownload,
219         isLoading,
220     };