Skip to content

Commit 7d8dce6

Browse files
committed
Initial commit
1 parent fd6a0a0 commit 7d8dce6

16 files changed

+5597
-1
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,3 +57,4 @@ typings/
5757
# dotenv environment variables file
5858
.env
5959

60+
dist

.npmignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
example
2+
webpack.config.js

README.md

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,43 @@
11
# react-file-preview
2-
Efficient way of rendering a image preview out of a File
2+
> Efficient way of rendering an image preview from a File
3+
4+
# Installation
5+
6+
```
7+
$ yarn add react-file-preview
8+
```
9+
10+
# Motivation
11+
- auto revoke
12+
- efficient preview
13+
14+
# Usage
15+
16+
```
17+
import FilePreview from 'react-file-preview';
18+
19+
class App extends React.Component {
20+
onInputChange = e => {
21+
const { currentTarget: { files } } = e;
22+
const file = files[0];
23+
24+
this.setState({file});
25+
}
26+
27+
render() {
28+
const {file} = this.state;
29+
30+
return (
31+
<div>
32+
<input type="file" onChange={this.onChange} />
33+
<FilePreview file={file}>
34+
{(preview) => {
35+
<img src={preview} />
36+
}}
37+
</FilePreview>
38+
</div>
39+
)
40+
}
41+
}
42+
43+
```

__tests__/filePreview.spec.tsx

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import * as React from 'react';
2+
import {shallow} from 'enzyme';
3+
import FilePreview from '../src';
4+
5+
describe('FilePreview', () => {
6+
const setup = () => {
7+
const file = {} as File;
8+
const createObjectURL = jest.fn().mockReturnValue('file-preview');
9+
const revokeObjectURL = jest.fn();
10+
11+
(global as any).URL = {
12+
createObjectURL,
13+
revokeObjectURL
14+
};
15+
16+
return {
17+
file,
18+
createObjectURL,
19+
revokeObjectURL
20+
};
21+
};
22+
23+
it('should render the children component passing the preview', () => {
24+
const {file, createObjectURL} = setup();
25+
const component = shallow(
26+
<FilePreview file={file} >
27+
{(preview) => <img src={preview} />}
28+
</FilePreview>
29+
);
30+
31+
expect(createObjectURL).toBeCalledWith(file);
32+
expect(component.find('img').prop('src')).toEqual('file-preview');
33+
});
34+
35+
it('should generate a new preview when file prop changes', () => {
36+
const {file, createObjectURL, revokeObjectURL} = setup();
37+
const newFile = {} as File;
38+
const component = shallow(
39+
<FilePreview file={file} >
40+
{() => <div />}
41+
</FilePreview>
42+
);
43+
44+
component.setProps({file: newFile});
45+
46+
expect(revokeObjectURL).toHaveBeenCalledTimes(1);
47+
expect(createObjectURL).toHaveBeenCalledTimes(2);
48+
expect(revokeObjectURL).toBeCalledWith('file-preview');
49+
expect(createObjectURL.mock.calls[0][0]).toEqual(file);
50+
expect(createObjectURL.mock.calls[1][0]).toEqual(newFile);
51+
});
52+
53+
it('should revoke url when component is unmounted', () => {
54+
const {file, createObjectURL, revokeObjectURL} = setup();
55+
const component = shallow(
56+
<FilePreview file={file} >
57+
{() => <div />}
58+
</FilePreview>
59+
);
60+
61+
component.unmount();
62+
63+
expect(revokeObjectURL).toHaveBeenCalledTimes(1);
64+
expect(revokeObjectURL).toBeCalledWith('file-preview');
65+
});
66+
});

example/app.tsx

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import * as React from 'react';
2+
import {Component} from 'react';
3+
import FilePreview from '../src';
4+
5+
export interface AppState {
6+
isPreviewVisible: boolean;
7+
file?: File;
8+
}
9+
10+
export default class App extends Component <{}, AppState> {
11+
state: AppState = {
12+
isPreviewVisible: true
13+
}
14+
15+
onChange = (e: any) => {
16+
const { currentTarget: { files } } = e;
17+
const file = files[0];
18+
19+
this.setState({file});
20+
}
21+
22+
onToggle = () => {
23+
this.setState({isPreviewVisible: !this.state.isPreviewVisible});
24+
}
25+
26+
renderPreview() {
27+
const {file, isPreviewVisible} = this.state;
28+
if (!isPreviewVisible || !file) return;
29+
30+
return (
31+
<FilePreview file={file}>
32+
{(preview) =>
33+
<img src={preview} />
34+
}
35+
</FilePreview>
36+
);
37+
}
38+
39+
render() {
40+
return (
41+
<div>
42+
<input id="browse" type="file" onChange={this.onChange} />
43+
<button onClick={this.onToggle}>Toggle image</button>
44+
<br/>
45+
{this.renderPreview()}
46+
</div>
47+
)
48+
}
49+
}

example/index.html

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8">
5+
<title>React File Preview</title>
6+
</head>
7+
<body>
8+
<div id="app"></div>
9+
<script src="../index.js"></script>
10+
</body>
11+
</html>

example/index.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import * as React from 'react';
2+
import * as ReactDOM from 'react-dom';
3+
import App from './app';
4+
5+
ReactDOM.render(<App />, document.getElementById('app'));

package.json

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
{
2+
"name": "react-file-preview",
3+
"version": "0.1.0",
4+
"description": "Efficient way of rendering an image preview from a File",
5+
"main": "dist/index.js",
6+
"types": "dist/index.d.ts",
7+
"scripts": {
8+
"start": "webpack -w",
9+
"dev": "webpack-dev-server",
10+
"test": "jest",
11+
"test:ci": "jest --runInBand --coverage",
12+
"build": "NODE_ENV=production tsc -p ./tsconfig.prod.json",
13+
"release": "npm version patch && git push --tags && git push && npm publish",
14+
"prepublishOnly": "yarn test:ci && yarn build"
15+
},
16+
"jest": {
17+
"moduleFileExtensions": [
18+
"ts",
19+
"tsx",
20+
"js"
21+
],
22+
"setupTestFrameworkScriptFile": "<rootDir>/setupTests.js",
23+
"transform": {
24+
"^.+\\.(ts|tsx)$": "<rootDir>/preprocessor.js"
25+
},
26+
"testMatch": [
27+
"**/__tests__/*.(ts|tsx)"
28+
],
29+
"resetMocks": true
30+
},
31+
"repository": "[email protected]:zzarcon/react-file-preview.git",
32+
"author": "Hector Leon Zarco Garcia <[email protected]>",
33+
"license": "MIT",
34+
"peerDependencies": {
35+
"react": "^16.2.0"
36+
},
37+
"devDependencies": {
38+
"@types/enzyme": "^3.1.9",
39+
"@types/jest": "^22.1.4",
40+
"@types/react": "^16.0.40",
41+
"@types/react-dom": "^16.0.4",
42+
"awesome-typescript-loader": "^3.4.1",
43+
"enzyme": "^3.3.0",
44+
"enzyme-adapter-react-16": "^1.1.1",
45+
"jest": "^22.4.2",
46+
"react": "^16.2.0",
47+
"react-dom": "^16.2.0",
48+
"typescript": "^2.7.2",
49+
"webpack": "^3.10.0",
50+
"webpack-dev-server": "^2.11.1"
51+
}
52+
}

preprocessor.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
const tsc = require('typescript');
2+
const tsConfig = require('./tsconfig.json');
3+
4+
module.exports = {
5+
process(src, path) {
6+
if (path.endsWith('.ts') || path.endsWith('.tsx')) {
7+
return tsc.transpile(src, tsConfig.compilerOptions, path, []);
8+
}
9+
return src;
10+
},
11+
};

setupTests.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
const Enzyme = require('enzyme');
2+
const Adapter = require('enzyme-adapter-react-16');
3+
4+
Enzyme.configure({ adapter: new Adapter() });

src/filePreview.tsx

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import * as React from 'react';
2+
import {Component, ReactNode} from 'react';
3+
4+
export interface FilePreviewProps {
5+
file: File;
6+
children: (preview: string) => ReactNode;
7+
}
8+
9+
export class FilePreview extends Component <FilePreviewProps> {
10+
preview: string;
11+
12+
componentWillReceiveProps({file: newFile}: FilePreviewProps) {
13+
const {file} = this.props;
14+
15+
if (file !== newFile) {
16+
this.revoke();
17+
this.createPreview(newFile);
18+
}
19+
}
20+
21+
componentWillMount() {
22+
const {file} = this.props;
23+
24+
this.createPreview(file);
25+
}
26+
27+
componentWillUnmount() {
28+
this.revoke();
29+
}
30+
31+
private createPreview(file: File) {
32+
this.preview = URL.createObjectURL(file);
33+
}
34+
35+
private revoke() {
36+
URL.revokeObjectURL(this.preview);
37+
}
38+
39+
render() {
40+
const {children} = this.props;
41+
42+
return children(this.preview);
43+
}
44+
}
45+
46+
export default FilePreview;

src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export {FilePreview, FilePreview as default} from './filePreview';

tsconfig.json

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"compilerOptions": {
3+
"noImplicitAny": true,
4+
"removeComments": true,
5+
"target": "es5",
6+
"lib": [
7+
"dom",
8+
"es5",
9+
"scripthost",
10+
"es2015.collection",
11+
"es2015.symbol",
12+
"es2015.iterable",
13+
"es2015.promise"
14+
],
15+
"jsx": "react"
16+
}
17+
}

tsconfig.prod.json

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"compilerOptions": {
3+
"noImplicitAny": true,
4+
"removeComments": true,
5+
"declaration": true,
6+
"outDir": "dist",
7+
"lib": ["dom", "es6"],
8+
"target": "es5",
9+
"jsx": "react"
10+
},
11+
"include": ["src/**/*.ts", "src/**/*.tsx"]
12+
}

webpack.config.js

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
const path = require('path');
2+
const { CheckerPlugin } = require('awesome-typescript-loader');
3+
4+
module.exports = {
5+
devServer: {
6+
7+
},
8+
entry: './example/index.tsx',
9+
output: {
10+
path: path.resolve(__dirname, 'dist'),
11+
filename: 'index.js'
12+
},
13+
resolve: {
14+
extensions: ['.ts', '.tsx', '.js', '.jsx']
15+
},
16+
devtool: 'source-map',
17+
module: {
18+
rules: [
19+
{
20+
test: /\.tsx?$/,
21+
loader: 'awesome-typescript-loader'
22+
}
23+
]
24+
},
25+
plugins: [
26+
new CheckerPlugin()
27+
]
28+
};

0 commit comments

Comments
 (0)