mirror of
https://github.com/strapi/strapi.git
synced 2025-12-27 15:13:21 +00:00
Add Delete an asset call to the ML (#11186)
This commit is contained in:
parent
e11729184c
commit
4b311e777c
12
package.json
12
package.json
@ -70,12 +70,12 @@
|
||||
"prettier:code": "prettier \"**/*.js\"",
|
||||
"prettier:other": "prettier \"**/*.{md,css,scss,yaml,yml}\"",
|
||||
"test:clean": "rimraf ./coverage",
|
||||
"test:front": "npm run test:clean && cross-env NODE_ENV=test IS_EE=true jest --config ./jest.config.front.js",
|
||||
"test:front:watch": "cross-env NODE_ENV=test IS_EE=true jest --config ./jest.config.front.js --watchAll",
|
||||
"test:front:update": "cross-env NODE_ENV=test IS_EE=true jest --config ./jest.config.front.js --u",
|
||||
"test:front:ce": "npm run test:clean && cross-env NODE_ENV=test IS_EE=false jest --config ./jest.config.front.js --coverage",
|
||||
"test:front:watch:ce": "cross-env NODE_ENV=test IS_EE=false jest --config ./jest.config.front.js --watchAll",
|
||||
"test:front:update:ce": "cross-env NODE_ENV=test IS_EE=false jest --config ./jest.config.front.js --u",
|
||||
"test:front": "npm run test:clean && cross-env TZ=UTC NODE_ENV=test IS_EE=true jest --config ./jest.config.front.js",
|
||||
"test:front:watch": "cross-env TZ=UTC NODE_ENV=test IS_EE=true jest --config ./jest.config.front.js --watchAll",
|
||||
"test:front:update": "cross-env TZ=UTC NODE_ENV=test IS_EE=true jest --config ./jest.config.front.js --u",
|
||||
"test:front:ce": "npm run test:clean && cross-env TZ=UTC NODE_ENV=test IS_EE=false jest --config ./jest.config.front.js --coverage",
|
||||
"test:front:watch:ce": "cross-env TZ=UTC NODE_ENV=test IS_EE=false jest --config ./jest.config.front.js --watchAll",
|
||||
"test:front:update:ce": "cross-env TZ=UTC NODE_ENV=test IS_EE=false jest --config ./jest.config.front.js --u",
|
||||
"test:snyk": "snyk test",
|
||||
"test:unit": "jest --verbose",
|
||||
"test:e2e": "FORCE_COLOR=true jest --config jest.config.e2e.js --verbose --runInBand --testRunner=jest-circus/runner --forceExit --detectOpenHandles",
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
import React from 'react';
|
||||
import React, { useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useIntl } from 'react-intl';
|
||||
import styled from 'styled-components';
|
||||
import { CopyToClipboard } from 'react-copy-to-clipboard';
|
||||
import { Box } from '@strapi/parts/Box';
|
||||
import { Row } from '@strapi/parts/Row';
|
||||
import { Stack } from '@strapi/parts/Stack';
|
||||
@ -10,7 +11,10 @@ import DeleteIcon from '@strapi/icons/DeleteIcon';
|
||||
import DownloadIcon from '@strapi/icons/DownloadIcon';
|
||||
import Resize from '@strapi/icons/Resize';
|
||||
import LinkIcon from '@strapi/icons/LinkIcon';
|
||||
import { prefixFileUrlWithBackendUrl, useNotification } from '@strapi/helper-plugin';
|
||||
import getTrad from '../../utils/getTrad';
|
||||
import { downloadFile } from '../../utils/downloadFile';
|
||||
import { RemoveAssetDialog } from './RemoveAssetDialog';
|
||||
|
||||
const Wrapper = styled.div`
|
||||
img {
|
||||
@ -25,43 +29,84 @@ const ActionRow = styled(Row)`
|
||||
height: ${52 / 16}rem;
|
||||
`;
|
||||
|
||||
export const PreviewBox = ({ children }) => {
|
||||
export const PreviewBox = ({ children, asset, onDelete }) => {
|
||||
const toggleNotification = useNotification();
|
||||
const { formatMessage } = useIntl();
|
||||
const [showConfirmDialog, setShowConfirmDialog] = useState(false);
|
||||
|
||||
const handleCopyClipBoard = () => {
|
||||
toggleNotification({
|
||||
type: 'success',
|
||||
message: { id: 'notification.link-copied', defaultMessage: 'Link copied into the clipboard' },
|
||||
});
|
||||
};
|
||||
|
||||
const assetUrl = prefixFileUrlWithBackendUrl(asset.url);
|
||||
|
||||
return (
|
||||
<Box hasRadius background="neutral150" borderColor="neutral200">
|
||||
<ActionRow paddingLeft={3} paddingRight={3} justifyContent="flex-end">
|
||||
<Stack size={1} horizontal>
|
||||
<IconButton
|
||||
label={formatMessage({ id: getTrad('control-card.delete'), defaultMessage: 'Delete' })}
|
||||
icon={<DeleteIcon />}
|
||||
/>
|
||||
<IconButton
|
||||
label={formatMessage({
|
||||
id: getTrad('control-card.download'),
|
||||
defaultMessage: 'Download',
|
||||
})}
|
||||
icon={<DownloadIcon />}
|
||||
/>
|
||||
<IconButton
|
||||
label={formatMessage({
|
||||
id: getTrad('control-card.copy-link'),
|
||||
defaultMessage: 'Copy link',
|
||||
})}
|
||||
icon={<LinkIcon />}
|
||||
/>
|
||||
<IconButton
|
||||
label={formatMessage({ id: getTrad('control-card.crop'), defaultMessage: 'Crop' })}
|
||||
icon={<Resize />}
|
||||
/>
|
||||
</Stack>
|
||||
</ActionRow>
|
||||
<Wrapper>{children}</Wrapper>
|
||||
<ActionRow paddingLeft={3} paddingRight={3} />
|
||||
</Box>
|
||||
<>
|
||||
<Box hasRadius background="neutral150" borderColor="neutral200">
|
||||
<ActionRow paddingLeft={3} paddingRight={3} justifyContent="flex-end">
|
||||
<Stack size={1} horizontal>
|
||||
<IconButton
|
||||
label={formatMessage({
|
||||
id: getTrad('app.utils.delete'),
|
||||
defaultMessage: 'Delete',
|
||||
})}
|
||||
icon={<DeleteIcon />}
|
||||
onClick={() => setShowConfirmDialog(true)}
|
||||
/>
|
||||
<IconButton
|
||||
label={formatMessage({
|
||||
id: getTrad('control-card.download'),
|
||||
defaultMessage: 'Download',
|
||||
})}
|
||||
icon={<DownloadIcon />}
|
||||
onClick={() => downloadFile(assetUrl, asset.name)}
|
||||
/>
|
||||
<CopyToClipboard text={assetUrl} onCopy={handleCopyClipBoard}>
|
||||
<IconButton
|
||||
label={formatMessage({
|
||||
id: getTrad('control-card.copy-link'),
|
||||
defaultMessage: 'Copy link',
|
||||
})}
|
||||
icon={<LinkIcon />}
|
||||
/>
|
||||
</CopyToClipboard>
|
||||
<IconButton
|
||||
label={formatMessage({ id: getTrad('control-card.crop'), defaultMessage: 'Crop' })}
|
||||
icon={<Resize />}
|
||||
/>
|
||||
</Stack>
|
||||
</ActionRow>
|
||||
<Wrapper>{children}</Wrapper>
|
||||
<ActionRow paddingLeft={3} paddingRight={3} />
|
||||
</Box>
|
||||
|
||||
{showConfirmDialog && (
|
||||
<RemoveAssetDialog
|
||||
onClose={() => {
|
||||
setShowConfirmDialog(false);
|
||||
onDelete();
|
||||
}}
|
||||
asset={asset}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
PreviewBox.propTypes = {
|
||||
asset: PropTypes.shape({
|
||||
id: PropTypes.number,
|
||||
height: PropTypes.number,
|
||||
width: PropTypes.number,
|
||||
size: PropTypes.number,
|
||||
createdAt: PropTypes.string,
|
||||
ext: PropTypes.string,
|
||||
name: PropTypes.string,
|
||||
url: PropTypes.string,
|
||||
}).isRequired,
|
||||
children: PropTypes.node.isRequired,
|
||||
onDelete: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
@ -0,0 +1,35 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { ConfirmDialog } from '@strapi/helper-plugin';
|
||||
import { useRemoveAsset } from '../../hooks/useRemoveAsset';
|
||||
|
||||
export const RemoveAssetDialog = ({ onClose, asset }) => {
|
||||
const { isLoading, removeAsset } = useRemoveAsset(onClose);
|
||||
|
||||
const handleConfirm = () => {
|
||||
removeAsset(asset.id);
|
||||
};
|
||||
|
||||
return (
|
||||
<ConfirmDialog
|
||||
isConfirmButtonLoading={isLoading}
|
||||
isOpen
|
||||
onToggleDialog={onClose}
|
||||
onConfirm={handleConfirm}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
RemoveAssetDialog.propTypes = {
|
||||
onClose: PropTypes.func.isRequired,
|
||||
asset: PropTypes.shape({
|
||||
id: PropTypes.number,
|
||||
height: PropTypes.number,
|
||||
width: PropTypes.number,
|
||||
size: PropTypes.number,
|
||||
createdAt: PropTypes.string,
|
||||
ext: PropTypes.string,
|
||||
name: PropTypes.string,
|
||||
url: PropTypes.string,
|
||||
}).isRequired,
|
||||
};
|
||||
@ -20,93 +20,94 @@ import { getTrad } from '../../utils';
|
||||
import formatBytes from '../../utils/formatBytes';
|
||||
|
||||
export const EditAssetDialog = ({ onClose, asset }) => {
|
||||
const { formatMessage } = useIntl();
|
||||
const formatter = new Intl.DateTimeFormat();
|
||||
const { formatMessage, formatDate } = useIntl();
|
||||
|
||||
// TODO implement when the code is ready
|
||||
const handleSubmit = () => {};
|
||||
|
||||
return (
|
||||
<ModalLayout onClose={onClose} labelledBy="title">
|
||||
<ModalHeader>
|
||||
<ButtonText textColor="neutral800" as="h2" id="title">
|
||||
{formatMessage({ id: getTrad('modal.edit.title'), defaultMessage: 'Details' })}
|
||||
</ButtonText>
|
||||
</ModalHeader>
|
||||
<ModalBody>
|
||||
<Grid gap={4}>
|
||||
<GridItem xs={12} col={6}>
|
||||
<PreviewBox>
|
||||
<img src={prefixFileUrlWithBackendUrl(asset.url)} alt={asset.name} />
|
||||
</PreviewBox>
|
||||
</GridItem>
|
||||
<GridItem xs={12} col={6}>
|
||||
<Stack size={3}>
|
||||
<AssetMeta
|
||||
size={formatBytes(asset.size)}
|
||||
dimension={asset.height && asset.width ? `${asset.height}✕${asset.width}` : ''}
|
||||
date={formatter.format(new Date(asset.createdAt))}
|
||||
extension={getFileExtension(asset.ext)}
|
||||
/>
|
||||
|
||||
<TextInput
|
||||
size="S"
|
||||
label={formatMessage({
|
||||
id: getTrad('form.input.label.file-name'),
|
||||
defaultMessage: 'File name',
|
||||
})}
|
||||
name="filename"
|
||||
/>
|
||||
<TextInput
|
||||
size="S"
|
||||
label={formatMessage({
|
||||
id: getTrad('form.input.label.file-alt'),
|
||||
defaultMessage: 'Alternative text',
|
||||
})}
|
||||
name="altText"
|
||||
hint={formatMessage({
|
||||
id: getTrad({ id: getTrad('form.input.decription.file-alt') }),
|
||||
defaultMessage: 'This text will be displayed if the asset can’t be shown.',
|
||||
})}
|
||||
/>
|
||||
<TextInput
|
||||
size="S"
|
||||
label={formatMessage({
|
||||
id: getTrad('form.input.label.file-caption'),
|
||||
defaultMessage: 'Caption',
|
||||
})}
|
||||
name="caption"
|
||||
/>
|
||||
</Stack>
|
||||
</GridItem>
|
||||
</Grid>
|
||||
</ModalBody>
|
||||
<ModalFooter
|
||||
startActions={
|
||||
<Button onClick={onClose} variant="tertiary">
|
||||
{formatMessage({ id: 'cancel', defaultMessage: 'Cancel' })}
|
||||
</Button>
|
||||
}
|
||||
endActions={
|
||||
<>
|
||||
<Button variant="secondary">
|
||||
{formatMessage({
|
||||
id: getTrad('control-card.replace-media'),
|
||||
defaultMessage: 'Replace media',
|
||||
})}
|
||||
<>
|
||||
<ModalLayout onClose={onClose} labelledBy="title">
|
||||
<ModalHeader>
|
||||
<ButtonText textColor="neutral800" as="h2" id="title">
|
||||
{formatMessage({ id: getTrad('modal.edit.title'), defaultMessage: 'Details' })}
|
||||
</ButtonText>
|
||||
</ModalHeader>
|
||||
<ModalBody>
|
||||
<Grid gap={4}>
|
||||
<GridItem xs={12} col={6}>
|
||||
<PreviewBox asset={asset} onDelete={onClose}>
|
||||
<img src={prefixFileUrlWithBackendUrl(asset.url)} alt={asset.name} />
|
||||
</PreviewBox>
|
||||
</GridItem>
|
||||
<GridItem xs={12} col={6}>
|
||||
<Stack size={3}>
|
||||
<AssetMeta
|
||||
size={formatBytes(asset.size)}
|
||||
dimension={asset.height && asset.width ? `${asset.height}✕${asset.width}` : ''}
|
||||
date={formatDate(new Date(asset.createdAt))}
|
||||
extension={getFileExtension(asset.ext)}
|
||||
/>
|
||||
<TextInput
|
||||
size="S"
|
||||
label={formatMessage({
|
||||
id: getTrad('form.input.label.file-name'),
|
||||
defaultMessage: 'File name',
|
||||
})}
|
||||
name="filename"
|
||||
/>
|
||||
<TextInput
|
||||
size="S"
|
||||
label={formatMessage({
|
||||
id: getTrad('form.input.label.file-alt'),
|
||||
defaultMessage: 'Alternative text',
|
||||
})}
|
||||
name="altText"
|
||||
hint={formatMessage({
|
||||
id: getTrad({ id: getTrad('form.input.decription.file-alt') }),
|
||||
defaultMessage: 'This text will be displayed if the asset can’t be shown.',
|
||||
})}
|
||||
/>
|
||||
<TextInput
|
||||
size="S"
|
||||
label={formatMessage({
|
||||
id: getTrad('form.input.label.file-caption'),
|
||||
defaultMessage: 'Caption',
|
||||
})}
|
||||
name="caption"
|
||||
/>
|
||||
</Stack>
|
||||
</GridItem>
|
||||
</Grid>
|
||||
</ModalBody>
|
||||
<ModalFooter
|
||||
startActions={
|
||||
<Button onClick={onClose} variant="tertiary">
|
||||
{formatMessage({ id: 'cancel', defaultMessage: 'Cancel' })}
|
||||
</Button>
|
||||
<Button onClick={handleSubmit}>
|
||||
{formatMessage({ id: 'form.button.finish', defaultMessage: 'Finish' })}
|
||||
</Button>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
</ModalLayout>
|
||||
}
|
||||
endActions={
|
||||
<>
|
||||
<Button variant="secondary">
|
||||
{formatMessage({
|
||||
id: getTrad('control-card.replace-media'),
|
||||
defaultMessage: 'Replace media',
|
||||
})}
|
||||
</Button>
|
||||
<Button onClick={handleSubmit}>
|
||||
{formatMessage({ id: 'form.button.finish', defaultMessage: 'Finish' })}
|
||||
</Button>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
</ModalLayout>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
EditAssetDialog.propTypes = {
|
||||
asset: PropTypes.shape({
|
||||
id: PropTypes.number,
|
||||
height: PropTypes.number,
|
||||
width: PropTypes.number,
|
||||
size: PropTypes.number,
|
||||
|
||||
@ -0,0 +1,653 @@
|
||||
import React from 'react';
|
||||
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
|
||||
import { ThemeProvider, lightTheme } from '@strapi/parts';
|
||||
import { IntlProvider } from 'react-intl';
|
||||
import { QueryClient, QueryClientProvider } from 'react-query';
|
||||
import { NotificationsProvider } from '@strapi/helper-plugin';
|
||||
import { RemoveAssetDialog } from '../RemoveAssetDialog';
|
||||
import en from '../../../translations/en.json';
|
||||
import server from './server';
|
||||
|
||||
const messageForPlugin = Object.keys(en).reduce((acc, curr) => {
|
||||
acc[curr] = `upload.${en[curr]}`;
|
||||
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
const asset = {
|
||||
id: 8,
|
||||
name: 'Screenshot 2.png',
|
||||
alternativeText: null,
|
||||
caption: null,
|
||||
width: 1476,
|
||||
height: 780,
|
||||
formats: {
|
||||
thumbnail: {
|
||||
name: 'thumbnail_Screenshot 2.png',
|
||||
hash: 'thumbnail_Screenshot_2_5d4a574d61',
|
||||
ext: '.png',
|
||||
mime: 'image/png',
|
||||
width: 245,
|
||||
height: 129,
|
||||
size: 10.7,
|
||||
path: null,
|
||||
url: '/uploads/thumbnail_Screenshot_2_5d4a574d61.png',
|
||||
},
|
||||
large: {
|
||||
name: 'large_Screenshot 2.png',
|
||||
hash: 'large_Screenshot_2_5d4a574d61',
|
||||
ext: '.png',
|
||||
mime: 'image/png',
|
||||
width: 1000,
|
||||
height: 528,
|
||||
size: 97.1,
|
||||
path: null,
|
||||
url: '/uploads/large_Screenshot_2_5d4a574d61.png',
|
||||
},
|
||||
medium: {
|
||||
name: 'medium_Screenshot 2.png',
|
||||
hash: 'medium_Screenshot_2_5d4a574d61',
|
||||
ext: '.png',
|
||||
mime: 'image/png',
|
||||
width: 750,
|
||||
height: 396,
|
||||
size: 58.7,
|
||||
path: null,
|
||||
url: '/uploads/medium_Screenshot_2_5d4a574d61.png',
|
||||
},
|
||||
small: {
|
||||
name: 'small_Screenshot 2.png',
|
||||
hash: 'small_Screenshot_2_5d4a574d61',
|
||||
ext: '.png',
|
||||
mime: 'image/png',
|
||||
width: 500,
|
||||
height: 264,
|
||||
size: 31.06,
|
||||
path: null,
|
||||
url: '/uploads/small_Screenshot_2_5d4a574d61.png',
|
||||
},
|
||||
},
|
||||
hash: 'Screenshot_2_5d4a574d61',
|
||||
ext: '.png',
|
||||
mime: 'image/png',
|
||||
size: 102.01,
|
||||
url: '/uploads/Screenshot_2_5d4a574d61.png',
|
||||
previewUrl: null,
|
||||
provider: 'local',
|
||||
provider_metadata: null,
|
||||
createdAt: '2021-10-04T09:42:31.670Z',
|
||||
updatedAt: '2021-10-04T09:42:31.670Z',
|
||||
};
|
||||
|
||||
const renderCompo = (handleCloseSpy = jest.fn(), toggleNotificationSpy = jest.fn()) => {
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
refetchOnWindowFocus: false,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return render(
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<ThemeProvider theme={lightTheme}>
|
||||
<NotificationsProvider toggleNotification={toggleNotificationSpy}>
|
||||
<IntlProvider locale="en" messages={messageForPlugin} defaultLocale="en">
|
||||
<RemoveAssetDialog onClose={handleCloseSpy} asset={asset} />
|
||||
</IntlProvider>
|
||||
</NotificationsProvider>
|
||||
</ThemeProvider>
|
||||
</QueryClientProvider>,
|
||||
{ container: document.body }
|
||||
);
|
||||
};
|
||||
|
||||
describe('RemoveAssetDialog', () => {
|
||||
beforeAll(() => server.listen());
|
||||
|
||||
afterEach(() => server.resetHandlers());
|
||||
afterAll(() => server.close());
|
||||
|
||||
it('snapshots the component', () => {
|
||||
const { container } = renderCompo();
|
||||
|
||||
expect(container).toMatchInlineSnapshot(`
|
||||
.c0 {
|
||||
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;
|
||||
}
|
||||
|
||||
.c20 {
|
||||
font-weight: 500;
|
||||
font-size: 0.75rem;
|
||||
line-height: 1.33;
|
||||
color: #32324d;
|
||||
}
|
||||
|
||||
.c23 {
|
||||
padding-right: 8px;
|
||||
}
|
||||
|
||||
.c17 {
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
cursor: pointer;
|
||||
padding: 8px;
|
||||
border-radius: 4px;
|
||||
background: #ffffff;
|
||||
border: 1px solid #dcdce4;
|
||||
}
|
||||
|
||||
.c17 svg {
|
||||
height: 12px;
|
||||
width: 12px;
|
||||
}
|
||||
|
||||
.c17 svg > g,
|
||||
.c17 svg path {
|
||||
fill: #ffffff;
|
||||
}
|
||||
|
||||
.c17[aria-disabled='true'] {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.c24 {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.c18 {
|
||||
-webkit-align-items: center;
|
||||
-webkit-box-align: center;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
padding: 8px 16px;
|
||||
background: #4945ff;
|
||||
border: none;
|
||||
border: 1px solid #dcdce4;
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.c18 .c22 {
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-align-items: center;
|
||||
-webkit-box-align: center;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.c18 .c19 {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.c18[aria-disabled='true'] {
|
||||
border: 1px solid #dcdce4;
|
||||
background: #eaeaef;
|
||||
}
|
||||
|
||||
.c18[aria-disabled='true'] .c19 {
|
||||
color: #666687;
|
||||
}
|
||||
|
||||
.c18[aria-disabled='true'] svg > g,
|
||||
.c18[aria-disabled='true'] svg path {
|
||||
fill: #666687;
|
||||
}
|
||||
|
||||
.c18[aria-disabled='true']:active {
|
||||
border: 1px solid #dcdce4;
|
||||
background: #eaeaef;
|
||||
}
|
||||
|
||||
.c18[aria-disabled='true']:active .c19 {
|
||||
color: #666687;
|
||||
}
|
||||
|
||||
.c18[aria-disabled='true']:active svg > g,
|
||||
.c18[aria-disabled='true']:active svg path {
|
||||
fill: #666687;
|
||||
}
|
||||
|
||||
.c18:hover {
|
||||
background-color: #f6f6f9;
|
||||
}
|
||||
|
||||
.c18:active {
|
||||
background-color: #eaeaef;
|
||||
}
|
||||
|
||||
.c18 .c19 {
|
||||
color: #32324d;
|
||||
}
|
||||
|
||||
.c18 svg > g,
|
||||
.c18 svg path {
|
||||
fill: #32324d;
|
||||
}
|
||||
|
||||
.c21 {
|
||||
-webkit-align-items: center;
|
||||
-webkit-box-align: center;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
padding: 8px 16px;
|
||||
background: #4945ff;
|
||||
border: none;
|
||||
border: 1px solid #f5c0b8;
|
||||
background: #fcecea;
|
||||
}
|
||||
|
||||
.c21 .c22 {
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-align-items: center;
|
||||
-webkit-box-align: center;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.c21 .c19 {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.c21[aria-disabled='true'] {
|
||||
border: 1px solid #dcdce4;
|
||||
background: #eaeaef;
|
||||
}
|
||||
|
||||
.c21[aria-disabled='true'] .c19 {
|
||||
color: #666687;
|
||||
}
|
||||
|
||||
.c21[aria-disabled='true'] svg > g,
|
||||
.c21[aria-disabled='true'] svg path {
|
||||
fill: #666687;
|
||||
}
|
||||
|
||||
.c21[aria-disabled='true']:active {
|
||||
border: 1px solid #dcdce4;
|
||||
background: #eaeaef;
|
||||
}
|
||||
|
||||
.c21[aria-disabled='true']:active .c19 {
|
||||
color: #666687;
|
||||
}
|
||||
|
||||
.c21[aria-disabled='true']:active svg > g,
|
||||
.c21[aria-disabled='true']:active svg path {
|
||||
fill: #666687;
|
||||
}
|
||||
|
||||
.c21:hover {
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
.c21:active {
|
||||
background-color: #ffffff;
|
||||
border: 1px solid #d02b20;
|
||||
}
|
||||
|
||||
.c21:active .c19 {
|
||||
color: #d02b20;
|
||||
}
|
||||
|
||||
.c21:active svg > g,
|
||||
.c21:active svg path {
|
||||
fill: #d02b20;
|
||||
}
|
||||
|
||||
.c21 .c19 {
|
||||
color: #b72b1a;
|
||||
}
|
||||
|
||||
.c21 svg > g,
|
||||
.c21 svg path {
|
||||
fill: #b72b1a;
|
||||
}
|
||||
|
||||
.c2 {
|
||||
background: #ffffff;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0px 2px 15px rgba(33,33,52,0.1);
|
||||
}
|
||||
|
||||
.c4 {
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.c8 {
|
||||
padding-top: 40px;
|
||||
padding-right: 24px;
|
||||
padding-bottom: 40px;
|
||||
padding-left: 24px;
|
||||
}
|
||||
|
||||
.c9 {
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
|
||||
.c14 {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.c5 {
|
||||
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;
|
||||
-webkit-align-items: center;
|
||||
-webkit-box-align: center;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.c7 {
|
||||
font-weight: 600;
|
||||
font-size: 1.125rem;
|
||||
line-height: 1.22;
|
||||
color: #32324d;
|
||||
}
|
||||
|
||||
.c1 {
|
||||
position: fixed;
|
||||
z-index: 4;
|
||||
inset: 0;
|
||||
background: #32324d33;
|
||||
padding: 0 40px;
|
||||
}
|
||||
|
||||
.c3 {
|
||||
max-width: 25.75rem;
|
||||
margin: 0 auto;
|
||||
overflow: hidden;
|
||||
margin-top: 10%;
|
||||
}
|
||||
|
||||
.c6 {
|
||||
border-bottom: 1px solid #eaeaef;
|
||||
}
|
||||
|
||||
.c10 svg {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
.c10 svg path {
|
||||
fill: #d02b20;
|
||||
}
|
||||
|
||||
.c16 {
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-flex-direction: row;
|
||||
-ms-flex-direction: row;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.c16 > * {
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.c16 > * + * {
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
.c15 {
|
||||
border-top: 1px solid #eaeaef;
|
||||
}
|
||||
|
||||
.c15 button {
|
||||
width: 100%;
|
||||
display: -webkit-inline-box;
|
||||
display: -webkit-inline-flex;
|
||||
display: -ms-inline-flexbox;
|
||||
display: inline-flex;
|
||||
-webkit-box-pack: center;
|
||||
-webkit-justify-content: center;
|
||||
-ms-flex-pack: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.c12 {
|
||||
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;
|
||||
-webkit-align-items: center;
|
||||
-webkit-box-align: center;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.c11 {
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-flex-direction: column;
|
||||
-ms-flex-direction: column;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.c11 > * {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.c11 > * + * {
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.c13 {
|
||||
font-weight: 400;
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.43;
|
||||
color: #32324d;
|
||||
}
|
||||
|
||||
<body
|
||||
class="lock-body-scroll"
|
||||
>
|
||||
<div
|
||||
class="c0"
|
||||
>
|
||||
<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
|
||||
data-react-portal="true"
|
||||
>
|
||||
<div
|
||||
class="c1"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
aria-labelledby="dialog-1-label"
|
||||
aria-modal="true"
|
||||
class="c2 c3"
|
||||
role="dialog"
|
||||
>
|
||||
<div
|
||||
class="c4 c5 c6"
|
||||
>
|
||||
<h2
|
||||
class="c7"
|
||||
id="dialog-1-label"
|
||||
>
|
||||
Confirmation
|
||||
</h2>
|
||||
</div>
|
||||
<div
|
||||
class=""
|
||||
>
|
||||
<div
|
||||
class="c8"
|
||||
>
|
||||
<div
|
||||
class="c9 c10"
|
||||
>
|
||||
<div
|
||||
class="c5"
|
||||
>
|
||||
<svg
|
||||
fill="none"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M12 0C5.383 0 0 5.383 0 12s5.383 12 12 12 12-5.383 12-12S18.617 0 12 0zm1.154 18.456h-2.308V16.15h2.308v2.307zm-.23-3.687h-1.847l-.346-9.23h2.538l-.346 9.23z"
|
||||
fill="#212134"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="c11"
|
||||
>
|
||||
<div
|
||||
class="c12"
|
||||
>
|
||||
<span
|
||||
class="c13"
|
||||
id="confirm-description"
|
||||
>
|
||||
Are you sure you want to delete this?
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="c14 c15"
|
||||
>
|
||||
<div
|
||||
class="c16"
|
||||
>
|
||||
<button
|
||||
aria-disabled="false"
|
||||
class="c17 c18"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
class="c19 c20"
|
||||
>
|
||||
Cancel
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
aria-disabled="false"
|
||||
class="c17 c21"
|
||||
id="confirm-delete"
|
||||
type="button"
|
||||
>
|
||||
<div
|
||||
aria-hidden="true"
|
||||
class="c22 c23 c24"
|
||||
>
|
||||
<svg
|
||||
fill="none"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M3.236 6.149a.2.2 0 00-.197.233L6 24h12l2.96-17.618a.2.2 0 00-.196-.233H3.236zM21.8 1.983c.11 0 .2.09.2.2v1.584a.2.2 0 01-.2.2H2.2a.2.2 0 01-.2-.2V2.183c0-.11.09-.2.2-.2h5.511c.9 0 1.631-1.09 1.631-1.983h5.316c0 .894.73 1.983 1.631 1.983H21.8z"
|
||||
fill="#32324D"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<span
|
||||
class="c19 c20"
|
||||
>
|
||||
Confirm
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
`);
|
||||
});
|
||||
|
||||
it('closes the dialog when pressing cancel', () => {
|
||||
const handleCloseSpy = jest.fn();
|
||||
renderCompo(handleCloseSpy);
|
||||
|
||||
fireEvent.click(screen.getByText('Cancel'));
|
||||
expect(handleCloseSpy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
describe('remove asset', () => {
|
||||
it('closes the dialog when everything is going okay when removing', async () => {
|
||||
const handleCloseSpy = jest.fn();
|
||||
const toggleNotificationSpy = jest.fn();
|
||||
renderCompo(handleCloseSpy, toggleNotificationSpy);
|
||||
|
||||
fireEvent.click(screen.getByText('Confirm'));
|
||||
|
||||
await waitFor(() => expect(handleCloseSpy).toHaveBeenCalled());
|
||||
await waitFor(() =>
|
||||
expect(toggleNotificationSpy).toHaveBeenCalledWith({
|
||||
message: {
|
||||
defaultMessage: 'The asset has been successfully removed.',
|
||||
id: 'modal.remove.success-label',
|
||||
},
|
||||
type: 'success',
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,12 @@
|
||||
import { setupServer } from 'msw/node';
|
||||
import { rest } from 'msw';
|
||||
|
||||
const handlers = [
|
||||
rest.delete('*/upload/files/8', (req, res, ctx) => {
|
||||
return res(ctx.status(200));
|
||||
}),
|
||||
];
|
||||
|
||||
const server = setupServer(...handlers);
|
||||
|
||||
export default server;
|
||||
44
packages/core/upload/admin/src/hooks/useRemoveAsset.js
Normal file
44
packages/core/upload/admin/src/hooks/useRemoveAsset.js
Normal file
@ -0,0 +1,44 @@
|
||||
import { useMutation, useQueryClient } from 'react-query';
|
||||
import { useNotification } from '@strapi/helper-plugin';
|
||||
import { axiosInstance } from '../utils';
|
||||
|
||||
const removeAssetRequest = assetId => {
|
||||
const endpoint = `/upload/files/${assetId}`;
|
||||
|
||||
return axiosInstance({
|
||||
method: 'delete',
|
||||
url: endpoint,
|
||||
headers: {},
|
||||
});
|
||||
};
|
||||
|
||||
export const useRemoveAsset = onSuccess => {
|
||||
const toggleNotification = useNotification();
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const mutation = useMutation(assetId => removeAssetRequest(assetId), {
|
||||
onSuccess: ({ data }) => {
|
||||
// Coupled with the cache of useAssets
|
||||
queryClient.setQueryData('assets', (cachedAssets = []) =>
|
||||
cachedAssets.filter(asset => asset.id !== data.id)
|
||||
);
|
||||
|
||||
toggleNotification({
|
||||
type: 'success',
|
||||
message: {
|
||||
id: 'modal.remove.success-label',
|
||||
defaultMessage: 'The asset has been successfully removed.',
|
||||
},
|
||||
});
|
||||
|
||||
onSuccess();
|
||||
},
|
||||
onError: error => {
|
||||
toggleNotification({ type: 'warning', message: error.message });
|
||||
},
|
||||
});
|
||||
|
||||
const removeAsset = assetId => mutation.mutate(assetId);
|
||||
|
||||
return { ...mutation, removeAsset };
|
||||
};
|
||||
@ -92,5 +92,6 @@
|
||||
"sort.updated_at_desc": "Most recent updates",
|
||||
"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."
|
||||
"window.confirm.close-modal.files": "Are you sure? You have some files that have not been uploaded yet.",
|
||||
"modal.remove.success-label": "The asset has been successfully removed."
|
||||
}
|
||||
|
||||
16
packages/core/upload/admin/src/utils/downloadFile.js
Normal file
16
packages/core/upload/admin/src/utils/downloadFile.js
Normal file
@ -0,0 +1,16 @@
|
||||
import axios from 'axios';
|
||||
|
||||
export const downloadFile = (url, fileName) => {
|
||||
axios({
|
||||
url,
|
||||
method: 'GET',
|
||||
responseType: 'blob',
|
||||
}).then(response => {
|
||||
const url = window.URL.createObjectURL(new Blob([response.data]));
|
||||
const link = document.createElement('a');
|
||||
|
||||
link.href = url;
|
||||
link.setAttribute('download', fileName);
|
||||
link.click();
|
||||
});
|
||||
};
|
||||
@ -78,10 +78,8 @@ module.exports = {
|
||||
},
|
||||
|
||||
async destroy(ctx) {
|
||||
const {
|
||||
state: { userAbility },
|
||||
params: { id },
|
||||
} = ctx;
|
||||
const { id } = ctx.params;
|
||||
const { userAbility } = ctx.state;
|
||||
|
||||
const { pm, file } = await findEntityAndCheckPermissions(
|
||||
userAbility,
|
||||
@ -218,9 +216,7 @@ const findEntityAndCheckPermissions = async (ability, action, model, id) => {
|
||||
|
||||
const pm = strapi.admin.services.permission.createPermissionsManager({ ability, action, model });
|
||||
|
||||
const author = await strapi.admin.services.user.findOne({ id: file[CREATED_BY_ATTRIBUTE].id }, [
|
||||
'roles',
|
||||
]);
|
||||
const author = await strapi.admin.services.user.findOne(file[CREATED_BY_ATTRIBUTE].id, ['roles']);
|
||||
|
||||
const fileWithRoles = _.set(_.cloneDeep(file), 'createdBy', author);
|
||||
|
||||
|
||||
@ -22,3 +22,5 @@ global.strapi = {
|
||||
},
|
||||
projectType: 'Community',
|
||||
};
|
||||
|
||||
global.prompt = jest.fn();
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user