mirror of
https://gitee.com/ant-design/ant-design.git
synced 2024-11-30 19:19:26 +08:00
refactor(upload): rewrite with hooks (#26196)
* refactor(upload): rewrite with hooks * refactor: optimize code style; chore(dragger): add forwardRef; * fix: lint * fix: trigger ci * fix: lint * chore: add test case
This commit is contained in:
parent
517d2eeccb
commit
ee50fe7952
@ -4,12 +4,15 @@ import { UploadProps } from './interface';
|
||||
|
||||
export type DraggerProps = UploadProps & { height?: number };
|
||||
|
||||
// stick class comoponent to avoid React ref warning inside Form
|
||||
// https://github.com/ant-design/ant-design/issues/18707
|
||||
// eslint-disable-next-line react/prefer-stateless-function
|
||||
export default class Dragger extends React.Component<DraggerProps, any> {
|
||||
render() {
|
||||
const { style, height, ...restProps } = this.props;
|
||||
return <Upload {...restProps} type="drag" style={{ ...style, height }} />;
|
||||
}
|
||||
}
|
||||
const InternalDragger: React.ForwardRefRenderFunction<unknown, DraggerProps> = (
|
||||
{ style, height, ...restProps },
|
||||
ref,
|
||||
) => {
|
||||
return <Upload ref={ref} {...restProps} type="drag" style={{ ...style, height }} />;
|
||||
};
|
||||
|
||||
const Dragger = React.forwardRef(InternalDragger) as React.FC<DraggerProps>;
|
||||
|
||||
Dragger.displayName = 'Dragger';
|
||||
|
||||
export default Dragger;
|
||||
|
@ -6,7 +6,6 @@ import UploadList from './UploadList';
|
||||
import {
|
||||
RcFile,
|
||||
UploadProps,
|
||||
UploadState,
|
||||
UploadFile,
|
||||
UploadLocale,
|
||||
UploadChangeParam,
|
||||
@ -16,72 +15,76 @@ import {
|
||||
import { T, fileToObject, getFileItem, removeFileItem } from './utils';
|
||||
import LocaleReceiver from '../locale-provider/LocaleReceiver';
|
||||
import defaultLocale from '../locale/default';
|
||||
import { ConfigConsumer, ConfigConsumerProps } from '../config-provider';
|
||||
import { ConfigContext } from '../config-provider';
|
||||
import devWarning from '../_util/devWarning';
|
||||
import useSyncState from './hooks/useSyncState';
|
||||
import useForceUpdate from './hooks/useForceUpdate';
|
||||
|
||||
export { UploadProps };
|
||||
|
||||
class Upload extends React.Component<UploadProps, UploadState> {
|
||||
static Dragger: typeof Dragger;
|
||||
const InternalUpload: React.ForwardRefRenderFunction<unknown, UploadProps> = (props, ref) => {
|
||||
const {
|
||||
fileList: fileListProp,
|
||||
defaultFileList,
|
||||
onRemove,
|
||||
showUploadList,
|
||||
listType,
|
||||
onPreview,
|
||||
onDownload,
|
||||
previewFile,
|
||||
disabled,
|
||||
locale: propLocale,
|
||||
iconRender,
|
||||
isImageUrl,
|
||||
progress,
|
||||
prefixCls: customizePrefixCls,
|
||||
className,
|
||||
type,
|
||||
children,
|
||||
style,
|
||||
} = props;
|
||||
|
||||
static defaultProps = {
|
||||
type: 'select' as UploadType,
|
||||
multiple: false,
|
||||
action: '',
|
||||
data: {},
|
||||
accept: '',
|
||||
beforeUpload: T,
|
||||
showUploadList: true,
|
||||
listType: 'text' as UploadListType, // or picture
|
||||
className: '',
|
||||
disabled: false,
|
||||
supportServerRender: true,
|
||||
};
|
||||
const [getFileList, setFileList] = useSyncState<Array<UploadFile>>(
|
||||
fileListProp || defaultFileList || [],
|
||||
);
|
||||
const [dragState, setDragState] = React.useState<string>('drop');
|
||||
|
||||
static getDerivedStateFromProps(nextProps: UploadProps) {
|
||||
if ('fileList' in nextProps) {
|
||||
return {
|
||||
fileList: nextProps.fileList || [],
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
recentUploadStatus: boolean | PromiseLike<any>;
|
||||
|
||||
progressTimer: any;
|
||||
|
||||
upload: any;
|
||||
|
||||
constructor(props: UploadProps) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
fileList: props.fileList || props.defaultFileList || [],
|
||||
dragState: 'drop',
|
||||
};
|
||||
const upload = React.useRef<any>();
|
||||
|
||||
React.useEffect(() => {
|
||||
setFileList(fileListProp || defaultFileList || []);
|
||||
devWarning(
|
||||
'fileList' in props || !('value' in props),
|
||||
'Upload',
|
||||
'`value` is not a valid prop, do you mean `fileList`?',
|
||||
);
|
||||
}
|
||||
}, []);
|
||||
|
||||
componentWillUnmount() {
|
||||
this.clearProgressTimer();
|
||||
}
|
||||
React.useEffect(() => {
|
||||
if ('fileList' in props) {
|
||||
setFileList(fileListProp || []);
|
||||
}
|
||||
}, [fileListProp]);
|
||||
|
||||
saveUpload = (node: any) => {
|
||||
this.upload = node;
|
||||
const onChange = (info: UploadChangeParam) => {
|
||||
if (!('fileList' in props)) {
|
||||
setFileList(info.fileList);
|
||||
}
|
||||
|
||||
const { onChange: onChangeProp } = props;
|
||||
if (onChangeProp) {
|
||||
onChangeProp({
|
||||
...info,
|
||||
fileList: [...info.fileList],
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
onStart = (file: RcFile) => {
|
||||
const { fileList } = this.state;
|
||||
const onStart = (file: RcFile) => {
|
||||
const targetItem = fileToObject(file);
|
||||
targetItem.status = 'uploading';
|
||||
|
||||
const nextFileList = fileList.concat();
|
||||
const nextFileList = getFileList().concat();
|
||||
|
||||
const fileIndex = nextFileList.findIndex(({ uid }: UploadFile) => uid === targetItem.uid);
|
||||
if (fileIndex === -1) {
|
||||
@ -90,14 +93,13 @@ class Upload extends React.Component<UploadProps, UploadState> {
|
||||
nextFileList[fileIndex] = targetItem;
|
||||
}
|
||||
|
||||
this.onChange({
|
||||
onChange({
|
||||
file: targetItem,
|
||||
fileList: nextFileList,
|
||||
});
|
||||
};
|
||||
|
||||
onSuccess = (response: any, file: UploadFile, xhr: any) => {
|
||||
this.clearProgressTimer();
|
||||
const onSuccess = (response: any, file: UploadFile, xhr: any) => {
|
||||
try {
|
||||
if (typeof response === 'string') {
|
||||
response = JSON.parse(response);
|
||||
@ -105,8 +107,7 @@ class Upload extends React.Component<UploadProps, UploadState> {
|
||||
} catch (e) {
|
||||
/* do nothing */
|
||||
}
|
||||
const { fileList } = this.state;
|
||||
const targetItem = getFileItem(file, fileList);
|
||||
const targetItem = getFileItem(file, getFileList());
|
||||
// removed
|
||||
if (!targetItem) {
|
||||
return;
|
||||
@ -114,31 +115,28 @@ class Upload extends React.Component<UploadProps, UploadState> {
|
||||
targetItem.status = 'done';
|
||||
targetItem.response = response;
|
||||
targetItem.xhr = xhr;
|
||||
this.onChange({
|
||||
onChange({
|
||||
file: { ...targetItem },
|
||||
fileList,
|
||||
fileList: getFileList().concat(),
|
||||
});
|
||||
};
|
||||
|
||||
onProgress = (e: { percent: number }, file: UploadFile) => {
|
||||
const { fileList } = this.state;
|
||||
const targetItem = getFileItem(file, fileList);
|
||||
const onProgress = (e: { percent: number }, file: UploadFile) => {
|
||||
const targetItem = getFileItem(file, getFileList());
|
||||
// removed
|
||||
if (!targetItem) {
|
||||
return;
|
||||
}
|
||||
targetItem.percent = e.percent;
|
||||
this.onChange({
|
||||
onChange({
|
||||
event: e,
|
||||
file: { ...targetItem },
|
||||
fileList,
|
||||
fileList: getFileList().concat(),
|
||||
});
|
||||
};
|
||||
|
||||
onError = (error: Error, response: any, file: UploadFile) => {
|
||||
this.clearProgressTimer();
|
||||
const { fileList } = this.state;
|
||||
const targetItem = getFileItem(file, fileList);
|
||||
const onError = (error: Error, response: any, file: UploadFile) => {
|
||||
const targetItem = getFileItem(file, getFileList());
|
||||
// removed
|
||||
if (!targetItem) {
|
||||
return;
|
||||
@ -146,32 +144,28 @@ class Upload extends React.Component<UploadProps, UploadState> {
|
||||
targetItem.error = error;
|
||||
targetItem.response = response;
|
||||
targetItem.status = 'error';
|
||||
this.onChange({
|
||||
onChange({
|
||||
file: { ...targetItem },
|
||||
fileList,
|
||||
fileList: getFileList().concat(),
|
||||
});
|
||||
};
|
||||
|
||||
handleRemove = (file: UploadFile) => {
|
||||
const { onRemove } = this.props;
|
||||
const { fileList } = this.state;
|
||||
|
||||
const handleRemove = (file: UploadFile) => {
|
||||
Promise.resolve(typeof onRemove === 'function' ? onRemove(file) : onRemove).then(ret => {
|
||||
// Prevent removing file
|
||||
if (ret === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
const removedFileList = removeFileItem(file, fileList);
|
||||
const removedFileList = removeFileItem(file, getFileList());
|
||||
|
||||
if (removedFileList) {
|
||||
file.status = 'removed';
|
||||
|
||||
if (this.upload) {
|
||||
this.upload.abort(file);
|
||||
if (upload.current) {
|
||||
upload.current.abort(file);
|
||||
}
|
||||
|
||||
this.onChange({
|
||||
onChange({
|
||||
file,
|
||||
fileList: removedFileList,
|
||||
});
|
||||
@ -179,43 +173,28 @@ class Upload extends React.Component<UploadProps, UploadState> {
|
||||
});
|
||||
};
|
||||
|
||||
onChange = (info: UploadChangeParam) => {
|
||||
if (!('fileList' in this.props)) {
|
||||
this.setState({ fileList: info.fileList });
|
||||
}
|
||||
|
||||
const { onChange } = this.props;
|
||||
if (onChange) {
|
||||
onChange({
|
||||
...info,
|
||||
fileList: [...info.fileList],
|
||||
});
|
||||
}
|
||||
const onFileDrop = (e: React.DragEvent<HTMLDivElement>) => {
|
||||
setDragState(e.type);
|
||||
};
|
||||
|
||||
onFileDrop = (e: React.DragEvent<HTMLDivElement>) => {
|
||||
this.setState({
|
||||
dragState: e.type,
|
||||
});
|
||||
};
|
||||
|
||||
beforeUpload = (file: RcFile, fileList: RcFile[]) => {
|
||||
const { beforeUpload } = this.props;
|
||||
const { fileList: stateFileList } = this.state;
|
||||
if (!beforeUpload) {
|
||||
const beforeUpload = (file: RcFile, fileListArgs: RcFile[]) => {
|
||||
const { beforeUpload: beforeUploadProp } = props;
|
||||
if (!beforeUploadProp) {
|
||||
return true;
|
||||
}
|
||||
const result = beforeUpload(file, fileList);
|
||||
const result = beforeUploadProp(file, fileListArgs);
|
||||
if (result === false) {
|
||||
// Get unique file list
|
||||
const uniqueList: UploadFile<any>[] = [];
|
||||
stateFileList.concat(fileList.map(fileToObject)).forEach(f => {
|
||||
if (uniqueList.every(uf => uf.uid !== f.uid)) {
|
||||
uniqueList.push(f);
|
||||
}
|
||||
});
|
||||
getFileList()
|
||||
.concat(fileListArgs.map(fileToObject))
|
||||
.forEach(f => {
|
||||
if (uniqueList.every(uf => uf.uid !== f.uid)) {
|
||||
uniqueList.push(f);
|
||||
}
|
||||
});
|
||||
|
||||
this.onChange({
|
||||
onChange({
|
||||
file,
|
||||
fileList: uniqueList,
|
||||
});
|
||||
@ -226,24 +205,19 @@ class Upload extends React.Component<UploadProps, UploadState> {
|
||||
}
|
||||
return true;
|
||||
};
|
||||
// Test needs
|
||||
const forceUpdate = useForceUpdate();
|
||||
React.useImperativeHandle(ref, () => ({
|
||||
onStart,
|
||||
onSuccess,
|
||||
onProgress,
|
||||
onError,
|
||||
fileList: getFileList(),
|
||||
upload: upload.current,
|
||||
forceUpdate,
|
||||
}));
|
||||
|
||||
clearProgressTimer() {
|
||||
clearInterval(this.progressTimer);
|
||||
}
|
||||
|
||||
renderUploadList = (locale: UploadLocale) => {
|
||||
const {
|
||||
showUploadList,
|
||||
listType,
|
||||
onPreview,
|
||||
onDownload,
|
||||
previewFile,
|
||||
disabled,
|
||||
locale: propLocale,
|
||||
iconRender,
|
||||
isImageUrl,
|
||||
progress,
|
||||
} = this.props;
|
||||
const renderUploadList = (locale: UploadLocale) => {
|
||||
const {
|
||||
showRemoveIcon,
|
||||
showPreviewIcon,
|
||||
@ -251,15 +225,14 @@ class Upload extends React.Component<UploadProps, UploadState> {
|
||||
removeIcon,
|
||||
downloadIcon,
|
||||
} = showUploadList as any;
|
||||
const { fileList } = this.state;
|
||||
return (
|
||||
<UploadList
|
||||
listType={listType}
|
||||
items={fileList}
|
||||
items={getFileList()}
|
||||
previewFile={previewFile}
|
||||
onPreview={onPreview}
|
||||
onDownload={onDownload}
|
||||
onRemove={this.handleRemove}
|
||||
onRemove={handleRemove}
|
||||
showRemoveIcon={!disabled && showRemoveIcon}
|
||||
showPreviewIcon={showPreviewIcon}
|
||||
showDownloadIcon={showDownloadIcon}
|
||||
@ -273,111 +246,120 @@ class Upload extends React.Component<UploadProps, UploadState> {
|
||||
);
|
||||
};
|
||||
|
||||
renderUpload = ({ getPrefixCls, direction }: ConfigConsumerProps) => {
|
||||
const {
|
||||
prefixCls: customizePrefixCls,
|
||||
className,
|
||||
showUploadList,
|
||||
listType,
|
||||
type,
|
||||
disabled,
|
||||
children,
|
||||
style,
|
||||
} = this.props;
|
||||
const { fileList, dragState } = this.state;
|
||||
const { getPrefixCls, direction } = React.useContext(ConfigContext);
|
||||
|
||||
const prefixCls = getPrefixCls('upload', customizePrefixCls);
|
||||
const prefixCls = getPrefixCls('upload', customizePrefixCls);
|
||||
|
||||
const rcUploadProps = {
|
||||
onStart: this.onStart,
|
||||
onError: this.onError,
|
||||
onProgress: this.onProgress,
|
||||
onSuccess: this.onSuccess,
|
||||
...this.props,
|
||||
const rcUploadProps = {
|
||||
onStart,
|
||||
onError,
|
||||
onProgress,
|
||||
onSuccess,
|
||||
...props,
|
||||
prefixCls,
|
||||
beforeUpload,
|
||||
};
|
||||
|
||||
delete rcUploadProps.className;
|
||||
delete rcUploadProps.style;
|
||||
|
||||
// Remove id to avoid open by label when trigger is hidden
|
||||
// !children: https://github.com/ant-design/ant-design/issues/14298
|
||||
// disabled: https://github.com/ant-design/ant-design/issues/16478
|
||||
// https://github.com/ant-design/ant-design/issues/24197
|
||||
if (!children || disabled) {
|
||||
delete rcUploadProps.id;
|
||||
}
|
||||
|
||||
const uploadList = showUploadList ? (
|
||||
<LocaleReceiver componentName="Upload" defaultLocale={defaultLocale.Upload}>
|
||||
{renderUploadList}
|
||||
</LocaleReceiver>
|
||||
) : null;
|
||||
|
||||
if (type === 'drag') {
|
||||
const dragCls = classNames(
|
||||
prefixCls,
|
||||
beforeUpload: this.beforeUpload,
|
||||
};
|
||||
|
||||
delete rcUploadProps.className;
|
||||
delete rcUploadProps.style;
|
||||
|
||||
// Remove id to avoid open by label when trigger is hidden
|
||||
// !children: https://github.com/ant-design/ant-design/issues/14298
|
||||
// disabled: https://github.com/ant-design/ant-design/issues/16478
|
||||
// https://github.com/ant-design/ant-design/issues/24197
|
||||
if (!children || disabled) {
|
||||
delete rcUploadProps.id;
|
||||
}
|
||||
|
||||
const uploadList = showUploadList ? (
|
||||
<LocaleReceiver componentName="Upload" defaultLocale={defaultLocale.Upload}>
|
||||
{this.renderUploadList}
|
||||
</LocaleReceiver>
|
||||
) : null;
|
||||
|
||||
if (type === 'drag') {
|
||||
const dragCls = classNames(
|
||||
prefixCls,
|
||||
{
|
||||
[`${prefixCls}-drag`]: true,
|
||||
[`${prefixCls}-drag-uploading`]: fileList.some(file => file.status === 'uploading'),
|
||||
[`${prefixCls}-drag-hover`]: dragState === 'dragover',
|
||||
[`${prefixCls}-disabled`]: disabled,
|
||||
[`${prefixCls}-rtl`]: direction === 'rtl',
|
||||
},
|
||||
className,
|
||||
);
|
||||
return (
|
||||
<span>
|
||||
<div
|
||||
className={dragCls}
|
||||
onDrop={this.onFileDrop}
|
||||
onDragOver={this.onFileDrop}
|
||||
onDragLeave={this.onFileDrop}
|
||||
style={style}
|
||||
>
|
||||
<RcUpload {...rcUploadProps} ref={this.saveUpload} className={`${prefixCls}-btn`}>
|
||||
<div className={`${prefixCls}-drag-container`}>{children}</div>
|
||||
</RcUpload>
|
||||
</div>
|
||||
{uploadList}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
const uploadButtonCls = classNames(prefixCls, {
|
||||
[`${prefixCls}-select`]: true,
|
||||
[`${prefixCls}-select-${listType}`]: true,
|
||||
[`${prefixCls}-disabled`]: disabled,
|
||||
[`${prefixCls}-rtl`]: direction === 'rtl',
|
||||
});
|
||||
|
||||
const uploadButton = (
|
||||
<div className={uploadButtonCls} style={children ? undefined : { display: 'none' }}>
|
||||
<RcUpload {...rcUploadProps} ref={this.saveUpload} />
|
||||
</div>
|
||||
{
|
||||
[`${prefixCls}-drag`]: true,
|
||||
[`${prefixCls}-drag-uploading`]: getFileList().some(file => file.status === 'uploading'),
|
||||
[`${prefixCls}-drag-hover`]: dragState === 'dragover',
|
||||
[`${prefixCls}-disabled`]: disabled,
|
||||
[`${prefixCls}-rtl`]: direction === 'rtl',
|
||||
},
|
||||
className,
|
||||
);
|
||||
|
||||
if (listType === 'picture-card') {
|
||||
return (
|
||||
<span className={classNames(className, `${prefixCls}-picture-card-wrapper`)}>
|
||||
{uploadList}
|
||||
{uploadButton}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<span className={className}>
|
||||
{uploadButton}
|
||||
<span>
|
||||
<div
|
||||
className={dragCls}
|
||||
onDrop={onFileDrop}
|
||||
onDragOver={onFileDrop}
|
||||
onDragLeave={onFileDrop}
|
||||
style={style}
|
||||
>
|
||||
<RcUpload {...rcUploadProps} ref={upload} className={`${prefixCls}-btn`}>
|
||||
<div className={`${prefixCls}-drag-container`}>{children}</div>
|
||||
</RcUpload>
|
||||
</div>
|
||||
{uploadList}
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
render() {
|
||||
return <ConfigConsumer>{this.renderUpload}</ConfigConsumer>;
|
||||
}
|
||||
|
||||
const uploadButtonCls = classNames(prefixCls, {
|
||||
[`${prefixCls}-select`]: true,
|
||||
[`${prefixCls}-select-${listType}`]: true,
|
||||
[`${prefixCls}-disabled`]: disabled,
|
||||
[`${prefixCls}-rtl`]: direction === 'rtl',
|
||||
});
|
||||
|
||||
const uploadButton = (
|
||||
<div className={uploadButtonCls} style={children ? undefined : { display: 'none' }}>
|
||||
<RcUpload {...rcUploadProps} ref={upload} />
|
||||
</div>
|
||||
);
|
||||
|
||||
if (listType === 'picture-card') {
|
||||
return (
|
||||
<span className={classNames(className, `${prefixCls}-picture-card-wrapper`)}>
|
||||
{uploadList}
|
||||
{uploadButton}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<span className={className}>
|
||||
{uploadButton}
|
||||
{uploadList}
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
interface CompoundedComponent
|
||||
extends React.ForwardRefExoticComponent<UploadProps & React.RefAttributes<any>> {
|
||||
Dragger: typeof Dragger;
|
||||
}
|
||||
|
||||
const Upload = React.forwardRef<unknown, UploadProps>(InternalUpload) as CompoundedComponent;
|
||||
|
||||
Upload.Dragger = Dragger;
|
||||
|
||||
Upload.displayName = 'Upload';
|
||||
|
||||
Upload.defaultProps = {
|
||||
type: 'select' as UploadType,
|
||||
multiple: false,
|
||||
action: '',
|
||||
data: {},
|
||||
accept: '',
|
||||
beforeUpload: T,
|
||||
showUploadList: true,
|
||||
listType: 'text' as UploadListType, // or picture
|
||||
className: '',
|
||||
disabled: false,
|
||||
supportServerRender: true,
|
||||
};
|
||||
|
||||
export default Upload;
|
||||
|
@ -14,25 +14,34 @@ import { UploadListProps, UploadFile, UploadListType } from './interface';
|
||||
import { previewImage, isImageUrl } from './utils';
|
||||
import Tooltip from '../tooltip';
|
||||
import Progress from '../progress';
|
||||
import { ConfigConsumer, ConfigConsumerProps } from '../config-provider';
|
||||
import { ConfigContext } from '../config-provider';
|
||||
import Button, { ButtonProps } from '../button';
|
||||
import useForceUpdate from './hooks/useForceUpdate';
|
||||
|
||||
export default class UploadList extends React.Component<UploadListProps, any> {
|
||||
static defaultProps = {
|
||||
listType: 'text' as UploadListType, // or picture
|
||||
progress: {
|
||||
strokeWidth: 2,
|
||||
showInfo: false,
|
||||
},
|
||||
showRemoveIcon: true,
|
||||
showDownloadIcon: false,
|
||||
showPreviewIcon: true,
|
||||
previewFile: previewImage,
|
||||
isImageUrl,
|
||||
};
|
||||
const InternalUploadList: React.ForwardRefRenderFunction<unknown, UploadListProps> = (
|
||||
{
|
||||
listType,
|
||||
previewFile,
|
||||
onPreview,
|
||||
onDownload,
|
||||
onRemove,
|
||||
locale,
|
||||
iconRender,
|
||||
isImageUrl: isImgUrl,
|
||||
prefixCls: customizePrefixCls,
|
||||
items = [],
|
||||
showPreviewIcon,
|
||||
showRemoveIcon,
|
||||
showDownloadIcon,
|
||||
removeIcon: customRemoveIcon,
|
||||
downloadIcon: customDownloadIcon,
|
||||
progress: progressProps,
|
||||
},
|
||||
ref,
|
||||
) => {
|
||||
const forceUpdate = useForceUpdate();
|
||||
|
||||
componentDidUpdate() {
|
||||
const { listType, items, previewFile } = this.props;
|
||||
React.useEffect(() => {
|
||||
if (listType !== 'picture' && listType !== 'picture-card') {
|
||||
return;
|
||||
}
|
||||
@ -52,14 +61,13 @@ export default class UploadList extends React.Component<UploadListProps, any> {
|
||||
previewFile(file.originFileObj as File).then((previewDataUrl: string) => {
|
||||
// Need append '' to avoid dead loop
|
||||
file.thumbUrl = previewDataUrl || '';
|
||||
this.forceUpdate();
|
||||
forceUpdate();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}, [listType, items, previewFile]);
|
||||
|
||||
handlePreview = (file: UploadFile, e: React.SyntheticEvent<HTMLElement>) => {
|
||||
const { onPreview } = this.props;
|
||||
const handlePreview = (file: UploadFile, e: React.SyntheticEvent<HTMLElement>) => {
|
||||
if (!onPreview) {
|
||||
return;
|
||||
}
|
||||
@ -67,8 +75,7 @@ export default class UploadList extends React.Component<UploadListProps, any> {
|
||||
return onPreview(file);
|
||||
};
|
||||
|
||||
handleDownload = (file: UploadFile) => {
|
||||
const { onDownload } = this.props;
|
||||
const handleDownload = (file: UploadFile) => {
|
||||
if (typeof onDownload === 'function') {
|
||||
onDownload(file);
|
||||
} else if (file.url) {
|
||||
@ -76,15 +83,13 @@ export default class UploadList extends React.Component<UploadListProps, any> {
|
||||
}
|
||||
};
|
||||
|
||||
handleClose = (file: UploadFile) => {
|
||||
const { onRemove } = this.props;
|
||||
const handleClose = (file: UploadFile) => {
|
||||
if (onRemove) {
|
||||
onRemove(file);
|
||||
}
|
||||
};
|
||||
|
||||
handleIconRender = (file: UploadFile) => {
|
||||
const { listType, locale, iconRender, isImageUrl: isImgUrl } = this.props;
|
||||
const handleIconRender = (file: UploadFile) => {
|
||||
if (iconRender) {
|
||||
return iconRender(file, listType);
|
||||
}
|
||||
@ -99,7 +104,7 @@ export default class UploadList extends React.Component<UploadListProps, any> {
|
||||
return icon;
|
||||
};
|
||||
|
||||
handleActionIconRender = (
|
||||
const handleActionIconRender = (
|
||||
customIcon: React.ReactNode,
|
||||
callback: () => void,
|
||||
prefixCls: string,
|
||||
@ -132,223 +137,229 @@ export default class UploadList extends React.Component<UploadListProps, any> {
|
||||
);
|
||||
};
|
||||
|
||||
renderUploadList = ({ getPrefixCls, direction }: ConfigConsumerProps) => {
|
||||
const {
|
||||
prefixCls: customizePrefixCls,
|
||||
items = [],
|
||||
listType,
|
||||
showPreviewIcon,
|
||||
showRemoveIcon,
|
||||
showDownloadIcon,
|
||||
removeIcon: customRemoveIcon,
|
||||
downloadIcon: customDownloadIcon,
|
||||
locale,
|
||||
progress: progressProps,
|
||||
isImageUrl: isImgUrl,
|
||||
} = this.props;
|
||||
const prefixCls = getPrefixCls('upload', customizePrefixCls);
|
||||
const list = items.map(file => {
|
||||
let progress;
|
||||
const iconNode = this.handleIconRender(file);
|
||||
let icon = <div className={`${prefixCls}-text-icon`}>{iconNode}</div>;
|
||||
if (listType === 'picture' || listType === 'picture-card') {
|
||||
if (file.status === 'uploading' || (!file.thumbUrl && !file.url)) {
|
||||
const uploadingClassName = classNames({
|
||||
[`${prefixCls}-list-item-thumbnail`]: true,
|
||||
[`${prefixCls}-list-item-file`]: file.status !== 'uploading',
|
||||
});
|
||||
icon = <div className={uploadingClassName}>{iconNode}</div>;
|
||||
} else {
|
||||
const thumbnail =
|
||||
isImgUrl && isImgUrl(file) ? (
|
||||
<img
|
||||
src={file.thumbUrl || file.url}
|
||||
alt={file.name}
|
||||
className={`${prefixCls}-list-item-image`}
|
||||
/>
|
||||
) : (
|
||||
iconNode
|
||||
);
|
||||
const aClassName = classNames({
|
||||
[`${prefixCls}-list-item-thumbnail`]: true,
|
||||
[`${prefixCls}-list-item-file`]: isImgUrl && !isImgUrl(file),
|
||||
});
|
||||
icon = (
|
||||
<a
|
||||
className={aClassName}
|
||||
onClick={e => this.handlePreview(file, e)}
|
||||
href={file.url || file.thumbUrl}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{thumbnail}
|
||||
</a>
|
||||
// Test needs
|
||||
React.useImperativeHandle(ref, () => ({
|
||||
handlePreview,
|
||||
handleDownload,
|
||||
}));
|
||||
|
||||
const { getPrefixCls, direction } = React.useContext(ConfigContext);
|
||||
|
||||
const prefixCls = getPrefixCls('upload', customizePrefixCls);
|
||||
const list = items.map(file => {
|
||||
let progress;
|
||||
const iconNode = handleIconRender(file);
|
||||
let icon = <div className={`${prefixCls}-text-icon`}>{iconNode}</div>;
|
||||
if (listType === 'picture' || listType === 'picture-card') {
|
||||
if (file.status === 'uploading' || (!file.thumbUrl && !file.url)) {
|
||||
const uploadingClassName = classNames({
|
||||
[`${prefixCls}-list-item-thumbnail`]: true,
|
||||
[`${prefixCls}-list-item-file`]: file.status !== 'uploading',
|
||||
});
|
||||
icon = <div className={uploadingClassName}>{iconNode}</div>;
|
||||
} else {
|
||||
const thumbnail =
|
||||
isImgUrl && isImgUrl(file) ? (
|
||||
<img
|
||||
src={file.thumbUrl || file.url}
|
||||
alt={file.name}
|
||||
className={`${prefixCls}-list-item-image`}
|
||||
/>
|
||||
) : (
|
||||
iconNode
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (file.status === 'uploading') {
|
||||
// show loading icon if upload progress listener is disabled
|
||||
const loadingProgress =
|
||||
'percent' in file ? (
|
||||
<Progress {...progressProps} type="line" percent={file.percent} />
|
||||
) : null;
|
||||
|
||||
progress = (
|
||||
<div className={`${prefixCls}-list-item-progress`} key="progress">
|
||||
{loadingProgress}
|
||||
</div>
|
||||
const aClassName = classNames({
|
||||
[`${prefixCls}-list-item-thumbnail`]: true,
|
||||
[`${prefixCls}-list-item-file`]: isImgUrl && !isImgUrl(file),
|
||||
});
|
||||
icon = (
|
||||
<a
|
||||
className={aClassName}
|
||||
onClick={e => handlePreview(file, e)}
|
||||
href={file.url || file.thumbUrl}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{thumbnail}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
const infoUploadingClass = classNames({
|
||||
[`${prefixCls}-list-item`]: true,
|
||||
[`${prefixCls}-list-item-${file.status}`]: true,
|
||||
[`${prefixCls}-list-item-list-type-${listType}`]: true,
|
||||
});
|
||||
const linkProps =
|
||||
typeof file.linkProps === 'string' ? JSON.parse(file.linkProps) : file.linkProps;
|
||||
}
|
||||
|
||||
const removeIcon = showRemoveIcon
|
||||
? this.handleActionIconRender(
|
||||
customRemoveIcon || <DeleteOutlined />,
|
||||
() => this.handleClose(file),
|
||||
if (file.status === 'uploading') {
|
||||
// show loading icon if upload progress listener is disabled
|
||||
const loadingProgress =
|
||||
'percent' in file ? (
|
||||
<Progress {...progressProps} type="line" percent={file.percent} />
|
||||
) : null;
|
||||
|
||||
progress = (
|
||||
<div className={`${prefixCls}-list-item-progress`} key="progress">
|
||||
{loadingProgress}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
const infoUploadingClass = classNames({
|
||||
[`${prefixCls}-list-item`]: true,
|
||||
[`${prefixCls}-list-item-${file.status}`]: true,
|
||||
[`${prefixCls}-list-item-list-type-${listType}`]: true,
|
||||
});
|
||||
const linkProps =
|
||||
typeof file.linkProps === 'string' ? JSON.parse(file.linkProps) : file.linkProps;
|
||||
|
||||
const removeIcon = showRemoveIcon
|
||||
? handleActionIconRender(
|
||||
customRemoveIcon || <DeleteOutlined />,
|
||||
() => handleClose(file),
|
||||
prefixCls,
|
||||
locale.removeFile,
|
||||
)
|
||||
: null;
|
||||
|
||||
const downloadIcon =
|
||||
showDownloadIcon && file.status === 'done'
|
||||
? handleActionIconRender(
|
||||
customDownloadIcon || <DownloadOutlined />,
|
||||
() => handleDownload(file),
|
||||
prefixCls,
|
||||
locale.removeFile,
|
||||
locale.downloadFile,
|
||||
)
|
||||
: null;
|
||||
|
||||
const downloadIcon =
|
||||
showDownloadIcon && file.status === 'done'
|
||||
? this.handleActionIconRender(
|
||||
customDownloadIcon || <DownloadOutlined />,
|
||||
() => this.handleDownload(file),
|
||||
prefixCls,
|
||||
locale.downloadFile,
|
||||
)
|
||||
: null;
|
||||
const downloadOrDelete = listType !== 'picture-card' && (
|
||||
<span
|
||||
key="download-delete"
|
||||
className={`${prefixCls}-list-item-card-actions ${
|
||||
listType === 'picture' ? 'picture' : ''
|
||||
}`}
|
||||
>
|
||||
{downloadIcon}
|
||||
{removeIcon}
|
||||
</span>
|
||||
);
|
||||
const listItemNameClass = classNames({
|
||||
[`${prefixCls}-list-item-name`]: true,
|
||||
[`${prefixCls}-list-item-name-icon-count-${
|
||||
[downloadIcon, removeIcon].filter(x => x).length
|
||||
}`]: true,
|
||||
});
|
||||
const preview = file.url
|
||||
? [
|
||||
<a
|
||||
key="view"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className={listItemNameClass}
|
||||
title={file.name}
|
||||
{...linkProps}
|
||||
href={file.url}
|
||||
onClick={e => this.handlePreview(file, e)}
|
||||
>
|
||||
{file.name}
|
||||
</a>,
|
||||
downloadOrDelete,
|
||||
]
|
||||
: [
|
||||
<span
|
||||
key="view"
|
||||
className={listItemNameClass}
|
||||
onClick={e => this.handlePreview(file, e)}
|
||||
title={file.name}
|
||||
>
|
||||
{file.name}
|
||||
</span>,
|
||||
downloadOrDelete,
|
||||
];
|
||||
const style: React.CSSProperties = {
|
||||
pointerEvents: 'none',
|
||||
opacity: 0.5,
|
||||
};
|
||||
const previewIcon = showPreviewIcon ? (
|
||||
<a
|
||||
href={file.url || file.thumbUrl}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
style={file.url || file.thumbUrl ? undefined : style}
|
||||
onClick={e => this.handlePreview(file, e)}
|
||||
title={locale.previewFile}
|
||||
>
|
||||
<EyeOutlined />
|
||||
</a>
|
||||
) : null;
|
||||
|
||||
const actions = listType === 'picture-card' && file.status !== 'uploading' && (
|
||||
<span className={`${prefixCls}-list-item-actions`}>
|
||||
{previewIcon}
|
||||
{file.status === 'done' && downloadIcon}
|
||||
{removeIcon}
|
||||
</span>
|
||||
);
|
||||
|
||||
let message;
|
||||
if (file.response && typeof file.response === 'string') {
|
||||
message = file.response;
|
||||
} else {
|
||||
message = (file.error && file.error.statusText) || locale.uploadError;
|
||||
}
|
||||
const iconAndPreview = (
|
||||
<span>
|
||||
{icon}
|
||||
{preview}
|
||||
</span>
|
||||
);
|
||||
const dom = (
|
||||
<div className={infoUploadingClass}>
|
||||
<div className={`${prefixCls}-list-item-info`}>{iconAndPreview}</div>
|
||||
{actions}
|
||||
<Animate transitionName="fade" component="">
|
||||
{progress}
|
||||
</Animate>
|
||||
</div>
|
||||
);
|
||||
const listContainerNameClass = classNames({
|
||||
[`${prefixCls}-list-picture-card-container`]: listType === 'picture-card',
|
||||
});
|
||||
return (
|
||||
<div key={file.uid} className={listContainerNameClass}>
|
||||
{file.status === 'error' ? (
|
||||
<Tooltip title={message} getPopupContainer={node => node.parentNode as HTMLElement}>
|
||||
{dom}
|
||||
</Tooltip>
|
||||
) : (
|
||||
<span>{dom}</span>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
const listClassNames = classNames({
|
||||
[`${prefixCls}-list`]: true,
|
||||
[`${prefixCls}-list-${listType}`]: true,
|
||||
[`${prefixCls}-list-rtl`]: direction === 'rtl',
|
||||
});
|
||||
const animationDirection = listType === 'picture-card' ? 'animate-inline' : 'animate';
|
||||
return (
|
||||
<Animate
|
||||
transitionName={`${prefixCls}-${animationDirection}`}
|
||||
component="div"
|
||||
className={listClassNames}
|
||||
const downloadOrDelete = listType !== 'picture-card' && (
|
||||
<span
|
||||
key="download-delete"
|
||||
className={`${prefixCls}-list-item-card-actions ${listType === 'picture' ? 'picture' : ''}`}
|
||||
>
|
||||
{list}
|
||||
</Animate>
|
||||
{downloadIcon}
|
||||
{removeIcon}
|
||||
</span>
|
||||
);
|
||||
};
|
||||
const listItemNameClass = classNames({
|
||||
[`${prefixCls}-list-item-name`]: true,
|
||||
[`${prefixCls}-list-item-name-icon-count-${
|
||||
[downloadIcon, removeIcon].filter(x => x).length
|
||||
}`]: true,
|
||||
});
|
||||
const preview = file.url
|
||||
? [
|
||||
<a
|
||||
key="view"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className={listItemNameClass}
|
||||
title={file.name}
|
||||
{...linkProps}
|
||||
href={file.url}
|
||||
onClick={e => handlePreview(file, e)}
|
||||
>
|
||||
{file.name}
|
||||
</a>,
|
||||
downloadOrDelete,
|
||||
]
|
||||
: [
|
||||
<span
|
||||
key="view"
|
||||
className={listItemNameClass}
|
||||
onClick={e => handlePreview(file, e)}
|
||||
title={file.name}
|
||||
>
|
||||
{file.name}
|
||||
</span>,
|
||||
downloadOrDelete,
|
||||
];
|
||||
const style: React.CSSProperties = {
|
||||
pointerEvents: 'none',
|
||||
opacity: 0.5,
|
||||
};
|
||||
const previewIcon = showPreviewIcon ? (
|
||||
<a
|
||||
href={file.url || file.thumbUrl}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
style={file.url || file.thumbUrl ? undefined : style}
|
||||
onClick={e => handlePreview(file, e)}
|
||||
title={locale.previewFile}
|
||||
>
|
||||
<EyeOutlined />
|
||||
</a>
|
||||
) : null;
|
||||
|
||||
render() {
|
||||
return <ConfigConsumer>{this.renderUploadList}</ConfigConsumer>;
|
||||
}
|
||||
}
|
||||
const actions = listType === 'picture-card' && file.status !== 'uploading' && (
|
||||
<span className={`${prefixCls}-list-item-actions`}>
|
||||
{previewIcon}
|
||||
{file.status === 'done' && downloadIcon}
|
||||
{removeIcon}
|
||||
</span>
|
||||
);
|
||||
|
||||
let message;
|
||||
if (file.response && typeof file.response === 'string') {
|
||||
message = file.response;
|
||||
} else {
|
||||
message = (file.error && file.error.statusText) || locale.uploadError;
|
||||
}
|
||||
const iconAndPreview = (
|
||||
<span>
|
||||
{icon}
|
||||
{preview}
|
||||
</span>
|
||||
);
|
||||
const dom = (
|
||||
<div className={infoUploadingClass}>
|
||||
<div className={`${prefixCls}-list-item-info`}>{iconAndPreview}</div>
|
||||
{actions}
|
||||
<Animate transitionName="fade" component="">
|
||||
{progress}
|
||||
</Animate>
|
||||
</div>
|
||||
);
|
||||
const listContainerNameClass = classNames({
|
||||
[`${prefixCls}-list-picture-card-container`]: listType === 'picture-card',
|
||||
});
|
||||
return (
|
||||
<div key={file.uid} className={listContainerNameClass}>
|
||||
{file.status === 'error' ? (
|
||||
<Tooltip title={message} getPopupContainer={node => node.parentNode as HTMLElement}>
|
||||
{dom}
|
||||
</Tooltip>
|
||||
) : (
|
||||
<span>{dom}</span>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
const listClassNames = classNames({
|
||||
[`${prefixCls}-list`]: true,
|
||||
[`${prefixCls}-list-${listType}`]: true,
|
||||
[`${prefixCls}-list-rtl`]: direction === 'rtl',
|
||||
});
|
||||
const animationDirection = listType === 'picture-card' ? 'animate-inline' : 'animate';
|
||||
return (
|
||||
<Animate
|
||||
transitionName={`${prefixCls}-${animationDirection}`}
|
||||
component="div"
|
||||
className={listClassNames}
|
||||
>
|
||||
{list}
|
||||
</Animate>
|
||||
);
|
||||
};
|
||||
|
||||
const UploadList = React.forwardRef<unknown, UploadListProps>(InternalUploadList);
|
||||
|
||||
UploadList.displayName = 'UploadList';
|
||||
|
||||
UploadList.defaultProps = {
|
||||
listType: 'text' as UploadListType, // or picture
|
||||
progress: {
|
||||
strokeWidth: 2,
|
||||
showInfo: false,
|
||||
},
|
||||
showRemoveIcon: true,
|
||||
showDownloadIcon: false,
|
||||
showPreviewIcon: true,
|
||||
previewFile: previewImage,
|
||||
isImageUrl,
|
||||
};
|
||||
|
||||
export default UploadList;
|
||||
|
@ -1,6 +1,7 @@
|
||||
/* eslint-disable react/no-string-refs, react/prefer-es6-class */
|
||||
import React from 'react';
|
||||
import { mount } from 'enzyme';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
import Upload from '..';
|
||||
import { setup, teardown } from './mock';
|
||||
import mountTest from '../../../tests/shared/mountTest';
|
||||
@ -12,6 +13,7 @@ describe('Upload.Dragger', () => {
|
||||
afterEach(() => teardown());
|
||||
|
||||
it('support drag file with over style', () => {
|
||||
jest.useFakeTimers();
|
||||
const wrapper = mount(
|
||||
<Upload.Dragger action="http://upload.com">
|
||||
<div />
|
||||
@ -23,6 +25,14 @@ describe('Upload.Dragger', () => {
|
||||
files: [{ file: 'foo.png' }],
|
||||
},
|
||||
});
|
||||
|
||||
act(() => {
|
||||
jest.runAllTimers();
|
||||
});
|
||||
wrapper.update();
|
||||
|
||||
expect(wrapper.find('.ant-upload-drag').hasClass('ant-upload-drag-hover')).toBe(true);
|
||||
|
||||
jest.useRealTimers();
|
||||
});
|
||||
});
|
||||
|
@ -55,7 +55,6 @@ describe('Upload', () => {
|
||||
<button type="button">upload</button>
|
||||
</Upload>,
|
||||
);
|
||||
|
||||
wrapper.find('input').simulate('change', {
|
||||
target: {
|
||||
files: [{ file: 'foo.png' }],
|
||||
@ -263,10 +262,11 @@ describe('Upload', () => {
|
||||
url: 'http://www.baidu.com/xxx.png',
|
||||
},
|
||||
];
|
||||
const wrapper = mount(<Upload />);
|
||||
expect(wrapper.instance().state.fileList).toEqual([]);
|
||||
const ref = React.createRef();
|
||||
const wrapper = mount(<Upload ref={ref} />);
|
||||
expect(ref.current.fileList).toEqual([]);
|
||||
wrapper.setProps({ fileList });
|
||||
expect(wrapper.instance().state.fileList).toEqual(fileList);
|
||||
expect(ref.current.fileList).toEqual(fileList);
|
||||
});
|
||||
|
||||
describe('util', () => {
|
||||
@ -464,25 +464,13 @@ describe('Upload', () => {
|
||||
|
||||
// https://github.com/ant-design/ant-design/issues/14439
|
||||
it('should allow call abort function through upload instance', () => {
|
||||
const wrapper = mount(
|
||||
<Upload>
|
||||
const ref = React.createRef();
|
||||
mount(
|
||||
<Upload ref={ref}>
|
||||
<button type="button">upload</button>
|
||||
</Upload>,
|
||||
);
|
||||
expect(typeof wrapper.instance().upload.abort).toBe('function');
|
||||
});
|
||||
|
||||
it('unmount', () => {
|
||||
const wrapper = mount(
|
||||
<Upload>
|
||||
<button type="button">upload</button>
|
||||
</Upload>,
|
||||
);
|
||||
const clearIntervalSpy = jest.spyOn(global, 'clearInterval');
|
||||
expect(clearIntervalSpy).not.toHaveBeenCalled();
|
||||
wrapper.unmount();
|
||||
expect(clearIntervalSpy).toHaveBeenCalled();
|
||||
clearIntervalSpy.mockRestore();
|
||||
expect(typeof ref.current.upload.abort).toBe('function');
|
||||
});
|
||||
|
||||
it('correct dragCls when type is drag', () => {
|
||||
@ -497,14 +485,34 @@ describe('Upload', () => {
|
||||
|
||||
it('return when targetItem is null', () => {
|
||||
const fileList = [{ uid: 'file' }];
|
||||
const wrapper = mount(
|
||||
<Upload type="drag" fileList={fileList}>
|
||||
const ref = React.createRef();
|
||||
mount(
|
||||
<Upload ref={ref} type="drag" fileList={fileList}>
|
||||
<button type="button">upload</button>
|
||||
</Upload>,
|
||||
).instance();
|
||||
expect(wrapper.onSuccess('', { uid: 'fileItem' })).toBe(undefined);
|
||||
expect(wrapper.onProgress('', { uid: 'fileItem' })).toBe(undefined);
|
||||
expect(wrapper.onError('', '', { uid: 'fileItem' })).toBe(undefined);
|
||||
);
|
||||
expect(ref.current.onSuccess('', { uid: 'fileItem' })).toBe(undefined);
|
||||
expect(ref.current.onProgress('', { uid: 'fileItem' })).toBe(undefined);
|
||||
expect(ref.current.onError('', '', { uid: 'fileItem' })).toBe(undefined);
|
||||
});
|
||||
|
||||
it('should replace file when targetItem already exists', () => {
|
||||
const fileList = [{ uid: 'file', name: 'file' }];
|
||||
const ref = React.createRef();
|
||||
mount(
|
||||
<Upload ref={ref} defaultFileList={fileList}>
|
||||
<button type="button">upload</button>
|
||||
</Upload>,
|
||||
);
|
||||
ref.current.onStart({
|
||||
uid: 'file',
|
||||
name: 'file1',
|
||||
});
|
||||
expect(ref.current.fileList.length).toBe(1);
|
||||
expect(ref.current.fileList[0].originFileObj).toEqual({
|
||||
name: 'file1',
|
||||
uid: 'file',
|
||||
});
|
||||
});
|
||||
|
||||
it('warning if set `value`', () => {
|
||||
|
@ -184,8 +184,10 @@ describe('Upload List', () => {
|
||||
|
||||
it('does concat filelist when beforeUpload returns false', () => {
|
||||
const handleChange = jest.fn();
|
||||
const ref = React.createRef();
|
||||
const wrapper = mount(
|
||||
<Upload
|
||||
ref={ref}
|
||||
listType="picture"
|
||||
defaultFileList={fileList}
|
||||
onChange={handleChange}
|
||||
@ -194,14 +196,13 @@ describe('Upload List', () => {
|
||||
<button type="button">upload</button>
|
||||
</Upload>,
|
||||
);
|
||||
|
||||
wrapper.find('input').simulate('change', {
|
||||
target: {
|
||||
files: [{ name: 'foo.png' }],
|
||||
},
|
||||
});
|
||||
|
||||
expect(wrapper.state().fileList.length).toBe(fileList.length + 1);
|
||||
expect(ref.current.fileList.length).toBe(fileList.length + 1);
|
||||
expect(handleChange.mock.calls[0][0].fileList).toHaveLength(3);
|
||||
});
|
||||
|
||||
@ -334,15 +335,21 @@ describe('Upload List', () => {
|
||||
};
|
||||
delete newFile.thumbUrl;
|
||||
newFileList.push(newFile);
|
||||
const wrapper = mount(
|
||||
<Upload listType="picture-card" defaultFileList={newFileList} onPreview={handlePreview}>
|
||||
const ref = React.createRef();
|
||||
mount(
|
||||
<Upload
|
||||
ref={ref}
|
||||
listType="picture-card"
|
||||
defaultFileList={newFileList}
|
||||
onPreview={handlePreview}
|
||||
>
|
||||
<button type="button">upload</button>
|
||||
</Upload>,
|
||||
);
|
||||
wrapper.setState({});
|
||||
ref.current.forceUpdate();
|
||||
await sleep();
|
||||
|
||||
expect(wrapper.state().fileList[2].thumbUrl).not.toBe(undefined);
|
||||
expect(ref.current.fileList[2].thumbUrl).not.toBe(undefined);
|
||||
expect(onDrawImage).toHaveBeenCalled();
|
||||
|
||||
// Offset check
|
||||
@ -580,21 +587,24 @@ describe('Upload List', () => {
|
||||
});
|
||||
|
||||
it('return when prop onPreview not exists', () => {
|
||||
const wrapper = mount(<UploadList />).instance();
|
||||
expect(wrapper.handlePreview()).toBe(undefined);
|
||||
const ref = React.createRef();
|
||||
mount(<UploadList ref={ref} />);
|
||||
expect(ref.current.handlePreview()).toBe(undefined);
|
||||
});
|
||||
|
||||
it('return when prop onDownload not exists', () => {
|
||||
const file = new File([''], 'test.txt', { type: 'text/plain' });
|
||||
const items = [{ uid: 'upload-list-item', url: '' }];
|
||||
const wrapper = mount(
|
||||
const ref = React.createRef();
|
||||
mount(
|
||||
<UploadList
|
||||
ref={ref}
|
||||
items={items}
|
||||
locale={{ downloadFile: '' }}
|
||||
showUploadList={{ showDownloadIcon: true }}
|
||||
/>,
|
||||
).instance();
|
||||
expect(wrapper.handleDownload(file)).toBe(undefined);
|
||||
);
|
||||
expect(ref.current.handleDownload(file)).toBe(undefined);
|
||||
});
|
||||
|
||||
it('previewFile should work correctly', async () => {
|
||||
@ -602,8 +612,8 @@ describe('Upload List', () => {
|
||||
const items = [{ uid: 'upload-list-item', url: '' }];
|
||||
const wrapper = mount(
|
||||
<UploadList listType="picture-card" items={items} locale={{ previewFile: '' }} />,
|
||||
).instance();
|
||||
return wrapper.props.previewFile(file);
|
||||
);
|
||||
return wrapper.props().previewFile(file);
|
||||
});
|
||||
|
||||
it('downloadFile should work correctly', async () => {
|
||||
@ -617,8 +627,8 @@ describe('Upload List', () => {
|
||||
locale={{ downloadFile: '' }}
|
||||
showUploadList={{ showDownloadIcon: true }}
|
||||
/>,
|
||||
).instance();
|
||||
return wrapper.props.onDownload(file);
|
||||
);
|
||||
return wrapper.props().onDownload(file);
|
||||
});
|
||||
|
||||
it('extname should work correctly when url not exists', () => {
|
||||
@ -683,10 +693,12 @@ describe('Upload List', () => {
|
||||
const wrapper = mount(
|
||||
<UploadList listType="picture-card" items={fileList} locale={{ uploading: 'uploading' }} />,
|
||||
);
|
||||
const instance = wrapper.instance();
|
||||
return instance.props.previewFile(mockFile).then(dataUrl => {
|
||||
expect(dataUrl).toEqual('data:image/png;base64,');
|
||||
});
|
||||
return wrapper
|
||||
.props()
|
||||
.previewFile(mockFile)
|
||||
.then(dataUrl => {
|
||||
expect(dataUrl).toEqual('data:image/png;base64,');
|
||||
});
|
||||
});
|
||||
|
||||
it("upload non image file shouldn't be converted to the base64", async () => {
|
||||
@ -697,10 +709,12 @@ describe('Upload List', () => {
|
||||
const wrapper = mount(
|
||||
<UploadList listType="picture-card" items={fileList} locale={{ uploading: 'uploading' }} />,
|
||||
);
|
||||
const instance = wrapper.instance();
|
||||
return instance.props.previewFile(mockFile).then(dataUrl => {
|
||||
expect(dataUrl).toBe('');
|
||||
});
|
||||
return wrapper
|
||||
.props()
|
||||
.previewFile(mockFile)
|
||||
.then(dataUrl => {
|
||||
expect(dataUrl).toBe('');
|
||||
});
|
||||
});
|
||||
|
||||
describe('customize previewFile support', () => {
|
||||
@ -715,22 +729,20 @@ describe('Upload List', () => {
|
||||
originFileObj: renderInstance(),
|
||||
};
|
||||
delete file.thumbUrl;
|
||||
|
||||
const ref = React.createRef();
|
||||
const wrapper = mount(
|
||||
<Upload listType="picture" defaultFileList={[file]} previewFile={previewFile}>
|
||||
<Upload ref={ref} listType="picture" defaultFileList={[file]} previewFile={previewFile}>
|
||||
<button type="button">button</button>
|
||||
</Upload>,
|
||||
);
|
||||
wrapper.setState({});
|
||||
await sleep();
|
||||
|
||||
ref.current.forceUpdate();
|
||||
expect(previewFile).toHaveBeenCalledWith(file.originFileObj);
|
||||
await sleep(100);
|
||||
wrapper.update();
|
||||
|
||||
expect(wrapper.find('.ant-upload-list-item-thumbnail img').prop('src')).toBe(mockThumbnail);
|
||||
});
|
||||
}
|
||||
|
||||
test('File', () => new File([], 'xxx.png'));
|
||||
test('Blob', () => new Blob());
|
||||
});
|
||||
|
6
components/upload/hooks/useForceUpdate.ts
Normal file
6
components/upload/hooks/useForceUpdate.ts
Normal file
@ -0,0 +1,6 @@
|
||||
import * as React from 'react';
|
||||
|
||||
export default function useForceUpdate() {
|
||||
const [, forceUpdate] = React.useReducer(x => x + 1, 0);
|
||||
return forceUpdate;
|
||||
}
|
18
components/upload/hooks/useSyncState.ts
Normal file
18
components/upload/hooks/useSyncState.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import * as React from 'react';
|
||||
import useForceUpdate from './useForceUpdate';
|
||||
|
||||
type UseSyncStateProps<T> = [() => T, (newValue: T) => void];
|
||||
|
||||
export default function useSyncState<T>(initialValue: T): UseSyncStateProps<T> {
|
||||
const ref = React.useRef<T>(initialValue);
|
||||
const forceUpdate = useForceUpdate();
|
||||
|
||||
return [
|
||||
() => ref.current,
|
||||
(newValue: T) => {
|
||||
ref.current = newValue;
|
||||
// re-render
|
||||
forceUpdate();
|
||||
},
|
||||
];
|
||||
}
|
Loading…
Reference in New Issue
Block a user