Skip to content

Commit d294801

Browse files
authored
chore: Use directory=native instead of folder (#681)
* Revert "feat: support folder and deprecated directory (#643)" This reverts commit c1ea49f. * chore: add get filter func * chore: compatible * chore: fix lint * test: add test case
1 parent 1896e01 commit d294801

File tree

3 files changed

+110
-28
lines changed

3 files changed

+110
-28
lines changed

src/AjaxUploader.tsx

Lines changed: 40 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
/* eslint react/no-is-mounted:0,react/sort-comp:0,react/prop-types:0 */
2-
import { clsx } from 'clsx';
32
import pickAttrs from '@rc-component/util/lib/pickAttrs';
3+
import { clsx } from 'clsx';
44
import React, { Component } from 'react';
55
import attrAccept from './attr-accept';
66
import type {
7+
AcceptConfig,
78
BeforeUploadFileType,
89
RcFile,
910
UploadProgressEvent,
@@ -30,12 +31,36 @@ class AjaxUploader extends Component<UploadProps> {
3031

3132
private _isMounted: boolean;
3233

33-
onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
34+
private filterFile = (file: RcFile | File, force = false) => {
3435
const { accept, directory } = this.props;
36+
37+
let filterFn: Exclude<AcceptConfig['filter'], 'native'>;
38+
let acceptFormat: string | undefined;
39+
40+
if (typeof accept === 'string') {
41+
acceptFormat = accept;
42+
} else {
43+
const { filter, format } = accept || {};
44+
45+
acceptFormat = format;
46+
if (filter === 'native') {
47+
filterFn = () => true;
48+
} else {
49+
filterFn = filter;
50+
}
51+
}
52+
53+
const mergedFilter =
54+
filterFn ||
55+
(directory || force
56+
? (currentFile: RcFile) => attrAccept(currentFile, acceptFormat)
57+
: () => true);
58+
return mergedFilter(file as RcFile);
59+
};
60+
61+
onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
3562
const { files } = e.target;
36-
const acceptedFiles = [...files].filter(
37-
(file: RcFile) => !directory || attrAccept(file, accept),
38-
);
63+
const acceptedFiles = [...files].filter(file => this.filterFile(file));
3964
this.uploadFiles(acceptedFiles);
4065
this.reset();
4166
};
@@ -67,7 +92,7 @@ class AjaxUploader extends Component<UploadProps> {
6792
};
6893

6994
onDataTransferFiles = async (dataTransfer: DataTransfer, existFileCallback?: () => void) => {
70-
const { multiple, accept, directory } = this.props;
95+
const { multiple, directory } = this.props;
7196

7297
const items: DataTransferItem[] = [...(dataTransfer.items || [])];
7398
let files: File[] = [...(dataTransfer.files || [])];
@@ -77,12 +102,10 @@ class AjaxUploader extends Component<UploadProps> {
77102
}
78103

79104
if (directory) {
80-
files = await traverseFileTree(Array.prototype.slice.call(items), (_file: RcFile) =>
81-
attrAccept(_file, this.props.accept),
82-
);
105+
files = await traverseFileTree(Array.prototype.slice.call(items), this.filterFile);
83106
this.uploadFiles(files);
84107
} else {
85-
let acceptFiles = [...files].filter((file: RcFile) => attrAccept(file, accept));
108+
let acceptFiles = [...files].filter(file => this.filterFile(file, true));
86109

87110
if (multiple === false) {
88111
acceptFiles = files.slice(0, 1);
@@ -322,17 +345,20 @@ class AjaxUploader extends Component<UploadProps> {
322345
capture,
323346
children,
324347
directory,
325-
folder,
326348
openFileDialogOnClick,
327349
onMouseEnter,
328350
onMouseLeave,
329351
hasControlInside,
330352
...otherProps
331353
} = this.props;
354+
355+
// Extract accept format for input element
356+
const acceptFormat = typeof accept === 'string' ? accept : accept?.format;
332357
const cls = clsx(prefixCls, { [`${prefixCls}-disabled`]: disabled, [className]: className });
333358
// because input don't have directory/webkitdirectory type declaration
334-
const dirProps: any =
335-
directory || folder ? { directory: 'directory', webkitdirectory: 'webkitdirectory' } : {};
359+
const dirProps: any = directory
360+
? { directory: 'directory', webkitdirectory: 'webkitdirectory' }
361+
: {};
336362
const events = disabled
337363
? {}
338364
: {
@@ -361,7 +387,7 @@ class AjaxUploader extends Component<UploadProps> {
361387
key={this.state.uid}
362388
style={{ display: 'none', ...styles.input }}
363389
className={classNames.input}
364-
accept={accept}
390+
accept={acceptFormat}
365391
{...dirProps}
366392
multiple={multiple}
367393
onChange={this.onChange}

src/interface.tsx

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,24 @@ export type BeforeUploadFileType = File | Blob | boolean | string;
44

55
export type Action = string | ((file: RcFile) => string | PromiseLike<string>);
66

7+
export type AcceptConfig = {
8+
format: string;
9+
filter?: 'native' | ((file: RcFile) => boolean);
10+
};
11+
712
export interface UploadProps
8-
extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'onError' | 'onProgress'> {
13+
extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'onError' | 'onProgress' | 'accept'> {
914
name?: string;
1015
style?: React.CSSProperties;
1116
className?: string;
1217
disabled?: boolean;
1318
component?: React.ComponentType<any> | string;
1419
action?: Action;
1520
method?: UploadRequestMethod;
16-
/** @deprecated Please use `folder` instead */
1721
directory?: boolean;
18-
folder?: boolean;
1922
data?: Record<string, unknown> | ((file: RcFile | string | Blob) => Record<string, unknown>);
2023
headers?: UploadRequestHeader;
21-
accept?: string;
24+
accept?: string | AcceptConfig;
2225
multiple?: boolean;
2326
onBatchStart?: (
2427
fileList: { file: RcFile; parsedFile: Exclude<BeforeUploadFileType, boolean> }[],

tests/uploader.spec.tsx

Lines changed: 63 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { fireEvent, render } from '@testing-library/react';
21
import { resetWarned } from '@rc-component/util/lib/warning';
2+
import { fireEvent, render } from '@testing-library/react';
33
import React from 'react';
44
import sinon from 'sinon';
55
import { format } from 'util';
@@ -1038,18 +1038,71 @@ describe('uploader', () => {
10381038
directory: false,
10391039
},
10401040
);
1041+
});
10411042

1042-
it('should trigger beforeUpload when uploading non-accepted files in folder mode', () => {
1043-
const beforeUpload = jest.fn();
1044-
const { container } = render(<Upload accept=".png" folder beforeUpload={beforeUpload} />);
1043+
describe('AcceptConfig', () => {
1044+
let uploader: ReturnType<typeof render>;
1045+
const handlers: UploadProps = {};
10451046

1046-
fireEvent.change(container.querySelector('input')!, {
1047-
target: {
1048-
files: [new File([], 'bamboo.png'), new File([], 'light.jpg')],
1049-
},
1047+
const props: UploadProps = {
1048+
action: '/test',
1049+
data: { a: 1, b: 2 },
1050+
directory: true, // Enable format filtering
1051+
onStart(file) {
1052+
if (handlers.onStart) {
1053+
handlers.onStart(file);
1054+
}
1055+
},
1056+
};
1057+
1058+
function testAcceptConfig(desc: string, accept: any, files: object[], expectCallTimes: number) {
1059+
it(desc, done => {
1060+
uploader = render(<Upload {...props} accept={accept} />);
1061+
const input = uploader.container.querySelector('input')!;
1062+
fireEvent.change(input, { target: { files } });
1063+
const mockStart = jest.fn();
1064+
handlers.onStart = mockStart;
1065+
1066+
setTimeout(() => {
1067+
expect(mockStart.mock.calls.length).toBe(expectCallTimes);
1068+
done();
1069+
}, 100);
10501070
});
1051-
expect(beforeUpload).toHaveBeenCalledTimes(2);
1052-
});
1071+
}
1072+
1073+
testAcceptConfig(
1074+
'should work with format only',
1075+
{ format: '.png' },
1076+
[{ name: 'test.png' }, { name: 'test.jpg' }],
1077+
1,
1078+
);
1079+
1080+
testAcceptConfig(
1081+
'should work with filter: native',
1082+
{ format: '.png', filter: 'native' },
1083+
[{ name: 'test.png' }, { name: 'test.jpg' }],
1084+
2, // native filter bypasses format check
1085+
);
1086+
1087+
testAcceptConfig(
1088+
'should work with custom filter function',
1089+
{
1090+
format: '.png',
1091+
filter: (file: any) => file.name.includes('custom'),
1092+
},
1093+
[{ name: 'custom.jpg' }, { name: 'test.png' }],
1094+
1, // only custom.jpg passes custom filter
1095+
);
1096+
1097+
testAcceptConfig(
1098+
'should work with MIME type format',
1099+
{ format: 'image/*' },
1100+
[
1101+
{ name: 'test.png', type: 'image/png' },
1102+
{ name: 'doc.txt', type: 'text/plain' },
1103+
],
1104+
1, // only image file passes
1105+
);
10531106
});
10541107

10551108
describe('transform file before request', () => {

0 commit comments

Comments
 (0)