Merge branch 'feat/inda-383-daily-stat' into 'main'
[ProtonMail-WebClient.git] / applications / drive / src / app / components / sections / FileBrowser / contentCells.tsx
blob3e88fbc8548707083c1e79ed04ce3a0c81fac983
1 import { c } from 'ttag';
3 import { Avatar, Button } from '@proton/atoms';
4 import { FileIcon, Icon, TableCell, useActiveBreakpoint, useConfirmActionModal } from '@proton/components';
5 import { useContactEmails } from '@proton/mail/contactEmails/hooks';
6 import { getInitials } from '@proton/shared/lib/helpers/string';
7 import clsx from '@proton/utils/clsx';
9 import { useActiveShare } from '../../../hooks/drive/useActiveShare';
10 import { useDriveSharingFlags, useInvitationsActions } from '../../../store';
11 import { formatAccessCount } from '../../../utils/formatters';
12 import { Cells } from '../../FileBrowser';
13 import SignatureIcon from '../../SignatureIcon';
14 import type { DeviceItem } from '../Devices/Devices';
15 import type { DriveItem } from '../Drive/Drive';
16 import type { SharedLinkItem } from '../SharedLinks/SharedLinks';
17 import type { SharedWithMeItem } from '../SharedWithMe/SharedWithMe';
18 import type { TrashItem } from '../Trash/Trash';
19 import CopyLinkIcon from './CopyLinkIcon';
20 import ShareIcon from './ShareIcon';
21 import { getDeviceIconText, getLinkIconText } from './utils';
23 const { LocationCell: LocationCellBase, SizeCell: SizeCellBase, NameCell: NameCellBase, TimeCell } = Cells;
25 export const NameCell = ({ item }: { item: DriveItem | SharedLinkItem | SharedWithMeItem | TrashItem }) => {
26     const iconText = getLinkIconText({
27         linkName: item.name,
28         mimeType: item.mimeType,
29         isFile: item.isFile,
30     });
32     return (
33         <TableCell className="m-0 flex items-center flex-nowrap flex-1" data-testid="column-name">
34             {item.cachedThumbnailUrl ? (
35                 <img
36                     src={item.cachedThumbnailUrl}
37                     alt={iconText}
38                     className="file-browser-list-item--thumbnail shrink-0 mr-2"
39                 />
40             ) : (
41                 <FileIcon
42                     mimeType={item.isFile ? item.mimeType : 'Folder'}
43                     alt={iconText}
44                     className="file-browser-list-item--icon mr-2"
45                 />
46             )}
47             <SignatureIcon
48                 signatureIssues={item.signatureIssues}
49                 isAnonymous={!item.activeRevision?.signatureAddress && !item.signatureAddress}
50                 isFile={item.isFile}
51                 className="mr-2 shrink-0"
52             />
53             <NameCellBase name={item.name} />
54         </TableCell>
55     );
58 export const DeviceNameCell = ({ item }: { item: DeviceItem }) => {
59     const iconText = getDeviceIconText(item.name);
61     return (
62         <TableCell
63             className="m-0 flex items-center flex-nowrap flex-1 filebrowser-list-device-name-cell"
64             data-testid="column-name"
65         >
66             <Icon name="tv" alt={iconText} className="mr-2" />
67             <NameCellBase name={item.name} />
68         </TableCell>
69     );
72 export const ModifiedCell = ({ item }: { item: DriveItem }) => {
73     return (
74         <TableCell className="flex items-center m-0 w-1/6" data-testid="column-modified">
75             <TimeCell time={item.fileModifyTime} />
76         </TableCell>
77     );
80 export const ModifiedCellDevice = ({ item }: { item: DeviceItem }) => {
81     return (
82         <TableCell className="flex items-center m-0 w-1/6" data-testid="column-modified">
83             <TimeCell time={item.modificationTime} />
84         </TableCell>
85     );
88 export function SizeCell({ item }: { item: DriveItem | TrashItem }) {
89     const { viewportWidth } = useActiveBreakpoint();
90     return (
91         <TableCell
92             className={clsx(['flex items-center m-0', viewportWidth['>=large'] ? 'w-1/10' : 'w-1/6'])}
93             data-testid="column-size"
94         >
95             {item.isFile ? <SizeCellBase size={item.size} /> : '-'}
96         </TableCell>
97     );
100 export const DeletedCell = ({ item }: { item: TrashItem }) => {
101     return (
102         <TableCell className="flex items-center m-0 w-1/4" data-testid="column-trashed">
103             <TimeCell time={item.trashed || item.fileModifyTime} />
104         </TableCell>
105     );
108 export const CreatedCell = ({ item }: { item: TrashItem | SharedLinkItem }) => {
109     const time = item.shareUrl?.createTime || item.sharedOn;
110     return (
111         <TableCell className="flex items-center m-0 w-1/6" data-testid="column-share-created">
112             {time && <TimeCell time={time} />}
113         </TableCell>
114     );
117 export const LocationCell = ({ item }: { item: TrashItem | SharedLinkItem }) => {
118     const { viewportWidth } = useActiveBreakpoint();
119     const shareId = item.rootShareId;
121     return (
122         <TableCell
123             className={`flex items-center ${clsx(['m-0', viewportWidth['>=large'] ? 'w-1/5' : 'w-1/4'])}`}
124             data-testid="column-location"
125         >
126             <LocationCellBase shareId={shareId} parentLinkId={item.parentLinkId} />
127         </TableCell>
128     );
131 export const AccessCountCell = ({ item }: { item: TrashItem }) => {
132     return (
133         <TableCell className="flex items-center m-0 w-1/6" data-testid="column-num-accesses">
134             {formatAccessCount(item.shareUrl?.numAccesses)}
135         </TableCell>
136     );
139 export const ExpirationCell = ({ item }: { item: TrashItem }) => {
140     const { viewportWidth } = useActiveBreakpoint();
142     const expiredPart = viewportWidth['>=large'] ? (
143         <span className="ml-1">{c('Label').t`(Expired)`}</span>
144     ) : (
145         <span>{c('Label').t`Expired`}</span>
146     );
148     let expiration;
149     if (item.shareUrl) {
150         expiration = item.shareUrl.expireTime ? (
151             <div className="flex flex-nowrap">
152                 {(viewportWidth['>=large'] || !item.shareUrl.isExpired) && <TimeCell time={item.shareUrl.expireTime} />}
153                 {item.shareUrl.isExpired ? expiredPart : null}
154             </div>
155         ) : (
156             c('Label').t`Never`
157         );
158     }
160     return (
161         <TableCell className="flex items-center m-0 w-1/5" data-testid="column-share-expires">
162             {expiration}
163         </TableCell>
164     );
167 export const ShareOptionsCell = ({ item }: { item: DriveItem }) => {
168     const { activeShareId } = useActiveShare();
169     const { isSharingInviteAvailable } = useDriveSharingFlags();
171     return (
172         <TableCell
173             className="m-0 file-browser-list--icon-column file-browser-list--context-menu-column flex items-center"
174             data-testid="column-share-options"
175         >
176             {isSharingInviteAvailable
177                 ? item.isShared &&
178                   item.showLinkSharingModal && (
179                       <ShareIcon
180                           shareId={activeShareId}
181                           linkId={item.linkId}
182                           trashed={item.trashed}
183                           showLinkSharingModal={item.showLinkSharingModal}
184                           isAdmin={item.isAdmin}
185                       />
186                   )
187                 : item.shareUrl && (
188                       <CopyLinkIcon
189                           shareId={activeShareId}
190                           linkId={item.linkId}
191                           isExpired={Boolean(item.shareUrl?.isExpired)}
192                           trashed={item.trashed}
193                       />
194                   )}
195         </TableCell>
196     );
199 export const SharedByCell = ({ item }: { item: SharedWithMeItem }) => {
200     const [contactEmails] = useContactEmails();
202     if (item.isBookmark) {
203         return (
204             <TableCell className="flex flex-nowrap items-center m-0 w-1/5" data-testid="column-shared-by">
205                 <>
206                     <Avatar
207                         color="weak"
208                         className="mr-2 min-w-custom max-w-custom max-h-custom"
209                         style={{
210                             '--min-w-custom': '1.75rem',
211                             '--max-w-custom': '1.75rem',
212                             '--max-h-custom': '1.75rem',
213                         }}
214                     >
215                         <Icon className="color-weak" name="globe" />
216                     </Avatar>
217                     <span className="text-ellipsis color-weak">{c('Info').t`Public link`}</span>
218                 </>
219             </TableCell>
220         );
221     }
222     const email = item.sharedBy;
223     const contactEmail = contactEmails?.find((contactEmail) => contactEmail.Email === email);
224     const displayName = email && contactEmails && contactEmail ? contactEmail.Name : email;
225     return (
226         <TableCell className="flex flex-nowrap items-center m-0 w-1/5" data-testid="column-shared-by">
227             {displayName && (
228                 <>
229                     <Avatar
230                         color="weak"
231                         className="mr-2 min-w-custom max-w-custom max-h-custom"
232                         style={{
233                             '--min-w-custom': '1.75rem',
234                             '--max-w-custom': '1.75rem',
235                             '--max-h-custom': '1.75rem',
236                         }}
237                     >
238                         {getInitials(displayName)}
239                     </Avatar>
240                     <span className="text-ellipsis">{displayName}</span>
241                 </>
242             )}
243         </TableCell>
244     );
247 export const SharedOnCell = ({ item }: { item: SharedWithMeItem }) => {
248     const time = item.bookmarkDetails?.createTime || item.sharedOn;
249     return (
250         <TableCell className="flex items-center m-0 w-1/6" data-testid="column-share-created">
251             {time && <TimeCell time={time} />}
252         </TableCell>
253     );
256 export const AcceptOrRejectInviteCell = ({ item }: { item: SharedWithMeItem }) => {
257     const { acceptInvitation, rejectInvitation } = useInvitationsActions();
258     const [confirmModal, showConfirmModal] = useConfirmActionModal();
259     const invitationDetails = item.invitationDetails;
260     return (
261         <>
262             <TableCell
263                 className="flex flex-nowrap items-center m-0 file-browser-list-item--accept-decline-cell"
264                 data-testid="column-share-accept-reject"
265             >
266                 {invitationDetails && (
267                     <div className="flex flex-nowrap">
268                         <Button
269                             loading={invitationDetails.isLocked}
270                             disabled={invitationDetails.isLocked}
271                             className="text-ellipsis"
272                             color="norm"
273                             shape="ghost"
274                             size="small"
275                             data-testid="share-accept-button"
276                             onClick={async (e) => {
277                                 e.stopPropagation();
278                                 await acceptInvitation(
279                                     new AbortController().signal,
280                                     invitationDetails.invitation.invitationId
281                                 );
282                             }}
283                         >
284                             <span className="file-browser-list-item--accept-decline-text">{c('Action').t`Accept`}</span>
285                         </Button>
286                         {!invitationDetails.isLocked && (
287                             <>
288                                 <Button
289                                     className="text-ellipsis file-browser-list-item--decline"
290                                     color="norm"
291                                     shape="ghost"
292                                     size="small"
293                                     data-testid="share-decline-button"
294                                     onClick={(e) => {
295                                         e.stopPropagation();
296                                         void rejectInvitation(new AbortController().signal, {
297                                             showConfirmModal,
298                                             invitationId: invitationDetails.invitation.invitationId,
299                                         });
300                                     }}
301                                 >
302                                     <span className="file-browser-list-item--accept-decline-text">{c('Action')
303                                         .t`Decline`}</span>
304                                 </Button>
305                             </>
306                         )}
307                     </div>
308                 )}
309             </TableCell>
310             {confirmModal}
311         </>
312     );