Merge branch 'feat/inda-383-daily-stat' into 'main'
[ProtonMail-WebClient.git] / applications / drive / src / app / containers / PreviewContainer.tsx
blob68bfc55f6c95122ef210a2183ef8ebb9b9d6c425
1 import { useCallback, useEffect, useMemo, useRef } from 'react';
2 import type { RouteComponentProps } from 'react-router-dom';
3 import { useLocation } from 'react-router-dom';
5 import { c } from 'ttag';
7 import { FilePreview, NavigationControl } from '@proton/components';
8 import { HTTP_STATUS_CODE } from '@proton/shared/lib/constants';
9 import { getCanAdmin } from '@proton/shared/lib/drive/permissions';
10 import { API_CUSTOM_ERROR_CODES } from '@proton/shared/lib/errors';
12 import { SignatureAlertBody } from '../components/SignatureAlert';
13 import SignatureIcon from '../components/SignatureIcon';
14 import { useDetailsModal } from '../components/modals/DetailsModal';
15 import { useLinkSharingModal } from '../components/modals/ShareLinkModal/ShareLinkModal';
16 import useIsEditEnabled from '../components/sections/useIsEditEnabled';
17 import { useActiveShare } from '../hooks/drive/useActiveShare';
18 import useNavigate from '../hooks/drive/useNavigate';
19 import { useActions, useDriveSharingFlags, useFileView } from '../store';
20 import { useOpenInDocs } from '../store/_documents';
21 // TODO: ideally not use here
22 import useSearchResults from '../store/_search/useSearchResults';
23 import { getSharedStatus } from '../utils/share';
25 export default function PreviewContainer({ match }: RouteComponentProps<{ shareId: string; linkId: string }>) {
26     const { shareId, linkId } = match.params;
27     const {
28         navigateToLink,
29         navigateToSharedByMe,
30         navigateToSharedWithMe,
31         navigateToTrash,
32         navigateToRoot,
33         navigateToNoAccess,
34         navigateToSearch,
35     } = useNavigate();
36     const { setFolder } = useActiveShare();
37     const [detailsModal, showDetailsModal] = useDetailsModal();
38     const [linkSharingModal, showLinkSharingModal] = useLinkSharingModal();
39     const { query: lastQuery } = useSearchResults();
40     const { isSharingInviteAvailable } = useDriveSharingFlags();
41     const { saveFile } = useActions();
43     const isEditEnabled = useIsEditEnabled();
45     const urlParams = new URLSearchParams(useLocation().search);
46     const isShareAction = urlParams.has('share');
47     const referer = urlParams.get('r');
48     const useNavigation =
49         !referer?.startsWith('/shared-with-me') &&
50         !referer?.startsWith('/shared-urls') &&
51         !referer?.startsWith('/trash') &&
52         !referer?.startsWith('/search');
54     const {
55         permissions,
56         isLinkLoading,
57         isContentLoading,
58         error,
59         link,
60         contents,
61         contentsMimeType,
62         downloadFile,
63         navigation,
64     } = useFileView(shareId, linkId, useNavigation);
66     const { showOpenInDocs, openInDocsAction } = useOpenInDocs(link);
68     const isAdmin = useMemo(() => getCanAdmin(permissions), [permissions]);
70     // Open sharing modal through URL parameter - needed for Proton Docs
71     useEffect(() => {
72         if (isShareAction) {
73             showLinkSharingModal({ shareId, linkId });
74         }
75     }, []);
77     // If the link is not type of file, probably user modified the URL.
78     useEffect(() => {
79         if (link && !link.isFile) {
80             navigateToLink(shareId, linkId, false);
81         }
82     }, [link?.isFile]);
84     useEffect(() => {
85         if (link && !referer?.startsWith('/shared-with-me')) {
86             setFolder({ shareId, linkId: link.parentLinkId });
87         }
88     }, [shareId, link?.parentLinkId]);
90     useEffect(() => {
91         if (!error) {
92             return;
93         }
94         if (error.data?.Code === API_CUSTOM_ERROR_CODES.NOT_FOUND) {
95             navigateToNoAccess();
96         } else if (
97             // Block not found (storage response).
98             error.status === HTTP_STATUS_CODE.NOT_FOUND ||
99             error.data?.Code === API_CUSTOM_ERROR_CODES.INVALID_ID
100         ) {
101             navigateToRoot();
102         }
103     }, [error]);
105     const navigateToParent = useCallback(() => {
106         if (referer?.startsWith('/shared-with-me')) {
107             navigateToSharedWithMe();
108             return;
109         }
110         if (referer?.startsWith('/shared-urls')) {
111             navigateToSharedByMe();
112             return;
113         }
114         if (referer?.startsWith('/trash')) {
115             navigateToTrash();
116             return;
117         }
118         if (referer?.startsWith('/search')) {
119             if (lastQuery) {
120                 navigateToSearch(lastQuery);
121                 return;
122             }
123         }
124         if (link?.parentLinkId) {
125             navigateToLink(shareId, link.parentLinkId, false);
126         }
127     }, [link?.parentLinkId, shareId, referer]);
129     const onOpen = useCallback(
130         (linkId: string | undefined) => {
131             if (linkId) {
132                 navigateToLink(shareId, linkId, true);
133             }
134         },
135         [shareId]
136     );
138     const signatureStatus = useMemo(() => {
139         if (!link) {
140             return;
141         }
143         return (
144             <SignatureIcon
145                 isFile={link.isFile}
146                 signatureIssues={link.signatureIssues}
147                 isAnonymous={!link.activeRevision?.signatureAddress && !link.signatureAddress}
148                 className="ml-2 color-danger"
149             />
150         );
151     }, [link]);
153     const signatureConfirmation = useMemo(() => {
154         if (!link?.signatureIssues?.blocks) {
155             return;
156         }
158         return (
159             <SignatureAlertBody
160                 signatureIssues={link.signatureIssues}
161                 signatureAddress={link.signatureAddress}
162                 isFile={link.isFile}
163                 name={link.name}
164             />
165         );
166     }, [link]);
168     const handleSaveFile = useCallback(
169         (content: Uint8Array[]) => {
170             if (!link) {
171                 return Promise.reject('missing link');
172             }
174             return saveFile(shareId, link.parentLinkId, link.name, link.mimeType, content);
175         },
176         [shareId, link?.name, link?.parentLinkId]
177     );
179     const rootRef = useRef<HTMLDivElement>(null);
181     return (
182         <>
183             <FilePreview
184                 isMetaLoading={isLinkLoading}
185                 isLoading={isContentLoading}
186                 error={error ? error.message || error.toString?.() || c('Info').t`Unknown error` : undefined}
187                 contents={contents}
188                 fileName={link?.name}
189                 mimeType={contentsMimeType}
190                 isSharingInviteAvailable={isSharingInviteAvailable}
191                 sharedStatus={getSharedStatus(link)}
192                 fileSize={link?.size}
193                 onClose={navigateToParent}
194                 onDownload={downloadFile}
195                 onSave={isEditEnabled ? handleSaveFile : undefined}
196                 onDetails={() => showDetailsModal({ shareId, linkId })}
197                 onShare={
198                     !isAdmin || isLinkLoading || !!link?.trashed
199                         ? undefined
200                         : () => showLinkSharingModal({ shareId, linkId })
201                 }
202                 onOpenInDocs={
203                     showOpenInDocs
204                         ? () => {
205                               void openInDocsAction({ shareId, linkId });
206                           }
207                         : undefined
208                 }
209                 imgThumbnailUrl={link?.cachedThumbnailUrl}
210                 ref={rootRef}
211                 navigationControls={
212                     link &&
213                     navigation && (
214                         <NavigationControl
215                             current={navigation.current}
216                             total={navigation.total}
217                             rootRef={rootRef}
218                             onPrev={() => onOpen?.(navigation.prevLinkId)}
219                             onNext={() => onOpen?.(navigation.nextLinkId)}
220                         />
221                     )
222                 }
223                 signatureStatus={signatureStatus}
224                 signatureConfirmation={signatureConfirmation}
225             />
226             {detailsModal}
227             {linkSharingModal}
228         </>
229     );