1 import type { PropsWithChildren } from 'react';
2 import { createContext, useContext, useMemo, useRef, useState } from 'react';
4 import { fromUnixTime } from 'date-fns';
5 import { c } from 'ttag';
7 import { useConfirmActionModal, useNotifications } from '@proton/components';
8 import type { SHARE_MEMBER_PERMISSIONS } from '@proton/shared/lib/drive/permissions';
9 import { isPreviewAvailable } from '@proton/shared/lib/helpers/preview';
10 import { dateLocale } from '@proton/shared/lib/i18n';
11 import { FileRevisionState } from '@proton/shared/lib/interfaces/drive/file';
12 import clsx from '@proton/utils/clsx';
14 import type { DecryptedLink, DriveFileRevision } from '../../store';
15 import { useDownload, useRevisionsView } from '../../store';
16 import PortalPreview from '../PortalPreview';
17 import { useRevisionDetailsModal } from '../modals/DetailsModal';
18 import type { CategorizedRevisions } from './getCategorizedRevisions';
19 import { getCategorizedRevisions } from './getCategorizedRevisions';
21 import './RevisionPreview.scss';
23 export interface RevisionsProviderState {
24 hasPreviewAvailable: boolean;
26 permissions: SHARE_MEMBER_PERMISSIONS;
27 currentRevision: DriveFileRevision;
28 categorizedRevisions: CategorizedRevisions;
29 openRevisionPreview: (revision: DriveFileRevision) => void;
30 openRevisionDetails: (revision: DriveFileRevision) => void;
31 deleteRevision: (abortSignal: AbortSignal, revision: DriveFileRevision) => void;
32 restoreRevision: (abortSignal: AbortSignal, revision: DriveFileRevision) => void;
33 downloadRevision: (revisionId: string) => void;
36 const RevisionsContext = createContext<RevisionsProviderState | null>(null);
38 export const RevisionsProvider = ({
41 }: PropsWithChildren<{
44 const { createNotification } = useNotifications();
45 const ref = useRef(null);
49 revisions: [currentRevision, ...olderRevisions],
53 } = useRevisionsView(link.rootShareId, link.linkId);
54 const categorizedRevisions = useMemo(() => getCategorizedRevisions(olderRevisions), [olderRevisions]);
55 const [confirmModal, showConfirmModal] = useConfirmActionModal();
56 const [revisionDetailsModal, showRevisionDetailsModal] = useRevisionDetailsModal();
57 const hasPreviewAvailable = !!link.mimeType && isPreviewAvailable(link.mimeType, link.size);
58 const [selectedRevision, setSelectedRevision] = useState<DriveFileRevision | null>(null);
59 const { download } = useDownload();
61 const downloadRevision = (revisionId: string) => {
62 void download([{ ...link, shareId: link.rootShareId, revisionId: revisionId }]);
65 const openRevisionDetails = (revision: DriveFileRevision) => {
66 void showRevisionDetailsModal({
68 shareId: link.rootShareId,
74 const handleRevisionDelete = (abortSignal: AbortSignal, revision: DriveFileRevision) => {
75 const formattedRevisionDate = new Intl.DateTimeFormat(dateLocale.code, {
78 }).format(fromUnixTime(revision.createTime));
79 void showConfirmModal({
81 title: c('Action').t`Delete this version`,
82 submitText: c('Action').t`Delete permanently`,
86 // translator: complete sentence example: Are you sure you want to permanently delete Yearly reports.docx, February 6, 2023 at 12:00 from the version history?
87 c('Info').t`Are you sure you want to permanently delete`
90 <span className="text-bold">
91 {link.name}, {formattedRevisionDate}
95 // translator: complete sentence example: Are you sure you want to permanently delete Yearly reports.docx, February 6, 2023 at 12:00 from the version history?
96 c('Info').t`from the version history?`
101 deleteRevision(abortSignal, revision.id).then(() => {
103 text: c('Info').t`Version is deleted`,
109 const handleRevisionRestore = (abortSignal: AbortSignal, revision: DriveFileRevision) => {
110 const formattedRevisionDate = new Intl.DateTimeFormat(dateLocale.code, {
113 }).format(fromUnixTime(revision.createTime));
114 void showConfirmModal({
116 title: c('Action').t`Restore this version`,
117 submitText: c('Action').t`Restore`,
122 <span className="text-bold">{link.name}</span>
125 // translator: complete sentence example: Yearly reports.docx. will be restored to the version from February 6, 2023 at 12:00. All other versions will still be saved.
127 .t`will be restored to the version from ${formattedRevisionDate}. All other versions will still be saved.`
132 restoreRevision(abortSignal, revision.id).then((Code) => {
135 text: c('Info').t`Version is restored`,
139 text: c('Info').t`Restore is in progress. This can take a few seconds.`,
146 const openRevisionPreview = (revision: DriveFileRevision) => {
147 setSelectedRevision(revision);
151 <RevisionsContext.Provider
156 deleteRevision: handleRevisionDelete,
157 restoreRevision: handleRevisionRestore,
159 categorizedRevisions,
166 {selectedRevision && (
168 key="portal-preview-revisions"
169 open={!!selectedRevision}
171 revisionId={selectedRevision.id}
172 shareId={link.rootShareId}
174 date={selectedRevision.createTime}
177 (confirmModal?.props.open || revisionDetailsModal?.props.open) && 'revision-preview--behind'
179 onDetails={() => openRevisionDetails(selectedRevision)}
181 selectedRevision.state !== FileRevisionState.Active
183 handleRevisionRestore(new AbortController().signal, selectedRevision);
187 onClose={() => setSelectedRevision(null)}
188 onExit={() => setSelectedRevision(null)}
192 {revisionDetailsModal}
194 </RevisionsContext.Provider>
198 export const useRevisionsProvider = () => {
199 const state = useContext(RevisionsContext);
201 throw new Error('Trying to use uninitialized RevisionsProvider');