feat: update list view to be table (#628)
This commit is contained in:
@@ -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 (
|
||||
<GalleryCard
|
||||
{...{
|
||||
handleLockedAsset,
|
||||
handleBulkDownload,
|
||||
handleOpenDeleteConfirmation,
|
||||
handleOpenAssetInfo,
|
||||
className,
|
||||
original,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<ListCard
|
||||
{...{
|
||||
handleLockedAsset,
|
||||
handleBulkDownload,
|
||||
handleOpenDeleteConfirmation,
|
||||
handleOpenAssetInfo,
|
||||
className,
|
||||
original,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
const fileCard = ({ className, original }) => (
|
||||
<GalleryCard
|
||||
{...{
|
||||
handleLockedAsset,
|
||||
handleBulkDownload,
|
||||
handleOpenDeleteConfirmation,
|
||||
handleOpenAssetInfo,
|
||||
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 = ({
|
||||
<div data-testid="files-data-table" className="mr-4 ml-3">
|
||||
<DataTable.TableControlBar />
|
||||
{ currentView === 'card' && <CardView CardComponent={fileCard} columnSizes={columnSizes} selectionPlacement="left" skeletonCardCount={6} /> }
|
||||
{ currentView === 'list' && <CardView CardComponent={fileCard} columnSizes={{ xs: 12 }} selectionPlacement="left" skeletonCardCount={4} /> }
|
||||
{ currentView === 'list' && <DataTable.Table /> }
|
||||
<DataTable.EmptyTable content={intl.formatMessage(messages.noResultsFoundMessage)} />
|
||||
<DataTable.TableFooter />
|
||||
<ApiStatusToast
|
||||
|
||||
@@ -168,7 +168,7 @@ describe('FilesAndUploads', () => {
|
||||
|
||||
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();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -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 (
|
||||
<OverlayTrigger
|
||||
placement="top"
|
||||
overlay={(
|
||||
<Tooltip id="access-tooltip-description">
|
||||
{intl.formatMessage(messages.lockFileTooltipContent)}
|
||||
</Tooltip>
|
||||
)}
|
||||
>
|
||||
{locked ? (
|
||||
<Icon src={Locked} size="sm" />
|
||||
) : (
|
||||
<Icon src={LockOpen} size="sm" />
|
||||
)}
|
||||
</OverlayTrigger>
|
||||
);
|
||||
};
|
||||
|
||||
AccessColumn.propTypes = {
|
||||
row: {
|
||||
original: {
|
||||
locked: PropTypes.bool.isRequired,
|
||||
}.isRequired,
|
||||
}.isRequired,
|
||||
// injected
|
||||
intl: intlShape.isRequired,
|
||||
};
|
||||
|
||||
export default injectIntl(AccessColumn);
|
||||
@@ -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 ? <Icon src={Check} /> : null;
|
||||
};
|
||||
|
||||
ActiveColumn.propTypes = {
|
||||
row: {
|
||||
original: {
|
||||
usageLocations: PropTypes.arrayOf(PropTypes.string).isRequired,
|
||||
}.isRequired,
|
||||
}.isRequired,
|
||||
};
|
||||
|
||||
export default ActiveColumn;
|
||||
@@ -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 (
|
||||
<>
|
||||
<IconButton src={MoreHoriz} iconAs={Icon} onClick={toggle} ref={setTarget} />
|
||||
<ModalPopup
|
||||
placement="left"
|
||||
positionRef={target}
|
||||
isOpen={isOpen}
|
||||
onClose={close}
|
||||
onEscapeKey={close}
|
||||
style={{
|
||||
|
||||
}}
|
||||
>
|
||||
<Menu
|
||||
className="border border-light-400"
|
||||
style={{ overflowX: 'hidden', boxShadow: '0px 0px 0px #000', maxHeight: '500px' }}
|
||||
>
|
||||
{wrapperType === 'video' ? (
|
||||
<MenuItem
|
||||
as={Button}
|
||||
variant="tertiary"
|
||||
size="inline"
|
||||
onClick={() => {
|
||||
navigator.clipboard.writeText(id);
|
||||
close();
|
||||
}}
|
||||
>
|
||||
Copy video ID
|
||||
</MenuItem>
|
||||
) : (
|
||||
<>
|
||||
<MenuItem
|
||||
as={Button}
|
||||
variant="tertiary"
|
||||
size="inline"
|
||||
onClick={() => {
|
||||
navigator.clipboard.writeText(portableUrl);
|
||||
close();
|
||||
}}
|
||||
>
|
||||
{intl.formatMessage(messages.copyStudioUrlTitle)}
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
as={Button}
|
||||
variant="tertiary"
|
||||
size="inline"
|
||||
onClick={() => {
|
||||
navigator.clipboard.writeText(externalUrl);
|
||||
close();
|
||||
}}
|
||||
>
|
||||
{intl.formatMessage(messages.copyWebUrlTitle)}
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
as={Button}
|
||||
variant="tertiary"
|
||||
size="inline"
|
||||
onClick={() => handleLock(id, !locked)}
|
||||
>
|
||||
{locked ? intl.formatMessage(messages.unlockMenuTitle) : intl.formatMessage(messages.lockMenuTitle)}
|
||||
</MenuItem>
|
||||
</>
|
||||
)}
|
||||
<MenuItem
|
||||
as={Button}
|
||||
variant="tertiary"
|
||||
size="inline"
|
||||
onClick={() => handleBulkDownload(
|
||||
[{ original: { id, displayName } }],
|
||||
)}
|
||||
>
|
||||
{intl.formatMessage(messages.downloadTitle)}
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
as={Button}
|
||||
variant="tertiary"
|
||||
size="inline"
|
||||
onClick={() => handleOpenAssetInfo(row.original)}
|
||||
>
|
||||
{intl.formatMessage(messages.infoTitle)}
|
||||
</MenuItem>
|
||||
<hr className="m-0" />
|
||||
<MenuItem
|
||||
as={Button}
|
||||
variant="tertiary"
|
||||
size="inline"
|
||||
data-testid="open-delete-confirmation-button"
|
||||
onClick={() => {
|
||||
handleOpenDeleteConfirmation([{ original: row.original }]);
|
||||
close();
|
||||
}}
|
||||
>
|
||||
{intl.formatMessage(messages.deleteTitle)}
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
</ModalPopup>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
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);
|
||||
@@ -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 (
|
||||
<Badge variant="light">
|
||||
{status}
|
||||
</Badge>
|
||||
);
|
||||
};
|
||||
|
||||
StatusColumn.propTypes = {
|
||||
row: {
|
||||
original: {
|
||||
status: PropTypes.string.isRequired,
|
||||
}.isRequired,
|
||||
}.isRequired,
|
||||
};
|
||||
|
||||
export default StatusColumn;
|
||||
@@ -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 (
|
||||
<div className="row align-items-center justify-content-center m-0 p-3">
|
||||
{thumbnail ? (
|
||||
<Image src={src} style={{ height: '76px', width: '135.71px' }} className="border rounded p-1" />
|
||||
) : (
|
||||
<div className="row border justify-content-center align-items-center rounded m-0" style={{ height: '76px', width: '135.71px' }}>
|
||||
<Icon src={src} style={{ height: '48px', width: '48px' }} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
ThumbnailColumn.propTypes = {
|
||||
row: {
|
||||
original: {
|
||||
thumbnail: PropTypes.string,
|
||||
wrapperType: PropTypes.string.isRequired,
|
||||
externalUrl: PropTypes.string,
|
||||
}.isRequired,
|
||||
}.isRequired,
|
||||
};
|
||||
|
||||
export default ThumbnailColumn;
|
||||
@@ -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,
|
||||
};
|
||||
Reference in New Issue
Block a user