├── .babelrc
├── .editorconfig
├── .eslintrc.js
├── .gitattributes
├── .gitignore
├── .npmignore
├── .travis.yml
├── LICENSE
├── README.md
├── __tests__
└── boilerplate_test.js
├── docs
├── index.js
└── main.css
├── lib
└── post_install.js
├── package.json
├── src
├── Camera.js
└── index.js
├── style.css
├── webpack.config.babel.js
└── yarn.lock
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | "react"
4 | ],
5 | "plugins": [
6 | [
7 | "transform-react-remove-prop-types",
8 |
9 | {
10 | "mode": "wrap"
11 | }
12 | ],
13 | ],
14 | "env": {
15 | "dev": {
16 | "presets": [
17 | "es2015",
18 | "react-hmre"
19 | ]
20 | },
21 | "development": {
22 | "presets": [
23 | "es2015"
24 | ]
25 | },
26 | "dist": {
27 | "presets": [
28 | "es2015"
29 | ]
30 | },
31 | "distMin": {
32 | "presets": [
33 | "es2015"
34 | ]
35 | },
36 | "ghPages": {
37 | "presets": [
38 | "es2015"
39 | ]
40 | },
41 | "modules": {
42 | "presets": [
43 | "es2015"
44 | ]
45 | },
46 | "es6": {
47 | "presets": [
48 | [
49 | "es2015",
50 | {
51 | "modules": false
52 | }
53 | ]
54 | ]
55 | },
56 | "test": {
57 | "presets": [
58 | "es2015"
59 | ]
60 | }
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig helps developers define and maintain consistent
2 | # coding styles between different editors and IDEs
3 | # editorconfig.org
4 |
5 | root = true
6 |
7 |
8 | [*]
9 |
10 | # Change these settings to your own preference
11 | indent_style = space
12 | indent_size = 2
13 |
14 | # We recommend you to keep these unchanged
15 | end_of_line = lf
16 | charset = utf-8
17 | trim_trailing_whitespace = true
18 | insert_final_newline = true
19 |
20 | [*.md]
21 | trim_trailing_whitespace = false
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | "extends": "airbnb",
3 | "parser": "babel-eslint",
4 | "env": {
5 | "browser": true,
6 | "jasmine": true,
7 | "node": true
8 | },
9 | "plugins": [
10 | "react"
11 | ],
12 | "rules": {
13 | "comma-dangle": ["error", "never"],
14 | "global-require": 0,
15 | "prefer-arrow-callback": 0,
16 | "func-names": 0,
17 | "import/no-extraneous-dependencies": 0,
18 | "no-underscore-dangle": 0,
19 | "no-unused-expressions": 0,
20 | "no-use-before-define": 0,
21 | "react/jsx-filename-extension": 0,
22 | "react/sort-comp": 0,
23 | "react/no-multi-comp": 0,
24 | "react/require-extension": 0
25 | }
26 | };
27 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | * text=auto
2 | bin/* eol=lf
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | build/
2 | gh-pages/
3 | dist/
4 | dist-es6/
5 | dist-modules/
6 | node_modules/
7 | coverage/
8 | npm-debug.log
9 | .eslintcache
10 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | demo/
2 | dist/
3 | tests/
4 | src/
5 | .*
6 |
7 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - "4"
4 | - "5"
5 | - "6"
6 | script:
7 | - npm run test:lint
8 | - npm run test:coverage
9 | after_success:
10 | - bash <(curl -s https://codecov.io/bash)
11 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2017 Jean Kévin
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy of
4 | this software and associated documentation files (the "Software"), to deal in
5 | the Software without restriction, including without limitation the rights to
6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
7 | of the Software, and to permit persons to whom the Software is furnished to do
8 | so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in all
11 | copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | SOFTWARE.
20 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # React Camera [](https://travis-ci.org/Miniplop/react-camera/) [](https://badge.fury.io/js/react-camera)
2 |
3 | The comprehensive camera module for React. Including photographs! (videos, and barcode scanning coming soon)
4 |
5 | ## Getting started
6 |
7 | `npm install react-camera`
8 |
9 | or
10 |
11 | `yarn add react-camera`
12 |
13 | ## Usage
14 |
15 | ```
16 | import React, { Component } from 'react';
17 | import Camera from 'react-camera';
18 |
19 | export default class App extends Component {
20 |
21 | constructor(props) {
22 | super(props);
23 | this.takePicture = this.takePicture.bind(this);
24 | }
25 |
26 | takePicture() {
27 | this.camera.capture()
28 | .then(blob => {
29 | this.img.src = URL.createObjectURL(blob);
30 | this.img.onload = () => { URL.revokeObjectURL(this.src); }
31 | })
32 | }
33 |
34 | render() {
35 | return (
36 |
37 |
{
40 | this.camera = cam;
41 | }}
42 | >
43 |
46 |
47 |
![]()
{
50 | this.img = img;
51 | }}
52 | />
53 |
54 | );
55 | }
56 | }
57 |
58 | const style = {
59 | preview: {
60 | position: 'relative',
61 | },
62 | captureContainer: {
63 | display: 'flex',
64 | position: 'absolute',
65 | justifyContent: 'center',
66 | zIndex: 1,
67 | bottom: 0,
68 | width: '100%'
69 | },
70 | captureButton: {
71 | backgroundColor: '#fff',
72 | borderRadius: '50%',
73 | height: 56,
74 | width: 56,
75 | color: '#000',
76 | margin: 20
77 | },
78 | captureImage: {
79 | width: '100%',
80 | }
81 | };
82 | ```
83 |
84 | ## Component instance methods
85 |
86 | You can access component methods by adding a ref (ie. ref="camera") prop to your element, then you can use this.refs.camera.capture(cb), etc. inside your component.
87 |
88 | #### `capture(): Promise`
89 |
--------------------------------------------------------------------------------
/__tests__/boilerplate_test.js:
--------------------------------------------------------------------------------
1 | // import React from 'react';
2 | // import {
3 | // renderIntoDocument,
4 | // findRenderedDOMComponentWithClass,
5 | // findRenderedDOMComponentWithTag,
6 | // Simulate
7 | // } from 'react-addons-test-utils';
8 | import { expect } from 'chai';
9 |
10 | describe('React Camera', function () {
11 | it('should render', function () {
12 | expect(true).to.equal(true);
13 | });
14 | });
15 |
--------------------------------------------------------------------------------
/docs/index.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable global-require, import/no-unresolved, react/no-multi-comp */
2 | import React from 'react';
3 | import ReactDOM from 'react-dom';
4 | import GithubCorner from 'react-github-corner';
5 | import { Catalog, CodeSpecimen, ReactSpecimen } from 'catalog';
6 |
7 | import 'purecss/build/pure.css';
8 | import './main.css';
9 | import '../style.css';
10 |
11 | // Add your documentation imports here. These are available to
12 | // React specimen. Do NOT pass React here as Catalog does that.
13 | const documentationImports = {};
14 | const title = `${NAME} v${VERSION}`; // eslint-disable-line no-undef
15 | const project = `${USER}/${NAME}`; // eslint-disable-line no-undef
16 | const pages = [
17 | {
18 | path: '/',
19 | title: 'Introduction',
20 | component: require('../README.md')
21 | }
22 | ];
23 |
24 | // Catalog - logoSrc="../images/logo.png"
25 | ReactDOM.render(
26 |
27 |
35 | ,
40 | js: props => ,
41 | jsx: props =>
42 | }}
43 | title={title}
44 | />
45 |
,
46 | document.getElementById('app')
47 | );
48 |
--------------------------------------------------------------------------------
/docs/main.css:
--------------------------------------------------------------------------------
1 | .github-corner svg {
2 | z-index: 1000;
3 | }
4 |
5 | /* TODO: insert main styles of your demo here */
6 |
--------------------------------------------------------------------------------
/lib/post_install.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | // adapted based on rackt/history (MIT)
3 | // Node 0.10+
4 | var execSync = require('child_process').execSync;
5 | var stat = require('fs').stat;
6 |
7 | // Node 0.10 check
8 | if (!execSync) {
9 | execSync = require('sync-exec');
10 | }
11 |
12 | function exec(command) {
13 | execSync(command, {
14 | stdio: [0, 1, 2]
15 | });
16 | }
17 |
18 | stat('dist-modules', function(error, stat) {
19 | // Skip building on Travis
20 | if (process.env.TRAVIS) {
21 | return;
22 | }
23 |
24 | if (error || !stat.isDirectory()) {
25 | exec('npm i babel-cli babel-preset-es2015 babel-preset-react');
26 | exec('npm run dist:modules');
27 | }
28 | });
29 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-camera",
3 | "version": "0.1.2",
4 | "license": "MIT",
5 | "description": "A Camera component for React.",
6 | "repository": {
7 | "type": "git",
8 | "url": "https://github.com/Miniplop/react-camera.git"
9 | },
10 | "author": "Jean Kévin",
11 | "scripts": {
12 | "start": "webpack-dev-server --env dev",
13 | "test": "jest",
14 | "test:coverage": "jest --coverage",
15 | "test:watch": "jest --watch",
16 | "test:lint": "eslint . --ignore-path .gitignore --cache",
17 | "gh-pages": "webpack --env ghPages",
18 | "gh-pages:deploy": "gh-pages -d gh-pages",
19 | "gh-pages:stats": "webpack --env ghPages --profile --json > stats.json",
20 | "dist": "webpack --env dist",
21 | "dist:es6": "rimraf ./dist-es6 && BABEL_ENV=es6 babel ./src --out-dir ./dist-es6",
22 | "dist:min": "webpack --env distMin",
23 | "dist:modules": "rimraf ./dist-modules && BABEL_ENV=modules babel ./src --out-dir ./dist-modules",
24 | "preversion": "npm run test && npm run dist && npm run dist:min && git commit --allow-empty -am \"Update dist\"",
25 | "prepublish": "npm run dist:es6 && npm run dist:modules",
26 | "postpublish": "npm run gh-pages && npm run gh-pages:deploy",
27 | "postinstall": "node lib/post_install.js"
28 | },
29 | "main": "dist-modules",
30 | "module": "dist-es6",
31 | "jsnext:main": "dist-es6",
32 | "devDependencies": {
33 | "babel-cli": "^6.24.1",
34 | "babel-core": "^6.24.1",
35 | "babel-eslint": "^7.2.3",
36 | "babel-jest": "^20.0.0",
37 | "babel-loader": "^7.0.0",
38 | "babel-plugin-transform-react-remove-prop-types": "^0.4.4",
39 | "babel-preset-es2015": "^6.24.1",
40 | "babel-preset-react": "^6.24.1",
41 | "babel-preset-react-hmre": "^1.1.1",
42 | "catalog": "^2.5.3",
43 | "chai": "^3.5.0",
44 | "clean-webpack-plugin": "^0.1.16",
45 | "css-loader": "^0.28.1",
46 | "eslint": "^3.19.0",
47 | "eslint-config-airbnb": "^14.1.0",
48 | "eslint-loader": "^1.7.1",
49 | "eslint-plugin-import": "^2.2.0",
50 | "eslint-plugin-jsx-a11y": "^4.0.0",
51 | "eslint-plugin-react": "^6.9.0",
52 | "extract-text-webpack-plugin": "^2.1.0",
53 | "file-loader": "^0.11.1",
54 | "gh-pages": "^0.12.0",
55 | "git-prepush-hook": "^1.0.2",
56 | "html-webpack-plugin": "^2.28.0",
57 | "html-webpack-template": "^6.0.1",
58 | "jest": "^20.0.0",
59 | "json-loader": "^0.5.4",
60 | "purecss": "^0.6.2",
61 | "raw-loader": "^0.5.1",
62 | "react": "^15.5.4",
63 | "react-addons-test-utils": "^15.5.1",
64 | "react-dom": "^15.5.4",
65 | "react-github-corner": "^0.3.0",
66 | "rimraf": "^2.6.1",
67 | "style-loader": "^0.17.0",
68 | "sync-exec": "^0.6.2",
69 | "system-bell-webpack-plugin": "^1.0.0",
70 | "url-loader": "^0.5.8",
71 | "webpack": "^2.5.1",
72 | "webpack-dev-server": "^2.4.5",
73 | "webpack-merge": "^4.1.0"
74 | },
75 | "peerDependencies": {
76 | "react": ">= 0.11.2 < 16.0.0"
77 | },
78 | "jest": {
79 | "collectCoverage": true,
80 | "moduleFileExtensions": [
81 | "js",
82 | "jsx"
83 | ],
84 | "moduleDirectories": [
85 | "node_modules",
86 | "packages"
87 | ]
88 | },
89 | "keywords": [
90 | "react",
91 | "reactjs",
92 | "camera"
93 | ],
94 | "pre-push": [
95 | "test"
96 | ]
97 | }
98 |
--------------------------------------------------------------------------------
/src/Camera.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | class Camera extends Component {
5 |
6 | componentWillMount() {
7 | const { video, audio } = this.props;
8 | if (navigator.mediaDevices) {
9 | navigator.mediaDevices.getUserMedia({ video, audio })
10 | .then((mediaStream) => {
11 | this.setState({ mediaStream });
12 | this.video.srcObject = mediaStream;
13 | this.video.play();
14 | })
15 | .catch(error => error);
16 | }
17 | }
18 |
19 | capture() {
20 | const mediaStreamTrack = this.state.mediaStream.getVideoTracks()[0];
21 | const imageCapture = new window.ImageCapture(mediaStreamTrack);
22 |
23 | return imageCapture.takePhoto();
24 | }
25 |
26 | render() {
27 | return (
28 |
29 | { this.props.children }
30 |
32 | );
33 | }
34 | }
35 |
36 | Camera.propTypes = {
37 | audio: PropTypes.bool,
38 | video: PropTypes.bool,
39 | children: PropTypes.element,
40 | style: PropTypes.oneOfType([
41 | PropTypes.string,
42 | PropTypes.number
43 | ])
44 | };
45 |
46 | Camera.defaultProps = {
47 | audio: false,
48 | video: true,
49 | style: {},
50 | children: null
51 | };
52 |
53 | export default Camera;
54 |
55 | const styles = {
56 | base: {
57 | width: '100%',
58 | height: '100%'
59 | }
60 | };
61 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | export { default } from './Camera';
2 |
--------------------------------------------------------------------------------
/style.css:
--------------------------------------------------------------------------------
1 | /* TODO: insert default styles of your component here for easy access */
2 |
--------------------------------------------------------------------------------
/webpack.config.babel.js:
--------------------------------------------------------------------------------
1 | import * as path from 'path';
2 |
3 | import webpack from 'webpack';
4 | import ExtractTextPlugin from 'extract-text-webpack-plugin';
5 | import HtmlWebpackPlugin from 'html-webpack-plugin';
6 | import SystemBellPlugin from 'system-bell-webpack-plugin';
7 | import CleanWebpackPlugin from 'clean-webpack-plugin';
8 | import merge from 'webpack-merge';
9 |
10 | const pkg = require('./package.json');
11 |
12 | const ROOT_PATH = __dirname;
13 | const config = {
14 | paths: {
15 | dist: path.join(ROOT_PATH, 'dist'),
16 | src: path.join(ROOT_PATH, 'src'),
17 | docs: path.join(ROOT_PATH, 'docs'),
18 | ghPages: path.join(ROOT_PATH, 'gh-pages')
19 | },
20 | filename: 'boilerplate',
21 | library: 'Boilerplate'
22 | };
23 |
24 | const common = {
25 | resolve: {
26 | extensions: ['.js', '.css', '.png', '.jpg']
27 | },
28 | module: {
29 | loaders: [
30 | {
31 | test: /\.js$/,
32 | enforce: 'pre',
33 | use: 'eslint-loader',
34 | include: [
35 | config.paths.docs,
36 | config.paths.src
37 | ]
38 | },
39 | {
40 | test: /\.md$/,
41 | use: ['catalog/lib/loader', 'raw-loader']
42 | },
43 | {
44 | test: /\.(jpg|png)$/,
45 | use: {
46 | loader: 'url-loader',
47 | options: {
48 | limit: 10000
49 | }
50 | }
51 | }
52 | ]
53 | },
54 | plugins: [
55 | new SystemBellPlugin()
56 | ]
57 | };
58 |
59 | const siteCommon = {
60 | plugins: [
61 | new HtmlWebpackPlugin({
62 | template: require('html-webpack-template'), // eslint-disable-line global-require
63 | inject: false,
64 | mobile: true,
65 | title: pkg.name,
66 | appMountId: 'app'
67 | }),
68 | new webpack.DefinePlugin({
69 | NAME: JSON.stringify(pkg.name),
70 | USER: JSON.stringify(pkg.user),
71 | VERSION: JSON.stringify(pkg.version)
72 | })
73 | ]
74 | };
75 |
76 | const dev = merge(common, siteCommon, {
77 | devtool: 'eval-source-map',
78 | entry: {
79 | docs: [config.paths.docs]
80 | },
81 | plugins: [
82 | new webpack.DefinePlugin({
83 | 'process.env.NODE_ENV': '"development"'
84 | }),
85 | new webpack.HotModuleReplacementPlugin()
86 | ],
87 | module: {
88 | loaders: [
89 | {
90 | test: /\.css$/,
91 | use: ['style-loader', 'css-loader']
92 | },
93 | {
94 | test: /\.js$/,
95 | use: {
96 | loader: 'babel-loader',
97 | options: {
98 | cacheDirectory: true
99 | }
100 | },
101 | include: [
102 | config.paths.docs,
103 | config.paths.src
104 | ]
105 | }
106 | ]
107 | },
108 | devServer: {
109 | historyApiFallback: true,
110 | hot: true,
111 | inline: true,
112 | host: process.env.HOST,
113 | port: process.env.PORT,
114 | stats: 'errors-only'
115 | }
116 | });
117 |
118 | const ghPages = merge(common, siteCommon, {
119 | entry: {
120 | app: config.paths.docs
121 | },
122 | output: {
123 | path: config.paths.ghPages,
124 | filename: '[name].[chunkhash].js',
125 | chunkFilename: '[chunkhash].js'
126 | },
127 | plugins: [
128 | new CleanWebpackPlugin(['gh-pages'], {
129 | verbose: false
130 | }),
131 | new ExtractTextPlugin('[name].[chunkhash].css'),
132 | new webpack.DefinePlugin({
133 | // This affects the react lib size
134 | 'process.env.NODE_ENV': '"production"'
135 | }),
136 | new webpack.optimize.UglifyJsPlugin({
137 | compress: {
138 | warnings: false
139 | }
140 | }),
141 | new webpack.optimize.CommonsChunkPlugin({
142 | name: 'vendor',
143 | minChunks: ({ resource }) => (
144 | resource &&
145 | resource.indexOf('node_modules') >= 0 &&
146 | resource.match(/\.js$/)
147 | )
148 | })
149 | ],
150 | module: {
151 | loaders: [
152 | {
153 | test: /\.css$/,
154 | use: ExtractTextPlugin.extract({
155 | fallback: 'style-loader',
156 | use: 'css-loader'
157 | })
158 | },
159 | {
160 | test: /\.js$/,
161 | use: 'babel-loader',
162 | include: [
163 | config.paths.docs,
164 | config.paths.src
165 | ]
166 | }
167 | ]
168 | }
169 | });
170 |
171 | const distCommon = {
172 | devtool: 'source-map',
173 | output: {
174 | path: config.paths.dist,
175 | libraryTarget: 'umd',
176 | library: config.library
177 | },
178 | entry: config.paths.src,
179 | externals: {
180 | react: {
181 | commonjs: 'react',
182 | commonjs2: 'react',
183 | amd: 'React',
184 | root: 'React'
185 | }
186 | },
187 | module: {
188 | loaders: [
189 | {
190 | test: /\.js$/,
191 | use: 'babel-loader',
192 | include: config.paths.src
193 | }
194 | ]
195 | },
196 | plugins: [
197 | new SystemBellPlugin()
198 | ]
199 | };
200 |
201 | const dist = merge(distCommon, {
202 | output: {
203 | filename: `${config.filename}.js`
204 | }
205 | });
206 |
207 | const distMin = merge(distCommon, {
208 | output: {
209 | filename: `${config.filename}.min.js`
210 | },
211 | plugins: [
212 | new webpack.optimize.UglifyJsPlugin({
213 | compress: {
214 | warnings: false
215 | }
216 | })
217 | ]
218 | });
219 |
220 | module.exports = (env) => {
221 | process.env.BABEL_ENV = env;
222 |
223 | const targets = {
224 | dev,
225 | dist,
226 | distMin,
227 | ghPages
228 | };
229 |
230 | return targets[env] ? targets[env] : common;
231 | };
232 |
--------------------------------------------------------------------------------