@jaak.ai/stamps 2.1.0-dev.6 → 2.1.0-dev.8
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/jaak-stamps-webcomponent.cjs.js +1 -1
- package/dist/cjs/jaak-stamps.cjs.entry.js +623 -31
- 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 +36 -11
- package/dist/collection/components/my-component/my-component.js +484 -24
- package/dist/collection/components/my-component/my-component.js.map +1 -1
- package/dist/collection/services/DetectionService.js +176 -9
- package/dist/collection/services/DetectionService.js.map +1 -1
- package/dist/collection/services/interfaces/IDetectionService.js.map +1 -1
- package/dist/components/jaak-stamps.js +627 -32
- 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 +623 -31
- 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-8e25497e.entry.js +2 -0
- package/dist/jaak-stamps-webcomponent/p-8e25497e.entry.js.map +1 -0
- package/dist/types/components/my-component/my-component.d.ts +32 -0
- package/dist/types/components.d.ts +3 -1
- package/dist/types/services/DetectionService.d.ts +9 -1
- package/dist/types/services/interfaces/IDetectionService.d.ts +6 -0
- package/package.json +1 -1
- package/dist/jaak-stamps-webcomponent/p-d05dc382.entry.js +0 -2
- package/dist/jaak-stamps-webcomponent/p-d05dc382.entry.js.map +0 -1
|
@@ -648,6 +648,10 @@ class DetectionService {
|
|
|
648
648
|
// Static canvas pool for reuse across instances
|
|
649
649
|
static canvasPool = new Map();
|
|
650
650
|
static MAX_POOL_SIZE = 5;
|
|
651
|
+
// Resource management for optimization
|
|
652
|
+
modelLoadController;
|
|
653
|
+
classificationLoadController;
|
|
654
|
+
modelBlobUrls = new Set();
|
|
651
655
|
constructor(debug = false, useDocumentClassification = false, useDocumentDetector = true) {
|
|
652
656
|
this.debug = debug;
|
|
653
657
|
this.useDocumentClassification = useDocumentClassification;
|
|
@@ -665,12 +669,32 @@ class DetectionService {
|
|
|
665
669
|
return;
|
|
666
670
|
}
|
|
667
671
|
try {
|
|
672
|
+
// Create AbortController for this load operation
|
|
673
|
+
this.modelLoadController = new AbortController();
|
|
668
674
|
const sessionOptions = this.deviceStrategy.getSessionOptions(this.debug);
|
|
669
675
|
try {
|
|
670
|
-
|
|
676
|
+
// Load model with abort signal support
|
|
677
|
+
const response = await fetch(this.MODEL_PATH, {
|
|
678
|
+
signal: this.modelLoadController.signal
|
|
679
|
+
});
|
|
680
|
+
if (!response.ok) {
|
|
681
|
+
throw new Error(`Failed to fetch model: ${response.status}`);
|
|
682
|
+
}
|
|
683
|
+
const modelBuffer = await response.arrayBuffer();
|
|
684
|
+
this.session = await window.ort.InferenceSession.create(modelBuffer, sessionOptions);
|
|
671
685
|
}
|
|
672
686
|
catch (error) {
|
|
673
|
-
|
|
687
|
+
// Handle abort signal
|
|
688
|
+
if (error.name === 'AbortError') {
|
|
689
|
+
console.log('Model loading was cancelled');
|
|
690
|
+
return;
|
|
691
|
+
}
|
|
692
|
+
// Múltiples tipos de errores de memoria que pueden ocurrir
|
|
693
|
+
const isMemoryError = error.message.includes('failed to allocate a buffer') ||
|
|
694
|
+
error.message.includes('Out of memory') ||
|
|
695
|
+
error.message.includes('RangeError: Out of memory') ||
|
|
696
|
+
error.message.includes('no available backend found');
|
|
697
|
+
if (isMemoryError) {
|
|
674
698
|
const fallbackOptions = {
|
|
675
699
|
executionProviders: ['wasm'],
|
|
676
700
|
graphOptimizationLevel: 'disabled',
|
|
@@ -680,8 +704,25 @@ class DetectionService {
|
|
|
680
704
|
executionMode: 'sequential',
|
|
681
705
|
interOpNumThreads: 1,
|
|
682
706
|
intraOpNumThreads: 1,
|
|
707
|
+
// Configuraciones adicionales para dispositivos con poca memoria
|
|
708
|
+
enableProfiling: false,
|
|
709
|
+
sessionLogSeverityLevel: 4,
|
|
710
|
+
sessionLogVerbosityLevel: 0,
|
|
683
711
|
};
|
|
684
|
-
|
|
712
|
+
try {
|
|
713
|
+
// Retry with fallback options and abort signal
|
|
714
|
+
const fallbackResponse = await fetch(this.MODEL_PATH, {
|
|
715
|
+
signal: this.modelLoadController.signal
|
|
716
|
+
});
|
|
717
|
+
if (!fallbackResponse.ok) {
|
|
718
|
+
throw new Error(`Failed to fetch model with fallback: ${fallbackResponse.status}`);
|
|
719
|
+
}
|
|
720
|
+
const fallbackBuffer = await fallbackResponse.arrayBuffer();
|
|
721
|
+
this.session = await window.ort.InferenceSession.create(fallbackBuffer, fallbackOptions);
|
|
722
|
+
}
|
|
723
|
+
catch (fallbackError) {
|
|
724
|
+
throw new Error(`no available backend found. ERR: [wasm] RangeError: Out of memory`);
|
|
725
|
+
}
|
|
685
726
|
}
|
|
686
727
|
else {
|
|
687
728
|
throw error;
|
|
@@ -698,9 +739,13 @@ class DetectionService {
|
|
|
698
739
|
return;
|
|
699
740
|
}
|
|
700
741
|
try {
|
|
742
|
+
// Create AbortController for classification model loading
|
|
743
|
+
this.classificationLoadController = new AbortController();
|
|
701
744
|
// Try to load class map (optional - SqueezeNet may not need it for basic classification)
|
|
702
745
|
try {
|
|
703
|
-
const classResponse = await fetch(this.MOBILENET_CLASSES_PATH
|
|
746
|
+
const classResponse = await fetch(this.MOBILENET_CLASSES_PATH, {
|
|
747
|
+
signal: this.classificationLoadController.signal
|
|
748
|
+
});
|
|
704
749
|
if (classResponse.ok) {
|
|
705
750
|
this.mobileNetClassMap = await classResponse.json();
|
|
706
751
|
}
|
|
@@ -718,10 +763,28 @@ class DetectionService {
|
|
|
718
763
|
// Load model
|
|
719
764
|
const sessionOptions = this.deviceStrategy.getSessionOptions(this.debug);
|
|
720
765
|
try {
|
|
721
|
-
|
|
766
|
+
// Load classification model with abort signal support
|
|
767
|
+
const response = await fetch(this.MOBILENET_MODEL_PATH, {
|
|
768
|
+
signal: this.classificationLoadController.signal
|
|
769
|
+
});
|
|
770
|
+
if (!response.ok) {
|
|
771
|
+
throw new Error(`Failed to fetch classification model: ${response.status}`);
|
|
772
|
+
}
|
|
773
|
+
const modelBuffer = await response.arrayBuffer();
|
|
774
|
+
this.mobileNetSession = await window.ort.InferenceSession.create(modelBuffer, sessionOptions);
|
|
722
775
|
}
|
|
723
776
|
catch (error) {
|
|
724
|
-
|
|
777
|
+
// Handle abort signal
|
|
778
|
+
if (error.name === 'AbortError') {
|
|
779
|
+
console.log('Classification model loading was cancelled');
|
|
780
|
+
return;
|
|
781
|
+
}
|
|
782
|
+
// Múltiples tipos de errores de memoria que pueden ocurrir
|
|
783
|
+
const isMemoryError = error.message.includes('failed to allocate a buffer') ||
|
|
784
|
+
error.message.includes('Out of memory') ||
|
|
785
|
+
error.message.includes('RangeError: Out of memory') ||
|
|
786
|
+
error.message.includes('no available backend found');
|
|
787
|
+
if (isMemoryError) {
|
|
725
788
|
const fallbackOptions = {
|
|
726
789
|
executionProviders: ['wasm'],
|
|
727
790
|
graphOptimizationLevel: 'disabled',
|
|
@@ -731,8 +794,25 @@ class DetectionService {
|
|
|
731
794
|
executionMode: 'sequential',
|
|
732
795
|
interOpNumThreads: 1,
|
|
733
796
|
intraOpNumThreads: 1,
|
|
797
|
+
// Configuraciones adicionales para dispositivos con poca memoria
|
|
798
|
+
enableProfiling: false,
|
|
799
|
+
sessionLogSeverityLevel: 4,
|
|
800
|
+
sessionLogVerbosityLevel: 0,
|
|
734
801
|
};
|
|
735
|
-
|
|
802
|
+
try {
|
|
803
|
+
// Retry with fallback options and abort signal
|
|
804
|
+
const fallbackResponse = await fetch(this.MOBILENET_MODEL_PATH, {
|
|
805
|
+
signal: this.classificationLoadController.signal
|
|
806
|
+
});
|
|
807
|
+
if (!fallbackResponse.ok) {
|
|
808
|
+
throw new Error(`Failed to fetch classification model with fallback: ${fallbackResponse.status}`);
|
|
809
|
+
}
|
|
810
|
+
const fallbackBuffer = await fallbackResponse.arrayBuffer();
|
|
811
|
+
this.mobileNetSession = await window.ort.InferenceSession.create(fallbackBuffer, fallbackOptions);
|
|
812
|
+
}
|
|
813
|
+
catch (fallbackError) {
|
|
814
|
+
throw new Error(`no available backend found. ERR: [wasm] RangeError: Out of memory`);
|
|
815
|
+
}
|
|
736
816
|
}
|
|
737
817
|
else {
|
|
738
818
|
throw error;
|
|
@@ -918,6 +998,9 @@ class DetectionService {
|
|
|
918
998
|
return alignment.top && alignment.right && alignment.bottom && alignment.left;
|
|
919
999
|
}
|
|
920
1000
|
cleanup() {
|
|
1001
|
+
// Cancel any ongoing requests
|
|
1002
|
+
this.abortPendingRequests();
|
|
1003
|
+
// Cleanup ONNX resources
|
|
921
1004
|
this.cleanupCanvasPool();
|
|
922
1005
|
if (this.session) {
|
|
923
1006
|
this.session.release?.();
|
|
@@ -929,6 +1012,8 @@ class DetectionService {
|
|
|
929
1012
|
}
|
|
930
1013
|
this.mobileNetClassMap = undefined;
|
|
931
1014
|
this.modelLoaded = false;
|
|
1015
|
+
// Cleanup blob URLs
|
|
1016
|
+
this.revokeModelBlobUrls();
|
|
932
1017
|
}
|
|
933
1018
|
initializeCanvasPool() {
|
|
934
1019
|
this.preprocessCanvas = document.createElement('canvas');
|
|
@@ -1026,11 +1111,93 @@ class DetectionService {
|
|
|
1026
1111
|
const pool = DetectionService.canvasPool.get(key);
|
|
1027
1112
|
// Only return to pool if not at max capacity
|
|
1028
1113
|
if (pool.length < DetectionService.MAX_POOL_SIZE) {
|
|
1114
|
+
const ctx = canvas.getContext('2d');
|
|
1115
|
+
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
1029
1116
|
pool.push(canvas);
|
|
1030
1117
|
}
|
|
1118
|
+
// If pool is full, let the canvas be garbage collected
|
|
1119
|
+
}
|
|
1120
|
+
// Resource management methods for cache and browser optimization
|
|
1121
|
+
async clearModelCache() {
|
|
1122
|
+
try {
|
|
1123
|
+
// Clear cache specific to models
|
|
1124
|
+
const cacheNames = await caches.keys();
|
|
1125
|
+
const modelCaches = cacheNames.filter(name => name.includes('onnx') ||
|
|
1126
|
+
name.includes('jaak-static') ||
|
|
1127
|
+
name.includes('squeezenet') ||
|
|
1128
|
+
name.includes('ddmyp'));
|
|
1129
|
+
await Promise.all(modelCaches.map(cacheName => caches.delete(cacheName)));
|
|
1130
|
+
if (this.debug) {
|
|
1131
|
+
console.log(`🧹 Cleared ${modelCaches.length} model caches`);
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
1134
|
+
catch (error) {
|
|
1135
|
+
console.warn('Error clearing model cache:', error);
|
|
1136
|
+
}
|
|
1137
|
+
}
|
|
1138
|
+
abortPendingRequests() {
|
|
1139
|
+
// Cancel any fetch in progress
|
|
1140
|
+
if (this.modelLoadController) {
|
|
1141
|
+
this.modelLoadController.abort();
|
|
1142
|
+
this.modelLoadController = undefined;
|
|
1143
|
+
}
|
|
1144
|
+
if (this.classificationLoadController) {
|
|
1145
|
+
this.classificationLoadController.abort();
|
|
1146
|
+
this.classificationLoadController = undefined;
|
|
1147
|
+
}
|
|
1148
|
+
}
|
|
1149
|
+
revokeModelBlobUrls() {
|
|
1150
|
+
// Revoke any blob URLs created for models
|
|
1151
|
+
this.modelBlobUrls.forEach(url => URL.revokeObjectURL(url));
|
|
1152
|
+
this.modelBlobUrls.clear();
|
|
1153
|
+
}
|
|
1154
|
+
// Progressive resource release for optimization
|
|
1155
|
+
async releaseMobileNetResources() {
|
|
1156
|
+
if (this.mobileNetSession) {
|
|
1157
|
+
this.mobileNetSession.release?.();
|
|
1158
|
+
this.mobileNetSession = undefined;
|
|
1159
|
+
}
|
|
1160
|
+
this.mobileNetClassMap = undefined;
|
|
1161
|
+
if (this.classificationLoadController) {
|
|
1162
|
+
this.classificationLoadController.abort();
|
|
1163
|
+
this.classificationLoadController = undefined;
|
|
1164
|
+
}
|
|
1165
|
+
if (this.debug) {
|
|
1166
|
+
console.log('🧹 Released MobileNet classification resources');
|
|
1167
|
+
}
|
|
1031
1168
|
}
|
|
1032
|
-
//
|
|
1033
|
-
|
|
1169
|
+
// Optimize canvas pool by reducing to specific size
|
|
1170
|
+
optimizeCanvasPool(maxSize) {
|
|
1171
|
+
DetectionService.canvasPool.forEach((pool, key) => {
|
|
1172
|
+
while (pool.length > maxSize) {
|
|
1173
|
+
const canvas = pool.pop();
|
|
1174
|
+
// Explicitly clear and nullify for garbage collection
|
|
1175
|
+
if (canvas) {
|
|
1176
|
+
const ctx = canvas.getContext('2d');
|
|
1177
|
+
if (ctx) {
|
|
1178
|
+
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1182
|
+
if (pool.length === 0) {
|
|
1183
|
+
DetectionService.canvasPool.delete(key);
|
|
1184
|
+
}
|
|
1185
|
+
});
|
|
1186
|
+
if (this.debug) {
|
|
1187
|
+
console.log(`🧹 Optimized canvas pool to max size: ${maxSize}`);
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
1190
|
+
// Clear all static canvas pools
|
|
1191
|
+
static clearAllCanvasPools() {
|
|
1192
|
+
DetectionService.canvasPool.forEach((pool) => {
|
|
1193
|
+
pool.forEach(canvas => {
|
|
1194
|
+
const ctx = canvas.getContext('2d');
|
|
1195
|
+
if (ctx) {
|
|
1196
|
+
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
1197
|
+
}
|
|
1198
|
+
});
|
|
1199
|
+
pool.length = 0;
|
|
1200
|
+
});
|
|
1034
1201
|
DetectionService.canvasPool.clear();
|
|
1035
1202
|
}
|
|
1036
1203
|
// Method to get pool statistics for debugging
|
|
@@ -1089,7 +1256,7 @@ class ServiceContainer {
|
|
|
1089
1256
|
}
|
|
1090
1257
|
}
|
|
1091
1258
|
|
|
1092
|
-
const myComponentCss = ":host{display:block;width:100%;height:100%;font-family:system-ui, -apple-system, sans-serif;color:#1a1a1a}.detector-container{display:flex;flex-direction:column;align-items:center;width:100%;height:100%}h1{font-size:24px;font-weight:500;color:#333;margin:0 0 24px 0}.video-container{position:relative;width:100%;height:100%;background:#333;border:1px solid #e0e0e0;border-radius:8px;overflow:hidden}video,.detection-overlay{position:absolute;width:100%;height:100%;border-radius:8px}video.mirror,.detection-overlay.mirror{transform:rotateY(180deg)}.detection-overlay{z-index:1;pointer-events:none}video{z-index:0}.detection-box{transition:opacity 0.2s ease}.status{margin-top:16px;font-size:12px;color:#666}.overlay-mask{position:absolute;top:0;left:0;width:100%;height:100%;z-index:10;display:block;pointer-events:none}.card-outline{position:absolute;top:var(--mask-center-y, 50%);left:var(--mask-center-x, 50%);transform:translate(-50%, -50%);width:var(--mask-width, 88%);height:var(--mask-height, 55%);border:none;border-radius:4px;background:transparent;opacity:0.8}.side{position:absolute;background:#999;transition:background-color 0.3s ease;border-radius:1px}.side-top.aligned{border-left-color:#28a745;border-top-color:#28a745}.side-right.aligned{border-right-color:#28a745;border-top-color:#28a745}.side-bottom.aligned{border-left-color:#28a745;border-bottom-color:#28a745}.side-left.aligned{border-right-color:#28a745;border-bottom-color:#28a745}.side-top{top:-3px;left:-3px;width:30px;height:30px;border-left:6px solid #ffffff;border-top:6px solid #ffffff;background:transparent}.side-right{top:-3px;right:-3px;width:30px;height:30px;border-right:6px solid #ffffff;border-top:6px solid #ffffff;background:transparent}.side-bottom{bottom:-3px;left:-3px;width:30px;height:30px;border-left:6px solid #ffffff;border-bottom:6px solid #ffffff;background:transparent}.side-left{bottom:-3px;right:-3px;width:30px;height:30px;border-right:6px solid #ffffff;border-bottom:6px solid #ffffff;background:transparent}.guide-text{position:absolute;top:50%;left:50%;transform:translate(-50%, -50%);background:rgba(0, 0, 0, 0.25);backdrop-filter:blur(20px);border-radius:12px;border:1px solid rgba(255, 255, 255, 0.1);color:#ffffff;font-size:12px;font-weight:500;text-align:center;white-space:normal;padding:13px;max-width:300px;z-index:20;height:fit-content;width:fit-content;animation:slideInFromTopCentered 0.3s ease-out}.quality-indicator{position:absolute;top:20px;right:20px;display:flex;align-items:center;gap:8px;background:rgba(0, 0, 0, 0.8);padding:8px 12px;border-radius:20px;z-index:25;transition:all 0.3s ease}.quality-indicator.warning{background:rgba(255, 152, 0, 0.9)}.quality-indicator.good{background:rgba(76, 175, 80, 0.9)}.quality-score{color:#fff;font-size:12px;font-weight:600;min-width:30px;text-align:center}.quality-status{width:20px;height:20px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:14px;font-weight:bold;color:#fff;transition:all 0.3s ease}.quality-status.ready{background:#4CAF50}.quality-status.not-ready{background:#f44336}.card-outline.quality-warning{animation:qualityWarning 1s ease-in-out infinite alternate}@keyframes qualityWarning{0%{box-shadow:0 0 0 3px rgba(255, 152, 0, 0.6)}100%{box-shadow:0 0 0 6px rgba(255, 152, 0, 0.3)}}.capture-animation{position:absolute;top:0;left:0;width:100%;height:100%;background:#fff;opacity:0;z-index:30;pointer-events:none;animation:captureFlash 0.6s ease-out}@keyframes captureFlash{0%{opacity:0}15%{opacity:0.8}30%{opacity:0}45%{opacity:0.4}60%{opacity:0}100%{opacity:0}}.card-outline.capturing{animation:pulseGreen 0.6s ease-out}@keyframes pulseGreen{0%{border-color:#28a745;box-shadow:0 0 0 4px rgba(40, 167, 69, 0)}50%{border-color:#28a745;box-shadow:0 0 0 8px rgba(40, 167, 69, 0.6)}100%{border-color:#28a745;box-shadow:0 0 0 4px rgba(40, 167, 69, 0)}}.flip-animation{position:absolute;top:50%;left:50%;transform:translate(-50%, -50%);z-index:35;pointer-events:none;opacity:0;animation:showFlipInstruction 3s ease-in-out;display:flex;flex-direction:column;align-items:center;justify-content:center}.id-card-icon{width:80px;height:50px;background:linear-gradient(135deg, #6c757d 0%, #495057 100%);border-radius:8px;position:relative;animation:flipCard 2s ease-in-out infinite;box-shadow:0 4px 12px rgba(0, 0, 0, 0.3)}.id-card-icon::before{content:'';position:absolute;top:8px;left:8px;width:16px;height:12px;background:rgba(255, 255, 255, 0.9);border-radius:2px}.id-card-icon::after{content:'';position:absolute;top:25px;left:8px;width:64px;height:3px;background:rgba(255, 255, 255, 0.7);border-radius:1px;box-shadow:0 6px 0 rgba(255, 255, 255, 0.7), 0 12px 0 rgba(255, 255, 255, 0.7)}.flip-text{margin-top:12px;color:#fff;font-size:14px;font-weight:600;text-align:center;background:rgba(128, 128, 128, 0.8);padding:12px 20px;border-radius:20px;max-width:300px}@keyframes showFlipInstruction{0%{opacity:0;transform:translate(-50%, -50%) scale(0.8)}15%{opacity:1;transform:translate(-50%, -50%) scale(1)}85%{opacity:1;transform:translate(-50%, -50%) scale(1)}100%{opacity:0;transform:translate(-50%, -50%) scale(0.8)}}@keyframes flipCard{0%{transform:rotateY(0deg)}50%{transform:rotateY(180deg)}100%{transform:rotateY(360deg)}}.success-animation{position:absolute;top:50%;left:50%;transform:translate(-50%, -50%);z-index:35;pointer-events:none;opacity:0;animation:showSuccessMessage 3s ease-in-out;display:flex;flex-direction:column;align-items:center;justify-content:center}.check-icon{width:80px;height:80px;background:linear-gradient(135deg, #6c757d 0%, #495057 100%);border-radius:50%;position:relative;animation:bounceSuccess 0.6s ease-out;box-shadow:0 4px 16px rgba(108, 117, 125, 0.4);display:flex;align-items:center;justify-content:center}.check-icon::before{content:'';position:absolute;width:20px;height:35px;border:4px solid #fff;border-top:none;border-left:none;transform:rotate(45deg);animation:drawCheck 0.4s ease-out 0.2s both}.success-text{margin-top:16px;color:#fff;font-size:14px;font-weight:600;text-align:center;background:rgba(128, 128, 128, 0.8);padding:12px 20px;border-radius:20px;max-width:300px;animation:fadeInUp 0.5s ease-out 0.4s both}@keyframes showSuccessMessage{0%{opacity:0;transform:translate(-50%, -50%) scale(0.5)}15%{opacity:1;transform:translate(-50%, -50%) scale(1)}85%{opacity:1;transform:translate(-50%, -50%) scale(1)}100%{opacity:0;transform:translate(-50%, -50%) scale(0.9)}}@keyframes bounceSuccess{0%{transform:scale(0)}50%{transform:scale(1.1)}100%{transform:scale(1)}}@keyframes drawCheck{0%{width:0;height:0}50%{width:20px;height:0}100%{width:20px;height:35px}}@keyframes fadeInUp{0%{opacity:0;transform:translateY(20px)}100%{opacity:1;transform:translateY(0)}}.skip-section{position:absolute;bottom:20px;left:50%;transform:translateX(-50%);z-index:25;display:flex;flex-direction:column;align-items:center;width:100%;max-width:300px}.back-capture-section{position:absolute;bottom:20px;left:50%;transform:translateX(-50%);z-index:25;display:flex;flex-direction:column;align-items:center;width:100%;max-width:320px}.back-capture-buttons{display:flex;gap:12px;align-items:center;justify-content:center;flex-wrap:wrap}.capture-button{pointer-events:auto;background:#fff;color:#333;border:none;border-radius:25px;padding:12px 24px;font-size:14px;font-weight:600;cursor:pointer;transition:all 0.3s ease}.capture-button:hover{background:#f8f9fa}.capture-button:active{background:#e9ecef;transform:translateY(1px)}.capture-button:disabled{background:#e9ecef;color:#6c757d;cursor:not-allowed;transform:none}@media (max-width: 480px){.back-capture-section{bottom:16px;max-width:300px}.back-capture-buttons{flex-direction:column;gap:8px;width:100%}.capture-button,.back-capture-buttons .skip-button{width:100%;max-width:200px;padding:10px 20px;font-size:13px}}.skip-explanation{background:rgba(0, 0, 0, 0.5);color:#ffffff;padding:10px 16px;border-radius:8px;font-size:14px;font-weight:500;text-align:center;margin-bottom:12px;box-shadow:0 2px 8px rgba(0, 0, 0, 0.2);backdrop-filter:blur(12px);border:1px solid rgba(255, 255, 255, 0.1);animation:fadeIn 0.3s ease-in-out}@keyframes fadeIn{from{opacity:0;transform:translateY(10px)}to{opacity:1;transform:translateY(0)}}.guide-text.performance-warning-text{animation:gentlePulse 2s ease-in-out infinite;background:rgba(255, 107, 107, 0.3);border:1px solid rgba(255, 107, 107, 0.5)}@keyframes gentlePulse{0%,100%{transform:translate(-50%, -50%) scale(1);background:rgba(255, 107, 107, 0.3)}50%{transform:translate(-50%, -50%) scale(1.02);background:rgba(255, 107, 107, 0.4)}}.skip-button{pointer-events:auto;background:#fff;color:#333;border:none;border-radius:25px;padding:12px 24px;font-size:14px;font-weight:600;cursor:pointer;transition:all 0.3s ease}.skip-button:hover{background:#f8f9fa}.skip-button:active{background:#e9ecef;transform:translateY(1px)}.camera-controls{position:absolute;top:16px;right:16px;z-index:25;display:flex;gap:8px;pointer-events:auto;animation:slideInFromTop 0.3s ease-out}.camera-selector-button{height:32px;padding:0 10px;border:none;border-radius:8px;background:rgba(0, 0, 0, 0.25);backdrop-filter:blur(12px);color:#ffffff;font-size:12px;font-weight:500;cursor:pointer;transition:all 0.2s ease;display:flex;align-items:center;justify-content:center;border:1px solid rgba(255, 255, 255, 0.1);white-space:nowrap;min-width:fit-content}.camera-selector-button:hover{background:rgba(0, 0, 0, 0.8);border-color:rgba(255, 255, 255, 0.2);transform:translateY(-1px)}.camera-selector-button:active{transform:translateY(0);background:rgba(0, 0, 0, 0.9)}.camera-selector-button:disabled,.camera-selector-button.loading{opacity:0.7;cursor:not-allowed;pointer-events:none}.camera-selector-button:disabled:hover,.camera-selector-button.loading:hover{transform:none;background:rgba(0, 0, 0, 0.6);border-color:rgba(255, 255, 255, 0.1)}.button-spinner{width:16px;height:16px;border:2px solid rgba(255, 255, 255, 0.3);border-top:2px solid #ffffff;border-radius:50%;animation:spin 1s linear infinite}@keyframes spin{0%{transform:rotate(0deg)}100%{transform:rotate(360deg)}}.camera-selector-dropdown{position:absolute;top:56px;right:16px;z-index:30;background:rgba(0, 0, 0, 0.25);backdrop-filter:blur(20px);border-radius:12px;min-width:260px;max-width:300px;border:1px solid rgba(255, 255, 255, 0.1);overflow:hidden;pointer-events:auto;animation:slideInFromTop 0.3s ease-out}@keyframes slideInFromTop{0%{opacity:0;transform:translateY(-8px) scale(0.95)}100%{opacity:1;transform:translateY(0) scale(1)}}@keyframes slideInFromTopCentered{0%{opacity:0;transform:translate(-50%, -50%) translateY(-8px) scale(0.95)}100%{opacity:1;transform:translate(-50%, -50%) translateY(0) scale(1)}}.camera-selector-header{display:flex;justify-content:space-between;align-items:center;padding:8px 12px;border-bottom:1px solid rgba(255, 255, 255, 0.1)}.camera-selector-header span{font-weight:500;color:#ffffff;font-size:13px;opacity:0.9}.close-selector{width:20px;height:20px;border:none;background:none;color:rgba(255, 255, 255, 0.7);font-size:16px;cursor:pointer;display:flex;align-items:center;justify-content:center;border-radius:4px;transition:all 0.2s ease}.close-selector:hover{background:rgba(255, 255, 255, 0.1);color:#ffffff}.camera-list{padding:4px 0;max-height:240px;overflow-y:auto}.camera-option{width:100%;padding:8px 12px;border:none;background:none;text-align:left;cursor:pointer;transition:all 0.2s ease;display:flex;justify-content:flex-start;align-items:center;gap:8px;color:rgba(255, 255, 255, 0.9)}.camera-option:hover{background:rgba(255, 255, 255, 0.08)}.camera-option.selected{background:rgba(255, 255, 255, 0.12);color:#ffffff}.camera-label{font-size:13px;flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;margin-right:0;font-weight:400;display:flex;align-items:center;gap:6px}.camera-label .autofocus-icon{font-size:12px;color:#ffffff !important;opacity:0.8;flex-shrink:0}.selected-indicator{font-size:14px;color:#ffffff;opacity:0.9;flex-shrink:0}.device-info{padding:6px 12px;border-top:1px solid rgba(255, 255, 255, 0.1);background:rgba(255, 255, 255, 0.05)}.device-info small{color:rgba(255, 255, 255, 0.6);font-size:11px;text-transform:capitalize;font-weight:400}@media (max-width: 480px){.camera-controls{top:12px;right:12px;gap:6px}.camera-selector-button{height:36px;padding:0 12px;font-size:11px;border-radius:6px}.camera-selector-dropdown{right:12px;top:48px;min-width:240px;max-width:calc(100vw - 24px)}.camera-selector-header{padding:6px 10px}.camera-option{padding:6px 10px}.device-info{padding:4px 10px}}.watermark{position:absolute;bottom:12px;right:12px;z-index:15;pointer-events:none;opacity:0.7}.watermark img{height:24px;width:auto;filter:drop-shadow(0 1px 2px rgba(0, 0, 0, 0.3))}.component-status{position:absolute;top:16px;left:16px;background:rgba(0, 0, 0, 0.25);backdrop-filter:blur(20px);border-radius:12px;border:1px solid rgba(255, 255, 255, 0.1);display:flex;align-items:center;gap:6px;padding:6px 10px;z-index:40;height:32px;width:fit-content;animation:slideInFromTop 0.3s ease-out}.status-spinner{width:16px;height:16px;border:1px solid rgba(255, 255, 255, 0.3);border-top:1px solid #ffffff;border-radius:50%;animation:statusSpin 1s linear infinite;flex-shrink:0}.status-content{display:flex;flex-direction:column;gap:1px}.status-message{color:#ffffff;font-size:12px;font-weight:500;margin:0}.status-description{color:rgba(255, 255, 255, 0.7);font-size:10px;line-height:1.2;margin:0}@keyframes statusSpin{0%{transform:rotate(0deg)}100%{transform:rotate(360deg)}}@media (max-width: 480px){.component-status{top:12px;left:12px;padding:4px 8px;gap:4px;height:28px;border-radius:8px}.status-spinner{width:14px;height:14px}.status-message{font-size:11px}.status-description{font-size:9px}.guide-text{font-size:11px;padding:4px 8px;border-radius:8px}}.loading-overlay{position:absolute;top:0;left:0;width:100%;height:100%;background:rgba(0, 0, 0, 0.85);backdrop-filter:blur(4px);display:flex;flex-direction:column;align-items:center;justify-content:center;z-index:30;border-radius:8px}.loading-spinner{width:50px;height:50px;border:3px solid rgba(255, 255, 255, 0.15);border-top:3px solid #28a745;border-radius:50%;animation:spin 1s ease-in-out infinite;margin-bottom:16px}.loading-text{color:#ffffff;font-size:14px;font-weight:500;text-align:center;opacity:0.9;margin-bottom:4px}.loading-description{color:rgba(255, 255, 255, 0.7);font-size:12px;text-align:center;opacity:0.8}@keyframes spin{0%{transform:rotate(0deg)}100%{transform:rotate(360deg)}}.performance-monitor{position:absolute;top:70px;left:16px;background:rgba(0, 0, 0, 0.25);backdrop-filter:blur(20px);border-radius:12px;border:1px solid rgba(255, 255, 255, 0.1);z-index:35;width:fit-content;animation:slideInFromTop 0.3s ease-out;transition:all 0.3s ease}@keyframes slideInFromLeft{0%{opacity:0;transform:translateX(-20px) scale(0.95)}100%{opacity:1;transform:translateX(0) scale(1)}}.performance-expanded{padding:6px 10px}.metrics-row{display:flex;gap:8px;margin-bottom:4px}.metrics-row:last-child{margin-bottom:0}.metric-compact{display:flex;flex-direction:column;align-items:center;gap:2px;min-width:50px}.metric-label{font-size:9px;color:rgba(255, 255, 255, 0.6);font-weight:500;text-transform:uppercase;letter-spacing:0.3px}.metric-value{font-size:10px;font-weight:600;font-family:'Monaco', 'Menlo', 'Consolas', monospace;padding:2px 4px;border-radius:3px;text-align:center;white-space:nowrap}.metric-value.good{color:#4ade80;background:rgba(74, 222, 128, 0.1);border:1px solid rgba(74, 222, 128, 0.2)}.metric-value.warning{color:#fbbf24;background:rgba(251, 191, 36, 0.1);border:1px solid rgba(251, 191, 36, 0.2)}.metric-value.danger{color:#f87171;background:rgba(248, 113, 113, 0.1);border:1px solid rgba(248, 113, 113, 0.2)}@media (max-width: 480px){.performance-monitor{top:60px;left:12px;width:fit-content}.performance-expanded{padding:4px 8px}.metrics-row{gap:6px;margin-bottom:3px}.metric-compact{min-width:45px;gap:1px}.metric-label{font-size:8px}.metric-value{font-size:9px;padding:1px 3px}}.quality-monitor{position:absolute;top:200px;left:16px;background:rgba(0, 0, 0, 0.25);backdrop-filter:blur(20px);border-radius:12px;border:1px solid rgba(255, 255, 255, 0.1);z-index:35;width:fit-content;animation:slideInFromLeft 0.3s ease-out;transition:all 0.3s ease}.quality-text{padding:6px 10px;font-size:11px;color:white;font-family:'Monaco', 'Menlo', 'Consolas', monospace;line-height:1.4}.quality-fail{color:#ff9999}@keyframes pulse{0%,100%{opacity:1}50%{opacity:0.7}}.metric-value.danger{animation:pulse 2s infinite}@media (max-width: 480px){.quality-monitor{top:160px;left:12px}.quality-text{padding:4px 8px;font-size:10px}}.manual-capture-section{position:absolute;bottom:20px;left:50%;transform:translateX(-50%);z-index:25;display:flex;flex-direction:column;align-items:center;width:100%;max-width:300px}.manual-capture-button{pointer-events:auto;background:#fff;color:#333;border:none;border-radius:25px;padding:12px 24px;font-size:14px;font-weight:600;cursor:pointer;transition:all 0.3s ease}.manual-capture-button:hover{background:#f8f9fa}.manual-capture-button:active{background:#e9ecef;transform:translateY(1px)}.manual-capture-button:disabled{background:#e9ecef;color:#6c757d;cursor:not-allowed;transform:none}@media (max-width: 480px){.manual-capture-section{bottom:16px;max-width:280px}.manual-capture-button{padding:10px 20px;font-size:13px}}";
|
|
1259
|
+
const myComponentCss = ":host{display:block;width:100%;height:100%;font-family:system-ui, -apple-system, sans-serif;color:#1a1a1a}.detector-container{display:flex;flex-direction:column;align-items:center;width:100%;height:100%}h1{font-size:24px;font-weight:500;color:#333;margin:0 0 24px 0}.video-container{position:relative;width:100%;height:100%;background:#333;border:1px solid #e0e0e0;border-radius:8px;overflow:hidden}video,.detection-overlay{position:absolute;width:100%;height:100%;border-radius:8px}video.mirror,.detection-overlay.mirror{transform:rotateY(180deg)}.detection-overlay{z-index:1;pointer-events:none}video{z-index:0}.detection-box{transition:opacity 0.2s ease}.status{margin-top:16px;font-size:12px;color:#666}.overlay-mask{position:absolute;top:0;left:0;width:100%;height:100%;z-index:10;display:block;pointer-events:none}.card-outline{position:absolute;top:var(--mask-center-y, 50%);left:var(--mask-center-x, 50%);transform:translate(-50%, -50%);width:var(--mask-width, 88%);height:var(--mask-height, 55%);border:none;border-radius:4px;background:transparent;opacity:0.8}.side{position:absolute;background:#999;transition:background-color 0.3s ease;border-radius:1px}.side-top.aligned{border-left-color:#28a745;border-top-color:#28a745}.side-right.aligned{border-right-color:#28a745;border-top-color:#28a745}.side-bottom.aligned{border-left-color:#28a745;border-bottom-color:#28a745}.side-left.aligned{border-right-color:#28a745;border-bottom-color:#28a745}.side-top{top:-3px;left:-3px;width:30px;height:30px;border-left:6px solid #ffffff;border-top:6px solid #ffffff;background:transparent}.side-right{top:-3px;right:-3px;width:30px;height:30px;border-right:6px solid #ffffff;border-top:6px solid #ffffff;background:transparent}.side-bottom{bottom:-3px;left:-3px;width:30px;height:30px;border-left:6px solid #ffffff;border-bottom:6px solid #ffffff;background:transparent}.side-left{bottom:-3px;right:-3px;width:30px;height:30px;border-right:6px solid #ffffff;border-bottom:6px solid #ffffff;background:transparent}.guide-text{position:absolute;top:50%;left:50%;transform:translate(-50%, -50%);background:rgba(0, 0, 0, 0.25);backdrop-filter:blur(20px);border-radius:12px;border:1px solid rgba(255, 255, 255, 0.1);color:#ffffff;font-size:12px;font-weight:500;text-align:center;white-space:normal;padding:13px;max-width:300px;z-index:20;height:fit-content;width:fit-content;animation:slideInFromTopCentered 0.3s ease-out}.quality-indicator{position:absolute;top:20px;right:20px;display:flex;align-items:center;gap:8px;background:rgba(0, 0, 0, 0.8);padding:8px 12px;border-radius:20px;z-index:25;transition:all 0.3s ease}.quality-indicator.warning{background:rgba(255, 152, 0, 0.9)}.quality-indicator.good{background:rgba(76, 175, 80, 0.9)}.quality-score{color:#fff;font-size:12px;font-weight:600;min-width:30px;text-align:center}.quality-status{width:20px;height:20px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:14px;font-weight:bold;color:#fff;transition:all 0.3s ease}.quality-status.ready{background:#4CAF50}.quality-status.not-ready{background:#f44336}.card-outline.quality-warning{animation:qualityWarning 1s ease-in-out infinite alternate}@keyframes qualityWarning{0%{box-shadow:0 0 0 3px rgba(255, 152, 0, 0.6)}100%{box-shadow:0 0 0 6px rgba(255, 152, 0, 0.3)}}.capture-animation{position:absolute;top:0;left:0;width:100%;height:100%;background:#fff;opacity:0;z-index:30;pointer-events:none;animation:captureFlash 0.6s ease-out}@keyframes captureFlash{0%{opacity:0}15%{opacity:0.8}30%{opacity:0}45%{opacity:0.4}60%{opacity:0}100%{opacity:0}}.card-outline.capturing{animation:pulseGreen 0.6s ease-out}@keyframes pulseGreen{0%{border-color:#28a745;box-shadow:0 0 0 4px rgba(40, 167, 69, 0)}50%{border-color:#28a745;box-shadow:0 0 0 8px rgba(40, 167, 69, 0.6)}100%{border-color:#28a745;box-shadow:0 0 0 4px rgba(40, 167, 69, 0)}}.flip-animation{position:absolute;top:50%;left:50%;transform:translate(-50%, -50%);z-index:35;pointer-events:none;opacity:0;animation:showFlipInstruction 3s ease-in-out;display:flex;flex-direction:column;align-items:center;justify-content:center}.id-card-icon{width:80px;height:50px;background:linear-gradient(135deg, #6c757d 0%, #495057 100%);border-radius:8px;position:relative;animation:flipCard 2s ease-in-out infinite;box-shadow:0 4px 12px rgba(0, 0, 0, 0.3)}.id-card-icon::before{content:'';position:absolute;top:8px;left:8px;width:16px;height:12px;background:rgba(255, 255, 255, 0.9);border-radius:2px}.id-card-icon::after{content:'';position:absolute;top:25px;left:8px;width:64px;height:3px;background:rgba(255, 255, 255, 0.7);border-radius:1px;box-shadow:0 6px 0 rgba(255, 255, 255, 0.7), 0 12px 0 rgba(255, 255, 255, 0.7)}.flip-text{margin-top:12px;color:#fff;font-size:14px;font-weight:600;text-align:center;background:rgba(128, 128, 128, 0.8);padding:12px 20px;border-radius:20px;max-width:300px}@keyframes showFlipInstruction{0%{opacity:0;transform:translate(-50%, -50%) scale(0.8)}15%{opacity:1;transform:translate(-50%, -50%) scale(1)}85%{opacity:1;transform:translate(-50%, -50%) scale(1)}100%{opacity:0;transform:translate(-50%, -50%) scale(0.8)}}@keyframes flipCard{0%{transform:rotateY(0deg)}50%{transform:rotateY(180deg)}100%{transform:rotateY(360deg)}}.success-animation{position:absolute;top:50%;left:50%;transform:translate(-50%, -50%);z-index:35;pointer-events:none;opacity:0;animation:showSuccessMessage 3s ease-in-out;display:flex;flex-direction:column;align-items:center;justify-content:center}.check-icon{width:80px;height:80px;background:linear-gradient(135deg, #6c757d 0%, #495057 100%);border-radius:50%;position:relative;animation:bounceSuccess 0.6s ease-out;box-shadow:0 4px 16px rgba(108, 117, 125, 0.4);display:flex;align-items:center;justify-content:center}.check-icon::before{content:'';position:absolute;width:20px;height:35px;border:4px solid #fff;border-top:none;border-left:none;transform:rotate(45deg);animation:drawCheck 0.4s ease-out 0.2s both}.success-text{margin-top:16px;color:#fff;font-size:14px;font-weight:600;text-align:center;background:rgba(128, 128, 128, 0.8);padding:12px 20px;border-radius:20px;max-width:300px;animation:fadeInUp 0.5s ease-out 0.4s both}@keyframes showSuccessMessage{0%{opacity:0;transform:translate(-50%, -50%) scale(0.5)}15%{opacity:1;transform:translate(-50%, -50%) scale(1)}85%{opacity:1;transform:translate(-50%, -50%) scale(1)}100%{opacity:0;transform:translate(-50%, -50%) scale(0.9)}}@keyframes bounceSuccess{0%{transform:scale(0)}50%{transform:scale(1.1)}100%{transform:scale(1)}}@keyframes drawCheck{0%{width:0;height:0}50%{width:20px;height:0}100%{width:20px;height:35px}}@keyframes fadeInUp{0%{opacity:0;transform:translateY(20px)}100%{opacity:1;transform:translateY(0)}}.skip-section{position:absolute;bottom:20px;left:50%;transform:translateX(-50%);z-index:25;display:flex;flex-direction:column;align-items:center;width:100%;max-width:300px}.back-capture-section{position:absolute;bottom:20px;left:50%;transform:translateX(-50%);z-index:25;display:flex;flex-direction:column;align-items:center;width:100%;max-width:320px}.back-capture-buttons{display:flex;gap:12px;align-items:center;justify-content:center;flex-wrap:wrap}.capture-button{pointer-events:auto;background:#fff;color:#333;border:none;border-radius:25px;padding:12px 24px;font-size:14px;font-weight:600;cursor:pointer;transition:all 0.3s ease}.capture-button:hover{background:#f8f9fa}.capture-button:active{background:#e9ecef;transform:translateY(1px)}.capture-button:disabled,.capture-button.primary-button:disabled{cursor:not-allowed !important;transform:none !important;opacity:0.7 !important;transition:none !important}@media (max-width: 480px){.back-capture-section{bottom:16px;max-width:300px}.back-capture-buttons{flex-direction:column;gap:8px;width:100%}.capture-button,.back-capture-buttons .skip-button{width:100%;max-width:200px;padding:10px 20px;font-size:13px}.capture-button:disabled,.capture-button.primary-button:disabled,.skip-button:disabled{opacity:0.7 !important;cursor:not-allowed !important}}.skip-explanation{background:rgba(0, 0, 0, 0.5);color:#ffffff;padding:10px 16px;border-radius:8px;font-size:14px;font-weight:500;text-align:center;margin-bottom:12px;box-shadow:0 2px 8px rgba(0, 0, 0, 0.2);backdrop-filter:blur(12px);border:1px solid rgba(255, 255, 255, 0.1);animation:fadeIn 0.3s ease-in-out}@keyframes fadeIn{from{opacity:0;transform:translateY(10px)}to{opacity:1;transform:translateY(0)}}.guide-text.performance-warning-text{animation:gentlePulse 2s ease-in-out infinite;background:rgba(255, 107, 107, 0.3);border:1px solid rgba(255, 107, 107, 0.5)}@keyframes gentlePulse{0%,100%{transform:translate(-50%, -50%) scale(1);background:rgba(255, 107, 107, 0.3)}50%{transform:translate(-50%, -50%) scale(1.02);background:rgba(255, 107, 107, 0.4)}}.skip-button{pointer-events:auto;background:#fff;color:#333;border:none;border-radius:25px;padding:12px 24px;font-size:14px;font-weight:600;cursor:pointer;transition:all 0.3s ease}.skip-button:hover{background:#f8f9fa}.skip-button:active{background:#e9ecef;transform:translateY(1px)}.skip-button:disabled{cursor:not-allowed !important;transform:none !important;opacity:0.7 !important;transition:none !important}.camera-controls{position:absolute;top:16px;right:16px;z-index:25;display:flex;gap:8px;pointer-events:auto;animation:slideInFromTop 0.3s ease-out}.camera-selector-button{height:32px;padding:0 10px;border:none;border-radius:8px;background:rgba(0, 0, 0, 0.25);backdrop-filter:blur(12px);color:#ffffff;font-size:12px;font-weight:500;cursor:pointer;transition:all 0.2s ease;display:flex;align-items:center;justify-content:center;border:1px solid rgba(255, 255, 255, 0.1);white-space:nowrap;min-width:fit-content}.camera-selector-button:hover{background:rgba(0, 0, 0, 0.8);border-color:rgba(255, 255, 255, 0.2);transform:translateY(-1px)}.camera-selector-button:active{transform:translateY(0);background:rgba(0, 0, 0, 0.9)}.camera-selector-button:disabled,.camera-selector-button.loading{opacity:0.7;cursor:not-allowed;pointer-events:none}.camera-selector-button:disabled:hover,.camera-selector-button.loading:hover{transform:none;background:rgba(0, 0, 0, 0.6);border-color:rgba(255, 255, 255, 0.1)}.button-spinner{width:16px;height:16px;border:2px solid rgba(0, 0, 0, 0.2);border-top:2px solid #333333;border-radius:50%;animation:spin 1s linear infinite;display:inline-block;margin-right:8px;vertical-align:middle}@keyframes spin{0%{transform:rotate(0deg)}100%{transform:rotate(360deg)}}.camera-selector-dropdown{position:absolute;top:56px;right:16px;z-index:30;background:rgba(0, 0, 0, 0.25);backdrop-filter:blur(20px);border-radius:12px;min-width:260px;max-width:300px;border:1px solid rgba(255, 255, 255, 0.1);overflow:hidden;pointer-events:auto;animation:slideInFromTop 0.3s ease-out}@keyframes slideInFromTop{0%{opacity:0;transform:translateY(-8px) scale(0.95)}100%{opacity:1;transform:translateY(0) scale(1)}}@keyframes slideInFromTopCentered{0%{opacity:0;transform:translate(-50%, -50%) translateY(-8px) scale(0.95)}100%{opacity:1;transform:translate(-50%, -50%) translateY(0) scale(1)}}.camera-selector-header{display:flex;justify-content:space-between;align-items:center;padding:8px 12px;border-bottom:1px solid rgba(255, 255, 255, 0.1)}.camera-selector-header span{font-weight:500;color:#ffffff;font-size:13px;opacity:0.9}.close-selector{width:20px;height:20px;border:none;background:none;color:rgba(255, 255, 255, 0.7);font-size:16px;cursor:pointer;display:flex;align-items:center;justify-content:center;border-radius:4px;transition:all 0.2s ease}.close-selector:hover{background:rgba(255, 255, 255, 0.1);color:#ffffff}.camera-list{padding:4px 0;max-height:240px;overflow-y:auto}.camera-option{width:100%;padding:8px 12px;border:none;background:none;text-align:left;cursor:pointer;transition:all 0.2s ease;display:flex;justify-content:flex-start;align-items:center;gap:8px;color:rgba(255, 255, 255, 0.9)}.camera-option:hover{background:rgba(255, 255, 255, 0.08)}.camera-option.selected{background:rgba(255, 255, 255, 0.12);color:#ffffff}.camera-label{font-size:13px;flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;margin-right:0;font-weight:400;display:flex;align-items:center;gap:6px}.camera-label .autofocus-icon{font-size:12px;color:#ffffff !important;opacity:0.8;flex-shrink:0}.selected-indicator{font-size:14px;color:#ffffff;opacity:0.9;flex-shrink:0}.device-info{padding:6px 12px;border-top:1px solid rgba(255, 255, 255, 0.1);background:rgba(255, 255, 255, 0.05)}.device-info small{color:rgba(255, 255, 255, 0.6);font-size:11px;text-transform:capitalize;font-weight:400}@media (max-width: 480px){.camera-controls{top:12px;right:12px;gap:6px}.camera-selector-button{height:36px;padding:0 12px;font-size:11px;border-radius:6px}.camera-selector-dropdown{right:12px;top:48px;min-width:240px;max-width:calc(100vw - 24px)}.camera-selector-header{padding:6px 10px}.camera-option{padding:6px 10px}.device-info{padding:4px 10px}}.watermark{position:absolute;bottom:12px;right:12px;z-index:15;pointer-events:none;opacity:0.7}.watermark img{height:24px;width:auto;filter:drop-shadow(0 1px 2px rgba(0, 0, 0, 0.3))}.component-status{position:absolute;top:16px;left:16px;background:rgba(0, 0, 0, 0.25);backdrop-filter:blur(20px);border-radius:12px;border:1px solid rgba(255, 255, 255, 0.1);display:flex;align-items:center;gap:6px;padding:6px 10px;z-index:40;height:32px;width:fit-content;animation:slideInFromTop 0.3s ease-out}.status-spinner{width:16px;height:16px;border:1px solid rgba(255, 255, 255, 0.3);border-top:1px solid #ffffff;border-radius:50%;animation:statusSpin 1s linear infinite;flex-shrink:0}.status-content{display:flex;flex-direction:column;gap:1px}.status-message{color:#ffffff;font-size:12px;font-weight:500;margin:0}.status-description{color:rgba(255, 255, 255, 0.7);font-size:10px;line-height:1.2;margin:0}@keyframes statusSpin{0%{transform:rotate(0deg)}100%{transform:rotate(360deg)}}@media (max-width: 480px){.component-status{top:12px;left:12px;padding:4px 8px;gap:4px;height:28px;border-radius:8px}.status-spinner{width:14px;height:14px}.status-message{font-size:11px}.status-description{font-size:9px}.guide-text{font-size:11px;padding:4px 8px;border-radius:8px}}.loading-overlay{position:absolute;top:0;left:0;width:100%;height:100%;background:rgba(0, 0, 0, 0.85);backdrop-filter:blur(4px);display:flex;flex-direction:column;align-items:center;justify-content:center;z-index:30;border-radius:8px}.loading-spinner{width:50px;height:50px;border:3px solid rgba(255, 255, 255, 0.15);border-top:3px solid #28a745;border-radius:50%;animation:spin 1s ease-in-out infinite;margin-bottom:16px}.loading-text{color:#ffffff;font-size:14px;font-weight:500;text-align:center;opacity:0.9;margin-bottom:4px}.loading-description{color:rgba(255, 255, 255, 0.7);font-size:12px;text-align:center;opacity:0.8}@keyframes spin{0%{transform:rotate(0deg)}100%{transform:rotate(360deg)}}.performance-monitor{position:absolute;top:70px;left:16px;background:rgba(0, 0, 0, 0.25);backdrop-filter:blur(20px);border-radius:12px;border:1px solid rgba(255, 255, 255, 0.1);z-index:35;width:fit-content;animation:slideInFromTop 0.3s ease-out;transition:all 0.3s ease}@keyframes slideInFromLeft{0%{opacity:0;transform:translateX(-20px) scale(0.95)}100%{opacity:1;transform:translateX(0) scale(1)}}.performance-expanded{padding:6px 10px}.metrics-row{display:flex;gap:8px;margin-bottom:4px}.metrics-row:last-child{margin-bottom:0}.metric-compact{display:flex;flex-direction:column;align-items:center;gap:2px;min-width:50px}.metric-label{font-size:9px;color:rgba(255, 255, 255, 0.6);font-weight:500;text-transform:uppercase;letter-spacing:0.3px}.metric-value{font-size:10px;font-weight:600;font-family:'Monaco', 'Menlo', 'Consolas', monospace;padding:2px 4px;border-radius:3px;text-align:center;white-space:nowrap}.metric-value.good{color:#4ade80;background:rgba(74, 222, 128, 0.1);border:1px solid rgba(74, 222, 128, 0.2)}.metric-value.warning{color:#fbbf24;background:rgba(251, 191, 36, 0.1);border:1px solid rgba(251, 191, 36, 0.2)}.metric-value.danger{color:#f87171;background:rgba(248, 113, 113, 0.1);border:1px solid rgba(248, 113, 113, 0.2)}@media (max-width: 480px){.performance-monitor{top:60px;left:12px;width:fit-content}.performance-expanded{padding:4px 8px}.metrics-row{gap:6px;margin-bottom:3px}.metric-compact{min-width:45px;gap:1px}.metric-label{font-size:8px}.metric-value{font-size:9px;padding:1px 3px}}.quality-monitor{position:absolute;top:200px;left:16px;background:rgba(0, 0, 0, 0.25);backdrop-filter:blur(20px);border-radius:12px;border:1px solid rgba(255, 255, 255, 0.1);z-index:35;width:fit-content;animation:slideInFromLeft 0.3s ease-out;transition:all 0.3s ease}.quality-text{padding:6px 10px;font-size:11px;color:white;font-family:'Monaco', 'Menlo', 'Consolas', monospace;line-height:1.4}.quality-fail{color:#ff9999}@keyframes pulse{0%,100%{opacity:1}50%{opacity:0.7}}.metric-value.danger{animation:pulse 2s infinite}@media (max-width: 480px){.quality-monitor{top:160px;left:12px}.quality-text{padding:4px 8px;font-size:10px}}.manual-capture-section{position:absolute;bottom:20px;left:50%;transform:translateX(-50%);z-index:25;display:flex;flex-direction:column;align-items:center;width:100%;max-width:300px}.manual-capture-button{pointer-events:auto;background:#fff;color:#333;border:none;border-radius:25px;padding:12px 24px;font-size:14px;font-weight:600;cursor:pointer;transition:all 0.3s ease}.manual-capture-button:hover{background:#f8f9fa}.manual-capture-button:active{background:#e9ecef;transform:translateY(1px)}.manual-capture-button:disabled{cursor:not-allowed !important;transform:none !important;opacity:0.7 !important;transition:none !important}@media (max-width: 480px){.manual-capture-section{bottom:16px;max-width:280px}.manual-capture-button{padding:10px 20px;font-size:13px}.manual-capture-button:disabled{opacity:0.7 !important;cursor:not-allowed !important}}";
|
|
1093
1260
|
|
|
1094
1261
|
const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H {
|
|
1095
1262
|
constructor() {
|
|
@@ -1150,6 +1317,7 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
|
|
|
1150
1317
|
performanceDegradedMode = false; // Auto-switched to manual mode due to performance
|
|
1151
1318
|
showPerformanceMessage = false; // Show performance message temporarily
|
|
1152
1319
|
captureStateVersion = 0; // Triggers re-render when StateManager changes
|
|
1320
|
+
processingButton = null; // Track which button is processing
|
|
1153
1321
|
// Services
|
|
1154
1322
|
serviceContainer;
|
|
1155
1323
|
eventBus;
|
|
@@ -1201,14 +1369,19 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
|
|
|
1201
1369
|
canvasPool = [];
|
|
1202
1370
|
MAX_CANVAS_POOL_SIZE = 3;
|
|
1203
1371
|
async componentDidLoad() {
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
this.
|
|
1372
|
+
try {
|
|
1373
|
+
this.updateStatus('Preparando capturador...', 'Configurando herramientas de captura', 'initializing');
|
|
1374
|
+
await this.initializeServices();
|
|
1375
|
+
this.updateStatus('Preparando funciones...', 'Configurando herramientas de trabajo', 'initializing');
|
|
1376
|
+
await this.setupEventListeners();
|
|
1377
|
+
this.updateStatus('Configurando cámara...', 'Detectando dispositivos disponibles', 'initializing');
|
|
1378
|
+
await this.initializeComponent();
|
|
1379
|
+
if (this.debug) {
|
|
1380
|
+
this.initializePerformanceMonitor();
|
|
1381
|
+
}
|
|
1382
|
+
}
|
|
1383
|
+
catch (error) {
|
|
1384
|
+
this.updateStatus('Error de inicialización', error.message, 'error');
|
|
1212
1385
|
}
|
|
1213
1386
|
}
|
|
1214
1387
|
async initializeServices() {
|
|
@@ -1503,7 +1676,13 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
|
|
|
1503
1676
|
}
|
|
1504
1677
|
try {
|
|
1505
1678
|
this.exitSession();
|
|
1506
|
-
|
|
1679
|
+
// Aggressive cleanup when stopping capture
|
|
1680
|
+
const cleanupResult = await this.aggressiveResourceCleanup();
|
|
1681
|
+
return {
|
|
1682
|
+
success: true,
|
|
1683
|
+
resourcesFreed: cleanupResult.freed,
|
|
1684
|
+
cleanupErrors: cleanupResult.errors
|
|
1685
|
+
};
|
|
1507
1686
|
}
|
|
1508
1687
|
catch (error) {
|
|
1509
1688
|
return { success: false, error: error.message };
|
|
@@ -1523,8 +1702,14 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
|
|
|
1523
1702
|
}
|
|
1524
1703
|
}
|
|
1525
1704
|
async skipBackCapture() {
|
|
1705
|
+
console.log('🟡 Botón clicked: Saltar reverso');
|
|
1706
|
+
// Set processing state immediately
|
|
1707
|
+
this.processingButton = 'skip-back';
|
|
1708
|
+
// Add small delay to show spinner
|
|
1709
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
1526
1710
|
const readyCheck = this.isComponentReady();
|
|
1527
1711
|
if (!readyCheck.ready) {
|
|
1712
|
+
this.processingButton = null;
|
|
1528
1713
|
return { success: false, error: readyCheck.message };
|
|
1529
1714
|
}
|
|
1530
1715
|
try {
|
|
@@ -1541,6 +1726,10 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
|
|
|
1541
1726
|
catch (error) {
|
|
1542
1727
|
return { success: false, error: error.message };
|
|
1543
1728
|
}
|
|
1729
|
+
finally {
|
|
1730
|
+
// Always clear processing state when done
|
|
1731
|
+
this.processingButton = null;
|
|
1732
|
+
}
|
|
1544
1733
|
}
|
|
1545
1734
|
async getStatus() {
|
|
1546
1735
|
const readyCheck = this.isComponentReady();
|
|
@@ -1718,9 +1907,76 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
|
|
|
1718
1907
|
// Always allow getting capture delay, even during initialization
|
|
1719
1908
|
return this.captureDelay;
|
|
1720
1909
|
}
|
|
1910
|
+
async getResourceReport() {
|
|
1911
|
+
// Public method to get resource usage report
|
|
1912
|
+
return this.getResourceReportInternal();
|
|
1913
|
+
}
|
|
1914
|
+
async forceResourceCleanup() {
|
|
1915
|
+
// Public method to force aggressive resource cleanup
|
|
1916
|
+
const readyCheck = this.isComponentReady();
|
|
1917
|
+
if (!readyCheck.ready) {
|
|
1918
|
+
return { success: false, error: readyCheck.message };
|
|
1919
|
+
}
|
|
1920
|
+
try {
|
|
1921
|
+
const cleanupResult = await this.aggressiveResourceCleanup();
|
|
1922
|
+
return {
|
|
1923
|
+
success: true,
|
|
1924
|
+
resourcesFreed: cleanupResult.freed,
|
|
1925
|
+
errors: cleanupResult.errors,
|
|
1926
|
+
timestamp: new Date().toISOString()
|
|
1927
|
+
};
|
|
1928
|
+
}
|
|
1929
|
+
catch (error) {
|
|
1930
|
+
return { success: false, error: error.message };
|
|
1931
|
+
}
|
|
1932
|
+
}
|
|
1933
|
+
// Memory and device capability detection
|
|
1934
|
+
checkDeviceCapabilities() {
|
|
1935
|
+
const deviceMemory = navigator.deviceMemory;
|
|
1936
|
+
const hardwareConcurrency = navigator.hardwareConcurrency || 1;
|
|
1937
|
+
const isLowEndDevice = hardwareConcurrency <= 2;
|
|
1938
|
+
const isMobile = /Android|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
|
|
1939
|
+
const isSafari = /Safari/.test(navigator.userAgent) && !/Chrome/.test(navigator.userAgent);
|
|
1940
|
+
// Si tenemos info de memoria del dispositivo
|
|
1941
|
+
if (deviceMemory && deviceMemory < 4) {
|
|
1942
|
+
return {
|
|
1943
|
+
canUseML: false,
|
|
1944
|
+
reason: `Memoria del dispositivo (${deviceMemory}GB) insuficiente para detección automática. Modo manual activado para mejor rendimiento.`,
|
|
1945
|
+
memoryMB: deviceMemory * 1024
|
|
1946
|
+
};
|
|
1947
|
+
}
|
|
1948
|
+
// Dispositivos móviles de gama baja
|
|
1949
|
+
if (isMobile && isLowEndDevice) {
|
|
1950
|
+
return {
|
|
1951
|
+
canUseML: false,
|
|
1952
|
+
reason: 'Dispositivo móvil optimizado detectado. Modo manual activado para mejor rendimiento y duración de batería.',
|
|
1953
|
+
memoryMB: null
|
|
1954
|
+
};
|
|
1955
|
+
}
|
|
1956
|
+
// Safari tiene limitaciones conocidas con WASM
|
|
1957
|
+
if (isSafari) {
|
|
1958
|
+
return {
|
|
1959
|
+
canUseML: false,
|
|
1960
|
+
reason: 'Safari detectado. Modo manual activado por compatibilidad optimizada con este navegador.',
|
|
1961
|
+
memoryMB: null
|
|
1962
|
+
};
|
|
1963
|
+
}
|
|
1964
|
+
return {
|
|
1965
|
+
canUseML: true,
|
|
1966
|
+
reason: 'Dispositivo compatible con detección automática.',
|
|
1967
|
+
memoryMB: deviceMemory ? deviceMemory * 1024 : null
|
|
1968
|
+
};
|
|
1969
|
+
}
|
|
1721
1970
|
// DETECTION METHODS
|
|
1722
1971
|
async startDetection() {
|
|
1723
1972
|
try {
|
|
1973
|
+
// Verificar capacidades del dispositivo antes de cargar modelos
|
|
1974
|
+
const capabilities = this.checkDeviceCapabilities();
|
|
1975
|
+
// Si el dispositivo no puede usar ML o tenemos problemas de memoria, usar modo manual
|
|
1976
|
+
if (!capabilities.canUseML && this.useDocumentDetector) {
|
|
1977
|
+
this.switchToManualModeWithWarning(capabilities.reason);
|
|
1978
|
+
return;
|
|
1979
|
+
}
|
|
1724
1980
|
// Paso 1: Verificar y cargar modelos si es necesario
|
|
1725
1981
|
if (!this.detectionService.isModelLoaded()) {
|
|
1726
1982
|
const loadStartTime = performance.now();
|
|
@@ -1730,16 +1986,32 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
|
|
|
1730
1986
|
await this.detectionService.loadModel();
|
|
1731
1987
|
}
|
|
1732
1988
|
catch (modelError) {
|
|
1733
|
-
|
|
1734
|
-
|
|
1989
|
+
// Si es un error de memoria, cambiar a modo manual con mensaje específico
|
|
1990
|
+
if (modelError.message.includes('Out of memory') || modelError.message.includes('no available backend')) {
|
|
1991
|
+
const memoryInfo = navigator.deviceMemory ? `(${navigator.deviceMemory}GB disponible)` : '';
|
|
1992
|
+
this.switchToManualModeWithWarning(`Memoria insuficiente para detección automática ${memoryInfo}. Modo manual activado - funciona igual de bien!`);
|
|
1993
|
+
return;
|
|
1994
|
+
}
|
|
1995
|
+
else {
|
|
1996
|
+
this.switchToManualModeWithError('Error al cargar modelo de detección');
|
|
1997
|
+
throw modelError;
|
|
1998
|
+
}
|
|
1735
1999
|
}
|
|
1736
2000
|
this.updateStatus('Optimizando detección...', 'Configurando reconocimiento de documentos', 'loading');
|
|
1737
2001
|
try {
|
|
1738
2002
|
await this.detectionService.loadClassificationModel();
|
|
1739
2003
|
}
|
|
1740
2004
|
catch (classificationError) {
|
|
1741
|
-
|
|
1742
|
-
|
|
2005
|
+
// Si es un error de memoria, cambiar a modo manual con mensaje específico
|
|
2006
|
+
if (classificationError.message.includes('Out of memory') || classificationError.message.includes('no available backend')) {
|
|
2007
|
+
const memoryInfo = navigator.deviceMemory ? `(${navigator.deviceMemory}GB disponible)` : '';
|
|
2008
|
+
this.switchToManualModeWithWarning(`Memoria insuficiente para clasificación automática ${memoryInfo}. Modo manual activado para optimizar rendimiento.`);
|
|
2009
|
+
return;
|
|
2010
|
+
}
|
|
2011
|
+
else {
|
|
2012
|
+
this.switchToManualModeWithError('Error al cargar modelo de clasificación');
|
|
2013
|
+
throw classificationError;
|
|
2014
|
+
}
|
|
1743
2015
|
}
|
|
1744
2016
|
const loadEndTime = performance.now();
|
|
1745
2017
|
const loadTime = loadEndTime - loadStartTime;
|
|
@@ -1750,11 +2022,27 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
|
|
|
1750
2022
|
}
|
|
1751
2023
|
// Paso 2: Detectar y configurar dispositivos
|
|
1752
2024
|
this.updateStatus('Configurando cámara...', 'Buscando dispositivos disponibles', 'loading');
|
|
1753
|
-
|
|
1754
|
-
|
|
2025
|
+
try {
|
|
2026
|
+
await this.cameraService.enumerateDevices();
|
|
2027
|
+
}
|
|
2028
|
+
catch (enumerateError) {
|
|
2029
|
+
throw new Error(`Error al enumerar dispositivos: ${enumerateError.message}`);
|
|
2030
|
+
}
|
|
2031
|
+
try {
|
|
2032
|
+
await this.updateCameraInfoWithAutofocus();
|
|
2033
|
+
}
|
|
2034
|
+
catch (cameraInfoError) {
|
|
2035
|
+
throw new Error(`Error al actualizar información de cámara: ${cameraInfoError.message}`);
|
|
2036
|
+
}
|
|
1755
2037
|
// Paso 3: Configurar cámara seleccionada
|
|
1756
2038
|
this.updateStatus('Ajustando cámara...', 'Configurando calidad de imagen', 'loading');
|
|
1757
|
-
|
|
2039
|
+
let stream;
|
|
2040
|
+
try {
|
|
2041
|
+
stream = await this.cameraService.setupCamera();
|
|
2042
|
+
}
|
|
2043
|
+
catch (setupError) {
|
|
2044
|
+
throw new Error(`Error al configurar cámara: ${setupError.message}`);
|
|
2045
|
+
}
|
|
1758
2046
|
// Paso 4: Inicializar video y mostrar estado de análisis inicial
|
|
1759
2047
|
if (this.useDocumentDetector) {
|
|
1760
2048
|
this.updateStatus('Analizando estabilidad...', 'Evaluando rendimiento del sistema', 'loading');
|
|
@@ -1762,7 +2050,12 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
|
|
|
1762
2050
|
else {
|
|
1763
2051
|
this.updateStatus('Captura activa', 'Posicione su documento y use el botón para capturar', 'active');
|
|
1764
2052
|
}
|
|
1765
|
-
|
|
2053
|
+
try {
|
|
2054
|
+
await this.initializeVideoStream(stream);
|
|
2055
|
+
}
|
|
2056
|
+
catch (videoError) {
|
|
2057
|
+
throw new Error(`Error al inicializar video: ${videoError.message}`);
|
|
2058
|
+
}
|
|
1766
2059
|
// Paso 5: Calibrar máscara
|
|
1767
2060
|
if (this.detectionContainer) {
|
|
1768
2061
|
const container = this.detectionContainer.parentElement;
|
|
@@ -1972,11 +2265,182 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
|
|
|
1972
2265
|
}
|
|
1973
2266
|
// Clear canvas pool
|
|
1974
2267
|
this.canvasPool = [];
|
|
2268
|
+
// Clean DOM references and memory leaks
|
|
2269
|
+
this.cleanupDOMReferences();
|
|
1975
2270
|
this.detectionBoxes = [];
|
|
1976
2271
|
this.alignmentStartTime = undefined;
|
|
1977
2272
|
this.hasDocumentDetected = false;
|
|
1978
2273
|
this.serviceContainer?.cleanup();
|
|
1979
2274
|
}
|
|
2275
|
+
// AGGRESSIVE RESOURCE CLEANUP METHODS
|
|
2276
|
+
// Clean DOM references and memory leaks
|
|
2277
|
+
cleanupDOMReferences() {
|
|
2278
|
+
// Remove event listeners that could maintain references
|
|
2279
|
+
if (this.videoRef) {
|
|
2280
|
+
// Remove common event listeners (if any were added)
|
|
2281
|
+
this.videoRef.srcObject = null;
|
|
2282
|
+
this.videoRef = undefined;
|
|
2283
|
+
}
|
|
2284
|
+
// Clean detection container
|
|
2285
|
+
if (this.detectionContainer) {
|
|
2286
|
+
this.detectionContainer = undefined;
|
|
2287
|
+
}
|
|
2288
|
+
// Clean references to images in the DOM and revoke blob URLs
|
|
2289
|
+
const images = this.el.shadowRoot?.querySelectorAll('img');
|
|
2290
|
+
images?.forEach(img => {
|
|
2291
|
+
if (img.src.startsWith('blob:')) {
|
|
2292
|
+
URL.revokeObjectURL(img.src);
|
|
2293
|
+
}
|
|
2294
|
+
img.src = '';
|
|
2295
|
+
});
|
|
2296
|
+
}
|
|
2297
|
+
// Clear browser cache and storage
|
|
2298
|
+
async clearBrowserCache() {
|
|
2299
|
+
try {
|
|
2300
|
+
// 1. Clear Service Worker cache if exists
|
|
2301
|
+
if ('serviceWorker' in navigator) {
|
|
2302
|
+
const registrations = await navigator.serviceWorker.getRegistrations();
|
|
2303
|
+
await Promise.all(registrations.map(registration => registration.unregister()));
|
|
2304
|
+
}
|
|
2305
|
+
// 2. Clear Cache API specific to the component
|
|
2306
|
+
const cacheNames = await caches.keys();
|
|
2307
|
+
const componentCaches = cacheNames.filter(name => name.includes('jaak') || name.includes('stamps'));
|
|
2308
|
+
await Promise.all(componentCaches.map(cacheName => caches.delete(cacheName)));
|
|
2309
|
+
// 3. Force garbage collection if available (only in dev)
|
|
2310
|
+
if (this.debug && window.gc) {
|
|
2311
|
+
window.gc();
|
|
2312
|
+
}
|
|
2313
|
+
}
|
|
2314
|
+
catch (error) {
|
|
2315
|
+
console.warn('Error clearing browser cache:', error);
|
|
2316
|
+
}
|
|
2317
|
+
}
|
|
2318
|
+
// Clear local storage specific to the component
|
|
2319
|
+
async clearLocalStorage() {
|
|
2320
|
+
try {
|
|
2321
|
+
// Clear localStorage specific to the component
|
|
2322
|
+
const keysToRemove = [];
|
|
2323
|
+
for (let i = 0; i < localStorage.length; i++) {
|
|
2324
|
+
const key = localStorage.key(i);
|
|
2325
|
+
if (key && (key.includes('jaak') || key.includes('onnx') || key.includes('stamps'))) {
|
|
2326
|
+
keysToRemove.push(key);
|
|
2327
|
+
}
|
|
2328
|
+
}
|
|
2329
|
+
keysToRemove.forEach(key => localStorage.removeItem(key));
|
|
2330
|
+
// Clear sessionStorage
|
|
2331
|
+
const sessionKeysToRemove = [];
|
|
2332
|
+
for (let i = 0; i < sessionStorage.length; i++) {
|
|
2333
|
+
const key = sessionStorage.key(i);
|
|
2334
|
+
if (key && (key.includes('jaak') || key.includes('onnx'))) {
|
|
2335
|
+
sessionKeysToRemove.push(key);
|
|
2336
|
+
}
|
|
2337
|
+
}
|
|
2338
|
+
sessionKeysToRemove.forEach(key => sessionStorage.removeItem(key));
|
|
2339
|
+
}
|
|
2340
|
+
catch (error) {
|
|
2341
|
+
console.warn('Error clearing local storage:', error);
|
|
2342
|
+
}
|
|
2343
|
+
}
|
|
2344
|
+
// Aggressive resource cleanup combining all strategies
|
|
2345
|
+
async aggressiveResourceCleanup() {
|
|
2346
|
+
const freed = [];
|
|
2347
|
+
const errors = [];
|
|
2348
|
+
try {
|
|
2349
|
+
// 1. Release ONNX resources
|
|
2350
|
+
this.releaseOnnxResources();
|
|
2351
|
+
freed.push('ONNX models');
|
|
2352
|
+
// 2. Clear model cache in detection service
|
|
2353
|
+
if (this.detectionService) {
|
|
2354
|
+
await this.detectionService.clearModelCache();
|
|
2355
|
+
freed.push('Model cache');
|
|
2356
|
+
}
|
|
2357
|
+
// 3. Clear browser cache
|
|
2358
|
+
await this.clearBrowserCache();
|
|
2359
|
+
freed.push('Browser cache');
|
|
2360
|
+
// 4. Clear local storage
|
|
2361
|
+
await this.clearLocalStorage();
|
|
2362
|
+
freed.push('Local storage');
|
|
2363
|
+
// 5. Clean DOM references
|
|
2364
|
+
this.cleanupDOMReferences();
|
|
2365
|
+
freed.push('DOM references');
|
|
2366
|
+
// 6. Optimize canvas pools
|
|
2367
|
+
if (this.detectionService) {
|
|
2368
|
+
this.detectionService.optimizeCanvasPool(0);
|
|
2369
|
+
freed.push('Canvas pools');
|
|
2370
|
+
}
|
|
2371
|
+
// 7. Clear component canvas pool
|
|
2372
|
+
this.canvasPool = [];
|
|
2373
|
+
freed.push('Component canvas pool');
|
|
2374
|
+
// 8. Force garbage collection (if available)
|
|
2375
|
+
if (this.debug && window.gc) {
|
|
2376
|
+
window.gc();
|
|
2377
|
+
freed.push('Garbage collection');
|
|
2378
|
+
}
|
|
2379
|
+
}
|
|
2380
|
+
catch (error) {
|
|
2381
|
+
errors.push(error.message);
|
|
2382
|
+
}
|
|
2383
|
+
return { freed, errors };
|
|
2384
|
+
}
|
|
2385
|
+
// Progressive resource release after front capture
|
|
2386
|
+
async onFrontCaptureComplete() {
|
|
2387
|
+
try {
|
|
2388
|
+
// Release classification model if not needed for back
|
|
2389
|
+
if (this.detectionService && !this.useDocumentClassification) {
|
|
2390
|
+
await this.detectionService.releaseMobileNetResources();
|
|
2391
|
+
}
|
|
2392
|
+
// Optimize canvas pool to minimum needed for back capture
|
|
2393
|
+
if (this.detectionService) {
|
|
2394
|
+
this.detectionService.optimizeCanvasPool(1);
|
|
2395
|
+
}
|
|
2396
|
+
// Reduce component canvas pool
|
|
2397
|
+
while (this.canvasPool.length > 1) {
|
|
2398
|
+
const canvas = this.canvasPool.pop();
|
|
2399
|
+
if (canvas) {
|
|
2400
|
+
const ctx = canvas.getContext('2d');
|
|
2401
|
+
if (ctx) {
|
|
2402
|
+
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
2403
|
+
}
|
|
2404
|
+
}
|
|
2405
|
+
}
|
|
2406
|
+
if (this.debug) {
|
|
2407
|
+
console.log('🧹 Progressive cleanup after front capture completed');
|
|
2408
|
+
}
|
|
2409
|
+
}
|
|
2410
|
+
catch (error) {
|
|
2411
|
+
console.warn('Error in progressive cleanup:', error);
|
|
2412
|
+
}
|
|
2413
|
+
}
|
|
2414
|
+
// Resource reporting for debugging
|
|
2415
|
+
getResourceReportInternal() {
|
|
2416
|
+
const report = {
|
|
2417
|
+
timestamp: new Date().toISOString(),
|
|
2418
|
+
component: {
|
|
2419
|
+
hasVideoStream: !!this.videoStream,
|
|
2420
|
+
hasAnimationFrame: !!this.animationId,
|
|
2421
|
+
canvasPoolSize: this.canvasPool.length,
|
|
2422
|
+
detectionBoxesCount: this.detectionBoxes.length,
|
|
2423
|
+
performanceMonitorActive: !!this.performanceUpdateInterval
|
|
2424
|
+
}
|
|
2425
|
+
};
|
|
2426
|
+
// Add detection service stats if available
|
|
2427
|
+
if (this.detectionService) {
|
|
2428
|
+
report.detectionService = {
|
|
2429
|
+
isModelLoaded: this.detectionService.isModelLoaded(),
|
|
2430
|
+
canvasPoolStats: this.detectionService.getPoolStats()
|
|
2431
|
+
};
|
|
2432
|
+
}
|
|
2433
|
+
// Add memory info if available
|
|
2434
|
+
if ('memory' in performance) {
|
|
2435
|
+
const memInfo = performance.memory;
|
|
2436
|
+
report.memory = {
|
|
2437
|
+
usedJSHeapSize: Math.round(memInfo.usedJSHeapSize / 1048576), // MB
|
|
2438
|
+
totalJSHeapSize: Math.round(memInfo.totalJSHeapSize / 1048576), // MB
|
|
2439
|
+
jsHeapSizeLimit: Math.round(memInfo.jsHeapSizeLimit / 1048576) // MB
|
|
2440
|
+
};
|
|
2441
|
+
}
|
|
2442
|
+
return report;
|
|
2443
|
+
}
|
|
1980
2444
|
render() {
|
|
1981
2445
|
const captureState = this.stateManager?.getCaptureState() || {
|
|
1982
2446
|
isVideoActive: false,
|
|
@@ -1986,7 +2450,7 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
|
|
|
1986
2450
|
isCapturing: false
|
|
1987
2451
|
};
|
|
1988
2452
|
const cameraInfo = this.cameraInfoWithAutofocus;
|
|
1989
|
-
return (h("div", { key: '
|
|
2453
|
+
return (h("div", { key: 'ca44b9eacbde9f688f2c41aa21ad35a2b882002b', class: "detector-container" }, h("div", { key: '86c7b5d31ed69276b62fa43e01c92b0c0b9f298d', class: "video-container" }, h("video", { key: 'bb310544c17ae734d8b42ed2617748c2f69743ff', ref: el => this.videoRef = el, autoplay: true, muted: true, playsinline: true, class: this.shouldMirrorVideo ? 'mirror' : '', style: { display: captureState.isVideoActive ? 'block' : 'none' } }), h("div", { key: '078a79a0c6386ca6c040e629acbf4f52f12d8e29', 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: {
|
|
1990
2454
|
position: 'absolute',
|
|
1991
2455
|
left: `${box.x}px`,
|
|
1992
2456
|
top: `${box.y}px`,
|
|
@@ -1995,9 +2459,9 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
|
|
|
1995
2459
|
border: '2px solid #32406C',
|
|
1996
2460
|
pointerEvents: 'none',
|
|
1997
2461
|
boxSizing: 'border-box'
|
|
1998
|
-
} })))), this.isMaskReady && (h("div", { key: '
|
|
2462
|
+
} })))), this.isMaskReady && (h("div", { key: '990015ff5b2ded89405dd2b891c027aad8788f3e', class: "overlay-mask" }, h("div", { key: 'dd9145140987addb1b6e4cc2119b8e9f89003726', class: "card-outline" }, h("div", { key: 'e8f4ec692a8b80c7a98075e4f708b79a272db67b', class: "side side-top" }), h("div", { key: '2577253b40425ecdcb3dd51b2f4a43c15c83ca79', class: "side side-right" }), h("div", { key: '30f4454020c00cf4fb92308af5ecf1843ec24005', class: "side side-bottom" }), h("div", { key: '3459f634987b8396106fff23fa1c99b295ee7d0a', class: "side side-left" })), !captureState.showFlipAnimation && !captureState.showSuccessAnimation && (h("div", { key: '84f4d70947e211a9c6b296531d53420223d2ca34', class: `guide-text ${this.showPerformanceMessage ? 'performance-warning-text' : ''}` }, this.getGuideText(captureState.step))), captureState.step === 'back' && !captureState.showFlipAnimation && !captureState.showSuccessAnimation && (h("div", { key: '7c28e86e8de6c33bf7d7baab3083c5f6a0ab060f', class: "back-capture-section" }, h("div", { key: '27bdcf9cfe9790c4bbad484423f6f4c353ccf0b3', class: "back-capture-buttons" }, (!this.useDocumentDetector || this.performanceDegradedMode) && (h("button", { key: 'ed0320b1df412bf53cbe4e4119f6d769926eb2c5', class: "capture-button primary-button", onClick: () => this.takeManualScreenshot(), type: "button", disabled: this.processingButton !== null }, this.processingButton === 'capture-back' && (h("span", { key: '0bba706c8ebda59c33faca8e97b7536146462d21', class: "button-spinner" })), "Capturar Reverso")), h("button", { key: 'b072c46b25d16a04c5e0a8f3975d8aa8d24576d5', class: "skip-button", onClick: () => this.skipBackCapture(), type: "button", disabled: this.processingButton !== null }, this.processingButton === 'skip-back' && (h("span", { key: 'efd0827d0f19c9072d49d1b3c0046ef6c95a842c', class: "button-spinner" })), this.enableBackDocumentTimer && this.backDocumentTimerRemaining > 0
|
|
1999
2463
|
? `Saltar reverso (${this.backDocumentTimerRemaining}s)`
|
|
2000
|
-
: 'Saltar reverso')))), captureState.isVideoActive && (h("div", { key: '
|
|
2464
|
+
: 'Saltar reverso')))), captureState.isVideoActive && (h("div", { key: 'bd0303e8ce85211e4c68fc3cd79545db7c74288b', class: "camera-controls" }, h("button", { key: 'e4327d260b988b67e0de3654c72f24455cd3bbbd', 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: '35da16baf4c62466c2a66650209a1daf842c0db2', class: "camera-selector-dropdown" }, h("div", { key: '7035dd0e22f614cf239a015c6d59531b68fa74a9', class: "camera-selector-header" }, h("span", { key: 'ae026f31931f52ea0daf72391a9de4ff9e4b88f1' }, "Seleccionar C\u00E1mara"), h("button", { key: '40f2ba0770bafea71fedbf849938010e743be776', class: "close-selector", onClick: () => this.toggleCameraSelector(), type: "button" }, "\u00D7")), h("div", { key: '6f26927094a724a35ebb07ef701caf14f296a837', 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: '082236981c369f8fd487f036439b16e6560514be', class: "device-info" }, h("small", { key: 'ccbbfa0bca96395f574bda71103338ae9226f709' }, "Dispositivo: ", cameraInfo.deviceType)))), this.showManualCaptureButton && captureState.step === 'front' && !captureState.showFlipAnimation && !captureState.showSuccessAnimation && (h("div", { key: '6baa487409503777e90453b39fd30649941cbbe4', class: "manual-capture-section" }, h("button", { key: '893f4715ca3315613f779603f178459955f686f0', class: "manual-capture-button", onClick: () => this.takeManualScreenshot(), type: "button", disabled: this.processingButton !== null }, this.processingButton === 'capture-front' && (h("span", { key: '7c30b680fe84f4c8227beba45d8416f1edb8348f', class: "button-spinner" })), "Capturar Frente"))))), captureState.isCapturing && (h("div", { key: 'd8ef052cf67d7bda4b3534febf96efa2cdcc6bf7', class: "capture-animation" })), captureState.showFlipAnimation && (h("div", { key: '3dae7634e770dda2655b1042ec8fb997b3cb2220', class: "flip-animation" }, h("div", { key: 'a977fc5e889369121bacc06cf5213dfee39d858d', class: "id-card-icon" }), h("div", { key: '4f052cdc8c7b493cddce90e108e976325c9bab8e', class: "flip-text" }, "\u00A1Voltea tu identificaci\u00F3n!"))), captureState.showSuccessAnimation && (h("div", { key: '5117cd6e63e1e0763c1503f6f60f3c7ef60dd665', class: "success-animation" }, h("div", { key: 'd11128c45c7118915cc0dd9c25845bb55b4b3eb2', class: "check-icon" }), h("div", { key: '693e197bf8a4c91ac7ddedb457d15aa3aa46a5d5', class: "success-text" }, "\u00A1Proceso completado!"))), h("div", { key: '89caaa9d78eb522e0fadfd1c0e844e05d29b8286', class: `component-status status-${this.currentStatus.type}` }, (this.currentStatus.type === 'loading' || this.currentStatus.type === 'initializing') && (h("div", { key: '3f41d40b05b71e13e7e1d0c0e980b48308cba9d6', class: "status-spinner" })), h("div", { key: '8674480928f4f4a77099c0600ed84f0e27903379', class: "status-content" }, h("div", { key: 'e88f4044ab8115a6a5cbb78cc4030975eef9149a', class: "status-message" }, this.currentStatus.message), this.currentStatus.description && (h("div", { key: 'ad581e42c30c304b837711549148c11b801dee5e', class: "status-description" }, this.currentStatus.description)))), this.debug && (h("div", { key: '1b371c1b47f3ba699885db00fef52eb448f9e245', class: "performance-monitor" }, h("div", { key: '8b9ac60449a15e9fb966c329b2e6c9285c0228c0', class: "performance-expanded" }, h("div", { key: '760c11a3ed28e248a144d53a92fd8adee6822ff2', class: "metrics-row" }, h("div", { key: '858ab8b106f1d12210e4e15c423ab3bab11bdb60', class: "metric-compact" }, h("span", { key: '65fe2479c3822d99452e7e5faa93c25a80765024', class: "metric-label" }, "FPS"), h("span", { key: '9faf7763d1284a6f3c1e15fb0f3c99f1d45bb4dc', class: `metric-value ${this.performanceData.fps < 15 ? 'warning' : this.performanceData.fps < 10 ? 'danger' : 'good'}` }, this.performanceData.fps)), h("div", { key: 'c95ed8fa8101f42bfba5cc8ca7debbad028caf42', class: "metric-compact" }, h("span", { key: '3555b3778cb71e959d132d6aa563f58f757915d8', class: "metric-label" }, "MEM"), h("span", { key: 'd72b0491147a6f80dcf33f5d72dd8dae5898f44a', class: `metric-value ${this.performanceData.memoryUsage > 100 ? 'warning' : this.performanceData.memoryUsage > 200 ? 'danger' : 'good'}` }, this.performanceData.memoryUsage, "MB"))), h("div", { key: 'cd4dc81f28f9e59a36a0295dde7bfee89f50d3e2', class: "metrics-row" }, h("div", { key: 'c43d8624c5da256ce3e97f67b022bdd98fb04e07', class: "metric-compact" }, h("span", { key: 'adb402d83809a90580b81544f31b5331bdff59dd', class: "metric-label" }, "INF"), h("span", { key: 'e48c9814c08acbe321460a29e42cb49b0eab38ff', class: `metric-value ${this.performanceData.inferenceTime > 100 ? 'warning' : this.performanceData.inferenceTime > 200 ? 'danger' : 'good'}` }, this.performanceData.inferenceTime, "ms")), h("div", { key: '71517f9279e9a6aa0d5b02e2169db868ca3fb676', class: "metric-compact" }, h("span", { key: '3ddbcc56b137aea702a40f900178e8e16efd146e', class: "metric-label" }, "FRAME"), h("span", { key: 'd3a70afb511d3f72b485b3a1cb922876faa62b11', class: `metric-value ${this.performanceData.frameProcessingTime > 50 ? 'warning' : this.performanceData.frameProcessingTime > 100 ? 'danger' : 'good'}` }, this.performanceData.frameProcessingTime, "ms"))), h("div", { key: 'a3b62cdbe830b1024ab96f7fb726825df24fecdc', class: "metrics-row" }, h("div", { key: '95471e5cdde748d1ce6bdee3540740dd3aa66865', class: "metric-compact" }, h("span", { key: 'c93e01c0bdc67621c3a666357d2477b7a5275d52', class: "metric-label" }, "DET"), h("span", { key: 'ef33c86a83101b0989b14c7f1ab0ce0b331b397f', class: "metric-value good" }, this.performanceData.successfulDetections, "/", this.performanceData.totalDetections)), h("div", { key: 'e14d56128bf96337e4c3859f8e4003e001e8e953', class: "metric-compact" }, h("span", { key: '8aa828632f0a977a27ed377f103bb8d5e7e0ed1a', class: "metric-label" }, "RATE"), h("span", { key: '59035c473b5a747a27f774e2554322a084b6d19f', class: `metric-value ${this.performanceData.detectionRate < 30 ? 'danger' : this.performanceData.detectionRate < 60 ? 'warning' : 'good'}` }, this.performanceData.detectionRate, "%")))))), h("div", { key: '4d0c2de00954416faac30ab622fb21f36c932aca', class: "watermark" }, h("img", { key: 'd8f775428ebb3ba86a943ca73b074db2180d0c29', src: "https://storage.googleapis.com/jaak-static/commons/powered-by-jaak.png", alt: "Powered by Jaak" })))));
|
|
2001
2465
|
}
|
|
2002
2466
|
// Utility methods
|
|
2003
2467
|
updateDetectionBoxes(boxes) {
|
|
@@ -2113,9 +2577,24 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
|
|
|
2113
2577
|
}
|
|
2114
2578
|
}
|
|
2115
2579
|
async takeManualScreenshot() {
|
|
2116
|
-
|
|
2580
|
+
console.log('🔵 Botón clicked: Capturar (Frente/Reverso)');
|
|
2581
|
+
// Set processing state immediately
|
|
2582
|
+
const captureState = this.stateManager?.getCaptureState();
|
|
2583
|
+
if (captureState?.step === 'front') {
|
|
2584
|
+
this.processingButton = 'capture-front';
|
|
2585
|
+
}
|
|
2586
|
+
else if (captureState?.step === 'back') {
|
|
2587
|
+
this.processingButton = 'capture-back';
|
|
2588
|
+
}
|
|
2589
|
+
console.log('🔵 processingButton set to:', this.processingButton);
|
|
2590
|
+
// Add small delay to show spinner
|
|
2591
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
2592
|
+
if (!this.videoRef) {
|
|
2593
|
+
this.processingButton = null;
|
|
2117
2594
|
return;
|
|
2595
|
+
}
|
|
2118
2596
|
// When using manual capture, use mask coordinates for cropping
|
|
2597
|
+
// Note: processingButton will be cleared inside takeScreenshotWithMaskCoordinates
|
|
2119
2598
|
await this.takeScreenshotWithMaskCoordinates();
|
|
2120
2599
|
}
|
|
2121
2600
|
async takeScreenshotWithMaskCoordinates() {
|
|
@@ -2191,6 +2670,8 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
|
|
|
2191
2670
|
this.completeProcess(true);
|
|
2192
2671
|
this.returnCanvasToPool(captureCanvas);
|
|
2193
2672
|
this.returnCanvasToPool(croppedCanvas);
|
|
2673
|
+
// Clear processing button when passport is detected
|
|
2674
|
+
this.processingButton = null;
|
|
2194
2675
|
return;
|
|
2195
2676
|
}
|
|
2196
2677
|
}
|
|
@@ -2204,6 +2685,8 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
|
|
|
2204
2685
|
showFlipAnimation: true
|
|
2205
2686
|
});
|
|
2206
2687
|
this.triggerRerender();
|
|
2688
|
+
// Progressive resource cleanup after front capture
|
|
2689
|
+
await this.onFrontCaptureComplete();
|
|
2207
2690
|
setTimeout(() => {
|
|
2208
2691
|
this.stateManager.updateCaptureState({
|
|
2209
2692
|
showFlipAnimation: false,
|
|
@@ -2211,6 +2694,8 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
|
|
|
2211
2694
|
});
|
|
2212
2695
|
this.triggerRerender();
|
|
2213
2696
|
this.startBackDocumentTimer();
|
|
2697
|
+
// Clear processing button after animation completes
|
|
2698
|
+
this.processingButton = null;
|
|
2214
2699
|
}, 3000);
|
|
2215
2700
|
}
|
|
2216
2701
|
else if (captureState.step === 'back') {
|
|
@@ -2221,6 +2706,8 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
|
|
|
2221
2706
|
}
|
|
2222
2707
|
});
|
|
2223
2708
|
this.completeProcess(false);
|
|
2709
|
+
// Clear processing button after back capture completes
|
|
2710
|
+
this.processingButton = null;
|
|
2224
2711
|
}
|
|
2225
2712
|
// Return canvases to pool after use
|
|
2226
2713
|
this.returnCanvasToPool(captureCanvas);
|
|
@@ -2275,6 +2762,8 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
|
|
|
2275
2762
|
isDetectionPaused: true,
|
|
2276
2763
|
showFlipAnimation: true
|
|
2277
2764
|
});
|
|
2765
|
+
// Progressive resource cleanup after front capture
|
|
2766
|
+
await this.onFrontCaptureComplete();
|
|
2278
2767
|
setTimeout(() => {
|
|
2279
2768
|
this.stateManager.updateCaptureState({
|
|
2280
2769
|
showFlipAnimation: false,
|
|
@@ -2314,6 +2803,9 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
|
|
|
2314
2803
|
this.stopPerformanceMonitoring();
|
|
2315
2804
|
// Liberar recursos de ONNX
|
|
2316
2805
|
this.releaseOnnxResources();
|
|
2806
|
+
// Resetear máscara a blanco y limpiar detecciones
|
|
2807
|
+
this.resetMaskToWhite();
|
|
2808
|
+
this.detectionBoxes = [];
|
|
2317
2809
|
this.hasAutoSwitchedToManual = true;
|
|
2318
2810
|
this.performanceDegradedMode = true;
|
|
2319
2811
|
this.showManualCaptureButton = true;
|
|
@@ -2324,6 +2816,75 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
|
|
|
2324
2816
|
}, 5000);
|
|
2325
2817
|
}
|
|
2326
2818
|
// Método para cambiar a modo manual por errores de ONNX
|
|
2819
|
+
switchToManualModeWithWarning(warningMessage) {
|
|
2820
|
+
// Solo cambiar si el detector está habilitado y no se ha cambiado ya
|
|
2821
|
+
if (!this.useDocumentDetector || this.hasAutoSwitchedToManual) {
|
|
2822
|
+
return;
|
|
2823
|
+
}
|
|
2824
|
+
// No necesitamos liberar recursos ONNX porque no se han cargado aún
|
|
2825
|
+
// Resetear máscara a blanco y limpiar detecciones
|
|
2826
|
+
this.resetMaskToWhite();
|
|
2827
|
+
this.detectionBoxes = [];
|
|
2828
|
+
// Cambiar a modo manual
|
|
2829
|
+
this.hasAutoSwitchedToManual = true;
|
|
2830
|
+
this.performanceDegradedMode = true;
|
|
2831
|
+
this.showManualCaptureButton = true;
|
|
2832
|
+
this.useDocumentDetector = false; // Desactivar detector automático
|
|
2833
|
+
// Actualizar el estado con mensaje informativo (no error)
|
|
2834
|
+
this.updateStatus('Modo manual activado', warningMessage, 'active');
|
|
2835
|
+
// Continuar con la configuración de cámara
|
|
2836
|
+
this.continueWithCameraSetup();
|
|
2837
|
+
}
|
|
2838
|
+
async continueWithCameraSetup() {
|
|
2839
|
+
try {
|
|
2840
|
+
// Paso 2: Detectar y configurar dispositivos
|
|
2841
|
+
this.updateStatus('Configurando cámara...', 'Buscando dispositivos disponibles', 'loading');
|
|
2842
|
+
try {
|
|
2843
|
+
await this.cameraService.enumerateDevices();
|
|
2844
|
+
}
|
|
2845
|
+
catch (enumerateError) {
|
|
2846
|
+
throw new Error(`Error al enumerar dispositivos: ${enumerateError.message}`);
|
|
2847
|
+
}
|
|
2848
|
+
try {
|
|
2849
|
+
await this.updateCameraInfoWithAutofocus();
|
|
2850
|
+
}
|
|
2851
|
+
catch (cameraInfoError) {
|
|
2852
|
+
throw new Error(`Error al actualizar información de cámara: ${cameraInfoError.message}`);
|
|
2853
|
+
}
|
|
2854
|
+
// Paso 3: Configurar cámara seleccionada
|
|
2855
|
+
this.updateStatus('Ajustando cámara...', 'Configurando calidad de imagen', 'loading');
|
|
2856
|
+
let stream;
|
|
2857
|
+
try {
|
|
2858
|
+
stream = await this.cameraService.setupCamera();
|
|
2859
|
+
}
|
|
2860
|
+
catch (setupError) {
|
|
2861
|
+
throw new Error(`Error al configurar cámara: ${setupError.message}`);
|
|
2862
|
+
}
|
|
2863
|
+
// Paso 4: Inicializar video
|
|
2864
|
+
this.updateStatus('Captura manual', 'Posicione su documento y use el botón para capturar', 'active');
|
|
2865
|
+
try {
|
|
2866
|
+
await this.initializeVideoStream(stream);
|
|
2867
|
+
}
|
|
2868
|
+
catch (videoError) {
|
|
2869
|
+
throw new Error(`Error al inicializar video: ${videoError.message}`);
|
|
2870
|
+
}
|
|
2871
|
+
// Paso 5: Calibrar máscara
|
|
2872
|
+
if (this.detectionContainer) {
|
|
2873
|
+
const container = this.detectionContainer.parentElement;
|
|
2874
|
+
const rect = container.getBoundingClientRect();
|
|
2875
|
+
this.updateMaskDimensions(rect);
|
|
2876
|
+
}
|
|
2877
|
+
// Finalizar
|
|
2878
|
+
this.startTime = Date.now();
|
|
2879
|
+
this.stateManager.updateCaptureState({ isLoading: false });
|
|
2880
|
+
// Mostrar botón manual (ya debe estar habilitado)
|
|
2881
|
+
this.showManualCaptureButton = true;
|
|
2882
|
+
}
|
|
2883
|
+
catch (error) {
|
|
2884
|
+
this.updateStatus('Error de cámara', error.message, 'error');
|
|
2885
|
+
throw error;
|
|
2886
|
+
}
|
|
2887
|
+
}
|
|
2327
2888
|
switchToManualModeWithError(errorMessage) {
|
|
2328
2889
|
// Solo cambiar si el detector está habilitado y no se ha cambiado ya
|
|
2329
2890
|
if (!this.useDocumentDetector || this.hasAutoSwitchedToManual) {
|
|
@@ -2331,6 +2892,9 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
|
|
|
2331
2892
|
}
|
|
2332
2893
|
// Liberar recursos de ONNX inmediatamente
|
|
2333
2894
|
this.releaseOnnxResources();
|
|
2895
|
+
// Resetear máscara a blanco y limpiar detecciones
|
|
2896
|
+
this.resetMaskToWhite();
|
|
2897
|
+
this.detectionBoxes = [];
|
|
2334
2898
|
// Cambiar a modo manual
|
|
2335
2899
|
this.hasAutoSwitchedToManual = true;
|
|
2336
2900
|
this.performanceDegradedMode = true;
|
|
@@ -2377,6 +2941,16 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
|
|
|
2377
2941
|
setTimeout(() => {
|
|
2378
2942
|
this.stateManager.updateCaptureState({ showSuccessAnimation: false });
|
|
2379
2943
|
}, 3000);
|
|
2944
|
+
// Aggressive cleanup after process completion with delay
|
|
2945
|
+
setTimeout(async () => {
|
|
2946
|
+
const cleanupResult = await this.aggressiveResourceCleanup();
|
|
2947
|
+
if (this.debug) {
|
|
2948
|
+
console.log('🧹 Post-completion cleanup:', cleanupResult.freed);
|
|
2949
|
+
if (cleanupResult.errors.length > 0) {
|
|
2950
|
+
console.warn('⚠️ Cleanup errors:', cleanupResult.errors);
|
|
2951
|
+
}
|
|
2952
|
+
}
|
|
2953
|
+
}, 1000); // Wait 1 second after completion
|
|
2380
2954
|
}
|
|
2381
2955
|
stopDetection() {
|
|
2382
2956
|
if (this.animationId) {
|
|
@@ -2581,6 +3155,24 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
|
|
|
2581
3155
|
// Performance is good, use base frame skip
|
|
2582
3156
|
return this.BASE_FRAME_SKIP;
|
|
2583
3157
|
}
|
|
3158
|
+
// Método para resetear la máscara a color blanco
|
|
3159
|
+
resetMaskToWhite() {
|
|
3160
|
+
const cardOutline = this.el.shadowRoot?.querySelector('.card-outline');
|
|
3161
|
+
const corners = this.el.shadowRoot?.querySelectorAll('.corner');
|
|
3162
|
+
const topSide = this.el.shadowRoot?.querySelector('.side-top');
|
|
3163
|
+
const rightSide = this.el.shadowRoot?.querySelector('.side-right');
|
|
3164
|
+
const bottomSide = this.el.shadowRoot?.querySelector('.side-bottom');
|
|
3165
|
+
const leftSide = this.el.shadowRoot?.querySelector('.side-left');
|
|
3166
|
+
// Remover todas las clases de alineación y estado perfecto
|
|
3167
|
+
cardOutline?.classList.remove('perfect-match');
|
|
3168
|
+
corners?.forEach(corner => corner.classList.remove('perfect-match'));
|
|
3169
|
+
topSide?.classList.remove('aligned');
|
|
3170
|
+
rightSide?.classList.remove('aligned');
|
|
3171
|
+
bottomSide?.classList.remove('aligned');
|
|
3172
|
+
leftSide?.classList.remove('aligned');
|
|
3173
|
+
// Resetear estado de alineación
|
|
3174
|
+
this.sideAlignment = { top: false, right: false, bottom: false, left: false };
|
|
3175
|
+
}
|
|
2584
3176
|
// Canvas pool management for screenshots
|
|
2585
3177
|
getPooledCanvas(width, height) {
|
|
2586
3178
|
// Try to find a canvas with matching dimensions
|
|
@@ -2638,6 +3230,7 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
|
|
|
2638
3230
|
"performanceDegradedMode": [32],
|
|
2639
3231
|
"showPerformanceMessage": [32],
|
|
2640
3232
|
"captureStateVersion": [32],
|
|
3233
|
+
"processingButton": [32],
|
|
2641
3234
|
"getCapturedImages": [64],
|
|
2642
3235
|
"isProcessCompleted": [64],
|
|
2643
3236
|
"startCapture": [64],
|
|
@@ -2649,7 +3242,9 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
|
|
|
2649
3242
|
"getCameraInfo": [64],
|
|
2650
3243
|
"setPreferredCamera": [64],
|
|
2651
3244
|
"setCaptureDelay": [64],
|
|
2652
|
-
"getCaptureDelay": [64]
|
|
3245
|
+
"getCaptureDelay": [64],
|
|
3246
|
+
"getResourceReport": [64],
|
|
3247
|
+
"forceResourceCleanup": [64]
|
|
2653
3248
|
}]);
|
|
2654
3249
|
function defineCustomElement$1() {
|
|
2655
3250
|
if (typeof customElements === "undefined") {
|