add upload component into schema components (#165)
* feat: add Upload component into schema components * docx file * upload with customRequest * action * optimize: remove downloadFile instead of saveAs * fix: direct download of non-image files Co-authored-by: chenos <chenlinxh@gmail.com>
This commit is contained in:
parent
5e7e3c2c46
commit
be1192531c
@ -25,6 +25,7 @@
|
|||||||
"@formily/react": "^2.0.7",
|
"@formily/react": "^2.0.7",
|
||||||
"ahooks": "^3.0.5",
|
"ahooks": "^3.0.5",
|
||||||
"axios": "^0.24.0",
|
"axios": "^0.24.0",
|
||||||
|
"file-saver": "^2.0.5",
|
||||||
"i18next": "^21.6.0",
|
"i18next": "^21.6.0",
|
||||||
"react-helmet": "^6.1.0",
|
"react-helmet": "^6.1.0",
|
||||||
"react-i18next": "^11.15.1",
|
"react-i18next": "^11.15.1",
|
||||||
|
@ -16,4 +16,4 @@ export * from './page';
|
|||||||
export * from './password';
|
export * from './password';
|
||||||
export * from './radio';
|
export * from './radio';
|
||||||
export * from './time-picker';
|
export * from './time-picker';
|
||||||
|
export * from './upload';
|
||||||
|
147
packages/client/src/schema-component/antd/upload/ReadPretty.tsx
Normal file
147
packages/client/src/schema-component/antd/upload/ReadPretty.tsx
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
import DownloadOutlined from '@ant-design/icons/DownloadOutlined';
|
||||||
|
import { Field } from '@formily/core';
|
||||||
|
import { useField } from '@formily/react';
|
||||||
|
import { Button, Space } from 'antd';
|
||||||
|
import cls from 'classnames';
|
||||||
|
import { saveAs } from 'file-saver';
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
import Lightbox from 'react-image-lightbox';
|
||||||
|
import { isImage, toArr, toImages } from './shared';
|
||||||
|
import type { UploadProps } from './type';
|
||||||
|
|
||||||
|
type Composed = React.FC<UploadProps> & {
|
||||||
|
Upload?: React.FC<UploadProps>;
|
||||||
|
Attachment?: React.FC<UploadProps>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ReadPretty: Composed = () => null;
|
||||||
|
|
||||||
|
ReadPretty.Attachment = (props: UploadProps) => {
|
||||||
|
const field = useField<Field>();
|
||||||
|
const images = toImages(toArr(field.value));
|
||||||
|
const [photoIndex, setPhotoIndex] = useState(0);
|
||||||
|
const [visible, setVisible] = useState(false);
|
||||||
|
const { size } = props;
|
||||||
|
console.log('field.value', field.value, images);
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className={cls('ant-upload-picture-card-wrapper nb-upload', size ? `nb-upload-${size}` : null)}>
|
||||||
|
<div className={'ant-upload-list ant-upload-list-picture-card'}>
|
||||||
|
{images.map((file) => {
|
||||||
|
const handleClick = (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
const index = images.indexOf(file);
|
||||||
|
if (isImage(file.extname)) {
|
||||||
|
setVisible(true);
|
||||||
|
setPhotoIndex(index);
|
||||||
|
} else {
|
||||||
|
saveAs(file.url, `${file.title}${file.extname}`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<div className={'ant-upload-list-picture-card-container'}>
|
||||||
|
<div className="ant-upload-list-item ant-upload-list-item-done ant-upload-list-item-list-type-picture-card">
|
||||||
|
<div className={'ant-upload-list-item-info'}>
|
||||||
|
<span className="ant-upload-span">
|
||||||
|
<a
|
||||||
|
className="ant-upload-list-item-thumbnail"
|
||||||
|
href={file.url}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
onClick={handleClick}
|
||||||
|
>
|
||||||
|
{file.imageUrl && (
|
||||||
|
<img
|
||||||
|
src={`${file.imageUrl}?x-oss-process=style/thumbnail`}
|
||||||
|
alt={file.title}
|
||||||
|
className="ant-upload-list-item-image"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="ant-upload-list-item-name"
|
||||||
|
title={file.title}
|
||||||
|
href={file.url}
|
||||||
|
onClick={handleClick}
|
||||||
|
>
|
||||||
|
{file.title}
|
||||||
|
</a>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
{size !== 'small' && (
|
||||||
|
<span className={'ant-upload-list-item-actions'}>
|
||||||
|
<Space size={3}>
|
||||||
|
<Button
|
||||||
|
size={'small'}
|
||||||
|
type={'text'}
|
||||||
|
icon={<DownloadOutlined />}
|
||||||
|
onClick={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
saveAs(file.url, `${file.title}${file.extname}`);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Space>
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{visible && (
|
||||||
|
<Lightbox
|
||||||
|
// discourageDownloads={true}
|
||||||
|
mainSrc={images[photoIndex]?.imageUrl}
|
||||||
|
nextSrc={images[(photoIndex + 1) % images.length]?.imageUrl}
|
||||||
|
prevSrc={images[(photoIndex + images.length - 1) % images.length]?.imageUrl}
|
||||||
|
// @ts-ignore
|
||||||
|
onCloseRequest={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
setVisible(false);
|
||||||
|
}}
|
||||||
|
onMovePrevRequest={() => setPhotoIndex((photoIndex + images.length - 1) % images.length)}
|
||||||
|
onMoveNextRequest={() => setPhotoIndex((photoIndex + 1) % images.length)}
|
||||||
|
imageTitle={images[photoIndex]?.title}
|
||||||
|
toolbarButtons={[
|
||||||
|
<button
|
||||||
|
style={{ fontSize: 22, background: 'none', lineHeight: 1 }}
|
||||||
|
type="button"
|
||||||
|
aria-label="Zoom in"
|
||||||
|
title="Zoom in"
|
||||||
|
className="ril-zoom-in ril__toolbarItemChild ril__builtinButton"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
const file = images[photoIndex];
|
||||||
|
saveAs(file.url, `${file.title}${file.extname}`);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<DownloadOutlined />
|
||||||
|
</button>,
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
ReadPretty.Upload = (props) => {
|
||||||
|
const field = useField<Field>();
|
||||||
|
return (field.value || []).map((item) => (
|
||||||
|
<div>
|
||||||
|
{item.url ? (
|
||||||
|
<a target={'_blank'} href={item.url}>
|
||||||
|
{item.name}
|
||||||
|
</a>
|
||||||
|
) : (
|
||||||
|
<span>{item.name}</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
));
|
||||||
|
};
|
207
packages/client/src/schema-component/antd/upload/Upload.tsx
Normal file
207
packages/client/src/schema-component/antd/upload/Upload.tsx
Normal file
@ -0,0 +1,207 @@
|
|||||||
|
import DeleteOutlined from '@ant-design/icons/DeleteOutlined';
|
||||||
|
import DownloadOutlined from '@ant-design/icons/DownloadOutlined';
|
||||||
|
import PlusOutlined from '@ant-design/icons/PlusOutlined';
|
||||||
|
import { usePrefixCls } from '@formily/antd/esm/__builtins__';
|
||||||
|
import { connect, mapProps, mapReadPretty } from '@formily/react';
|
||||||
|
import { Button, Progress, Space, Upload as AntdUpload } from 'antd';
|
||||||
|
import cls from 'classnames';
|
||||||
|
import { saveAs } from 'file-saver';
|
||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import Lightbox from 'react-image-lightbox';
|
||||||
|
import 'react-image-lightbox/style.css'; // This only needs to be imported once in your app
|
||||||
|
import { ReadPretty } from './ReadPretty';
|
||||||
|
import { isImage, toArr, toFileList, toItem, toValue, useUploadProps } from './shared';
|
||||||
|
import './style.less';
|
||||||
|
import type { ComposedUpload, DraggerProps, UploadProps } from './type';
|
||||||
|
|
||||||
|
export const Upload: ComposedUpload = connect(
|
||||||
|
(props: UploadProps) => {
|
||||||
|
return <AntdUpload {...useUploadProps(props)} />;
|
||||||
|
},
|
||||||
|
mapProps({
|
||||||
|
value: 'fileList',
|
||||||
|
}),
|
||||||
|
mapReadPretty(ReadPretty.Upload),
|
||||||
|
);
|
||||||
|
|
||||||
|
Upload.Attachment = connect((props: UploadProps) => {
|
||||||
|
const { disabled, multiple, value, onChange } = props;
|
||||||
|
const [fileList, setFileList] = useState([]);
|
||||||
|
const [sync, setSync] = useState(true);
|
||||||
|
const images = fileList;
|
||||||
|
const [photoIndex, setPhotoIndex] = useState(0);
|
||||||
|
const [visible, setVisible] = useState(false);
|
||||||
|
const { t } = useTranslation();
|
||||||
|
useEffect(() => {
|
||||||
|
if (sync) {
|
||||||
|
setFileList(toFileList(value));
|
||||||
|
}
|
||||||
|
}, [value, sync]);
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className={cls('ant-upload-picture-card-wrapper nb-upload')}>
|
||||||
|
<div className={'ant-upload-list ant-upload-list-picture-card'}>
|
||||||
|
{fileList.map((file) => {
|
||||||
|
const handleClick = (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
const index = fileList.indexOf(file);
|
||||||
|
if (isImage(file.extname)) {
|
||||||
|
setVisible(true);
|
||||||
|
setPhotoIndex(index);
|
||||||
|
} else {
|
||||||
|
saveAs(file.url, `${file.title}${file.extname}`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<div className={'ant-upload-list-picture-card-container'}>
|
||||||
|
<div className="ant-upload-list-item ant-upload-list-item-done ant-upload-list-item-list-type-picture-card">
|
||||||
|
<div className={'ant-upload-list-item-info'}>
|
||||||
|
<span className="ant-upload-span">
|
||||||
|
<a
|
||||||
|
className="ant-upload-list-item-thumbnail"
|
||||||
|
href={file.url}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
onClick={handleClick}
|
||||||
|
>
|
||||||
|
{file.imageUrl && (
|
||||||
|
<img src={file.imageUrl} alt={file.title} className="ant-upload-list-item-image" />
|
||||||
|
)}
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="ant-upload-list-item-name"
|
||||||
|
title={file.title}
|
||||||
|
href={file.url}
|
||||||
|
onClick={handleClick}
|
||||||
|
>
|
||||||
|
{file.status === 'uploading' ? t('Uploading') : file.title}
|
||||||
|
</a>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<span className={'ant-upload-list-item-actions'}>
|
||||||
|
<Space size={3}>
|
||||||
|
<Button
|
||||||
|
size={'small'}
|
||||||
|
type={'text'}
|
||||||
|
icon={<DownloadOutlined />}
|
||||||
|
onClick={() => {
|
||||||
|
saveAs(file.url, `${file.title}${file.extname}`);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{!disabled && (
|
||||||
|
<Button
|
||||||
|
size={'small'}
|
||||||
|
type={'text'}
|
||||||
|
icon={<DeleteOutlined />}
|
||||||
|
onClick={() => {
|
||||||
|
setSync(false);
|
||||||
|
setFileList((prevFileList) => {
|
||||||
|
if (!multiple) {
|
||||||
|
onChange(null);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
const index = prevFileList.indexOf(file);
|
||||||
|
prevFileList.splice(index, 1);
|
||||||
|
onChange(toValue([...prevFileList]));
|
||||||
|
return [...prevFileList];
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Space>
|
||||||
|
</span>
|
||||||
|
{file.status === 'uploading' && (
|
||||||
|
<div className={'ant-upload-list-item-progress'}>
|
||||||
|
<Progress strokeWidth={2} type={'line'} showInfo={false} percent={file.percent} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
<div className={'ant-upload-list-picture-card-container'}>
|
||||||
|
<AntdUpload
|
||||||
|
{...useUploadProps({ ...props })}
|
||||||
|
disabled={disabled}
|
||||||
|
multiple={multiple}
|
||||||
|
listType={'picture-card'}
|
||||||
|
fileList={fileList}
|
||||||
|
onChange={(info) => {
|
||||||
|
setSync(false);
|
||||||
|
if (multiple) {
|
||||||
|
if (info.file.status === 'done') {
|
||||||
|
onChange(toValue(info.fileList));
|
||||||
|
}
|
||||||
|
setFileList(info.fileList.map(toItem));
|
||||||
|
} else {
|
||||||
|
if (info.file.status === 'done') {
|
||||||
|
console.log('field.value', info.file?.response?.data);
|
||||||
|
// TODO(BUG): object 的联动有问题,不响应,折中的办法先置空再赋值
|
||||||
|
onChange(null);
|
||||||
|
onChange(info.file?.response?.data);
|
||||||
|
}
|
||||||
|
setFileList([toItem(info.file)]);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
showUploadList={false}
|
||||||
|
>
|
||||||
|
{!disabled && (multiple || toArr(value).length < 1) && (
|
||||||
|
<span>
|
||||||
|
<PlusOutlined />
|
||||||
|
<br /> {t('Upload')}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</AntdUpload>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{visible && (
|
||||||
|
<Lightbox
|
||||||
|
// discourageDownloads={true}
|
||||||
|
mainSrc={images[photoIndex]?.imageUrl}
|
||||||
|
nextSrc={images[(photoIndex + 1) % images.length]?.imageUrl}
|
||||||
|
prevSrc={images[(photoIndex + images.length - 1) % images.length]?.imageUrl}
|
||||||
|
onCloseRequest={() => setVisible(false)}
|
||||||
|
onMovePrevRequest={() => setPhotoIndex((photoIndex + images.length - 1) % images.length)}
|
||||||
|
onMoveNextRequest={() => setPhotoIndex((photoIndex + 1) % images.length)}
|
||||||
|
imageTitle={images[photoIndex]?.title}
|
||||||
|
toolbarButtons={[
|
||||||
|
<button
|
||||||
|
style={{ fontSize: 22, background: 'none', lineHeight: 1 }}
|
||||||
|
type="button"
|
||||||
|
aria-label="Zoom in"
|
||||||
|
title="Zoom in"
|
||||||
|
className="ril-zoom-in ril__toolbarItemChild ril__builtinButton"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
const file = images[photoIndex];
|
||||||
|
saveAs(file.url, `${file.title}${file.extname}`);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<DownloadOutlined />
|
||||||
|
</button>,
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}, mapReadPretty(ReadPretty.Attachment));
|
||||||
|
|
||||||
|
Upload.Dragger = connect(
|
||||||
|
(props: DraggerProps) => {
|
||||||
|
return (
|
||||||
|
<div className={usePrefixCls('upload-dragger')}>
|
||||||
|
<AntdUpload.Dragger {...useUploadProps(props)} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
mapProps({
|
||||||
|
value: 'fileList',
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
export default Upload;
|
@ -0,0 +1,43 @@
|
|||||||
|
import { APIClient } from '@nocobase/client';
|
||||||
|
import MockAdapter from 'axios-mock-adapter';
|
||||||
|
|
||||||
|
export const apiClient = new APIClient({
|
||||||
|
baseURL: 'http://localhost:8001/api/',
|
||||||
|
});
|
||||||
|
|
||||||
|
const mock = new MockAdapter(apiClient.axios);
|
||||||
|
|
||||||
|
const sleep = (value: number) => new Promise((resolve) => setTimeout(resolve, value));
|
||||||
|
|
||||||
|
mock.onPost('/attachments:upload').reply(async (config) => {
|
||||||
|
const total = 1024; // mocked file size
|
||||||
|
for (const progress of [0, 0.2, 0.4, 0.6, 0.8, 1]) {
|
||||||
|
await sleep(500);
|
||||||
|
if (config.onUploadProgress) {
|
||||||
|
config.onUploadProgress({ loaded: total * progress, total });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return [
|
||||||
|
200,
|
||||||
|
{
|
||||||
|
data: {
|
||||||
|
id: 2,
|
||||||
|
title: 'd9f6ad6669902a9a8a1229d9f362235a (6)',
|
||||||
|
filename: '7edb55e4e3145e5ac59ea3a44ca840e9.docx',
|
||||||
|
extname: '.docx',
|
||||||
|
path: '',
|
||||||
|
size: null,
|
||||||
|
url: 'https://nocobase.oss-cn-beijing.aliyuncs.com/7edb55e4e3145e5ac59ea3a44ca840e9.docx',
|
||||||
|
mimetype: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
||||||
|
meta: {},
|
||||||
|
storage_id: 2,
|
||||||
|
updated_at: '2022-01-21T07:21:21.084Z',
|
||||||
|
created_at: '2022-01-21T07:21:21.084Z',
|
||||||
|
created_by_id: null,
|
||||||
|
updated_by_id: null,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
});
|
||||||
|
|
||||||
|
export default apiClient;
|
@ -0,0 +1,51 @@
|
|||||||
|
/**
|
||||||
|
* title: Upload
|
||||||
|
*/
|
||||||
|
import { FormItem } from '@formily/antd';
|
||||||
|
import { APIClientProvider, SchemaComponent, SchemaComponentProvider, Upload } from '@nocobase/client';
|
||||||
|
import React from 'react';
|
||||||
|
import apiClient from './apiClient';
|
||||||
|
|
||||||
|
const schema = {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
input: {
|
||||||
|
type: 'string',
|
||||||
|
title: `编辑模式`,
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Upload.Attachment',
|
||||||
|
'x-component-props': {
|
||||||
|
action: 'attachments:upload',
|
||||||
|
// multiple: true,
|
||||||
|
},
|
||||||
|
'x-reactions': {
|
||||||
|
target: 'read',
|
||||||
|
fulfill: {
|
||||||
|
state: {
|
||||||
|
value: '{{$self.value}}',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
read: {
|
||||||
|
type: 'string',
|
||||||
|
title: `阅读模式`,
|
||||||
|
'x-read-pretty': true,
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Upload.Attachment',
|
||||||
|
'x-component-props': {
|
||||||
|
// multiple: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default () => {
|
||||||
|
return (
|
||||||
|
<APIClientProvider apiClient={apiClient}>
|
||||||
|
<SchemaComponentProvider components={{ Upload, FormItem }}>
|
||||||
|
<SchemaComponent schema={schema} />
|
||||||
|
</SchemaComponentProvider>
|
||||||
|
</APIClientProvider>
|
||||||
|
);
|
||||||
|
};
|
115
packages/client/src/schema-component/antd/upload/demos/demo2.tsx
Normal file
115
packages/client/src/schema-component/antd/upload/demos/demo2.tsx
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
/**
|
||||||
|
* title: Upload
|
||||||
|
*/
|
||||||
|
import { FormItem } from '@formily/antd';
|
||||||
|
import { APIClientProvider, SchemaComponent, SchemaComponentProvider, Upload } from '@nocobase/client';
|
||||||
|
import React from 'react';
|
||||||
|
import apiClient from './apiClient';
|
||||||
|
|
||||||
|
const schema = {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
input: {
|
||||||
|
type: 'object',
|
||||||
|
title: `编辑模式`,
|
||||||
|
default: [
|
||||||
|
{
|
||||||
|
id: 45,
|
||||||
|
title: 's33766399',
|
||||||
|
name: 's33766399',
|
||||||
|
filename: 'cd48dc833ab01aa3959ac39309fc39de.jpg',
|
||||||
|
extname: '.jpg',
|
||||||
|
size: null,
|
||||||
|
mimetype: 'image/jpeg',
|
||||||
|
path: '',
|
||||||
|
meta: {},
|
||||||
|
status: 'uploading',
|
||||||
|
percent: 60,
|
||||||
|
url: 'https://nocobase.oss-cn-beijing.aliyuncs.com/cd48dc833ab01aa3959ac39309fc39de.jpg',
|
||||||
|
created_at: '2021-08-13T15:00:17.423Z',
|
||||||
|
updated_at: '2021-08-13T15:00:17.423Z',
|
||||||
|
created_by_id: null,
|
||||||
|
updated_by_id: null,
|
||||||
|
storage_id: 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 7,
|
||||||
|
title: '简历',
|
||||||
|
filename: 'd9f6ad6669902a9a8a1229d9f362235a.docx',
|
||||||
|
extname: '.docx',
|
||||||
|
size: null,
|
||||||
|
mimetype: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
||||||
|
path: '',
|
||||||
|
meta: {},
|
||||||
|
url: 'https://nocobase.oss-cn-beijing.aliyuncs.com/d9f6ad6669902a9a8a1229d9f362235a.docx',
|
||||||
|
created_at: '2021-09-12T01:22:06.229Z',
|
||||||
|
updated_at: '2021-09-12T01:22:06.229Z',
|
||||||
|
created_by_id: null,
|
||||||
|
updated_by_id: 1,
|
||||||
|
storage_id: 2,
|
||||||
|
t_jh7a28dsfzi: {
|
||||||
|
createdAt: '2021-09-12T01:22:07.886Z',
|
||||||
|
updatedAt: '2021-09-12T01:22:07.886Z',
|
||||||
|
f_xg3mysbjfra: 1,
|
||||||
|
f_gc7ppj0b7n1: 7,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Upload.Attachment',
|
||||||
|
'x-component-props': {
|
||||||
|
action: 'attachments:upload',
|
||||||
|
multiple: true,
|
||||||
|
},
|
||||||
|
'x-reactions': [
|
||||||
|
{
|
||||||
|
target: 'read',
|
||||||
|
fulfill: {
|
||||||
|
state: {
|
||||||
|
value: '{{$self.value}}',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
target: 'read2',
|
||||||
|
fulfill: {
|
||||||
|
state: {
|
||||||
|
value: '{{$self.value}}',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
read: {
|
||||||
|
type: 'object',
|
||||||
|
title: `阅读模式`,
|
||||||
|
'x-read-pretty': true,
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Upload.Attachment',
|
||||||
|
'x-component-props': {
|
||||||
|
multiple: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
read2: {
|
||||||
|
type: 'object',
|
||||||
|
title: `小图预览`,
|
||||||
|
'x-read-pretty': true,
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Upload.Attachment',
|
||||||
|
'x-component-props': {
|
||||||
|
multiple: true,
|
||||||
|
size: 'small',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default () => {
|
||||||
|
return (
|
||||||
|
<APIClientProvider apiClient={apiClient}>
|
||||||
|
<SchemaComponentProvider components={{ Upload, FormItem }}>
|
||||||
|
<SchemaComponent schema={schema} />
|
||||||
|
</SchemaComponentProvider>
|
||||||
|
</APIClientProvider>
|
||||||
|
);
|
||||||
|
};
|
@ -6,3 +6,13 @@ group:
|
|||||||
---
|
---
|
||||||
|
|
||||||
# Upload
|
# Upload
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
### Upload
|
||||||
|
|
||||||
|
<code src="./demos/demo1.tsx" />
|
||||||
|
|
||||||
|
### Upload
|
||||||
|
|
||||||
|
<code src="./demos/demo2.tsx" />
|
||||||
|
@ -0,0 +1 @@
|
|||||||
|
export * from './Upload';
|
@ -0,0 +1,62 @@
|
|||||||
|
export const UPLOAD_PLACEHOLDER = [
|
||||||
|
{
|
||||||
|
ext: /\.docx?$/i,
|
||||||
|
icon: '//img.alicdn.com/tfs/TB1n8jfr1uSBuNjy1XcXXcYjFXa-200-200.png',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ext: /\.pptx?$/i,
|
||||||
|
icon: '//img.alicdn.com/tfs/TB1ItgWr_tYBeNjy1XdXXXXyVXa-200-200.png',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ext: /\.jpe?g$/i,
|
||||||
|
icon: '//img.alicdn.com/tfs/TB1wrT5r9BYBeNjy0FeXXbnmFXa-200-200.png',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ext: /\.pdf$/i,
|
||||||
|
icon: '//img.alicdn.com/tfs/TB1GwD8r9BYBeNjy0FeXXbnmFXa-200-200.png',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ext: /\.png$/i,
|
||||||
|
icon: '//img.alicdn.com/tfs/TB1BHT5r9BYBeNjy0FeXXbnmFXa-200-200.png',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ext: /\.eps$/i,
|
||||||
|
icon: '//img.alicdn.com/tfs/TB1G_iGrVOWBuNjy0FiXXXFxVXa-200-200.png',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ext: /\.ai$/i,
|
||||||
|
icon: '//img.alicdn.com/tfs/TB1B2cVr_tYBeNjy1XdXXXXyVXa-200-200.png',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ext: /\.gif$/i,
|
||||||
|
icon: '//img.alicdn.com/tfs/TB1DTiGrVOWBuNjy0FiXXXFxVXa-200-200.png',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ext: /\.svg$/i,
|
||||||
|
icon: '//img.alicdn.com/tfs/TB1uUm9rY9YBuNjy0FgXXcxcXXa-200-200.png',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ext: /\.xlsx?$/i,
|
||||||
|
icon: '//img.alicdn.com/tfs/TB1any1r1OSBuNjy0FdXXbDnVXa-200-200.png',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ext: /\.psd?$/i,
|
||||||
|
icon: '//img.alicdn.com/tfs/TB1_nu1r1OSBuNjy0FdXXbDnVXa-200-200.png',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ext: /\.(wav|aif|aiff|au|mp1|mp2|mp3|ra|rm|ram|mid|rmi)$/i,
|
||||||
|
icon: '//img.alicdn.com/tfs/TB1jPvwr49YBuNjy0FfXXXIsVXa-200-200.png',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ext: /\.(avi|wmv|mpg|mpeg|vob|dat|3gp|mp4|mkv|rm|rmvb|mov|flv)$/i,
|
||||||
|
icon: '//img.alicdn.com/tfs/TB1FrT5r9BYBeNjy0FeXXbnmFXa-200-200.png',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ext: /\.(zip|rar|arj|z|gz|iso|jar|ace|tar|uue|dmg|pkg|lzh|cab)$/i,
|
||||||
|
icon: '//img.alicdn.com/tfs/TB10jmfr29TBuNjy0FcXXbeiFXa-200-200.png',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ext: /\.[^.]+$/i,
|
||||||
|
icon: '//img.alicdn.com/tfs/TB10.R4r3mTBuNjy1XbXXaMrVXa-200-200.png',
|
||||||
|
},
|
||||||
|
];
|
228
packages/client/src/schema-component/antd/upload/shared.ts
Normal file
228
packages/client/src/schema-component/antd/upload/shared.ts
Normal file
@ -0,0 +1,228 @@
|
|||||||
|
import { Field } from '@formily/core';
|
||||||
|
import { useField } from '@formily/react';
|
||||||
|
import { reaction } from '@formily/reactive';
|
||||||
|
import { isArr, isValid, toArr as toArray } from '@formily/shared';
|
||||||
|
import { useAPIClient } from '@nocobase/client';
|
||||||
|
import { UploadChangeParam } from 'antd/lib/upload';
|
||||||
|
import { UploadFile } from 'antd/lib/upload/interface';
|
||||||
|
import { useEffect } from 'react';
|
||||||
|
import { UPLOAD_PLACEHOLDER } from './placeholder';
|
||||||
|
import type { IUploadProps, UploadProps } from './type';
|
||||||
|
|
||||||
|
export const isImage = (extName: string) => {
|
||||||
|
var reg = /\.(png|jpg|gif|jpeg|webp)$/;
|
||||||
|
return reg.test(extName);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const toMap = (fileList: any) => {
|
||||||
|
if (!fileList) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
if (typeof fileList !== 'object') {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
let list = fileList;
|
||||||
|
if (!Array.isArray(fileList)) {
|
||||||
|
if (Object.keys({ ...fileList }).length === 0) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
list = [fileList];
|
||||||
|
}
|
||||||
|
console.log({ list, fileList });
|
||||||
|
return list.map((item) => {
|
||||||
|
return [item.id || item.uid, toItem(item)];
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const toImages = (fileList) => {
|
||||||
|
if (!fileList) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
if (typeof fileList !== 'object') {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
if (Object.keys(fileList).length === 0) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
let list = fileList;
|
||||||
|
if (!Array.isArray(fileList) && typeof fileList === 'object') {
|
||||||
|
list = [fileList];
|
||||||
|
}
|
||||||
|
return list.map((item) => {
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
title: item.title || item.name,
|
||||||
|
imageUrl: getImageByUrl(item.url, {
|
||||||
|
exclude: ['.png', '.jpg', '.jpeg', '.gif'],
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const toArr = (value) => {
|
||||||
|
if (!isValid(value)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
if (Object.keys(value).length === 0) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return toArray(value);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const testOpts = (ext: RegExp, options: { exclude?: string[]; include?: string[] }) => {
|
||||||
|
if (options && isArr(options.include)) {
|
||||||
|
return options.include.some((url) => ext.test(url));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options && isArr(options.exclude)) {
|
||||||
|
return !options.exclude.some((url) => ext.test(url));
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getImageByUrl = (url: string, options: any) => {
|
||||||
|
for (let i = 0; i < UPLOAD_PLACEHOLDER.length; i++) {
|
||||||
|
if (UPLOAD_PLACEHOLDER[i].ext.test(url) && testOpts(UPLOAD_PLACEHOLDER[i].ext, options)) {
|
||||||
|
return UPLOAD_PLACEHOLDER[i].icon || url;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return url;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getURL = (target: any) => {
|
||||||
|
return target?.['url'] || target?.['downloadURL'] || target?.['imgURL'];
|
||||||
|
};
|
||||||
|
export const getThumbURL = (target: any) => {
|
||||||
|
return target?.['thumbUrl'] || target?.['url'] || target?.['downloadURL'] || target?.['imgURL'];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getErrorMessage = (target: any) => {
|
||||||
|
return target?.errorMessage ||
|
||||||
|
target?.errMsg ||
|
||||||
|
target?.errorMsg ||
|
||||||
|
target?.message ||
|
||||||
|
typeof target?.error === 'string'
|
||||||
|
? target.error
|
||||||
|
: '';
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getState = (target: any) => {
|
||||||
|
if (target?.success === false) return 'error';
|
||||||
|
if (target?.failed === true) return 'error';
|
||||||
|
if (target?.error) return 'error';
|
||||||
|
return target?.state || target?.status;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const normalizeFileList = (fileList: UploadFile[]) => {
|
||||||
|
if (fileList && fileList.length) {
|
||||||
|
return fileList.map((file, index) => {
|
||||||
|
return {
|
||||||
|
...file,
|
||||||
|
uid: file.uid || `${index}`,
|
||||||
|
status: getState(file.response) || getState(file),
|
||||||
|
url: getURL(file) || getURL(file?.response),
|
||||||
|
thumbUrl: getImageByUrl(getThumbURL(file) || getThumbURL(file?.response), {
|
||||||
|
exclude: ['.png', '.jpg', '.jpeg', '.gif'],
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useValidator = (validator: (value: any) => string) => {
|
||||||
|
const field = useField<Field>();
|
||||||
|
useEffect(() => {
|
||||||
|
const dispose = reaction(
|
||||||
|
() => field.value,
|
||||||
|
(value) => {
|
||||||
|
const message = validator(value);
|
||||||
|
field.setFeedback({
|
||||||
|
type: 'error',
|
||||||
|
code: 'UploadError',
|
||||||
|
messages: message ? [message] : [],
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
return () => {
|
||||||
|
dispose();
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useUploadValidator = (serviceErrorMessage = 'Upload Service Error') => {
|
||||||
|
useValidator((value) => {
|
||||||
|
const list = toArr(value);
|
||||||
|
for (let i = 0; i < list.length; i++) {
|
||||||
|
if (list[i]?.status === 'error') {
|
||||||
|
return getErrorMessage(list[i]?.response) || getErrorMessage(list[i]) || serviceErrorMessage;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export function useUploadProps<T extends IUploadProps = UploadProps>({ serviceErrorMessage, ...props }: T) {
|
||||||
|
useUploadValidator(serviceErrorMessage);
|
||||||
|
const onChange = (param: UploadChangeParam<UploadFile>) => {
|
||||||
|
props.onChange?.(normalizeFileList([...param.fileList]));
|
||||||
|
};
|
||||||
|
|
||||||
|
const api = useAPIClient();
|
||||||
|
|
||||||
|
return {
|
||||||
|
...props,
|
||||||
|
customRequest({ action, data, file, filename, headers, onError, onProgress, onSuccess, withCredentials }) {
|
||||||
|
const formData = new FormData();
|
||||||
|
if (data) {
|
||||||
|
Object.keys(data).forEach((key) => {
|
||||||
|
formData.append(key, data[key]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
formData.append(filename, file);
|
||||||
|
api.axios
|
||||||
|
.post(action, formData, {
|
||||||
|
withCredentials,
|
||||||
|
headers,
|
||||||
|
onUploadProgress: ({ total, loaded }) => {
|
||||||
|
onProgress({ percent: Math.round((loaded / total) * 100).toFixed(2) }, file);
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then(({ data }) => {
|
||||||
|
onSuccess(data, file);
|
||||||
|
})
|
||||||
|
.catch(onError);
|
||||||
|
|
||||||
|
return {
|
||||||
|
abort() {
|
||||||
|
console.log('upload progress is aborted.');
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
onChange,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const toItem = (file) => {
|
||||||
|
if (file?.response?.data) {
|
||||||
|
file = file.response.data;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
...file,
|
||||||
|
id: file.id || file.uid,
|
||||||
|
title: file.title || file.name,
|
||||||
|
imageUrl: getImageByUrl(file.url, {
|
||||||
|
exclude: ['.png', '.jpg', '.jpeg', '.gif'],
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const toFileList = (fileList: any) => {
|
||||||
|
return toArr(fileList).map(toItem);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const toValue = (fileList: any) => {
|
||||||
|
return toArr(fileList)
|
||||||
|
.filter((file) => !file.response || file.status === 'done')
|
||||||
|
.map((file) => file?.response?.data || file);
|
||||||
|
};
|
57
packages/client/src/schema-component/antd/upload/style.less
Normal file
57
packages/client/src/schema-component/antd/upload/style.less
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
.nb-file-list {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nb-upload {
|
||||||
|
.ant-upload-list-item.ant-upload-list-item-list-type-picture-card {
|
||||||
|
padding: 3px;
|
||||||
|
}
|
||||||
|
.ant-upload-list-item-thumbnail {
|
||||||
|
img {
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.ant-upload-list-item-actions {
|
||||||
|
left: auto;
|
||||||
|
right: 4px;
|
||||||
|
top: 4px;
|
||||||
|
transform: none;
|
||||||
|
}
|
||||||
|
.ant-upload-list-picture-card .ant-upload-list-item-info {
|
||||||
|
overflow: inherit;
|
||||||
|
}
|
||||||
|
.ant-upload-list-picture-card .ant-upload-list-item-name {
|
||||||
|
display: block;
|
||||||
|
margin-top: 10px;
|
||||||
|
font-size: 13px;
|
||||||
|
color: #636363;
|
||||||
|
}
|
||||||
|
.ant-upload-list-picture-card .ant-upload-list-item:hover .ant-upload-list-item-info::before {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.ant-upload-list-picture-card .ant-upload-list-item-progress {
|
||||||
|
bottom: calc(50% - 11px);
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
.ant-btn {
|
||||||
|
background: rgba(0, 0, 0, 0.5);
|
||||||
|
}
|
||||||
|
.ant-upload-list-picture-card-container {
|
||||||
|
margin-bottom: 28px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.nb-upload-small {
|
||||||
|
.ant-upload-list-picture-card-container {
|
||||||
|
margin: 0 3px 3px 0;
|
||||||
|
height: 32px;
|
||||||
|
width: 32px;
|
||||||
|
}
|
||||||
|
.ant-upload-list-picture-card .ant-upload-list-item-name {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.ant-upload-list-picture .ant-upload-list-item,
|
||||||
|
.ant-upload-list-picture-card .ant-upload-list-item {
|
||||||
|
padding: 1px;
|
||||||
|
}
|
||||||
|
}
|
26
packages/client/src/schema-component/antd/upload/type.d.ts
vendored
Normal file
26
packages/client/src/schema-component/antd/upload/type.d.ts
vendored
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import type { DraggerProps as AntdDraggerProps, UploadProps as AntdUploadProps } from 'antd/lib/upload';
|
||||||
|
import { UploadFile } from 'antd/lib/upload/interface';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
export type UploadProps = Omit<AntdUploadProps, 'onChange'> & {
|
||||||
|
onChange?: (fileList: UploadFile[]) => void;
|
||||||
|
serviceErrorMessage?: string;
|
||||||
|
value?: any;
|
||||||
|
size?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type DraggerProps = Omit<AntdDraggerProps, 'onChange'> & {
|
||||||
|
onChange?: (fileList: UploadFile[]) => void;
|
||||||
|
serviceErrorMessage?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ComposedUpload = React.FC<UploadProps> & {
|
||||||
|
Dragger?: React.FC<DraggerProps>;
|
||||||
|
File?: React.FC<UploadProps>;
|
||||||
|
Attachment?: React.FC<UploadProps>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type IUploadProps = {
|
||||||
|
serviceErrorMessage?: string;
|
||||||
|
onChange?: (...args: any) => void;
|
||||||
|
};
|
35
yarn.lock
35
yarn.lock
@ -6270,6 +6270,11 @@ execa@^5.0.0:
|
|||||||
signal-exit "^3.0.3"
|
signal-exit "^3.0.3"
|
||||||
strip-final-newline "^2.0.0"
|
strip-final-newline "^2.0.0"
|
||||||
|
|
||||||
|
exenv@^1.2.0:
|
||||||
|
version "1.2.2"
|
||||||
|
resolved "https://registry.npmmirror.com/exenv/download/exenv-1.2.2.tgz#2ae78e85d9894158670b03d47bec1f03bd91bb9d"
|
||||||
|
integrity sha1-KueOhdmJQVhnCwPUe+wfA72Ru50=
|
||||||
|
|
||||||
exit@^0.1.2:
|
exit@^0.1.2:
|
||||||
version "0.1.2"
|
version "0.1.2"
|
||||||
resolved "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c"
|
resolved "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c"
|
||||||
@ -6443,6 +6448,11 @@ file-entry-cache@^6.0.1:
|
|||||||
dependencies:
|
dependencies:
|
||||||
flat-cache "^3.0.4"
|
flat-cache "^3.0.4"
|
||||||
|
|
||||||
|
file-saver@^2.0.5:
|
||||||
|
version "2.0.5"
|
||||||
|
resolved "https://registry.npmmirror.com/file-saver/download/file-saver-2.0.5.tgz#d61cfe2ce059f414d899e9dd6d4107ee25670c38"
|
||||||
|
integrity sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA==
|
||||||
|
|
||||||
file-type@^3.3.0:
|
file-type@^3.3.0:
|
||||||
version "3.9.0"
|
version "3.9.0"
|
||||||
resolved "https://registry.npmjs.org/file-type/-/file-type-3.9.0.tgz#257a078384d1db8087bc449d107d52a52672b9e9"
|
resolved "https://registry.npmjs.org/file-type/-/file-type-3.9.0.tgz#257a078384d1db8087bc449d107d52a52672b9e9"
|
||||||
@ -12343,6 +12353,14 @@ react-i18next@^11.15.1:
|
|||||||
html-escaper "^2.0.2"
|
html-escaper "^2.0.2"
|
||||||
html-parse-stringify "^3.0.1"
|
html-parse-stringify "^3.0.1"
|
||||||
|
|
||||||
|
react-image-lightbox@^5.1.4:
|
||||||
|
version "5.1.4"
|
||||||
|
resolved "https://registry.nlark.com/react-image-lightbox/download/react-image-lightbox-5.1.4.tgz#5b847dcb79e9efdf9d7cd5621a92e0f156d2cf30"
|
||||||
|
integrity sha1-W4R9y3np79+dfNViGpLg8VbSzzA=
|
||||||
|
dependencies:
|
||||||
|
prop-types "^15.7.2"
|
||||||
|
react-modal "^3.11.1"
|
||||||
|
|
||||||
react-is@^16.12.0, react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.1, react-is@^16.8.4:
|
react-is@^16.12.0, react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.1, react-is@^16.8.4:
|
||||||
version "16.13.1"
|
version "16.13.1"
|
||||||
resolved "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
|
resolved "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
|
||||||
@ -12353,6 +12371,21 @@ react-is@^17.0.1:
|
|||||||
resolved "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0"
|
resolved "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0"
|
||||||
integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==
|
integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==
|
||||||
|
|
||||||
|
react-lifecycles-compat@^3.0.0:
|
||||||
|
version "3.0.4"
|
||||||
|
resolved "https://registry.npmmirror.com/react-lifecycles-compat/download/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362"
|
||||||
|
integrity sha1-TxonOv38jzSIqMUWv9p4+HI1I2I=
|
||||||
|
|
||||||
|
react-modal@^3.11.1:
|
||||||
|
version "3.14.4"
|
||||||
|
resolved "https://registry.npmmirror.com/react-modal/download/react-modal-3.14.4.tgz#2ca7e8e9a180955e5c9508c228b73167c1e6f6a3"
|
||||||
|
integrity sha512-8surmulejafYCH9wfUmFyj4UfbSJwjcgbS9gf3oOItu4Hwd6ivJyVBETI0yHRhpJKCLZMUtnhzk76wXTsNL6Qg==
|
||||||
|
dependencies:
|
||||||
|
exenv "^1.2.0"
|
||||||
|
prop-types "^15.7.2"
|
||||||
|
react-lifecycles-compat "^3.0.0"
|
||||||
|
warning "^4.0.3"
|
||||||
|
|
||||||
react-refresh@0.10.0:
|
react-refresh@0.10.0:
|
||||||
version "0.10.0"
|
version "0.10.0"
|
||||||
resolved "https://registry.npmjs.org/react-refresh/-/react-refresh-0.10.0.tgz#2f536c9660c0b9b1d500684d9e52a65e7404f7e3"
|
resolved "https://registry.npmjs.org/react-refresh/-/react-refresh-0.10.0.tgz#2f536c9660c0b9b1d500684d9e52a65e7404f7e3"
|
||||||
@ -14934,7 +14967,7 @@ walker@^1.0.7, walker@~1.0.5:
|
|||||||
dependencies:
|
dependencies:
|
||||||
makeerror "1.0.12"
|
makeerror "1.0.12"
|
||||||
|
|
||||||
warning@^4.0.1:
|
warning@^4.0.1, warning@^4.0.3:
|
||||||
version "4.0.3"
|
version "4.0.3"
|
||||||
resolved "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz#16e9e077eb8a86d6af7d64aa1e05fd85b4678ca3"
|
resolved "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz#16e9e077eb8a86d6af7d64aa1e05fd85b4678ca3"
|
||||||
integrity sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==
|
integrity sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==
|
||||||
|
Loading…
Reference in New Issue
Block a user