Merge branch 'feat/inda-383-daily-stat' into 'main'
[ProtonMail-WebClient.git] / applications / drive / src / app / components / DriveBreadcrumbs / DriveBreadcrumbs.tsx
blobb9ef9a1478f6a04aba57c79992d8c13ccbbd3593
1 import { useEffect, useState } from 'react';
2 import { NavLink } from 'react-router-dom';
4 import { c } from 'ttag';
6 import { type BreadcrumbInfo, CollapsingBreadcrumbs, Icon, Loader, useNotifications } from '@proton/components';
7 import noop from '@proton/utils/noop';
9 import type { DriveFolder } from '../../hooks/drive/useActiveShare';
10 import { useDriveDragMoveTarget } from '../../hooks/drive/useDriveDragMove';
11 import useNavigate from '../../hooks/drive/useNavigate';
12 import { useLinkPath } from '../../store';
13 import type { Share } from '../../store/_shares';
14 import { ShareType, useShare } from '../../store/_shares';
15 import { useDirectSharingInfo } from '../../store/_shares/useDirectSharingInfo';
16 import { sendErrorReport } from '../../utils/errorHandling';
17 import SignatureIcon from '../SignatureIcon';
18 import { useDetailsModal } from '../modals/DetailsModal';
19 import { getDevicesSectionName } from '../sections/Devices/constants';
21 interface Props {
22     activeFolder: DriveFolder;
25 const DriveBreadcrumbs = ({ activeFolder }: Props) => {
26     const { navigateToLink, navigateToDevices } = useNavigate();
27     const { createNotification } = useNotifications();
28     const { getHandleItemDrop } = useDriveDragMoveTarget(activeFolder.shareId);
29     const { traverseLinksToRoot } = useLinkPath(); // TODO: Get data using useFolderView instead one day.
30     const [detailsModal, showDetailsModal] = useDetailsModal();
31     const { isSharedWithMe: getIsSharedWithMe } = useDirectSharingInfo();
33     const [dropTarget, setDropTarget] = useState<string>();
34     const [rootShare, setRootShare] = useState<Share>();
35     const { getShare } = useShare();
36     const sectionTitle = getDevicesSectionName();
38     const [breadcrumbs, setBreadcrumbs] = useState<BreadcrumbInfo[]>([]);
39     const [isSharedWithMeFolder, setIsSharedWithMeFolder] = useState(false);
41     useEffect(() => {
42         const abortController = new AbortController();
44         traverseLinksToRoot(abortController.signal, activeFolder.shareId, activeFolder.linkId)
45             .then((pathItems) => {
46                 const breadcrumbs = pathItems.map(({ linkId, name, isRoot, link, isReadOnly }) => {
47                     const handleDrop = getHandleItemDrop(linkId);
49                     let onClick;
50                     if (linkId === activeFolder.linkId) {
51                         onClick = link.signatureIssues
52                             ? () => showDetailsModal({ shareId: activeFolder.shareId, linkId })
53                             : undefined;
54                     } else {
55                         onClick = () => navigateToLink(activeFolder.shareId, linkId, false);
56                     }
58                     const breadcrumb: BreadcrumbInfo = {
59                         key: linkId,
60                         text: name,
61                         richText: (
62                             <span className="flex items-center flex-nowrap flex-1">
63                                 <SignatureIcon
64                                     isFile={link.isFile}
65                                     signatureIssues={link.signatureIssues}
66                                     isAnonymous={!link.activeRevision?.signatureAddress && !link.signatureAddress}
67                                     className="mr-1"
68                                 />
69                                 <span className="text-pre text-ellipsis">{name}</span>
70                             </span>
71                         ),
72                         noShrink: isRoot && rootShare?.type !== ShareType.device, // Keep root (My files) to be always fully visible.
73                         highlighted: dropTarget === linkId,
74                         collapsedText: name,
75                         onClick,
76                         onDragLeave: () => {
77                             if (isReadOnly) {
78                                 return;
79                             }
80                             setDropTarget(undefined);
81                         },
82                         onDragOver: (e) => {
83                             if (isReadOnly) {
84                                 return;
85                             }
86                             e.stopPropagation();
87                             e.preventDefault();
88                             if (dropTarget !== linkId) {
89                                 setDropTarget(linkId);
90                             }
91                         },
92                         onDrop: async (e) => {
93                             if (isReadOnly) {
94                                 return;
95                             }
96                             setDropTarget(undefined);
97                             try {
98                                 await handleDrop(e);
99                             } catch (e: any) {
100                                 createNotification({
101                                     text: c('Notification').t`Failed to move, please try again`,
102                                     type: 'error',
103                                 });
104                                 console.error(e);
105                             }
106                         },
107                     };
108                     return breadcrumb;
109                 });
110                 setBreadcrumbs(breadcrumbs);
111             })
112             .catch((err: any) => {
113                 sendErrorReport(err);
114             });
116         return () => {
117             abortController.abort();
118         };
119     }, [activeFolder.shareId, activeFolder.linkId, dropTarget, rootShare]);
121     useEffect(() => {
122         const abortController = new AbortController();
123         getShare(abortController.signal, activeFolder.shareId)
124             .then((share) => {
125                 setRootShare(share);
126             })
127             .catch(sendErrorReport);
129         getIsSharedWithMe(abortController.signal, activeFolder.shareId)
130             .then((result) => {
131                 setIsSharedWithMeFolder(result);
132             })
133             .catch(noop);
134         return () => {
135             abortController.abort();
136         };
137     }, [activeFolder.shareId]);
139     if (breadcrumbs.length === 0) {
140         return <Loader className="py-2 px-3" />;
141     }
143     if (rootShare?.type === ShareType.device) {
144         breadcrumbs.unshift({
145             key: 'devices-root',
146             text: sectionTitle,
147             noShrink: true,
148             onClick: navigateToDevices,
149         });
150     }
152     return (
153         <>
154             {isSharedWithMeFolder ? (
155                 <>
156                     <NavLink
157                         to="/shared-with-me"
158                         className="button button-medium button-ghost-weak text-pre p-1 m-0 text-ellipsis *:pointer-events-none color-weak"
159                     >
160                         {c('Link').t`Shared with me`}
161                     </NavLink>
162                     <div className="rtl:mirror shrink-0" aria-hidden="true">
163                         <Icon size={4} name="chevron-right" />
164                     </div>
165                 </>
166             ) : null}
167             <CollapsingBreadcrumbs breadcrumbs={breadcrumbs} />
168             {detailsModal}
169         </>
170     );
173 export default DriveBreadcrumbs;