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"
}
},
"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",

View File

@@ -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"
},

View File

@@ -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: {

View File

@@ -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);
});
}
}

View File

@@ -53,6 +53,7 @@ module.exports = (api) => {
'@context': './src/context',
'@components': './src/components',
'@hook': './src/hook',
'@actions': './src/actions',
'@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"
}
},
"@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",

View File

@@ -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",

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, {
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>
);
};

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 '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;
}
}

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 */
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,

View File

@@ -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,