mirror of
https://github.com/strapi/strapi.git
synced 2025-12-27 23:24:03 +00:00
Merge remote-tracking branch 'origin/features/MediaLibrary-listview' into MediaLibrary-listview/view-switch
This commit is contained in:
commit
530cb8b701
@ -23,7 +23,7 @@ import { FolderDefinition, AssetDefinition, viewOptions } from '../../../constan
|
||||
import getTrad from '../../../utils/getTrad';
|
||||
import { getBreadcrumbDataCM } from '../../../utils';
|
||||
import getAllowedFiles from '../../../utils/getAllowedFiles';
|
||||
import { AssetList } from '../../AssetList';
|
||||
import { AssetGridList } from '../../AssetGridList';
|
||||
import { FolderList } from '../../FolderList';
|
||||
import { EmptyAssets } from '../../EmptyAssets';
|
||||
import { Breadcrumbs } from '../../Breadcrumbs';
|
||||
@ -304,7 +304,7 @@ export const BrowseStep = ({
|
||||
|
||||
{assetCount > 0 && (
|
||||
<Box paddingTop={6}>
|
||||
<AssetList
|
||||
<AssetGridList
|
||||
allowedTypes={allowedTypes}
|
||||
size="S"
|
||||
assets={assets}
|
||||
|
||||
@ -3,7 +3,7 @@ import { useIntl } from 'react-intl';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Stack } from '@strapi/design-system/Stack';
|
||||
import { Typography } from '@strapi/design-system/Typography';
|
||||
import { AssetList } from '../../AssetList';
|
||||
import { AssetGridList } from '../../AssetGridList';
|
||||
import getTrad from '../../../utils/getTrad';
|
||||
|
||||
export const SelectedStep = ({ selectedAssets, onSelectAsset, onReorderAsset }) => {
|
||||
@ -30,7 +30,7 @@ export const SelectedStep = ({ selectedAssets, onSelectAsset, onReorderAsset })
|
||||
</Typography>
|
||||
</Stack>
|
||||
|
||||
<AssetList
|
||||
<AssetGridList
|
||||
size="S"
|
||||
assets={selectedAssets}
|
||||
onSelectAsset={onSelectAsset}
|
||||
|
||||
@ -8,7 +8,7 @@ import { Typography } from '@strapi/design-system/Typography';
|
||||
import { AssetCard } from '../AssetCard/AssetCard';
|
||||
import { Draggable } from './Draggable';
|
||||
|
||||
export const AssetList = ({
|
||||
export const AssetGridList = ({
|
||||
allowedTypes,
|
||||
assets,
|
||||
onEditAsset,
|
||||
@ -41,7 +41,7 @@ export const AssetList = ({
|
||||
asset={asset}
|
||||
isSelected={isSelected}
|
||||
onEdit={onEditAsset ? () => onEditAsset(asset) : undefined}
|
||||
onSelect={() => onSelectAsset({ ...asset, type: 'asset' })}
|
||||
onSelect={() => onSelectAsset(asset)}
|
||||
size={size}
|
||||
/>
|
||||
</Draggable>
|
||||
@ -57,7 +57,7 @@ export const AssetList = ({
|
||||
asset={asset}
|
||||
isSelected={isSelected}
|
||||
onEdit={onEditAsset ? () => onEditAsset(asset) : undefined}
|
||||
onSelect={() => onSelectAsset({ ...asset, type: 'asset' })}
|
||||
onSelect={() => onSelectAsset(asset)}
|
||||
size={size}
|
||||
/>
|
||||
</GridItem>
|
||||
@ -68,7 +68,7 @@ export const AssetList = ({
|
||||
);
|
||||
};
|
||||
|
||||
AssetList.defaultProps = {
|
||||
AssetGridList.defaultProps = {
|
||||
allowedTypes: ['images', 'files', 'videos', 'audios'],
|
||||
onEditAsset: undefined,
|
||||
size: 'M',
|
||||
@ -76,7 +76,7 @@ AssetList.defaultProps = {
|
||||
title: null,
|
||||
};
|
||||
|
||||
AssetList.propTypes = {
|
||||
AssetGridList.propTypes = {
|
||||
allowedTypes: PropTypes.arrayOf(PropTypes.string),
|
||||
assets: PropTypes.arrayOf(PropTypes.shape({})).isRequired,
|
||||
onEditAsset: PropTypes.func,
|
||||
@ -2,7 +2,7 @@ import React from 'react';
|
||||
import { ThemeProvider, lightTheme } from '@strapi/design-system';
|
||||
import { render as renderTL } from '@testing-library/react';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import { AssetList } from '..';
|
||||
import { AssetGridList } from '..';
|
||||
import en from '../../../translations/en.json';
|
||||
|
||||
jest.mock('../../../utils', () => ({
|
||||
@ -123,7 +123,7 @@ const setup = (props = { assets: data, selectedAssets: [], onSelectAsset: jest.f
|
||||
renderTL(
|
||||
<MemoryRouter>
|
||||
<ThemeProvider theme={lightTheme}>
|
||||
<AssetList {...props} />
|
||||
<AssetGridList {...props} />
|
||||
</ThemeProvider>
|
||||
</MemoryRouter>
|
||||
);
|
||||
@ -0,0 +1,72 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useIntl } from 'react-intl';
|
||||
import { getFileExtension } from '@strapi/helper-plugin';
|
||||
import { Typography } from '@strapi/design-system/Typography';
|
||||
|
||||
import { PreviewCell } from './PreviewCell';
|
||||
import { formatBytes } from '../../utils';
|
||||
|
||||
export const CellContent = ({
|
||||
alternativeText,
|
||||
content,
|
||||
cellType,
|
||||
elementType,
|
||||
mime,
|
||||
fileExtension,
|
||||
thumbnailURL,
|
||||
url,
|
||||
}) => {
|
||||
const { formatDate } = useIntl();
|
||||
switch (cellType) {
|
||||
case 'image':
|
||||
return (
|
||||
<PreviewCell
|
||||
alternativeText={alternativeText}
|
||||
fileExtension={fileExtension}
|
||||
mime={mime}
|
||||
type={elementType}
|
||||
thumbnailURL={thumbnailURL}
|
||||
url={url}
|
||||
/>
|
||||
);
|
||||
case 'date':
|
||||
return <Typography>{formatDate(content)}</Typography>;
|
||||
|
||||
case 'size':
|
||||
if (elementType === 'folder') return <Typography>-</Typography>;
|
||||
|
||||
return <Typography>{formatBytes(content)}</Typography>;
|
||||
|
||||
case 'ext':
|
||||
if (elementType === 'folder') return <Typography>-</Typography>;
|
||||
|
||||
return <Typography>{getFileExtension(content).toUpperCase()}</Typography>;
|
||||
|
||||
case 'text':
|
||||
return <Typography>{content}</Typography>;
|
||||
|
||||
default:
|
||||
return <Typography>-</Typography>;
|
||||
}
|
||||
};
|
||||
|
||||
CellContent.defaultProps = {
|
||||
alternativeText: null,
|
||||
content: '',
|
||||
fileExtension: '',
|
||||
mime: '',
|
||||
thumbnailURL: null,
|
||||
url: null,
|
||||
};
|
||||
|
||||
CellContent.propTypes = {
|
||||
alternativeText: PropTypes.string,
|
||||
content: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
|
||||
fileExtension: PropTypes.string,
|
||||
mime: PropTypes.string,
|
||||
thumbnailURL: PropTypes.string,
|
||||
cellType: PropTypes.string.isRequired,
|
||||
elementType: PropTypes.string.isRequired,
|
||||
url: PropTypes.string,
|
||||
};
|
||||
@ -0,0 +1,69 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import styled from 'styled-components';
|
||||
import { prefixFileUrlWithBackendUrl, pxToRem } from '@strapi/helper-plugin';
|
||||
import { Avatar } from '@strapi/design-system/Avatar';
|
||||
import { Flex } from '@strapi/design-system/Flex';
|
||||
import { Typography } from '@strapi/design-system/Typography';
|
||||
import { Icon } from '@strapi/design-system/Icon';
|
||||
import Folder from '@strapi/icons/Folder';
|
||||
|
||||
const GenericAssetWrapper = styled(Flex)`
|
||||
span {
|
||||
/* The smallest fontSize in the DS is not small enough in this case */
|
||||
font-size: ${pxToRem(10)};
|
||||
}
|
||||
`;
|
||||
|
||||
export const PreviewCell = ({ alternativeText, fileExtension, mime, thumbnailURL, type, url }) => {
|
||||
if (type === 'folder') {
|
||||
return (
|
||||
<Flex
|
||||
background="secondary100"
|
||||
height={pxToRem(26)}
|
||||
justifyContent="center"
|
||||
width={pxToRem(26)}
|
||||
borderRadius="50%"
|
||||
>
|
||||
<Icon color="secondary500" as={Folder} />
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
|
||||
if (mime.includes('image')) {
|
||||
const mediaURL = prefixFileUrlWithBackendUrl(thumbnailURL) ?? prefixFileUrlWithBackendUrl(url);
|
||||
|
||||
return <Avatar src={mediaURL} alt={alternativeText} preview />;
|
||||
}
|
||||
|
||||
return (
|
||||
<GenericAssetWrapper
|
||||
background="secondary100"
|
||||
height={pxToRem(26)}
|
||||
justifyContent="center"
|
||||
width={pxToRem(26)}
|
||||
borderRadius="50%"
|
||||
>
|
||||
<Typography variant="sigma" textColor="secondary600">
|
||||
{fileExtension}
|
||||
</Typography>
|
||||
</GenericAssetWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
PreviewCell.defaultProps = {
|
||||
alternativeText: null,
|
||||
fileExtension: '',
|
||||
mime: '',
|
||||
thumbnailURL: null,
|
||||
url: null,
|
||||
};
|
||||
|
||||
PreviewCell.propTypes = {
|
||||
alternativeText: PropTypes.string,
|
||||
fileExtension: PropTypes.string,
|
||||
mime: PropTypes.string,
|
||||
thumbnailURL: PropTypes.string,
|
||||
type: PropTypes.string.isRequired,
|
||||
url: PropTypes.string,
|
||||
};
|
||||
@ -0,0 +1,93 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useIntl } from 'react-intl';
|
||||
import { getFileExtension } from '@strapi/helper-plugin';
|
||||
import { BaseCheckbox } from '@strapi/design-system/BaseCheckbox';
|
||||
import { IconButton } from '@strapi/design-system/IconButton';
|
||||
import { Tbody, Td, Tr } from '@strapi/design-system/Table';
|
||||
import Pencil from '@strapi/icons/Pencil';
|
||||
|
||||
import { CellContent } from './CellContent';
|
||||
import { AssetDefinition, FolderDefinition, tableHeaders as cells } from '../../constants';
|
||||
import { getTrad } from '../../utils';
|
||||
|
||||
export const TableRows = ({ onEditAsset, onEditFolder, onSelectOne, rows, selected }) => {
|
||||
const { formatMessage } = useIntl();
|
||||
|
||||
return (
|
||||
<Tbody>
|
||||
{rows.map((element) => {
|
||||
const { alternativeText, id, name, ext, url, mime, formats, type: elementType } = element;
|
||||
|
||||
const isSelected = !!selected.find((currentRow) => currentRow.id === id);
|
||||
|
||||
return (
|
||||
<Tr key={id}>
|
||||
<Td>
|
||||
<BaseCheckbox
|
||||
aria-label={formatMessage(
|
||||
{
|
||||
id: elementType === 'asset' ? 'list-assets-select' : 'list.folder.select',
|
||||
defaultMessage:
|
||||
elementType === 'asset' ? 'Select {name} asset' : 'Select {name} folder',
|
||||
},
|
||||
{ name }
|
||||
)}
|
||||
onValueChange={() => onSelectOne({ ...element, elementType })}
|
||||
checked={isSelected}
|
||||
/>
|
||||
</Td>
|
||||
{cells.map(({ name, type: cellType }) => {
|
||||
return (
|
||||
<Td key={name}>
|
||||
<CellContent
|
||||
alternativeText={alternativeText}
|
||||
content={element[name]}
|
||||
fileExtension={getFileExtension(ext)}
|
||||
mime={mime}
|
||||
cellType={cellType}
|
||||
elementType={elementType}
|
||||
thumbnailURL={formats?.thumbnail?.url}
|
||||
url={url}
|
||||
/>
|
||||
</Td>
|
||||
);
|
||||
})}
|
||||
{((elementType === 'asset' && onEditAsset) ||
|
||||
(elementType === 'folder' && onEditFolder)) && (
|
||||
<Td>
|
||||
<IconButton
|
||||
label={formatMessage({
|
||||
id: getTrad('control-card.edit'),
|
||||
defaultMessage: 'Edit',
|
||||
})}
|
||||
onClick={() =>
|
||||
elementType === 'asset' ? onEditAsset(element) : onEditFolder(element)
|
||||
}
|
||||
noBorder
|
||||
>
|
||||
<Pencil />
|
||||
</IconButton>
|
||||
</Td>
|
||||
)}
|
||||
</Tr>
|
||||
);
|
||||
})}
|
||||
</Tbody>
|
||||
);
|
||||
};
|
||||
|
||||
TableRows.defaultProps = {
|
||||
onEditAsset: null,
|
||||
onEditFolder: null,
|
||||
rows: [],
|
||||
selected: [],
|
||||
};
|
||||
|
||||
TableRows.propTypes = {
|
||||
rows: PropTypes.arrayOf(AssetDefinition, FolderDefinition),
|
||||
onEditAsset: PropTypes.func,
|
||||
onEditFolder: PropTypes.func,
|
||||
onSelectOne: PropTypes.func.isRequired,
|
||||
selected: PropTypes.arrayOf(AssetDefinition, FolderDefinition),
|
||||
};
|
||||
93
packages/core/upload/admin/src/components/TableList/index.js
Normal file
93
packages/core/upload/admin/src/components/TableList/index.js
Normal file
@ -0,0 +1,93 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useIntl } from 'react-intl';
|
||||
import { BaseCheckbox } from '@strapi/design-system/BaseCheckbox';
|
||||
import { Table, Th, Thead, Tr } from '@strapi/design-system/Table';
|
||||
import { Typography } from '@strapi/design-system/Typography';
|
||||
import { VisuallyHidden } from '@strapi/design-system/VisuallyHidden';
|
||||
|
||||
import { getTrad } from '../../utils';
|
||||
import { AssetDefinition, tableHeaders, FolderDefinition } from '../../constants';
|
||||
import { TableRows } from './TableRows';
|
||||
|
||||
export const TableList = ({
|
||||
assetCount,
|
||||
folderCount,
|
||||
indeterminate,
|
||||
onEditAsset,
|
||||
onEditFolder,
|
||||
onSelectAll,
|
||||
onSelectOne,
|
||||
rows,
|
||||
selected,
|
||||
}) => {
|
||||
const { formatMessage } = useIntl();
|
||||
|
||||
return (
|
||||
<Table colCount={tableHeaders.length + 2} rowCount={assetCount + folderCount + 1}>
|
||||
<Thead>
|
||||
<Tr>
|
||||
<Th>
|
||||
<BaseCheckbox
|
||||
aria-label={formatMessage({
|
||||
id: getTrad('bulk.select.label'),
|
||||
defaultMessage: 'Select all folders & assets',
|
||||
})}
|
||||
indeterminate={indeterminate}
|
||||
onChange={(e) => onSelectAll(e, rows)}
|
||||
value={
|
||||
(assetCount > 0 || folderCount > 0) && selected.length === assetCount + folderCount
|
||||
}
|
||||
/>
|
||||
</Th>
|
||||
{tableHeaders.map(({ metadatas, key }) => {
|
||||
return (
|
||||
<Th key={key}>
|
||||
<Typography textColor="neutral600" variant="sigma">
|
||||
{formatMessage(metadatas.label)}
|
||||
</Typography>
|
||||
</Th>
|
||||
);
|
||||
})}
|
||||
<Th>
|
||||
<VisuallyHidden>
|
||||
{formatMessage({
|
||||
id: getTrad('list-table-header-actions'),
|
||||
defaultMessage: 'actions',
|
||||
})}
|
||||
</VisuallyHidden>
|
||||
</Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<TableRows
|
||||
onEditAsset={onEditAsset}
|
||||
onEditFolder={onEditFolder}
|
||||
rows={rows}
|
||||
onSelectOne={onSelectOne}
|
||||
selected={selected}
|
||||
/>
|
||||
</Table>
|
||||
);
|
||||
};
|
||||
|
||||
TableList.defaultProps = {
|
||||
assetCount: 0,
|
||||
folderCount: 0,
|
||||
indeterminate: false,
|
||||
onEditAsset: null,
|
||||
onEditFolder: null,
|
||||
rows: [],
|
||||
selected: [],
|
||||
};
|
||||
|
||||
TableList.propTypes = {
|
||||
assetCount: PropTypes.number,
|
||||
folderCount: PropTypes.number,
|
||||
indeterminate: PropTypes.bool,
|
||||
onEditAsset: PropTypes.func,
|
||||
onEditFolder: PropTypes.func,
|
||||
onSelectAll: PropTypes.func.isRequired,
|
||||
onSelectOne: PropTypes.func.isRequired,
|
||||
rows: PropTypes.arrayOf(AssetDefinition, FolderDefinition),
|
||||
selected: PropTypes.arrayOf(AssetDefinition, FolderDefinition),
|
||||
};
|
||||
@ -0,0 +1,109 @@
|
||||
import React from 'react';
|
||||
import { IntlProvider } from 'react-intl';
|
||||
import { render } from '@testing-library/react';
|
||||
import { ThemeProvider, lightTheme } from '@strapi/design-system';
|
||||
|
||||
import { CellContent } from '../CellContent';
|
||||
|
||||
const PROPS_FIXTURE = {
|
||||
alternativeText: 'alternative alt',
|
||||
cellType: 'image',
|
||||
elementType: 'asset',
|
||||
content: 'michka-picture-url-default.jpeg',
|
||||
fileExtension: '.jpeg',
|
||||
mime: 'image/jpeg',
|
||||
thumbnailURL: 'michka-picture-url-thumbnail.jpeg',
|
||||
url: 'michka-picture-url-default.jpeg',
|
||||
};
|
||||
|
||||
const ComponentFixture = (props) => {
|
||||
const customProps = {
|
||||
...PROPS_FIXTURE,
|
||||
...props,
|
||||
};
|
||||
|
||||
return (
|
||||
<IntlProvider locale="en" messages={{}}>
|
||||
<ThemeProvider theme={lightTheme}>
|
||||
<CellContent {...PROPS_FIXTURE} {...customProps} />
|
||||
</ThemeProvider>
|
||||
</IntlProvider>
|
||||
);
|
||||
};
|
||||
|
||||
const setup = (props) => render(<ComponentFixture {...props} />);
|
||||
|
||||
describe('TableList | CellContent', () => {
|
||||
it('should render image cell type when element type is asset and mime includes image', () => {
|
||||
const { container, getByRole } = setup();
|
||||
|
||||
expect(getByRole('img', { name: 'alternative alt' })).toBeInTheDocument();
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should render image cell type when element type is asset and mime does not include image', () => {
|
||||
const { container, getByText } = setup({ mime: 'application/pdf', fileExtension: 'pdf' });
|
||||
|
||||
expect(getByText('pdf')).toBeInTheDocument();
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should render image cell type when element type is folder', () => {
|
||||
const { container } = setup({ elementType: 'folder' });
|
||||
|
||||
expect(container.querySelector('path')).toBeInTheDocument();
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should render text cell type', () => {
|
||||
const { container, getByText } = setup({ cellType: 'text', content: 'some text' });
|
||||
|
||||
expect(getByText('some text')).toBeInTheDocument();
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should render extension cell type when element type is asset', () => {
|
||||
const { container, getByText } = setup({ cellType: 'ext', content: '.pdf' });
|
||||
|
||||
expect(getByText('PDF')).toBeInTheDocument();
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should render extension cell type with "-" when element type is folder', () => {
|
||||
const { container, getByText } = setup({ cellType: 'ext', elementType: 'folder' });
|
||||
|
||||
expect(getByText('-')).toBeInTheDocument();
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should render size cell type when element type is asset', () => {
|
||||
const { container, getByText } = setup({ cellType: 'size', content: '20.5435' });
|
||||
|
||||
expect(getByText('21KB')).toBeInTheDocument();
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should render size cell type with "-" when element type is folder', () => {
|
||||
const { container, getByText } = setup({ cellType: 'size', elementType: 'folder' });
|
||||
|
||||
expect(getByText('-')).toBeInTheDocument();
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should render date cell type', () => {
|
||||
const { container, getByText } = setup({
|
||||
cellType: 'date',
|
||||
content: '2022-11-18T12:08:02.202Z',
|
||||
});
|
||||
|
||||
expect(getByText('11/18/2022')).toBeInTheDocument();
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should render "-" by default when no recognized cell type is passed', () => {
|
||||
const { container, getByText } = setup({ cellType: 'not recognized type' });
|
||||
|
||||
expect(getByText('-')).toBeInTheDocument();
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,75 @@
|
||||
import React from 'react';
|
||||
import { render } from '@testing-library/react';
|
||||
import { ThemeProvider, lightTheme } from '@strapi/design-system';
|
||||
|
||||
import { PreviewCell } from '../PreviewCell';
|
||||
|
||||
const PROPS_FIXTURE = {
|
||||
alternativeText: 'alternative alt',
|
||||
fileExtension: 'jpeg',
|
||||
mime: 'image/jpeg',
|
||||
name: 'michka',
|
||||
thumbnailURL: 'michka-picture-url-thumbnail.jpeg',
|
||||
url: 'michka-picture-url-default.jpeg',
|
||||
type: 'asset',
|
||||
};
|
||||
|
||||
const ComponentFixture = (props) => {
|
||||
const customProps = {
|
||||
...PROPS_FIXTURE,
|
||||
...props,
|
||||
};
|
||||
|
||||
return (
|
||||
<ThemeProvider theme={lightTheme}>
|
||||
<PreviewCell {...PROPS_FIXTURE} {...customProps} />
|
||||
</ThemeProvider>
|
||||
);
|
||||
};
|
||||
|
||||
const setup = (props) => render(<ComponentFixture {...props} />);
|
||||
|
||||
describe('TableList | PreviewCell', () => {
|
||||
describe('rendering images', () => {
|
||||
it('should render an image with thumbnail if available', () => {
|
||||
const { getByRole } = setup();
|
||||
|
||||
expect(getByRole('img', { name: 'alternative alt' })).toHaveAttribute(
|
||||
'src',
|
||||
'michka-picture-url-thumbnail.jpeg'
|
||||
);
|
||||
});
|
||||
|
||||
it('should render an image with default url if thumbnail is not available', () => {
|
||||
const { getByRole } = setup({ thumbnailURL: undefined });
|
||||
|
||||
expect(getByRole('img', { name: 'alternative alt' })).toHaveAttribute(
|
||||
'src',
|
||||
'michka-picture-url-default.jpeg'
|
||||
);
|
||||
});
|
||||
|
||||
it('should render alternative text if available', () => {
|
||||
const { getByRole, queryByRole } = setup();
|
||||
|
||||
expect(getByRole('img', { name: 'alternative alt' })).toBeInTheDocument();
|
||||
expect(queryByRole('img', { name: 'michka' })).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('rendering files', () => {
|
||||
it('should render a file with fileExtension', () => {
|
||||
const { getByText } = setup({ mime: 'application/pdf', fileExtension: 'pdf' });
|
||||
|
||||
expect(getByText('pdf')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('rendering folder', () => {
|
||||
it('should render a folder', () => {
|
||||
const { container } = setup({ type: 'folder' });
|
||||
|
||||
expect(container.querySelector('path')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,96 @@
|
||||
import React from 'react';
|
||||
import { IntlProvider } from 'react-intl';
|
||||
import { render } from '@testing-library/react';
|
||||
import { ThemeProvider, lightTheme } from '@strapi/design-system';
|
||||
|
||||
import { TableList } from '..';
|
||||
|
||||
jest.mock('@strapi/helper-plugin', () => ({
|
||||
...jest.requireActual('@strapi/helper-plugin'),
|
||||
useTracking: jest.fn(() => ({ trackUsage: jest.fn() })),
|
||||
}));
|
||||
|
||||
const PROPS_FIXTURE = {
|
||||
rows: [
|
||||
{
|
||||
alternativeText: 'alternative text',
|
||||
createdAt: '2021-10-18T08:04:56.326Z',
|
||||
ext: '.jpeg',
|
||||
formats: {
|
||||
thumbnail: {
|
||||
url: '/uploads/thumbnail_3874873_b5818bb250.jpg',
|
||||
},
|
||||
},
|
||||
id: 1,
|
||||
mime: 'image/jpeg',
|
||||
name: 'michka',
|
||||
size: 11.79,
|
||||
updatedAt: '2021-10-18T08:04:56.326Z',
|
||||
url: '/uploads/michka.jpg',
|
||||
type: 'asset',
|
||||
},
|
||||
],
|
||||
onEditAsset: jest.fn(),
|
||||
onSelectOne: jest.fn(),
|
||||
onSelectAll: jest.fn(),
|
||||
selected: [],
|
||||
};
|
||||
|
||||
const ComponentFixture = (props) => {
|
||||
const customProps = {
|
||||
...PROPS_FIXTURE,
|
||||
...props,
|
||||
};
|
||||
|
||||
return (
|
||||
<IntlProvider locale="en" messages={{}}>
|
||||
<ThemeProvider theme={lightTheme}>
|
||||
<TableList {...customProps} />
|
||||
</ThemeProvider>
|
||||
</IntlProvider>
|
||||
);
|
||||
};
|
||||
|
||||
const setup = (props) => render(<ComponentFixture {...props} />);
|
||||
|
||||
describe('TableList', () => {
|
||||
it('should render table headers labels', () => {
|
||||
const { getByText } = setup();
|
||||
|
||||
expect(getByText('preview')).toBeInTheDocument();
|
||||
expect(getByText('name')).toBeInTheDocument();
|
||||
expect(getByText('extension')).toBeInTheDocument();
|
||||
expect(getByText('size')).toBeInTheDocument();
|
||||
expect(getByText('created')).toBeInTheDocument();
|
||||
expect(getByText('last update')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render a visually hidden edit table headers label', () => {
|
||||
const { getByRole } = setup();
|
||||
|
||||
expect(getByRole('columnheader', { name: 'actions' })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render assets', () => {
|
||||
const { getByText } = setup();
|
||||
|
||||
expect(getByText('michka')).toBeInTheDocument();
|
||||
expect(getByText('JPEG')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render folders', () => {
|
||||
const { getByText } = setup({
|
||||
rows: [
|
||||
{
|
||||
createdAt: '2022-11-17T10:40:06.022Z',
|
||||
id: 2,
|
||||
name: 'folder 1',
|
||||
type: 'folder',
|
||||
updatedAt: '2022-11-17T10:40:06.022Z',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
expect(getByText('folder 1')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,145 @@
|
||||
import React from 'react';
|
||||
import { IntlProvider } from 'react-intl';
|
||||
import { render, fireEvent } from '@testing-library/react';
|
||||
import { ThemeProvider, lightTheme } from '@strapi/design-system';
|
||||
|
||||
import { TableRows } from '../TableRows';
|
||||
|
||||
const PROPS_FIXTURE = {
|
||||
rows: [
|
||||
{
|
||||
alternativeText: 'alternative text',
|
||||
createdAt: '2021-10-01T08:04:56.326Z',
|
||||
ext: '.jpeg',
|
||||
formats: {
|
||||
thumbnail: {
|
||||
url: '/uploads/thumbnail_3874873_b5818bb250.jpg',
|
||||
},
|
||||
},
|
||||
id: 1,
|
||||
mime: 'image/jpeg',
|
||||
name: 'michka',
|
||||
size: 11.79,
|
||||
updatedAt: '2021-10-18T08:04:56.326Z',
|
||||
url: '/uploads/michka.jpg',
|
||||
type: 'asset',
|
||||
},
|
||||
],
|
||||
onEditAsset: jest.fn(),
|
||||
onEditFolder: jest.fn(),
|
||||
onSelectOne: jest.fn(),
|
||||
selected: [],
|
||||
};
|
||||
|
||||
const FOLDER_FIXTURE = {
|
||||
createdAt: '2022-11-17T10:40:06.022Z',
|
||||
id: 2,
|
||||
name: 'folder 1',
|
||||
type: 'folder',
|
||||
updatedAt: '2022-11-17T10:40:06.022Z',
|
||||
};
|
||||
|
||||
const ComponentFixture = (props) => {
|
||||
const customProps = {
|
||||
...PROPS_FIXTURE,
|
||||
...props,
|
||||
};
|
||||
|
||||
return (
|
||||
<IntlProvider locale="en" messages={{}}>
|
||||
<ThemeProvider theme={lightTheme}>
|
||||
<table>
|
||||
<TableRows {...customProps} />
|
||||
</table>
|
||||
</ThemeProvider>
|
||||
</IntlProvider>
|
||||
);
|
||||
};
|
||||
|
||||
const setup = (props) => render(<ComponentFixture {...props} />);
|
||||
|
||||
describe('TableList | TableRows', () => {
|
||||
describe('rendering assets', () => {
|
||||
it('should properly render every asset attribute', () => {
|
||||
const { getByRole, getByText } = setup();
|
||||
|
||||
expect(getByRole('img', { name: 'alternative text' })).toBeInTheDocument();
|
||||
expect(getByText('michka')).toBeInTheDocument();
|
||||
expect(getByText('JPEG')).toBeInTheDocument();
|
||||
expect(getByText('12KB')).toBeInTheDocument();
|
||||
expect(getByText('10/1/2021')).toBeInTheDocument();
|
||||
expect(getByText('10/18/2021')).toBeInTheDocument();
|
||||
expect(getByText('10/18/2021')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should call onSelectAsset callback', () => {
|
||||
const onSelectOneSpy = jest.fn();
|
||||
const { getByRole } = setup({ onSelectOne: onSelectOneSpy });
|
||||
|
||||
fireEvent.click(getByRole('checkbox', { name: 'Select michka asset' }));
|
||||
|
||||
expect(onSelectOneSpy).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should reflect non selected assets state', () => {
|
||||
const { getByRole } = setup();
|
||||
|
||||
expect(getByRole('checkbox', { name: 'Select michka asset' })).not.toBeChecked();
|
||||
});
|
||||
|
||||
it('should reflect selected assets state', () => {
|
||||
const { getByRole } = setup({ selected: [{ id: 1 }] });
|
||||
|
||||
expect(getByRole('checkbox', { name: 'Select michka asset' })).toBeChecked();
|
||||
});
|
||||
|
||||
it('should call onEditAsset callback', () => {
|
||||
const onEditAssetSpy = jest.fn();
|
||||
const { getByRole } = setup({ onEditAsset: onEditAssetSpy });
|
||||
|
||||
fireEvent.click(getByRole('button', { name: 'Edit' }));
|
||||
|
||||
expect(onEditAssetSpy).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('rendering folders', () => {
|
||||
it('should render folder', () => {
|
||||
const { getByText } = setup({
|
||||
rows: [FOLDER_FIXTURE],
|
||||
});
|
||||
|
||||
expect(getByText('folder 1')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should call onEditFolder callback', () => {
|
||||
const onEditFolderSpy = jest.fn();
|
||||
const { getByRole } = setup({
|
||||
rows: [FOLDER_FIXTURE],
|
||||
onEditFolder: onEditFolderSpy,
|
||||
});
|
||||
|
||||
fireEvent.click(getByRole('button', { name: 'Edit' }));
|
||||
|
||||
expect(onEditFolderSpy).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should reflect non selected folder state', () => {
|
||||
const { getByRole } = setup({ rows: [FOLDER_FIXTURE] });
|
||||
|
||||
expect(getByRole('checkbox', { name: 'Select folder 1 folder' })).not.toBeChecked();
|
||||
});
|
||||
|
||||
it('should reflect selected folder state', () => {
|
||||
const { getByRole } = setup({ rows: [FOLDER_FIXTURE], selected: [{ id: 2 }] });
|
||||
|
||||
expect(getByRole('checkbox', { name: 'Select folder 1 folder' })).toBeChecked();
|
||||
});
|
||||
|
||||
it('should not display size and ext', () => {
|
||||
const { getAllByText } = setup({ rows: [FOLDER_FIXTURE] });
|
||||
|
||||
expect(getAllByText('-').length).toEqual(2);
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,594 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`TableList | CellContent should render "-" by default when no recognized cell type is passed 1`] = `
|
||||
.c1 {
|
||||
border: 0;
|
||||
-webkit-clip: rect(0 0 0 0);
|
||||
clip: rect(0 0 0 0);
|
||||
height: 1px;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
padding: 0;
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
}
|
||||
|
||||
.c0 {
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.43;
|
||||
color: #32324d;
|
||||
}
|
||||
|
||||
<div>
|
||||
<span
|
||||
class="c0"
|
||||
>
|
||||
-
|
||||
</span>
|
||||
<div
|
||||
class="c1"
|
||||
>
|
||||
<p
|
||||
aria-live="polite"
|
||||
aria-relevant="all"
|
||||
id="live-region-log"
|
||||
role="log"
|
||||
/>
|
||||
<p
|
||||
aria-live="polite"
|
||||
aria-relevant="all"
|
||||
id="live-region-status"
|
||||
role="status"
|
||||
/>
|
||||
<p
|
||||
aria-live="assertive"
|
||||
aria-relevant="all"
|
||||
id="live-region-alert"
|
||||
role="alert"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`TableList | CellContent should render date cell type 1`] = `
|
||||
.c1 {
|
||||
border: 0;
|
||||
-webkit-clip: rect(0 0 0 0);
|
||||
clip: rect(0 0 0 0);
|
||||
height: 1px;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
padding: 0;
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
}
|
||||
|
||||
.c0 {
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.43;
|
||||
color: #32324d;
|
||||
}
|
||||
|
||||
<div>
|
||||
<span
|
||||
class="c0"
|
||||
>
|
||||
11/18/2022
|
||||
</span>
|
||||
<div
|
||||
class="c1"
|
||||
>
|
||||
<p
|
||||
aria-live="polite"
|
||||
aria-relevant="all"
|
||||
id="live-region-log"
|
||||
role="log"
|
||||
/>
|
||||
<p
|
||||
aria-live="polite"
|
||||
aria-relevant="all"
|
||||
id="live-region-status"
|
||||
role="status"
|
||||
/>
|
||||
<p
|
||||
aria-live="assertive"
|
||||
aria-relevant="all"
|
||||
id="live-region-alert"
|
||||
role="alert"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`TableList | CellContent should render extension cell type when element type is asset 1`] = `
|
||||
.c1 {
|
||||
border: 0;
|
||||
-webkit-clip: rect(0 0 0 0);
|
||||
clip: rect(0 0 0 0);
|
||||
height: 1px;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
padding: 0;
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
}
|
||||
|
||||
.c0 {
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.43;
|
||||
color: #32324d;
|
||||
}
|
||||
|
||||
<div>
|
||||
<span
|
||||
class="c0"
|
||||
>
|
||||
PDF
|
||||
</span>
|
||||
<div
|
||||
class="c1"
|
||||
>
|
||||
<p
|
||||
aria-live="polite"
|
||||
aria-relevant="all"
|
||||
id="live-region-log"
|
||||
role="log"
|
||||
/>
|
||||
<p
|
||||
aria-live="polite"
|
||||
aria-relevant="all"
|
||||
id="live-region-status"
|
||||
role="status"
|
||||
/>
|
||||
<p
|
||||
aria-live="assertive"
|
||||
aria-relevant="all"
|
||||
id="live-region-alert"
|
||||
role="alert"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`TableList | CellContent should render extension cell type with "-" when element type is folder 1`] = `
|
||||
.c1 {
|
||||
border: 0;
|
||||
-webkit-clip: rect(0 0 0 0);
|
||||
clip: rect(0 0 0 0);
|
||||
height: 1px;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
padding: 0;
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
}
|
||||
|
||||
.c0 {
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.43;
|
||||
color: #32324d;
|
||||
}
|
||||
|
||||
<div>
|
||||
<span
|
||||
class="c0"
|
||||
>
|
||||
-
|
||||
</span>
|
||||
<div
|
||||
class="c1"
|
||||
>
|
||||
<p
|
||||
aria-live="polite"
|
||||
aria-relevant="all"
|
||||
id="live-region-log"
|
||||
role="log"
|
||||
/>
|
||||
<p
|
||||
aria-live="polite"
|
||||
aria-relevant="all"
|
||||
id="live-region-status"
|
||||
role="status"
|
||||
/>
|
||||
<p
|
||||
aria-live="assertive"
|
||||
aria-relevant="all"
|
||||
id="live-region-alert"
|
||||
role="alert"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`TableList | CellContent should render image cell type when element type is asset and mime does not include image 1`] = `
|
||||
.c4 {
|
||||
border: 0;
|
||||
-webkit-clip: rect(0 0 0 0);
|
||||
clip: rect(0 0 0 0);
|
||||
height: 1px;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
padding: 0;
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
}
|
||||
|
||||
.c0 {
|
||||
background: #eaf5ff;
|
||||
border-radius: 50%;
|
||||
width: 1.625rem;
|
||||
height: 1.625rem;
|
||||
}
|
||||
|
||||
.c1 {
|
||||
-webkit-align-items: center;
|
||||
-webkit-box-align: center;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-flex-direction: row;
|
||||
-ms-flex-direction: row;
|
||||
flex-direction: row;
|
||||
-webkit-box-pack: center;
|
||||
-webkit-justify-content: center;
|
||||
-ms-flex-pack: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.c3 {
|
||||
font-weight: 600;
|
||||
font-size: 0.6875rem;
|
||||
line-height: 1.45;
|
||||
text-transform: uppercase;
|
||||
color: #0c75af;
|
||||
}
|
||||
|
||||
.c2 span {
|
||||
font-size: 0.625rem;
|
||||
}
|
||||
|
||||
<div>
|
||||
<div
|
||||
class="c0 c1 c2"
|
||||
height="1.625rem"
|
||||
width="1.625rem"
|
||||
>
|
||||
<span
|
||||
class="c3"
|
||||
>
|
||||
pdf
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="c4"
|
||||
>
|
||||
<p
|
||||
aria-live="polite"
|
||||
aria-relevant="all"
|
||||
id="live-region-log"
|
||||
role="log"
|
||||
/>
|
||||
<p
|
||||
aria-live="polite"
|
||||
aria-relevant="all"
|
||||
id="live-region-status"
|
||||
role="status"
|
||||
/>
|
||||
<p
|
||||
aria-live="assertive"
|
||||
aria-relevant="all"
|
||||
id="live-region-alert"
|
||||
role="alert"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`TableList | CellContent should render image cell type when element type is asset and mime includes image 1`] = `
|
||||
.c2 {
|
||||
border: 0;
|
||||
-webkit-clip: rect(0 0 0 0);
|
||||
clip: rect(0 0 0 0);
|
||||
height: 1px;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
padding: 0;
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
}
|
||||
|
||||
.c1 {
|
||||
border-radius: 50%;
|
||||
object-fit: cover;
|
||||
display: block;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.c0 {
|
||||
position: relative;
|
||||
width: 1.625rem;
|
||||
height: 1.625rem;
|
||||
}
|
||||
|
||||
<div>
|
||||
<span>
|
||||
<div
|
||||
class="c0"
|
||||
>
|
||||
<img
|
||||
alt="alternative alt"
|
||||
class="c1"
|
||||
height="26px"
|
||||
src="michka-picture-url-thumbnail.jpeg"
|
||||
width="26px"
|
||||
/>
|
||||
</div>
|
||||
</span>
|
||||
<div
|
||||
class="c2"
|
||||
>
|
||||
<p
|
||||
aria-live="polite"
|
||||
aria-relevant="all"
|
||||
id="live-region-log"
|
||||
role="log"
|
||||
/>
|
||||
<p
|
||||
aria-live="polite"
|
||||
aria-relevant="all"
|
||||
id="live-region-status"
|
||||
role="status"
|
||||
/>
|
||||
<p
|
||||
aria-live="assertive"
|
||||
aria-relevant="all"
|
||||
id="live-region-alert"
|
||||
role="alert"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`TableList | CellContent should render image cell type when element type is folder 1`] = `
|
||||
.c4 {
|
||||
border: 0;
|
||||
-webkit-clip: rect(0 0 0 0);
|
||||
clip: rect(0 0 0 0);
|
||||
height: 1px;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
padding: 0;
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
}
|
||||
|
||||
.c0 {
|
||||
background: #eaf5ff;
|
||||
border-radius: 50%;
|
||||
width: 1.625rem;
|
||||
height: 1.625rem;
|
||||
}
|
||||
|
||||
.c2 {
|
||||
color: #66b7f1;
|
||||
}
|
||||
|
||||
.c1 {
|
||||
-webkit-align-items: center;
|
||||
-webkit-box-align: center;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-flex-direction: row;
|
||||
-ms-flex-direction: row;
|
||||
flex-direction: row;
|
||||
-webkit-box-pack: center;
|
||||
-webkit-justify-content: center;
|
||||
-ms-flex-pack: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.c3 path {
|
||||
fill: #66b7f1;
|
||||
}
|
||||
|
||||
<div>
|
||||
<div
|
||||
class="c0 c1"
|
||||
height="1.625rem"
|
||||
width="1.625rem"
|
||||
>
|
||||
<svg
|
||||
class="c2 c3"
|
||||
fill="none"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M12.414 5H21a1 1 0 011 1v14a1 1 0 01-1 1H3a1 1 0 01-1-1V4a1 1 0 011-1h7.414l2 2z"
|
||||
fill="#212134"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div
|
||||
class="c4"
|
||||
>
|
||||
<p
|
||||
aria-live="polite"
|
||||
aria-relevant="all"
|
||||
id="live-region-log"
|
||||
role="log"
|
||||
/>
|
||||
<p
|
||||
aria-live="polite"
|
||||
aria-relevant="all"
|
||||
id="live-region-status"
|
||||
role="status"
|
||||
/>
|
||||
<p
|
||||
aria-live="assertive"
|
||||
aria-relevant="all"
|
||||
id="live-region-alert"
|
||||
role="alert"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`TableList | CellContent should render size cell type when element type is asset 1`] = `
|
||||
.c1 {
|
||||
border: 0;
|
||||
-webkit-clip: rect(0 0 0 0);
|
||||
clip: rect(0 0 0 0);
|
||||
height: 1px;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
padding: 0;
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
}
|
||||
|
||||
.c0 {
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.43;
|
||||
color: #32324d;
|
||||
}
|
||||
|
||||
<div>
|
||||
<span
|
||||
class="c0"
|
||||
>
|
||||
21KB
|
||||
</span>
|
||||
<div
|
||||
class="c1"
|
||||
>
|
||||
<p
|
||||
aria-live="polite"
|
||||
aria-relevant="all"
|
||||
id="live-region-log"
|
||||
role="log"
|
||||
/>
|
||||
<p
|
||||
aria-live="polite"
|
||||
aria-relevant="all"
|
||||
id="live-region-status"
|
||||
role="status"
|
||||
/>
|
||||
<p
|
||||
aria-live="assertive"
|
||||
aria-relevant="all"
|
||||
id="live-region-alert"
|
||||
role="alert"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`TableList | CellContent should render size cell type with "-" when element type is folder 1`] = `
|
||||
.c1 {
|
||||
border: 0;
|
||||
-webkit-clip: rect(0 0 0 0);
|
||||
clip: rect(0 0 0 0);
|
||||
height: 1px;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
padding: 0;
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
}
|
||||
|
||||
.c0 {
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.43;
|
||||
color: #32324d;
|
||||
}
|
||||
|
||||
<div>
|
||||
<span
|
||||
class="c0"
|
||||
>
|
||||
-
|
||||
</span>
|
||||
<div
|
||||
class="c1"
|
||||
>
|
||||
<p
|
||||
aria-live="polite"
|
||||
aria-relevant="all"
|
||||
id="live-region-log"
|
||||
role="log"
|
||||
/>
|
||||
<p
|
||||
aria-live="polite"
|
||||
aria-relevant="all"
|
||||
id="live-region-status"
|
||||
role="status"
|
||||
/>
|
||||
<p
|
||||
aria-live="assertive"
|
||||
aria-relevant="all"
|
||||
id="live-region-alert"
|
||||
role="alert"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`TableList | CellContent should render text cell type 1`] = `
|
||||
.c1 {
|
||||
border: 0;
|
||||
-webkit-clip: rect(0 0 0 0);
|
||||
clip: rect(0 0 0 0);
|
||||
height: 1px;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
padding: 0;
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
}
|
||||
|
||||
.c0 {
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.43;
|
||||
color: #32324d;
|
||||
}
|
||||
|
||||
<div>
|
||||
<span
|
||||
class="c0"
|
||||
>
|
||||
some text
|
||||
</span>
|
||||
<div
|
||||
class="c1"
|
||||
>
|
||||
<p
|
||||
aria-live="polite"
|
||||
aria-relevant="all"
|
||||
id="live-region-log"
|
||||
role="log"
|
||||
/>
|
||||
<p
|
||||
aria-live="polite"
|
||||
aria-relevant="all"
|
||||
id="live-region-status"
|
||||
role="status"
|
||||
/>
|
||||
<p
|
||||
aria-live="assertive"
|
||||
aria-relevant="all"
|
||||
id="live-region-alert"
|
||||
role="alert"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@ -1,4 +1,5 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import { getTrad } from './utils';
|
||||
|
||||
export const AssetType = {
|
||||
Video: 'video',
|
||||
@ -100,3 +101,70 @@ export const viewOptions = {
|
||||
GRID: 0,
|
||||
LIST: 1,
|
||||
};
|
||||
export const tableHeaders = [
|
||||
{
|
||||
name: 'preview',
|
||||
key: 'preview',
|
||||
metadatas: {
|
||||
label: { id: getTrad('list-table-header-preview'), defaultMessage: 'preview' },
|
||||
sortable: false,
|
||||
},
|
||||
type: 'image',
|
||||
},
|
||||
{
|
||||
name: 'name',
|
||||
key: 'name',
|
||||
metadatas: {
|
||||
label: { id: getTrad('list-table-header-name'), defaultMessage: 'name' },
|
||||
sortable: true,
|
||||
},
|
||||
type: 'text',
|
||||
},
|
||||
{
|
||||
name: 'ext',
|
||||
key: 'extension',
|
||||
metadatas: {
|
||||
label: { id: getTrad('list-table-header-ext'), defaultMessage: 'extension' },
|
||||
sortable: false,
|
||||
},
|
||||
type: 'ext',
|
||||
},
|
||||
{
|
||||
name: 'size',
|
||||
key: 'size',
|
||||
metadatas: {
|
||||
label: { id: getTrad('list-table-header-size'), defaultMessage: 'size' },
|
||||
sortable: false,
|
||||
},
|
||||
type: 'size',
|
||||
},
|
||||
{
|
||||
name: 'createdAt',
|
||||
key: 'createdAt',
|
||||
metadatas: {
|
||||
label: { id: getTrad('list-table-header-createdAt'), defaultMessage: 'created' },
|
||||
sortable: true,
|
||||
},
|
||||
type: 'date',
|
||||
},
|
||||
{
|
||||
name: 'updatedAt',
|
||||
key: 'updatedAt',
|
||||
metadatas: {
|
||||
label: { id: getTrad('list-table-header-updatedAt'), defaultMessage: 'last update' },
|
||||
sortable: true,
|
||||
},
|
||||
type: 'date',
|
||||
},
|
||||
];
|
||||
|
||||
export const pageSizes = [10, 20, 50, 100];
|
||||
|
||||
export const sortOptions = [
|
||||
{ key: 'sort.created_at_desc', value: 'createdAt:DESC' },
|
||||
{ key: 'sort.created_at_asc', value: 'createdAt:ASC' },
|
||||
{ key: 'sort.name_asc', value: 'name:ASC' },
|
||||
{ key: 'sort.name_desc', value: 'name:DESC' },
|
||||
{ key: 'sort.updated_at_desc', value: 'updatedAt:DESC' },
|
||||
{ key: 'sort.updated_at_asc', value: 'updatedAt:ASC' },
|
||||
];
|
||||
|
||||
@ -31,7 +31,8 @@ import Grid from '@strapi/icons/Grid';
|
||||
import { UploadAssetDialog } from '../../components/UploadAssetDialog/UploadAssetDialog';
|
||||
import { EditFolderDialog } from '../../components/EditFolderDialog';
|
||||
import { EditAssetDialog } from '../../components/EditAssetDialog';
|
||||
import { AssetList } from '../../components/AssetList';
|
||||
import { AssetGridList } from '../../components/AssetGridList';
|
||||
import { TableList } from '../../components/TableList';
|
||||
import { FolderList } from '../../components/FolderList';
|
||||
import SortPicker from '../../components/SortPicker';
|
||||
import { useAssets } from '../../hooks/useAssets';
|
||||
@ -102,7 +103,7 @@ export const MediaLibrary = () => {
|
||||
});
|
||||
|
||||
const {
|
||||
data: folders,
|
||||
data: foldersData,
|
||||
isLoading: foldersLoading,
|
||||
errors: foldersError,
|
||||
} = useFolders({
|
||||
@ -123,15 +124,19 @@ export const MediaLibrary = () => {
|
||||
push(pathname);
|
||||
}
|
||||
|
||||
const folders = foldersData?.map((folder) => ({ ...folder, type: 'folder' })) ?? [];
|
||||
const folderCount = folders?.length || 0;
|
||||
const assets = assetsData?.results;
|
||||
const assets = assetsData?.results?.map((asset) => ({ ...asset, type: 'asset' })) || [];
|
||||
const assetCount = assets?.length ?? 0;
|
||||
|
||||
const isLoading = isCurrentFolderLoading || foldersLoading || permissionsLoading || assetsLoading;
|
||||
const [showUploadAssetDialog, setShowUploadAssetDialog] = useState(false);
|
||||
const [showEditFolderDialog, setShowEditFolderDialog] = useState(false);
|
||||
const [assetToEdit, setAssetToEdit] = useState(undefined);
|
||||
const [folderToEdit, setFolderToEdit] = useState(undefined);
|
||||
const [selected, { selectOne, selectAll }] = useSelectionState(['type', 'id'], []);
|
||||
const indeterminateBulkSelect =
|
||||
selected?.length > 0 && selected?.length !== assetCount + folderCount;
|
||||
const toggleUploadAssetDialog = () => setShowUploadAssetDialog((prev) => !prev);
|
||||
const toggleEditFolderDialog = ({ created = false } = {}) => {
|
||||
// folders are only displayed on the first page, therefore
|
||||
@ -147,6 +152,14 @@ export const MediaLibrary = () => {
|
||||
setShowEditFolderDialog((prev) => !prev);
|
||||
};
|
||||
|
||||
const handleBulkSelect = (event, elements) => {
|
||||
if (event.target.checked) {
|
||||
trackUsage('didSelectAllMediaLibraryElements');
|
||||
}
|
||||
|
||||
selectAll(elements);
|
||||
};
|
||||
|
||||
const handleChangeSort = (value) => {
|
||||
trackUsage('didSortMediaLibraryElements', {
|
||||
location: 'upload',
|
||||
@ -186,7 +199,7 @@ export const MediaLibrary = () => {
|
||||
<ActionLayout
|
||||
startActions={
|
||||
<>
|
||||
{canUpdate && (assetCount > 0 || folderCount > 0) && (
|
||||
{canUpdate && isGridView && (assetCount > 0 || folderCount > 0) && (
|
||||
<BoxWithHeight
|
||||
paddingLeft={2}
|
||||
paddingRight={2}
|
||||
@ -199,26 +212,16 @@ export const MediaLibrary = () => {
|
||||
id: getTrad('bulk.select.label'),
|
||||
defaultMessage: 'Select all folders & assets',
|
||||
})}
|
||||
indeterminate={
|
||||
selected?.length > 0 && selected?.length !== assetCount + folderCount
|
||||
}
|
||||
indeterminate={indeterminateBulkSelect}
|
||||
value={
|
||||
(assetCount > 0 || folderCount > 0) &&
|
||||
selected.length === assetCount + folderCount
|
||||
}
|
||||
onChange={(e) => {
|
||||
if (e.target.checked) {
|
||||
trackUsage('didSelectAllMediaLibraryElements');
|
||||
}
|
||||
selectAll([
|
||||
...assets.map((asset) => ({ ...asset, type: 'asset' })),
|
||||
...folders.map((folder) => ({ ...folder, type: 'folder' })),
|
||||
]);
|
||||
}}
|
||||
onChange={(e) => handleBulkSelect(e, [...assets, ...folders])}
|
||||
/>
|
||||
</BoxWithHeight>
|
||||
)}
|
||||
{canRead && <SortPicker onChangeSort={handleChangeSort} />}
|
||||
{canRead && isGridView && <SortPicker onChangeSort={handleChangeSort} />}
|
||||
{canRead && <Filters />}
|
||||
</>
|
||||
}
|
||||
@ -272,11 +275,34 @@ export const MediaLibrary = () => {
|
||||
/>
|
||||
)}
|
||||
|
||||
{canRead && (
|
||||
{/* TODO: fix AssetListTable should handle no assets views (loading) */}
|
||||
{canRead && !isGridView && (assetCount > 0 || folderCount > 0) && (
|
||||
<TableList
|
||||
assetCount={assetCount}
|
||||
folderCount={folderCount}
|
||||
indeterminate={indeterminateBulkSelect}
|
||||
onEditAsset={setAssetToEdit}
|
||||
onEditFolder={handleEditFolder}
|
||||
onSelectOne={selectOne}
|
||||
onSelectAll={handleBulkSelect}
|
||||
rows={
|
||||
// TODO: remove when fixed on DS side
|
||||
// when number of rows in Table changes, the keyboard tab from a row to another
|
||||
// is not working for 1st and last column
|
||||
!assetsLoading && !foldersLoading ? [...folders, ...assets] : []
|
||||
}
|
||||
selected={selected}
|
||||
/>
|
||||
)}
|
||||
|
||||
{canRead && isGridView && (
|
||||
<>
|
||||
{folderCount > 0 && (
|
||||
<FolderList
|
||||
title={
|
||||
// Folders title should only appear if:
|
||||
// user is filtering and there are assets to display, to divide both type of elements
|
||||
// user is not filtering
|
||||
(((isFiltering && assetCount > 0) || !isFiltering) &&
|
||||
formatMessage(
|
||||
{
|
||||
@ -312,7 +338,7 @@ export const MediaLibrary = () => {
|
||||
<FolderCardCheckbox
|
||||
data-testid={`folder-checkbox-${folder.id}`}
|
||||
value={isSelected}
|
||||
onChange={() => selectOne({ ...folder, type: 'folder' })}
|
||||
onChange={() => selectOne(folder)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@ -370,33 +396,33 @@ export const MediaLibrary = () => {
|
||||
)}
|
||||
|
||||
{assetCount > 0 && (
|
||||
<>
|
||||
<AssetList
|
||||
assets={assets}
|
||||
onEditAsset={setAssetToEdit}
|
||||
onSelectAsset={selectOne}
|
||||
selectedAssets={selected.filter(({ type }) => type === 'asset')}
|
||||
title={
|
||||
((!isFiltering || (isFiltering && folderCount > 0)) &&
|
||||
assetsData?.pagination?.page === 1 &&
|
||||
formatMessage(
|
||||
{
|
||||
id: getTrad('list.assets.title'),
|
||||
defaultMessage: 'Assets ({count})',
|
||||
},
|
||||
{ count: assetCount }
|
||||
)) ||
|
||||
''
|
||||
}
|
||||
/>
|
||||
|
||||
{assetsData?.pagination && (
|
||||
<PaginationFooter pagination={assetsData.pagination} />
|
||||
)}
|
||||
</>
|
||||
<AssetGridList
|
||||
assets={assets}
|
||||
onEditAsset={setAssetToEdit}
|
||||
onSelectAsset={selectOne}
|
||||
selectedAssets={selected.filter(({ type }) => type === 'asset')}
|
||||
title={
|
||||
// Assets title should only appear if:
|
||||
// - user is not filtering
|
||||
// - user is filtering and there are folders to display, to separate them
|
||||
// - user is on page 1 since folders won't appear on any other page than the first one (no need to visually separate them)
|
||||
((!isFiltering || (isFiltering && folderCount > 0)) &&
|
||||
assetsData?.pagination?.page === 1 &&
|
||||
formatMessage(
|
||||
{
|
||||
id: getTrad('list.assets.title'),
|
||||
defaultMessage: 'Assets ({count})',
|
||||
},
|
||||
{ count: assetCount }
|
||||
)) ||
|
||||
''
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
{assetsData?.pagination && <PaginationFooter pagination={assetsData.pagination} />}
|
||||
</ContentLayout>
|
||||
</Main>
|
||||
|
||||
|
||||
@ -38,7 +38,7 @@
|
||||
"input.placeholder.icon": "Drop the asset in this zone",
|
||||
"input.url.description": "Separate your URL links by a carriage return.",
|
||||
"input.url.label": "URL",
|
||||
"list.assets.title": "Assets",
|
||||
"list.assets.title": "Assets ({count})",
|
||||
"list.asset.at.finished": "The assets have finished loading.",
|
||||
"list.assets-empty.search": "No result found",
|
||||
"list.assets-empty.subtitle": "Add one to the list.",
|
||||
@ -51,11 +51,13 @@
|
||||
"list.assets.not-supported-content": "No preview available",
|
||||
"list.assets.preview-asset": "Preview for the video at path {path}",
|
||||
"list.assets.selected": "{numberFolders, plural, one {1 folder} other {# folders}} - {numberAssets, plural, one {1 asset} other {# assets}}",
|
||||
"list-assets-select": "Select {name} asset",
|
||||
"list.assets.type-not-allowed": "This type of file is not allowed.",
|
||||
"list.assets.to-upload": "{number, plural, =0 {No asset} one {1 asset} other {# assets}} ready to upload",
|
||||
"list.folder.edit": "Edit folder",
|
||||
"list.folder.select": "Select {name} folder",
|
||||
"list.folder.subtitle": "{folderCount, plural, =0 {# folder} one {# folder} other {# folders}}, {filesCount, plural, =0 {# asset} one {# asset} other {# assets}}",
|
||||
"list.folders.title": "Folders",
|
||||
"list.folders.title": "Folders ({count})",
|
||||
"mediaLibraryInput.actions.nextSlide": "Next slide",
|
||||
"mediaLibraryInput.actions.previousSlide": "Previous slide",
|
||||
"mediaLibraryInput.placeholder": "Click to add an asset or drag and drop one in this area",
|
||||
@ -112,6 +114,13 @@
|
||||
"sort.name_desc": "Reverse alphabetical order (Z to A)",
|
||||
"sort.updated_at_asc": "Oldest updates",
|
||||
"sort.updated_at_desc": "Most recent updates",
|
||||
"list-table-header-actions": "actions",
|
||||
"list-table-header-preview": "preview",
|
||||
"list-table-header-name": "name",
|
||||
"list-table-header-ext": "extension",
|
||||
"list-table-header-size": "size",
|
||||
"list-table-header-createdAt": "Created",
|
||||
"list-table-header-updatedAt": "Last update",
|
||||
"tabs.title": "How do you want to upload your assets?",
|
||||
"window.confirm.close-modal.file": "Are you sure? Your changes will be lost.",
|
||||
"window.confirm.close-modal.files": "Are you sure? You have some files that have not been uploaded yet.",
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user