Newer
Older
import {css, html, LitElement} from 'lit-element';
import DBPLitElement from 'dbp-common/dbp-lit-element';
import * as commonStyles from 'dbp-common/styles';
import {ScopedElementsMixin} from '@open-wc/scoped-elements';
import * as commonUtils from 'dbp-common/utils';
import {Button, Icon, MiniSpinner} from 'dbp-common';
import {classMap} from 'lit-html/directives/class-map.js';
/**
* Notification web component
*/
export class QrCodeScanner extends ScopedElementsMixin(DBPLitElement) {
constructor() {
super();
this.lang = 'de';
this.videoRunning = false;
this.notSupported = false;
this.scanIsOk = false;
this.showOutput = false;
}
static get scopedElements() {
return {
'dbp-icon': Icon,
'dbp-mini-spinner': MiniSpinner,
};
}
/**
* See: https://lit-element.polymer-project.org/guide/properties#initialize
*/
static get properties() {
return {
lang: { type: String },
askPermission: { type: Boolean, attribute: false },
videoRunning: { type: Boolean, attribute: false },
notSupported: { type: Boolean, attribute: false },
front: { type: Boolean, attribute: false },
loading: { type: Boolean, attribute: false },
scanIsOk: { type: Boolean, attribute: 'scan-is-ok' },
showOutput: { type: Boolean, attribute: 'show-output' },
stopScan: { type: Boolean, attribute: 'stop-scan' }
};
}
connectedCallback() {
super.connectedCallback();
i18n.changeLanguage(this.lang);
const that = this;
this.updateComplete.then(()=>{
if (navigator.mediaDevices
&& navigator.mediaDevices.enumerateDevices
&& navigator.mediaDevices.getUserMedia) {
navigator.mediaDevices.enumerateDevices()
.then(function (devices) {
devices.forEach(function (device) {
console.log(device.kind + ": " + device.label +
" id = " + device.deviceId);
// that._("#error").innerText += " | device.kind: " + device.kind + " id: " + device.deviceId + " label: " + device.label + " | ";
if (device.kind === 'videoinput') {
let id = device.deviceId;
if (/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)) {
devices_map.set('environment', i18n.t('back-camera'));
devices_map.set('user', i18n.t('front-camera'));
} else {
devices_map.set(id ? id : true, device.label || i18n.t('camera') + (devices_map.size + 1));
}
});
if (devices_map.size < 1) {
that.notSupported = true;
for (let [id, label] of devices_map) {
let opt = document.createElement("option");
opt.value = id;
opt.text = label;
that._('#videoSource').appendChild(opt);
}
console.log(devices_map);
})
.catch(function (err) {
console.log(err.name + ": " + err.message);
});
} else if (MediaStreamTrack && MediaStreamTrack.getSources) {
this._log("MediaStreamTrack.getSources used");
const callback = sourceInfos => {
const results = [];
for (let i = 0; i !== sourceInfos.length; ++i) {
const sourceInfo = sourceInfos[i];
// that._("#error").innerText += " * kind: " + sourceInfo.kind + " id: " + sourceInfo.id + " label: " + sourceInfo.label + " * ";
if (sourceInfo.kind === 'video') {
devices_map.set(sourceInfo.id ? sourceInfo.id : true, sourceInfo.label || i18n.t('camera') + (devices_map.size + 1))
results.push({
id: sourceInfo.id,
label: sourceInfo.label
});
}
this._log(`${results.length} results found`);
resolve(results);
}
MediaStreamTrack.getSources(callback);
}
else {
that.notSupported = true;
}
getLoadingMsg(string) {
let loadingMsg = html `<dbp-mini-spinner class="spinner"></dbp-mini-spinner> ${i18n.t(string)}`;
return loadingMsg;
}
let video = document.createElement("video");
let canvasElement = this._("#canvas");
let canvas = canvasElement.getContext("2d");
let loadingMessage = this._("#loadingMessage");
let loadingMessageInner = this._(".loadingMsg");
let outputContainer = this._("#output");
let outputMessage = this._("#outputMessage");
let outputData = this._("#outputData");
let color = this.scanIsOk ? getComputedStyle(document.documentElement)
.getPropertyValue('--dbp-success-bg-color') : getComputedStyle(document.documentElement)
.getPropertyValue('--dbp-danger-bg-color');
function drawLine(begin, end, color) {
canvas.beginPath();
canvas.moveTo(begin.x, begin.y);
canvas.lineTo(end.x, end.y);
canvas.lineWidth = 4;
canvas.strokeStyle = color;
canvas.stroke();
}
const that = this;
let constraint = { video: { deviceId: this._('#videoSource').options[this._('#videoSource').selectedIndex].value } };
if ( (this._('#videoSource').options[this._('#videoSource').selectedIndex].value === 'environment') ) {
constraint = { video: { facingMode: "environment" } };
} else if ( this._('#videoSource').options[this._('#videoSource').selectedIndex].value === 'user' ) {
constraint = { video: { facingMode: "user" } };
navigator.mediaDevices.getUserMedia(constraint).then(function(stream) {
video.srcObject = stream;
video.setAttribute("playsinline", true); // required to tell iOS safari we don't want fullscreen
video.play();
that.videoRunning = true;
requestAnimationFrame(tick);
}).catch((e) => { console.log(e); that.askPermission = true;});
function tick() {
if (that.videoRunning === false) {
video.srcObject.getTracks().forEach(function(track) {
track.stop();
console.log("stop early");
loadingMessage.hidden = false;
canvasElement.hidden = true;
outputContainer.hidden = true;
});
loadingMessageInner.innerText = i18n.t('no-camera-access');
if (that.stopScan) {
video.srcObject.getTracks().forEach(function(track) {
track.stop();
console.log("stop early");
loadingMessage.hidden = false;
canvasElement.hidden = true;
outputContainer.hidden = true;
});
loadingMessageInner.innerText = i18n.t('finished-scan');
return;
}
that.loading = true;
loadingMessageInner.innerText = i18n.t('loading-video');
if (video.readyState === video.HAVE_ENOUGH_DATA) {
loadingMessage.hidden = true;
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
canvasElement.hidden = false;
outputContainer.hidden = false;
canvasElement.height = video.videoHeight;
canvasElement.width = video.videoWidth;
canvas.drawImage(video, 0, 0, canvasElement.width, canvasElement.height);
var imageData = canvas.getImageData(0, 0, canvasElement.width, canvasElement.height);
var code = jsQR(imageData.data, imageData.width, imageData.height, {
inversionAttempts: "dontInvert",
});
if (code) {
drawLine(code.location.topLeftCorner, code.location.topRightCorner, that.scanIsOk ? 'green' : 'red');
drawLine(code.location.topRightCorner, code.location.bottomRightCorner, that.scanIsOk ? 'green' : 'red');
drawLine(code.location.bottomRightCorner, code.location.bottomLeftCorner, that.scanIsOk ? 'green' : 'red');
drawLine(code.location.bottomLeftCorner, code.location.topLeftCorner, that.scanIsOk ? 'green' : 'red');
outputMessage.hidden = true;
outputData.parentElement.hidden = false;
outputData.innerText = code.data;
that.sendUrl(code.data);
} else {
outputMessage.hidden = false;
outputData.parentElement.hidden = true;
}
}
requestAnimationFrame(tick);
}
}
stopScanning() {
this.videoRunning = false;
}
sendUrl(url) {
const event = new CustomEvent("dbp-qr-code-scanner-url",
{ bubbles: true, composed: true , detail: url});
this.dispatchEvent(event);
}
static get styles() {
// language=css
return css`
${commonStyles.getThemeCSS()}
${commonStyles.getGeneralCSS()}
${commonStyles.getButtonCSS()}
text-align: center;
padding: 40px;
}
.wrapper-msg {
width: 100%;
display: flex;
justify-content: center;
align-items: baseline;
margin-top: 20px;
background: #eee;
padding: 10px;
padding-bottom: 0;
padding-bottom: 10px;
word-wrap: break-word;
text-align: center;
}
.spinner{
margin-right: 10px;
font-size: 0.7em;
}
#videoSource{
padding-bottom: calc(0.375em - 2px);
padding-left: 0.75em;
padding-right: 1.75em;
padding-top: calc(0.375em - 2px);
background-position-x: calc(100% - 0.4rem);
font-size: inherit;
}
select:not(.select)#videoSource{
background-size: 15%;
margin-top: 2rem;
padding-top: 2rem;
border-top: 1px solid black;
`;
}
//doku
//demo
render() {
return html`
<div class="columns">
<div class="column" id="qr">
<div class="${classMap({hidden: this.notSupported})}">
<button class="start button is-primary ${classMap({hidden: this.videoRunning})}" @click="${() => this.qrCodeScannerInit()}" title="${i18n.t('start-scan')}">${i18n.t('start-scan')}</button>
<button class="stop button is-primary ${classMap({hidden: !this.videoRunning})}" @click="${() => this.stopScanning()}" title="${i18n.t('stop-scan')}">${i18n.t('stop-scan')}</button>
<select id="videoSource"></select>
<div id="loadingMessage" class="border ${classMap({hidden: !this.askPermission})}">
<div class="wrapper-msg">
<dbp-mini-spinner class="spinner ${classMap({hidden: !this.loading})}"></dbp-mini-spinner>
<div class="loadingMsg">${i18n.t('no-camera-access')}</div>
</div>
</div>
<canvas id="canvas" hidden class="border"></canvas>
<div id="output" hidden class="border ${classMap({hidden: !this.showOutput})}">
<div id="outputMessage">${i18n.t('no-qr-detectede')}</div>
<div hidden><b>${i18n.t('data')}:</b> <span id="outputData"></span></div>
<div class="border ${classMap({hidden: !this.notSupported})}">
${i18n.t('no-support')}
</div>
</div>
`;
}
}