Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support paste upload file #543

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions docs/demo/paste.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
title: paste
nav:
title: Demo
path: /demo
---

<code src="../examples/paste.tsx"/></code>
8 changes: 8 additions & 0 deletions docs/demo/pasteDirectory.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
title: pasteDirectory
nav:
title: Demo
path: /demo
---

<code src="../examples/pasteDirectory.tsx"/></code>
43 changes: 43 additions & 0 deletions docs/examples/paste.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/* eslint no-console:0 */
import React from 'react';
import Upload from 'rc-upload';

const props = {
action: '/upload.do',
type: 'drag',
accept: '.png',
beforeUpload(file) {
console.log('beforeUpload', file.name);
},
onStart: file => {
console.log('onStart', file.name);
},
onSuccess(file) {
console.log('onSuccess', file);
},
onProgress(step, file) {
console.log('onProgress', Math.round(step.percent), file.name);
},
onError(err) {
console.log('onError', err);
},
style: { display: 'inline-block', width: 200, height: 200, background: '#eee' },
};

const Test = () => {
return (
<div
style={{
margin: 100,
}}
>
<div>
<Upload {...props}>
<a>开始上传</a>
</Upload>
</div>
</div>
);
};

export default Test;
44 changes: 44 additions & 0 deletions docs/examples/pasteDirectory.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/* eslint no-console:0 */
import React from 'react';
import Upload from 'rc-upload';

const props = {
action: '/upload.do',
type: 'drag',
accept: '.png',
directory: true,
beforeUpload(file) {
console.log('beforeUpload', file.name);
},
onStart: file => {
console.log('onStart', file.name);
},
onSuccess(file) {
console.log('onSuccess', file);
},
onProgress(step, file) {
console.log('onProgress', Math.round(step.percent), file.name);
},
onError(err) {
console.log('onError', err);
},
style: { display: 'inline-block', width: 200, height: 200, background: '#eee' },
};

const Test = () => {
return (
<div
style={{
margin: 100,
}}
>
<div>
<Upload {...props}>
<a>开始上传</a>
</Upload>
</div>
</div>
);
};

export default Test;
65 changes: 47 additions & 18 deletions src/AjaxUploader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ class AjaxUploader extends Component<UploadProps> {

private fileInput: HTMLInputElement;

private isMouseEnter: boolean;

private _isMounted: boolean;

onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
Expand Down Expand Up @@ -66,41 +68,58 @@ class AjaxUploader extends Component<UploadProps> {
}
};

onFileDrop = async (e: React.DragEvent<HTMLDivElement>) => {
const { multiple } = this.props;

onFileDropOrPaste = async (e: React.DragEvent<HTMLDivElement> | ClipboardEvent) => {
e.preventDefault();

if (e.type === 'dragover') {
return;
}

if (this.props.directory) {
const files = await traverseFileTree(
Array.prototype.slice.call(e.dataTransfer.items),
(_file: RcFile) => attrAccept(_file, this.props.accept),
const { multiple, accept, directory } = this.props;
let items: DataTransferItem[] = [];
let files: File[] = [];

if (e.type === 'drop') {
const dataTransfer = (e as React.DragEvent<HTMLDivElement>).dataTransfer;
items = [...(dataTransfer.items || [])];
files = [...(dataTransfer.files || [])];
} else if (e.type === 'paste') {
const clipboardData = (e as ClipboardEvent).clipboardData;
items = [...(clipboardData.items || [])];
files = [...(clipboardData.files || [])];
}

if (directory) {
files = await traverseFileTree(Array.prototype.slice.call(items), (_file: RcFile) =>
attrAccept(_file, this.props.accept),
);
this.uploadFiles(files);
} else {
let files = [...e.dataTransfer.files].filter((file: RcFile) =>
attrAccept(file, this.props.accept),
);
let acceptFiles = [...files].filter((file: RcFile) => attrAccept(file, accept));

if (multiple === false) {
files = files.slice(0, 1);
acceptFiles = files.slice(0, 1);
}

this.uploadFiles(files);
this.uploadFiles(acceptFiles);
}
};

onPrePaste(e: ClipboardEvent) {
if (this.isMouseEnter) {
this.onFileDropOrPaste(e);
}
}

componentDidMount() {
this._isMounted = true;
document.addEventListener('paste', this.onPrePaste.bind(this));
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

事件监听器绑定方式可能导致内存泄漏

每次调用 .bind(this) 都会创建一个新的函数实例,这可能导致添加和移除的不是同一个函数引用,从而可能引起内存泄漏。

+constructor(props) {
+  super(props);
+  this.boundOnPrePaste = this.onPrePaste.bind(this);
+}

componentDidMount() {
  this._isMounted = true;
-  document.addEventListener('paste', this.onPrePaste.bind(this));
+  document.addEventListener('paste', this.boundOnPrePaste);
}

componentWillUnmount() {
  this._isMounted = false;
  this.abort();
-  document.removeEventListener('paste', this.onPrePaste.bind(this));
+  document.removeEventListener('paste', this.boundOnPrePaste);
}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
document.addEventListener('paste', this.onPrePaste.bind(this));
class AjaxUploader extends React.Component<Props, State> {
constructor(props) {
super(props);
this.boundOnPrePaste = this.onPrePaste.bind(this);
}
componentDidMount() {
this._isMounted = true;
document.addEventListener('paste', this.boundOnPrePaste);
}
componentWillUnmount() {
this._isMounted = false;
this.abort();
document.removeEventListener('paste', this.boundOnPrePaste);
}
onPrePaste(event) {
// existing logic for handling paste events
}
// additional component methods...
}

}

componentWillUnmount() {
this._isMounted = false;
this.abort();
document.removeEventListener('paste', this.onPrePaste.bind(this));
}

uploadFiles = (files: File[]) => {
Expand Down Expand Up @@ -261,6 +280,18 @@ class AjaxUploader extends Component<UploadProps> {
this.fileInput = node;
};

handleMouseEnter = (e: React.MouseEvent<HTMLDivElement>) => {
this.isMouseEnter = true;

this.props.onMouseEnter?.(e);
};

handleMouseLeave = (e: React.MouseEvent<HTMLDivElement>) => {
this.isMouseEnter = false;

this.props.onMouseLeave?.(e);
};

render() {
const {
component: Tag,
Expand All @@ -278,8 +309,6 @@ class AjaxUploader extends Component<UploadProps> {
children,
directory,
openFileDialogOnClick,
onMouseEnter,
onMouseLeave,
hasControlInside,
...otherProps
} = this.props;
Expand All @@ -297,10 +326,10 @@ class AjaxUploader extends Component<UploadProps> {
: {
onClick: openFileDialogOnClick ? this.onClick : () => {},
onKeyDown: openFileDialogOnClick ? this.onKeyDown : () => {},
onMouseEnter,
onMouseLeave,
onDrop: this.onFileDrop,
onDragOver: this.onFileDrop,
onMouseEnter: this.handleMouseEnter,
onMouseLeave: this.handleMouseLeave,
onDrop: this.onFileDropOrPaste,
onDragOver: this.onFileDropOrPaste,
tabIndex: hasControlInside ? undefined : '0',
};
return (
Expand Down
Loading