1 import { useEffect, useMemo, useRef, useState } from 'react';
2 import type { RouteComponentProps } from 'react-router';
3 import { Route } from 'react-router';
5 import { Loader, useAppTitle } from '@proton/components';
6 import useLoading from '@proton/hooks/useLoading';
7 import { LinkURLType } from '@proton/shared/lib/drive/constants';
8 import { API_CUSTOM_ERROR_CODES } from '@proton/shared/lib/errors';
9 import noop from '@proton/utils/noop';
11 import DriveStartupModals from '../components/modals/DriveStartupModals';
12 import type { DriveSectionRouteProps } from '../components/sections/Drive/DriveView';
13 import DriveView from '../components/sections/Drive/DriveView';
14 import type { DriveFolder } from '../hooks/drive/useActiveShare';
15 import { useActiveShare } from '../hooks/drive/useActiveShare';
16 import { useFolderContainerTitle } from '../hooks/drive/useFolderContainerTitle';
17 import useNavigate from '../hooks/drive/useNavigate';
18 import { useContextShareHandler, useDefaultShare, useDriveEventManager } from '../store';
19 import { VolumeType, useVolumesState } from '../store/_volumes';
20 import PreviewContainer from './PreviewContainer';
22 const hasValidLinkType = (type: string) => {
23 return type === LinkURLType.FILE || type === LinkURLType.FOLDER;
26 export default function FolderContainer({ match }: RouteComponentProps<DriveSectionRouteProps>) {
27 const { navigateToRoot, navigateToNoAccess } = useNavigate();
28 const { activeFolder, setFolder } = useActiveShare();
29 const volumesState = useVolumesState();
30 const lastFolderPromise = useRef<Promise<DriveFolder | undefined>>();
31 const [, setError] = useState();
32 const { getDefaultShare, isShareAvailable } = useDefaultShare();
33 const driveEventManager = useDriveEventManager();
35 useFolderContainerTitle({ params: match.params, setAppTitle: useAppTitle });
37 const folderPromise = useMemo(async () => {
38 const { shareId, type, linkId } = match.params;
40 if (!shareId && !type && !linkId) {
41 const defaultShare = await getDefaultShare();
43 return { shareId: defaultShare.shareId, linkId: defaultShare.rootLinkId };
46 // Throwing error in async function does not propagate it to
47 // the view. Therefore we need to use this setError hack.
48 throw new Error('Drive is not initilized, cache has been cleared unexpectedly');
50 } else if (!shareId || !hasValidLinkType(type as string) || !linkId) {
51 console.warn('Missing parameters, should be none or shareId/type/linkId');
53 } else if (type === LinkURLType.FOLDER) {
54 const ac = new AbortController();
55 const isAvailable = await isShareAvailable(ac.signal, shareId);
57 console.warn('Provided share is not available, probably locked or soft deleted');
61 return { shareId, linkId };
63 return lastFolderPromise.current;
64 }, [match.params.shareId, match.params.type, match.params.linkId]);
75 if (err.data?.Code === API_CUSTOM_ERROR_CODES.NOT_FOUND) {
83 // With sharing we need to subscribe to events from different volumes.
84 // This will happen during Shared with me navigation
86 const volumeId = volumesState.findVolumeId(activeFolder.shareId);
91 .then((defaultShare) => {
92 // We exclude subscribing to volumes event of main share (Already done in MainContainer)
93 if (defaultShare.volumeId !== volumeId) {
94 driveEventManager.volumes.startSubscription(volumeId, VolumeType.shared).catch(noop);
100 // This is memoized so should not make another call
101 void getDefaultShare().then((defaultShare) => {
102 // We exclude pausing subscription to volumes event of main share
103 if (defaultShare.volumeId !== volumeId) {
104 driveEventManager.volumes.pauseSubscription(volumeId);
108 }, [activeFolder.shareId]);
110 // In case we open preview, folder doesn't need to change.
111 lastFolderPromise.current = folderPromise;
113 const shouldRenderDriveView = Boolean(activeFolder.shareId && activeFolder.linkId);
117 {shouldRenderDriveView ? <DriveView /> : null}
118 <DriveStartupModals />
119 <Route path={`/:shareId?/${LinkURLType.FILE}/:linkId?`} component={PreviewContainer} exact />
124 export const FolderConntainerWrapper = ({ match, ...props }: RouteComponentProps<DriveSectionRouteProps>) => {
125 const { isShareAvailable } = useDefaultShare();
126 const [isLoading, withLoading] = useLoading(true);
127 const { navigateToRoot } = useNavigate();
128 const { handleContextShare } = useContextShareHandler();
131 const abortController = new AbortController();
132 void withLoading(async () => {
133 const { shareId, type, linkId } = match.params;
134 if (!shareId || !linkId || !type || !hasValidLinkType(type)) {
137 await isShareAvailable(abortController.signal, shareId).catch(async (err) => {
138 if (err.data?.Code === API_CUSTOM_ERROR_CODES.NOT_ALLOWED) {
139 await handleContextShare(abortController.signal, {
142 isFile: type === LinkURLType.FILE,
146 console.warn('Provided share is not available, probably locked or soft deleted');
152 abortController.abort();
154 }, [match.params.shareId, match.params.type, match.params.linkId]);
157 return <Loader size="medium" className="absolute inset-center" />;
159 return <FolderContainer match={match} {...props} />;