Merged feature/109 to 1.0
commit
9221529e16
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -0,0 +1,22 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"/>
|
||||
|
||||
<title>index</title>
|
||||
<meta name="description" content=""/>
|
||||
<meta name="author" content="Christoph Oberhofer"/>
|
||||
|
||||
<meta name="viewport" content="width=device-width; initial-scale=1.0"/>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<section id="container" class="container">
|
||||
<div id="interactive" class="viewport"></div>
|
||||
<div id="debug" class="detection"></div>
|
||||
</section>
|
||||
<script src="../dist/quagga.js" type="text/javascript"></script>
|
||||
<script src="api-test.js" type="text/javascript"></script>
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,67 @@
|
||||
console.log(typeof Quagga);
|
||||
|
||||
|
||||
// creates a new instance!
|
||||
var code128Scanner = Quagga
|
||||
.decoder({readers: ['code_128_reader']})
|
||||
.locator({patchSize: 'medium'});
|
||||
|
||||
var eanScanner = Quagga
|
||||
.decoder({readers: ['ean_reader']})
|
||||
.locator({patchSize: 'medium'});
|
||||
|
||||
var i2of5Scanner = Quagga
|
||||
.decoder({readers: ['i2of5_reader']})
|
||||
.locator({patchSize: 'small', halfSample: false});
|
||||
|
||||
/* eanScanner
|
||||
.fromImage('../test/fixtures/ean/image-001.jpg', {size: 640})
|
||||
.toPromise().then((result) => {
|
||||
console.log(result.codeResult.code);
|
||||
}).catch(() => {
|
||||
console.log("EAN not found!");
|
||||
});
|
||||
|
||||
i2of5Scanner
|
||||
.fromImage('../test/fixtures/i2of5/image-001.jpg', {size: 800})
|
||||
.addEventListener('detected', (result) => {
|
||||
console.log("Detected: " + result.codeResult.code);
|
||||
})
|
||||
.addEventListener('processed', (result) => {
|
||||
console.log("Image Processed");
|
||||
})
|
||||
.start(); */
|
||||
|
||||
/* imageReader.addEventListener('processed', (result) => {
|
||||
console.log(result);
|
||||
}); */
|
||||
|
||||
// or
|
||||
|
||||
// uses same canvas?
|
||||
// queue image requests?
|
||||
/*imageReader = customScanner
|
||||
.fromImage('../test/fixtures/ean/image-002.jpg', {size: 640}); */
|
||||
|
||||
/*imageReader.addEventListener('processed', (result) => {
|
||||
console.log(result.codeResult.code);
|
||||
});*/
|
||||
|
||||
code128Scanner = code128Scanner
|
||||
.config({frequency: 2})
|
||||
.fromVideo({
|
||||
constraints: {
|
||||
width: 800,
|
||||
height: 600,
|
||||
facingMode: "environment"
|
||||
}
|
||||
});
|
||||
|
||||
code128Scanner
|
||||
.addEventListener('detected', (result) => {
|
||||
console.log(result);
|
||||
})
|
||||
.addEventListener('processed', result => {
|
||||
console.log("Processed");
|
||||
})
|
||||
.start();
|
@ -1,16 +0,0 @@
|
||||
@charset "UTF-8";
|
||||
/* LESS - http://lesscss.org style sheet */
|
||||
/* Palette color codes */
|
||||
/* Palette URL: http://paletton.com/#uid=31g0q0kHZAviRSkrHLOGomVNzac */
|
||||
/* Feel free to copy&paste color codes to your application */
|
||||
/* MIXINS */
|
||||
/* As hex codes */
|
||||
/* Main Primary color */
|
||||
/* Main Secondary color (1) */
|
||||
/* Main Secondary color (2) */
|
||||
/* As RGBa codes */
|
||||
/* Main Primary color */
|
||||
/* Main Secondary color (1) */
|
||||
/* Main Secondary color (2) */
|
||||
/* Generated by Paletton.com ├é┬® 2002-2014 */
|
||||
/* http://paletton.com */
|
@ -1 +1,31 @@
|
||||
@import url("http://fonts.googleapis.com/css?family=Ubuntu:400,700|Cabin+Condensed:400,600");
|
||||
@import url("https://fonts.googleapis.com/css?family=Ubuntu:400,700|Cabin+Condensed:400,600");
|
||||
|
||||
@font-face {
|
||||
font-family: 'icomoon';
|
||||
src: url('../fonts/icomoon.eot?tad2ln');
|
||||
src: url('../fonts/icomoon.eot?tad2ln#iefix') format('embedded-opentype'),
|
||||
url('../fonts/icomoon.ttf?tad2ln') format('truetype'),
|
||||
url('../fonts/icomoon.woff?tad2ln') format('woff'),
|
||||
url('../fonts/icomoon.svg?tad2ln#icomoon') format('svg');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
[class^="icon-"]:before, [class*=" icon-"]:before {
|
||||
/* use !important to prevent issues with browser extensions that change fonts */
|
||||
font-family: 'icomoon' !important;
|
||||
speak: none;
|
||||
font-style: normal;
|
||||
font-weight: normal;
|
||||
font-variant: normal;
|
||||
text-transform: none;
|
||||
line-height: 1;
|
||||
|
||||
/* Better Font Rendering =========== */
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.icon-barcode:before {
|
||||
content: "\e937";
|
||||
}
|
||||
|
@ -0,0 +1,139 @@
|
||||
/* http://prismjs.com/download.html?themes=prism&languages=markup+css+clike+javascript+jsx */
|
||||
/**
|
||||
* prism.js default theme for JavaScript, CSS and HTML
|
||||
* Based on dabblet (http://dabblet.com)
|
||||
* @author Lea Verou
|
||||
*/
|
||||
|
||||
code[class*="language-"],
|
||||
pre[class*="language-"] {
|
||||
color: black;
|
||||
background: none;
|
||||
text-shadow: 0 1px white;
|
||||
font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
|
||||
text-align: left;
|
||||
white-space: pre;
|
||||
word-spacing: normal;
|
||||
word-break: normal;
|
||||
word-wrap: normal;
|
||||
line-height: 1.5;
|
||||
|
||||
-moz-tab-size: 4;
|
||||
-o-tab-size: 4;
|
||||
tab-size: 4;
|
||||
|
||||
-webkit-hyphens: none;
|
||||
-moz-hyphens: none;
|
||||
-ms-hyphens: none;
|
||||
hyphens: none;
|
||||
}
|
||||
|
||||
pre[class*="language-"]::-moz-selection, pre[class*="language-"] ::-moz-selection,
|
||||
code[class*="language-"]::-moz-selection, code[class*="language-"] ::-moz-selection {
|
||||
text-shadow: none;
|
||||
background: #b3d4fc;
|
||||
}
|
||||
|
||||
pre[class*="language-"]::selection, pre[class*="language-"] ::selection,
|
||||
code[class*="language-"]::selection, code[class*="language-"] ::selection {
|
||||
text-shadow: none;
|
||||
background: #b3d4fc;
|
||||
}
|
||||
|
||||
@media print {
|
||||
code[class*="language-"],
|
||||
pre[class*="language-"] {
|
||||
text-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* Code blocks */
|
||||
pre[class*="language-"] {
|
||||
padding: 1em;
|
||||
margin: .5em 0;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
:not(pre) > code[class*="language-"],
|
||||
pre[class*="language-"] {
|
||||
background: #f5f2f0;
|
||||
}
|
||||
|
||||
/* Inline code */
|
||||
:not(pre) > code[class*="language-"] {
|
||||
padding: .1em;
|
||||
border-radius: .3em;
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
.token.comment,
|
||||
.token.prolog,
|
||||
.token.doctype,
|
||||
.token.cdata {
|
||||
color: slategray;
|
||||
}
|
||||
|
||||
.token.punctuation {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.namespace {
|
||||
opacity: .7;
|
||||
}
|
||||
|
||||
.token.property,
|
||||
.token.tag,
|
||||
.token.boolean,
|
||||
.token.number,
|
||||
.token.constant,
|
||||
.token.symbol,
|
||||
.token.deleted {
|
||||
color: #905;
|
||||
}
|
||||
|
||||
.token.selector,
|
||||
.token.attr-name,
|
||||
.token.string,
|
||||
.token.char,
|
||||
.token.builtin,
|
||||
.token.inserted {
|
||||
color: #690;
|
||||
}
|
||||
|
||||
.token.operator,
|
||||
.token.entity,
|
||||
.token.url,
|
||||
.language-css .token.string,
|
||||
.style .token.string {
|
||||
color: #a67f59;
|
||||
background: hsla(0, 0%, 100%, .5);
|
||||
}
|
||||
|
||||
.token.atrule,
|
||||
.token.attr-value,
|
||||
.token.keyword {
|
||||
color: #07a;
|
||||
}
|
||||
|
||||
.token.function {
|
||||
color: #DD4A68;
|
||||
}
|
||||
|
||||
.token.regex,
|
||||
.token.important,
|
||||
.token.variable {
|
||||
color: #e90;
|
||||
}
|
||||
|
||||
.token.important,
|
||||
.token.bold {
|
||||
font-weight: bold;
|
||||
}
|
||||
.token.italic {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.token.entity {
|
||||
cursor: help;
|
||||
}
|
||||
|
@ -0,0 +1,122 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
|
||||
|
||||
<title>index</title>
|
||||
<meta name="description" content="" />
|
||||
<meta name="author" content="Christoph Oberhofer" />
|
||||
|
||||
<meta name="viewport" content="width=device-width; initial-scale=1.0" />
|
||||
<link rel="stylesheet" type="text/css" href="../css/fonts.css" />
|
||||
<link rel="stylesheet" type="text/css" href="../css/styles.css" />
|
||||
<link rel="stylesheet" type="text/css" href="../css/prism.css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<header>
|
||||
<div class="headline">
|
||||
<h1>QuaggaJS</h1>
|
||||
<h2>An advanced barcode-scanner written in JavaScript</h2>
|
||||
</div>
|
||||
</header>
|
||||
<section id="container" class="container">
|
||||
<h3>Scan barcode to input-field</h3>
|
||||
<p>Click the <strong>button</strong> next to the input-field
|
||||
to select a file or snap a picture</p>
|
||||
<div>
|
||||
<form>
|
||||
<div class="input-field">
|
||||
<label for="isbn_input">EAN:</label>
|
||||
<input id="isbn_input" class="isbn" type="text" />
|
||||
<button type="button" class="icon-barcode button scan"> </button>
|
||||
<input type="file" id="file" capture/>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<p>This example demonstrates the following features:
|
||||
<ul>
|
||||
<li>Use static image as source</li>
|
||||
<li>Configuring EAN-Reader</li>
|
||||
<li>Use custom mount-point (Query-Selector)</li>
|
||||
</ul>
|
||||
</p>
|
||||
<div class="source-code">
|
||||
<h4>Source</h4>
|
||||
<div class="collapsable-source">
|
||||
<pre>
|
||||
<code class="language-javascript">
|
||||
var Quagga = window.Quagga;
|
||||
var App = {
|
||||
_scanner: null,
|
||||
init: function() {
|
||||
this.attachListeners();
|
||||
},
|
||||
decode: function(src) {
|
||||
Quagga
|
||||
.decoder({readers: ['ean_reader']})
|
||||
.locator({patchSize: 'medium'})
|
||||
.fromImage(src, {size: 800})
|
||||
.toPromise()
|
||||
.then(function(result) {
|
||||
document.querySelector('input.isbn').value = result.codeResult.code;
|
||||
})
|
||||
.catch(function() {
|
||||
document.querySelector('input.isbn').value = "Not Found";
|
||||
})
|
||||
.then(function() {
|
||||
this.attachListeners();
|
||||
}.bind(this));
|
||||
},
|
||||
attachListeners: function() {
|
||||
var self = this,
|
||||
button = document.querySelector('.input-field input + .button.scan'),
|
||||
fileInput = document.querySelector('.input-field input[type=file]');
|
||||
|
||||
button.addEventListener("click", function onClick(e) {
|
||||
e.preventDefault();
|
||||
button.removeEventListener("click", onClick);
|
||||
document.querySelector('.input-field input[type=file]').click();
|
||||
});
|
||||
|
||||
fileInput.addEventListener("change", function onChange(e) {
|
||||
e.preventDefault();
|
||||
fileInput.removeEventListener("change", onChange);
|
||||
if (e.target.files && e.target.files.length) {
|
||||
self.decode(URL.createObjectURL(e.target.files[0]));
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
App.init();
|
||||
</code>
|
||||
</pre>
|
||||
</div>
|
||||
<div class="collapsable-source">
|
||||
<pre>
|
||||
<code class="language-html">
|
||||
<form>
|
||||
<div class="input-field">
|
||||
<label for="isbn_input">EAN:</label>
|
||||
<input id="isbn_input" class="isbn" type="text" />
|
||||
<button type="button" class="icon-barcode button scan">&nbsp;</button>
|
||||
<input type="file" id="file" capture/>
|
||||
</div>
|
||||
</form>
|
||||
</code>
|
||||
</pre>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<footer>
|
||||
<p>
|
||||
© Copyright by Christoph Oberhofer
|
||||
</p>
|
||||
</footer>
|
||||
|
||||
<script src="../../dist/quagga.js" type="text/javascript"></script>
|
||||
<script src="index.js" type="text/javascript"></script>
|
||||
<script src="../vendor/prism.js"></script>
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,43 @@
|
||||
var Quagga = window.Quagga;
|
||||
var App = {
|
||||
_scanner: null,
|
||||
init: function() {
|
||||
this.attachListeners();
|
||||
},
|
||||
decode: function(file) {
|
||||
Quagga
|
||||
.decoder({readers: ['ean_reader']})
|
||||
.locator({patchSize: 'medium'})
|
||||
.fromSource(file, {size: 800})
|
||||
.toPromise()
|
||||
.then(function(result) {
|
||||
document.querySelector('input.isbn').value = result.codeResult.code;
|
||||
})
|
||||
.catch(function() {
|
||||
document.querySelector('input.isbn').value = "Not Found";
|
||||
})
|
||||
.then(function() {
|
||||
this.attachListeners();
|
||||
}.bind(this));
|
||||
},
|
||||
attachListeners: function() {
|
||||
var self = this,
|
||||
button = document.querySelector('.input-field input + .button.scan'),
|
||||
fileInput = document.querySelector('.input-field input[type=file]');
|
||||
|
||||
button.addEventListener("click", function onClick(e) {
|
||||
e.preventDefault();
|
||||
button.removeEventListener("click", onClick);
|
||||
document.querySelector('.input-field input[type=file]').click();
|
||||
});
|
||||
|
||||
fileInput.addEventListener("change", function onChange(e) {
|
||||
e.preventDefault();
|
||||
fileInput.removeEventListener("change", onChange);
|
||||
if (e.target.files && e.target.files.length) {
|
||||
self.decode(e.target.files[0]);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
App.init();
|
Binary file not shown.
@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
|
||||
<svg xmlns="http://www.w3.org/2000/svg">
|
||||
<metadata>Generated by IcoMoon</metadata>
|
||||
<defs>
|
||||
<font id="icomoon" horiz-adv-x="1024">
|
||||
<font-face units-per-em="1024" ascent="960" descent="-64" />
|
||||
<missing-glyph horiz-adv-x="1024" />
|
||||
<glyph unicode=" " horiz-adv-x="512" d="" />
|
||||
<glyph unicode="" glyph-name="barcode" d="M0 832h128v-640h-128zM192 832h64v-640h-64zM320 832h64v-640h-64zM512 832h64v-640h-64zM768 832h64v-640h-64zM960 832h64v-640h-64zM640 832h32v-640h-32zM448 832h32v-640h-32zM864 832h32v-640h-32zM0 128h64v-64h-64zM192 128h64v-64h-64zM320 128h64v-64h-64zM640 128h64v-64h-64zM960 128h64v-64h-64zM768 128h128v-64h-128zM448 128h128v-64h-128z" />
|
||||
</font></defs></svg>
|
After Width: | Height: | Size: 821 B |
Binary file not shown.
Binary file not shown.
@ -0,0 +1,80 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
|
||||
<meta name="mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
|
||||
<title>index</title>
|
||||
<meta name="description" content="" />
|
||||
<meta name="author" content="Christoph Oberhofer" />
|
||||
|
||||
<meta name="viewport" content="width=device-width; initial-scale=1.0" />
|
||||
<link rel="stylesheet" type="text/css" href="../css/fonts.css" />
|
||||
<link rel="stylesheet" type="text/css" href="../css/styles.css" />
|
||||
<link rel="stylesheet" type="text/css" href="./styles.css" />
|
||||
<link rel="stylesheet" type="text/css" href="../css/prism.css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<header>
|
||||
<div class="headline">
|
||||
<h1>QuaggaJS</h1>
|
||||
<h2>An advanced barcode-scanner written in JavaScript</h2>
|
||||
</div>
|
||||
</header>
|
||||
<section id="container" class="container">
|
||||
<div class="controls">
|
||||
<h3>Scan various barcodes continously</h3>
|
||||
<button type="button" class="icon-barcode button scan">Start Scanning</button>
|
||||
<div class="readers">
|
||||
<label>
|
||||
<span>EAN-13</span>
|
||||
<input type="checkbox" checked name="ean_reader" />
|
||||
</label>
|
||||
<label>
|
||||
<span>EAN-8</span>
|
||||
<input type="checkbox" name="ean_8_reader" />
|
||||
</label>
|
||||
<label>
|
||||
<span>UPC-E</span>
|
||||
<input type="checkbox" name="upc_e_reader" />
|
||||
</label>
|
||||
<label>
|
||||
<span>Code 39</span>
|
||||
<input type="checkbox" checked name="code_39_reader" />
|
||||
</label>
|
||||
<label>
|
||||
<span>Codabar</span>
|
||||
<input type="checkbox" name="codabar_reader" />
|
||||
</label>
|
||||
<label>
|
||||
<span>Code 128</span>
|
||||
<input type="checkbox" checked name="code_128_reader" />
|
||||
</label>
|
||||
<label>
|
||||
<span>Interleaved 2 of 5</span>
|
||||
<input type="checkbox" name="i2of5_reader" />
|
||||
</label>
|
||||
</div>
|
||||
<p>
|
||||
Be aware that not all combinations play together nicely. Some
|
||||
codes do not have a checksum (e.g. ITF).
|
||||
</p>
|
||||
</div>
|
||||
<div class="overlay overlay--inline">
|
||||
<div class="overlay__content">
|
||||
<div class="overlay__close">X</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<ul class="results"></ul>
|
||||
|
||||
<script src="../../dist/quagga.js" type="text/javascript"></script>
|
||||
<script src="index.js" type="text/javascript"></script>
|
||||
<script src="../vendor/prism.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
@ -0,0 +1,96 @@
|
||||
var Quagga = window.Quagga;
|
||||
var App = {
|
||||
_lastResult: null,
|
||||
init: function() {
|
||||
this.attachListeners();
|
||||
},
|
||||
activateScanner: function() {
|
||||
var scanner = this.configureScanner('.overlay__content'),
|
||||
onDetected = function (result) {
|
||||
this.addToResults(result);
|
||||
}.bind(this),
|
||||
stop = function() {
|
||||
scanner.stop(); // should also clear all event-listeners?
|
||||
scanner.removeEventListener('detected', onDetected);
|
||||
this.hideOverlay();
|
||||
this.attachListeners();
|
||||
}.bind(this);
|
||||
|
||||
this.showOverlay(stop);
|
||||
console.log("activateScanner");
|
||||
scanner.addEventListener('detected', onDetected).start();
|
||||
},
|
||||
addToResults: function(result) {
|
||||
if (this._lastResult === result.codeResult.code) {
|
||||
return;
|
||||
}
|
||||
this._lastResult = result.codeResult.code;
|
||||
var results = document.querySelector('ul.results'),
|
||||
li = document.createElement('li'),
|
||||
format = document.createElement('span'),
|
||||
code = document.createElement('span');
|
||||
|
||||
li.className = "result";
|
||||
format.className = "format";
|
||||
code.className = "code";
|
||||
|
||||
li.appendChild(format);
|
||||
li.appendChild(code);
|
||||
|
||||
format.appendChild(document.createTextNode(result.codeResult.format));
|
||||
code.appendChild(document.createTextNode(result.codeResult.code));
|
||||
|
||||
results.insertBefore(li, results.firstChild);
|
||||
},
|
||||
attachListeners: function() {
|
||||
var button = document.querySelector('button.scan'),
|
||||
self = this;
|
||||
|
||||
button.addEventListener("click", function clickListener (e) {
|
||||
e.preventDefault();
|
||||
button.removeEventListener("click", clickListener);
|
||||
self.activateScanner();
|
||||
});
|
||||
},
|
||||
showOverlay: function(cancelCb) {
|
||||
document.querySelector('.container .controls')
|
||||
.classList.add('hide');
|
||||
document.querySelector('.overlay--inline')
|
||||
.classList.add('show');
|
||||
var closeButton = document.querySelector('.overlay__close');
|
||||
closeButton.addEventListener('click', function closeHandler() {
|
||||
closeButton.removeEventListener("click", closeHandler);
|
||||
cancelCb();
|
||||
});
|
||||
},
|
||||
hideOverlay: function() {
|
||||
document.querySelector('.container .controls')
|
||||
.classList.remove('hide');
|
||||
document.querySelector('.overlay--inline')
|
||||
.classList.remove('show');
|
||||
},
|
||||
querySelectedReaders: function() {
|
||||
return Array.prototype.slice.call(document.querySelectorAll('.readers input[type=checkbox]'))
|
||||
.filter(function(element) {
|
||||
return !!element.checked;
|
||||
})
|
||||
.map(function(element) {
|
||||
return element.getAttribute("name");
|
||||
});
|
||||
},
|
||||
configureScanner: function(selector) {
|
||||
var scanner = Quagga
|
||||
.decoder({readers: this.querySelectedReaders()})
|
||||
.locator({patchSize: 'medium'})
|
||||
.fromSource({
|
||||
target: selector,
|
||||
constraints: {
|
||||
width: 600,
|
||||
height: 600,
|
||||
facingMode: "environment"
|
||||
}
|
||||
});
|
||||
return scanner;
|
||||
}
|
||||
};
|
||||
App.init();
|
@ -0,0 +1,140 @@
|
||||
body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding-bottom: 0;
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body > header {
|
||||
flex: 0 0 auto;
|
||||
padding: 0.5rem 1rem;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: inherit;
|
||||
}
|
||||
|
||||
header > .headline {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.headline h1, .headline h2 {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.headline h2 {
|
||||
font-size: 1.3rem;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
body > .container {
|
||||
margin: 0;
|
||||
flex: 1 1 auto;
|
||||
overflow-y: scroll;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* Controls */
|
||||
|
||||
/* Scan! Button */
|
||||
|
||||
.icon-barcode.scan {
|
||||
font-size: x-large;
|
||||
padding: 0.5rem;
|
||||
text-align: center;
|
||||
margin: 0 auto;
|
||||
display: block;
|
||||
width: 70%;
|
||||
}
|
||||
|
||||
.icon-barcode.scan:before {
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
|
||||
/* Reader Checkboxes */
|
||||
|
||||
.controls .readers {
|
||||
font-size: 1.3rem;
|
||||
width: 70%;
|
||||
margin: 2rem auto 0;
|
||||
}
|
||||
|
||||
.controls .readers label {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.controls .readers input {
|
||||
flex: 0 0 auto;
|
||||
height: 1.5rem;
|
||||
width: 1.5rem;
|
||||
}
|
||||
|
||||
.controls .readers span {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
/* Results */
|
||||
|
||||
body > .results {
|
||||
flex: 0 0 auto;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: nowrap;
|
||||
background-color: #DDD;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style-type: none;
|
||||
padding: 0.25rem;
|
||||
overflow-x: scroll;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
border-top: 1px solid #777;
|
||||
}
|
||||
|
||||
.results > li {
|
||||
flex: 0 1 auto;
|
||||
margin: 0.25rem;
|
||||
background: #fff;
|
||||
border-radius: 2px;
|
||||
box-shadow: 0 3px 6px rgba(0,0,0,0.16), 0 3px 6px rgba(0,0,0,0.23);
|
||||
}
|
||||
|
||||
.results:empty {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.result {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 0.5rem;
|
||||
}
|
||||
|
||||
.result > .format {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.result > .code {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
/* Scanner */
|
||||
|
||||
.container .controls.hide {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.overlay--inline {
|
||||
position: absolute;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.overlay--inline.show {
|
||||
display: block;
|
||||
}
|
@ -0,0 +1,4 @@
|
||||
{
|
||||
"plugins": ["transform-class-properties"],
|
||||
"presets": ["es2015-webpack", "stage-0", "react"]
|
||||
}
|
@ -0,0 +1 @@
|
||||
static/
|
@ -0,0 +1,9 @@
|
||||
# Quagga meets React
|
||||
|
||||
This example uses webpack & babel to transpile/bundle the required files.
|
||||
|
||||
## How to run the example
|
||||
|
||||
```console
|
||||
> npm install && webpack
|
||||
```
|
@ -0,0 +1,49 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
|
||||
|
||||
<title>index</title>
|
||||
<meta name="description" content="" />
|
||||
<meta name="author" content="Christoph Oberhofer" />
|
||||
|
||||
<meta name="viewport" content="width=device-width; initial-scale=1.0" />
|
||||
<link rel="stylesheet" type="text/css" href="../css/fonts.css" />
|
||||
<link rel="stylesheet" type="text/css" href="../css/styles.css" />
|
||||
<link rel="stylesheet" type="text/css" href="../css/prism.css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<header>
|
||||
<div class="headline">
|
||||
<h1>QuaggaJS</h1>
|
||||
<h2>An advanced barcode-scanner written in JavaScript</h2>
|
||||
</div>
|
||||
</header>
|
||||
<section id="container" class="container">
|
||||
<h3>Scan barcode to input-field</h3>
|
||||
<p>Click the <strong>button</strong> next to the input-field
|
||||
to start scanning an <strong>EAN-13</strong> barcode</p>
|
||||
<div id="react">
|
||||
|
||||
</div>
|
||||
<p>This example demonstrates the following features:
|
||||
<ul>
|
||||
<li>Access to user's camera</li>
|
||||
<li>Configuring EAN-Reader</li>
|
||||
<li>Use custom mount-point (Query-Selector)</li>
|
||||
<li>Start/Stop scanning based on certain events</li>
|
||||
</ul>
|
||||
</p>
|
||||
</section>
|
||||
<footer>
|
||||
<p>
|
||||
© Copyright by Christoph Oberhofer
|
||||
</p>
|
||||
</footer>
|
||||
|
||||
<script src="static/bundle.js" type="text/javascript"></script>
|
||||
<script src="../vendor/prism.js"></script>
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,24 @@
|
||||
{
|
||||
"name": "quagga-react",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "Christoph Oberhofer <ch.oberhofer@gmail.com>",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"babel-core": "^6.9.0",
|
||||
"babel-loader": "^6.2.4",
|
||||
"babel-plugin-transform-class-properties": "^6.9.0",
|
||||
"babel-preset-es2015-webpack": "^6.4.1",
|
||||
"babel-preset-react": "^6.5.0",
|
||||
"babel-preset-stage-0": "^6.5.0",
|
||||
"webpack": "^2.1.0-beta.7"
|
||||
},
|
||||
"dependencies": {
|
||||
"react": "^15.0.2",
|
||||
"react-dom": "^15.0.2"
|
||||
}
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
import React from 'react';
|
||||
import Scanner from './Scanner';
|
||||
|
||||
export default class App extends React.Component {
|
||||
state = {
|
||||
scanning: false
|
||||
};
|
||||
|
||||
_startScanning = (e) => {
|
||||
e.preventDefault();
|
||||
if (!this.state.scanning) {
|
||||
this.setState({scanning: true});
|
||||
}
|
||||
};
|
||||
|
||||
_stopScanning = () => {
|
||||
this.setState({scanning: false});
|
||||
};
|
||||
|
||||
_handleResult = (result) => {
|
||||
this._stopScanning();
|
||||
this.refs.eanInput.value = result.codeResult.code;
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<form>
|
||||
<div className="input-field">
|
||||
<label>EAN:</label>
|
||||
<input className="ean" ref="eanInput" type="text" />
|
||||
<button type="button" onClick={this._startScanning} className="icon-barcode button scan"></button>
|
||||
</div>
|
||||
</form>
|
||||
{this.state.scanning &&
|
||||
<Scanner onDetected={this._handleResult} onCancel={this._stopScanning} />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
import React from 'react';
|
||||
import Quagga from '../../../../dist/quagga';
|
||||
|
||||
export default class Scanner extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this._scanner = Quagga
|
||||
.decoder({readers: ['ean_reader']})
|
||||
.locator({patchSize: 'medium'})
|
||||
.fromSource({
|
||||
target: '.overlay__content',
|
||||
constraints: {
|
||||
width: 800,
|
||||
height: 600,
|
||||
facingMode: "environment"
|
||||
}
|
||||
});
|
||||
this._onCancel = this._onCancel.bind(this);
|
||||
}
|
||||
_onCancel(e) {
|
||||
e.preventDefault();
|
||||
this.props.onCancel();
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<div className="overlay">
|
||||
<div className="overlay__content">
|
||||
<div className="overlay__close" onClick={this._onCancel}>X</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
componentDidMount() {
|
||||
this._scanner
|
||||
.addEventListener('detected', this.props.onDetected)
|
||||
.start();
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this._scanner
|
||||
.removeEventListener('detected', this.props.onDetected)
|
||||
.stop();
|
||||
}
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import App from './components/App';
|
||||
|
||||
|
||||
ReactDOM.render(
|
||||
<App />,
|
||||
document.getElementById('react')
|
||||
);
|
@ -0,0 +1,23 @@
|
||||
module.exports = {
|
||||
entry: [
|
||||
'./src/index.js'
|
||||
],
|
||||
devtool: 'source-map',
|
||||
module: {
|
||||
loaders: [{
|
||||
test: /\.jsx?$/,
|
||||
exclude: /(node_modules|quagga\.js)/,
|
||||
loader: 'babel-loader'
|
||||
}]
|
||||
},
|
||||
resolve: {
|
||||
modules: [
|
||||
'node_modules'
|
||||
]
|
||||
},
|
||||
output: {
|
||||
path: __dirname + '/static',
|
||||
publicPath: '/',
|
||||
filename: 'bundle.js'
|
||||
}
|
||||
};
|
@ -0,0 +1,4 @@
|
||||
{
|
||||
"plugins": ["transform-class-properties"],
|
||||
"presets": ["es2015-webpack", "stage-0", "react"]
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
{
|
||||
"root": true,
|
||||
"extends": "./node_modules/eslint-config-netconomy/react.js",
|
||||
"parser": "babel-eslint",
|
||||
"rules": {
|
||||
"comma-dangle": [2, "always-multiline"],
|
||||
}
|
||||
}
|
@ -0,0 +1 @@
|
||||
static/*.js*
|
@ -0,0 +1,9 @@
|
||||
# Quagga meets React
|
||||
|
||||
This example uses webpack & babel to transpile/bundle the required files.
|
||||
|
||||
## How to run the example
|
||||
|
||||
```console
|
||||
> npm install && webpack
|
||||
```
|
@ -0,0 +1,22 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
|
||||
|
||||
<title>index</title>
|
||||
<link rel="manifest" href="manifest.json">
|
||||
<meta name="description" content="" />
|
||||
<meta name="author" content="Christoph Oberhofer" />
|
||||
|
||||
<link rel="stylesheet" type="text/css" href="static/style.css" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1">
|
||||
<link href='https://fonts.googleapis.com/css?family=Roboto:400,300,500' rel='stylesheet' type='text/css'>
|
||||
|
||||
</head>
|
||||
|
||||
<body style="margin: 0">
|
||||
<div id="react"></div>
|
||||
<script src="static/bundle.min.js" type="text/javascript"></script>
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,14 @@
|
||||
{
|
||||
"short_name": "Quagga Sandbox",
|
||||
"name": "Sandbox for Barcode-Scanning with Quagga",
|
||||
"icons": [
|
||||
{
|
||||
"src": "static/icon.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png"
|
||||
}
|
||||
],
|
||||
"start_url": "index.html",
|
||||
"display": "standalone",
|
||||
"orientation": "portrait"
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
{
|
||||
"name": "quagga-react",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"build:watch": "webpack --config webpack.config.js --watch",
|
||||
"build:dev": "webpack --config webpack.config.js",
|
||||
"build:prod": "webpack --config webpack.config.min.js"
|
||||
},
|
||||
"author": "Christoph Oberhofer <ch.oberhofer@gmail.com>",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"babel-core": "^6.10.4",
|
||||
"babel-eslint": "^6.1.2",
|
||||
"babel-loader": "^6.2.4",
|
||||
"babel-plugin-transform-class-properties": "^6.10.2",
|
||||
"babel-preset-es2015-webpack": "^6.4.1",
|
||||
"babel-preset-react": "^6.11.1",
|
||||
"babel-preset-stage-0": "^6.5.0",
|
||||
"eslint": "^3.1.1",
|
||||
"eslint-config-netconomy": "^300.0.1",
|
||||
"eslint-plugin-react": "^5.2.2",
|
||||
"json-loader": "^0.5.4",
|
||||
"webpack": "^2.1.0-beta.18"
|
||||
},
|
||||
"dependencies": {
|
||||
"material-ui": "^0.15.2",
|
||||
"react": "^15.2.1",
|
||||
"react-dom": "^15.2.1",
|
||||
"react-tap-event-plugin": "^1.0.0"
|
||||
}
|
||||
}
|
@ -0,0 +1,185 @@
|
||||
import React from 'react';
|
||||
import AppBar from 'material-ui/AppBar';
|
||||
import Drawer from 'material-ui/Drawer';
|
||||
import FloatingActionButton from 'material-ui/FloatingActionButton';
|
||||
import Dialog from 'material-ui/Dialog';
|
||||
import FlatButton from 'material-ui/FlatButton';
|
||||
import {Card, CardText} from 'material-ui/Card';
|
||||
import IconButton from 'material-ui/IconButton';
|
||||
import TuneIcon from 'material-ui/svg-icons/image/tune';
|
||||
|
||||
import Scanner from './Scanner';
|
||||
import ScanIcon from './ScanIcon';
|
||||
import ScannedCode from './ScannedCode';
|
||||
import ConfigView from './ConfigView';
|
||||
import {persist, load} from '../utils/Storage';
|
||||
|
||||
const cleanConfig = config => {
|
||||
if (typeof config.inputStream.constraints.deviceId === 'number') {
|
||||
config.inputStream.constraints.deviceId = null;
|
||||
}
|
||||
return config;
|
||||
};
|
||||
|
||||
const defaultConfig = {
|
||||
frequency: 5,
|
||||
numOfWorkers: 2,
|
||||
locate: true,
|
||||
inputStream: {
|
||||
name: "Live",
|
||||
type: "LiveStream",
|
||||
constraints: {
|
||||
width: 800,
|
||||
height: 600,
|
||||
deviceId: 0,
|
||||
facingMode: "environment",
|
||||
},
|
||||
area: {
|
||||
top: "0%",
|
||||
right: "0%",
|
||||
left: "0%",
|
||||
bottom: "0%",
|
||||
},
|
||||
},
|
||||
decoder: {
|
||||
readers: [
|
||||
'ean_reader',
|
||||
'code_39_reader',
|
||||
'code_128_reader',
|
||||
],
|
||||
},
|
||||
locator: {
|
||||
halfSample: true,
|
||||
patchSize: "medium",
|
||||
},
|
||||
};
|
||||
|
||||
export default class App extends React.Component {
|
||||
state = {
|
||||
drawerOpen: false,
|
||||
scanning: false,
|
||||
currentView: 'root',
|
||||
config: load("config") || defaultConfig,
|
||||
scannedCodes: load('scannedCodes') || [],
|
||||
};
|
||||
|
||||
_handleToggle = () => {
|
||||
this.setState({drawerOpen: !this.state.drawerOpen});
|
||||
}
|
||||
_handleClose = () => this.setState({drawerOpen: false});
|
||||
_onRequestChange = drawerOpen => {
|
||||
this.setState({drawerOpen});
|
||||
};
|
||||
|
||||
_startScanning = (e) => {
|
||||
e.preventDefault();
|
||||
if (!this.state.scanning) {
|
||||
this.setState({scanning: true});
|
||||
}
|
||||
};
|
||||
|
||||
_stopScanning = () => {
|
||||
this.setState({scanning: false});
|
||||
};
|
||||
|
||||
_handleResult = (result) => {
|
||||
this._stopScanning();
|
||||
const scannedCodes =
|
||||
[{
|
||||
angle: result.angle,
|
||||
box: result.box,
|
||||
codeResult: {
|
||||
code: result.codeResult.code,
|
||||
direction: result.codeResult.direction,
|
||||
format: result.codeResult.format,
|
||||
},
|
||||
line: result.line,
|
||||
}]
|
||||
.concat(this.state.scannedCodes);
|
||||
this.setState({scannedCodes});
|
||||
persist('scannedCodes', scannedCodes);
|
||||
}
|
||||
|
||||
_navigateTo = (route) => {
|
||||
this.setState({
|
||||
drawerOpen: false,
|
||||
currentView: route,
|
||||
});
|
||||
}
|
||||
|
||||
_handleConfigChange = config => {
|
||||
this.setState({config: cleanConfig(config)});
|
||||
persist("config", config);
|
||||
}
|
||||
|
||||
_handleDelete = (scannedCode) => {
|
||||
const index = this.state.scannedCodes.indexOf(scannedCode);
|
||||
if (index !== -1) {
|
||||
const newArray = this.state.scannedCodes.slice();
|
||||
newArray.splice(index, 1);
|
||||
this.setState({scannedCodes: newArray});
|
||||
persist('scannedCodes', newArray);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<Drawer
|
||||
docked={false}
|
||||
open={this.state.drawerOpen}
|
||||
onRequestChange={this._onRequestChange}
|
||||
>
|
||||
<ConfigView config={this.state.config} onChange={this._handleConfigChange} />
|
||||
</Drawer>
|
||||
<AppBar
|
||||
style={{position: 'fixed', top: '0px'}}
|
||||
title="QuaggaJS"
|
||||
iconElementLeft={<IconButton onTouchTap={this._handleToggle}><TuneIcon /></IconButton>}
|
||||
onLeftIconButtonTouchTap={this._handleToggle}
|
||||
/>
|
||||
<Dialog
|
||||
style={{paddingTop: '0px'}}
|
||||
bodyStyle={{padding: '0.5rem'}}
|
||||
repositionOnUpdate={false}
|
||||
actions={[
|
||||
<FlatButton
|
||||
label="Cancel"
|
||||
primary={true}
|
||||
onTouchTap={this._stopScanning}/>,
|
||||
]}
|
||||
modal={true}
|
||||
contentStyle={{width: '95%', maxWidth: '95%', height: '95%', maxHeight: '95%'}}
|
||||
open={this.state.scanning}
|
||||
>
|
||||
<Scanner
|
||||
config={this.state.config}
|
||||
onDetected={this._handleResult}
|
||||
onCancel={this._stopScanning} />
|
||||
</Dialog>
|
||||
<div style={{paddingTop: '64px'}}>
|
||||
{this.state.currentView === 'root' && this.state.scannedCodes.length === 0 &&
|
||||
<Card style={{margin: '0.5em 0.25em 0em'}}>
|
||||
<CardText>
|
||||
Nothing scanned yet
|
||||
</CardText>
|
||||
</Card>
|
||||
}
|
||||
{this.state.currentView === 'root' && this.state.scannedCodes.map((scannedCode, i) => (
|
||||
<ScannedCode
|
||||
key={i}
|
||||
scannedCode={scannedCode}
|
||||
onDelete={this._handleDelete.bind(this, scannedCode)} />
|
||||
))}
|
||||
</div>
|
||||
<FloatingActionButton
|
||||
secondary={true}
|
||||
onMouseDown={this._startScanning}
|
||||
style={{position: 'fixed', right: 0, bottom: 0, margin: '0 1em 1em 0'}}
|
||||
>
|
||||
<ScanIcon />
|
||||
</FloatingActionButton>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
import React from 'react';
|
||||
|
||||
export default (Component) => (class ConfigOption extends React.Component {
|
||||
static propTypes = {
|
||||
path: React.PropTypes.array.isRequired,
|
||||
onChange: React.PropTypes.func,
|
||||
onToggle: React.PropTypes.func,
|
||||
};
|
||||
|
||||
_handleChange = (event, key, payload) => {
|
||||
this.props.onChange(event, key, payload, this.props.path);
|
||||
}
|
||||
|
||||
_handleToggle = (event, value) => {
|
||||
this.props.onToggle(event, value, this.props.path);
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
path,
|
||||
onChange,
|
||||
onToggle,
|
||||
...rest,
|
||||
} = this.props;
|
||||
if (onChange) {
|
||||
return <Component {...rest} onChange={this._handleChange} />;
|
||||
} else if (onToggle) {
|
||||
return <Component {...rest} onToggle={this._handleToggle} />;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
});
|
@ -0,0 +1,193 @@
|
||||
import React from 'react';
|
||||
import {List, ListItem} from 'material-ui/List';
|
||||
import Subheader from 'material-ui/Subheader';
|
||||
import Toggle from 'material-ui/Toggle';
|
||||
import SelectField from 'material-ui/SelectField';
|
||||
import MenuItem from 'material-ui/MenuItem';
|
||||
import createConfigOption from './ConfigOption';
|
||||
|
||||
let configSections = [{
|
||||
name: "Constraints",
|
||||
path: ["inputStream", "constraints"],
|
||||
options: {
|
||||
width: [[640, "640px"], [800, "800px"], [1280, "1280px"], [1920, "1920px"]],
|
||||
height: [[480, "480px"], [600, "600px"], [720, "720px"], [1080, "1080px"]],
|
||||
facingMode: [["user", "user"], ["environment", "environment"]],
|
||||
aspectRatio: [[1, '1/1'], [4 / 3, '4/3'], [16 / 9, '16/9'], [21 / 9, '21/9']],
|
||||
deviceId: [],
|
||||
},
|
||||
}, {
|
||||
name: "General",
|
||||
path: [],
|
||||
options: {
|
||||
locate: true,
|
||||
numOfWorkers: [[0, "0"], [1, "1"], [2, "2"], [4, "4"], [8, "8"]],
|
||||
frequency: [[0, "full"], [1, "1Hz"], [2, "2Hz"], [5, "5Hz"],
|
||||
[10, "10Hz"], [15, "15Hz"], [20, "20Hz"]],
|
||||
},
|
||||
}, {
|
||||
name: "Reader",
|
||||
path: ["decoder", "readers"],
|
||||
options: {
|
||||
ean_reader: false,
|
||||
ean_8_reader: false,
|
||||
upc_e_reader: false,
|
||||
code_39_reader: false,
|
||||
codabar_reader: false,
|
||||
code_128_reader: false,
|
||||
i2of5_reader: false,
|
||||
},
|
||||
}, {
|
||||
name: "Locator",
|
||||
path: ["locator"],
|
||||
options: {
|
||||
halfSample: true,
|
||||
patchSize: [
|
||||
["x-small", "x-small"],
|
||||
["small", "small"],
|
||||
["medium", "medium"],
|
||||
["large", "large"],
|
||||
["x-large", "x-large"],
|
||||
],
|
||||
},
|
||||
}];
|
||||
|
||||
const selectStyle = {
|
||||
};
|
||||
|
||||
const selectedItemStyle = {
|
||||
whiteSpace: 'nowrap',
|
||||
textOverflow: 'ellipsis',
|
||||
overflow: 'hidden',
|
||||
};
|
||||
|
||||
const reducer = function reduce(state, path, value) {
|
||||
if (path.length === 1 && typeof value === 'boolean' && Array.isArray(state)) {
|
||||
const index = state.indexOf(path[0]);
|
||||
if (index !== -1 && value || index === -1 && !value) {
|
||||
return state;
|
||||
} else if (index !== -1 && !value) {
|
||||
const newArray = state.slice();
|
||||
newArray.splice(index, 1);
|
||||
return newArray;
|
||||
} else {
|
||||
return state.concat([path[0]]);
|
||||
}
|
||||
}
|
||||
if (path.length === 0) {
|
||||
return value;
|
||||
}
|
||||
const [part, ...rest] = path;
|
||||
return Object.assign({}, state, {[part]: reduce(state[part], rest, value)});
|
||||
};
|
||||
|
||||
const get = function get(object, path) {
|
||||
if (Array.isArray(object) && (typeof path[0] === 'string')) {
|
||||
return object.indexOf(path[0]) !== -1;
|
||||
}
|
||||
if (path.length === 0) {
|
||||
return object;
|
||||
}
|
||||
const [part, ...rest] = path;
|
||||
return get(object[part], rest);
|
||||
};
|
||||
|
||||
const getConfigByPath = function(object, path) {
|
||||
return get(object, ['config'].concat(path));
|
||||
};
|
||||
|
||||
const SelectConfigOption = createConfigOption(SelectField);
|
||||
const ToggleConfigOption = createConfigOption(Toggle);
|
||||
|
||||
export default class ConfigView extends React.Component {
|
||||
static propTypes = {
|
||||
onChange: React.PropTypes.func,
|
||||
config: React.PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
state = {
|
||||
devicesFetched: false,
|
||||
};
|
||||
|
||||
handleChange = (event, index, value, path) => {
|
||||
const newState = reducer(this.props.config, path, value);
|
||||
this.props.onChange(newState);
|
||||
}
|
||||
|
||||
handleToggle = (event, value, path) => {
|
||||
return this.handleChange(event, 0, value, path);
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
navigator.mediaDevices
|
||||
.enumerateDevices()
|
||||
.then((devices) => {
|
||||
const videoDevices = devices
|
||||
.filter(info => info.kind === 'videoinput')
|
||||
.map(videoDevice => ([
|
||||
videoDevice.deviceId,
|
||||
videoDevice.label,
|
||||
]));
|
||||
const constraints = configSections
|
||||
.map((section, index) => (section.name === "Constraints" ? {section, index} : null))
|
||||
.filter(sectionIndex => !!sectionIndex)[0];
|
||||
configSections = configSections.slice();
|
||||
constraints.section = {
|
||||
...constraints.section,
|
||||
options: {
|
||||
...constraints.section.options,
|
||||
deviceId: [[0, "no preference"]].concat(videoDevices),
|
||||
},
|
||||
};
|
||||
configSections[constraints.index] = constraints.section;
|
||||
this.setState({devicesFetched: true});
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
return (<div>{
|
||||
configSections.map(section => (
|
||||
<List>
|
||||
<Subheader>{section.name}</Subheader>
|
||||
{Object.keys(section.options).map(option => {
|
||||
const path = section.path.concat([option]);
|
||||
if (typeof section.options[option] === 'boolean') {
|
||||
return (
|
||||
<ListItem
|
||||
key={option}
|
||||
path={path}
|
||||
primaryText={option}
|
||||
rightToggle={
|
||||
<ToggleConfigOption
|
||||
onToggle={this.handleToggle}
|
||||
path={path}
|
||||
toggled={!!getConfigByPath(this.props, path)} />
|
||||
}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<div style={{paddingLeft: 16, paddingRight: 16}}>
|
||||
<SelectConfigOption
|
||||
fullWidth={true}
|
||||
key={option}
|
||||
path={path}
|
||||
style={selectStyle}
|
||||
labelStyle={selectedItemStyle}
|
||||
value={getConfigByPath(this.props, path)}
|
||||
onChange={this.handleChange}
|
||||
floatingLabelText={option}
|
||||
>
|
||||
{section.options[option].map(([value, label]) => (
|
||||
<MenuItem key={value} value={value} primaryText={label} />
|
||||
))}
|
||||
</SelectConfigOption>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
})}
|
||||
</List>
|
||||
))
|
||||
}</div>);
|
||||
}
|
||||
};
|
@ -0,0 +1,15 @@
|
||||
import React from 'react';
|
||||
import pure from 'recompose/pure';
|
||||
import SvgIcon from 'material-ui/SvgIcon';
|
||||
|
||||
let ActionScanBarcode = (props) => (
|
||||
<SvgIcon {...props}>
|
||||
<path transform="translate(2, -3)" d="M0 4h4v20h-4zM6 4h2v20h-2zM10 4h2v20h-2zM16 4h2v20h-2zM24 4h2v20h-2zM30 4h2v20h-2zM20 4h1v20h-1zM14 4h1v20h-1zM27 4h1v20h-1zM0 26h2v2h-2zM6 26h2v2h-2zM10 26h2v2h-2zM20 26h2v2h-2zM30 26h2v2h-2zM24 26h4v2h-4zM14 26h4v2h-4z"/>
|
||||
</SvgIcon>
|
||||
);
|
||||
|
||||
ActionScanBarcode = pure(ActionScanBarcode);
|
||||
ActionScanBarcode.displayName = 'ActionAccessibility';
|
||||
ActionScanBarcode.muiName = 'SvgIcon';
|
||||
|
||||
export default ActionScanBarcode;
|
@ -0,0 +1,99 @@
|
||||
import React from 'react';
|
||||
import {Card, CardActions, CardText, CardHeader} from 'material-ui/Card';
|
||||
import FlatButton from 'material-ui/FlatButton';
|
||||
import DeleteIcon from 'material-ui/svg-icons/action/delete';
|
||||
import Divider from 'material-ui/Divider';
|
||||
|
||||
const pointGenerator = points => i => `(${points[i][0].toFixed(0)}, ${points[i][1].toFixed(0)})`;
|
||||
const lineGenerator = line => i => `(${line[i].x.toFixed(0)}, ${line[i].y.toFixed(0)})`;
|
||||
|
||||
const renderLineSegments = items => (
|
||||
<div style={{display: 'flex', flexWrap: 'wrap', justifyContent: 'space-between', flexBasis: '100%'}}>
|
||||
{items.map((item, i) => (
|
||||
<div key={i} style={{width: `30%`, textAlign: 'center', padding: '0.2rem'}} >{item}</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
|
||||
const renderBox = box => {
|
||||
const p = pointGenerator(box);
|
||||
const items = [p(1), `→`, p(2), `↑`, ' ', `↓`, p(0), `←`, p(3)];
|
||||
return renderLineSegments(items);
|
||||
};
|
||||
|
||||
const renderLine = line => {
|
||||
const l = lineGenerator(line);
|
||||
const items = [l(0), `→`, l(1)];
|
||||
return renderLineSegments(items);
|
||||
};
|
||||
|
||||
const renderAngle = angle => (
|
||||
<span>
|
||||
{(angle * 180 / Math.PI).toFixed(2)}
|
||||
</span>
|
||||
);
|
||||
|
||||
const renderDirection = direction => (
|
||||
<span>
|
||||
{direction === -1 ? 'forward' : `reverse`}
|
||||
</span>
|
||||
);
|
||||
|
||||
const keyStyle = {
|
||||
fontWeight: 'bold',
|
||||
flex: '0 1 70px',
|
||||
};
|
||||
|
||||
const lineStyle = {
|
||||
marginBottom: '0.5rem',
|
||||
marginTop: '0.5rem',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
};
|
||||
|
||||
const ScannedCode = ({scannedCode, onDelete}) => {
|
||||
return (
|
||||
<Card
|
||||
style={{margin: '0.5em 0.25em 0em'}}>
|
||||
<CardHeader
|
||||
textStyle={{paddingRight: '20px', maxWidth: '100%', boxSizing: 'border-box'}}
|
||||
titleStyle={{fontSize: '18px', wordWrap: 'break-word'}}
|
||||
title={scannedCode.codeResult.code}
|
||||
subtitle={scannedCode.codeResult.format}
|
||||
actAsExpander={true}
|
||||
showExpandableButton={true}
|
||||
/>
|
||||
<CardText expandable={true}>
|
||||
<div style={lineStyle}>
|
||||
<div style={keyStyle}>Direction: </div><div>{renderDirection(scannedCode.codeResult.direction)}</div>
|
||||
</div>
|
||||
<Divider />
|
||||
<div style={lineStyle}>
|
||||
<div style={keyStyle}>Angle: </div><div>{renderAngle(scannedCode.angle)} deg</div>
|
||||
</div>
|
||||
<Divider />
|
||||
<div style={lineStyle}>
|
||||
<div style={keyStyle}>Line: </div>{renderLine(scannedCode.line)}
|
||||
</div>
|
||||
<Divider />
|
||||
<div style={lineStyle}>
|
||||
<div style={keyStyle}>Box: </div>
|
||||
{renderBox(scannedCode.box)}
|
||||
</div>
|
||||
</CardText>
|
||||
<CardActions expandable={true}>
|
||||
<FlatButton
|
||||
label=""
|
||||
style={{minWidth: '36px', color: '#aaa'}}
|
||||
onClick={onDelete} icon={<DeleteIcon />} />
|
||||
</CardActions>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
ScannedCode.propTypes = {
|
||||
scannedCode: React.PropTypes.object.isRequired,
|
||||
onDelete: React.PropTypes.func,
|
||||
};
|
||||
|
||||
export default ScannedCode;
|
@ -0,0 +1,47 @@
|
||||
import React from 'react';
|
||||
import Quagga from '../../../../dist/quagga';
|
||||
|
||||
export default class Scanner extends React.Component {
|
||||
static propTypes = {
|
||||
onDetected: React.PropTypes.func,
|
||||
onCancel: React.PropTypes.func,
|
||||
config: React.PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this._scanner = Quagga
|
||||
.config(props.config)
|
||||
.fromSource({
|
||||
...this.props.config.inputStream,
|
||||
target: '.overlay__content',
|
||||
});
|
||||
}
|
||||
|
||||
_onCancel = (e) => {
|
||||
e.preventDefault();
|
||||
if (this._scanUntilResult) {
|
||||
this._scanUntilResult.cancel();
|
||||
this._scanUntilResult = null;
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this._scanUntilResult = this._scanner.toPromise();
|
||||
this._scanUntilResult.promise
|
||||
.then(this.props.onDetected)
|
||||
.catch(this.props.onCancel);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="overlay__content" />
|
||||
);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this._scanner
|
||||
.removeEventListener('detected', this.props.onDetected)
|
||||
.stop();
|
||||
}
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import App from './components/App';
|
||||
import getMuiTheme from 'material-ui/styles/getMuiTheme';
|
||||
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';
|
||||
import injectTapEventPlugin from 'react-tap-event-plugin';
|
||||
|
||||
injectTapEventPlugin();
|
||||
|
||||
ReactDOM.render(
|
||||
<MuiThemeProvider muiTheme={getMuiTheme()}>
|
||||
<App />
|
||||
</MuiThemeProvider>,
|
||||
document.getElementById('react')
|
||||
);
|
@ -0,0 +1,35 @@
|
||||
function storageWrapper(cb) {
|
||||
if (typeof window.localStorage !== 'undefined') {
|
||||
return cb(window.localStorage);
|
||||
} else {
|
||||
console.log("localStorage not available");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export function persist(key, object) {
|
||||
storageWrapper((storage) => {
|
||||
storage.setItem(key, JSON.stringify(object));
|
||||
});
|
||||
};
|
||||
|
||||
export function push(key, object) {
|
||||
storageWrapper((storage) => {
|
||||
const item = storage.getItem() || "[]",
|
||||
parsed = JSON.parse(item);
|
||||
if (Array.isArray(parsed)) {
|
||||
parsed.push(object);
|
||||
storage.setItem(key, JSON.stringify(parsed));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function load(key) {
|
||||
return storageWrapper((storage) => {
|
||||
const item = storage.getItem(key);
|
||||
if (item) {
|
||||
return JSON.parse(item);
|
||||
}
|
||||
return null;
|
||||
});
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 5.9 KiB |
@ -0,0 +1,20 @@
|
||||
html {
|
||||
font-family: 'Roboto', sans-serif;
|
||||
}
|
||||
|
||||
.overlay__content {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.overlay__content video, .overlay__content canvas {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.overlay__content canvas {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
max-width: 100%;
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
module.exports = {
|
||||
entry: [
|
||||
'./src/index.js'
|
||||
],
|
||||
devtool: 'source-map',
|
||||
module: {
|
||||
loaders: [{
|
||||
test: /\.jsx?$/,
|
||||
exclude: /(node_modules|quagga\.js)/,
|
||||
loader: 'babel-loader'
|
||||
}]
|
||||
},
|
||||
resolve: {
|
||||
modules: [
|
||||
'node_modules'
|
||||
]
|
||||
},
|
||||
output: {
|
||||
path: __dirname + '/static',
|
||||
publicPath: '/',
|
||||
filename: 'bundle.js'
|
||||
}
|
||||
};
|
@ -0,0 +1,44 @@
|
||||
var webpack = require('webpack'),
|
||||
path = require('path');
|
||||
|
||||
module.exports = {
|
||||
entry: ['./src/index.js'],
|
||||
output: {
|
||||
path: __dirname + '/static',
|
||||
publicPath: '/',
|
||||
filename: 'bundle.min.js'
|
||||
},
|
||||
resolve: {
|
||||
modules: [
|
||||
'node_modules'
|
||||
],
|
||||
},
|
||||
plugins: [
|
||||
new webpack.DefinePlugin({
|
||||
'process.env': {NODE_ENV: '"production"'}
|
||||
}),
|
||||
new webpack.LoaderOptionsPlugin({
|
||||
minimize: true,
|
||||
debug: false
|
||||
}),
|
||||
new webpack.optimize.UglifyJsPlugin({
|
||||
compress: {
|
||||
warnings: false
|
||||
},
|
||||
output: {
|
||||
comments: /@preserve/
|
||||
},
|
||||
sourceMap: false
|
||||
}),
|
||||
],
|
||||
module: {
|
||||
loaders: [{
|
||||
test: /\.jsx?$/,
|
||||
exclude: /(node_modules|quagga\.js)/,
|
||||
loader: 'babel-loader',
|
||||
}, {
|
||||
test: /\.json$/,
|
||||
loader: "json-loader",
|
||||
}],
|
||||
},
|
||||
};
|
@ -1,10 +0,0 @@
|
||||
i.icon-24-scan{
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6QzFFMjMzNTBFNjcwMTFFMkIzMERGOUMzMzEzM0E1QUMiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6QzFFMjMzNTFFNjcwMTFFMkIzMERGOUMzMzEzM0E1QUMiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDpDMUUyMzM0RUU2NzAxMUUyQjMwREY5QzMzMTMzQTVBQyIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDpDMUUyMzM0RkU2NzAxMUUyQjMwREY5QzMzMTMzQTVBQyIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PtQr90wAAAUuSURBVHjanFVLbFRVGP7ua97T9DGPthbamAYYBNSMVbBpjCliWWGIEBMWsnDJxkh8RDeEDW5MDGticMmGBWnSlRSCwgLFNkqmmrRIqzjTznTazkxn5s7c6/efzm0G0Jhwkj/nP+d/nv91tIWFBTQaDQWapkGW67p4ltUub5qmAi0UCqF/a/U2m81tpmddotwwDGSz2dzi4uKSaOucnJycGhsbe1XXdQiIIcdxEAgEtgXq9brySHCht79UXi/8QheawN27d385fPjwuEl6XyKR6LdtW7t06RLK5TKOHj2K/fv3Q87Dw8OYn5/HiRMnMDs7i5mZGQwODiqlPp8PuVwO6XRaOXb16lXl1OnTp5FMJvtosF8M+MWLarWqGJaWlpBKpRRcu3YN4+PjmJ6exsTEhDJw5coVjI6OKgPhcBiZTAbxeBx+vx+XL19Gd3c3Tp48Ka9zqDYgBlTQxYNgMIhIJKLCILkQb+TZsgvdsiyFi+feWRR7oRNZyanQtvW2V4DEUUBiK2eJpeDirSyhCe7F2QPh8fiEp72i9PbsC5G52DbiKZA771yr1dTuGfJ4PQNPFoAyQNR1aNEmsS5eyB3PgjeooMZd2AWvNmzYci/Gea7TeFOcI93jV/K67noGmi4vdRI9gPSDeMLSdKUBZZczlWm1rTtHjLZ24d+WER2tc8N1m+Y+ID74wx0zGYvhg9UNrJdtHJyZRdQfwPsrq9g99xsGlgsYmr6BNzO/IVwsYfjBQ6XYz6JI/72MV366B5/lw0elOkJWGUM3bmKtWjXSLuLaBWhnPnnp0FfoiFi4+TMfVAb2poBkDLjO845uYLEAjL4ALGWBP5YAOsP4AJYBFDaB1HOSVWD2PuV95H2RdV93Lv74/cf6p6Zxq/h6OofeOPJBC39JtONdwOAAViOs4p4OFGTf0Uc8iiyrr9YdQrUnDLsngrVOC0jQib44HlF2RafRZBz1Qy+vfhgK3NJZBlrm+LEm9qWwzFgLU7Ozg0JxZP06jQSRpQ7EerAWDSt6PuhHPmChEAog56fCLvJT5hHTm3OZkz3DyLx7XNWTGEA1GkV14gjWgwbW0ESVjYRwCOuai03L5E7OUBAV4kXSS4auoGIaKOma4m8EA5R1sMEGLh95C+XuLph0WJWpxepYYLtfT0RRgY1KgNODY6BoaChRuEhDCIZQYseuki5KN6hcQHiq7OZNv4/Zq2O6P4Lfkwn46vZjjaYZrIpvWbpzjLErrc4xUGE4avRedpYJalRcIl5hQius/SrPm9xrNOQYJhao6BvNUeWqtY8KaWuNjHOFAr7mM9f4NA4UbKysoUJ8PV9UzVOx6wxDDWUOxnK1pmCD07fOMAvtIsM3l89Dl3HRGhVma9AZMqjOnz2LQqWCxs6dqr3T7x1DTzKJaG8SekcHhg4cgI/56uKdlKnBV/WndqN3YAB/7tyBd3oT6GBIOzs7kc/nDfFdDFT5bS73cp06dQoaPa/Rw/rtO/resTHxxE2m9rCrbSR27UJCcMf1BpiA5rAAGgdfc868fUR1sMwj0cm9Iu9IctweisViB3hhKTHDcHc5jv/LspbyaZrR1OD82/fIlOkuB9LnEWRmDX2TsddUPg3D5gvuc0je0rZaD5EW6G3yjS+A3eeBEWq3XW/Abw1HhUspXADufQb86oW7tZytkYCN//3hHwBvDALPi8EnSOYK8DAOfCc2h4aGcO7cuafkzampqf9UripH12/DtOZbx8ciVGzYy5OO40o25ascGRl5Ssc/AgwAjW3JwqIUjSYAAAAASUVORK5CYII=");
|
||||
display: inline-block;
|
||||
background-repeat: no-repeat;
|
||||
line-height: 24px;
|
||||
margin-top: 1px;
|
||||
vertical-align: text-top;
|
||||
}
|
@ -1,48 +0,0 @@
|
||||
$overlayWidth: $videoWidth;
|
||||
$overlayHeadline: 16px;
|
||||
$overlayHeadlineMargin: 14px;
|
||||
$overlayPadding: 20px;
|
||||
$overlayHeight: $videoHeight + $overlayHeadlineMargin + $overlayHeadline;
|
||||
|
||||
.scanner-overlay {
|
||||
display: none;
|
||||
width: $overlayWidth;
|
||||
height: $overlayHeight;
|
||||
position: absolute;
|
||||
padding: $overlayPadding;
|
||||
top: 50%;
|
||||
margin-top: -($overlayHeight)/2 - $overlayPadding;
|
||||
left: 50%;
|
||||
margin-left: -($overlayWidth + 2*$overlayPadding)/2;
|
||||
background-color: $overlayBackground;
|
||||
@include box-shadow(#333333 0px 4px 10px);
|
||||
|
||||
& > .header{
|
||||
position: relative;
|
||||
margin-bottom: $overlayHeadlineMargin;
|
||||
h4, .close{
|
||||
line-height: $overlayHeadline;
|
||||
}
|
||||
h4{
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
}
|
||||
.close{
|
||||
position: absolute;
|
||||
right: 0px;
|
||||
top: 0px;
|
||||
height: $overlayHeadline;
|
||||
width: $overlayHeadline;
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
& > .body{
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
@import "phone/core";
|
||||
@import "phone/viewport";
|
||||
@import "phone/overlay";
|
@ -1,4 +0,0 @@
|
||||
@include tablet {
|
||||
/* tablet styles */
|
||||
|
||||
}
|
@ -1,17 +0,0 @@
|
||||
$tablet_upper: 1024px;
|
||||
$phone_upper: 603px;
|
||||
|
||||
@mixin phone {
|
||||
@media (max-width: $phone_upper) {
|
||||
@content;
|
||||
}
|
||||
|
||||
} // @mixin phone
|
||||
|
||||
@mixin tablet {
|
||||
|
||||
@media (min-width: $phone_upper+1) and (max-width: $tablet_upper) {
|
||||
@content;
|
||||
}
|
||||
|
||||
} // @mixin tablet
|
@ -1,7 +0,0 @@
|
||||
$background_color: #FFF;
|
||||
$overlayBackground: #FFF;
|
||||
$videoWidth: 640px;
|
||||
$videoHeight: 480px;
|
||||
|
||||
$text-font: Ubuntu, sans-serif;
|
||||
$header-font: 'Cabin Condensed', sans-serif;
|
@ -1,89 +0,0 @@
|
||||
#interactive.viewport{
|
||||
width: $videoWidth;
|
||||
height: $videoHeight;
|
||||
}
|
||||
|
||||
#interactive.viewport canvas, video{
|
||||
float: left;
|
||||
width: $videoWidth;
|
||||
height: $videoHeight;
|
||||
&.drawingBuffer{
|
||||
margin-left: -$videoWidth;
|
||||
}
|
||||
}
|
||||
|
||||
.controls {
|
||||
fieldset {
|
||||
border: none;
|
||||
}
|
||||
.input-group {
|
||||
float: left;
|
||||
input, button {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
.reader-config-group {
|
||||
float: right;
|
||||
|
||||
label {
|
||||
display: block;
|
||||
span {
|
||||
width: 11rem;
|
||||
display: inline-block;
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
}
|
||||
&:after {
|
||||
content:'';
|
||||
display: block;
|
||||
clear: both;
|
||||
}
|
||||
}
|
||||
|
||||
#result_strip{
|
||||
margin: 10px 0;
|
||||
border-top: 1px solid #EEE;
|
||||
border-bottom: 1px solid #EEE;
|
||||
padding: 10px 0;
|
||||
|
||||
& > ul {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
list-style-type: none;
|
||||
width: auto;
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
white-space: nowrap;
|
||||
|
||||
& > li{
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
width: $videoWidth/4;
|
||||
.thumbnail{
|
||||
padding: 5px;
|
||||
margin: 4px;
|
||||
border: 1px dashed #CCC;
|
||||
|
||||
img{
|
||||
max-width: $videoWidth/4 - 20px;
|
||||
}
|
||||
.caption{
|
||||
white-space: normal;
|
||||
h4{
|
||||
text-align: center;
|
||||
word-wrap: break-word;
|
||||
height: 40px;
|
||||
margin: 0px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&:after{
|
||||
content: "";
|
||||
display: table;
|
||||
clear: both;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,55 +0,0 @@
|
||||
/* LESS - http://lesscss.org style sheet */
|
||||
/* Palette color codes */
|
||||
/* Palette URL: http://paletton.com/#uid=31g0q0kHZAviRSkrHLOGomVNzac */
|
||||
|
||||
/* Feel free to copy&paste color codes to your application */
|
||||
|
||||
|
||||
/* MIXINS */
|
||||
|
||||
/* As hex codes */
|
||||
|
||||
$color-primary-0: #FFC600; /* Main Primary color */
|
||||
$color-primary-1: #FFDD69;
|
||||
$color-primary-2: #FFCE22;
|
||||
$color-primary-3: #B78E00;
|
||||
$color-primary-4: #513F00;
|
||||
|
||||
$color-secondary-1-0: #0A4DB7; /* Main Secondary color (1) */
|
||||
$color-secondary-1-1: #6C9CE8;
|
||||
$color-secondary-1-2: #2E6FD6;
|
||||
$color-secondary-1-3: #073379;
|
||||
$color-secondary-1-4: #021636;
|
||||
|
||||
$color-secondary-2-0: #5809BB; /* Main Secondary color (2) */
|
||||
$color-secondary-2-1: #A36BE9;
|
||||
$color-secondary-2-2: #782DD8;
|
||||
$color-secondary-2-3: #3A077C;
|
||||
$color-secondary-2-4: #190237;
|
||||
|
||||
|
||||
|
||||
/* As RGBa codes */
|
||||
|
||||
$rgba-primary-0: rgba(255,198, 0,1); /* Main Primary color */
|
||||
$rgba-primary-1: rgba(255,221,105,1);
|
||||
$rgba-primary-2: rgba(255,206, 34,1);
|
||||
$rgba-primary-3: rgba(183,142, 0,1);
|
||||
$rgba-primary-4: rgba( 81, 63, 0,1);
|
||||
|
||||
$rgba-secondary-1-0: rgba( 10, 77,183,1); /* Main Secondary color (1) */
|
||||
$rgba-secondary-1-1: rgba(108,156,232,1);
|
||||
$rgba-secondary-1-2: rgba( 46,111,214,1);
|
||||
$rgba-secondary-1-3: rgba( 7, 51,121,1);
|
||||
$rgba-secondary-1-4: rgba( 2, 22, 54,1);
|
||||
|
||||
$rgba-secondary-2-0: rgba( 88, 9,187,1); /* Main Secondary color (2) */
|
||||
$rgba-secondary-2-1: rgba(163,107,233,1);
|
||||
$rgba-secondary-2-2: rgba(120, 45,216,1);
|
||||
$rgba-secondary-2-3: rgba( 58, 7,124,1);
|
||||
$rgba-secondary-2-4: rgba( 25, 2, 55,1);
|
||||
|
||||
|
||||
|
||||
/* Generated by Paletton.com © 2002-2014 */
|
||||
/* http://paletton.com */
|
@ -1 +0,0 @@
|
||||
@import url('http://fonts.googleapis.com/css?family=Ubuntu:400,700|Cabin+Condensed:400,600');
|
@ -1,15 +0,0 @@
|
||||
@include phone {
|
||||
#container{
|
||||
width: 300px;
|
||||
margin: 10px auto;
|
||||
@include box-shadow(none);
|
||||
|
||||
form.voucher-form{
|
||||
input{
|
||||
&.voucher-code{
|
||||
width: 180px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,28 +0,0 @@
|
||||
@include phone {
|
||||
$overlayWidth: $videoWidth;
|
||||
$overlayHeadline: 16px;
|
||||
$overlayHeadlineMargin: 14px;
|
||||
$overlayPadding: 20px;
|
||||
$overlayHeight: $videoHeight + $overlayHeadlineMargin + $overlayHeadline;
|
||||
|
||||
.overlay.scanner {
|
||||
width: $overlayWidth;
|
||||
height: $overlayHeight;
|
||||
padding: $overlayPadding;
|
||||
margin-top: -($overlayHeight)/2 - $overlayPadding;
|
||||
margin-left: -($overlayWidth + 2*$overlayPadding)/2;
|
||||
background-color: $overlayBackground;
|
||||
@include box-shadow(none);
|
||||
|
||||
& > .header{
|
||||
margin-bottom: $overlayHeadlineMargin;
|
||||
h4, .close{
|
||||
line-height: $overlayHeadline;
|
||||
}
|
||||
.close{
|
||||
height: $overlayHeadline;
|
||||
width: $overlayHeadline;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,42 +0,0 @@
|
||||
@include phone {
|
||||
$videoWidth: 300px;
|
||||
$videoHeight: 400px;
|
||||
|
||||
#interactive.viewport{
|
||||
width: $videoWidth;
|
||||
height: $videoWidth;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#interactive.viewport canvas, video{
|
||||
margin-top: ($videoWidth - $videoHeight)/2;
|
||||
width: $videoWidth;
|
||||
height: $videoHeight;
|
||||
&.drawingBuffer{
|
||||
margin-left: -$videoWidth;
|
||||
}
|
||||
}
|
||||
|
||||
#result_strip{
|
||||
margin-top: 5px;
|
||||
padding-top: 5px;
|
||||
ul.thumbnails{
|
||||
& > li{
|
||||
width: $videoWidth/2;
|
||||
.thumbnail{
|
||||
.imgWrapper{
|
||||
width: $videoWidth/2 - 20px;
|
||||
height: $videoWidth/2 - 20px;
|
||||
overflow: hidden;
|
||||
img{
|
||||
margin-top: (($videoWidth/2 - 20px) - ($videoHeight/2 - 20px))/2;
|
||||
width: $videoWidth/2 - 20px;
|
||||
height: $videoHeight/2 - 20px;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
|
||||
@import "compass/css3";
|
||||
@import "variables";
|
||||
@import "utility";
|
||||
|
||||
/* usual styles */
|
||||
|
||||
@import "fonts";
|
||||
@import "viewport";
|
||||
@import "overlay";
|
||||
@import "icons";
|
||||
|
||||
@import "tablet";
|
||||
@import "phone";
|
@ -1,56 +0,0 @@
|
||||
@import "compass/css3";
|
||||
@import "variables";
|
||||
@import "utility";
|
||||
|
||||
/* usual styles */
|
||||
@import "colors";
|
||||
@import "fonts";
|
||||
@import "viewport";
|
||||
@import "overlay";
|
||||
@import "icons";
|
||||
|
||||
@import "tablet";
|
||||
@import "phone";
|
||||
|
||||
body{
|
||||
background-color: #FFF;
|
||||
margin: 0px;
|
||||
font-family: $text-font;
|
||||
color: #1e1e1e;
|
||||
font-weight: normal;
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
h1,h2,h3,h4 {
|
||||
font-family: $header-font;
|
||||
}
|
||||
|
||||
header {
|
||||
background: $color-primary-0;
|
||||
padding: 1em;
|
||||
.headline {
|
||||
width: 640px;
|
||||
margin: 0 auto;
|
||||
h1 {
|
||||
color: $color-primary-1;
|
||||
font-size: 3em;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
h2 {
|
||||
margin-top: 0.2em;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
footer {
|
||||
background: $color-secondary-1-0;
|
||||
color: $color-secondary-1-1;
|
||||
padding: 1em 2em 2em;
|
||||
}
|
||||
|
||||
#container{
|
||||
width: 640px;
|
||||
margin: 20px auto;
|
||||
padding: 10px;
|
||||
}
|
||||
|
@ -0,0 +1,159 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
|
||||
|
||||
<title>index</title>
|
||||
<meta name="description" content="" />
|
||||
<meta name="author" content="Christoph Oberhofer" />
|
||||
|
||||
<meta name="viewport" content="width=device-width; initial-scale=1.0" />
|
||||
<link rel="stylesheet" type="text/css" href="../css/fonts.css" />
|
||||
<link rel="stylesheet" type="text/css" href="../css/styles.css" />
|
||||
<link rel="stylesheet" type="text/css" href="../css/prism.css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<header>
|
||||
<div class="headline">
|
||||
<h1>QuaggaJS</h1>
|
||||
<h2>An advanced barcode-scanner written in JavaScript</h2>
|
||||
</div>
|
||||
</header>
|
||||
<section id="container" class="container">
|
||||
<h3>Scan barcode to input-field</h3>
|
||||
<p>Click the <strong>button</strong> next to the input-field
|
||||
to start scanning an <strong>EAN-13</strong> barcode</p>
|
||||
<div>
|
||||
<form>
|
||||
<div class="input-field">
|
||||
<label for="isbn_input">EAN:</label>
|
||||
<input id="isbn_input" class="isbn" type="text" />
|
||||
<button type="button" class="icon-barcode button scan"> </button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<p>This example demonstrates the following features:
|
||||
<ul>
|
||||
<li>Access to user's camera</li>
|
||||
<li>Configuring EAN-Reader</li>
|
||||
<li>Use custom mount-point (Query-Selector)</li>
|
||||
<li>Start/Stop scanning based on certain events</li>
|
||||
</ul>
|
||||
</p>
|
||||
<div class="source-code">
|
||||
<h4>Source</h4>
|
||||
<div class="collapsable-source">
|
||||
<pre>
|
||||
<code class="language-javascript">
|
||||
var Quagga = window.Quagga;
|
||||
var App = {
|
||||
_scanner: null,
|
||||
init: function() {
|
||||
this.attachListeners();
|
||||
},
|
||||
activateScanner: function() {
|
||||
var scanner = this.configureScanner('.overlay__content'),
|
||||
onDetected = function (result) {
|
||||
document.querySelector('input.isbn').value = result.codeResult.code;
|
||||
stop();
|
||||
}.bind(this),
|
||||
stop = function() {
|
||||
scanner.stop(); // should also clear all event-listeners?
|
||||
scanner.removeEventListener('detected', onDetected);
|
||||
this.hideOverlay();
|
||||
this.attachListeners();
|
||||
}.bind(this);
|
||||
|
||||
this.showOverlay(stop);
|
||||
scanner.addEventListener('detected', onDetected).start();
|
||||
},
|
||||
attachListeners: function() {
|
||||
var self = this,
|
||||
button = document.querySelector('.input-field input + button.scan');
|
||||
|
||||
button.addEventListener("click", function onClick(e) {
|
||||
e.preventDefault();
|
||||
button.removeEventListener("click", onClick);
|
||||
self.activateScanner();
|
||||
});
|
||||
},
|
||||
showOverlay: function(cancelCb) {
|
||||
if (!this._overlay) {
|
||||
var content = document.createElement('div'),
|
||||
closeButton = document.createElement('div');
|
||||
|
||||
closeButton.appendChild(document.createTextNode('X'));
|
||||
content.className = 'overlay__content';
|
||||
closeButton.className = 'overlay__close';
|
||||
this._overlay = document.createElement('div');
|
||||
this._overlay.className = 'overlay';
|
||||
this._overlay.appendChild(content);
|
||||
content.appendChild(closeButton);
|
||||
closeButton.addEventListener('click', function closeClick() {
|
||||
closeButton.removeEventListener('click', closeClick);
|
||||
cancelCb();
|
||||
});
|
||||
document.body.appendChild(this._overlay);
|
||||
} else {
|
||||
var closeButton = document.querySelector('.overlay__close');
|
||||
closeButton.addEventListener('click', function closeClick() {
|
||||
closeButton.removeEventListener('click', closeClick);
|
||||
cancelCb();
|
||||
});
|
||||
}
|
||||
this._overlay.style.display = "block";
|
||||
},
|
||||
hideOverlay: function() {
|
||||
if (this._overlay) {
|
||||
this._overlay.style.display = "none";
|
||||
}
|
||||
},
|
||||
configureScanner: function(selector) {
|
||||
if (!this._scanner) {
|
||||
this._scanner = Quagga
|
||||
.decoder({readers: ['ean_reader']})
|
||||
.locator({patchSize: 'medium'})
|
||||
.fromVideo({
|
||||
target: selector,
|
||||
constraints: {
|
||||
width: 800,
|
||||
height: 600,
|
||||
facingMode: "environment"
|
||||
}
|
||||
});
|
||||
}
|
||||
return this._scanner;
|
||||
}
|
||||
};
|
||||
App.init();
|
||||
</code>
|
||||
</pre>
|
||||
</div>
|
||||
<div class="collapsable-source">
|
||||
<pre>
|
||||
<code class="language-html">
|
||||
<form>
|
||||
<div class="input-field">
|
||||
<label for="isbn_input">EAN:</label>
|
||||
<input id="isbn_input" class="isbn" type="text" />
|
||||
<button type="button" class="icon-barcode button scan"> </button>
|
||||
</div>
|
||||
</form>
|
||||
</code>
|
||||
</pre>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<footer>
|
||||
<p>
|
||||
© Copyright by Christoph Oberhofer
|
||||
</p>
|
||||
</footer>
|
||||
|
||||
<script src="../../dist/quagga.js" type="text/javascript"></script>
|
||||
<script src="index.js" type="text/javascript"></script>
|
||||
<script src="../vendor/prism.js"></script>
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,81 @@
|
||||
var Quagga = window.Quagga;
|
||||
var App = {
|
||||
_scanner: null,
|
||||
init: function() {
|
||||
this.attachListeners();
|
||||
},
|
||||
activateScanner: function() {
|
||||
var scanner = this.configureScanner('.overlay__content'),
|
||||
onDetected = function (result) {
|
||||
document.querySelector('input.isbn').value = result.codeResult.code;
|
||||
stop();
|
||||
}.bind(this),
|
||||
stop = function() {
|
||||
scanner.stop(); // should also clear all event-listeners?
|
||||
scanner.removeEventListener('detected', onDetected);
|
||||
this.hideOverlay();
|
||||
this.attachListeners();
|
||||
}.bind(this);
|
||||
|
||||
this.showOverlay(stop);
|
||||
scanner.addEventListener('detected', onDetected).start();
|
||||
},
|
||||
attachListeners: function() {
|
||||
var self = this,
|
||||
button = document.querySelector('.input-field input + button.scan');
|
||||
|
||||
button.addEventListener("click", function onClick(e) {
|
||||
e.preventDefault();
|
||||
button.removeEventListener("click", onClick);
|
||||
self.activateScanner();
|
||||
});
|
||||
},
|
||||
showOverlay: function(cancelCb) {
|
||||
if (!this._overlay) {
|
||||
var content = document.createElement('div'),
|
||||
closeButton = document.createElement('div');
|
||||
|
||||
closeButton.appendChild(document.createTextNode('X'));
|
||||
content.className = 'overlay__content';
|
||||
closeButton.className = 'overlay__close';
|
||||
this._overlay = document.createElement('div');
|
||||
this._overlay.className = 'overlay';
|
||||
this._overlay.appendChild(content);
|
||||
content.appendChild(closeButton);
|
||||
closeButton.addEventListener('click', function closeClick() {
|
||||
closeButton.removeEventListener('click', closeClick);
|
||||
cancelCb();
|
||||
});
|
||||
document.body.appendChild(this._overlay);
|
||||
} else {
|
||||
var closeButton = document.querySelector('.overlay__close');
|
||||
closeButton.addEventListener('click', function closeClick() {
|
||||
closeButton.removeEventListener('click', closeClick);
|
||||
cancelCb();
|
||||
});
|
||||
}
|
||||
this._overlay.style.display = "block";
|
||||
},
|
||||
hideOverlay: function() {
|
||||
if (this._overlay) {
|
||||
this._overlay.style.display = "none";
|
||||
}
|
||||
},
|
||||
configureScanner: function(selector) {
|
||||
if (!this._scanner) {
|
||||
this._scanner = Quagga
|
||||
.decoder({readers: ['ean_reader']})
|
||||
.locator({patchSize: 'medium'})
|
||||
.fromSource({
|
||||
target: selector,
|
||||
constraints: {
|
||||
width: 800,
|
||||
height: 600,
|
||||
facingMode: "environment"
|
||||
}
|
||||
});
|
||||
}
|
||||
return this._scanner;
|
||||
}
|
||||
};
|
||||
App.init();
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -0,0 +1,25 @@
|
||||
const hasWindow = typeof window !== 'undefined';
|
||||
const windowRef = hasWindow ? window : {};
|
||||
|
||||
const windowObjects = [
|
||||
"MediaStream",
|
||||
"HTMLImageElement",
|
||||
"HTMLVideoElement",
|
||||
"HTMLCanvasElement",
|
||||
"FileList",
|
||||
"File",
|
||||
"URL"
|
||||
];
|
||||
|
||||
const DOMHelper = windowObjects.reduce((result, obj) => {
|
||||
return {
|
||||
...result,
|
||||
[obj]: obj in windowRef ? windowRef[obj] : () => {}
|
||||
};
|
||||
}, {});
|
||||
|
||||
DOMHelper.setObject = (key, value) => {
|
||||
DOMHelper[key] = value;
|
||||
};
|
||||
|
||||
export default DOMHelper;
|
@ -0,0 +1,138 @@
|
||||
import {merge, pick, omitBy, isEmpty} from 'lodash';
|
||||
|
||||
import DOMHelper from '../common/dom_helper';
|
||||
|
||||
const isDataURL = {regex: /^\s*data:([a-z]+\/[a-z0-9\-\+]+(;[a-z\-]+\=[a-z0-9\-]+)?)?(;base64)?,[a-z0-9\!\$\&\'\,\(\)\*\+\,\;\=\-\.\_\~\:\@\/\?\%\s]*\s*$/i}, // eslint-disable-line max-len
|
||||
isBlobURL = {regex: /^\s*blob:(.*)$/i},
|
||||
isMediaURL = {regex: /^(?:(?:http[s]?|ftp):\/)?\/?(?:(?:[^:\/\s]+)(?:(?:\/\w+)*\/))?([\w\-]+\.([^#?\s]+))(?:.*)?(?:#[\w\-]+)?$/i}, // eslint-disable-line max-len
|
||||
isImageExt = {regex: /(jpe?g|png|gif|tiff)(?:\s+|$)/i},
|
||||
isVideoExt = {regex: /(webm|ogg|mp4|m4v)/i};
|
||||
|
||||
export function createConfigFromSource(config, sourceConfig, source) {
|
||||
if (source instanceof DOMHelper.MediaStream) {
|
||||
return createConfigForStream(config, sourceConfig, {srcObject: source});
|
||||
} else if (source instanceof DOMHelper.HTMLImageElement) {
|
||||
throw new Error('Source "HTMLImageElement": not yet supported');
|
||||
// return createConfigForImage(config, inputConfig, {image: source});
|
||||
} else if (source instanceof DOMHelper.HTMLVideoElement) {
|
||||
throw new Error('Source "HTMLVideoElement": not yet supported');
|
||||
// return createConfigForVideo(config, inputConfig, {video: source});
|
||||
} else if (source instanceof DOMHelper.HTMLCanvasElement) {
|
||||
return createConfigForCanvas(config, sourceConfig, {canvas: source});
|
||||
} else if (source instanceof DOMHelper.FileList) {
|
||||
if (source.length > 0) {
|
||||
return createConfigForFile(config, sourceConfig, source[0]);
|
||||
}
|
||||
} else if (source instanceof DOMHelper.File) {
|
||||
return createConfigForFile(config, sourceConfig, source);
|
||||
} else if (typeof source === 'string') {
|
||||
return createConfigForString(config, sourceConfig, source);
|
||||
} else if (typeof source === 'object'
|
||||
&& (typeof source.constraints !== 'undefined'
|
||||
|| typeof source.area !== 'undefined')) {
|
||||
return createConfigForLiveStream(config, source);
|
||||
} else {
|
||||
throw new Error("No source given!");
|
||||
}
|
||||
}
|
||||
|
||||
function createConfigForImage(config, source, inputConfig = {}) {
|
||||
const staticImageConfig = {
|
||||
inputStream: merge({
|
||||
type: "ImageStream",
|
||||
sequence: false,
|
||||
size: 800
|
||||
}, source),
|
||||
numOfWorkers: (ENV.development && config.debug) ? 0 : 1
|
||||
};
|
||||
return merge(
|
||||
config,
|
||||
staticImageConfig,
|
||||
{numOfWorkers: typeof config.numOfWorkers === 'number' && config.numOfWorkers > 0 ? 1 : 0},
|
||||
{inputStream: omitBy(pick(config.inputStream, ['size']), isEmpty)},
|
||||
{inputStream: inputConfig});
|
||||
}
|
||||
|
||||
function createConfigForMimeType(config, inputConfig, {src, mime}) {
|
||||
const [, type] = mime.match(/^(video|image)\/(.*)$/i) || [];
|
||||
if (type === 'video') {
|
||||
return createConfigForVideo(config, {src}, inputConfig);
|
||||
} else if (type === 'image') {
|
||||
return createConfigForImage(config, {src}, inputConfig);
|
||||
}
|
||||
throw new Error(`Source with mimetype: "${type}" not supported`);
|
||||
}
|
||||
|
||||
function createConfigForFile(config, inputConfig, file) {
|
||||
const src = DOMHelper.URL.createObjectURL(file);
|
||||
return createConfigForMimeType(config, inputConfig, {
|
||||
src,
|
||||
mime: file.type
|
||||
});
|
||||
}
|
||||
|
||||
function createConfigForString(config, inputConfig = {}, source) {
|
||||
const [, mime] = source.match(isDataURL.regex) || [];
|
||||
if (mime) {
|
||||
return createConfigForMimeType(config, inputConfig, {src: source, mime});
|
||||
}
|
||||
const blobURL = source.match(isBlobURL.regex);
|
||||
if (blobURL) {
|
||||
throw new Error(`Source "objectURL": not supported`);
|
||||
}
|
||||
const [, , ext] = source.match(isMediaURL.regex) || [];
|
||||
if (ext) {
|
||||
return createConfigForMediaExtension(config, inputConfig, {src: source, ext});
|
||||
}
|
||||
throw new Error(`Source "${source}": not recognized`);
|
||||
}
|
||||
|
||||
function createConfigForMediaExtension(config, inputConfig, {src, ext}) {
|
||||
if (ext.match(isImageExt.regex)) {
|
||||
return createConfigForImage(config, {src}, inputConfig);
|
||||
} else if (ext.match(isVideoExt.regex)) {
|
||||
return createConfigForVideo(config, {src}, inputConfig);
|
||||
}
|
||||
throw new Error(`Source "MediaString": not recognized`);
|
||||
}
|
||||
|
||||
function createConfigForCanvas (config, {canvas}, inputConfig = {}) {
|
||||
// TODO: adjust stream & frame-grabber
|
||||
// once/continous
|
||||
throw new Error('Source "Canvas": not implemented!');
|
||||
}
|
||||
|
||||
function createConfigForVideo (config, source, inputConfig = {}) {
|
||||
return merge({},
|
||||
config,
|
||||
{
|
||||
inputStream: merge({
|
||||
type: "VideoStream"
|
||||
}, source)
|
||||
}, {
|
||||
inputStream: inputConfig
|
||||
});
|
||||
}
|
||||
|
||||
function createConfigForStream(config, {srcObject}, inputConfig = {}) {
|
||||
// TODO: attach to <video> element
|
||||
// wait for the video to be ready (dimensions known)
|
||||
throw new Error('Source "MediaStream": not implemented!');
|
||||
}
|
||||
|
||||
function createConfigForLiveStream(config, inputConfig = {}) {
|
||||
return merge({},
|
||||
config,
|
||||
{
|
||||
inputStream: {
|
||||
type: "LiveStream",
|
||||
constraints: {
|
||||
width: 640,
|
||||
height: 480,
|
||||
facingMode: "environment"
|
||||
}
|
||||
}
|
||||
}, {
|
||||
inputStream: inputConfig
|
||||
});
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -1,543 +1,137 @@
|
||||
import TypeDefs from './common/typedefs'; // eslint-disable-line no-unused-vars
|
||||
import WebrtcAdapter from 'webrtc-adapter'; // eslint-disable-line no-unused-vars
|
||||
import './common/typedefs';
|
||||
import 'webrtc-adapter';
|
||||
import createScanner from './scanner';
|
||||
import ImageWrapper from './common/image_wrapper';
|
||||
import BarcodeLocator from './locator/barcode_locator';
|
||||
import BarcodeDecoder from './decoder/barcode_decoder';
|
||||
import Events from './common/events';
|
||||
import CameraAccess from './input/camera_access';
|
||||
import ImageDebug from './common/image_debug';
|
||||
import ResultCollector from './analytics/result_collector';
|
||||
import Config from './config/config';
|
||||
import InputStream from 'input_stream';
|
||||
import FrameGrabber from 'frame_grabber';
|
||||
import {merge} from 'lodash';
|
||||
const vec2 = {
|
||||
clone: require('gl-vec2/clone')
|
||||
};
|
||||
import {createConfigFromSource} from './input/config_factory';
|
||||
|
||||
var _inputStream,
|
||||
_framegrabber,
|
||||
_stopped,
|
||||
_canvasContainer = {
|
||||
ctx: {
|
||||
image: null,
|
||||
overlay: null
|
||||
function fromConfig(config) {
|
||||
const scanner = createScanner();
|
||||
let pendingStart = null;
|
||||
let initialized = false;
|
||||
return {
|
||||
addEventListener(eventType, cb) {
|
||||
scanner.subscribe(eventType, cb);
|
||||
return this;
|
||||
},
|
||||
dom: {
|
||||
image: null,
|
||||
overlay: null
|
||||
}
|
||||
},
|
||||
_inputImageWrapper,
|
||||
_boxSize,
|
||||
_decoder,
|
||||
_workerPool = [],
|
||||
_onUIThread = true,
|
||||
_resultCollector,
|
||||
_config = {};
|
||||
|
||||
function initializeData(imageWrapper) {
|
||||
initBuffers(imageWrapper);
|
||||
_decoder = BarcodeDecoder.create(_config.decoder, _inputImageWrapper);
|
||||
}
|
||||
|
||||
function initInputStream(cb) {
|
||||
var video;
|
||||
if (_config.inputStream.type === "VideoStream") {
|
||||
video = document.createElement("video");
|
||||
_inputStream = InputStream.createVideoStream(video);
|
||||
} else if (_config.inputStream.type === "ImageStream") {
|
||||
_inputStream = InputStream.createImageStream();
|
||||
} else if (_config.inputStream.type === "LiveStream") {
|
||||
var $viewport = getViewPort();
|
||||
if ($viewport) {
|
||||
video = $viewport.querySelector("video");
|
||||
if (!video) {
|
||||
video = document.createElement("video");
|
||||
$viewport.appendChild(video);
|
||||
}
|
||||
}
|
||||
_inputStream = InputStream.createLiveStream(video);
|
||||
CameraAccess.request(video, _config.inputStream.constraints)
|
||||
.then(() => {
|
||||
_inputStream.trigger("canrecord");
|
||||
}).catch((err) => {
|
||||
return cb(err);
|
||||
});
|
||||
}
|
||||
|
||||
_inputStream.setAttribute("preload", "auto");
|
||||
_inputStream.setInputStream(_config.inputStream);
|
||||
_inputStream.addEventListener("canrecord", canRecord.bind(undefined, cb));
|
||||
}
|
||||
|
||||
function getViewPort() {
|
||||
var target = _config.inputStream.target;
|
||||
// Check if target is already a DOM element
|
||||
if (target && target.nodeName && target.nodeType === 1) {
|
||||
return target;
|
||||
} else {
|
||||
// Use '#interactive.viewport' as a fallback selector (backwards compatibility)
|
||||
var selector = typeof target === 'string' ? target : '#interactive.viewport';
|
||||
return document.querySelector(selector);
|
||||
}
|
||||
}
|
||||
|
||||
function canRecord(cb) {
|
||||
BarcodeLocator.checkImageConstraints(_inputStream, _config.locator);
|
||||
initCanvas(_config);
|
||||
_framegrabber = FrameGrabber.create(_inputStream, _canvasContainer.dom.image);
|
||||
|
||||
adjustWorkerPool(_config.numOfWorkers, function() {
|
||||
if (_config.numOfWorkers === 0) {
|
||||
initializeData();
|
||||
}
|
||||
ready(cb);
|
||||
});
|
||||
}
|
||||
|
||||
function ready(cb){
|
||||
_inputStream.play();
|
||||
cb();
|
||||
}
|
||||
|
||||
function initCanvas() {
|
||||
if (typeof document !== "undefined") {
|
||||
var $viewport = getViewPort();
|
||||
_canvasContainer.dom.image = document.querySelector("canvas.imgBuffer");
|
||||
if (!_canvasContainer.dom.image) {
|
||||
_canvasContainer.dom.image = document.createElement("canvas");
|
||||
_canvasContainer.dom.image.className = "imgBuffer";
|
||||
if ($viewport && _config.inputStream.type === "ImageStream") {
|
||||
$viewport.appendChild(_canvasContainer.dom.image);
|
||||
removeEventListener(eventType, cb) {
|
||||
scanner.unsubscribe(eventType, cb);
|
||||
return this;
|
||||
},
|
||||
start() {
|
||||
if (scanner.isRunning()) {
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
}
|
||||
_canvasContainer.ctx.image = _canvasContainer.dom.image.getContext("2d");
|
||||
_canvasContainer.dom.image.width = _inputStream.getCanvasSize().x;
|
||||
_canvasContainer.dom.image.height = _inputStream.getCanvasSize().y;
|
||||
|
||||
_canvasContainer.dom.overlay = document.querySelector("canvas.drawingBuffer");
|
||||
if (!_canvasContainer.dom.overlay) {
|
||||
_canvasContainer.dom.overlay = document.createElement("canvas");
|
||||
_canvasContainer.dom.overlay.className = "drawingBuffer";
|
||||
if ($viewport) {
|
||||
$viewport.appendChild(_canvasContainer.dom.overlay);
|
||||
if (pendingStart) {
|
||||
return pendingStart;
|
||||
}
|
||||
var clearFix = document.createElement("br");
|
||||
clearFix.setAttribute("clear", "all");
|
||||
if ($viewport) {
|
||||
$viewport.appendChild(clearFix);
|
||||
if (initialized) {
|
||||
scanner.start();
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
}
|
||||
_canvasContainer.ctx.overlay = _canvasContainer.dom.overlay.getContext("2d");
|
||||
_canvasContainer.dom.overlay.width = _inputStream.getCanvasSize().x;
|
||||
_canvasContainer.dom.overlay.height = _inputStream.getCanvasSize().y;
|
||||
}
|
||||
}
|
||||
|
||||
function initBuffers(imageWrapper) {
|
||||
if (imageWrapper) {
|
||||
_inputImageWrapper = imageWrapper;
|
||||
} else {
|
||||
_inputImageWrapper = new ImageWrapper({
|
||||
x: _inputStream.getWidth(),
|
||||
y: _inputStream.getHeight()
|
||||
});
|
||||
}
|
||||
|
||||
if (ENV.development) {
|
||||
console.log(_inputImageWrapper.size);
|
||||
}
|
||||
_boxSize = [
|
||||
vec2.clone([0, 0]),
|
||||
vec2.clone([0, _inputImageWrapper.size.y]),
|
||||
vec2.clone([_inputImageWrapper.size.x, _inputImageWrapper.size.y]),
|
||||
vec2.clone([_inputImageWrapper.size.x, 0])
|
||||
];
|
||||
BarcodeLocator.init(_inputImageWrapper, _config.locator);
|
||||
}
|
||||
|
||||
function getBoundingBoxes() {
|
||||
if (_config.locate) {
|
||||
return BarcodeLocator.locate();
|
||||
} else {
|
||||
return [[
|
||||
vec2.clone(_boxSize[0]),
|
||||
vec2.clone(_boxSize[1]),
|
||||
vec2.clone(_boxSize[2]),
|
||||
vec2.clone(_boxSize[3])]];
|
||||
}
|
||||
}
|
||||
|
||||
function transformResult(result) {
|
||||
var topRight = _inputStream.getTopRight(),
|
||||
xOffset = topRight.x,
|
||||
yOffset = topRight.y,
|
||||
i;
|
||||
|
||||
if (xOffset === 0 && yOffset === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (result.barcodes) {
|
||||
for (i = 0; i < result.barcodes.length; i++) {
|
||||
transformResult(result.barcodes[i]);
|
||||
}
|
||||
}
|
||||
|
||||
if (result.line && result.line.length === 2) {
|
||||
moveLine(result.line);
|
||||
}
|
||||
|
||||
if (result.box) {
|
||||
moveBox(result.box);
|
||||
}
|
||||
|
||||
if (result.boxes && result.boxes.length > 0) {
|
||||
for (i = 0; i < result.boxes.length; i++) {
|
||||
moveBox(result.boxes[i]);
|
||||
}
|
||||
}
|
||||
|
||||
function moveBox(box) {
|
||||
var corner = box.length;
|
||||
|
||||
while (corner--) {
|
||||
box[corner][0] += xOffset;
|
||||
box[corner][1] += yOffset;
|
||||
}
|
||||
}
|
||||
|
||||
function moveLine(line) {
|
||||
line[0].x += xOffset;
|
||||
line[0].y += yOffset;
|
||||
line[1].x += xOffset;
|
||||
line[1].y += yOffset;
|
||||
}
|
||||
}
|
||||
|
||||
function addResult (result, imageData) {
|
||||
if (!imageData || !_resultCollector) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (result.barcodes) {
|
||||
result.barcodes.filter(barcode => barcode.codeResult)
|
||||
.forEach(barcode => addResult(barcode, imageData));
|
||||
} else if (result.codeResult) {
|
||||
_resultCollector.addResult(imageData, _inputStream.getCanvasSize(), result.codeResult);
|
||||
}
|
||||
}
|
||||
|
||||
function hasCodeResult (result) {
|
||||
return result && (result.barcodes ?
|
||||
result.barcodes.some(barcode => barcode.codeResult) :
|
||||
result.codeResult);
|
||||
}
|
||||
|
||||
function publishResult(result, imageData) {
|
||||
let resultToPublish = result;
|
||||
|
||||
if (result && _onUIThread) {
|
||||
transformResult(result);
|
||||
addResult(result, imageData);
|
||||
resultToPublish = result.barcodes || result;
|
||||
}
|
||||
|
||||
Events.publish("processed", resultToPublish);
|
||||
if (hasCodeResult(result)) {
|
||||
Events.publish("detected", resultToPublish);
|
||||
}
|
||||
}
|
||||
|
||||
function locateAndDecode() {
|
||||
var result,
|
||||
boxes;
|
||||
|
||||
boxes = getBoundingBoxes();
|
||||
if (boxes) {
|
||||
result = _decoder.decodeFromBoundingBoxes(boxes);
|
||||
result = result || {};
|
||||
result.boxes = boxes;
|
||||
publishResult(result, _inputImageWrapper.data);
|
||||
} else {
|
||||
publishResult();
|
||||
}
|
||||
}
|
||||
|
||||
function update() {
|
||||
var availableWorker;
|
||||
|
||||
if (_onUIThread) {
|
||||
if (_workerPool.length > 0) {
|
||||
availableWorker = _workerPool.filter(function(workerThread) {
|
||||
return !workerThread.busy;
|
||||
})[0];
|
||||
if (availableWorker) {
|
||||
_framegrabber.attachData(availableWorker.imageData);
|
||||
} else {
|
||||
return; // all workers are busy
|
||||
}
|
||||
} else {
|
||||
_framegrabber.attachData(_inputImageWrapper.data);
|
||||
}
|
||||
if (_framegrabber.grab()) {
|
||||
if (availableWorker) {
|
||||
availableWorker.busy = true;
|
||||
availableWorker.worker.postMessage({
|
||||
cmd: 'process',
|
||||
imageData: availableWorker.imageData
|
||||
}, [availableWorker.imageData.buffer]);
|
||||
pendingStart = new Promise((resolve, reject) => {
|
||||
scanner.init(config, (error) => {
|
||||
if (error) {
|
||||
console.log(error);
|
||||
reject(error);
|
||||
}
|
||||
initialized = true;
|
||||
scanner.start();
|
||||
resolve();
|
||||
pendingStart = null;
|
||||
});
|
||||
});
|
||||
return pendingStart;
|
||||
},
|
||||
stop() {
|
||||
scanner.stop();
|
||||
initialized = false;
|
||||
return this;
|
||||
},
|
||||
toPromise() {
|
||||
if (config.inputStream.type === 'LiveStream'
|
||||
|| config.inputStream.type === 'VideoStream') {
|
||||
let cancelRequested = false;
|
||||
return {
|
||||
cancel() {
|
||||
cancelRequested = true;
|
||||
},
|
||||
promise: new Promise((resolve, reject) => {
|
||||
function onProcessed(result) {
|
||||
if (result && result.codeResult && result.codeResult.code) {
|
||||
scanner.stop();
|
||||
scanner.unsubscribe("processed", onProcessed);
|
||||
resolve(result);
|
||||
}
|
||||
if (cancelRequested) {
|
||||
scanner.stop();
|
||||
scanner.unsubscribe("processed", onProcessed);
|
||||
reject("cancelled!");
|
||||
}
|
||||
}
|
||||
scanner.subscribe("processed", onProcessed);
|
||||
this.start();
|
||||
})
|
||||
};
|
||||
} else {
|
||||
locateAndDecode();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
locateAndDecode();
|
||||
}
|
||||
}
|
||||
|
||||
function startContinuousUpdate() {
|
||||
var next = null,
|
||||
delay = 1000 / (_config.frequency || 60);
|
||||
|
||||
_stopped = false;
|
||||
(function frame(timestamp) {
|
||||
next = next || timestamp;
|
||||
if (!_stopped) {
|
||||
if (timestamp >= next) {
|
||||
next += delay;
|
||||
update();
|
||||
return new Promise((resolve, reject) => {
|
||||
scanner.decodeSingle(config, (result) => {
|
||||
if (result && result.codeResult && result.codeResult.code) {
|
||||
return resolve(result);
|
||||
}
|
||||
return reject(result);
|
||||
});
|
||||
});
|
||||
}
|
||||
window.requestAnimFrame(frame);
|
||||
}
|
||||
}(performance.now()));
|
||||
},
|
||||
registerResultCollector(resultCollector) {
|
||||
scanner.registerResultCollector(resultCollector);
|
||||
},
|
||||
getCanvas() {
|
||||
return scanner.canvas.dom.image;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function start() {
|
||||
if (_onUIThread && _config.inputStream.type === "LiveStream") {
|
||||
startContinuousUpdate();
|
||||
} else {
|
||||
update();
|
||||
}
|
||||
function fromSource(config, source, inputConfig = {}) {
|
||||
config = createConfigFromSource(config, inputConfig, source);
|
||||
return fromConfig(config);
|
||||
}
|
||||
|
||||
function initWorker(cb) {
|
||||
var blobURL,
|
||||
workerThread = {
|
||||
worker: undefined,
|
||||
imageData: new Uint8Array(_inputStream.getWidth() * _inputStream.getHeight()),
|
||||
busy: true
|
||||
};
|
||||
|
||||
blobURL = generateWorkerBlob();
|
||||
workerThread.worker = new Worker(blobURL);
|
||||
|
||||
workerThread.worker.onmessage = function(e) {
|
||||
if (e.data.event === 'initialized') {
|
||||
URL.revokeObjectURL(blobURL);
|
||||
workerThread.busy = false;
|
||||
workerThread.imageData = new Uint8Array(e.data.imageData);
|
||||
if (ENV.development) {
|
||||
console.log("Worker initialized");
|
||||
}
|
||||
return cb(workerThread);
|
||||
} else if (e.data.event === 'processed') {
|
||||
workerThread.imageData = new Uint8Array(e.data.imageData);
|
||||
workerThread.busy = false;
|
||||
publishResult(e.data.result, workerThread.imageData);
|
||||
} else if (e.data.event === 'error') {
|
||||
if (ENV.development) {
|
||||
console.log("Worker error: " + e.data.message);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
workerThread.worker.postMessage({
|
||||
cmd: 'init',
|
||||
size: {x: _inputStream.getWidth(), y: _inputStream.getHeight()},
|
||||
imageData: workerThread.imageData,
|
||||
config: configForWorker(_config)
|
||||
}, [workerThread.imageData.buffer]);
|
||||
function setConfig(configuration = {}, key, config = {}) {
|
||||
var mergedConfig = merge({}, configuration, {[key]: config});
|
||||
return createApi(mergedConfig);
|
||||
}
|
||||
|
||||
function configForWorker(config) {
|
||||
function createApi(configuration = Config) {
|
||||
return {
|
||||
...config,
|
||||
inputStream: {
|
||||
...config.inputStream,
|
||||
target: null
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function workerInterface(factory) {
|
||||
/* eslint-disable no-undef*/
|
||||
if (factory) {
|
||||
var Quagga = factory().default;
|
||||
if (!Quagga) {
|
||||
self.postMessage({'event': 'error', message: 'Quagga could not be created'});
|
||||
return;
|
||||
}
|
||||
}
|
||||
var imageWrapper;
|
||||
|
||||
self.onmessage = function(e) {
|
||||
if (e.data.cmd === 'init') {
|
||||
var config = e.data.config;
|
||||
config.numOfWorkers = 0;
|
||||
imageWrapper = new Quagga.ImageWrapper({
|
||||
x: e.data.size.x,
|
||||
y: e.data.size.y
|
||||
}, new Uint8Array(e.data.imageData));
|
||||
Quagga.init(config, ready, imageWrapper);
|
||||
Quagga.onProcessed(onProcessed);
|
||||
} else if (e.data.cmd === 'process') {
|
||||
imageWrapper.data = new Uint8Array(e.data.imageData);
|
||||
Quagga.start();
|
||||
} else if (e.data.cmd === 'setReaders') {
|
||||
Quagga.setReaders(e.data.readers);
|
||||
fromSource(src, inputConfig) {
|
||||
return fromSource(configuration, src, inputConfig);
|
||||
},
|
||||
fromConfig(conf) {
|
||||
return fromConfig(merge({}, configuration, conf));
|
||||
},
|
||||
decoder(conf) {
|
||||
return setConfig(configuration, "decoder", conf);
|
||||
},
|
||||
locator(conf) {
|
||||
return setConfig(configuration, "locator", conf);
|
||||
},
|
||||
throttle(timeInMs) {
|
||||
return setConfig(configuration, "frequency", 1000 / parseInt(timeInMs));
|
||||
},
|
||||
config(conf) {
|
||||
return createApi(merge({}, configuration, conf));
|
||||
},
|
||||
ImageWrapper,
|
||||
ImageDebug,
|
||||
ResultCollector,
|
||||
_worker: {
|
||||
createScanner
|
||||
}
|
||||
};
|
||||
|
||||
function onProcessed(result) {
|
||||
self.postMessage({
|
||||
'event': 'processed',
|
||||
imageData: imageWrapper.data,
|
||||
result: result
|
||||
}, [imageWrapper.data.buffer]);
|
||||
}
|
||||
|
||||
function ready() { // eslint-disable-line
|
||||
self.postMessage({'event': 'initialized', imageData: imageWrapper.data}, [imageWrapper.data.buffer]);
|
||||
}
|
||||
|
||||
/* eslint-enable */
|
||||
}
|
||||
|
||||
function generateWorkerBlob() {
|
||||
var blob,
|
||||
factorySource;
|
||||
|
||||
/* jshint ignore:start */
|
||||
if (typeof __factorySource__ !== 'undefined') {
|
||||
factorySource = __factorySource__; // eslint-disable-line no-undef
|
||||
}
|
||||
/* jshint ignore:end */
|
||||
|
||||
blob = new Blob(['(' + workerInterface.toString() + ')(' + factorySource + ');'],
|
||||
{type: 'text/javascript'});
|
||||
|
||||
return window.URL.createObjectURL(blob);
|
||||
}
|
||||
|
||||
function setReaders(readers) {
|
||||
if (_decoder) {
|
||||
_decoder.setReaders(readers);
|
||||
} else if (_onUIThread && _workerPool.length > 0) {
|
||||
_workerPool.forEach(function(workerThread) {
|
||||
workerThread.worker.postMessage({cmd: 'setReaders', readers: readers});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function adjustWorkerPool(capacity, cb) {
|
||||
const increaseBy = capacity - _workerPool.length;
|
||||
if (increaseBy === 0) {
|
||||
return cb && cb();
|
||||
}
|
||||
if (increaseBy < 0) {
|
||||
const workersToTerminate = _workerPool.slice(increaseBy);
|
||||
workersToTerminate.forEach(function(workerThread) {
|
||||
workerThread.worker.terminate();
|
||||
if (ENV.development) {
|
||||
console.log("Worker terminated!");
|
||||
}
|
||||
});
|
||||
_workerPool = _workerPool.slice(0, increaseBy);
|
||||
return cb && cb();
|
||||
} else {
|
||||
for (var i = 0; i < increaseBy; i++) {
|
||||
initWorker(workerInitialized);
|
||||
}
|
||||
|
||||
function workerInitialized(workerThread) {
|
||||
_workerPool.push(workerThread);
|
||||
if (_workerPool.length >= capacity){
|
||||
cb && cb();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
init: function(config, cb, imageWrapper) {
|
||||
_config = merge({}, Config, config);
|
||||
if (imageWrapper) {
|
||||
_onUIThread = false;
|
||||
initializeData(imageWrapper);
|
||||
return cb();
|
||||
} else {
|
||||
initInputStream(cb);
|
||||
}
|
||||
},
|
||||
start: function() {
|
||||
start();
|
||||
},
|
||||
stop: function() {
|
||||
_stopped = true;
|
||||
adjustWorkerPool(0);
|
||||
if (_config.inputStream.type === "LiveStream") {
|
||||
CameraAccess.release();
|
||||
_inputStream.clearEventHandlers();
|
||||
}
|
||||
},
|
||||
pause: function() {
|
||||
_stopped = true;
|
||||
},
|
||||
onDetected: function(callback) {
|
||||
Events.subscribe("detected", callback);
|
||||
},
|
||||
offDetected: function(callback) {
|
||||
Events.unsubscribe("detected", callback);
|
||||
},
|
||||
onProcessed: function(callback) {
|
||||
Events.subscribe("processed", callback);
|
||||
},
|
||||
offProcessed: function(callback) {
|
||||
Events.unsubscribe("processed", callback);
|
||||
},
|
||||
setReaders: function(readers) {
|
||||
setReaders(readers);
|
||||
},
|
||||
registerResultCollector: function(resultCollector) {
|
||||
if (resultCollector && typeof resultCollector.addResult === 'function') {
|
||||
_resultCollector = resultCollector;
|
||||
}
|
||||
},
|
||||
canvas: _canvasContainer,
|
||||
decodeSingle: function(config, resultCallback) {
|
||||
config = merge({
|
||||
inputStream: {
|
||||
type: "ImageStream",
|
||||
sequence: false,
|
||||
size: 800,
|
||||
src: config.src
|
||||
},
|
||||
numOfWorkers: (ENV.development && config.debug) ? 0 : 1,
|
||||
locator: {
|
||||
halfSample: false
|
||||
}
|
||||
}, config);
|
||||
this.init(config, () => {
|
||||
Events.once("processed", (result) => {
|
||||
this.stop();
|
||||
resultCallback.call(null, result);
|
||||
}, true);
|
||||
start();
|
||||
});
|
||||
},
|
||||
ImageWrapper: ImageWrapper,
|
||||
ImageDebug: ImageDebug,
|
||||
ResultCollector: ResultCollector
|
||||
};
|
||||
export default createApi();
|
||||
|
@ -0,0 +1,508 @@
|
||||
import ImageWrapper from './common/image_wrapper';
|
||||
import createLocator, {checkImageConstraints} from './locator/barcode_locator';
|
||||
import BarcodeDecoder from './decoder/barcode_decoder';
|
||||
import createEventedElement from './common/events';
|
||||
import CameraAccess from './input/camera_access';
|
||||
import ImageDebug from './common/image_debug';
|
||||
import ResultCollector from './analytics/result_collector';
|
||||
import Config from './config/config';
|
||||
import InputStream from 'input_stream';
|
||||
import FrameGrabber from 'frame_grabber';
|
||||
import {merge} from 'lodash';
|
||||
const vec2 = {
|
||||
clone: require('gl-vec2/clone')
|
||||
};
|
||||
|
||||
|
||||
function createScanner() {
|
||||
var _inputStream,
|
||||
_framegrabber,
|
||||
_stopped = true,
|
||||
_canvasContainer = {
|
||||
ctx: {
|
||||
image: null
|
||||
},
|
||||
dom: {
|
||||
image: null
|
||||
}
|
||||
},
|
||||
_inputImageWrapper,
|
||||
_boxSize,
|
||||
_decoder,
|
||||
_workerPool = [],
|
||||
_onUIThread = true,
|
||||
_resultCollector,
|
||||
_config = {},
|
||||
_events = createEventedElement(),
|
||||
_locator;
|
||||
|
||||
function initializeData(imageWrapper) {
|
||||
initBuffers(imageWrapper);
|
||||
_decoder = BarcodeDecoder.create(_config.decoder, _inputImageWrapper);
|
||||
}
|
||||
|
||||
function initInputStream(cb) {
|
||||
var video;
|
||||
if (_config.inputStream.type === "VideoStream") {
|
||||
video = document.createElement("video");
|
||||
_inputStream = InputStream.createVideoStream(video);
|
||||
} else if (_config.inputStream.type === "ImageStream") {
|
||||
_inputStream = InputStream.createImageStream();
|
||||
} else if (_config.inputStream.type === "LiveStream") {
|
||||
var $viewport = getViewPort();
|
||||
if ($viewport) {
|
||||
video = $viewport.querySelector("video");
|
||||
if (!video) {
|
||||
video = document.createElement("video");
|
||||
$viewport.appendChild(video);
|
||||
}
|
||||
}
|
||||
_inputStream = InputStream.createLiveStream(video);
|
||||
CameraAccess.request(video, _config.inputStream.constraints)
|
||||
.then(() => {
|
||||
_inputStream.trigger("canrecord");
|
||||
}).catch((err) => {
|
||||
return cb(err);
|
||||
});
|
||||
}
|
||||
|
||||
_inputStream.setAttribute("preload", "auto");
|
||||
_inputStream.setInputStream(_config.inputStream);
|
||||
_inputStream.addEventListener("canrecord", canRecord.bind(undefined, cb));
|
||||
}
|
||||
|
||||
function getViewPort() {
|
||||
var target = _config.inputStream.target;
|
||||
// Check if target is already a DOM element
|
||||
if (target && target.nodeName && target.nodeType === 1) {
|
||||
return target;
|
||||
} else {
|
||||
// Use '#interactive.viewport' as a fallback selector (backwards compatibility)
|
||||
var selector = typeof target === 'string' ? target : '#interactive.viewport';
|
||||
return document.querySelector(selector);
|
||||
}
|
||||
}
|
||||
|
||||
function canRecord(cb) {
|
||||
checkImageConstraints(_inputStream, _config.locator);
|
||||
initCanvas(_config);
|
||||
_framegrabber = FrameGrabber.create(_inputStream, _canvasContainer.dom.image);
|
||||
|
||||
adjustWorkerPool(_config.numOfWorkers, function() {
|
||||
if (_config.numOfWorkers === 0) {
|
||||
initializeData();
|
||||
}
|
||||
ready(cb);
|
||||
});
|
||||
}
|
||||
|
||||
function ready(cb){
|
||||
_inputStream.play();
|
||||
cb();
|
||||
}
|
||||
|
||||
function initCanvas() {
|
||||
if (typeof document !== "undefined") {
|
||||
var $viewport = getViewPort();
|
||||
_canvasContainer.dom.image = document.querySelector("canvas.imgBuffer");
|
||||
if (!_canvasContainer.dom.image) {
|
||||
_canvasContainer.dom.image = document.createElement("canvas");
|
||||
_canvasContainer.dom.image.className = "imgBuffer";
|
||||
if ($viewport && _config.inputStream.type === "ImageStream") {
|
||||
$viewport.appendChild(_canvasContainer.dom.image);
|
||||
}
|
||||
}
|
||||
_canvasContainer.ctx.image = _canvasContainer.dom.image.getContext("2d");
|
||||
_canvasContainer.dom.image.width = _inputStream.getCanvasSize().x;
|
||||
_canvasContainer.dom.image.height = _inputStream.getCanvasSize().y;
|
||||
}
|
||||
}
|
||||
|
||||
function initBuffers(imageWrapper) {
|
||||
if (imageWrapper) {
|
||||
_inputImageWrapper = imageWrapper;
|
||||
} else {
|
||||
_inputImageWrapper = new ImageWrapper({
|
||||
x: _inputStream.getWidth(),
|
||||
y: _inputStream.getHeight()
|
||||
});
|
||||
}
|
||||
|
||||
if (ENV.development) {
|
||||
console.log(_inputImageWrapper.size);
|
||||
}
|
||||
_boxSize = [
|
||||
vec2.clone([0, 0]),
|
||||
vec2.clone([0, _inputImageWrapper.size.y]),
|
||||
vec2.clone([_inputImageWrapper.size.x, _inputImageWrapper.size.y]),
|
||||
vec2.clone([_inputImageWrapper.size.x, 0])
|
||||
];
|
||||
_locator = createLocator(_inputImageWrapper, _config.locator);
|
||||
}
|
||||
|
||||
function getBoundingBoxes() {
|
||||
if (_config.locate) {
|
||||
return _locator.locate();
|
||||
} else {
|
||||
return [[
|
||||
vec2.clone(_boxSize[0]),
|
||||
vec2.clone(_boxSize[1]),
|
||||
vec2.clone(_boxSize[2]),
|
||||
vec2.clone(_boxSize[3])]];
|
||||
}
|
||||
}
|
||||
|
||||
function transformResult(result) {
|
||||
var topRight = _inputStream.getTopRight(),
|
||||
xOffset = topRight.x,
|
||||
yOffset = topRight.y,
|
||||
i;
|
||||
|
||||
if (xOffset === 0 && yOffset === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (result.barcodes) {
|
||||
for (i = 0; i < result.barcodes.length; i++) {
|
||||
transformResult(result.barcodes[i]);
|
||||
}
|
||||
}
|
||||
|
||||
if (result.line && result.line.length === 2) {
|
||||
moveLine(result.line);
|
||||
}
|
||||
|
||||
if (result.box) {
|
||||
moveBox(result.box);
|
||||
}
|
||||
|
||||
if (result.boxes && result.boxes.length > 0) {
|
||||
for (i = 0; i < result.boxes.length; i++) {
|
||||
moveBox(result.boxes[i]);
|
||||
}
|
||||
}
|
||||
|
||||
function moveBox(box) {
|
||||
var corner = box.length;
|
||||
|
||||
while (corner--) {
|
||||
box[corner][0] += xOffset;
|
||||
box[corner][1] += yOffset;
|
||||
}
|
||||
}
|
||||
|
||||
function moveLine(line) {
|
||||
line[0].x += xOffset;
|
||||
line[0].y += yOffset;
|
||||
line[1].x += xOffset;
|
||||
line[1].y += yOffset;
|
||||
}
|
||||
}
|
||||
|
||||
function addResult (result, imageData) {
|
||||
if (!imageData || !_resultCollector) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (result.barcodes) {
|
||||
result.barcodes.filter(barcode => barcode.codeResult)
|
||||
.forEach(barcode => addResult(barcode, imageData));
|
||||
} else if (result.codeResult) {
|
||||
_resultCollector.addResult(imageData, _inputStream.getCanvasSize(), result.codeResult);
|
||||
}
|
||||
}
|
||||
|
||||
function hasCodeResult (result) {
|
||||
return result && (result.barcodes ?
|
||||
result.barcodes.some(barcode => barcode.codeResult) :
|
||||
result.codeResult);
|
||||
}
|
||||
|
||||
function publishResult(result, imageData) {
|
||||
let resultToPublish = result;
|
||||
|
||||
if (result && _onUIThread) {
|
||||
transformResult(result);
|
||||
addResult(result, imageData);
|
||||
resultToPublish = result.barcodes || result;
|
||||
}
|
||||
|
||||
_events.publish("processed", resultToPublish);
|
||||
if (hasCodeResult(result)) {
|
||||
_events.publish("detected", resultToPublish);
|
||||
}
|
||||
}
|
||||
|
||||
function locateAndDecode() {
|
||||
var result,
|
||||
boxes;
|
||||
|
||||
boxes = getBoundingBoxes();
|
||||
if (boxes) {
|
||||
result = _decoder.decodeFromBoundingBoxes(boxes);
|
||||
result = result || {};
|
||||
result.boxes = boxes;
|
||||
publishResult(result, _inputImageWrapper.data);
|
||||
} else {
|
||||
publishResult();
|
||||
}
|
||||
}
|
||||
|
||||
function update() {
|
||||
var availableWorker;
|
||||
|
||||
if (_onUIThread) {
|
||||
if (_workerPool.length > 0) {
|
||||
availableWorker = _workerPool.filter(function(workerThread) {
|
||||
return !workerThread.busy;
|
||||
})[0];
|
||||
if (availableWorker) {
|
||||
_framegrabber.attachData(availableWorker.imageData);
|
||||
} else {
|
||||
return; // all workers are busy
|
||||
}
|
||||
} else {
|
||||
_framegrabber.attachData(_inputImageWrapper.data);
|
||||
}
|
||||
if (_framegrabber.grab()) {
|
||||
if (availableWorker) {
|
||||
availableWorker.busy = true;
|
||||
availableWorker.worker.postMessage({
|
||||
cmd: 'process',
|
||||
imageData: availableWorker.imageData
|
||||
}, [availableWorker.imageData.buffer]);
|
||||
} else {
|
||||
locateAndDecode();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
locateAndDecode();
|
||||
}
|
||||
}
|
||||
|
||||
function startContinuousUpdate() {
|
||||
var next = null,
|
||||
delay = 1000 / (_config.frequency === 0 ? 60 : (_config.frequency || 60));
|
||||
|
||||
_stopped = false;
|
||||
(function frame(timestamp) {
|
||||
next = next || timestamp;
|
||||
if (!_stopped) {
|
||||
if (timestamp >= next) {
|
||||
next += delay;
|
||||
update();
|
||||
}
|
||||
window.requestAnimFrame(frame);
|
||||
}
|
||||
}(performance.now()));
|
||||
}
|
||||
|
||||
function start() {
|
||||
if (_onUIThread && _config.inputStream.type === "LiveStream") {
|
||||
startContinuousUpdate();
|
||||
} else {
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
||||
function initWorker(cb) {
|
||||
var blobURL,
|
||||
workerThread = {
|
||||
worker: undefined,
|
||||
imageData: new Uint8Array(_inputStream.getWidth() * _inputStream.getHeight()),
|
||||
busy: true
|
||||
};
|
||||
|
||||
blobURL = generateWorkerBlob();
|
||||
workerThread.worker = new Worker(blobURL);
|
||||
|
||||
workerThread.worker.onmessage = function(e) {
|
||||
if (e.data.event === 'initialized') {
|
||||
URL.revokeObjectURL(blobURL);
|
||||
workerThread.busy = false;
|
||||
workerThread.imageData = new Uint8Array(e.data.imageData);
|
||||
if (ENV.development) {
|
||||
console.log("Worker initialized");
|
||||
}
|
||||
return cb(workerThread);
|
||||
} else if (e.data.event === 'processed') {
|
||||
workerThread.imageData = new Uint8Array(e.data.imageData);
|
||||
workerThread.busy = false;
|
||||
publishResult(e.data.result, workerThread.imageData);
|
||||
} else if (e.data.event === 'error') {
|
||||
if (ENV.development) {
|
||||
console.log("Worker error: " + e.data.message);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
workerThread.worker.postMessage({
|
||||
cmd: 'init',
|
||||
size: {x: _inputStream.getWidth(), y: _inputStream.getHeight()},
|
||||
imageData: workerThread.imageData,
|
||||
config: configForWorker(_config)
|
||||
}, [workerThread.imageData.buffer]);
|
||||
}
|
||||
|
||||
function configForWorker(config) {
|
||||
return {
|
||||
...config,
|
||||
inputStream: {
|
||||
...config.inputStream,
|
||||
target: null
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function workerInterface(factory) {
|
||||
/* eslint-disable no-undef*/
|
||||
if (factory) {
|
||||
var Quagga = factory().default;
|
||||
if (!Quagga) {
|
||||
self.postMessage({'event': 'error', message: 'Quagga could not be created'});
|
||||
return;
|
||||
}
|
||||
}
|
||||
var imageWrapper,
|
||||
scanner = Quagga._worker.createScanner();
|
||||
|
||||
self.onmessage = function(e) {
|
||||
if (e.data.cmd === 'init') {
|
||||
var config = e.data.config;
|
||||
config.numOfWorkers = 0;
|
||||
imageWrapper = new Quagga.ImageWrapper({
|
||||
x: e.data.size.x,
|
||||
y: e.data.size.y
|
||||
}, new Uint8Array(e.data.imageData));
|
||||
scanner.init(config, ready, imageWrapper);
|
||||
scanner.subscribe("processed", onProcessed);
|
||||
} else if (e.data.cmd === 'process') {
|
||||
imageWrapper.data = new Uint8Array(e.data.imageData);
|
||||
scanner.start();
|
||||
}
|
||||
};
|
||||
|
||||
function onProcessed(result) {
|
||||
self.postMessage({
|
||||
'event': 'processed',
|
||||
imageData: imageWrapper.data,
|
||||
result: result
|
||||
}, [imageWrapper.data.buffer]);
|
||||
}
|
||||
|
||||
function ready() { // eslint-disable-line
|
||||
self.postMessage({'event': 'initialized', imageData: imageWrapper.data}, [imageWrapper.data.buffer]);
|
||||
}
|
||||
|
||||
/* eslint-enable */
|
||||
}
|
||||
|
||||
function generateWorkerBlob() {
|
||||
var blob,
|
||||
factorySource;
|
||||
|
||||
/* jshint ignore:start */
|
||||
if (typeof __factorySource__ !== 'undefined') {
|
||||
factorySource = __factorySource__; // eslint-disable-line no-undef
|
||||
}
|
||||
/* jshint ignore:end */
|
||||
|
||||
blob = new Blob(['(' + workerInterface.toString() + ')(' + factorySource + ');'],
|
||||
{type: 'text/javascript'});
|
||||
|
||||
return window.URL.createObjectURL(blob);
|
||||
}
|
||||
|
||||
function setReaders(readers) {
|
||||
if (_decoder) {
|
||||
_decoder.setReaders(readers);
|
||||
} else if (_onUIThread && _workerPool.length > 0) {
|
||||
_workerPool.forEach(function(workerThread) {
|
||||
workerThread.worker.postMessage({cmd: 'setReaders', readers: readers});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function adjustWorkerPool(capacity, cb) {
|
||||
const increaseBy = capacity - _workerPool.length;
|
||||
if (increaseBy === 0) {
|
||||
return cb && cb();
|
||||
}
|
||||
if (increaseBy < 0) {
|
||||
const workersToTerminate = _workerPool.slice(increaseBy);
|
||||
workersToTerminate.forEach(function(workerThread) {
|
||||
workerThread.worker.terminate();
|
||||
if (ENV.development) {
|
||||
console.log("Worker terminated!");
|
||||
}
|
||||
});
|
||||
_workerPool = _workerPool.slice(0, increaseBy);
|
||||
return cb && cb();
|
||||
} else {
|
||||
for (var i = 0; i < increaseBy; i++) {
|
||||
initWorker(workerInitialized);
|
||||
}
|
||||
|
||||
function workerInitialized(workerThread) {
|
||||
_workerPool.push(workerThread);
|
||||
if (_workerPool.length >= capacity){
|
||||
cb && cb();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
init: function(config, cb, imageWrapper) {
|
||||
_config = merge({}, Config, config);
|
||||
|
||||
if (imageWrapper) {
|
||||
_onUIThread = false;
|
||||
initializeData(imageWrapper);
|
||||
return cb();
|
||||
} else {
|
||||
initInputStream(cb);
|
||||
}
|
||||
},
|
||||
start: function() {
|
||||
start();
|
||||
},
|
||||
isRunning: function() {
|
||||
return !_stopped;
|
||||
},
|
||||
stop: function() {
|
||||
_stopped = true;
|
||||
adjustWorkerPool(0);
|
||||
if (_config.inputStream.type === "LiveStream") {
|
||||
CameraAccess.release();
|
||||
}
|
||||
_inputStream.clearEventHandlers();
|
||||
},
|
||||
pause: function() {
|
||||
_stopped = true;
|
||||
},
|
||||
subscribe(eventName, callback) {
|
||||
_events.subscribe(eventName, callback);
|
||||
},
|
||||
unsubscribe(eventName, callback) {
|
||||
_events.unsubscribe(eventName, callback);
|
||||
},
|
||||
registerResultCollector: function(resultCollector) {
|
||||
if (resultCollector && typeof resultCollector.addResult === 'function') {
|
||||
_resultCollector = resultCollector;
|
||||
}
|
||||
},
|
||||
decodeSingle: function(config, resultCallback) {
|
||||
this.init(config, () => {
|
||||
_events.once("processed", (result) => {
|
||||
this.stop();
|
||||
resultCallback.call(null, result);
|
||||
}, true);
|
||||
start();
|
||||
});
|
||||
},
|
||||
canvas: _canvasContainer
|
||||
};
|
||||
}
|
||||
|
||||
export default createScanner;
|
@ -0,0 +1,134 @@
|
||||
import {createConfigFromSource} from '../../src/input/config_factory';
|
||||
import DOMHelper from '../../src/common/dom_helper';
|
||||
|
||||
function MyFileList(file) {
|
||||
Array.call(this);
|
||||
this.push(file);
|
||||
};
|
||||
MyFileList.prototype = Object.create(Array.prototype);
|
||||
MyFileList.prototype.constructor = MyFileList;
|
||||
|
||||
const OriginalFileList = DOMHelper.FileList;
|
||||
|
||||
function expectImageConfig(config) {
|
||||
expect(config.inputStream.type).to.equal("ImageStream");
|
||||
expect(config.inputStream.sequence).to.equal(false);
|
||||
expect(config.inputStream.size).to.be.above(0);
|
||||
expect(config.numOfWorkers).to.equal(0);
|
||||
}
|
||||
|
||||
function expectVideoConfig(config) {
|
||||
expect(config.inputStream.type).to.equal("VideoStream");
|
||||
expect(config.inputStream.src).to.be.a('string');
|
||||
}
|
||||
|
||||
function expectLiveConfig(config) {
|
||||
expect(config.inputStream.type).to.equal("LiveStream");
|
||||
expect(config.inputStream.src).to.not.exist;
|
||||
expect(config.inputStream.constraints.width).to.be.above(0);
|
||||
expect(config.inputStream.constraints.height).to.be.above(0);
|
||||
}
|
||||
|
||||
describe("createConfigFromSource", () => {
|
||||
beforeEach(function() {
|
||||
DOMHelper.setObject('FileList', MyFileList);
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
DOMHelper.setObject('FileList', OriginalFileList);
|
||||
});
|
||||
|
||||
it("should create an image config for an image-file", () => {
|
||||
const file = new File([], "image.jpg", {type: 'image/jpg'});
|
||||
const config = createConfigFromSource({}, {}, file);
|
||||
expectImageConfig(config);
|
||||
expect(config.inputStream.src).to.have.string("blob:");
|
||||
});
|
||||
|
||||
it("should create an image config for a data-url", () => {
|
||||
const config = createConfigFromSource({}, {}, "data:image/png;base64," +
|
||||
"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAABlBMVEUAAAD///+l2Z/d" +
|
||||
"AAAAM0lEQVR4nGP4/5/h/1+G/58ZDrAz3D/McH8yw83NDDeNGe4Ug9C9zwz3gVLMDA/A" +
|
||||
"6P9/AFGGFyjOXZtQAAAAAElFTkSuQmCC");
|
||||
|
||||
expectImageConfig(config);
|
||||
expect(config.inputStream.src).to.have.string("data:image/png");
|
||||
});
|
||||
|
||||
it("should create an image config for a regular image url", () => {
|
||||
const config = createConfigFromSource({}, {}, "/image-001.jpg");
|
||||
expectImageConfig(config);
|
||||
expect(config.inputStream.src).to.have.string("/image-001.jpg");
|
||||
});
|
||||
|
||||
it("should create an image config for an absolute image url", () => {
|
||||
const config = createConfigFromSource({}, {}, "http://dja.com/ige.png");
|
||||
expectImageConfig(config);
|
||||
expect(config.inputStream.src).to.have.string("http://dja.com/ige.png");
|
||||
});
|
||||
|
||||
it("should throw an error in case of an blob-url", () => {
|
||||
expect(createConfigFromSource.bind(null, {}, {}, "blob:das"))
|
||||
.to.throw(Error, /objectURL/);
|
||||
});
|
||||
|
||||
it("should throw an error in case an arbitrary string is given", () => {
|
||||
expect(createConfigFromSource.bind(null, {}, {}, "dhfskjdfhsdfsdf"))
|
||||
.to.throw(Error, /dhfskjdfhsdfsdf/);
|
||||
});
|
||||
|
||||
it("should throw an error in case of an unsupported mime type", () => {
|
||||
expect(createConfigFromSource.bind(null, {}, {}, "data:audio/mp3;base64,379"))
|
||||
.to.throw(Error, /mimetype/);
|
||||
});
|
||||
|
||||
it("should throw an error in case of an unsupported extension", () => {
|
||||
expect(createConfigFromSource.bind(null, {}, {}, "sdflsdkf.mp3"))
|
||||
.to.throw(Error, /MediaString/);
|
||||
});
|
||||
|
||||
it("should throw an error in case of an HTMLImageElement", () => {
|
||||
expect(createConfigFromSource.bind(null, {}, {}, new Image()))
|
||||
.to.throw(Error, /HTMLImageElement/);
|
||||
});
|
||||
|
||||
it("should throw an error in case of an HTMLVideoElement", () => {
|
||||
const video = document.createElement("video");
|
||||
console.log(typeof video);
|
||||
expect(createConfigFromSource.bind(null, {}, {}, video))
|
||||
.to.throw(Error, /HTMLVideoElement/);
|
||||
});
|
||||
|
||||
it("should work with a fileList", () => {
|
||||
const file = new File([], "image.jpg", {type: 'image/jpg'});
|
||||
const fileList = new MyFileList(file);
|
||||
const config = createConfigFromSource({}, {}, fileList);
|
||||
expectImageConfig(config);
|
||||
expect(config.inputStream.src).to.have.string("blob:");
|
||||
});
|
||||
|
||||
it("should create a video config for a given url", () => {
|
||||
const config = createConfigFromSource({}, {}, "/video-001.ogg");
|
||||
expectVideoConfig(config);
|
||||
expect(config.inputStream.src).to.have.string("/video-001.ogg");
|
||||
});
|
||||
|
||||
it("should create a video config for a given file", () => {
|
||||
const file = new File([], "video-001.ogg", {type: 'video/ogg'});
|
||||
const config = createConfigFromSource({}, {}, file);
|
||||
expectVideoConfig(config);
|
||||
expect(config.inputStream.src).to.have.string("blob:");
|
||||
});
|
||||
|
||||
it("should create a live config", () => {
|
||||
const config = createConfigFromSource({}, {}, {
|
||||
constraints: {
|
||||
width: 1280,
|
||||
height: 480,
|
||||
facingMode: "user"
|
||||
}
|
||||
});
|
||||
expectLiveConfig(config);
|
||||
expect(config.inputStream.constraints.facingMode).to.equal("user");
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue