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({
28 mimeType: item.mimeType,
33 <TableCell className="m-0 flex items-center flex-nowrap flex-1" data-testid="column-name">
34 {item.cachedThumbnailUrl ? (
36 src={item.cachedThumbnailUrl}
38 className="file-browser-list-item--thumbnail shrink-0 mr-2"
42 mimeType={item.isFile ? item.mimeType : 'Folder'}
44 className="file-browser-list-item--icon mr-2"
48 signatureIssues={item.signatureIssues}
49 isAnonymous={!item.activeRevision?.signatureAddress && !item.signatureAddress}
51 className="mr-2 shrink-0"
53 <NameCellBase name={item.name} />
58 export const DeviceNameCell = ({ item }: { item: DeviceItem }) => {
59 const iconText = getDeviceIconText(item.name);
63 className="m-0 flex items-center flex-nowrap flex-1 filebrowser-list-device-name-cell"
64 data-testid="column-name"
66 <Icon name="tv" alt={iconText} className="mr-2" />
67 <NameCellBase name={item.name} />
72 export const ModifiedCell = ({ item }: { item: DriveItem }) => {
74 <TableCell className="flex items-center m-0 w-1/6" data-testid="column-modified">
75 <TimeCell time={item.fileModifyTime} />
80 export const ModifiedCellDevice = ({ item }: { item: DeviceItem }) => {
82 <TableCell className="flex items-center m-0 w-1/6" data-testid="column-modified">
83 <TimeCell time={item.modificationTime} />
88 export function SizeCell({ item }: { item: DriveItem | TrashItem }) {
89 const { viewportWidth } = useActiveBreakpoint();
92 className={clsx(['flex items-center m-0', viewportWidth['>=large'] ? 'w-1/10' : 'w-1/6'])}
93 data-testid="column-size"
95 {item.isFile ? <SizeCellBase size={item.size} /> : '-'}
100 export const DeletedCell = ({ item }: { item: TrashItem }) => {
102 <TableCell className="flex items-center m-0 w-1/4" data-testid="column-trashed">
103 <TimeCell time={item.trashed || item.fileModifyTime} />
108 export const CreatedCell = ({ item }: { item: TrashItem | SharedLinkItem }) => {
109 const time = item.shareUrl?.createTime || item.sharedOn;
111 <TableCell className="flex items-center m-0 w-1/6" data-testid="column-share-created">
112 {time && <TimeCell time={time} />}
117 export const LocationCell = ({ item }: { item: TrashItem | SharedLinkItem }) => {
118 const { viewportWidth } = useActiveBreakpoint();
119 const shareId = item.rootShareId;
123 className={`flex items-center ${clsx(['m-0', viewportWidth['>=large'] ? 'w-1/5' : 'w-1/4'])}`}
124 data-testid="column-location"
126 <LocationCellBase shareId={shareId} parentLinkId={item.parentLinkId} />
131 export const AccessCountCell = ({ item }: { item: TrashItem }) => {
133 <TableCell className="flex items-center m-0 w-1/6" data-testid="column-num-accesses">
134 {formatAccessCount(item.shareUrl?.numAccesses)}
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>
145 <span>{c('Label').t`Expired`}</span>
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}
161 <TableCell className="flex items-center m-0 w-1/5" data-testid="column-share-expires">
167 export const ShareOptionsCell = ({ item }: { item: DriveItem }) => {
168 const { activeShareId } = useActiveShare();
169 const { isSharingInviteAvailable } = useDriveSharingFlags();
173 className="m-0 file-browser-list--icon-column file-browser-list--context-menu-column flex items-center"
174 data-testid="column-share-options"
176 {isSharingInviteAvailable
178 item.showLinkSharingModal && (
180 shareId={activeShareId}
182 trashed={item.trashed}
183 showLinkSharingModal={item.showLinkSharingModal}
184 isAdmin={item.isAdmin}
189 shareId={activeShareId}
191 isExpired={Boolean(item.shareUrl?.isExpired)}
192 trashed={item.trashed}
199 export const SharedByCell = ({ item }: { item: SharedWithMeItem }) => {
200 const [contactEmails] = useContactEmails();
202 if (item.isBookmark) {
204 <TableCell className="flex flex-nowrap items-center m-0 w-1/5" data-testid="column-shared-by">
208 className="mr-2 min-w-custom max-w-custom max-h-custom"
210 '--min-w-custom': '1.75rem',
211 '--max-w-custom': '1.75rem',
212 '--max-h-custom': '1.75rem',
215 <Icon className="color-weak" name="globe" />
217 <span className="text-ellipsis color-weak">{c('Info').t`Public link`}</span>
222 const email = item.sharedBy;
223 const contactEmail = contactEmails?.find((contactEmail) => contactEmail.Email === email);
224 const displayName = email && contactEmails && contactEmail ? contactEmail.Name : email;
226 <TableCell className="flex flex-nowrap items-center m-0 w-1/5" data-testid="column-shared-by">
231 className="mr-2 min-w-custom max-w-custom max-h-custom"
233 '--min-w-custom': '1.75rem',
234 '--max-w-custom': '1.75rem',
235 '--max-h-custom': '1.75rem',
238 {getInitials(displayName)}
240 <span className="text-ellipsis">{displayName}</span>
247 export const SharedOnCell = ({ item }: { item: SharedWithMeItem }) => {
248 const time = item.bookmarkDetails?.createTime || item.sharedOn;
250 <TableCell className="flex items-center m-0 w-1/6" data-testid="column-share-created">
251 {time && <TimeCell time={time} />}
256 export const AcceptOrRejectInviteCell = ({ item }: { item: SharedWithMeItem }) => {
257 const { acceptInvitation, rejectInvitation } = useInvitationsActions();
258 const [confirmModal, showConfirmModal] = useConfirmActionModal();
259 const invitationDetails = item.invitationDetails;
263 className="flex flex-nowrap items-center m-0 file-browser-list-item--accept-decline-cell"
264 data-testid="column-share-accept-reject"
266 {invitationDetails && (
267 <div className="flex flex-nowrap">
269 loading={invitationDetails.isLocked}
270 disabled={invitationDetails.isLocked}
271 className="text-ellipsis"
275 data-testid="share-accept-button"
276 onClick={async (e) => {
278 await acceptInvitation(
279 new AbortController().signal,
280 invitationDetails.invitation.invitationId
284 <span className="file-browser-list-item--accept-decline-text">{c('Action').t`Accept`}</span>
286 {!invitationDetails.isLocked && (
289 className="text-ellipsis file-browser-list-item--decline"
293 data-testid="share-decline-button"
296 void rejectInvitation(new AbortController().signal, {
298 invitationId: invitationDetails.invitation.invitationId,
302 <span className="file-browser-list-item--accept-decline-text">{c('Action')