@twintag/twintag-core 0.2.276 → 0.2.277-fix-scanner-ios-zoom-0c21a76d75c16ef4e56326dcc59c57b571aac187
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/twintag-offline-support.cjs.entry.js +1 -1
- package/dist/cjs/twintag-qr-scanner.cjs.entry.js +102 -79
- package/dist/cjs/twintag-scanner.cjs.entry.js +1 -1
- package/dist/collection/components/twintag-scanner/twintag-qr-scanner.js +116 -79
- package/dist/collection/components/twintag-scanner/twintag-scanner.css +9 -48
- package/dist/collection/version.js +1 -1
- package/dist/components/twintag-offline-support.js +1 -1
- package/dist/components/twintag-qr-scanner.js +102 -79
- package/dist/components/twintag-scanner.js +1 -1
- package/dist/esm/twintag-offline-support.entry.js +1 -1
- package/dist/esm/twintag-qr-scanner.entry.js +102 -79
- package/dist/esm/twintag-scanner.entry.js +1 -1
- package/dist/stencil-web-components/p-5c9af93d.entry.js +1 -0
- package/dist/stencil-web-components/p-67fa5b70.entry.js +1 -0
- package/dist/stencil-web-components/{p-7767a9b0.entry.js → p-707ee419.entry.js} +1 -1
- package/dist/stencil-web-components/stencil-web-components.esm.js +1 -1
- package/dist/types/components/twintag-scanner/twintag-qr-scanner.d.ts +11 -4
- package/dist/types/components.d.ts +1 -0
- package/dist/types/drone/src/libs/stencil-web-components/.stencil/libs/twintag-sdk/src/lib/version.d.ts +1 -1
- package/dist/types/version.d.ts +1 -1
- package/package.json +1 -1
- package/twintag-sdk/src/lib/version.js +1 -1
- package/dist/stencil-web-components/p-10c8870d.entry.js +0 -1
- package/dist/stencil-web-components/p-f09e2ad8.entry.js +0 -1
|
@@ -11,19 +11,18 @@ const workerMsgId = 'stencil.scanner.worker';
|
|
|
11
11
|
const workerPath = /*@__PURE__*/(typeof document === 'undefined' ? new (require('u' + 'rl').URL)('file:' + __dirname + '/scanner.worker-b82b2299.js').href : new URL('scanner.worker-b82b2299.js', document.currentScript && document.currentScript.src || document.baseURI).href);
|
|
12
12
|
const worker = /*@__PURE__*/_workerHelper.createWorker(workerPath, workerName, workerMsgId);
|
|
13
13
|
|
|
14
|
-
const twintagScannerCss = ":host{--twintag-scanner-bg-select-cameras:white;--twintag-scanner-text-select-cameras:black;--twintag-scanner-radius-select-cameras:6px 0 0 0;--twintag-scanner-
|
|
14
|
+
const twintagScannerCss = ":host{--twintag-scanner-bg-select-cameras:white;--twintag-scanner-text-select-cameras:black;--twintag-scanner-radius-select-cameras:6px 0 0 0;--twintag-scanner-corners-color:white;--twintag-scanner-corners-width:4px;--twintag-scanner-corners-length:36px;--twintag-scanner-corners-offset:24px;display:grid;place-items:center;position:relative;width:100%}:host .icon-container{display:grid;place-items:center;position:absolute;inset:0;z-index:-2222}:host .icon-container .check-icon{opacity:0;z-index:2222}:host .video-container{--crop:0;display:flex;align-items:center;justify-content:center;position:relative;width:100%;height:100%;opacity:0;overflow:hidden;z-index:1}:host .video-container::after{content:\"\";position:absolute;width:100%;height:100%;opacity:inherit;background:rgba(0, 0, 0, 0.5);-webkit-clip-path:polygon(0% 0%, 0% 100%, calc(var(--crop) * 1%) 100%, calc(var(--crop) * 1%) calc(var(--crop) * 1%), calc(calc(100 - var(--crop)) * 1%) calc(var(--crop) * 1%), calc(calc(100 - var(--crop)) * 1%) calc(calc(100 - var(--crop)) * 1%), calc(var(--crop) * 1%) calc(calc(100 - var(--crop)) * 1%), calc(var(--crop) * 1%) 100%, 100% 100%, 100% 0%);clip-path:polygon(0% 0%, 0% 100%, calc(var(--crop) * 1%) 100%, calc(var(--crop) * 1%) calc(var(--crop) * 1%), calc(calc(100 - var(--crop)) * 1%) calc(var(--crop) * 1%), calc(calc(100 - var(--crop)) * 1%) calc(calc(100 - var(--crop)) * 1%), calc(var(--crop) * 1%) calc(calc(100 - var(--crop)) * 1%), calc(var(--crop) * 1%) 100%, 100% 100%, 100% 0%)}:host .video-container:before{display:block;content:\"\";width:100%;padding-top:100%}:host .video-container .video-corners{position:absolute;width:calc((1 - var(--crop) / 50) * 100% + var(--twintag-scanner-corners-offset));height:calc((1 - var(--crop) / 50) * 100% + var(--twintag-scanner-corners-offset));opacity:inherit;background:linear-gradient(to right, var(--twintag-scanner-corners-color) var(--twintag-scanner-corners-width), transparent var(--twintag-scanner-corners-width)) 0 0, linear-gradient(to right, var(--twintag-scanner-corners-color) var(--twintag-scanner-corners-width), transparent var(--twintag-scanner-corners-width)) 0 100%, linear-gradient(to left, var(--twintag-scanner-corners-color) var(--twintag-scanner-corners-width), transparent var(--twintag-scanner-corners-width)) 100% 0, linear-gradient(to left, var(--twintag-scanner-corners-color) var(--twintag-scanner-corners-width), transparent var(--twintag-scanner-corners-width)) 100% 100%, linear-gradient(to bottom, var(--twintag-scanner-corners-color) var(--twintag-scanner-corners-width), transparent var(--twintag-scanner-corners-width)) 0 0, linear-gradient(to bottom, var(--twintag-scanner-corners-color) var(--twintag-scanner-corners-width), transparent var(--twintag-scanner-corners-width)) 100% 0, linear-gradient(to top, var(--twintag-scanner-corners-color) var(--twintag-scanner-corners-width), transparent var(--twintag-scanner-corners-width)) 0 100%, linear-gradient(to top, var(--twintag-scanner-corners-color) var(--twintag-scanner-corners-width), transparent var(--twintag-scanner-corners-width)) 100% 100%;background-size:var(--twintag-scanner-corners-length) var(--twintag-scanner-corners-length);background-repeat:no-repeat;z-index:2222}:host .video-container video{position:absolute;width:100% !important;object-fit:cover;object-position:center;aspect-ratio:1/1 !important;min-height:var(--twintag-scanner-min-height, 100%)}";
|
|
15
15
|
|
|
16
16
|
const TwintagQRScanner = class {
|
|
17
17
|
constructor(hostRef) {
|
|
18
18
|
index.registerInstance(this, hostRef);
|
|
19
19
|
this.symbol = index.createEvent(this, "symbol", 7);
|
|
20
20
|
this.streamIsActive = index.createEvent(this, "streamIsActive", 7);
|
|
21
|
-
this.
|
|
22
|
-
this.stageHeight = 0;
|
|
21
|
+
this.frame = index.createEvent(this, "frame", 7);
|
|
23
22
|
this.devices = [];
|
|
24
23
|
this.stream = null;
|
|
25
24
|
this.mode = 'single';
|
|
26
|
-
this.format = '';
|
|
25
|
+
this.format = 'QRCode';
|
|
27
26
|
this.crop = 1;
|
|
28
27
|
this.zoom = 0;
|
|
29
28
|
}
|
|
@@ -45,15 +44,15 @@ const TwintagQRScanner = class {
|
|
|
45
44
|
willReadFrequently: true,
|
|
46
45
|
});
|
|
47
46
|
this.videoContainerRef.style.setProperty('--crop', `${(1 - this.crop) * 50}`);
|
|
48
|
-
this.
|
|
47
|
+
await this.startStream();
|
|
49
48
|
}
|
|
50
49
|
async readBarcodeFromCanvas() {
|
|
51
50
|
const imgWidth = this.canvas.width;
|
|
52
51
|
const imgHeight = this.canvas.height;
|
|
53
52
|
const imageData = this.canvas.getContext('2d').getImageData(0, 0, imgWidth, imgHeight);
|
|
54
53
|
const sourceBuffer = imageData.data;
|
|
55
|
-
this.drawInCanvas();
|
|
56
54
|
await this.updateVideoZoom();
|
|
55
|
+
this.drawInCanvas();
|
|
57
56
|
worker.postMessage({
|
|
58
57
|
type: 'read',
|
|
59
58
|
format: this.format,
|
|
@@ -63,50 +62,86 @@ const TwintagQRScanner = class {
|
|
|
63
62
|
sourceBuffer: sourceBuffer
|
|
64
63
|
}
|
|
65
64
|
});
|
|
65
|
+
this.frame.emit(this.canvas.toDataURL('image/png'));
|
|
66
66
|
requestAnimationFrame(this.readBarcodeFromCanvas.bind(this));
|
|
67
67
|
}
|
|
68
|
-
stopStream() {
|
|
69
|
-
worker.removeEventListener('message', this.messageHandler.bind(this));
|
|
70
|
-
this.stream.getTracks().forEach((track) => {
|
|
71
|
-
return track.stop();
|
|
72
|
-
});
|
|
73
|
-
index$1.gsapWithCSS.to(this.videoContainerRef, {
|
|
74
|
-
duration: 0.2,
|
|
75
|
-
opacity: 0,
|
|
76
|
-
onComplete: () => {
|
|
77
|
-
this.video.srcObject = null;
|
|
78
|
-
this.context.clearRect(0, 0, this.context.canvas.width, this.context.canvas.height);
|
|
79
|
-
},
|
|
80
|
-
});
|
|
81
|
-
}
|
|
82
68
|
drawInCanvas() {
|
|
83
|
-
const
|
|
84
|
-
|
|
85
|
-
this.stageWidth = this.video.videoHeight;
|
|
86
|
-
this.stageHeight = this.video.videoHeight;
|
|
87
|
-
}
|
|
88
|
-
else if (ratio < 1) {
|
|
89
|
-
this.stageWidth = this.video.videoWidth;
|
|
90
|
-
this.stageHeight = this.video.videoWidth;
|
|
91
|
-
}
|
|
92
|
-
else if (ratio === 1) {
|
|
93
|
-
this.stageWidth = this.video.videoWidth;
|
|
94
|
-
this.stageHeight = this.video.videoHeight;
|
|
95
|
-
}
|
|
69
|
+
const stage = Math.min(this.video.videoWidth, this.video.videoHeight);
|
|
70
|
+
const squareLength = stage * this.crop;
|
|
96
71
|
const center = {
|
|
97
72
|
x: this.video.videoWidth / 2,
|
|
98
73
|
y: this.video.videoHeight / 2,
|
|
99
74
|
};
|
|
100
|
-
const sx = center.x -
|
|
101
|
-
const sy = center.y -
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
this.
|
|
107
|
-
|
|
75
|
+
const sx = center.x - squareLength / 2;
|
|
76
|
+
const sy = center.y - squareLength / 2;
|
|
77
|
+
this.context.drawImage(this.video, sx, sy, squareLength, squareLength, 0, 0, stage, stage);
|
|
78
|
+
}
|
|
79
|
+
async updateStream(constraints) {
|
|
80
|
+
if (this.stream) {
|
|
81
|
+
this.stream.getTracks().forEach((track) => {
|
|
82
|
+
return track.stop();
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
try {
|
|
86
|
+
this.stream = await navigator.mediaDevices.getUserMedia(constraints);
|
|
87
|
+
this.video.srcObject = this.stream;
|
|
88
|
+
this.video.setAttribute("playsinline", 'true'); // required to tell iOS safari we don't want fullscreen
|
|
89
|
+
await this.video.play();
|
|
90
|
+
}
|
|
91
|
+
catch (err) {
|
|
92
|
+
console.error(err);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
async getTrackSettings(constraints) {
|
|
96
|
+
try {
|
|
97
|
+
const stream = await navigator.mediaDevices.getUserMedia(constraints);
|
|
98
|
+
return stream.getVideoTracks()[0].getSettings();
|
|
99
|
+
}
|
|
100
|
+
catch (err) {
|
|
101
|
+
console.error(err);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
async getMedia(constraints) {
|
|
105
|
+
constraints.video.width = { min: 1080, ideal: 1600, max: 1920 };
|
|
106
|
+
try {
|
|
107
|
+
await this.updateStream(constraints);
|
|
108
|
+
const devices = await navigator.mediaDevices.enumerateDevices();
|
|
109
|
+
this.devices = devices.filter((device) => device.kind.toLowerCase() === 'videoinput' && device.label.toLowerCase().includes('back'));
|
|
110
|
+
if (this.devices.length === 0) {
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
if (this.devices.length === 1) {
|
|
114
|
+
constraints.video.deviceId = { exact: this.devices[0].deviceId };
|
|
115
|
+
await this.updateStream(constraints);
|
|
116
|
+
}
|
|
117
|
+
let backCameraIndex = this.devices.length - 1;
|
|
118
|
+
const cameraResolutions = this.devices.map(camera => {
|
|
119
|
+
const match = camera.label.match(/\b([0-9]+)MP?\b/i);
|
|
120
|
+
if (match != null) {
|
|
121
|
+
return parseInt(match[1], 10);
|
|
122
|
+
}
|
|
123
|
+
return NaN;
|
|
124
|
+
});
|
|
125
|
+
if (!cameraResolutions.some(cameraResolution => isNaN(cameraResolution))) {
|
|
126
|
+
backCameraIndex = cameraResolutions.lastIndexOf(Math.max(...cameraResolutions));
|
|
127
|
+
}
|
|
128
|
+
// back camera with the highest resolution
|
|
129
|
+
const backCamera = this.devices[backCameraIndex];
|
|
130
|
+
if (backCamera) {
|
|
131
|
+
constraints.video.deviceId = { exact: backCamera.deviceId };
|
|
132
|
+
await this.updateStream(constraints);
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
const settings = await this.getTrackSettings(constraints);
|
|
136
|
+
if (settings.focusDistance) {
|
|
137
|
+
const focusDistance = settings.focusDistance;
|
|
138
|
+
constraints.video.advanced = [{ focusMode: 'continuous', focusDistance: focusDistance }];
|
|
139
|
+
await this.updateStream(constraints);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
catch (err) {
|
|
143
|
+
console.error(err);
|
|
108
144
|
}
|
|
109
|
-
this.context.drawImage(this.video, sx, sy, sWidth, sHeight, 0, 0, sWidth, sHeight);
|
|
110
145
|
}
|
|
111
146
|
async updateVideoZoom() {
|
|
112
147
|
const [videoTrack] = this.stream.getVideoTracks();
|
|
@@ -126,64 +161,52 @@ const TwintagQRScanner = class {
|
|
|
126
161
|
}
|
|
127
162
|
}
|
|
128
163
|
}
|
|
129
|
-
async
|
|
130
|
-
const selectedCamera = window.localStorage.getItem('twintag-scanner-camera');
|
|
164
|
+
async startStream() {
|
|
131
165
|
const constraints = {
|
|
132
166
|
audio: false,
|
|
133
|
-
video:
|
|
167
|
+
video: {
|
|
168
|
+
aspectRatio: { ideal: 1 },
|
|
169
|
+
facingMode: 'environment',
|
|
170
|
+
}
|
|
134
171
|
};
|
|
135
172
|
const supports = navigator.mediaDevices.getSupportedConstraints();
|
|
136
173
|
if (supports.zoom) {
|
|
137
174
|
constraints.video['zoom'] = true;
|
|
138
175
|
}
|
|
139
|
-
|
|
140
|
-
window.localStorage.setItem('twintag-scanner-camera', deviceId);
|
|
141
|
-
constraints.video['deviceId'] = {
|
|
142
|
-
exact: deviceId,
|
|
143
|
-
};
|
|
144
|
-
}
|
|
145
|
-
else if (selectedCamera && selectedCamera !== 'undefined') {
|
|
146
|
-
constraints.video['deviceId'] = {
|
|
147
|
-
exact: selectedCamera,
|
|
148
|
-
};
|
|
149
|
-
}
|
|
150
|
-
else {
|
|
151
|
-
constraints.video['facingMode'] = 'environment';
|
|
152
|
-
}
|
|
153
|
-
if (!this.stream) {
|
|
154
|
-
const stream = await navigator.mediaDevices.getUserMedia(constraints);
|
|
155
|
-
this.stream = stream;
|
|
156
|
-
}
|
|
157
|
-
else {
|
|
158
|
-
this.stream = this.stream;
|
|
159
|
-
}
|
|
160
|
-
this.streamIsActive.emit(true);
|
|
161
|
-
const allDevices = await navigator.mediaDevices.enumerateDevices();
|
|
162
|
-
this.devices = allDevices.filter((device) => device.kind === 'videoinput' && device.label.includes('back'));
|
|
176
|
+
await this.getMedia(constraints);
|
|
163
177
|
await this.updateVideoZoom();
|
|
164
|
-
this.video.srcObject = this.stream;
|
|
165
|
-
this.video.setAttribute("playsinline", 'true'); // required to tell iOS safari we don't want fullscreen
|
|
166
|
-
await this.video.play();
|
|
167
|
-
this.canvas.width = this.video.clientWidth;
|
|
168
|
-
this.canvas.height = this.video.clientHeight;
|
|
169
178
|
index$1.gsapWithCSS.to(this.videoContainerRef, {
|
|
170
179
|
duration: 0.2,
|
|
171
180
|
opacity: 1,
|
|
172
181
|
onComplete: () => {
|
|
182
|
+
this.streamIsActive.emit(true);
|
|
183
|
+
const stage = Math.min(this.video.videoWidth, this.video.videoHeight);
|
|
184
|
+
this.canvas.width = stage;
|
|
185
|
+
this.canvas.height = stage;
|
|
173
186
|
this.readBarcodeFromCanvas();
|
|
174
187
|
}
|
|
175
188
|
});
|
|
176
189
|
}
|
|
177
190
|
;
|
|
191
|
+
stopStream() {
|
|
192
|
+
worker.removeEventListener('message', this.messageHandler.bind(this));
|
|
193
|
+
index$1.gsapWithCSS.to(this.videoContainerRef, {
|
|
194
|
+
duration: 0.2,
|
|
195
|
+
opacity: 0,
|
|
196
|
+
onComplete: () => {
|
|
197
|
+
this.stream.getTracks().forEach((track) => {
|
|
198
|
+
return track.stop();
|
|
199
|
+
});
|
|
200
|
+
this.video.srcObject = null;
|
|
201
|
+
this.context.clearRect(0, 0, this.context.canvas.width, this.context.canvas.height);
|
|
202
|
+
},
|
|
203
|
+
});
|
|
204
|
+
}
|
|
178
205
|
disconnectedCallback() {
|
|
179
206
|
this.stopStream();
|
|
180
207
|
}
|
|
181
208
|
render() {
|
|
182
|
-
return (index.h(index.Host, null, index.h("div", { class: "video-container", ref: (el) => (this.videoContainerRef = el) }, index.h("div", { class: "video-corners" }), index.h("video", { crossOrigin: "Anonymous", autoplay: true, playsinline: true, ref: (el) => (this.video = el) })
|
|
183
|
-
this.stopStream();
|
|
184
|
-
this.updateVideoStream(e.target.value);
|
|
185
|
-
} }, index.h("option", { hidden: true, disabled: true }, "-- Select a camera --"), this.devices.map((deviceInfo, idx) => (index.h("option", { key: idx, value: deviceInfo.deviceId, selected: window.localStorage.getItem('twintag-scanner-camera') ===
|
|
186
|
-
deviceInfo.deviceId }, `${deviceInfo.label}`)))))) : null)));
|
|
209
|
+
return (index.h(index.Host, null, index.h("div", { class: "video-container", ref: (el) => (this.videoContainerRef = el) }, index.h("div", { class: "video-corners" }), index.h("video", { crossOrigin: "Anonymous", autoplay: true, playsinline: true, ref: (el) => (this.video = el) }))));
|
|
187
210
|
}
|
|
188
211
|
static get assetsDirs() { return ["assets"]; }
|
|
189
212
|
};
|
|
@@ -36218,7 +36218,7 @@ class Preprocessing {
|
|
|
36218
36218
|
}
|
|
36219
36219
|
}
|
|
36220
36220
|
|
|
36221
|
-
const twintagScannerCss = ":host{--twintag-scanner-bg-select-cameras:white;--twintag-scanner-text-select-cameras:black;--twintag-scanner-radius-select-cameras:6px 0 0 0;--twintag-scanner-
|
|
36221
|
+
const twintagScannerCss = ":host{--twintag-scanner-bg-select-cameras:white;--twintag-scanner-text-select-cameras:black;--twintag-scanner-radius-select-cameras:6px 0 0 0;--twintag-scanner-corners-color:white;--twintag-scanner-corners-width:4px;--twintag-scanner-corners-length:36px;--twintag-scanner-corners-offset:24px;display:grid;place-items:center;position:relative;width:100%}:host .icon-container{display:grid;place-items:center;position:absolute;inset:0;z-index:-2222}:host .icon-container .check-icon{opacity:0;z-index:2222}:host .video-container{--crop:0;display:flex;align-items:center;justify-content:center;position:relative;width:100%;height:100%;opacity:0;overflow:hidden;z-index:1}:host .video-container::after{content:\"\";position:absolute;width:100%;height:100%;opacity:inherit;background:rgba(0, 0, 0, 0.5);-webkit-clip-path:polygon(0% 0%, 0% 100%, calc(var(--crop) * 1%) 100%, calc(var(--crop) * 1%) calc(var(--crop) * 1%), calc(calc(100 - var(--crop)) * 1%) calc(var(--crop) * 1%), calc(calc(100 - var(--crop)) * 1%) calc(calc(100 - var(--crop)) * 1%), calc(var(--crop) * 1%) calc(calc(100 - var(--crop)) * 1%), calc(var(--crop) * 1%) 100%, 100% 100%, 100% 0%);clip-path:polygon(0% 0%, 0% 100%, calc(var(--crop) * 1%) 100%, calc(var(--crop) * 1%) calc(var(--crop) * 1%), calc(calc(100 - var(--crop)) * 1%) calc(var(--crop) * 1%), calc(calc(100 - var(--crop)) * 1%) calc(calc(100 - var(--crop)) * 1%), calc(var(--crop) * 1%) calc(calc(100 - var(--crop)) * 1%), calc(var(--crop) * 1%) 100%, 100% 100%, 100% 0%)}:host .video-container:before{display:block;content:\"\";width:100%;padding-top:100%}:host .video-container .video-corners{position:absolute;width:calc((1 - var(--crop) / 50) * 100% + var(--twintag-scanner-corners-offset));height:calc((1 - var(--crop) / 50) * 100% + var(--twintag-scanner-corners-offset));opacity:inherit;background:linear-gradient(to right, var(--twintag-scanner-corners-color) var(--twintag-scanner-corners-width), transparent var(--twintag-scanner-corners-width)) 0 0, linear-gradient(to right, var(--twintag-scanner-corners-color) var(--twintag-scanner-corners-width), transparent var(--twintag-scanner-corners-width)) 0 100%, linear-gradient(to left, var(--twintag-scanner-corners-color) var(--twintag-scanner-corners-width), transparent var(--twintag-scanner-corners-width)) 100% 0, linear-gradient(to left, var(--twintag-scanner-corners-color) var(--twintag-scanner-corners-width), transparent var(--twintag-scanner-corners-width)) 100% 100%, linear-gradient(to bottom, var(--twintag-scanner-corners-color) var(--twintag-scanner-corners-width), transparent var(--twintag-scanner-corners-width)) 0 0, linear-gradient(to bottom, var(--twintag-scanner-corners-color) var(--twintag-scanner-corners-width), transparent var(--twintag-scanner-corners-width)) 100% 0, linear-gradient(to top, var(--twintag-scanner-corners-color) var(--twintag-scanner-corners-width), transparent var(--twintag-scanner-corners-width)) 0 100%, linear-gradient(to top, var(--twintag-scanner-corners-color) var(--twintag-scanner-corners-width), transparent var(--twintag-scanner-corners-width)) 100% 100%;background-size:var(--twintag-scanner-corners-length) var(--twintag-scanner-corners-length);background-repeat:no-repeat;z-index:2222}:host .video-container video{position:absolute;width:100% !important;object-fit:cover;object-position:center;aspect-ratio:1/1 !important;min-height:var(--twintag-scanner-min-height, 100%)}";
|
|
36222
36222
|
|
|
36223
36223
|
const TwintagScanner = class {
|
|
36224
36224
|
openHandler() {
|
|
@@ -3,12 +3,10 @@ import gsap from 'gsap';
|
|
|
3
3
|
import { worker } from '../../scanner.worker?worker';
|
|
4
4
|
export class TwintagQRScanner {
|
|
5
5
|
constructor() {
|
|
6
|
-
this.stageWidth = 0;
|
|
7
|
-
this.stageHeight = 0;
|
|
8
6
|
this.devices = [];
|
|
9
7
|
this.stream = null;
|
|
10
8
|
this.mode = 'single';
|
|
11
|
-
this.format = '';
|
|
9
|
+
this.format = 'QRCode';
|
|
12
10
|
this.crop = 1;
|
|
13
11
|
this.zoom = 0;
|
|
14
12
|
}
|
|
@@ -30,15 +28,15 @@ export class TwintagQRScanner {
|
|
|
30
28
|
willReadFrequently: true,
|
|
31
29
|
});
|
|
32
30
|
this.videoContainerRef.style.setProperty('--crop', `${(1 - this.crop) * 50}`);
|
|
33
|
-
this.
|
|
31
|
+
await this.startStream();
|
|
34
32
|
}
|
|
35
33
|
async readBarcodeFromCanvas() {
|
|
36
34
|
const imgWidth = this.canvas.width;
|
|
37
35
|
const imgHeight = this.canvas.height;
|
|
38
36
|
const imageData = this.canvas.getContext('2d').getImageData(0, 0, imgWidth, imgHeight);
|
|
39
37
|
const sourceBuffer = imageData.data;
|
|
40
|
-
this.drawInCanvas();
|
|
41
38
|
await this.updateVideoZoom();
|
|
39
|
+
this.drawInCanvas();
|
|
42
40
|
worker.postMessage({
|
|
43
41
|
type: 'read',
|
|
44
42
|
format: this.format,
|
|
@@ -48,50 +46,86 @@ export class TwintagQRScanner {
|
|
|
48
46
|
sourceBuffer: sourceBuffer
|
|
49
47
|
}
|
|
50
48
|
});
|
|
49
|
+
this.frame.emit(this.canvas.toDataURL('image/png'));
|
|
51
50
|
requestAnimationFrame(this.readBarcodeFromCanvas.bind(this));
|
|
52
51
|
}
|
|
53
|
-
stopStream() {
|
|
54
|
-
worker.removeEventListener('message', this.messageHandler.bind(this));
|
|
55
|
-
this.stream.getTracks().forEach((track) => {
|
|
56
|
-
return track.stop();
|
|
57
|
-
});
|
|
58
|
-
gsap.to(this.videoContainerRef, {
|
|
59
|
-
duration: 0.2,
|
|
60
|
-
opacity: 0,
|
|
61
|
-
onComplete: () => {
|
|
62
|
-
this.video.srcObject = null;
|
|
63
|
-
this.context.clearRect(0, 0, this.context.canvas.width, this.context.canvas.height);
|
|
64
|
-
},
|
|
65
|
-
});
|
|
66
|
-
}
|
|
67
52
|
drawInCanvas() {
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
this.stageWidth = this.video.videoHeight;
|
|
71
|
-
this.stageHeight = this.video.videoHeight;
|
|
72
|
-
}
|
|
73
|
-
else if (ratio < 1) {
|
|
74
|
-
this.stageWidth = this.video.videoWidth;
|
|
75
|
-
this.stageHeight = this.video.videoWidth;
|
|
76
|
-
}
|
|
77
|
-
else if (ratio === 1) {
|
|
78
|
-
this.stageWidth = this.video.videoWidth;
|
|
79
|
-
this.stageHeight = this.video.videoHeight;
|
|
80
|
-
}
|
|
53
|
+
const stage = Math.min(this.video.videoWidth, this.video.videoHeight);
|
|
54
|
+
const squareLength = stage * this.crop;
|
|
81
55
|
const center = {
|
|
82
56
|
x: this.video.videoWidth / 2,
|
|
83
57
|
y: this.video.videoHeight / 2,
|
|
84
58
|
};
|
|
85
|
-
const sx = center.x -
|
|
86
|
-
const sy = center.y -
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
this.
|
|
92
|
-
|
|
59
|
+
const sx = center.x - squareLength / 2;
|
|
60
|
+
const sy = center.y - squareLength / 2;
|
|
61
|
+
this.context.drawImage(this.video, sx, sy, squareLength, squareLength, 0, 0, stage, stage);
|
|
62
|
+
}
|
|
63
|
+
async updateStream(constraints) {
|
|
64
|
+
if (this.stream) {
|
|
65
|
+
this.stream.getTracks().forEach((track) => {
|
|
66
|
+
return track.stop();
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
try {
|
|
70
|
+
this.stream = await navigator.mediaDevices.getUserMedia(constraints);
|
|
71
|
+
this.video.srcObject = this.stream;
|
|
72
|
+
this.video.setAttribute("playsinline", 'true'); // required to tell iOS safari we don't want fullscreen
|
|
73
|
+
await this.video.play();
|
|
74
|
+
}
|
|
75
|
+
catch (err) {
|
|
76
|
+
console.error(err);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
async getTrackSettings(constraints) {
|
|
80
|
+
try {
|
|
81
|
+
const stream = await navigator.mediaDevices.getUserMedia(constraints);
|
|
82
|
+
return stream.getVideoTracks()[0].getSettings();
|
|
83
|
+
}
|
|
84
|
+
catch (err) {
|
|
85
|
+
console.error(err);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
async getMedia(constraints) {
|
|
89
|
+
constraints.video.width = { min: 1080, ideal: 1600, max: 1920 };
|
|
90
|
+
try {
|
|
91
|
+
await this.updateStream(constraints);
|
|
92
|
+
const devices = await navigator.mediaDevices.enumerateDevices();
|
|
93
|
+
this.devices = devices.filter((device) => device.kind.toLowerCase() === 'videoinput' && device.label.toLowerCase().includes('back'));
|
|
94
|
+
if (this.devices.length === 0) {
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
if (this.devices.length === 1) {
|
|
98
|
+
constraints.video.deviceId = { exact: this.devices[0].deviceId };
|
|
99
|
+
await this.updateStream(constraints);
|
|
100
|
+
}
|
|
101
|
+
let backCameraIndex = this.devices.length - 1;
|
|
102
|
+
const cameraResolutions = this.devices.map(camera => {
|
|
103
|
+
const match = camera.label.match(/\b([0-9]+)MP?\b/i);
|
|
104
|
+
if (match != null) {
|
|
105
|
+
return parseInt(match[1], 10);
|
|
106
|
+
}
|
|
107
|
+
return NaN;
|
|
108
|
+
});
|
|
109
|
+
if (!cameraResolutions.some(cameraResolution => isNaN(cameraResolution))) {
|
|
110
|
+
backCameraIndex = cameraResolutions.lastIndexOf(Math.max(...cameraResolutions));
|
|
111
|
+
}
|
|
112
|
+
// back camera with the highest resolution
|
|
113
|
+
const backCamera = this.devices[backCameraIndex];
|
|
114
|
+
if (backCamera) {
|
|
115
|
+
constraints.video.deviceId = { exact: backCamera.deviceId };
|
|
116
|
+
await this.updateStream(constraints);
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
const settings = await this.getTrackSettings(constraints);
|
|
120
|
+
if (settings.focusDistance) {
|
|
121
|
+
const focusDistance = settings.focusDistance;
|
|
122
|
+
constraints.video.advanced = [{ focusMode: 'continuous', focusDistance: focusDistance }];
|
|
123
|
+
await this.updateStream(constraints);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
catch (err) {
|
|
127
|
+
console.error(err);
|
|
93
128
|
}
|
|
94
|
-
this.context.drawImage(this.video, sx, sy, sWidth, sHeight, 0, 0, sWidth, sHeight);
|
|
95
129
|
}
|
|
96
130
|
async updateVideoZoom() {
|
|
97
131
|
const [videoTrack] = this.stream.getVideoTracks();
|
|
@@ -111,64 +145,52 @@ export class TwintagQRScanner {
|
|
|
111
145
|
}
|
|
112
146
|
}
|
|
113
147
|
}
|
|
114
|
-
async
|
|
115
|
-
const selectedCamera = window.localStorage.getItem('twintag-scanner-camera');
|
|
148
|
+
async startStream() {
|
|
116
149
|
const constraints = {
|
|
117
150
|
audio: false,
|
|
118
|
-
video:
|
|
151
|
+
video: {
|
|
152
|
+
aspectRatio: { ideal: 1 },
|
|
153
|
+
facingMode: 'environment',
|
|
154
|
+
}
|
|
119
155
|
};
|
|
120
156
|
const supports = navigator.mediaDevices.getSupportedConstraints();
|
|
121
157
|
if (supports.zoom) {
|
|
122
158
|
constraints.video['zoom'] = true;
|
|
123
159
|
}
|
|
124
|
-
|
|
125
|
-
window.localStorage.setItem('twintag-scanner-camera', deviceId);
|
|
126
|
-
constraints.video['deviceId'] = {
|
|
127
|
-
exact: deviceId,
|
|
128
|
-
};
|
|
129
|
-
}
|
|
130
|
-
else if (selectedCamera && selectedCamera !== 'undefined') {
|
|
131
|
-
constraints.video['deviceId'] = {
|
|
132
|
-
exact: selectedCamera,
|
|
133
|
-
};
|
|
134
|
-
}
|
|
135
|
-
else {
|
|
136
|
-
constraints.video['facingMode'] = 'environment';
|
|
137
|
-
}
|
|
138
|
-
if (!this.stream) {
|
|
139
|
-
const stream = await navigator.mediaDevices.getUserMedia(constraints);
|
|
140
|
-
this.stream = stream;
|
|
141
|
-
}
|
|
142
|
-
else {
|
|
143
|
-
this.stream = this.stream;
|
|
144
|
-
}
|
|
145
|
-
this.streamIsActive.emit(true);
|
|
146
|
-
const allDevices = await navigator.mediaDevices.enumerateDevices();
|
|
147
|
-
this.devices = allDevices.filter((device) => device.kind === 'videoinput' && device.label.includes('back'));
|
|
160
|
+
await this.getMedia(constraints);
|
|
148
161
|
await this.updateVideoZoom();
|
|
149
|
-
this.video.srcObject = this.stream;
|
|
150
|
-
this.video.setAttribute("playsinline", 'true'); // required to tell iOS safari we don't want fullscreen
|
|
151
|
-
await this.video.play();
|
|
152
|
-
this.canvas.width = this.video.clientWidth;
|
|
153
|
-
this.canvas.height = this.video.clientHeight;
|
|
154
162
|
gsap.to(this.videoContainerRef, {
|
|
155
163
|
duration: 0.2,
|
|
156
164
|
opacity: 1,
|
|
157
165
|
onComplete: () => {
|
|
166
|
+
this.streamIsActive.emit(true);
|
|
167
|
+
const stage = Math.min(this.video.videoWidth, this.video.videoHeight);
|
|
168
|
+
this.canvas.width = stage;
|
|
169
|
+
this.canvas.height = stage;
|
|
158
170
|
this.readBarcodeFromCanvas();
|
|
159
171
|
}
|
|
160
172
|
});
|
|
161
173
|
}
|
|
162
174
|
;
|
|
175
|
+
stopStream() {
|
|
176
|
+
worker.removeEventListener('message', this.messageHandler.bind(this));
|
|
177
|
+
gsap.to(this.videoContainerRef, {
|
|
178
|
+
duration: 0.2,
|
|
179
|
+
opacity: 0,
|
|
180
|
+
onComplete: () => {
|
|
181
|
+
this.stream.getTracks().forEach((track) => {
|
|
182
|
+
return track.stop();
|
|
183
|
+
});
|
|
184
|
+
this.video.srcObject = null;
|
|
185
|
+
this.context.clearRect(0, 0, this.context.canvas.width, this.context.canvas.height);
|
|
186
|
+
},
|
|
187
|
+
});
|
|
188
|
+
}
|
|
163
189
|
disconnectedCallback() {
|
|
164
190
|
this.stopStream();
|
|
165
191
|
}
|
|
166
192
|
render() {
|
|
167
|
-
return (h(Host, null, h("div", { class: "video-container", ref: (el) => (this.videoContainerRef = el) }, h("div", { class: "video-corners" }), h("video", { crossOrigin: "Anonymous", autoplay: true, playsinline: true, ref: (el) => (this.video = el) })
|
|
168
|
-
this.stopStream();
|
|
169
|
-
this.updateVideoStream(e.target.value);
|
|
170
|
-
} }, h("option", { hidden: true, disabled: true }, "-- Select a camera --"), this.devices.map((deviceInfo, idx) => (h("option", { key: idx, value: deviceInfo.deviceId, selected: window.localStorage.getItem('twintag-scanner-camera') ===
|
|
171
|
-
deviceInfo.deviceId }, `${deviceInfo.label}`)))))) : null)));
|
|
193
|
+
return (h(Host, null, h("div", { class: "video-container", ref: (el) => (this.videoContainerRef = el) }, h("div", { class: "video-corners" }), h("video", { crossOrigin: "Anonymous", autoplay: true, playsinline: true, ref: (el) => (this.video = el) }))));
|
|
172
194
|
}
|
|
173
195
|
static get is() { return "twintag-qr-scanner"; }
|
|
174
196
|
static get encapsulation() { return "shadow"; }
|
|
@@ -244,7 +266,7 @@ export class TwintagQRScanner {
|
|
|
244
266
|
},
|
|
245
267
|
"attribute": "format",
|
|
246
268
|
"reflect": false,
|
|
247
|
-
"defaultValue": "''"
|
|
269
|
+
"defaultValue": "'QRCode'"
|
|
248
270
|
},
|
|
249
271
|
"crop": {
|
|
250
272
|
"type": "number",
|
|
@@ -320,6 +342,21 @@ export class TwintagQRScanner {
|
|
|
320
342
|
"resolved": "boolean",
|
|
321
343
|
"references": {}
|
|
322
344
|
}
|
|
345
|
+
}, {
|
|
346
|
+
"method": "frame",
|
|
347
|
+
"name": "frame",
|
|
348
|
+
"bubbles": true,
|
|
349
|
+
"cancelable": true,
|
|
350
|
+
"composed": true,
|
|
351
|
+
"docs": {
|
|
352
|
+
"tags": [],
|
|
353
|
+
"text": ""
|
|
354
|
+
},
|
|
355
|
+
"complexType": {
|
|
356
|
+
"original": "string",
|
|
357
|
+
"resolved": "string",
|
|
358
|
+
"references": {}
|
|
359
|
+
}
|
|
323
360
|
}];
|
|
324
361
|
}
|
|
325
362
|
}
|
|
@@ -2,12 +2,14 @@
|
|
|
2
2
|
--twintag-scanner-bg-select-cameras: white;
|
|
3
3
|
--twintag-scanner-text-select-cameras: black;
|
|
4
4
|
--twintag-scanner-radius-select-cameras: 6px 0 0 0;
|
|
5
|
-
--twintag-scanner-min-height: min-content;
|
|
6
5
|
--twintag-scanner-corners-color: white;
|
|
7
6
|
--twintag-scanner-corners-width: 4px;
|
|
8
7
|
--twintag-scanner-corners-length: 36px;
|
|
9
8
|
--twintag-scanner-corners-offset: 24px;
|
|
9
|
+
display: grid;
|
|
10
|
+
place-items: center;
|
|
10
11
|
position: relative;
|
|
12
|
+
width: 100%;
|
|
11
13
|
}
|
|
12
14
|
:host .icon-container {
|
|
13
15
|
display: grid;
|
|
@@ -22,10 +24,12 @@
|
|
|
22
24
|
}
|
|
23
25
|
:host .video-container {
|
|
24
26
|
--crop: 0;
|
|
25
|
-
display:
|
|
26
|
-
|
|
27
|
+
display: flex;
|
|
28
|
+
align-items: center;
|
|
29
|
+
justify-content: center;
|
|
27
30
|
position: relative;
|
|
28
31
|
width: 100%;
|
|
32
|
+
height: 100%;
|
|
29
33
|
opacity: 0;
|
|
30
34
|
overflow: hidden;
|
|
31
35
|
z-index: 1;
|
|
@@ -57,53 +61,10 @@
|
|
|
57
61
|
z-index: 2222;
|
|
58
62
|
}
|
|
59
63
|
:host .video-container video {
|
|
60
|
-
position: absolute
|
|
61
|
-
top: 0 !important;
|
|
62
|
-
right: 0 !important;
|
|
63
|
-
bottom: 0 !important;
|
|
64
|
-
left: 0 !important;
|
|
64
|
+
position: absolute;
|
|
65
65
|
width: 100% !important;
|
|
66
|
-
height: 100% !important;
|
|
67
66
|
object-fit: cover;
|
|
68
67
|
object-position: center;
|
|
69
68
|
aspect-ratio: 1/1 !important;
|
|
70
|
-
min-height: var(--twintag-scanner-min-height);
|
|
71
|
-
}
|
|
72
|
-
:host .cameras-container {
|
|
73
|
-
position: absolute;
|
|
74
|
-
right: 0;
|
|
75
|
-
bottom: 0;
|
|
76
|
-
width: 100%;
|
|
77
|
-
min-width: 10ch;
|
|
78
|
-
max-width: max-content;
|
|
79
|
-
padding: 0rem 0.25rem;
|
|
80
|
-
border: none;
|
|
81
|
-
cursor: pointer;
|
|
82
|
-
border-radius: var(--twintag-scanner-radius-select-cameras);
|
|
83
|
-
background-color: var(--twintag-scanner-bg-select-cameras);
|
|
84
|
-
line-height: 1.1;
|
|
85
|
-
font-size: 1rem;
|
|
86
|
-
opacity: inherit;
|
|
87
|
-
z-index: 2222;
|
|
88
|
-
}
|
|
89
|
-
:host .cameras-container select {
|
|
90
|
-
display: grid;
|
|
91
|
-
place-items: center;
|
|
92
|
-
width: max-content;
|
|
93
|
-
margin: 0;
|
|
94
|
-
padding: 0.75rem;
|
|
95
|
-
border: none;
|
|
96
|
-
background-color: transparent;
|
|
97
|
-
appearance: none;
|
|
98
|
-
--webkit-appearance: none;
|
|
99
|
-
-moz-appearance: none;
|
|
100
|
-
color: var(--twintag-scanner-text-select-cameras);
|
|
101
|
-
cursor: inherit;
|
|
102
|
-
font-family: inherit;
|
|
103
|
-
font-size: inherit;
|
|
104
|
-
line-height: inherit;
|
|
105
|
-
outline: none;
|
|
106
|
-
}
|
|
107
|
-
:host .cameras-container select::-ms-expand {
|
|
108
|
-
display: none;
|
|
69
|
+
min-height: var(--twintag-scanner-min-height, 100%);
|
|
109
70
|
}
|