Use stimulus controller to implement barcode scanner.

This commit is contained in:
Jan Böhmer 2022-03-07 01:28:32 +01:00
parent 1460894946
commit cb683f611c
4 changed files with 89 additions and 324 deletions

View file

@ -0,0 +1,86 @@
import {Controller} from "@hotwired/stimulus";
import * as ZXing from "@zxing/library";
/* stimulusFetch: 'lazy' */
export default class extends Controller {
static targets = [ "source" ]
codeReader = null;
connect() {
console.log('Init Scanner');
this.codeReader = new ZXing.BrowserMultiFormatReader();
this.initScanner();
}
codeScannedHandler(result, err) {
if (result) {
//@ts-ignore
document.getElementById('scan_dialog_input').value = result.text;
//Submit form
//@ts-ignore
document.getElementById('scan_dialog_form').submit();
}
if (err && !(err instanceof ZXing.NotFoundException)) {
console.error(err);
//document.getElementById('result').textContent = err
}
}
initScanner() {
let selectedDeviceId;
this.codeReader.listVideoInputDevices()
.then((videoInputDevices) => {
if (videoInputDevices.length >= 1) {
const sourceSelect = document.getElementById('sourceSelect');
videoInputDevices.forEach((element) => {
const sourceOption = document.createElement('option');
sourceOption.text = element.label;
sourceOption.value = element.deviceId;
sourceSelect.appendChild(sourceOption);
});
//Try to retrieve last selected webcam...
let last_cam_id = localStorage.getItem('scanner_last_cam_id');
if (!!last_cam_id) {
//selectedDeviceId = localStorage.getItem('scanner_last_cam_id');
sourceSelect.value = last_cam_id;
} else {
selectedDeviceId = videoInputDevices[0].deviceId;
}
sourceSelect.onchange = () => {
//@ts-ignore
selectedDeviceId = sourceSelect.value;
localStorage.setItem('scanner_last_cam_id', selectedDeviceId);
changeHandler();
};
document.getElementById('sourceSelectPanel').classList.remove('d-none');
document.getElementById('video').classList.remove('d-none');
document.getElementById('scanner-warning').classList.add('d-none');
}
let changeHandler = () => {
this.codeReader.reset();
this.codeReader.decodeFromVideoDevice(selectedDeviceId, 'video', (result, err) => this.codeScannedHandler(result, err));
console.log(`Started continous decode from camera with id ${selectedDeviceId}`)
};
//Register Change Src Button
//document.getElementById('changeSrcBtn').addEventListener('click', changeHandler);
//Try to start logging automatically.
changeHandler();
})
.catch((err) => {
console.error(err)
})
}
}

View file

@ -14,12 +14,13 @@ const RegisterEventHelper = class {
constructor() { constructor() {
this.registerToasts(); this.registerToasts();
this.registerTooltips(); this.registerTooltips();
this.registerFileInput();
this.registerJumpToTopBtn(); this.registerJumpToTopBtn();
this.registerTriStateCheckboxes(); this.registerTriStateCheckboxes();
this.registerFileInput();
this.registerSpecialCharInput(); this.registerSpecialCharInput();
this.registerHoverPics(); this.registerHoverPics();
} }

View file

@ -31,101 +31,6 @@ import * as ZXing from "@zxing/library";
* *
***********************************/ ***********************************/
//Register greek input in search fields.
$(document).on("ajaxUI:start ajaxUI:reload", function() {
//@ts-ignore
$("input[type=text], textarea, input[type=search]").unbind("keydown").keydown(function (event : KeyboardEvent) {
let greek = event.altKey;
let greek_char : string = "";
if (greek){
switch(event.key) {
case "w": //Omega
greek_char = '\u2126';
break;
case "u":
case "m": //Micro
greek_char = "\u00B5";
break;
case "p": //Phi
greek_char = "\u03C6";
break;
case "a": //Alpha
greek_char = "\u03B1";
break;
case "b": //Beta
greek_char = "\u03B2";
break;
case "c": //Gamma
greek_char = "\u03B3";
break;
case "d": //Delta
greek_char = "\u03B4";
break;
case "l": //Pound
greek_char = "\u00A3";
break;
case "y": //Yen
greek_char = "\u00A5";
break;
case "o": //Yen
greek_char = "\u00A4";
break;
case "1": //Sum symbol
greek_char = "\u2211";
break;
case "2": //Integral
greek_char = "\u222B";
break;
case "3": //Less-than or equal
greek_char = "\u2264";
break;
case "4": //Greater than or equal
greek_char = "\u2265";
break;
case "5": //PI
greek_char = "\u03c0";
break;
case "q": //Copyright
greek_char = "\u00A9";
break;
case "e": //Euro
greek_char = "\u20AC";
break;
}
if(greek_char=="") return;
let $txt = $(this);
//@ts-ignore
let caretPos = $txt[0].selectionStart;
let textAreaTxt = $txt.val().toString();
$txt.val(textAreaTxt.substring(0, caretPos) + greek_char + textAreaTxt.substring(caretPos) );
}
});
//@ts-ignore
this.greek_once = true;
});
//Register bootstrap select picker
$(document).on("ajaxUI:reload ajaxUI:start", function () {
//@ts-ignore
$(".selectpicker").selectpicker({
dropdownAlignRight: 'auto',
container: '#content',
});
});
//Use bootstrap tooltips for the most tooltips
$(document).on("ajaxUI:start ajaxUI:reload ajaxUI:dt_loaded", function () {
$(".tooltip").remove();
$('a[title], button[title], span[title], h6[title], h3[title], i.fas[title]')
//@ts-ignore
.tooltip("hide").tooltip({container: "body", placement: "auto", boundary: 'window'});
});
// Add bootstrap treeview on divs with data-tree-data attribute // Add bootstrap treeview on divs with data-tree-data attribute
$(document).on("ajaxUI:start ajaxUI:reload", function() { $(document).on("ajaxUI:start ajaxUI:reload", function() {
$("[data-tree-data]").each(function(index, element) { $("[data-tree-data]").each(function(index, element) {
@ -263,60 +168,6 @@ $(document).on("ajaxUI:reload", function () {
$(".file").fileinput(); $(".file").fileinput();
}); });
$(document).on("ajaxUI:start ajaxUI:reload", function () {
$('input.tagsinput').each(function() {
//Use typeahead if an autocomplete url was specified.
if($(this).data('autocomplete')) {
//@ts-ignore
var engine = new Bloodhound({
//@ts-ignore
datumTokenizer: Bloodhound.tokenizers.obj.whitespace(''),
//@ts-ignore
queryTokenizer: Bloodhound.tokenizers.obj.whitespace(''),
remote: {
url: $(this).data('autocomplete'),
wildcard: 'QUERY'
}
});
//@ts-ignore
$(this).tagsinput({
typeaheadjs: {
name: 'tags',
source: engine.ttAdapter()
}
});
} else { //Init tagsinput without typeahead
//@ts-ignore
$(this).tagsinput();
}
})
});
/**
* Register the button, to jump to the top of the page.
*/
$(document).on("ajaxUI:start", function registerJumpToTop() {
$(window).scroll(function () {
if ($(this).scrollTop() > 50) {
$('#back-to-top').fadeIn();
} else {
$('#back-to-top').fadeOut();
}
});
// scroll body to 0px on click
$('#back-to-top').click(function () {
$('#back-to-top').tooltip('hide');
$('body,html').animate({
scrollTop: 0
}, 800);
return false;
}).tooltip();
});
/** /**
* This listener keeps track of which tab is currently selected (using hash and localstorage) and will try to open * This listener keeps track of which tab is currently selected (using hash and localstorage) and will try to open
@ -374,48 +225,6 @@ $(document).on("ajaxUI:reload ajaxUI:start ajaxUI:dt_loaded", function () {
}); });
}); });
/*
* Register the button which is used to
*/
$(document).on("ajaxUI:start", function() {
let $sidebar = $("#fixed-sidebar");
let $container = $("#main");
let $toggler = $('#sidebar-toggle-button');
function sidebarHide() {
$sidebar.hide();
$container.removeClass('col-md-9 col-lg-10 offset-md-3 offset-lg-2');
$container.addClass('col-12');
$toggler.html('<i class="fas fa-angle-right"></i>');
$toggler.data('hidden', true);
localStorage.setItem('sidebarHidden', 'true');
}
function sidebarShow() {
let $sidebar = $("#fixed-sidebar");
$sidebar.show();
let $container = $("#main");
$container.removeClass('col-12');
$container.addClass('col-md-9 col-lg-10 offset-md-3 offset-lg-2');
$toggler.html('<i class="fas fa-angle-left"></i>');
$toggler.data('hidden', false);
localStorage.setItem('sidebarHidden', 'false');
}
//Make the state persistent over reloads
if(localStorage.getItem('sidebarHidden') === 'true') {
sidebarHide();
}
//Register handler
$toggler.click(function() {
if($(this).data('hidden')) {
sidebarShow();
} else {
sidebarHide();
}
});
});
//Register typeaheads //Register typeaheads
$(document).on("ajaxUI:reload ajaxUI:start attachment:create", function () { $(document).on("ajaxUI:reload ajaxUI:start attachment:create", function () {
@ -459,51 +268,6 @@ $(document).on("ajaxUI:reload ajaxUI:start attachment:create", function () {
}) })
}); });
$(document).on("ajaxUI:start", function () {
function decodeHTML(html) {
var txt = document.createElement('textarea');
txt.innerHTML = html;
return txt.value;
}
function parseMarkdown() {
$('.markdown').each(function() {
let unescaped = marked(decodeHTML( $(this).data('markdown')));
//@ts-ignore
let escaped = DOMPurify.sanitize(unescaped);
$(this).html(escaped);
//Remove markdown from DOM
$(this).removeAttr('data-markdown');
//Make all links external
$('a', this).addClass('link-external').attr('target', '_blank').attr('rel', 'noopener');
//Bootstrapify objects
$('table', this).addClass('table table-hover table-striped table-bordered');
});
//Latex rendering have to be done after markdown parsing
$('.latex').each(function(index, element) {
//@ts-ignore
window.renderMathInElement(element, {
delimiters: [
{left: "$$", right: "$$", display: true},
{left: "$", right: "$", display: false},
{left: "\\(", right: "\\)", display: false},
{left: "\\[", right: "\\]", display: true}
]
});
});
}
//Configure markdown
marked.setOptions({
gfm: true,
});
parseMarkdown();
$(document).on("ajaxUI:reload", parseMarkdown);
$(document).on("ajaxUI:dt_loaded", parseMarkdown);
});
$(document).on("ajaxUI:start ajaxUI:reload attachment:create", function() { $(document).on("ajaxUI:start ajaxUI:reload attachment:create", function() {
let updater = function() { let updater = function() {
@ -571,92 +335,6 @@ $(document).on("ajaxUI:reload", function() {
}) })
}); });
//Reuse codereader between multiple requests
const codeReader = new ZXing.BrowserMultiFormatReader();
//Init barcode scanner
$(document).on("ajaxUI:start ajaxUI:reload", function() {
//Skip if we are not on scanner page...
if (!document.getElementById('scan_dialog_form')) {
codeReader.reset();
return;
}
let selectedDeviceId;
//Save it for later, so we can reset it
console.log('ZXing code reader initialized');
codeReader.listVideoInputDevices()
.then((videoInputDevices) => {
if (videoInputDevices.length >= 1) {
const sourceSelect = document.getElementById('sourceSelect');
videoInputDevices.forEach((element) => {
const sourceOption = document.createElement('option');
sourceOption.text = element.label;
sourceOption.value = element.deviceId;
sourceSelect.appendChild(sourceOption);
});
//Try to retrieve last selected webcam...
let last_cam_id = localStorage.getItem('scanner_last_cam_id');
if (!!last_cam_id) {
//selectedDeviceId = localStorage.getItem('scanner_last_cam_id');
$(sourceSelect).val(last_cam_id);
} else {
selectedDeviceId = videoInputDevices[0].deviceId;
}
sourceSelect.onchange = () => {
//@ts-ignore
selectedDeviceId = sourceSelect.value;
localStorage.setItem('scanner_last_cam_id', selectedDeviceId);
changeHandler();
};
document.getElementById('sourceSelectPanel').classList.remove('d-none');
document.getElementById('video').classList.remove('d-none');
document.getElementById('scanner-warning').classList.add('d-none');
}
let changeHandler = () => {
codeReader.reset();
codeReader.decodeFromVideoDevice(selectedDeviceId, 'video', (result, err) => {
if (result) {
//@ts-ignore
document.getElementById('scan_dialog_input').value = result.text;
//Submit form
//@ts-ignore
document.getElementById('scan_dialog_form').submit();
}
if (err && !(err instanceof ZXing.NotFoundException)) {
console.error(err);
//document.getElementById('result').textContent = err
}
});
console.log(`Started continous decode from camera with id ${selectedDeviceId}`)
};
//Register Change Src Button
//document.getElementById('changeSrcBtn').addEventListener('click', changeHandler);
//Try to start logging automatically.
changeHandler();
})
.catch((err) => {
console.error(err)
})
});
//Need for proper body padding, with every navbar height //Need for proper body padding, with every navbar height
$(window).resize(function () { $(window).resize(function () {
let height : number = $('#navbar').height() + 10; let height : number = $('#navbar').height() + 10;

View file

@ -14,7 +14,7 @@
<div class=""> <div class="">
<div class="form-group row"> <div class="form-group row">
<div class="offset-sm-3 col-sm-9"> <div class="offset-sm-3 col-sm-9">
<video id="video" width="100%" height="auto" class="scanner-video img-thumbnail d-none"> <video id="video" width="100%" height="auto" class="scanner-video img-thumbnail d-none" {{ stimulus_controller('pages/barcode_scan') }}>
</video> </video>
</div> </div>
</div> </div>