diff --git a/src/files-and-uploads/FilesAndUploads.jsx b/src/files-and-uploads/FilesAndUploads.jsx index d2a203de0..97f48cfc9 100644 --- a/src/files-and-uploads/FilesAndUploads.jsx +++ b/src/files-and-uploads/FilesAndUploads.jsx @@ -28,7 +28,7 @@ import { updateAssetOrder, fetchAssetDownload, } from './data/thunks'; -import { sortFiles } from './data/utils'; +import { getFileSizeToClosestByte, sortFiles } from './data/utils'; import messages from './messages'; import FileInfo from './FileInfo'; @@ -36,9 +36,9 @@ import FileInput, { useFileInput } from './FileInput'; import FilesAndUploadsProvider from './FilesAndUploadsProvider'; import { GalleryCard, - ListCard, TableActions, } from './table-components'; +import { AccessColumn, MoreInfoColumn, ThumbnailColumn } from './table-components/table-custom-columns'; import ApiStatusToast from './ApiStatusToast'; import { clearErrors } from './data/slice'; import getPageHeadTitle from '../generic/utils'; @@ -141,34 +141,83 @@ const FilesAndUploads = ({ /> ); - const fileCard = ({ className, original }) => { - if (currentView === defaultVal) { - return ( - - ); - } - return ( - - ); + const fileCard = ({ className, original }) => ( + + ); + + const accessColumn = { + id: 'locked', + Header: 'Access', + Cell: ({ row }) => AccessColumn({ row }), }; + const thumbnailColumn = { + id: 'thumbnail', + Header: '', + Cell: ({ row }) => ThumbnailColumn({ row }), + }; + const fileSizeColumn = { + id: 'fileSize', + Header: 'File size', + Cell: ({ row }) => { + const { fileSize } = row.original; + return getFileSizeToClosestByte(fileSize); + }, + }; + const moreInfoColumn = { + id: 'moreInfo', + Header: '', + Cell: ({ row }) => MoreInfoColumn({ + row, + handleLock: handleLockedAsset, + handleBulkDownload, + handleOpenAssetInfo, + handleOpenDeleteConfirmation, + }), + }; + + const tableColumns = [ + { ...thumbnailColumn }, + { + Header: 'File name', + accessor: 'displayName', + }, + { ...fileSizeColumn }, + { + Header: 'Type', + accessor: 'wrapperType', + Filter: CheckboxFilter, + filter: 'includesValue', + filterChoices: [ + { + name: 'Code', + value: 'code', + }, + { + name: 'Images', + value: 'image', + }, + { + name: 'Documents', + value: 'document', + }, + { + name: 'Audio', + value: 'audio', + }, + ], + }, + { ...accessColumn }, + { ...moreInfoColumn }, + ]; if (loadingStatus === RequestStatus.DENIED) { return ( @@ -244,36 +293,7 @@ const FilesAndUploads = ({ }} tableActions={headerActions} bulkActions={headerActions} - columns={[ - { - Header: 'Name', - accessor: 'displayName', - }, - { - Header: 'Type', - accessor: 'wrapperType', - Filter: CheckboxFilter, - filter: 'includesValue', - filterChoices: [ - { - name: 'Code', - value: 'code', - }, - { - name: 'Images', - value: 'image', - }, - { - name: 'Documents', - value: 'document', - }, - { - name: 'Audio', - value: 'audio', - }, - ], - }, - ]} + columns={tableColumns} itemCount={totalCount} pageCount={Math.ceil(totalCount / 50)} data={assets} @@ -292,7 +312,7 @@ const FilesAndUploads = ({
{ currentView === 'card' && } - { currentView === 'list' && } + { currentView === 'list' && } { expect(screen.getByTestId('grid-card-mOckID1')).toBeVisible(); - expect(screen.queryByTestId('list-card-mOckID1')).toBeNull(); + expect(screen.queryByRole('table')).toBeNull(); const listButton = screen.getByLabelText('List'); await act(async () => { @@ -176,7 +176,7 @@ describe('FilesAndUploads', () => { }); expect(screen.queryByTestId('grid-card-mOckID1')).toBeNull(); - expect(screen.getByTestId('list-card-mOckID1')).toBeVisible(); + expect(screen.getByRole('table')).toBeVisible(); }); }); diff --git a/src/files-and-uploads/table-components/table-custom-columns/AccessColumn.jsx b/src/files-and-uploads/table-components/table-custom-columns/AccessColumn.jsx new file mode 100644 index 000000000..b1cde1e03 --- /dev/null +++ b/src/files-and-uploads/table-components/table-custom-columns/AccessColumn.jsx @@ -0,0 +1,42 @@ +import React from 'react'; +import { PropTypes } from 'prop-types'; +import { Icon, OverlayTrigger, Tooltip } from '@edx/paragon'; +import { Locked, LockOpen } from '@edx/paragon/icons'; +import { injectIntl, intlShape } from '@edx/frontend-platform/i18n'; +import messages from '../../messages'; + +const AccessColumn = ({ + row, + // injected + intl, +}) => { + const { locked } = row.original; + return ( + + {intl.formatMessage(messages.lockFileTooltipContent)} + + )} + > + {locked ? ( + + ) : ( + + )} + + ); +}; + +AccessColumn.propTypes = { + row: { + original: { + locked: PropTypes.bool.isRequired, + }.isRequired, + }.isRequired, + // injected + intl: intlShape.isRequired, +}; + +export default injectIntl(AccessColumn); diff --git a/src/files-and-uploads/table-components/table-custom-columns/ActiveColumn.jsx b/src/files-and-uploads/table-components/table-custom-columns/ActiveColumn.jsx new file mode 100644 index 000000000..a604dfe34 --- /dev/null +++ b/src/files-and-uploads/table-components/table-custom-columns/ActiveColumn.jsx @@ -0,0 +1,20 @@ +import React from 'react'; +import { PropTypes } from 'prop-types'; +import { Icon } from '@edx/paragon'; +import { Check } from '@edx/paragon/icons'; + +const ActiveColumn = ({ row }) => { + const { usageLocations } = row.original; + const numOfUsageLocations = usageLocations.length; + return numOfUsageLocations > 0 ? : null; +}; + +ActiveColumn.propTypes = { + row: { + original: { + usageLocations: PropTypes.arrayOf(PropTypes.string).isRequired, + }.isRequired, + }.isRequired, +}; + +export default ActiveColumn; diff --git a/src/files-and-uploads/table-components/table-custom-columns/MoreInfoColumn.jsx b/src/files-and-uploads/table-components/table-custom-columns/MoreInfoColumn.jsx new file mode 100644 index 000000000..762b553ab --- /dev/null +++ b/src/files-and-uploads/table-components/table-custom-columns/MoreInfoColumn.jsx @@ -0,0 +1,155 @@ +import React, { useState } from 'react'; +import { PropTypes } from 'prop-types'; +import { injectIntl, intlShape } from '@edx/frontend-platform/i18n'; +import { + Button, + Icon, + IconButton, + ModalPopup, + Menu, + MenuItem, + useToggle, +} from '@edx/paragon'; +import { MoreHoriz } from '@edx/paragon/icons'; + +import messages from '../../messages'; + +const MoreInfoColumn = ({ + row, + handleLock, + handleBulkDownload, + handleOpenAssetInfo, + handleOpenDeleteConfirmation, + // injected + intl, +}) => { + const [isOpen, , close, toggle] = useToggle(); + const [target, setTarget] = useState(null); + + const { + externalUrl, + locked, + portableUrl, + id, + wrapperType, + displayName, + } = row.original; + return ( + <> + + + + {wrapperType === 'video' ? ( + { + navigator.clipboard.writeText(id); + close(); + }} + > + Copy video ID + + ) : ( + <> + { + navigator.clipboard.writeText(portableUrl); + close(); + }} + > + {intl.formatMessage(messages.copyStudioUrlTitle)} + + { + navigator.clipboard.writeText(externalUrl); + close(); + }} + > + {intl.formatMessage(messages.copyWebUrlTitle)} + + handleLock(id, !locked)} + > + {locked ? intl.formatMessage(messages.unlockMenuTitle) : intl.formatMessage(messages.lockMenuTitle)} + + + )} + handleBulkDownload( + [{ original: { id, displayName } }], + )} + > + {intl.formatMessage(messages.downloadTitle)} + + handleOpenAssetInfo(row.original)} + > + {intl.formatMessage(messages.infoTitle)} + +
+ { + handleOpenDeleteConfirmation([{ original: row.original }]); + close(); + }} + > + {intl.formatMessage(messages.deleteTitle)} + +
+
+ + ); +}; + +MoreInfoColumn.propTypes = { + row: { + original: { + externalUrl: PropTypes.string, + locked: PropTypes.bool, + portableUrl: PropTypes.string, + id: PropTypes.string.isRequired, + wrapperType: PropTypes.string, + }.isRequired, + }.isRequired, + handleLock: PropTypes.func.isRequired, + handleBulkDownload: PropTypes.func.isRequired, + handleOpenAssetInfo: PropTypes.func.isRequired, + handleOpenDeleteConfirmation: PropTypes.func.isRequired, + // injected + intl: intlShape.isRequired, +}; + +export default injectIntl(MoreInfoColumn); diff --git a/src/files-and-uploads/table-components/table-custom-columns/StatusColumn.jsx b/src/files-and-uploads/table-components/table-custom-columns/StatusColumn.jsx new file mode 100644 index 000000000..8773042e1 --- /dev/null +++ b/src/files-and-uploads/table-components/table-custom-columns/StatusColumn.jsx @@ -0,0 +1,22 @@ +import React from 'react'; +import { PropTypes } from 'prop-types'; +import { Badge } from '@edx/paragon'; + +const StatusColumn = ({ row }) => { + const { status } = row.original; + return ( + + {status} + + ); +}; + +StatusColumn.propTypes = { + row: { + original: { + status: PropTypes.string.isRequired, + }.isRequired, + }.isRequired, +}; + +export default StatusColumn; diff --git a/src/files-and-uploads/table-components/table-custom-columns/ThumbnailColumn.jsx b/src/files-and-uploads/table-components/table-custom-columns/ThumbnailColumn.jsx new file mode 100644 index 000000000..cfbbc962f --- /dev/null +++ b/src/files-and-uploads/table-components/table-custom-columns/ThumbnailColumn.jsx @@ -0,0 +1,37 @@ +import React from 'react'; +import { PropTypes } from 'prop-types'; +import { Image, Icon } from '@edx/paragon'; +import { getSrc } from '../../data/utils'; + +const ThumbnailColumn = ({ row }) => { + const { + thumbnail, + wrapperType, + externalUrl, + } = row.original; + + const src = getSrc({ thumbnail, wrapperType, externalUrl }); + return ( +
+ {thumbnail ? ( + + ) : ( +
+ +
+ )} +
+ ); +}; + +ThumbnailColumn.propTypes = { + row: { + original: { + thumbnail: PropTypes.string, + wrapperType: PropTypes.string.isRequired, + externalUrl: PropTypes.string, + }.isRequired, + }.isRequired, +}; + +export default ThumbnailColumn; diff --git a/src/files-and-uploads/table-components/table-custom-columns/index.js b/src/files-and-uploads/table-components/table-custom-columns/index.js new file mode 100644 index 000000000..78284945f --- /dev/null +++ b/src/files-and-uploads/table-components/table-custom-columns/index.js @@ -0,0 +1,13 @@ +import AccessColumn from './AccessColumn'; +import ActiveColumn from './ActiveColumn'; +import MoreInfoColumn from './MoreInfoColumn'; +import StatusColumn from './StatusColumn'; +import ThumbnailColumn from './ThumbnailColumn'; + +export { + AccessColumn, + ActiveColumn, + MoreInfoColumn, + StatusColumn, + ThumbnailColumn, +};