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"
47 <SignatureIcon signatureIssues={item.signatureIssues} isFile={item.isFile} className="mr-2 shrink-0" />
48 <NameCellBase name={item.name} />
53 export const DeviceNameCell = ({ item }: { item: DeviceItem }) => {
54 const iconText = getDeviceIconText(item.name);
58 className="m-0 flex items-center flex-nowrap flex-1 filebrowser-list-device-name-cell"
59 data-testid="column-name"
61 <Icon name="tv" alt={iconText} className="mr-2" />
62 <NameCellBase name={item.name} />
67 export const ModifiedCell = ({ item }: { item: DriveItem }) => {
69 <TableCell className="flex items-center m-0 w-1/6" data-testid="column-modified">
70 <TimeCell time={item.fileModifyTime} />
75 export const ModifiedCellDevice = ({ item }: { item: DeviceItem }) => {
77 <TableCell className="flex items-center m-0 w-1/6" data-testid="column-modified">
78 <TimeCell time={item.modificationTime} />
83 export function SizeCell({ item }: { item: DriveItem | TrashItem }) {
84 const { viewportWidth } = useActiveBreakpoint();
87 className={clsx(['flex items-center m-0', viewportWidth['>=large'] ? 'w-1/10' : 'w-1/6'])}
88 data-testid="column-size"
90 {item.isFile ? <SizeCellBase size={item.size} /> : '-'}
95 export const DeletedCell = ({ item }: { item: TrashItem }) => {
97 <TableCell className="flex items-center m-0 w-1/4" data-testid="column-trashed">
98 <TimeCell time={item.trashed || item.fileModifyTime} />
103 export const CreatedCell = ({ item }: { item: TrashItem | SharedLinkItem }) => {
104 const time = item.shareUrl?.createTime || item.sharedOn;
106 <TableCell className="flex items-center m-0 w-1/6" data-testid="column-share-created">
107 {time && <TimeCell time={time} />}
112 export const LocationCell = ({ item }: { item: TrashItem | SharedLinkItem }) => {
113 const { viewportWidth } = useActiveBreakpoint();
114 const shareId = item.rootShareId;
118 className={`flex items-center ${clsx(['m-0', viewportWidth['>=large'] ? 'w-1/5' : 'w-1/4'])}`}
119 data-testid="column-location"
121 <LocationCellBase shareId={shareId} parentLinkId={item.parentLinkId} />
126 export const AccessCountCell = ({ item }: { item: TrashItem }) => {
128 <TableCell className="flex items-center m-0 w-1/6" data-testid="column-num-accesses">
129 {formatAccessCount(item.shareUrl?.numAccesses)}
134 export const ExpirationCell = ({ item }: { item: TrashItem }) => {
135 const { viewportWidth } = useActiveBreakpoint();
137 const expiredPart = viewportWidth['>=large'] ? (
138 <span className="ml-1">{c('Label').t`(Expired)`}</span>
140 <span>{c('Label').t`Expired`}</span>
145 expiration = item.shareUrl.expireTime ? (
146 <div className="flex flex-nowrap">
147 {(viewportWidth['>=large'] || !item.shareUrl.isExpired) && <TimeCell time={item.shareUrl.expireTime} />}
148 {item.shareUrl.isExpired ? expiredPart : null}
156 <TableCell className="flex items-center m-0 w-1/5" data-testid="column-share-expires">
162 export const ShareOptionsCell = ({ item }: { item: DriveItem }) => {
163 const { activeShareId } = useActiveShare();
164 const { isSharingInviteAvailable } = useDriveSharingFlags();
168 className="m-0 file-browser-list--icon-column file-browser-list--context-menu-column flex items-center"
169 data-testid="column-share-options"
171 {isSharingInviteAvailable
173 item.showLinkSharingModal && (
175 shareId={activeShareId}
177 trashed={item.trashed}
178 showLinkSharingModal={item.showLinkSharingModal}
179 isAdmin={item.isAdmin}
184 shareId={activeShareId}
186 isExpired={Boolean(item.shareUrl?.isExpired)}
187 trashed={item.trashed}
194 export const SharedByCell = ({ item }: { item: SharedWithMeItem }) => {
195 const [contactEmails] = useContactEmails();
197 if (item.isBookmark) {
199 <TableCell className="flex flex-nowrap items-center m-0 w-1/5" data-testid="column-shared-by">
203 className="mr-2 min-w-custom max-w-custom max-h-custom"
205 '--min-w-custom': '1.75rem',
206 '--max-w-custom': '1.75rem',
207 '--max-h-custom': '1.75rem',
210 <Icon className="color-weak" name="globe" />
212 <span className="text-ellipsis color-weak">{c('Info').t`Public link`}</span>
217 const email = item.sharedBy;
218 const contactEmail = contactEmails?.find((contactEmail) => contactEmail.Email === email);
219 const displayName = email && contactEmails && contactEmail ? contactEmail.Name : email;
221 <TableCell className="flex flex-nowrap items-center m-0 w-1/5" data-testid="column-shared-by">
226 className="mr-2 min-w-custom max-w-custom max-h-custom"
228 '--min-w-custom': '1.75rem',
229 '--max-w-custom': '1.75rem',
230 '--max-h-custom': '1.75rem',
233 {getInitials(displayName)}
235 <span className="text-ellipsis">{displayName}</span>
242 export const SharedOnCell = ({ item }: { item: SharedWithMeItem }) => {
243 const time = item.bookmarkDetails?.createTime || item.sharedOn;
245 <TableCell className="flex items-center m-0 w-1/6" data-testid="column-share-created">
246 {time && <TimeCell time={time} />}
251 export const AcceptOrRejectInviteCell = ({ item }: { item: SharedWithMeItem }) => {
252 const { acceptInvitation, rejectInvitation } = useInvitationsActions();
253 const [confirmModal, showConfirmModal] = useConfirmActionModal();
254 const invitationDetails = item.invitationDetails;
258 className="flex flex-nowrap items-center m-0 file-browser-list-item--accept-decline-cell"
259 data-testid="column-share-accept-reject"
261 {invitationDetails && (
262 <div className="flex flex-nowrap">
264 loading={invitationDetails.isLocked}
265 disabled={invitationDetails.isLocked}
266 className="text-ellipsis"
270 data-testid="share-accept-button"
271 onClick={async (e) => {
273 await acceptInvitation(
274 new AbortController().signal,
275 invitationDetails.invitation.invitationId
279 <span className="file-browser-list-item--accept-decline-text">{c('Action').t`Accept`}</span>
281 {!invitationDetails.isLocked && (
284 className="text-ellipsis file-browser-list-item--decline"
288 data-testid="share-decline-button"
291 void rejectInvitation(new AbortController().signal, {
293 invitationId: invitationDetails.invitation.invitationId,
297 <span className="file-browser-list-item--accept-decline-text">{c('Action')