Skip to content

Commit

Permalink
Add onFinish & onFileChange api (#5)
Browse files Browse the repository at this point in the history
Add onFinish & onFileChange api
  • Loading branch information
Kunduin authored Jun 18, 2019
2 parents eddb974 + a1258aa commit ff491d5
Show file tree
Hide file tree
Showing 5 changed files with 195 additions and 32 deletions.
148 changes: 139 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,139 @@
<div align="center" markdown="1">
<h1>React Uploader Skeleton</h1>
<strong>This project is under active development</strong>
</div>

- [x] basic image list
- [ ] drag & drop
- [ ] limit uploading thread
- [ ] custom upload interface
# React Uploader Skeleton

[![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/kunduin/react-uploader-skeleton/blob/master/LICENSE) [![Travis (.com)](https://img.shields.io/travis/com/kunduin/react-uploader-skeleton.svg)](https://travis-ci.com/Kunduin/react-uploader-skeleton) [![npm](https://img.shields.io/npm/v/react-uploader-skeleton.svg)](https://www.npmjs.com/package/react-uploader-skeleton)

A simple skeleton for building an awesome uploader component.

## Install

```Bash
# npm
npm install react-uploader-skeleton

# yarn
yarn add react-uploader-skeleton
```

## Basic Example

```tsx
<ReactUploaderSkeleton
onFinish={e => console.log('onFinished', e)}
onFileChange={e => console.log('onFileChange', e)}
request={(fileData, onProgress, onError, onSuccess) => {
const xhr = new XMLHttpRequest();
xhr.upload.addEventListener('progress', e => {
const done = e.loaded;
const total = e.total;
const progress = done / total;
if (progress > 1) {
onProgress(1);
} else {
onProgress(done / total);
}
});
xhr.addEventListener('readystatechange', () => {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
onSuccess(xhr.response);
} else {
onError();
}
}
});
xhr.open('POST', 'http://127.0.0.1:3000/file');
const formData = new FormData();
formData.append('file', fileData.fileData as File);
xhr.send(formData);
}}
/>
```

## Props

### parallelUploads `not required`

**TYPE**

```ts
number;
```

**COMMENT**

How many file uploads to process in parallel.

### onFileChange `required`

**TYPE**

```ts
(files: IUploaderFileData[]) => void
```

see [IUploaderFileData](#iuploaderfiledata)

**COMMENT**

Called when files change.

### onFinish `not required`

**TYPE**

```ts
(files: IUploaderFileData[]) => void
```

see [IUploaderFileData](#iuploaderfiledata)

**COMMENT**

Called when the upload is complete.

### request `required`

```ts
request: (
uploaderFileData: IUploaderFileData,
onProgress: (percent: number) => void,
onError: (message?: string) => void,
onSuccess: (url?: string) => void
) => void;
```

see [IUploaderFileData](#iuploaderfiledata)

**COMMENT**

Custom upload request.

## children `not required`

**TYPE**

```ts
React.ReactNode;
```

**COMMENT**

Placeholders when there is no file upload.

## Type

### IUploaderFileData

```ts
interface IUploaderFileData {
name: string; // name of file
state: string; // file state ["resolved","error","waiting","uploading"]
url?: string; // link to file in cloud
fileData?: File; // raw file data
progress?: number; // progress of upload
}
```

## Licence

[MIT License](https://github.com/kunduin/react-uploader-skeleton/blob/master/LICENSE)
25 changes: 12 additions & 13 deletions src/DefaultFilePreview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -96,19 +96,18 @@ class DefaultFilePreview extends React.Component<IDefaultFilePreviewProps> {
{uploaderFileData.state === FileState.ERROR && <Close />}
</div>

{uploaderFileData.state !== FileState.RESOLVED &&
uploaderFileData.state !== FileState.ERROR && (
<div className="rus-preview_progress-bar-container">
<div
style={{ transform: `scaleX(${progress})` }}
className={
state === FileState.ERROR
? "rus-preview_progress-bar--error"
: "rus-preview_progress-bar"
}
/>
</div>
)}
{uploaderFileData.state !== FileState.RESOLVED && (
<div className="rus-preview_progress-bar-container">
<div
style={{ transform: `scaleX(${progress})` }}
className={
state === FileState.ERROR
? "rus-preview_progress-bar--error"
: "rus-preview_progress-bar"
}
/>
</div>
)}
</div>
<div className="rus-preview_delete" onClick={onRemove}>
<CloseCircle />
Expand Down
50 changes: 40 additions & 10 deletions src/ReactUploaderSkeleton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import generatorFileName from "./utils/generatorFileName";

export interface IReactUploaderSkeletonProps {
parallelUploads: number;
onFileChange: (files: IUploaderFileData[]) => void;
onFinish: (files: IUploaderFileData[]) => void;
request: (
uploaderFileData: IUploaderFileData,
onProgress: (percent: number) => void,
Expand All @@ -32,7 +34,8 @@ class ReactUploaderSkeleton extends React.Component<
IReactUploaderSkeletonState
> {
public static defaultProps = {
parallelUploads: 5
parallelUploads: 5,
onFinish: () => null
};

public readonly state: IReactUploaderSkeletonState = {
Expand Down Expand Up @@ -81,6 +84,7 @@ class ReactUploaderSkeleton extends React.Component<
const nextCurrentFiles = [...currentFiles, ...changeFiles];

this.setState({ currentFiles: nextCurrentFiles });

this.arrangeFileUpload();
}
};
Expand All @@ -92,7 +96,7 @@ class ReactUploaderSkeleton extends React.Component<
for (const eachFile of currentFiles) {
sumPercent += eachFile.progress || 0;
}
sumPercent = currentFiles.length / sumPercent;
sumPercent = sumPercent / currentFiles.length;
if (sumPercent > 1) {
sumPercent = 1;
}
Expand Down Expand Up @@ -126,14 +130,19 @@ class ReactUploaderSkeleton extends React.Component<
key={eachFile.name}
uploaderFileData={eachFile}
onRemove={() => {
this.setState(preState => {
const nextFiles = [...preState.currentFiles];
const targetIndex = nextFiles.findIndex(
value => value.name === eachFile.name
);
nextFiles.splice(targetIndex, 1);
return { currentFiles: nextFiles };
});
this.setState(
preState => {
const nextFiles = [...preState.currentFiles];
const targetIndex = nextFiles.findIndex(
value => value.name === eachFile.name
);
nextFiles.splice(targetIndex, 1);
return { currentFiles: nextFiles };
},
() => {
this.fileChangeLifeCycle(this.state.currentFiles);
}
);
}}
/>
))}
Expand All @@ -152,6 +161,26 @@ class ReactUploaderSkeleton extends React.Component<
);
}

private fileChangeLifeCycle = (files: IUploaderFileData[]) => {
const { onFileChange, onFinish } = this.props;
onFileChange(files);

let isFinished = true;

for (const fileData of files) {
if (
fileData.state !== FileState.RESOLVED &&
fileData.state !== FileState.ERROR
) {
isFinished = false;
break;
}
}
if (isFinished) {
onFinish(files);
}
};

private arrangeFileUpload = () => {
this.setState(({ currentFiles }) => {
const { parallelUploads, request } = this.props;
Expand Down Expand Up @@ -207,6 +236,7 @@ class ReactUploaderSkeleton extends React.Component<
}
}
}
this.fileChangeLifeCycle(currentFiles);
return { currentFiles: [...currentFiles] };
});
};
Expand Down
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
import "./styles.css";
export { default as ReactUploaderSkeleton } from "./ReactUploaderSkeleton";
export * from "./FileState";
3 changes: 3 additions & 0 deletions stories/index.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* tslint:disable */
import { withA11y } from "@storybook/addon-a11y";
import { storiesOf, StoryDecorator } from "@storybook/react";
import React from "react";
Expand All @@ -13,6 +14,8 @@ storiesOf("Hello World", module)
.addDecorator(CenterDecorator)
.add("with text", () => (
<ReactUploaderSkeleton
onFinish={e => console.log("onFinished", e)}
onFileChange={e => console.log("onFileChange", e)}
request={(fileData, onProgress, onError, onSuccess) => {
const xhr = new XMLHttpRequest();
xhr.upload.addEventListener("progress", e => {
Expand Down

0 comments on commit ff491d5

Please sign in to comment.