@jaak.ai/stamps 2.1.0-dev.1 → 2.1.0-dev.2
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/README.md +11 -1
- package/dist/cjs/jaak-stamps-webcomponent.cjs.js +1 -1
- package/dist/cjs/jaak-stamps.cjs.entry.js +219 -18
- package/dist/cjs/jaak-stamps.cjs.entry.js.map +1 -1
- package/dist/cjs/jaak-stamps.entry.cjs.js.map +1 -1
- package/dist/cjs/loader.cjs.js +1 -1
- package/dist/collection/components/my-component/my-component.css +164 -9
- package/dist/collection/components/my-component/my-component.js +223 -16
- package/dist/collection/components/my-component/my-component.js.map +1 -1
- package/dist/collection/services/DetectionService.js +16 -1
- package/dist/collection/services/DetectionService.js.map +1 -1
- package/dist/collection/services/ServiceContainer.js +3 -2
- package/dist/collection/services/ServiceContainer.js.map +1 -1
- package/dist/components/jaak-stamps.js +221 -18
- package/dist/components/jaak-stamps.js.map +1 -1
- package/dist/esm/jaak-stamps-webcomponent.js +1 -1
- package/dist/esm/jaak-stamps.entry.js +219 -18
- package/dist/esm/jaak-stamps.entry.js.map +1 -1
- package/dist/esm/loader.js +1 -1
- package/dist/jaak-stamps-webcomponent/jaak-stamps-webcomponent.esm.js +1 -1
- package/dist/jaak-stamps-webcomponent/jaak-stamps.entry.esm.js.map +1 -1
- package/dist/jaak-stamps-webcomponent/p-41e88688.entry.js +2 -0
- package/dist/jaak-stamps-webcomponent/p-41e88688.entry.js.map +1 -0
- package/dist/types/components/my-component/my-component.d.ts +6 -0
- package/dist/types/components.d.ts +8 -0
- package/dist/types/services/DetectionService.d.ts +2 -1
- package/dist/types/services/ServiceContainer.d.ts +1 -0
- package/package.json +1 -1
- package/dist/jaak-stamps-webcomponent/p-39560f23.entry.js +0 -2
- package/dist/jaak-stamps-webcomponent/p-39560f23.entry.js.map +0 -1
package/dist/cjs/loader.cjs.js
CHANGED
|
@@ -5,7 +5,7 @@ var index = require('./index-BfhtOB0D.js');
|
|
|
5
5
|
const defineCustomElements = async (win, options) => {
|
|
6
6
|
if (typeof window === 'undefined') return undefined;
|
|
7
7
|
await index.globalScripts();
|
|
8
|
-
return index.bootstrapLazy([["jaak-stamps.cjs",[[1,"jaak-stamps",{"debug":[4],"alignmentTolerance":[2,"alignment-tolerance"],"maskSize":[2,"mask-size"],"cropMargin":[2,"crop-margin"],"useDocumentClassification":[4,"use-document-classification"],"preferredCamera":[1,"preferred-camera"],"captureDelay":[2,"capture-delay"],"enableBackDocumentTimer":[4,"enable-back-document-timer"],"backDocumentTimerDuration":[2,"back-document-timer-duration"],"detectionBoxes":[32],"sideAlignment":[32],"isMaskReady":[32],"shouldMirrorVideo":[32],"showCameraSelector":[32],"isSwitchingCamera":[32],"hasDocumentDetected":[32],"cameraInfoWithAutofocus":[32],"currentStatus":[32],"performanceData":[32],"backDocumentTimerRemaining":[32],"getCapturedImages":[64],"isProcessCompleted":[64],"startCapture":[64],"stopCapture":[64],"resetCapture":[64],"skipBackCapture":[64],"getStatus":[64],"preloadModel":[64],"getCameraInfo":[64],"setPreferredCamera":[64],"setCaptureDelay":[64],"getCaptureDelay":[64]}]]]], options);
|
|
8
|
+
return index.bootstrapLazy([["jaak-stamps.cjs",[[1,"jaak-stamps",{"debug":[4],"alignmentTolerance":[2,"alignment-tolerance"],"maskSize":[2,"mask-size"],"cropMargin":[2,"crop-margin"],"useDocumentClassification":[4,"use-document-classification"],"useDocumentDetector":[4,"use-document-detector"],"preferredCamera":[1,"preferred-camera"],"captureDelay":[2,"capture-delay"],"enableBackDocumentTimer":[4,"enable-back-document-timer"],"backDocumentTimerDuration":[2,"back-document-timer-duration"],"detectionBoxes":[32],"sideAlignment":[32],"isMaskReady":[32],"shouldMirrorVideo":[32],"showCameraSelector":[32],"isSwitchingCamera":[32],"hasDocumentDetected":[32],"cameraInfoWithAutofocus":[32],"currentStatus":[32],"performanceData":[32],"backDocumentTimerRemaining":[32],"showManualCaptureButton":[32],"getCapturedImages":[64],"isProcessCompleted":[64],"startCapture":[64],"stopCapture":[64],"resetCapture":[64],"skipBackCapture":[64],"getStatus":[64],"preloadModel":[64],"getCameraInfo":[64],"setPreferredCamera":[64],"setCaptureDelay":[64],"getCaptureDelay":[64]}]]]], options);
|
|
9
9
|
};
|
|
10
10
|
|
|
11
11
|
exports.setNonce = index.setNonce;
|
|
@@ -170,18 +170,21 @@ video {
|
|
|
170
170
|
top: 50%;
|
|
171
171
|
left: 50%;
|
|
172
172
|
transform: translate(-50%, -50%);
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
173
|
+
background: rgba(0, 0, 0, 0.25);
|
|
174
|
+
backdrop-filter: blur(20px);
|
|
175
|
+
border-radius: 12px;
|
|
176
|
+
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
177
|
+
color: #ffffff;
|
|
178
|
+
font-size: 12px;
|
|
179
|
+
font-weight: 500;
|
|
176
180
|
text-align: center;
|
|
177
181
|
white-space: normal;
|
|
178
|
-
|
|
179
|
-
backdrop-filter: blur(12px);
|
|
180
|
-
padding: 12px 20px;
|
|
181
|
-
border-radius: 8px;
|
|
182
|
+
padding: 13px;
|
|
182
183
|
max-width: 300px;
|
|
183
184
|
z-index: 20;
|
|
184
|
-
|
|
185
|
+
height: fit-content;
|
|
186
|
+
width: fit-content;
|
|
187
|
+
animation: slideInFromTopCentered 0.3s ease-out;
|
|
185
188
|
}
|
|
186
189
|
|
|
187
190
|
/* Quality indicator */
|
|
@@ -523,6 +526,81 @@ video {
|
|
|
523
526
|
max-width: 300px;
|
|
524
527
|
}
|
|
525
528
|
|
|
529
|
+
/* Sección unificada para captura del reverso */
|
|
530
|
+
.back-capture-section {
|
|
531
|
+
position: absolute;
|
|
532
|
+
bottom: 20px;
|
|
533
|
+
left: 50%;
|
|
534
|
+
transform: translateX(-50%);
|
|
535
|
+
z-index: 25;
|
|
536
|
+
display: flex;
|
|
537
|
+
flex-direction: column;
|
|
538
|
+
align-items: center;
|
|
539
|
+
width: 100%;
|
|
540
|
+
max-width: 320px;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
|
|
544
|
+
.back-capture-buttons {
|
|
545
|
+
display: flex;
|
|
546
|
+
gap: 12px;
|
|
547
|
+
align-items: center;
|
|
548
|
+
justify-content: center;
|
|
549
|
+
flex-wrap: wrap;
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
.capture-button {
|
|
553
|
+
pointer-events: auto;
|
|
554
|
+
background: #fff;
|
|
555
|
+
color: #333;
|
|
556
|
+
border: none;
|
|
557
|
+
border-radius: 25px;
|
|
558
|
+
padding: 12px 24px;
|
|
559
|
+
font-size: 14px;
|
|
560
|
+
font-weight: 600;
|
|
561
|
+
cursor: pointer;
|
|
562
|
+
transition: all 0.3s ease;
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
.capture-button:hover {
|
|
566
|
+
background: #f8f9fa;
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
.capture-button:active {
|
|
570
|
+
background: #e9ecef;
|
|
571
|
+
transform: translateY(1px);
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
.capture-button:disabled {
|
|
575
|
+
background: #e9ecef;
|
|
576
|
+
color: #6c757d;
|
|
577
|
+
cursor: not-allowed;
|
|
578
|
+
transform: none;
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
/* Responsive para móviles */
|
|
582
|
+
@media (max-width: 480px) {
|
|
583
|
+
.back-capture-section {
|
|
584
|
+
bottom: 16px;
|
|
585
|
+
max-width: 300px;
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
|
|
589
|
+
.back-capture-buttons {
|
|
590
|
+
flex-direction: column;
|
|
591
|
+
gap: 8px;
|
|
592
|
+
width: 100%;
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
.capture-button,
|
|
596
|
+
.back-capture-buttons .skip-button {
|
|
597
|
+
width: 100%;
|
|
598
|
+
max-width: 200px;
|
|
599
|
+
padding: 10px 20px;
|
|
600
|
+
font-size: 13px;
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
|
|
526
604
|
.skip-explanation {
|
|
527
605
|
background: rgba(0, 0, 0, 0.5);
|
|
528
606
|
color: #ffffff;
|
|
@@ -575,6 +653,7 @@ video {
|
|
|
575
653
|
display: flex;
|
|
576
654
|
gap: 8px;
|
|
577
655
|
pointer-events: auto;
|
|
656
|
+
animation: slideInFromTop 0.3s ease-out;
|
|
578
657
|
}
|
|
579
658
|
|
|
580
659
|
|
|
@@ -665,6 +744,18 @@ video {
|
|
|
665
744
|
}
|
|
666
745
|
}
|
|
667
746
|
|
|
747
|
+
/* Animación específica para elementos centrados */
|
|
748
|
+
@keyframes slideInFromTopCentered {
|
|
749
|
+
0% {
|
|
750
|
+
opacity: 0;
|
|
751
|
+
transform: translate(-50%, -50%) translateY(-8px) scale(0.95);
|
|
752
|
+
}
|
|
753
|
+
100% {
|
|
754
|
+
opacity: 1;
|
|
755
|
+
transform: translate(-50%, -50%) translateY(0) scale(1);
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
|
|
668
759
|
.camera-selector-header {
|
|
669
760
|
display: flex;
|
|
670
761
|
justify-content: space-between;
|
|
@@ -903,6 +994,12 @@ video {
|
|
|
903
994
|
.status-description {
|
|
904
995
|
font-size: 9px;
|
|
905
996
|
}
|
|
997
|
+
|
|
998
|
+
.guide-text {
|
|
999
|
+
font-size: 11px;
|
|
1000
|
+
padding: 4px 8px;
|
|
1001
|
+
border-radius: 8px;
|
|
1002
|
+
}
|
|
906
1003
|
}
|
|
907
1004
|
|
|
908
1005
|
/* Animación de carga mejorada */
|
|
@@ -968,7 +1065,7 @@ video {
|
|
|
968
1065
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
969
1066
|
z-index: 35;
|
|
970
1067
|
width: fit-content;
|
|
971
|
-
animation:
|
|
1068
|
+
animation: slideInFromTop 0.3s ease-out;
|
|
972
1069
|
transition: all 0.3s ease;
|
|
973
1070
|
}
|
|
974
1071
|
|
|
@@ -1127,3 +1224,61 @@ video {
|
|
|
1127
1224
|
font-size: 10px;
|
|
1128
1225
|
}
|
|
1129
1226
|
}
|
|
1227
|
+
|
|
1228
|
+
/* Sección de captura manual */
|
|
1229
|
+
.manual-capture-section {
|
|
1230
|
+
position: absolute;
|
|
1231
|
+
bottom: 20px;
|
|
1232
|
+
left: 50%;
|
|
1233
|
+
transform: translateX(-50%);
|
|
1234
|
+
z-index: 25;
|
|
1235
|
+
display: flex;
|
|
1236
|
+
flex-direction: column;
|
|
1237
|
+
align-items: center;
|
|
1238
|
+
width: 100%;
|
|
1239
|
+
max-width: 300px;
|
|
1240
|
+
}
|
|
1241
|
+
|
|
1242
|
+
|
|
1243
|
+
.manual-capture-button {
|
|
1244
|
+
pointer-events: auto;
|
|
1245
|
+
background: #fff;
|
|
1246
|
+
color: #333;
|
|
1247
|
+
border: none;
|
|
1248
|
+
border-radius: 25px;
|
|
1249
|
+
padding: 12px 24px;
|
|
1250
|
+
font-size: 14px;
|
|
1251
|
+
font-weight: 600;
|
|
1252
|
+
cursor: pointer;
|
|
1253
|
+
transition: all 0.3s ease;
|
|
1254
|
+
}
|
|
1255
|
+
|
|
1256
|
+
.manual-capture-button:hover {
|
|
1257
|
+
background: #f8f9fa;
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1260
|
+
.manual-capture-button:active {
|
|
1261
|
+
background: #e9ecef;
|
|
1262
|
+
transform: translateY(1px);
|
|
1263
|
+
}
|
|
1264
|
+
|
|
1265
|
+
.manual-capture-button:disabled {
|
|
1266
|
+
background: #e9ecef;
|
|
1267
|
+
color: #6c757d;
|
|
1268
|
+
cursor: not-allowed;
|
|
1269
|
+
transform: none;
|
|
1270
|
+
}
|
|
1271
|
+
|
|
1272
|
+
/* Responsive para móviles */
|
|
1273
|
+
@media (max-width: 480px) {
|
|
1274
|
+
.manual-capture-section {
|
|
1275
|
+
bottom: 16px;
|
|
1276
|
+
max-width: 280px;
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1279
|
+
|
|
1280
|
+
.manual-capture-button {
|
|
1281
|
+
padding: 10px 20px;
|
|
1282
|
+
font-size: 13px;
|
|
1283
|
+
}
|
|
1284
|
+
}
|
|
@@ -7,6 +7,7 @@ export class JaakStamps {
|
|
|
7
7
|
maskSize = 90;
|
|
8
8
|
cropMargin = 20;
|
|
9
9
|
useDocumentClassification = false;
|
|
10
|
+
useDocumentDetector = true;
|
|
10
11
|
preferredCamera = 'auto';
|
|
11
12
|
captureDelay = 1500;
|
|
12
13
|
enableBackDocumentTimer = false;
|
|
@@ -47,6 +48,7 @@ export class JaakStamps {
|
|
|
47
48
|
detectionRate: 0
|
|
48
49
|
};
|
|
49
50
|
backDocumentTimerRemaining = 0;
|
|
51
|
+
showManualCaptureButton = false;
|
|
50
52
|
// Services
|
|
51
53
|
serviceContainer;
|
|
52
54
|
eventBus;
|
|
@@ -110,6 +112,7 @@ export class JaakStamps {
|
|
|
110
112
|
maskSize: this.maskSize,
|
|
111
113
|
cropMargin: this.cropMargin,
|
|
112
114
|
useDocumentClassification: this.useDocumentClassification,
|
|
115
|
+
useDocumentDetector: this.useDocumentDetector,
|
|
113
116
|
preferredCamera: this.preferredCamera,
|
|
114
117
|
captureDelay: this.captureDelay
|
|
115
118
|
};
|
|
@@ -167,27 +170,39 @@ export class JaakStamps {
|
|
|
167
170
|
document.head.appendChild(script);
|
|
168
171
|
await new Promise((resolve) => {
|
|
169
172
|
script.onload = () => {
|
|
170
|
-
setTimeout(() => {
|
|
171
|
-
this.finalizeInitialization();
|
|
173
|
+
setTimeout(async () => {
|
|
174
|
+
await this.finalizeInitialization();
|
|
172
175
|
resolve(undefined);
|
|
173
176
|
}, 300);
|
|
174
177
|
};
|
|
175
178
|
});
|
|
176
179
|
}
|
|
177
180
|
else {
|
|
178
|
-
setTimeout(() => {
|
|
179
|
-
this.finalizeInitialization();
|
|
181
|
+
setTimeout(async () => {
|
|
182
|
+
await this.finalizeInitialization();
|
|
180
183
|
}, 300);
|
|
181
184
|
}
|
|
182
185
|
}
|
|
183
|
-
finalizeInitialization() {
|
|
186
|
+
async finalizeInitialization() {
|
|
184
187
|
this.stateManager.updateCaptureState({ isLoading: false });
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
188
|
+
// Check camera permissions before showing "ready" status
|
|
189
|
+
const hasPermissions = await this.checkCameraPermissions();
|
|
190
|
+
if (hasPermissions) {
|
|
191
|
+
this.currentStatus = {
|
|
192
|
+
message: 'Listo para capturar',
|
|
193
|
+
description: '',
|
|
194
|
+
type: 'ready',
|
|
195
|
+
isInitialized: true
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
else {
|
|
199
|
+
this.currentStatus = {
|
|
200
|
+
message: 'Permisos de cámara requeridos',
|
|
201
|
+
description: 'Por favor, otorgue permisos de cámara para continuar',
|
|
202
|
+
type: 'error',
|
|
203
|
+
isInitialized: true
|
|
204
|
+
};
|
|
205
|
+
}
|
|
191
206
|
this.emitReadyEvent();
|
|
192
207
|
}
|
|
193
208
|
updateStatus(message, description, type = 'loading') {
|
|
@@ -202,6 +217,26 @@ export class JaakStamps {
|
|
|
202
217
|
const isDocumentReady = !!window.ort && this.detectionService.isModelLoaded();
|
|
203
218
|
this.isReady.emit(isDocumentReady);
|
|
204
219
|
}
|
|
220
|
+
async checkCameraPermissions() {
|
|
221
|
+
try {
|
|
222
|
+
if (!navigator.permissions) {
|
|
223
|
+
// Fallback: try to access camera directly
|
|
224
|
+
try {
|
|
225
|
+
const stream = await navigator.mediaDevices.getUserMedia({ video: true });
|
|
226
|
+
stream.getTracks().forEach(track => track.stop());
|
|
227
|
+
return true;
|
|
228
|
+
}
|
|
229
|
+
catch {
|
|
230
|
+
return false;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
const permission = await navigator.permissions.query({ name: 'camera' });
|
|
234
|
+
return permission.state === 'granted';
|
|
235
|
+
}
|
|
236
|
+
catch (error) {
|
|
237
|
+
return false;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
205
240
|
handleStateChange(data) {
|
|
206
241
|
// React to state changes from StateManager
|
|
207
242
|
// This is where the component updates its internal state based on service state changes
|
|
@@ -225,6 +260,29 @@ export class JaakStamps {
|
|
|
225
260
|
this.updateMaskDimensions(rect);
|
|
226
261
|
}
|
|
227
262
|
}
|
|
263
|
+
getGuideText(step) {
|
|
264
|
+
if (step === 'completed') {
|
|
265
|
+
return 'Proceso completado';
|
|
266
|
+
}
|
|
267
|
+
if (step === 'front') {
|
|
268
|
+
// Para el frente, si el detector manual está activo, mostrar instrucciones de captura manual
|
|
269
|
+
if (this.showManualCaptureButton) {
|
|
270
|
+
return 'Posicione el frente de su documento en el marco y presione el botón para capturar';
|
|
271
|
+
}
|
|
272
|
+
// Instrucción básica de alineación
|
|
273
|
+
return 'Alinee su identificación con el marco';
|
|
274
|
+
}
|
|
275
|
+
else if (step === 'back') {
|
|
276
|
+
// Para el reverso, mostrar instrucciones específicas según el modo
|
|
277
|
+
if (this.useDocumentDetector) {
|
|
278
|
+
return 'Si tu documento no tiene lado trasero, da clic en el botón para continuar con el proceso';
|
|
279
|
+
}
|
|
280
|
+
else {
|
|
281
|
+
return 'Posicione el reverso de su documento en el marco y capture, o salte este paso';
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
return 'Alinee su identificación con el marco';
|
|
285
|
+
}
|
|
228
286
|
updateMaskDimensions(containerRect) {
|
|
229
287
|
if (!this.videoRef)
|
|
230
288
|
return;
|
|
@@ -572,7 +630,12 @@ export class JaakStamps {
|
|
|
572
630
|
this.updateStatus('Ajustando cámara...', 'Configurando calidad de imagen', 'loading');
|
|
573
631
|
const stream = await this.cameraService.setupCamera();
|
|
574
632
|
// Paso 4: Inicializar video y ocultar estado inmediatamente
|
|
575
|
-
this.
|
|
633
|
+
if (this.useDocumentDetector) {
|
|
634
|
+
this.updateStatus('Captura activa', 'Buscando documento en el marco de captura', 'active');
|
|
635
|
+
}
|
|
636
|
+
else {
|
|
637
|
+
this.updateStatus('Captura activa', 'Posicione su documento y use el botón para capturar', 'active');
|
|
638
|
+
}
|
|
576
639
|
await this.initializeVideoStream(stream);
|
|
577
640
|
// Paso 5: Calibrar máscara
|
|
578
641
|
if (this.detectionContainer) {
|
|
@@ -583,6 +646,10 @@ export class JaakStamps {
|
|
|
583
646
|
// Finalizar e iniciar captura
|
|
584
647
|
this.startTime = Date.now();
|
|
585
648
|
this.stateManager.updateCaptureState({ isLoading: false });
|
|
649
|
+
// Mostrar botón manual si el detector está deshabilitado
|
|
650
|
+
if (!this.useDocumentDetector) {
|
|
651
|
+
this.showManualCaptureButton = true;
|
|
652
|
+
}
|
|
586
653
|
this.detectFrame();
|
|
587
654
|
}
|
|
588
655
|
catch (err) {
|
|
@@ -614,6 +681,18 @@ export class JaakStamps {
|
|
|
614
681
|
const captureState = this.stateManager.getCaptureState();
|
|
615
682
|
if (!this.videoRef || !this.detectionContainer || !this.detectionService.isModelLoaded())
|
|
616
683
|
return;
|
|
684
|
+
// Show manual capture button when document detector is disabled
|
|
685
|
+
if (!this.useDocumentDetector) {
|
|
686
|
+
this.showManualCaptureButton = true;
|
|
687
|
+
// Continue the loop for UI updates but skip detection logic
|
|
688
|
+
if (captureState.step !== 'completed') {
|
|
689
|
+
this.animationId = requestAnimationFrame(() => this.detectFrame());
|
|
690
|
+
}
|
|
691
|
+
return;
|
|
692
|
+
}
|
|
693
|
+
else {
|
|
694
|
+
this.showManualCaptureButton = false;
|
|
695
|
+
}
|
|
617
696
|
if (captureState.isDetectionPaused) {
|
|
618
697
|
if (captureState.step !== 'completed') {
|
|
619
698
|
this.animationId = requestAnimationFrame(() => this.detectFrame());
|
|
@@ -742,7 +821,7 @@ export class JaakStamps {
|
|
|
742
821
|
isCapturing: false
|
|
743
822
|
};
|
|
744
823
|
const cameraInfo = this.cameraInfoWithAutofocus;
|
|
745
|
-
return (h("div", { key: '
|
|
824
|
+
return (h("div", { key: 'e0b32b1b41b58e3a58b552199384080e8f2c069b', class: "detector-container" }, h("div", { key: '03c77af311ff12d936994d629d58550cbaae190c', class: "video-container" }, h("video", { key: '94f15b34f34367a10d17816a31cd147adb57bc90', ref: el => this.videoRef = el, autoplay: true, muted: true, playsinline: true, class: this.shouldMirrorVideo ? 'mirror' : '', style: { display: captureState.isVideoActive ? 'block' : 'none' } }), h("div", { key: '982be192792dfaf644eb173bfee9b26ebd229288', ref: el => this.detectionContainer = el, class: `detection-overlay ${this.shouldMirrorVideo ? 'mirror' : ''}` }, this.debug && this.detectionBoxes.map((box, index) => (h("div", { key: index, class: "detection-box", style: {
|
|
746
825
|
position: 'absolute',
|
|
747
826
|
left: `${box.x}px`,
|
|
748
827
|
top: `${box.y}px`,
|
|
@@ -751,9 +830,9 @@ export class JaakStamps {
|
|
|
751
830
|
border: '2px solid #32406C',
|
|
752
831
|
pointerEvents: 'none',
|
|
753
832
|
boxSizing: 'border-box'
|
|
754
|
-
} })))), this.isMaskReady && (h("div", { key: '
|
|
833
|
+
} })))), this.isMaskReady && (h("div", { key: 'f5212ff70db65d2b836a3ac456a1701701630660', class: "overlay-mask" }, h("div", { key: '5080217f4962c2c7152def39cc978ba1c40a3a1c', class: "card-outline" }, h("div", { key: 'd37f1ca8723d79b10d1c6a3afaf54e2f2657ae79', class: "side side-top" }), h("div", { key: 'c5bb4f7d646a991f43598688ffa909c04697bb06', class: "side side-right" }), h("div", { key: '4a5040b4a236367dcdf72f4e72d497b8b6dee6c0', class: "side side-bottom" }), h("div", { key: 'c74ecef56eee8e92cd9491d693fa89a6c01bdbbd', class: "side side-left" })), !captureState.showFlipAnimation && !captureState.showSuccessAnimation && (h("div", { key: '2a2932b0c03835193ebf86ddb5f13173b1dba367', class: "guide-text" }, this.getGuideText(captureState.step))), captureState.step === 'back' && !captureState.showFlipAnimation && !captureState.showSuccessAnimation && (h("div", { key: '41fcf2d79106d3ed928fc2842cb6a6725ba4d4f6', class: "back-capture-section" }, h("div", { key: '98dbe1bbe454bad42c3e155247f25a9151aaf1f9', class: "back-capture-buttons" }, !this.useDocumentDetector && (h("button", { key: '01693bc0d6915a4d02f318268020dfbcf3a70b94', class: "capture-button primary-button", onClick: () => this.takeManualScreenshot(), type: "button", disabled: captureState.isCapturing }, captureState.isCapturing ? 'Capturando...' : 'Capturar Reverso')), h("button", { key: '0a4ba55cbbea867b936a83a16a622a3c36a225f9', class: "skip-button", onClick: () => this.skipBackCapture(), type: "button" }, this.enableBackDocumentTimer && this.backDocumentTimerRemaining > 0
|
|
755
834
|
? `Saltar reverso (${this.backDocumentTimerRemaining}s)`
|
|
756
|
-
: 'Saltar reverso'))), captureState.isVideoActive && (h("div", { key: '
|
|
835
|
+
: 'Saltar reverso')))), captureState.isVideoActive && (h("div", { key: '1db208a53dea37211bdd88254e575142b7abf91f', class: "camera-controls" }, h("button", { key: '90755839358f997c3ce2f4c36a8d17d39ea5f8ad', class: `camera-selector-button ${this.isSwitchingCamera ? 'loading' : ''}`, onClick: () => this.toggleCameraSelector(), type: "button", title: "Seleccionar c\u00E1mara", disabled: this.isSwitchingCamera }, this.isSwitchingCamera ? (h("div", { class: "button-spinner" })) : ('Cámaras')))), this.showCameraSelector && cameraInfo.availableCameras.length > 0 && (h("div", { key: 'ef7889ab82f9eaabcabec3e3dc3ebb7850818dd4', class: "camera-selector-dropdown" }, h("div", { key: 'e28fb005316a742318be3cf8582f520392b71bba', class: "camera-selector-header" }, h("span", { key: '97338030b1aedb50c5f5a588ea03c0f19bcb0ddd' }, "Seleccionar C\u00E1mara"), h("button", { key: '1537c907dfab17c232a256f9fc9210cbd0125cc6', class: "close-selector", onClick: () => this.toggleCameraSelector(), type: "button" }, "\u00D7")), h("div", { key: '509178119e6eed5fe0a6ea287904a53bd9fbecbc', class: "camera-list" }, cameraInfo.availableCameras.map((camera) => (h("button", { key: camera.id, class: `camera-option ${cameraInfo.selectedCameraId === camera.id ? 'selected' : ''}`, onClick: () => this.handleCameraSwitch(camera.id), type: "button" }, h("span", { class: "camera-label" }, camera.label || `Cámara ${cameraInfo.availableCameras.indexOf(camera) + 1}`, camera.hasAutofocus && (h("span", { class: "autofocus-icon", title: "Autofoco disponible" }, "\u29BF"))), cameraInfo.selectedCameraId === camera.id && (h("span", { class: "selected-indicator" }, "\u2713")))))), h("div", { key: '485f7e1a97c43ed280a106bde3c6ee910625025c', class: "device-info" }, h("small", { key: 'd76edba943f4e06c14024923496555fa1fe6d845' }, "Dispositivo: ", cameraInfo.deviceType)))), this.showManualCaptureButton && captureState.step === 'front' && !captureState.showFlipAnimation && !captureState.showSuccessAnimation && (h("div", { key: 'c5184dd93aa41cff61b88ea2046b481efc87c1f2', class: "manual-capture-section" }, h("button", { key: '6e45d9a0fec60469eb926ff353cb1a440d59f5fe', class: "manual-capture-button", onClick: () => this.takeManualScreenshot(), type: "button", disabled: captureState.isCapturing }, captureState.isCapturing ? 'Capturando...' : 'Capturar Frente'))))), captureState.isCapturing && (h("div", { key: '9e296a942ba51cc1fd152e76ae406620c84ed721', class: "capture-animation" })), captureState.showFlipAnimation && (h("div", { key: '517db50f7273788d94d5e6db6c40f65e00e18784', class: "flip-animation" }, h("div", { key: 'fbf6a26c4cc0539bd16b45899d19984af3449610', class: "id-card-icon" }), h("div", { key: 'fb3a201864d04531a078074458aa6e7f6580d565', class: "flip-text" }, "\u00A1Voltea tu identificaci\u00F3n!"))), captureState.showSuccessAnimation && (h("div", { key: '10c694e233d26523d538ff0d2edbf6dcfb8e37c8', class: "success-animation" }, h("div", { key: '87e545650825ab805e887667281ecdf8bdb34c45', class: "check-icon" }), h("div", { key: '359da71f391d68e9437b749b949ee90685b96b3f', class: "success-text" }, "\u00A1Proceso completado!"))), h("div", { key: '7c5533bb875eb3d9cdcc6cfdfc20e4d5bd98d103', class: `component-status status-${this.currentStatus.type}` }, (this.currentStatus.type === 'loading' || this.currentStatus.type === 'initializing') && (h("div", { key: 'f32df46ab83279387de606eadf53cd4a4009f449', class: "status-spinner" })), h("div", { key: 'dd640de5a3b64f5f99363318e2fc70e32ccbc7db', class: "status-content" }, h("div", { key: '618c0ab9cb0a24b0af3f6946052beab0d660fe6d', class: "status-message" }, this.currentStatus.message), this.currentStatus.description && (h("div", { key: 'e52c2a4945c7f5a190a446ac588aa1668c21f26d', class: "status-description" }, this.currentStatus.description)))), this.debug && (h("div", { key: '61cea10bc3438fb74aa176d71c4238b070cb0f09', class: "performance-monitor" }, h("div", { key: '44f6b1accb56d99a236b22c0769f870638d9c768', class: "performance-expanded" }, h("div", { key: '6febc85e319752b0aebc0a4e4050ea6d597375a7', class: "metrics-row" }, h("div", { key: '96b81895ad3cd742c96308ed53a2126af76eb619', class: "metric-compact" }, h("span", { key: '774a11c6fed6a87db7673b018b3cdc378c5252ef', class: "metric-label" }, "FPS"), h("span", { key: 'cb866dcbea661e5f715b82c4dd760f786fd5bb2a', class: `metric-value ${this.performanceData.fps < 15 ? 'warning' : this.performanceData.fps < 10 ? 'danger' : 'good'}` }, this.performanceData.fps)), h("div", { key: 'a935aaadb83ea400bfaa984d1d845bdfd87becc8', class: "metric-compact" }, h("span", { key: 'ffacaa2cb7838403a297145b70586e255cc845c7', class: "metric-label" }, "MEM"), h("span", { key: 'a1d7a7abec97dc0544d50b11b372f699f750b76f', class: `metric-value ${this.performanceData.memoryUsage > 100 ? 'warning' : this.performanceData.memoryUsage > 200 ? 'danger' : 'good'}` }, this.performanceData.memoryUsage, "MB"))), h("div", { key: 'bb3d156a46569a8c290bc518c42ec7957bc5b94e', class: "metrics-row" }, h("div", { key: '4cadd8f845e4c12120ed549e4a73e02c6cbd9257', class: "metric-compact" }, h("span", { key: '1fc5661222f495b79fd7eb126e04f52eecab6fb3', class: "metric-label" }, "INF"), h("span", { key: 'eca1f59d1c41ab99158eec81592bc7150bfeabe1', class: `metric-value ${this.performanceData.inferenceTime > 100 ? 'warning' : this.performanceData.inferenceTime > 200 ? 'danger' : 'good'}` }, this.performanceData.inferenceTime, "ms")), h("div", { key: 'f708e2757adcb03e633111d96bd6786fb6b80ea6', class: "metric-compact" }, h("span", { key: 'a13099ded64180996d6f65bf215483ba83daccbc', class: "metric-label" }, "FRAME"), h("span", { key: '2765cd1f7c1a49df9de64639cc1b777c79f89f4c', class: `metric-value ${this.performanceData.frameProcessingTime > 50 ? 'warning' : this.performanceData.frameProcessingTime > 100 ? 'danger' : 'good'}` }, this.performanceData.frameProcessingTime, "ms"))), h("div", { key: '13aaf85a1b4b66294c4204f02d956b64665aa766', class: "metrics-row" }, h("div", { key: '40012f3f646d70c535a606ebbe581a2fe6fd2e50', class: "metric-compact" }, h("span", { key: '03f8b17746fe404485820d06a50173678db2fd25', class: "metric-label" }, "DET"), h("span", { key: 'c4f87430f142355054d9361330d6a5d508974b70', class: "metric-value good" }, this.performanceData.successfulDetections, "/", this.performanceData.totalDetections)), h("div", { key: 'cf366a41d7f7cda6046c32cc7c514eb7b46b05d1', class: "metric-compact" }, h("span", { key: '925e7de15b96ba4dbbcdbf8a9f0e3173bae49573', class: "metric-label" }, "RATE"), h("span", { key: '9701e60c7793f2c205f3c9ffd254b6a115b8a68c', class: `metric-value ${this.performanceData.detectionRate < 30 ? 'danger' : this.performanceData.detectionRate < 60 ? 'warning' : 'good'}` }, this.performanceData.detectionRate, "%")))))), h("div", { key: '636aeb9b2b8dd992d8a28a8db44b213e12514096', class: "watermark" }, h("img", { key: '0407b550d962a6113e05ad75e25a1a1bbd76d9ce', src: "https://storage.googleapis.com/jaak-static/commons/powered-by-jaak.png", alt: "Powered by Jaak" })))));
|
|
757
836
|
}
|
|
758
837
|
// Utility methods
|
|
759
838
|
updateDetectionBoxes(boxes) {
|
|
@@ -868,6 +947,113 @@ export class JaakStamps {
|
|
|
868
947
|
}
|
|
869
948
|
}
|
|
870
949
|
}
|
|
950
|
+
async takeManualScreenshot() {
|
|
951
|
+
if (!this.videoRef)
|
|
952
|
+
return;
|
|
953
|
+
// When using manual capture, use mask coordinates for cropping
|
|
954
|
+
await this.takeScreenshotWithMaskCoordinates();
|
|
955
|
+
}
|
|
956
|
+
async takeScreenshotWithMaskCoordinates() {
|
|
957
|
+
if (!this.videoRef || !this.detectionContainer)
|
|
958
|
+
return;
|
|
959
|
+
this.stateManager.updateCaptureState({ isCapturing: true });
|
|
960
|
+
this.triggerCaptureAnimation();
|
|
961
|
+
// Use pooled canvas for optimization
|
|
962
|
+
const captureCanvas = this.getPooledCanvas(this.videoRef.videoWidth, this.videoRef.videoHeight);
|
|
963
|
+
const captureCtx = captureCanvas.getContext('2d', { alpha: false });
|
|
964
|
+
captureCtx.clearRect(0, 0, captureCanvas.width, captureCanvas.height);
|
|
965
|
+
captureCtx.drawImage(this.videoRef, 0, 0, captureCanvas.width, captureCanvas.height);
|
|
966
|
+
// Calculate mask coordinates for cropping
|
|
967
|
+
const container = this.detectionContainer.parentElement;
|
|
968
|
+
const containerRect = container.getBoundingClientRect();
|
|
969
|
+
// Get mask dimensions from CSS properties
|
|
970
|
+
const maskWidthPercent = parseFloat(this.el.style.getPropertyValue('--mask-width').replace('%', '')) || 90;
|
|
971
|
+
const maskHeightPercent = parseFloat(this.el.style.getPropertyValue('--mask-height').replace('%', '')) || 56.25;
|
|
972
|
+
const maskCenterXPercent = parseFloat(this.el.style.getPropertyValue('--mask-center-x').replace('%', '')) || 50;
|
|
973
|
+
const maskCenterYPercent = parseFloat(this.el.style.getPropertyValue('--mask-center-y').replace('%', '')) || 50;
|
|
974
|
+
// Convert percentages to actual video coordinates
|
|
975
|
+
const videoWidth = this.videoRef.videoWidth;
|
|
976
|
+
const videoHeight = this.videoRef.videoHeight;
|
|
977
|
+
const videoAspectRatio = videoWidth / videoHeight;
|
|
978
|
+
const containerAspectRatio = containerRect.width / containerRect.height;
|
|
979
|
+
let displayedVideoWidth, displayedVideoHeight;
|
|
980
|
+
let videoOffsetX = 0, videoOffsetY = 0;
|
|
981
|
+
if (videoAspectRatio > containerAspectRatio) {
|
|
982
|
+
displayedVideoWidth = containerRect.width;
|
|
983
|
+
displayedVideoHeight = containerRect.width / videoAspectRatio;
|
|
984
|
+
videoOffsetY = (containerRect.height - displayedVideoHeight) / 2;
|
|
985
|
+
}
|
|
986
|
+
else {
|
|
987
|
+
displayedVideoHeight = containerRect.height;
|
|
988
|
+
displayedVideoWidth = containerRect.height * videoAspectRatio;
|
|
989
|
+
videoOffsetX = (containerRect.width - displayedVideoWidth) / 2;
|
|
990
|
+
}
|
|
991
|
+
// Calculate mask coordinates in video space
|
|
992
|
+
const maskWidthInContainer = (maskWidthPercent / 100) * containerRect.width;
|
|
993
|
+
const maskHeightInContainer = (maskHeightPercent / 100) * containerRect.height;
|
|
994
|
+
const maskCenterXInContainer = (maskCenterXPercent / 100) * containerRect.width;
|
|
995
|
+
const maskCenterYInContainer = (maskCenterYPercent / 100) * containerRect.height;
|
|
996
|
+
// Convert to video coordinates
|
|
997
|
+
const scaleX = videoWidth / displayedVideoWidth;
|
|
998
|
+
const scaleY = videoHeight / displayedVideoHeight;
|
|
999
|
+
const maskCenterXInVideo = (maskCenterXInContainer - videoOffsetX) * scaleX;
|
|
1000
|
+
const maskCenterYInVideo = (maskCenterYInContainer - videoOffsetY) * scaleY;
|
|
1001
|
+
const maskWidthInVideo = maskWidthInContainer * scaleX;
|
|
1002
|
+
const maskHeightInVideo = maskHeightInContainer * scaleY;
|
|
1003
|
+
// Calculate crop coordinates
|
|
1004
|
+
const cropX = Math.max(0, maskCenterXInVideo - (maskWidthInVideo / 2) - this.cropMargin);
|
|
1005
|
+
const cropY = Math.max(0, maskCenterYInVideo - (maskHeightInVideo / 2) - this.cropMargin);
|
|
1006
|
+
const cropWidth = Math.min(maskWidthInVideo + (2 * this.cropMargin), videoWidth - cropX);
|
|
1007
|
+
const cropHeight = Math.min(maskHeightInVideo + (2 * this.cropMargin), videoHeight - cropY);
|
|
1008
|
+
// Use pooled canvas for cropped version
|
|
1009
|
+
const croppedCanvas = this.getPooledCanvas(cropWidth, cropHeight);
|
|
1010
|
+
const croppedCtx = croppedCanvas.getContext('2d', { alpha: false });
|
|
1011
|
+
croppedCtx.clearRect(0, 0, croppedCanvas.width, croppedCanvas.height);
|
|
1012
|
+
croppedCtx.drawImage(this.videoRef, cropX, cropY, cropWidth, cropHeight, 0, 0, cropWidth, cropHeight);
|
|
1013
|
+
const captureState = this.stateManager.getCaptureState();
|
|
1014
|
+
if (captureState.step === 'front') {
|
|
1015
|
+
this.stateManager.setCapturedImages({
|
|
1016
|
+
front: {
|
|
1017
|
+
fullFrame: captureCanvas.toDataURL('image/png'),
|
|
1018
|
+
cropped: croppedCanvas.toDataURL('image/png')
|
|
1019
|
+
}
|
|
1020
|
+
});
|
|
1021
|
+
// Check if document classification is enabled (independent of detector)
|
|
1022
|
+
if (this.useDocumentClassification) {
|
|
1023
|
+
const classification = await this.detectionService.classifyDocument(croppedCanvas);
|
|
1024
|
+
if (classification && classification.class === 'passport') {
|
|
1025
|
+
this.completeProcess(true);
|
|
1026
|
+
this.returnCanvasToPool(captureCanvas);
|
|
1027
|
+
this.returnCanvasToPool(croppedCanvas);
|
|
1028
|
+
return;
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
this.stateManager.updateCaptureState({
|
|
1032
|
+
step: 'back',
|
|
1033
|
+
isDetectionPaused: true,
|
|
1034
|
+
showFlipAnimation: true
|
|
1035
|
+
});
|
|
1036
|
+
setTimeout(() => {
|
|
1037
|
+
this.stateManager.updateCaptureState({
|
|
1038
|
+
showFlipAnimation: false,
|
|
1039
|
+
isDetectionPaused: false
|
|
1040
|
+
});
|
|
1041
|
+
this.startBackDocumentTimer();
|
|
1042
|
+
}, 3000);
|
|
1043
|
+
}
|
|
1044
|
+
else if (captureState.step === 'back') {
|
|
1045
|
+
this.stateManager.setCapturedImages({
|
|
1046
|
+
back: {
|
|
1047
|
+
fullFrame: captureCanvas.toDataURL('image/png'),
|
|
1048
|
+
cropped: croppedCanvas.toDataURL('image/png')
|
|
1049
|
+
}
|
|
1050
|
+
});
|
|
1051
|
+
this.completeProcess(false);
|
|
1052
|
+
}
|
|
1053
|
+
// Return canvases to pool after use
|
|
1054
|
+
this.returnCanvasToPool(captureCanvas);
|
|
1055
|
+
this.returnCanvasToPool(croppedCanvas);
|
|
1056
|
+
}
|
|
871
1057
|
async takeScreenshot() {
|
|
872
1058
|
if (!this.videoRef || !this.lastDetectedBox)
|
|
873
1059
|
return;
|
|
@@ -1301,6 +1487,26 @@ export class JaakStamps {
|
|
|
1301
1487
|
"reflect": false,
|
|
1302
1488
|
"defaultValue": "false"
|
|
1303
1489
|
},
|
|
1490
|
+
"useDocumentDetector": {
|
|
1491
|
+
"type": "boolean",
|
|
1492
|
+
"attribute": "use-document-detector",
|
|
1493
|
+
"mutable": false,
|
|
1494
|
+
"complexType": {
|
|
1495
|
+
"original": "boolean",
|
|
1496
|
+
"resolved": "boolean",
|
|
1497
|
+
"references": {}
|
|
1498
|
+
},
|
|
1499
|
+
"required": false,
|
|
1500
|
+
"optional": false,
|
|
1501
|
+
"docs": {
|
|
1502
|
+
"tags": [],
|
|
1503
|
+
"text": ""
|
|
1504
|
+
},
|
|
1505
|
+
"getter": false,
|
|
1506
|
+
"setter": false,
|
|
1507
|
+
"reflect": false,
|
|
1508
|
+
"defaultValue": "true"
|
|
1509
|
+
},
|
|
1304
1510
|
"preferredCamera": {
|
|
1305
1511
|
"type": "string",
|
|
1306
1512
|
"attribute": "preferred-camera",
|
|
@@ -1395,7 +1601,8 @@ export class JaakStamps {
|
|
|
1395
1601
|
"cameraInfoWithAutofocus": {},
|
|
1396
1602
|
"currentStatus": {},
|
|
1397
1603
|
"performanceData": {},
|
|
1398
|
-
"backDocumentTimerRemaining": {}
|
|
1604
|
+
"backDocumentTimerRemaining": {},
|
|
1605
|
+
"showManualCaptureButton": {}
|
|
1399
1606
|
};
|
|
1400
1607
|
}
|
|
1401
1608
|
static get events() {
|