This commit is contained in:
2020-05-25 20:59:33 +03:00
parent 2e4f90d8ff
commit 81bf9dc585
25 changed files with 614 additions and 132 deletions

146
api/package-lock.json generated
View File

@@ -1272,6 +1272,11 @@
"picomatch": "^2.0.4" "picomatch": "^2.0.4"
} }
}, },
"append-field": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz",
"integrity": "sha1-HjRA6RXwsSA9I3SOeO3XubW0PlY="
},
"argparse": { "argparse": {
"version": "1.0.10", "version": "1.0.10",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
@@ -1687,6 +1692,38 @@
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz",
"integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==" "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A=="
}, },
"busboy": {
"version": "0.2.14",
"resolved": "https://registry.npmjs.org/busboy/-/busboy-0.2.14.tgz",
"integrity": "sha1-bCpiLvz0fFe7vh4qnDetNseSVFM=",
"requires": {
"dicer": "0.2.5",
"readable-stream": "1.1.x"
},
"dependencies": {
"isarray": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
"integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8="
},
"readable-stream": {
"version": "1.1.14",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz",
"integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=",
"requires": {
"core-util-is": "~1.0.0",
"inherits": "~2.0.1",
"isarray": "0.0.1",
"string_decoder": "~0.10.x"
}
},
"string_decoder": {
"version": "0.10.31",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
"integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ="
}
}
},
"bytes": { "bytes": {
"version": "3.1.0", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz",
@@ -1996,6 +2033,46 @@
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
}, },
"concat-stream": {
"version": "1.6.2",
"resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz",
"integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==",
"requires": {
"buffer-from": "^1.0.0",
"inherits": "^2.0.3",
"readable-stream": "^2.2.2",
"typedarray": "^0.0.6"
},
"dependencies": {
"readable-stream": {
"version": "2.3.7",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
"integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==",
"requires": {
"core-util-is": "~1.0.0",
"inherits": "~2.0.3",
"isarray": "~1.0.0",
"process-nextick-args": "~2.0.0",
"safe-buffer": "~5.1.1",
"string_decoder": "~1.1.1",
"util-deprecate": "~1.0.1"
}
},
"safe-buffer": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
},
"string_decoder": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
"requires": {
"safe-buffer": "~5.1.0"
}
}
}
},
"configstore": { "configstore": {
"version": "5.0.1", "version": "5.0.1",
"resolved": "https://registry.npmjs.org/configstore/-/configstore-5.0.1.tgz", "resolved": "https://registry.npmjs.org/configstore/-/configstore-5.0.1.tgz",
@@ -2309,6 +2386,38 @@
"resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz",
"integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA="
}, },
"dicer": {
"version": "0.2.5",
"resolved": "https://registry.npmjs.org/dicer/-/dicer-0.2.5.tgz",
"integrity": "sha1-WZbAhrszIYyBLAkL3cCc0S+stw8=",
"requires": {
"readable-stream": "1.1.x",
"streamsearch": "0.1.2"
},
"dependencies": {
"isarray": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
"integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8="
},
"readable-stream": {
"version": "1.1.14",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz",
"integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=",
"requires": {
"core-util-is": "~1.0.0",
"inherits": "~2.0.1",
"isarray": "0.0.1",
"string_decoder": "~0.10.x"
}
},
"string_decoder": {
"version": "0.10.31",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
"integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ="
}
}
},
"dns-prefetch-control": { "dns-prefetch-control": {
"version": "0.2.0", "version": "0.2.0",
"resolved": "https://registry.npmjs.org/dns-prefetch-control/-/dns-prefetch-control-0.2.0.tgz", "resolved": "https://registry.npmjs.org/dns-prefetch-control/-/dns-prefetch-control-0.2.0.tgz",
@@ -4604,7 +4713,6 @@
"version": "0.5.5", "version": "0.5.5",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz",
"integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==",
"dev": true,
"requires": { "requires": {
"minimist": "^1.2.5" "minimist": "^1.2.5"
} }
@@ -4619,6 +4727,21 @@
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
}, },
"multer": {
"version": "1.4.2",
"resolved": "https://registry.npmjs.org/multer/-/multer-1.4.2.tgz",
"integrity": "sha512-xY8pX7V+ybyUpbYMxtjM9KAiD9ixtg5/JkeKUTD6xilfDv0vzzOFcCp4Ljb1UU3tSOM3VTZtKo63OmzOrGi3Cg==",
"requires": {
"append-field": "^1.0.0",
"busboy": "^0.2.11",
"concat-stream": "^1.5.2",
"mkdirp": "^0.5.1",
"object-assign": "^4.1.1",
"on-finished": "^2.3.0",
"type-is": "^1.6.4",
"xtend": "^4.0.0"
}
},
"mute-stream": { "mute-stream": {
"version": "0.0.8", "version": "0.0.8",
"resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz",
@@ -4758,8 +4881,7 @@
"object-assign": { "object-assign": {
"version": "4.1.1", "version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
"integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM="
"dev": true
}, },
"object-copy": { "object-copy": {
"version": "0.1.0", "version": "0.1.0",
@@ -5163,8 +5285,7 @@
"process-nextick-args": { "process-nextick-args": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="
"optional": true
}, },
"progress": { "progress": {
"version": "2.0.3", "version": "2.0.3",
@@ -6101,6 +6222,11 @@
"resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz", "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz",
"integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=" "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks="
}, },
"streamsearch": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-0.1.2.tgz",
"integrity": "sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo="
},
"string-width": { "string-width": {
"version": "4.2.0", "version": "4.2.0",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz",
@@ -6443,6 +6569,11 @@
"mime-types": "~2.1.24" "mime-types": "~2.1.24"
} }
}, },
"typedarray": {
"version": "0.0.6",
"resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
"integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c="
},
"typedarray-to-buffer": { "typedarray-to-buffer": {
"version": "3.1.5", "version": "3.1.5",
"resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz",
@@ -6878,6 +7009,11 @@
"@babel/runtime-corejs3": "^7.8.3" "@babel/runtime-corejs3": "^7.8.3"
} }
}, },
"xtend": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
"integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ=="
},
"yallist": { "yallist": {
"version": "2.1.2", "version": "2.1.2",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz",

View File

@@ -16,6 +16,7 @@
"@babel/node": "7.8.7", "@babel/node": "7.8.7",
"@babel/plugin-proposal-class-properties": "7.8.3", "@babel/plugin-proposal-class-properties": "7.8.3",
"@babel/preset-env": "7.9.6", "@babel/preset-env": "7.9.6",
"body-parser": "^1.19.0",
"cacheman": "2.2.1", "cacheman": "2.2.1",
"cacheman-file": "0.2.1", "cacheman-file": "0.2.1",
"compression": "1.7.4", "compression": "1.7.4",
@@ -25,6 +26,7 @@
"form-data": "3.0.0", "form-data": "3.0.0",
"helmet": "3.22.0", "helmet": "3.22.0",
"jsdom": "16.2.2", "jsdom": "16.2.2",
"multer": "^1.4.2",
"node-fetch": "2.6.0", "node-fetch": "2.6.0",
"puppeteer": "3.1.0" "puppeteer": "3.1.0"
}, },

View File

@@ -15,8 +15,8 @@ class PlateRecognizr {
fetch(image) { fetch(image) {
const body = new FormData(); const body = new FormData();
body.append('upload', fs.createReadStream('test.jpg')); body.append('upload', image);
body.append('regions', 'gb'); // Change to your country body.append('regions', 'ee'); // Change to your country
return fetch(ALPR_API_PATH, { return fetch(ALPR_API_PATH, {
method: 'POST', method: 'POST',
headers: { headers: {

View File

@@ -2,21 +2,34 @@ import express from 'express';
import http from 'http'; import http from 'http';
import compression from 'compression'; import compression from 'compression';
import helmet from 'helmet'; import helmet from 'helmet';
import multer from 'multer';
import bodyParser from 'body-parser';
import Hack from '../components/Hack'; import Hack from '../components/Hack';
import PlateRecognizr from '../api/PlateRecognizr';
const PORT = process.env.PORT || "8000"; const PORT = process.env.PORT || '8000';
const upload = multer({ dest: '/tmp' });
class BasicApi { class BasicApi {
app; app;
server; server;
hack; hack;
alpr;
constructor() { constructor() {
this.app = express(); this.app = express();
this.app.use(express.static('public'));
this.app.use(bodyParser.urlencoded({ extended: false }));
this.app.use(compression()); this.app.use(compression());
this.app.use(helmet()); this.app.use(helmet());
this.app.use((req, res, next) => {
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept');
next();
});
this.server = http.createServer(this.app); this.server = http.createServer(this.app);
this.hack = new Hack(); this.hack = new Hack();
this.alpr = new PlateRecognizr();
} }
init() { init() {
@@ -34,6 +47,14 @@ class BasicApi {
.status(200) .status(200)
.send(this.hack.getCar()); .send(this.hack.getCar());
}); });
this.app.post('/alpr', upload.single('upload'), async ({ body }, response) => {
const { upload: image } = body;
console.log(Object.keys(body));
const plate = await (await this.alpr.fetch(image)).json();
console.log(plate);
response.status(200).json(plate);
});
} }
} }

View File

@@ -53,6 +53,7 @@ module.exports = (api) => {
'@context': './src/context', '@context': './src/context',
'@components': './src/components', '@components': './src/components',
'@hook': './src/hook', '@hook': './src/hook',
'@actions': './src/actions',
'@assets': './assets', '@assets': './assets',
} }
}] }]

View File

@@ -125,5 +125,6 @@ module.exports = {
], ],
}, },
], ],
'no-console': 0,
}, },
}; };

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

View File

@@ -1175,6 +1175,11 @@
"to-fast-properties": "^2.0.0" "to-fast-properties": "^2.0.0"
} }
}, },
"@fortawesome/fontawesome-free": {
"version": "5.13.0",
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-5.13.0.tgz",
"integrity": "sha512-xKOeQEl5O47GPZYIMToj6uuA2syyFlq9EMSl2ui0uytjY9xbe8XS0pexNWmxrdcCyNGyDmLyYw5FtKsalBUeOg=="
},
"@nodelib/fs.scandir": { "@nodelib/fs.scandir": {
"version": "2.1.3", "version": "2.1.3",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.3.tgz", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.3.tgz",
@@ -4356,6 +4361,12 @@
} }
} }
}, },
"dotenv": {
"version": "8.2.0",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.2.0.tgz",
"integrity": "sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw==",
"dev": true
},
"duplexer": { "duplexer": {
"version": "0.1.1", "version": "0.1.1",
"resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz", "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz",

View File

@@ -4,7 +4,7 @@
"description": "Base React template", "description": "Base React template",
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {
"start": "nodemon --exec \"cross-env NODE_ENV=development webpack-dev-server\"", "start": "nodemon --exec \"cross-env NODE_ENV=development webpack-dev-server -r dotenv/config \"",
"prebundle": "rimraf build/*", "prebundle": "rimraf build/*",
"bundle": "cross-env NODE_ENV=production webpack", "bundle": "cross-env NODE_ENV=production webpack",
"cacheclean": "rimraf node_modules/.cache/hard-source", "cacheclean": "rimraf node_modules/.cache/hard-source",
@@ -22,6 +22,7 @@
] ]
}, },
"dependencies": { "dependencies": {
"@fortawesome/fontawesome-free": "5.13.0",
"@reduxjs/toolkit": "1.3.6", "@reduxjs/toolkit": "1.3.6",
"core-js": "3.6.5", "core-js": "3.6.5",
"react": "16.13.1", "react": "16.13.1",
@@ -56,6 +57,7 @@
"copy-webpack-plugin": "6.0.1", "copy-webpack-plugin": "6.0.1",
"cross-env": "7.0.2", "cross-env": "7.0.2",
"css-loader": "3.5.3", "css-loader": "3.5.3",
"dotenv": "^8.2.0",
"eslint": "7.1.0", "eslint": "7.1.0",
"eslint-config-airbnb": "18.1.0", "eslint-config-airbnb": "18.1.0",
"eslint-import-resolver-babel-module": "5.1.2", "eslint-import-resolver-babel-module": "5.1.2",

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,21 @@
const API_BASE_PATH = process.env.NODE_ENV === 'production'
? process.env.API_BASE_PATH
: 'http://localhost:4000';
export const getPlateRecognized = async (image) => {
const body = new FormData();
body.append('upload', image);
try {
const response = await fetch(`${API_BASE_PATH}/alpr`, {
method: 'POST',
body,
});
console.log(response);
const data = await response.json();
console.log(data);
} catch (e) {
console.log(e);
}
};
export const getPlateData = (plate) => null;

View File

@@ -0,0 +1,9 @@
import React from 'react';
const Button = ({ text, onClick }) => (
<button className="button light" type="button" onClick={onClick}>
{text}
</button>
);
export default Button;

View File

@@ -0,0 +1,6 @@
import React from 'react';
import '@style/spinner.scss';
const Loader = () => (<div className="loader">Loading...</div>);
export default Loader;

View File

@@ -0,0 +1,108 @@
import React, {
useState, useEffect, useRef, useCallback,
} from 'react';
import Webcam from 'react-webcam';
import FacingMode from '@constant/FacingMode';
import Loader from '@components/Loader';
import '@style/RootContainer.scss';
import CameraOptions from './CameraOptions';
import Capture from './Capture';
const CAMERA_OPTIONS = {
format: 'image/jpeg',
minHeight: 1920,
minWidth: 1080,
quality: 0.75,
defaultView: FacingMode.FRONT,
};
const CameraContainer = () => {
const ref = useRef(null);
const [imgSrc, setImgSrc] = useState(null);
const [captureError, setCaptureError] = useState(null);
const [videoInputs, setVideoInputs] = useState([]);
const [resetCameraView, setResetCameraView] = useState(false);
const [videoConstraints, setVideoConstraints] = useState({
facingMode: CAMERA_OPTIONS.defaultView,
});
useEffect(() => {
const gotDevices = (mediaDevices) => new Promise((resolve, reject) => {
const availableVideoInputs = mediaDevices
.filter(({ kind }) => kind === 'videoinput')
.map(({ deviceId, label }) => ({ deviceId, label }));
if (availableVideoInputs.length > 0) {
resolve(availableVideoInputs);
} else {
reject(new Error('ERR::NO_MEDIA_TO_STREAM'));
}
});
navigator.mediaDevices
.enumerateDevices()
.then(gotDevices)
.then((availableVideoInputs) => setVideoInputs({ availableVideoInputs }))
.catch((err) => console.error(err));
}, []);
const changeCameraView = () => {
if (videoInputs.length === 1) {
console.error('ERR::AVAILABLE_MEDIA_STREAMS_IS_1');
return;
}
setResetCameraView(true);
setTimeout(() => {
const { facingMode } = videoConstraints;
const newFacingMode = facingMode === FacingMode.FRONT ? FacingMode.REAR : FacingMode.FRONT;
setResetCameraView(false);
setVideoConstraints({ ...videoConstraints, facingMode: newFacingMode });
}, 100);
};
const capture = useCallback(() => {
const imageSrc = ref.current.getScreenshot();
setImgSrc(imageSrc);
if (!imageSrc) {
setCaptureError(['Failed to capture camera. Please try again.']);
} else {
setCaptureError(null);
}
}, [ref, setImgSrc]);
const onBackClick = () => setImgSrc(null);
const cameraView = () => (
<>
<div className="row">
{resetCameraView ? (
<Loader />
) : (
<Webcam
mirrored={false}
width="100%"
height="100%"
audio={false}
ref={ref}
screenshotFormat={CAMERA_OPTIONS.format}
minScreenshotWidth={CAMERA_OPTIONS.minWidth}
minScreenshotHeight={CAMERA_OPTIONS.minHeight}
screenshotQuality={CAMERA_OPTIONS.quality}
videoConstraints={videoConstraints} />
)}
</div>
<CameraOptions capture={capture} changeCameraView={changeCameraView} />
</>
);
return (
<>
{imgSrc
? <Capture imgSrc={imgSrc} error={captureError} onBackClick={onBackClick} />
: cameraView()}
</>
);
};
export default CameraContainer;

View File

@@ -0,0 +1,11 @@
import React from 'react';
import Button from '@components/Button';
const CameraOptions = ({ capture, changeCameraView }) => (
<div className="row button-container">
<Button text="Switch camera" onClick={changeCameraView} />
<Button text="Take image" onClick={capture} />
</div>
);
export default CameraOptions;

View File

@@ -0,0 +1,32 @@
import React from 'react';
import Loader from '@components/Loader';
import { getPlateRecognized } from '@actions/CaptureActions';
import Button from '@components/Button';
const UploadCapture = ({ imgSrc, onBackClick }) => {
const onUploadClick = () => {
getPlateRecognized(imgSrc);
};
return (
<div className="container">
<div className="row">
<img src={imgSrc} alt="captured" />
</div>
<div className="row button-container">
<Button text="Back" onClick={onBackClick} />
<Button text="Upload" onClick={onUploadClick} />
</div>
</div>
);
};
const Capture = ({
imgSrc, isCapturing, error, onBackClick,
}) => (
<div className="capture-container row">
{isCapturing && (<Loader />)}
{error ? (<p>{error}</p>) : (<UploadCapture imgSrc={imgSrc} onBackClick={onBackClick} />)}
</div>
);
export default Capture;

View File

@@ -0,0 +1,6 @@
export default {
FRONT: 'user',
REAR: {
exact: 'environment',
},
};

View File

@@ -1,90 +1,14 @@
import React, { import React from 'react';
useState, useRef, useEffect, useCallback, import CameraContainer from '@components/camera/CameraContainer';
} from 'react'; import '@style/RootContainer.scss';
import Webcam from 'react-webcam';
import '@style/RootContainer';
const RootContainer = () => { const RootContainer = () => {
const webcamRef = useRef(null); console.log('Hello wlrld');
const [videoInputs, setVideoInputs] = useState([]);
const [resetCameraView, setResetCameraView] = useState(false);
const [videoConstraints, setVideoConstraints] = useState({
facingMode: { exact: 'environment' },
});
const [imgSrc, setImgSrc] = useState(null);
const capture = useCallback(() => {
const imageSrc = webcamRef.current.getScreenshot();
setImgSrc(imageSrc);
}, [webcamRef, setImgSrc]);
useEffect(() => {
const gotDevices = (mediaDevices) => new Promise((resolve, reject) => {
const availableVideoInputs = [];
mediaDevices.forEach((mediaDevice) => {
if (mediaDevice.kind === 'videoinput') {
availableVideoInputs.push({
deviceId: mediaDevice.deviceId,
label: mediaDevice.label,
});
}
});
if (availableVideoInputs.length > 0) {
resolve(availableVideoInputs);
} else {
reject(new Error('ERR::NO_MEDIA_TO_STREAM'));
}
});
navigator.mediaDevices
.enumerateDevices()
.then(gotDevices)
.then((availableVideoInputs) => setVideoInputs({ availableVideoInputs }))
.catch((err) => console.error(err));
}, []);
const changeCameraView = () => {
if (videoInputs.length === 1) {
return console.error('ERR::AVAILABLE_MEDIA_STREAMS_IS_1');
}
setResetCameraView(true);
setTimeout(() => {
const { facingMode } = videoConstraints;
const newFacingMode = facingMode === 'user' ? { exact: 'environment' } : 'user';
setResetCameraView(false);
setVideoConstraints({ ...videoConstraints, facingMode: newFacingMode });
}, 100);
};
return ( return (
<> <div className="container root-container">
{resetCameraView ? ( <div className="header" />
'Loading...' <CameraContainer />
) : ( </div>
<Webcam
audio={false}
height="100%"
ref={webcamRef}
screenshotFormat="image/png"
minScreenshotWidth={1080}
minScreenshotHeight={1920}
screenshotQuality={1}
width="100%"
videoConstraints={videoConstraints} />
)}
<button type="button" onClick={changeCameraView}>
Switch camera
</button>
<button type="button" onClick={capture}>
Take image
</button>
{imgSrc && (<img src={imgSrc} />)}
</>
); );
}; };

View File

@@ -0,0 +1,42 @@
import React, { useState, useEffect, useRef } from 'react';
// Path2D for a Heart SVG
const heartSVG = 'M0 200 v-200 h200 a100,100 90 0,1 0,200 a100,100 90 0,1 -200,0 z';
const SVG_PATH = new Path2D(heartSVG);
// Scaling Constants for Canvas
const SCALE = 0.1;
const OFFSET = 80;
export const canvasWidth = window.innerWidth * 0.7;
export const canvasHeight = window.innerHeight * 0.7;
export function draw(ctx, location) {
console.log('attempting to draw');
ctx.fillStyle = 'red';
ctx.shadowColor = 'blue';
ctx.shadowBlur = 15;
ctx.save();
ctx.scale(SCALE, SCALE);
ctx.translate(location.x / SCALE - OFFSET, location.y / SCALE - OFFSET);
ctx.rotate(225 * Math.PI / 180);
ctx.fill(SVG_PATH);
// .restore(): Canvas 2D API restores the most recently saved canvas state
ctx.restore();
}
export function useCanvas() {
const canvasRef = useRef(null);
const [coordinates, setCoordinates] = useState([]);
useEffect(() => {
const canvasObj = canvasRef.current;
const ctx = canvasObj.getContext('2d');
// clear the canvas area before rendering the coordinates held in state
ctx.clearRect(0, 0, canvasObj.parentNode.clientWidth, canvasObj.parentNode.clientHeight);
// draw all coordinates held in state
coordinates.forEach((coordinate) => { draw(ctx, coordinate); });
});
return [coordinates, setCoordinates, canvasRef, canvasWidth, canvasHeight];
}

View File

@@ -1,10 +1,65 @@
@import 'camphor.scss';
@import 'palette.scss'; @import 'palette.scss';
@import 'constants.scss'; @import 'constants.scss';
body { body {
margin: 0; margin: 0;
font-family: 'Roboto'; font-family: 'Camphor' !important;
font: 400 13.333px 'Camphor' !important;
overflow-x: hidden; overflow-x: hidden;
width: 100vw; width: 100vw;
height: 100vh; background-color: $orange;
}
.root-container {
//box-shadow: 0 30px 40px rgba(0,0,0,.1);
padding: 2em;
margin-top: 2em;
height: 90vh;
display: flex;
flex-direction: column;
.header {
background-image: url('../../assets/images/Maanteeamet.png');
background-size: cover;
height: 120px;
margin-bottom: 3em;
background-repeat: no-repeat;
background-position: center;
}
img {
text-align: center;
}
}
.button-container {
margin-top: 1em;
text-align: center;
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
button {
margin: 0.25em;
font-family: inherit;
&.light {
background-color: rgba(255, 121, 121, 0.5);
border: 1px solid rgba(255, 121, 121, 1.0);
}
}
}
.capture-container {
canvas {
max-width: 100%;
width: 100%;
}
& img {
max-width: 100%;
max-height: auto;
}
} }

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
$orange: #fed8b1;

View File

@@ -0,0 +1,46 @@
.loader,
.loader:after {
border-radius: 50%;
width: 10em;
height: 10em;
}
.loader {
margin: 60px auto;
font-size: 10px;
position: relative;
text-indent: -9999em;
border-top: 1.1em solid rgba(255, 255, 255, 0.2);
border-right: 1.1em solid rgba(255, 255, 255, 0.2);
border-bottom: 1.1em solid rgba(255, 255, 255, 0.2);
border-left: 1.1em solid #ffffff;
-webkit-transform: translateZ(0);
-ms-transform: translateZ(0);
transform: translateZ(0);
-webkit-animation: load8 1.1s infinite linear;
animation: load8 1.1s infinite linear;
}
@-webkit-keyframes load8 {
0% {
-webkit-transform: rotate(0deg);
transform: rotate(0deg);
}
100% {
-webkit-transform: rotate(360deg);
transform: rotate(360deg);
}
}
@keyframes load8 {
0% {
-webkit-transform: rotate(0deg);
transform: rotate(0deg);
}
100% {
-webkit-transform: rotate(360deg);
transform: rotate(360deg);
}
}

View File

@@ -1,6 +1,6 @@
/* eslint-disable import/no-extraneous-dependencies */ /* eslint-disable import/no-extraneous-dependencies */
const { ProgressPlugin } = require('webpack'); const { ProgressPlugin, EnvironmentPlugin } = require('webpack');
const convert = require('koa-connect'); const convert = require('koa-connect');
const history = require('connect-history-api-fallback'); const history = require('connect-history-api-fallback');
const HtmlWebpackPlugin = require('html-webpack-plugin'); const HtmlWebpackPlugin = require('html-webpack-plugin');
@@ -71,6 +71,7 @@ module.exports = {
extensions: ['*', '.js', '.jsx', '.css', '.scss'], extensions: ['*', '.js', '.jsx', '.css', '.scss'],
}, },
plugins: [ plugins: [
new EnvironmentPlugin(['NODE_ENV', 'API_BASE_PATH']),
new ProgressPlugin(), new ProgressPlugin(),
new HtmlWebpackPlugin({ new HtmlWebpackPlugin({
template: commonPaths.templatePath, template: commonPaths.templatePath,

View File

@@ -46,44 +46,44 @@ module.exports = {
contentBase: commonPaths.outputPath, contentBase: commonPaths.outputPath,
compress: true, compress: true,
hot: true, hot: true,
port: 3001, port: 3000,
}, },
plugins: [ plugins: [
new HotModuleReplacementPlugin(), new HotModuleReplacementPlugin(),
new HardSourceWebpackPlugin({ // new HardSourceWebpackPlugin({
// Either an absolute path or relative to webpack's options.context. // // Either an absolute path or relative to webpack's options.context.
cacheDirectory: commonPaths.cachePath, // cacheDirectory: commonPaths.cachePath,
// Either a string of object hash function given a webpack config. // // Either a string of object hash function given a webpack config.
// node-object-hash on npm can be used to build this. // // node-object-hash on npm can be used to build this.
configHash: (webpackConfig) => require('node-object-hash')({ // eslint-disable-line // configHash: (webpackConfig) => require('node-object-hash')({ // eslint-disable-line
sort: false, // sort: false,
}).hash(webpackConfig), // }).hash(webpackConfig),
// Either false, a string, an object, or a project hashing function. // // Either false, a string, an object, or a project hashing function.
environmentHash: { // environmentHash: {
root: process.cwd(), // root: process.cwd(),
directories: [], // directories: [],
files: [ // files: [
// Cache will get an unique hash based on those files // // Cache will get an unique hash based on those files
// if either of them changes, new cache must be generated // // if either of them changes, new cache must be generated
'package-lock.json', // 'package-lock.json',
'.babelrc.js', // '.babelrc.js',
], // ],
}, // },
info: { // info: {
mode: 'none', // mode: 'none',
level: 'debug', // level: 'debug',
}, // },
// Clean up large, old caches automatically. // // Clean up large, old caches automatically.
cachePrune: { // cachePrune: {
// Caches younger than `maxAge` are not considered for deletion. // // Caches younger than `maxAge` are not considered for deletion.
// They must be at least this old in milliseconds. // // They must be at least this old in milliseconds.
maxAge: 3 * 60 * 60 * 1000, // 3 hours // maxAge: 3 * 60 * 60 * 1000, // 3 hours
// All caches together must be larger than `sizeThreshold` before any // // All caches together must be larger than `sizeThreshold` before any
// caches will be deleted. // // caches will be deleted.
// Together they must be at least this big in bytes. // // Together they must be at least this big in bytes.
sizeThreshold: 50 * 1024 * 1024, // 50 MB // sizeThreshold: 50 * 1024 * 1024, // 50 MB
}, // },
}), // }),
new BundleAnalyzerPlugin({ new BundleAnalyzerPlugin({
analyzerPort: 8888, analyzerPort: 8888,
openAnalyzer: false, openAnalyzer: false,