Merge branch 'feat/inda-383-daily-stat' into 'main'
[ProtonMail-WebClient.git] / applications / drive / src / app / components / sections / Drive / Drive.tsx
blob7cd7b76a8367f4b38634559edfebff6e972a1502
1 import { useCallback, useEffect, useMemo, useRef } from 'react';
3 import { useActiveBreakpoint } from '@proton/components';
4 import { getCanAdmin } from '@proton/shared/lib/drive/permissions';
5 import { isProtonDocument } from '@proton/shared/lib/helpers/mimetype';
7 import type { DriveFolder } from '../../../hooks/drive/useActiveShare';
8 import useDriveDragMove from '../../../hooks/drive/useDriveDragMove';
9 import useNavigate from '../../../hooks/drive/useNavigate';
10 import { useOnItemRenderedMetrics } from '../../../hooks/drive/useOnItemRenderedMetrics';
11 import type { EncryptedLink, LinkShareUrl, SignatureIssues, useFolderView } from '../../../store';
12 import { useThumbnailsDownload } from '../../../store';
13 import { useDocumentActions, useDriveDocsFeatureFlag } from '../../../store/_documents';
14 import { SortField } from '../../../store/_views/utils/useSorting';
15 import { sendErrorReport } from '../../../utils/errorHandling';
16 import FileBrowser, {
17     Cells,
18     GridHeader,
19     useContextMenuControls,
20     useItemContextMenu,
21     useSelection,
22 } from '../../FileBrowser';
23 import type { BrowserItemId, FileBrowserBaseItem, ListViewHeaderItem } from '../../FileBrowser/interface';
24 import { useLinkSharingModal } from '../../modals/ShareLinkModal/ShareLinkModal';
25 import useOpenPreview from '../../useOpenPreview';
26 import { GridViewItem } from '../FileBrowser/GridViewItemLink';
27 import { ModifiedCell, NameCell, ShareOptionsCell, SizeCell } from '../FileBrowser/contentCells';
28 import headerItems from '../FileBrowser/headerCells';
29 import { translateSortField } from '../SortDropdown';
30 import { getSelectedItems } from '../helpers';
31 import { DriveItemContextMenu } from './DriveContextMenu';
32 import EmptyDeviceRoot from './EmptyDeviceRoot';
33 import EmptyFolder from './EmptyFolder';
34 import { FolderContextMenu } from './FolderContextMenu';
36 export interface DriveItem extends FileBrowserBaseItem {
37     activeRevision?: EncryptedLink['activeRevision'];
38     cachedThumbnailUrl?: string;
39     hasThumbnail: boolean;
40     isFile: boolean;
41     mimeType: string;
42     fileModifyTime: number;
43     name: string;
44     shareUrl?: LinkShareUrl;
45     signatureIssues?: SignatureIssues;
46     signatureAddress?: string;
47     size: number;
48     trashed: number | null;
49     parentLinkId: string;
50     isShared: boolean;
51     isAdmin: boolean;
52     showLinkSharingModal?: ReturnType<typeof useLinkSharingModal>[1];
55 interface Props {
56     activeFolder: DriveFolder;
57     folderView: ReturnType<typeof useFolderView>;
60 const { CheckboxCell, ContextMenuCell } = Cells;
62 const myFilesLargeScreenCells: React.FC<{ item: DriveItem }>[] = [
63     CheckboxCell,
64     NameCell,
65     ModifiedCell,
66     SizeCell,
67     ShareOptionsCell,
68     ContextMenuCell,
70 const myFilesSmallScreenCells = [CheckboxCell, NameCell, ContextMenuCell];
72 const headerItemsLargeScreen: ListViewHeaderItem[] = [
73     headerItems.checkbox,
74     headerItems.name,
75     headerItems.modificationDate,
76     headerItems.size,
77     headerItems.placeholder,
78     headerItems.placeholder,
81 const headerItemsSmallScreen: ListViewHeaderItem[] = [headerItems.checkbox, headerItems.name, headerItems.placeholder];
83 type DriveSortFields = Extract<SortField, SortField.name | SortField.fileModifyTime | SortField.size>;
84 const SORT_FIELDS: DriveSortFields[] = [SortField.name, SortField.fileModifyTime, SortField.size];
86 function Drive({ activeFolder, folderView }: Props) {
87     const { shareId, linkId } = activeFolder;
88     const contextMenuAnchorRef = useRef<HTMLDivElement>(null);
90     const browserContextMenu = useContextMenuControls();
91     const browserItemContextMenu = useItemContextMenu();
92     const thumbnails = useThumbnailsDownload();
93     const { navigateToLink } = useNavigate();
94     const selectionControls = useSelection();
95     const { viewportWidth } = useActiveBreakpoint();
96     const { openDocument } = useDocumentActions();
97     const { canUseDocs } = useDriveDocsFeatureFlag();
98     const [linkSharingModal, showLinkSharingModal] = useLinkSharingModal();
99     const { incrementItemRenderedCounter } = useOnItemRenderedMetrics(folderView.layout, folderView.isLoading);
100     const { permissions, layout, folderName, items, sortParams, setSorting, isLoading } = folderView;
102     const isAdmin = useMemo(() => getCanAdmin(permissions), [permissions]);
104     const selectedItems = useMemo(
105         () => getSelectedItems(items, selectionControls!.selectedItemIds),
106         [items, selectionControls!.selectedItemIds]
107     );
109     const openPreview = useOpenPreview();
110     const browserItems: DriveItem[] = items.map((item) => ({
111         ...item,
112         id: item.linkId,
113         // TODO: Improve this, we should not pass showLinkSharingModal here
114         showLinkSharingModal: item.isShared ? showLinkSharingModal : undefined,
115         isAdmin,
116     }));
117     const { getDragMoveControls } = useDriveDragMove(shareId, browserItems, selectionControls!.clearSelections);
119     /* eslint-disable react/display-name */
120     const GridHeaderComponent = useMemo(
121         () =>
122             ({ scrollAreaRef }: { scrollAreaRef: React.RefObject<HTMLDivElement> }) => {
123                 const activeSortingText = translateSortField(sortParams.sortField);
124                 return (
125                     <GridHeader
126                         isLoading={isLoading}
127                         sortFields={SORT_FIELDS}
128                         onSort={setSorting}
129                         sortField={sortParams.sortField}
130                         sortOrder={sortParams.sortOrder}
131                         itemCount={browserItems.length}
132                         scrollAreaRef={scrollAreaRef}
133                         activeSortingText={activeSortingText}
134                     />
135                 );
136             },
137         [sortParams.sortField, sortParams.sortOrder, isLoading]
138     );
140     const handleItemRender = useCallback(
141         (item: DriveItem) => {
142             incrementItemRenderedCounter();
144             if (item.hasThumbnail && item.activeRevision && !item.cachedThumbnailUrl) {
145                 thumbnails.addToDownloadQueue(shareId, item.linkId, item.activeRevision.id);
146             }
147         },
148         [thumbnails, shareId, incrementItemRenderedCounter]
149     );
151     const handleClick = useCallback(
152         (id: BrowserItemId) => {
153             const item = browserItems.find((item) => item.id === id);
155             if (!item) {
156                 return;
157             }
158             document.getSelection()?.removeAllRanges();
160             if (isProtonDocument(item.mimeType)) {
161                 void canUseDocs(shareId)
162                     .then((canUse) => {
163                         if (!canUse) {
164                             return;
165                         }
167                         return openDocument({
168                             linkId: id,
169                             shareId,
170                             openBehavior: 'tab',
171                         });
172                     })
173                     .catch(sendErrorReport);
174                 return;
175             }
177             if (item.isFile) {
178                 openPreview(shareId, id);
179                 return;
180             }
181             navigateToLink(shareId, id, item.isFile);
182         },
183         [navigateToLink, shareId, browserItems]
184     );
186     const handleScroll = () => {
187         browserContextMenu.close();
188         browserItemContextMenu.close();
189     };
191     useEffect(() => {
192         browserContextMenu.close();
193         browserItemContextMenu.close();
194     }, [shareId, linkId]);
196     if (!items.length && !isLoading) {
197         if (folderView.isActiveLinkReadOnly) {
198             return <EmptyDeviceRoot />;
199         }
201         return <EmptyFolder shareId={shareId} permissions={permissions} />;
202     }
204     const Cells = viewportWidth['>=large'] ? myFilesLargeScreenCells : myFilesSmallScreenCells;
205     const headerItems = viewportWidth['>=large'] ? headerItemsLargeScreen : headerItemsSmallScreen;
207     return (
208         <>
209             <FolderContextMenu
210                 permissions={permissions}
211                 isActiveLinkReadOnly={folderView.isActiveLinkReadOnly}
212                 shareId={shareId}
213                 anchorRef={contextMenuAnchorRef}
214                 close={browserContextMenu.close}
215                 isOpen={browserContextMenu.isOpen}
216                 open={browserContextMenu.open}
217                 position={browserContextMenu.position}
218             />
219             <DriveItemContextMenu
220                 permissions={permissions}
221                 isActiveLinkReadOnly={folderView.isActiveLinkReadOnly}
222                 shareId={shareId}
223                 selectedLinks={selectedItems}
224                 anchorRef={contextMenuAnchorRef}
225                 close={browserItemContextMenu.close}
226                 isOpen={browserItemContextMenu.isOpen}
227                 open={browserItemContextMenu.open}
228                 position={browserItemContextMenu.position}
229             />
230             <FileBrowser
231                 // data
232                 caption={folderName}
233                 headerItems={headerItems}
234                 items={browserItems}
235                 layout={layout}
236                 loading={isLoading}
237                 sortParams={sortParams}
238                 // components
239                 Cells={Cells}
240                 GridHeaderComponent={GridHeaderComponent}
241                 GridViewItem={GridViewItem}
242                 // handlers
243                 onItemOpen={handleClick}
244                 contextMenuAnchorRef={contextMenuAnchorRef}
245                 onItemContextMenu={browserItemContextMenu.handleContextMenu}
246                 onItemRender={handleItemRender}
247                 onSort={setSorting}
248                 onScroll={handleScroll}
249                 onViewContextMenu={browserContextMenu.handleContextMenu}
250                 getDragMoveControls={folderView.isActiveLinkReadOnly ? undefined : getDragMoveControls}
251             />
252             {linkSharingModal}
253         </>
254     );
257 export default Drive;