crazy
This commit is contained in:
146
api/package-lock.json
generated
146
api/package-lock.json
generated
@@ -1272,6 +1272,11 @@
|
||||
"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": {
|
||||
"version": "1.0.10",
|
||||
"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",
|
||||
"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": {
|
||||
"version": "3.1.0",
|
||||
"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",
|
||||
"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": {
|
||||
"version": "5.0.1",
|
||||
"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",
|
||||
"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": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/dns-prefetch-control/-/dns-prefetch-control-0.2.0.tgz",
|
||||
@@ -4604,7 +4713,6 @@
|
||||
"version": "0.5.5",
|
||||
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz",
|
||||
"integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"minimist": "^1.2.5"
|
||||
}
|
||||
@@ -4619,6 +4727,21 @@
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||
"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": {
|
||||
"version": "0.0.8",
|
||||
"resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz",
|
||||
@@ -4758,8 +4881,7 @@
|
||||
"object-assign": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||
"integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=",
|
||||
"dev": true
|
||||
"integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM="
|
||||
},
|
||||
"object-copy": {
|
||||
"version": "0.1.0",
|
||||
@@ -5163,8 +5285,7 @@
|
||||
"process-nextick-args": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
|
||||
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==",
|
||||
"optional": true
|
||||
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="
|
||||
},
|
||||
"progress": {
|
||||
"version": "2.0.3",
|
||||
@@ -6101,6 +6222,11 @@
|
||||
"resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz",
|
||||
"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": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz",
|
||||
@@ -6443,6 +6569,11 @@
|
||||
"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": {
|
||||
"version": "3.1.5",
|
||||
"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"
|
||||
}
|
||||
},
|
||||
"xtend": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
|
||||
"integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ=="
|
||||
},
|
||||
"yallist": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz",
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
"@babel/node": "7.8.7",
|
||||
"@babel/plugin-proposal-class-properties": "7.8.3",
|
||||
"@babel/preset-env": "7.9.6",
|
||||
"body-parser": "^1.19.0",
|
||||
"cacheman": "2.2.1",
|
||||
"cacheman-file": "0.2.1",
|
||||
"compression": "1.7.4",
|
||||
@@ -25,6 +26,7 @@
|
||||
"form-data": "3.0.0",
|
||||
"helmet": "3.22.0",
|
||||
"jsdom": "16.2.2",
|
||||
"multer": "^1.4.2",
|
||||
"node-fetch": "2.6.0",
|
||||
"puppeteer": "3.1.0"
|
||||
},
|
||||
|
||||
@@ -15,8 +15,8 @@ class PlateRecognizr {
|
||||
|
||||
fetch(image) {
|
||||
const body = new FormData();
|
||||
body.append('upload', fs.createReadStream('test.jpg'));
|
||||
body.append('regions', 'gb'); // Change to your country
|
||||
body.append('upload', image);
|
||||
body.append('regions', 'ee'); // Change to your country
|
||||
return fetch(ALPR_API_PATH, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
|
||||
@@ -2,21 +2,34 @@ import express from 'express';
|
||||
import http from 'http';
|
||||
import compression from 'compression';
|
||||
import helmet from 'helmet';
|
||||
import multer from 'multer';
|
||||
import bodyParser from 'body-parser';
|
||||
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 {
|
||||
app;
|
||||
server;
|
||||
hack;
|
||||
alpr;
|
||||
|
||||
constructor() {
|
||||
this.app = express();
|
||||
this.app.use(express.static('public'));
|
||||
this.app.use(bodyParser.urlencoded({ extended: false }));
|
||||
this.app.use(compression());
|
||||
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.hack = new Hack();
|
||||
this.alpr = new PlateRecognizr();
|
||||
}
|
||||
|
||||
init() {
|
||||
@@ -34,6 +47,14 @@ class BasicApi {
|
||||
.status(200)
|
||||
.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);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -53,6 +53,7 @@ module.exports = (api) => {
|
||||
'@context': './src/context',
|
||||
'@components': './src/components',
|
||||
'@hook': './src/hook',
|
||||
'@actions': './src/actions',
|
||||
'@assets': './assets',
|
||||
}
|
||||
}]
|
||||
|
||||
@@ -125,5 +125,6 @@ module.exports = {
|
||||
],
|
||||
},
|
||||
],
|
||||
'no-console': 0,
|
||||
},
|
||||
};
|
||||
|
||||
BIN
frontend/assets/images/Maanteeamet.png
Normal file
BIN
frontend/assets/images/Maanteeamet.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.0 KiB |
11
frontend/package-lock.json
generated
11
frontend/package-lock.json
generated
@@ -1175,6 +1175,11 @@
|
||||
"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": {
|
||||
"version": "2.1.3",
|
||||
"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": {
|
||||
"version": "0.1.1",
|
||||
"resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz",
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"description": "Base React template",
|
||||
"main": "index.js",
|
||||
"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/*",
|
||||
"bundle": "cross-env NODE_ENV=production webpack",
|
||||
"cacheclean": "rimraf node_modules/.cache/hard-source",
|
||||
@@ -22,6 +22,7 @@
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-free": "5.13.0",
|
||||
"@reduxjs/toolkit": "1.3.6",
|
||||
"core-js": "3.6.5",
|
||||
"react": "16.13.1",
|
||||
@@ -56,6 +57,7 @@
|
||||
"copy-webpack-plugin": "6.0.1",
|
||||
"cross-env": "7.0.2",
|
||||
"css-loader": "3.5.3",
|
||||
"dotenv": "^8.2.0",
|
||||
"eslint": "7.1.0",
|
||||
"eslint-config-airbnb": "18.1.0",
|
||||
"eslint-import-resolver-babel-module": "5.1.2",
|
||||
|
||||
2
frontend/public/assets/css/skeleton.min.css
vendored
2
frontend/public/assets/css/skeleton.min.css
vendored
File diff suppressed because one or more lines are too long
21
frontend/src/actions/CaptureActions.js
Normal file
21
frontend/src/actions/CaptureActions.js
Normal 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;
|
||||
9
frontend/src/components/Button.jsx
Normal file
9
frontend/src/components/Button.jsx
Normal 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;
|
||||
6
frontend/src/components/Loader.jsx
Normal file
6
frontend/src/components/Loader.jsx
Normal file
@@ -0,0 +1,6 @@
|
||||
import React from 'react';
|
||||
import '@style/spinner.scss';
|
||||
|
||||
const Loader = () => (<div className="loader">Loading...</div>);
|
||||
|
||||
export default Loader;
|
||||
108
frontend/src/components/camera/CameraContainer.jsx
Normal file
108
frontend/src/components/camera/CameraContainer.jsx
Normal 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;
|
||||
11
frontend/src/components/camera/CameraOptions.jsx
Normal file
11
frontend/src/components/camera/CameraOptions.jsx
Normal 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;
|
||||
32
frontend/src/components/camera/Capture.jsx
Normal file
32
frontend/src/components/camera/Capture.jsx
Normal 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;
|
||||
6
frontend/src/constant/FacingMode.js
Normal file
6
frontend/src/constant/FacingMode.js
Normal file
@@ -0,0 +1,6 @@
|
||||
export default {
|
||||
FRONT: 'user',
|
||||
REAR: {
|
||||
exact: 'environment',
|
||||
},
|
||||
};
|
||||
@@ -1,90 +1,14 @@
|
||||
import React, {
|
||||
useState, useRef, useEffect, useCallback,
|
||||
} from 'react';
|
||||
import Webcam from 'react-webcam';
|
||||
|
||||
import '@style/RootContainer';
|
||||
import React from 'react';
|
||||
import CameraContainer from '@components/camera/CameraContainer';
|
||||
import '@style/RootContainer.scss';
|
||||
|
||||
const RootContainer = () => {
|
||||
const webcamRef = useRef(null);
|
||||
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);
|
||||
};
|
||||
|
||||
console.log('Hello wlrld');
|
||||
return (
|
||||
<>
|
||||
{resetCameraView ? (
|
||||
'Loading...'
|
||||
) : (
|
||||
<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} />)}
|
||||
</>
|
||||
<div className="container root-container">
|
||||
<div className="header" />
|
||||
<CameraContainer />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
42
frontend/src/hook/useCanvas.js
Normal file
42
frontend/src/hook/useCanvas.js
Normal 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];
|
||||
}
|
||||
@@ -1,10 +1,65 @@
|
||||
@import 'camphor.scss';
|
||||
@import 'palette.scss';
|
||||
@import 'constants.scss';
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: 'Roboto';
|
||||
font-family: 'Camphor' !important;
|
||||
font: 400 13.333px 'Camphor' !important;
|
||||
overflow-x: hidden;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
46
frontend/src/style/camphor.scss
Normal file
46
frontend/src/style/camphor.scss
Normal file
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
||||
$orange: #fed8b1;
|
||||
|
||||
46
frontend/src/style/spinner.scss
Normal file
46
frontend/src/style/spinner.scss
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
/* eslint-disable import/no-extraneous-dependencies */
|
||||
|
||||
const { ProgressPlugin } = require('webpack');
|
||||
const { ProgressPlugin, EnvironmentPlugin } = require('webpack');
|
||||
const convert = require('koa-connect');
|
||||
const history = require('connect-history-api-fallback');
|
||||
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
||||
@@ -71,6 +71,7 @@ module.exports = {
|
||||
extensions: ['*', '.js', '.jsx', '.css', '.scss'],
|
||||
},
|
||||
plugins: [
|
||||
new EnvironmentPlugin(['NODE_ENV', 'API_BASE_PATH']),
|
||||
new ProgressPlugin(),
|
||||
new HtmlWebpackPlugin({
|
||||
template: commonPaths.templatePath,
|
||||
|
||||
@@ -46,44 +46,44 @@ module.exports = {
|
||||
contentBase: commonPaths.outputPath,
|
||||
compress: true,
|
||||
hot: true,
|
||||
port: 3001,
|
||||
port: 3000,
|
||||
},
|
||||
plugins: [
|
||||
new HotModuleReplacementPlugin(),
|
||||
new HardSourceWebpackPlugin({
|
||||
// Either an absolute path or relative to webpack's options.context.
|
||||
cacheDirectory: commonPaths.cachePath,
|
||||
// Either a string of object hash function given a webpack config.
|
||||
// node-object-hash on npm can be used to build this.
|
||||
configHash: (webpackConfig) => require('node-object-hash')({ // eslint-disable-line
|
||||
sort: false,
|
||||
}).hash(webpackConfig),
|
||||
// Either false, a string, an object, or a project hashing function.
|
||||
environmentHash: {
|
||||
root: process.cwd(),
|
||||
directories: [],
|
||||
files: [
|
||||
// Cache will get an unique hash based on those files
|
||||
// if either of them changes, new cache must be generated
|
||||
'package-lock.json',
|
||||
'.babelrc.js',
|
||||
],
|
||||
},
|
||||
info: {
|
||||
mode: 'none',
|
||||
level: 'debug',
|
||||
},
|
||||
// Clean up large, old caches automatically.
|
||||
cachePrune: {
|
||||
// Caches younger than `maxAge` are not considered for deletion.
|
||||
// They must be at least this old in milliseconds.
|
||||
maxAge: 3 * 60 * 60 * 1000, // 3 hours
|
||||
// All caches together must be larger than `sizeThreshold` before any
|
||||
// caches will be deleted.
|
||||
// Together they must be at least this big in bytes.
|
||||
sizeThreshold: 50 * 1024 * 1024, // 50 MB
|
||||
},
|
||||
}),
|
||||
// new HardSourceWebpackPlugin({
|
||||
// // Either an absolute path or relative to webpack's options.context.
|
||||
// cacheDirectory: commonPaths.cachePath,
|
||||
// // Either a string of object hash function given a webpack config.
|
||||
// // node-object-hash on npm can be used to build this.
|
||||
// configHash: (webpackConfig) => require('node-object-hash')({ // eslint-disable-line
|
||||
// sort: false,
|
||||
// }).hash(webpackConfig),
|
||||
// // Either false, a string, an object, or a project hashing function.
|
||||
// environmentHash: {
|
||||
// root: process.cwd(),
|
||||
// directories: [],
|
||||
// files: [
|
||||
// // Cache will get an unique hash based on those files
|
||||
// // if either of them changes, new cache must be generated
|
||||
// 'package-lock.json',
|
||||
// '.babelrc.js',
|
||||
// ],
|
||||
// },
|
||||
// info: {
|
||||
// mode: 'none',
|
||||
// level: 'debug',
|
||||
// },
|
||||
// // Clean up large, old caches automatically.
|
||||
// cachePrune: {
|
||||
// // Caches younger than `maxAge` are not considered for deletion.
|
||||
// // They must be at least this old in milliseconds.
|
||||
// maxAge: 3 * 60 * 60 * 1000, // 3 hours
|
||||
// // All caches together must be larger than `sizeThreshold` before any
|
||||
// // caches will be deleted.
|
||||
// // Together they must be at least this big in bytes.
|
||||
// sizeThreshold: 50 * 1024 * 1024, // 50 MB
|
||||
// },
|
||||
// }),
|
||||
new BundleAnalyzerPlugin({
|
||||
analyzerPort: 8888,
|
||||
openAnalyzer: false,
|
||||
|
||||
Reference in New Issue
Block a user