Merge branch 'INDA-330-pii-update' into 'main'
[ProtonMail-WebClient.git] / applications / mail / src / app / components / sidebar / MailSidebarSystemFolders.tsx
blob8738b33ab0c6610f4ef3a223c78b16fbca804110
1 import type { DragEvent, DragEventHandler } from 'react';
2 import { useEffect, useRef, useState } from 'react';
4 import type { Location } from 'history';
5 import { c } from 'ttag';
7 import { SimpleSidebarListItemHeader } from '@proton/components';
8 import { MAILBOX_LABEL_IDS } from '@proton/shared/lib/constants';
9 import type { MailSettings } from '@proton/shared/lib/interfaces';
10 import clsx from '@proton/utils/clsx';
12 import { isConversationMode } from '../../helpers/mailSettings';
13 import type { MoveParams } from '../../hooks/actions/move/useMoveToFolder';
14 import useMoveSystemFolders, { SYSTEM_FOLDER_SECTION } from '../../hooks/useMoveSystemFolders';
15 import type { UnreadCounts } from './MailSidebarList';
16 import SidebarItem from './SidebarItem';
18 interface Props {
19     counterMap: UnreadCounts;
20     currentLabelID: string;
21     location: Location;
22     mailSettings: MailSettings;
23     setFocusedItem: (id: string) => void;
24     totalMessagesMap: UnreadCounts;
25     displayMoreItems: boolean;
26     showScheduled: boolean;
27     showSnoozed: boolean;
28     onToggleMoreItems: (display: boolean) => void;
29     collapsed?: boolean;
30     moveToFolder: (params: MoveParams) => void;
31     applyLabels: (params: any) => void;
34 const DND_MORE_FOLDER_ID = 'DND_MORE_FOLDER_ID';
36 interface DnDWrapperProps extends React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement> {
37     isDnDAllowed: boolean;
38     children: React.ReactElement;
40 const DnDElementWrapper = ({ isDnDAllowed, children, ...rest }: DnDWrapperProps) => {
41     return isDnDAllowed ? (
42         // eslint-disable-next-line jsx-a11y/prefer-tag-over-role
43         <div role="presentation" {...rest}>
44             {children}
45         </div>
46     ) : (
47         children
48     );
51 const MailSidebarSystemFolders = ({
52     counterMap,
53     currentLabelID,
54     location,
55     mailSettings,
56     setFocusedItem,
57     showScheduled,
58     showSnoozed,
59     totalMessagesMap,
60     displayMoreItems,
61     onToggleMoreItems,
62     collapsed = false,
63     moveToFolder,
64     applyLabels,
65 }: Props) => {
66     const { ShowMoved, AlmostAllMail } = mailSettings;
67     const [sidebarElements, moveSidebarElement] = useMoveSystemFolders({
68         showMoved: ShowMoved,
69         showScheduled,
70         showSnoozed,
71         showAlmostAllMail: AlmostAllMail,
72     });
73     const isConversation = isConversationMode(currentLabelID, mailSettings, location);
75     const lastDragTimeRef = useRef<number>();
76     const isDragging = useRef<boolean>();
77     const dragOverlay = useRef<HTMLDivElement>();
78     const [draggedElementId, setDraggedElementId] = useState<MAILBOX_LABEL_IDS | undefined>();
79     const [dragOveredElementId, setDragOveredElementId] = useState<string | undefined>();
80     const [isOverMoreFolder, setIsOverMoreFolder] = useState<boolean>();
82     const getCommonProps = (labelID: string) => ({
83         currentLabelID,
84         labelID,
85         isConversation,
86         unreadCount: counterMap[labelID],
87         totalMessagesCount: totalMessagesMap[labelID] || 0,
88     });
90     type HandleDragOver = (
91         elementId: MAILBOX_LABEL_IDS | typeof DND_MORE_FOLDER_ID
92     ) => DragEventHandler<HTMLDivElement>;
93     const handleDragOver: HandleDragOver = (elementId) => (event) => {
94         if (!isDragging.current) {
95             event.preventDefault();
96             return;
97         }
99         // "dragover" event must be prevented to allow "drop" event
100         event.preventDefault();
102         if (elementId === DND_MORE_FOLDER_ID && displayMoreItems === false) {
103             setIsOverMoreFolder(true);
104         } else {
105             setIsOverMoreFolder(false);
106         }
108         setDragOveredElementId(elementId);
109         lastDragTimeRef.current = Date.now();
110     };
112     const handleDragStart = (labelId: MAILBOX_LABEL_IDS) => (event: DragEvent<HTMLDivElement>) => {
113         if (labelId === MAILBOX_LABEL_IDS.INBOX) {
114             event.preventDefault();
115             event.stopPropagation();
116             return;
117         }
119         const draggedSystemFolder = sidebarElements.find((element) => labelId === element.labelID);
121         if (!draggedSystemFolder) {
122             return;
123         }
124         const sidebarElementName = draggedSystemFolder.text;
126         dragOverlay.current = document.createElement('div');
127         // translator: This is the text overlay following the cursor when dragging a sidebar element. Ex: Move Inbox
128         dragOverlay.current.innerHTML = c('Label').t`Move ${sidebarElementName}`;
129         dragOverlay.current.className = 'absolute bg-weak text-white py-2 px-4 rounded';
130         document.body.appendChild(dragOverlay.current);
132         event.dataTransfer.setDragImage(dragOverlay.current, 0, 30);
134         setDraggedElementId(labelId);
136         isDragging.current = true;
137     };
139     const handleResetDragState = () => {
140         isDragging.current = false;
141         setDraggedElementId(undefined);
142         setDragOveredElementId(undefined);
143         setIsOverMoreFolder(undefined);
144         if (dragOverlay.current) {
145             document.body.removeChild(dragOverlay.current);
146             lastDragTimeRef.current = undefined;
147             isDragging.current = undefined;
148             dragOverlay.current = undefined;
149         }
150     };
152     type HandleDrop = (
153         droppedId: MAILBOX_LABEL_IDS | 'MORE_FOLDER_ITEM',
154         draggedId: MAILBOX_LABEL_IDS | undefined
155     ) => DragEventHandler;
156     const handleDrop: HandleDrop = (droppedId, draggedId) => (event: DragEvent<HTMLDivElement>) => {
157         event.preventDefault();
159         if (!isDragging.current || !draggedId) {
160             return;
161         }
163         handleResetDragState();
164         moveSidebarElement(draggedId, droppedId);
165     };
167     const getDnDClasses = (hoveredId: MAILBOX_LABEL_IDS, draggedId: MAILBOX_LABEL_IDS | undefined) => {
168         if (dragOveredElementId !== hoveredId || hoveredId === draggedId || draggedId === MAILBOX_LABEL_IDS.INBOX) {
169             return '';
170         }
172         const draggedElementOrder = sidebarElements.find((el) => el.labelID === draggedId)?.order;
173         const hoveredElementOrder = sidebarElements.find((el) => el.labelID === hoveredId)?.order;
175         if (!draggedElementOrder || !hoveredElementOrder) {
176             return undefined;
177         }
179         const goesUp = draggedElementOrder > hoveredElementOrder;
181         if (hoveredId === MAILBOX_LABEL_IDS.INBOX) {
182             return 'border-bottom';
183         }
185         return goesUp ? 'border-top' : 'border-bottom';
186     };
188     // Set dragOveredElementId
189     useEffect(() => {
190         if (!dragOveredElementId) {
191             return;
192         }
194         const interval = setInterval(() => {
195             const lastDragTime = lastDragTimeRef.current || 0;
196             const elapsedTime = Date.now() - lastDragTime;
198             if (elapsedTime > 300) {
199                 setDragOveredElementId(undefined);
200                 lastDragTimeRef.current = undefined;
201             }
202         }, 150);
204         return () => {
205             clearInterval(interval);
206         };
207     }, [dragOveredElementId]);
209     // Opens the "more" folder
210     useEffect(() => {
211         if (!isOverMoreFolder) {
212             return;
213         }
215         const OPEN_FOLDER_AFTER = 1000;
216         const now = Date.now();
218         const timeout = setTimeout(() => {
219             const lastDragOverTime = lastDragTimeRef.current || 0;
220             if (now - 50 < lastDragOverTime) {
221                 onToggleMoreItems(true);
222             }
223         }, OPEN_FOLDER_AFTER);
225         return () => {
226             clearTimeout(timeout);
227         };
228     }, [isOverMoreFolder]);
230     return (
231         <>
232             {sidebarElements
233                 .filter((element) => element.display === SYSTEM_FOLDER_SECTION.MAIN)
234                 .map((element) => (
235                     <DnDElementWrapper
236                         isDnDAllowed
237                         key={element.ID}
238                         onDragStart={handleDragStart(element.labelID)}
239                         onDragEnd={handleResetDragState}
240                         onDragOver={handleDragOver(element.labelID)}
241                         onDrop={handleDrop(element.labelID, draggedElementId)}
242                         className={clsx([getDnDClasses(element.labelID, draggedElementId)])}
243                         onClick={(e) => e.stopPropagation()}
244                     >
245                         <SidebarItem
246                             {...getCommonProps(element.labelID)}
247                             icon={element.icon}
248                             id={element.ID}
249                             hideCountOnHover={false}
250                             isFolder={element.labelID !== MAILBOX_LABEL_IDS.STARRED}
251                             onFocus={setFocusedItem}
252                             shortcutText={element.shortcutText}
253                             text={element.text}
254                             collapsed={collapsed}
255                             moveToFolder={moveToFolder}
256                             applyLabels={applyLabels}
257                         />
258                     </DnDElementWrapper>
259                 ))}
260             {!collapsed && (
261                 <DnDElementWrapper
262                     isDnDAllowed
263                     key={'MORE_FOLDER_ITEM'}
264                     onDragOver={handleDragOver(DND_MORE_FOLDER_ID)}
265                     onDrop={handleDrop('MORE_FOLDER_ITEM', draggedElementId)}
266                 >
267                     <SimpleSidebarListItemHeader
268                         toggle={displayMoreItems}
269                         onToggle={(display: boolean) => onToggleMoreItems(display)}
270                         text={displayMoreItems ? c('Link').t`Less` : c('Link').t`More`}
271                         title={displayMoreItems ? c('Link').t`Less` : c('Link').t`More`}
272                         id="toggle-more-items"
273                         onFocus={setFocusedItem}
274                         collapsed={collapsed}
275                     />
276                 </DnDElementWrapper>
277             )}
279             {displayMoreItems
280                 ? sidebarElements
281                       .filter((element) => element.display === SYSTEM_FOLDER_SECTION.MORE)
282                       .map((element) => (
283                           <DnDElementWrapper
284                               isDnDAllowed
285                               onClick={(e) => e.stopPropagation()}
286                               key={element.ID}
287                               onDragStart={handleDragStart(element.labelID)}
288                               onDragEnd={handleResetDragState}
289                               onDragOver={handleDragOver(element.labelID)}
290                               onDrop={handleDrop(element.labelID, draggedElementId)}
291                               className={clsx([getDnDClasses(element.labelID, draggedElementId)])}
292                           >
293                               <SidebarItem
294                                   {...getCommonProps(element.labelID)}
295                                   icon={element.icon}
296                                   id={element.ID}
297                                   isFolder={element.labelID !== MAILBOX_LABEL_IDS.STARRED}
298                                   hideCountOnHover={false}
299                                   onFocus={setFocusedItem}
300                                   shortcutText={element.shortcutText}
301                                   text={element.text}
302                                   collapsed={collapsed}
303                                   moveToFolder={moveToFolder}
304                                   applyLabels={applyLabels}
305                               />
306                           </DnDElementWrapper>
307                       ))
308                 : null}
309         </>
310     );
313 export default MailSidebarSystemFolders;