@jaak.ai/stamps 2.1.0-dev.1 → 2.1.0-dev.3
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 +225 -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 +230 -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 +228 -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 +225 -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-4b2f9fb3.entry.js +2 -0
- package/dist/jaak-stamps-webcomponent/p-4b2f9fb3.entry.js.map +1 -0
- package/dist/types/components/my-component/my-component.d.ts +8 -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],"captureStateVersion":[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,8 @@ export class JaakStamps {
|
|
|
47
48
|
detectionRate: 0
|
|
48
49
|
};
|
|
49
50
|
backDocumentTimerRemaining = 0;
|
|
51
|
+
showManualCaptureButton = false;
|
|
52
|
+
captureStateVersion = 0; // Triggers re-render when StateManager changes
|
|
50
53
|
// Services
|
|
51
54
|
serviceContainer;
|
|
52
55
|
eventBus;
|
|
@@ -110,6 +113,7 @@ export class JaakStamps {
|
|
|
110
113
|
maskSize: this.maskSize,
|
|
111
114
|
cropMargin: this.cropMargin,
|
|
112
115
|
useDocumentClassification: this.useDocumentClassification,
|
|
116
|
+
useDocumentDetector: this.useDocumentDetector,
|
|
113
117
|
preferredCamera: this.preferredCamera,
|
|
114
118
|
captureDelay: this.captureDelay
|
|
115
119
|
};
|
|
@@ -167,27 +171,39 @@ export class JaakStamps {
|
|
|
167
171
|
document.head.appendChild(script);
|
|
168
172
|
await new Promise((resolve) => {
|
|
169
173
|
script.onload = () => {
|
|
170
|
-
setTimeout(() => {
|
|
171
|
-
this.finalizeInitialization();
|
|
174
|
+
setTimeout(async () => {
|
|
175
|
+
await this.finalizeInitialization();
|
|
172
176
|
resolve(undefined);
|
|
173
177
|
}, 300);
|
|
174
178
|
};
|
|
175
179
|
});
|
|
176
180
|
}
|
|
177
181
|
else {
|
|
178
|
-
setTimeout(() => {
|
|
179
|
-
this.finalizeInitialization();
|
|
182
|
+
setTimeout(async () => {
|
|
183
|
+
await this.finalizeInitialization();
|
|
180
184
|
}, 300);
|
|
181
185
|
}
|
|
182
186
|
}
|
|
183
|
-
finalizeInitialization() {
|
|
187
|
+
async finalizeInitialization() {
|
|
184
188
|
this.stateManager.updateCaptureState({ isLoading: false });
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
189
|
+
// Check camera permissions before showing "ready" status
|
|
190
|
+
const hasPermissions = await this.checkCameraPermissions();
|
|
191
|
+
if (hasPermissions) {
|
|
192
|
+
this.currentStatus = {
|
|
193
|
+
message: 'Listo para capturar',
|
|
194
|
+
description: '',
|
|
195
|
+
type: 'ready',
|
|
196
|
+
isInitialized: true
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
else {
|
|
200
|
+
this.currentStatus = {
|
|
201
|
+
message: 'Permisos de cámara requeridos',
|
|
202
|
+
description: 'Por favor, otorgue permisos de cámara para continuar',
|
|
203
|
+
type: 'error',
|
|
204
|
+
isInitialized: true
|
|
205
|
+
};
|
|
206
|
+
}
|
|
191
207
|
this.emitReadyEvent();
|
|
192
208
|
}
|
|
193
209
|
updateStatus(message, description, type = 'loading') {
|
|
@@ -202,6 +218,26 @@ export class JaakStamps {
|
|
|
202
218
|
const isDocumentReady = !!window.ort && this.detectionService.isModelLoaded();
|
|
203
219
|
this.isReady.emit(isDocumentReady);
|
|
204
220
|
}
|
|
221
|
+
async checkCameraPermissions() {
|
|
222
|
+
try {
|
|
223
|
+
if (!navigator.permissions) {
|
|
224
|
+
// Fallback: try to access camera directly
|
|
225
|
+
try {
|
|
226
|
+
const stream = await navigator.mediaDevices.getUserMedia({ video: true });
|
|
227
|
+
stream.getTracks().forEach(track => track.stop());
|
|
228
|
+
return true;
|
|
229
|
+
}
|
|
230
|
+
catch {
|
|
231
|
+
return false;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
const permission = await navigator.permissions.query({ name: 'camera' });
|
|
235
|
+
return permission.state === 'granted';
|
|
236
|
+
}
|
|
237
|
+
catch (error) {
|
|
238
|
+
return false;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
205
241
|
handleStateChange(data) {
|
|
206
242
|
// React to state changes from StateManager
|
|
207
243
|
// This is where the component updates its internal state based on service state changes
|
|
@@ -225,6 +261,32 @@ export class JaakStamps {
|
|
|
225
261
|
this.updateMaskDimensions(rect);
|
|
226
262
|
}
|
|
227
263
|
}
|
|
264
|
+
triggerRerender() {
|
|
265
|
+
this.captureStateVersion = this.captureStateVersion + 1;
|
|
266
|
+
}
|
|
267
|
+
getGuideText(step) {
|
|
268
|
+
if (step === 'completed') {
|
|
269
|
+
return 'Proceso completado';
|
|
270
|
+
}
|
|
271
|
+
if (step === 'front') {
|
|
272
|
+
// Para el frente, si el detector manual está activo, mostrar instrucciones de captura manual
|
|
273
|
+
if (this.showManualCaptureButton) {
|
|
274
|
+
return 'Posicione el frente de su documento en el marco y presione el botón para capturar';
|
|
275
|
+
}
|
|
276
|
+
// Instrucción básica de alineación
|
|
277
|
+
return 'Alinee su identificación con el marco';
|
|
278
|
+
}
|
|
279
|
+
else if (step === 'back') {
|
|
280
|
+
// Para el reverso, mostrar instrucciones específicas según el modo
|
|
281
|
+
if (this.useDocumentDetector) {
|
|
282
|
+
return 'Si tu documento no tiene lado trasero, da clic en el botón para continuar con el proceso';
|
|
283
|
+
}
|
|
284
|
+
else {
|
|
285
|
+
return 'Posicione el reverso de su documento en el marco y capture, o salte este paso';
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
return 'Alinee su identificación con el marco';
|
|
289
|
+
}
|
|
228
290
|
updateMaskDimensions(containerRect) {
|
|
229
291
|
if (!this.videoRef)
|
|
230
292
|
return;
|
|
@@ -572,7 +634,12 @@ export class JaakStamps {
|
|
|
572
634
|
this.updateStatus('Ajustando cámara...', 'Configurando calidad de imagen', 'loading');
|
|
573
635
|
const stream = await this.cameraService.setupCamera();
|
|
574
636
|
// Paso 4: Inicializar video y ocultar estado inmediatamente
|
|
575
|
-
this.
|
|
637
|
+
if (this.useDocumentDetector) {
|
|
638
|
+
this.updateStatus('Captura activa', 'Buscando documento en el marco de captura', 'active');
|
|
639
|
+
}
|
|
640
|
+
else {
|
|
641
|
+
this.updateStatus('Captura activa', 'Posicione su documento y use el botón para capturar', 'active');
|
|
642
|
+
}
|
|
576
643
|
await this.initializeVideoStream(stream);
|
|
577
644
|
// Paso 5: Calibrar máscara
|
|
578
645
|
if (this.detectionContainer) {
|
|
@@ -583,6 +650,10 @@ export class JaakStamps {
|
|
|
583
650
|
// Finalizar e iniciar captura
|
|
584
651
|
this.startTime = Date.now();
|
|
585
652
|
this.stateManager.updateCaptureState({ isLoading: false });
|
|
653
|
+
// Mostrar botón manual si el detector está deshabilitado
|
|
654
|
+
if (!this.useDocumentDetector) {
|
|
655
|
+
this.showManualCaptureButton = true;
|
|
656
|
+
}
|
|
586
657
|
this.detectFrame();
|
|
587
658
|
}
|
|
588
659
|
catch (err) {
|
|
@@ -614,6 +685,18 @@ export class JaakStamps {
|
|
|
614
685
|
const captureState = this.stateManager.getCaptureState();
|
|
615
686
|
if (!this.videoRef || !this.detectionContainer || !this.detectionService.isModelLoaded())
|
|
616
687
|
return;
|
|
688
|
+
// Show manual capture button when document detector is disabled
|
|
689
|
+
if (!this.useDocumentDetector) {
|
|
690
|
+
this.showManualCaptureButton = true;
|
|
691
|
+
// Continue the loop for UI updates but skip detection logic
|
|
692
|
+
if (captureState.step !== 'completed') {
|
|
693
|
+
this.animationId = requestAnimationFrame(() => this.detectFrame());
|
|
694
|
+
}
|
|
695
|
+
return;
|
|
696
|
+
}
|
|
697
|
+
else {
|
|
698
|
+
this.showManualCaptureButton = false;
|
|
699
|
+
}
|
|
617
700
|
if (captureState.isDetectionPaused) {
|
|
618
701
|
if (captureState.step !== 'completed') {
|
|
619
702
|
this.animationId = requestAnimationFrame(() => this.detectFrame());
|
|
@@ -742,7 +825,7 @@ export class JaakStamps {
|
|
|
742
825
|
isCapturing: false
|
|
743
826
|
};
|
|
744
827
|
const cameraInfo = this.cameraInfoWithAutofocus;
|
|
745
|
-
return (h("div", { key: '
|
|
828
|
+
return (h("div", { key: '3026fb421368ee82a1d2cefd8f9355ebf3194146', class: "detector-container" }, h("div", { key: '62b1230c3268412f4ff301d480d666065037d9b3', class: "video-container" }, h("video", { key: '170a0e7235522dec33b914b6d2841b36503c781f', ref: el => this.videoRef = el, autoplay: true, muted: true, playsinline: true, class: this.shouldMirrorVideo ? 'mirror' : '', style: { display: captureState.isVideoActive ? 'block' : 'none' } }), h("div", { key: '915af3649a7a551a46f1cb97af0a45916666cc12', 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
829
|
position: 'absolute',
|
|
747
830
|
left: `${box.x}px`,
|
|
748
831
|
top: `${box.y}px`,
|
|
@@ -751,9 +834,9 @@ export class JaakStamps {
|
|
|
751
834
|
border: '2px solid #32406C',
|
|
752
835
|
pointerEvents: 'none',
|
|
753
836
|
boxSizing: 'border-box'
|
|
754
|
-
} })))), this.isMaskReady && (h("div", { key: '
|
|
837
|
+
} })))), this.isMaskReady && (h("div", { key: 'b40cf136e5b9a120aa7243c12005383196428b34', class: "overlay-mask" }, h("div", { key: 'e82324a37682c94f03576e89d880c3b17520b5e5', class: "card-outline" }, h("div", { key: 'b1fd7df2a36300f2e6eb96474b449be74abd1ed4', class: "side side-top" }), h("div", { key: 'a6b3eb63fb35b22d0d53c8fd5aa88b2e299ae8c4', class: "side side-right" }), h("div", { key: '2d43e9281ad4fe05e314627aa249f56a7b0de9f1', class: "side side-bottom" }), h("div", { key: '231440a2dccc0f8ede35971ef092523d7be444b9', class: "side side-left" })), !captureState.showFlipAnimation && !captureState.showSuccessAnimation && (h("div", { key: 'a923e81e11ead323b9bcf76bd6b0fea421bc1517', class: "guide-text" }, this.getGuideText(captureState.step))), captureState.step === 'back' && !captureState.showFlipAnimation && !captureState.showSuccessAnimation && (h("div", { key: '4b68212634cbc4916cd16c4e00ca4e91b924de6e', class: "back-capture-section" }, h("div", { key: 'f2ec22b132defe1e4026ad38977c52974c5cdb21', class: "back-capture-buttons" }, !this.useDocumentDetector && (h("button", { key: 'ae8c0e452fbf1bb69b6683ae775f1c1a4b7d22d9', class: "capture-button primary-button", onClick: () => this.takeManualScreenshot(), type: "button", disabled: captureState.isCapturing }, captureState.isCapturing ? 'Capturando...' : 'Capturar Reverso')), h("button", { key: 'f1ec52715a584da161885bcbae0db7a38632905c', class: "skip-button", onClick: () => this.skipBackCapture(), type: "button" }, this.enableBackDocumentTimer && this.backDocumentTimerRemaining > 0
|
|
755
838
|
? `Saltar reverso (${this.backDocumentTimerRemaining}s)`
|
|
756
|
-
: 'Saltar reverso'))), captureState.isVideoActive && (h("div", { key: '
|
|
839
|
+
: 'Saltar reverso')))), captureState.isVideoActive && (h("div", { key: 'a831afce8ac493fe9716fc5608c1be5373cac9f4', class: "camera-controls" }, h("button", { key: '9a56a494d6357eac86211af857b74db4509aad64', 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: 'b08699bf97018b3318771aa9c19a5848fae6e09f', class: "camera-selector-dropdown" }, h("div", { key: '24d010fde00991a80f92803511ae31ab23d2d4ee', class: "camera-selector-header" }, h("span", { key: 'ea0a21c5989cc28dff59bab870cfbb39b401b466' }, "Seleccionar C\u00E1mara"), h("button", { key: '2cbd997565bfaeceb799a95fcb8e26681bab22ef', class: "close-selector", onClick: () => this.toggleCameraSelector(), type: "button" }, "\u00D7")), h("div", { key: 'c00abb13cb7299ff325b85d3fcd44dcad7839e53', 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: 'e369c9ae07f0638b490d67bb01ad2a3a7f9b56a1', class: "device-info" }, h("small", { key: '04d4a8c24a1a4487939f87e8af240962705fd685' }, "Dispositivo: ", cameraInfo.deviceType)))), this.showManualCaptureButton && captureState.step === 'front' && !captureState.showFlipAnimation && !captureState.showSuccessAnimation && (h("div", { key: 'f955e8e5b6917d01477fd171e3c16a44a3d0a916', class: "manual-capture-section" }, h("button", { key: 'cb122ec84fec7afae8185e561f4da197a210b0dd', class: "manual-capture-button", onClick: () => this.takeManualScreenshot(), type: "button", disabled: captureState.isCapturing }, captureState.isCapturing ? 'Capturando...' : 'Capturar Frente'))))), captureState.isCapturing && (h("div", { key: '98d15bcd0afab9daa3bc64096535db74f795723b', class: "capture-animation" })), captureState.showFlipAnimation && (h("div", { key: '86132c96fc3ff3296c04c32c03ef624511d175fd', class: "flip-animation" }, h("div", { key: '05efba20dc5a4890a6de5650e67b5144853fa9d6', class: "id-card-icon" }), h("div", { key: 'a2e307b126a1c65681733e68bb737ed21e9cb119', class: "flip-text" }, "\u00A1Voltea tu identificaci\u00F3n!"))), captureState.showSuccessAnimation && (h("div", { key: '38e1e4f5c3c8a5002fc45c8490e3134e31b4652a', class: "success-animation" }, h("div", { key: '2f6faebde51e90059106e64ef50af51d9395c0cf', class: "check-icon" }), h("div", { key: '63385f75ab3933966f58bb954c13f4cacb4d5c21', class: "success-text" }, "\u00A1Proceso completado!"))), h("div", { key: '0853a33e794b26c727336fb13c9430d99b992d7f', class: `component-status status-${this.currentStatus.type}` }, (this.currentStatus.type === 'loading' || this.currentStatus.type === 'initializing') && (h("div", { key: 'e0d258b6897ebaa281473ee6633244a89d9b3cc2', class: "status-spinner" })), h("div", { key: 'e54b82a5a895734bdaeb95fc9bfb631fd4680c87', class: "status-content" }, h("div", { key: '57e712d858574138a9ffdefa298adc99765788f3', class: "status-message" }, this.currentStatus.message), this.currentStatus.description && (h("div", { key: 'b2e1240eb71ed506157353fe5e24d62c3a0da8ab', class: "status-description" }, this.currentStatus.description)))), this.debug && (h("div", { key: '3f3acb1ffc8e2c1d64cc8dd0e76bf64772a92def', class: "performance-monitor" }, h("div", { key: '83c1640404aad9552ad082a78327acf9facd79ad', class: "performance-expanded" }, h("div", { key: '18ee837b0fa64bae25285183cbf79733f7654153', class: "metrics-row" }, h("div", { key: 'b7d6b1c15b7e57e67d886c1bbc279573e795a3a3', class: "metric-compact" }, h("span", { key: '2b02e2323aadc064231ab8735a5810acc7bdd871', class: "metric-label" }, "FPS"), h("span", { key: 'd0cbe3e212f216834127456574eef6e61b4f3d6c', class: `metric-value ${this.performanceData.fps < 15 ? 'warning' : this.performanceData.fps < 10 ? 'danger' : 'good'}` }, this.performanceData.fps)), h("div", { key: '974538f93bbe0abb25eadba4a73d9a28f9c9ae0a', class: "metric-compact" }, h("span", { key: 'e04bc2180ede406b787f63f92eb6086d4e372c77', class: "metric-label" }, "MEM"), h("span", { key: 'c7911542d7a39a334b0a2f0f258256d11b297fa4', class: `metric-value ${this.performanceData.memoryUsage > 100 ? 'warning' : this.performanceData.memoryUsage > 200 ? 'danger' : 'good'}` }, this.performanceData.memoryUsage, "MB"))), h("div", { key: '92bbce4d85240a84d53afce554792ae51597e16f', class: "metrics-row" }, h("div", { key: '7b78afc87a85a229b582d870e85d3015ca54b941', class: "metric-compact" }, h("span", { key: '7579b2b804c8a869894c0ed5a89c71f19d493c69', class: "metric-label" }, "INF"), h("span", { key: '029733bca2504d1d3c1fef77519e0df90b28d5d1', class: `metric-value ${this.performanceData.inferenceTime > 100 ? 'warning' : this.performanceData.inferenceTime > 200 ? 'danger' : 'good'}` }, this.performanceData.inferenceTime, "ms")), h("div", { key: 'f7fe505877d8e9214c2c819e714e3039c95c0ec7', class: "metric-compact" }, h("span", { key: 'ecef2280e78aecb7b811f9fa14c7fdbda8c4d065', class: "metric-label" }, "FRAME"), h("span", { key: 'c6cadf43725c125f371d3d849e8b09953a3747fc', class: `metric-value ${this.performanceData.frameProcessingTime > 50 ? 'warning' : this.performanceData.frameProcessingTime > 100 ? 'danger' : 'good'}` }, this.performanceData.frameProcessingTime, "ms"))), h("div", { key: '832e8ee75377791a17279a3c7eee12fc45f21820', class: "metrics-row" }, h("div", { key: 'abb2dc9ce8a52374bc04e4b36560885427303fb6', class: "metric-compact" }, h("span", { key: '8f8c9cfab162a01807d07f44278f4f5889a1b206', class: "metric-label" }, "DET"), h("span", { key: '7359af4c236091e99b1f653ba20d8e3bd3acaf1b', class: "metric-value good" }, this.performanceData.successfulDetections, "/", this.performanceData.totalDetections)), h("div", { key: '7dd17d6d47289a11087a33c6104269899b72e46e', class: "metric-compact" }, h("span", { key: '0b1317becd3af6804e1fb1965593882886c51b15', class: "metric-label" }, "RATE"), h("span", { key: '9e94ba4257aa707e6c751852db48a17fb7ec8bae', class: `metric-value ${this.performanceData.detectionRate < 30 ? 'danger' : this.performanceData.detectionRate < 60 ? 'warning' : 'good'}` }, this.performanceData.detectionRate, "%")))))), h("div", { key: '77beb6f47153c43fddbc643fe00d1777ecfab295', class: "watermark" }, h("img", { key: 'e6aec0055f8ffb058dc2249fa1182dae06638a31', src: "https://storage.googleapis.com/jaak-static/commons/powered-by-jaak.png", alt: "Powered by Jaak" })))));
|
|
757
840
|
}
|
|
758
841
|
// Utility methods
|
|
759
842
|
updateDetectionBoxes(boxes) {
|
|
@@ -868,6 +951,115 @@ export class JaakStamps {
|
|
|
868
951
|
}
|
|
869
952
|
}
|
|
870
953
|
}
|
|
954
|
+
async takeManualScreenshot() {
|
|
955
|
+
if (!this.videoRef)
|
|
956
|
+
return;
|
|
957
|
+
// When using manual capture, use mask coordinates for cropping
|
|
958
|
+
await this.takeScreenshotWithMaskCoordinates();
|
|
959
|
+
}
|
|
960
|
+
async takeScreenshotWithMaskCoordinates() {
|
|
961
|
+
if (!this.videoRef || !this.detectionContainer)
|
|
962
|
+
return;
|
|
963
|
+
this.stateManager.updateCaptureState({ isCapturing: true });
|
|
964
|
+
this.triggerCaptureAnimation();
|
|
965
|
+
// Use pooled canvas for optimization
|
|
966
|
+
const captureCanvas = this.getPooledCanvas(this.videoRef.videoWidth, this.videoRef.videoHeight);
|
|
967
|
+
const captureCtx = captureCanvas.getContext('2d', { alpha: false });
|
|
968
|
+
captureCtx.clearRect(0, 0, captureCanvas.width, captureCanvas.height);
|
|
969
|
+
captureCtx.drawImage(this.videoRef, 0, 0, captureCanvas.width, captureCanvas.height);
|
|
970
|
+
// Calculate mask coordinates for cropping
|
|
971
|
+
const container = this.detectionContainer.parentElement;
|
|
972
|
+
const containerRect = container.getBoundingClientRect();
|
|
973
|
+
// Get mask dimensions from CSS properties
|
|
974
|
+
const maskWidthPercent = parseFloat(this.el.style.getPropertyValue('--mask-width').replace('%', '')) || 90;
|
|
975
|
+
const maskHeightPercent = parseFloat(this.el.style.getPropertyValue('--mask-height').replace('%', '')) || 56.25;
|
|
976
|
+
const maskCenterXPercent = parseFloat(this.el.style.getPropertyValue('--mask-center-x').replace('%', '')) || 50;
|
|
977
|
+
const maskCenterYPercent = parseFloat(this.el.style.getPropertyValue('--mask-center-y').replace('%', '')) || 50;
|
|
978
|
+
// Convert percentages to actual video coordinates
|
|
979
|
+
const videoWidth = this.videoRef.videoWidth;
|
|
980
|
+
const videoHeight = this.videoRef.videoHeight;
|
|
981
|
+
const videoAspectRatio = videoWidth / videoHeight;
|
|
982
|
+
const containerAspectRatio = containerRect.width / containerRect.height;
|
|
983
|
+
let displayedVideoWidth, displayedVideoHeight;
|
|
984
|
+
let videoOffsetX = 0, videoOffsetY = 0;
|
|
985
|
+
if (videoAspectRatio > containerAspectRatio) {
|
|
986
|
+
displayedVideoWidth = containerRect.width;
|
|
987
|
+
displayedVideoHeight = containerRect.width / videoAspectRatio;
|
|
988
|
+
videoOffsetY = (containerRect.height - displayedVideoHeight) / 2;
|
|
989
|
+
}
|
|
990
|
+
else {
|
|
991
|
+
displayedVideoHeight = containerRect.height;
|
|
992
|
+
displayedVideoWidth = containerRect.height * videoAspectRatio;
|
|
993
|
+
videoOffsetX = (containerRect.width - displayedVideoWidth) / 2;
|
|
994
|
+
}
|
|
995
|
+
// Calculate mask coordinates in video space
|
|
996
|
+
const maskWidthInContainer = (maskWidthPercent / 100) * containerRect.width;
|
|
997
|
+
const maskHeightInContainer = (maskHeightPercent / 100) * containerRect.height;
|
|
998
|
+
const maskCenterXInContainer = (maskCenterXPercent / 100) * containerRect.width;
|
|
999
|
+
const maskCenterYInContainer = (maskCenterYPercent / 100) * containerRect.height;
|
|
1000
|
+
// Convert to video coordinates
|
|
1001
|
+
const scaleX = videoWidth / displayedVideoWidth;
|
|
1002
|
+
const scaleY = videoHeight / displayedVideoHeight;
|
|
1003
|
+
const maskCenterXInVideo = (maskCenterXInContainer - videoOffsetX) * scaleX;
|
|
1004
|
+
const maskCenterYInVideo = (maskCenterYInContainer - videoOffsetY) * scaleY;
|
|
1005
|
+
const maskWidthInVideo = maskWidthInContainer * scaleX;
|
|
1006
|
+
const maskHeightInVideo = maskHeightInContainer * scaleY;
|
|
1007
|
+
// Calculate crop coordinates
|
|
1008
|
+
const cropX = Math.max(0, maskCenterXInVideo - (maskWidthInVideo / 2) - this.cropMargin);
|
|
1009
|
+
const cropY = Math.max(0, maskCenterYInVideo - (maskHeightInVideo / 2) - this.cropMargin);
|
|
1010
|
+
const cropWidth = Math.min(maskWidthInVideo + (2 * this.cropMargin), videoWidth - cropX);
|
|
1011
|
+
const cropHeight = Math.min(maskHeightInVideo + (2 * this.cropMargin), videoHeight - cropY);
|
|
1012
|
+
// Use pooled canvas for cropped version
|
|
1013
|
+
const croppedCanvas = this.getPooledCanvas(cropWidth, cropHeight);
|
|
1014
|
+
const croppedCtx = croppedCanvas.getContext('2d', { alpha: false });
|
|
1015
|
+
croppedCtx.clearRect(0, 0, croppedCanvas.width, croppedCanvas.height);
|
|
1016
|
+
croppedCtx.drawImage(this.videoRef, cropX, cropY, cropWidth, cropHeight, 0, 0, cropWidth, cropHeight);
|
|
1017
|
+
const captureState = this.stateManager.getCaptureState();
|
|
1018
|
+
if (captureState.step === 'front') {
|
|
1019
|
+
this.stateManager.setCapturedImages({
|
|
1020
|
+
front: {
|
|
1021
|
+
fullFrame: captureCanvas.toDataURL('image/png'),
|
|
1022
|
+
cropped: croppedCanvas.toDataURL('image/png')
|
|
1023
|
+
}
|
|
1024
|
+
});
|
|
1025
|
+
// Check if document classification is enabled (independent of detector)
|
|
1026
|
+
if (this.useDocumentClassification) {
|
|
1027
|
+
const classification = await this.detectionService.classifyDocument(croppedCanvas);
|
|
1028
|
+
if (classification && classification.class === 'passport') {
|
|
1029
|
+
this.completeProcess(true);
|
|
1030
|
+
this.returnCanvasToPool(captureCanvas);
|
|
1031
|
+
this.returnCanvasToPool(croppedCanvas);
|
|
1032
|
+
return;
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
this.stateManager.updateCaptureState({
|
|
1036
|
+
step: 'back',
|
|
1037
|
+
isDetectionPaused: true,
|
|
1038
|
+
showFlipAnimation: true
|
|
1039
|
+
});
|
|
1040
|
+
this.triggerRerender();
|
|
1041
|
+
setTimeout(() => {
|
|
1042
|
+
this.stateManager.updateCaptureState({
|
|
1043
|
+
showFlipAnimation: false,
|
|
1044
|
+
isDetectionPaused: false
|
|
1045
|
+
});
|
|
1046
|
+
this.triggerRerender();
|
|
1047
|
+
this.startBackDocumentTimer();
|
|
1048
|
+
}, 3000);
|
|
1049
|
+
}
|
|
1050
|
+
else if (captureState.step === 'back') {
|
|
1051
|
+
this.stateManager.setCapturedImages({
|
|
1052
|
+
back: {
|
|
1053
|
+
fullFrame: captureCanvas.toDataURL('image/png'),
|
|
1054
|
+
cropped: croppedCanvas.toDataURL('image/png')
|
|
1055
|
+
}
|
|
1056
|
+
});
|
|
1057
|
+
this.completeProcess(false);
|
|
1058
|
+
}
|
|
1059
|
+
// Return canvases to pool after use
|
|
1060
|
+
this.returnCanvasToPool(captureCanvas);
|
|
1061
|
+
this.returnCanvasToPool(croppedCanvas);
|
|
1062
|
+
}
|
|
871
1063
|
async takeScreenshot() {
|
|
872
1064
|
if (!this.videoRef || !this.lastDetectedBox)
|
|
873
1065
|
return;
|
|
@@ -1301,6 +1493,26 @@ export class JaakStamps {
|
|
|
1301
1493
|
"reflect": false,
|
|
1302
1494
|
"defaultValue": "false"
|
|
1303
1495
|
},
|
|
1496
|
+
"useDocumentDetector": {
|
|
1497
|
+
"type": "boolean",
|
|
1498
|
+
"attribute": "use-document-detector",
|
|
1499
|
+
"mutable": false,
|
|
1500
|
+
"complexType": {
|
|
1501
|
+
"original": "boolean",
|
|
1502
|
+
"resolved": "boolean",
|
|
1503
|
+
"references": {}
|
|
1504
|
+
},
|
|
1505
|
+
"required": false,
|
|
1506
|
+
"optional": false,
|
|
1507
|
+
"docs": {
|
|
1508
|
+
"tags": [],
|
|
1509
|
+
"text": ""
|
|
1510
|
+
},
|
|
1511
|
+
"getter": false,
|
|
1512
|
+
"setter": false,
|
|
1513
|
+
"reflect": false,
|
|
1514
|
+
"defaultValue": "true"
|
|
1515
|
+
},
|
|
1304
1516
|
"preferredCamera": {
|
|
1305
1517
|
"type": "string",
|
|
1306
1518
|
"attribute": "preferred-camera",
|
|
@@ -1395,7 +1607,9 @@ export class JaakStamps {
|
|
|
1395
1607
|
"cameraInfoWithAutofocus": {},
|
|
1396
1608
|
"currentStatus": {},
|
|
1397
1609
|
"performanceData": {},
|
|
1398
|
-
"backDocumentTimerRemaining": {}
|
|
1610
|
+
"backDocumentTimerRemaining": {},
|
|
1611
|
+
"showManualCaptureButton": {},
|
|
1612
|
+
"captureStateVersion": {}
|
|
1399
1613
|
};
|
|
1400
1614
|
}
|
|
1401
1615
|
static get events() {
|