@smileid/web-components 11.3.0 → 11.4.1
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/esm/DocumentCaptureScreens-wvJcTVkA.js +4147 -0
- package/dist/esm/DocumentCaptureScreens-wvJcTVkA.js.map +1 -0
- package/dist/esm/{EndUserConsent-BgKCCMMn.js → EndUserConsent-BXvS7t8z.js} +4 -4
- package/dist/esm/{EndUserConsent-BgKCCMMn.js.map → EndUserConsent-BXvS7t8z.js.map} +1 -1
- package/dist/esm/Navigation-BRFmg7s1.js +138 -0
- package/dist/esm/Navigation-BRFmg7s1.js.map +1 -0
- package/dist/esm/{SelfieCaptureScreens-DKd0f7K8.js → SelfieCaptureScreens-BkJBfzHv.js} +2651 -3086
- package/dist/esm/SelfieCaptureScreens-BkJBfzHv.js.map +1 -0
- package/dist/esm/{TotpConsent-BQm8j4-u.js → TotpConsent-Cn2DkVza.js} +2 -2
- package/dist/esm/{TotpConsent-BQm8j4-u.js.map → TotpConsent-Cn2DkVza.js.map} +1 -1
- package/dist/esm/combobox.js +14 -16
- package/dist/esm/combobox.js.map +1 -1
- package/dist/esm/document.js +1 -1
- package/dist/esm/end-user-consent.js +1 -1
- package/dist/esm/{index-Cjzs1eA_.js → index-DBUdxnp9.js} +91 -91
- package/dist/esm/{index-Cjzs1eA_.js.map → index-DBUdxnp9.js.map} +1 -1
- package/dist/esm/localisation.js +1 -1
- package/dist/esm/main.js +6 -6
- package/dist/esm/navigation.js +1 -1
- package/dist/esm/package-Dax8ezDK.js +565 -0
- package/dist/esm/package-Dax8ezDK.js.map +1 -0
- package/dist/esm/selfie.js +1 -1
- package/dist/esm/smart-camera-web.js +16 -11
- package/dist/esm/smart-camera-web.js.map +1 -1
- package/dist/esm/{styles-BOEZtbuc.js → styles-BTEClL7R.js} +2 -2
- package/dist/esm/{styles-BOEZtbuc.js.map → styles-BTEClL7R.js.map} +1 -1
- package/dist/esm/totp-consent.js +1 -1
- package/dist/smart-camera-web.js +446 -179
- package/dist/smart-camera-web.js.map +1 -1
- package/dist/types/main.d.ts +183 -33
- package/lib/components/combobox/src/Combobox.js +14 -12
- package/lib/components/document/src/DocumentCaptureScreens.js +15 -5
- package/lib/components/document/src/assets/icons/guidelines/greenbook/good.svg +77 -0
- package/lib/components/document/src/assets/icons/guidelines/greenbook/not-blurry.svg +84 -0
- package/lib/components/document/src/assets/icons/guidelines/greenbook/not-cropped.svg +83 -0
- package/lib/components/document/src/assets/icons/guidelines/greenbook/not-reflective.svg +89 -0
- package/lib/components/document/src/assets/icons/guidelines/id-card/good.svg +87 -0
- package/lib/components/document/src/assets/icons/guidelines/id-card/not-blurry.svg +100 -0
- package/lib/components/document/src/assets/icons/guidelines/id-card/not-cropped.svg +93 -0
- package/lib/components/document/src/assets/icons/guidelines/id-card/not-reflective.svg +98 -0
- package/lib/components/document/src/assets/icons/guidelines/passport/good.svg +91 -0
- package/lib/components/document/src/assets/icons/guidelines/passport/not-blurry.svg +109 -0
- package/lib/components/document/src/assets/icons/guidelines/passport/not-cropped.svg +102 -0
- package/lib/components/document/src/assets/icons/guidelines/passport/not-reflective.svg +207 -0
- package/lib/components/document/src/assets/lottie/taking photo of ID FLIP 2D.lottie +0 -0
- package/lib/components/document/src/assets/lottie/taking photo of ID.lottie +0 -0
- package/lib/components/document/src/assets/lottie/taking photo of green book passport.lottie +0 -0
- package/lib/components/document/src/assets/lottie/taking photo of passport 2.lottie +0 -0
- package/lib/components/document/src/document-capture-instructions/DocumentCaptureInstructions.stories.js +123 -12
- package/lib/components/document/src/document-capture-instructions/DocumentCaptureInstructions.tsx +808 -0
- package/lib/components/document/src/document-capture-instructions/index.js +1 -0
- package/lib/components/navigation/src/Navigation.js +57 -56
- package/lib/components/navigation/src/Navigation.stories.js +7 -0
- package/lib/components/selfie/src/smartselfie-capture/SmartSelfieCapture.tsx +3 -15
- package/lib/components/selfie/src/smartselfie-capture/components/CameraPreview.tsx +6 -10
- package/lib/components/selfie/src/smartselfie-capture/components/CaptureControls.tsx +7 -1
- package/lib/components/selfie/src/smartselfie-capture/hooks/useFaceCapture.ts +33 -19
- package/lib/components/selfie/src/smartselfie-capture/utils/alertMessages.ts +0 -1
- package/lib/components/selfie/src/smartselfie-capture/utils/mediapipeManager.ts +127 -30
- package/lib/components/signature-pad/package-lock.json +24 -19
- package/lib/components/signature-pad/package.json +1 -1
- package/lib/components/smart-camera-web/src/SmartCameraWeb.js +7 -1
- package/lib/styles/src/styles.js +1 -1
- package/package.json +5 -3
- package/dist/README.md +0 -15
- package/dist/components/README.md +0 -14
- package/dist/components/document/src/README.md +0 -111
- package/dist/components/document/src/document-capture/README.md +0 -90
- package/dist/components/document/src/document-capture-instructions/README.md +0 -56
- package/dist/components/document/src/document-capture-review/README.md +0 -79
- package/dist/components/selfie/README.md +0 -225
- package/dist/components/smart-camera-web/src/README.md +0 -206
- package/dist/domain/camera/src/README.md +0 -38
- package/dist/domain/file-upload/README.md +0 -35
- package/dist/esm/DocumentCaptureScreens-N-0o7eE5.js +0 -1580
- package/dist/esm/DocumentCaptureScreens-N-0o7eE5.js.map +0 -1
- package/dist/esm/Navigation-DOFYqTZt.js +0 -144
- package/dist/esm/Navigation-DOFYqTZt.js.map +0 -1
- package/dist/esm/SelfieCaptureScreens-DKd0f7K8.js.map +0 -1
- package/dist/esm/package-KQ2l43v1.js +0 -90
- package/dist/esm/package-KQ2l43v1.js.map +0 -1
- package/dist/package-lock.json +0 -4948
- package/dist/package.json +0 -59
- package/dist/smart-camera-web.js.gz +0 -0
- package/dist/src/components/combobox/src/index.js +0 -425
- package/dist/src/components/combobox/src/index.js.map +0 -7
- package/dist/src/components/document/src/index.js +0 -1423
- package/dist/src/components/document/src/index.js.map +0 -7
- package/dist/src/components/end-user-consent/src/index.js +0 -1586
- package/dist/src/components/end-user-consent/src/index.js.map +0 -7
- package/dist/src/components/selfie/src/index.js +0 -1221
- package/dist/src/components/selfie/src/index.js.map +0 -7
- package/dist/src/components/signature-pad/src/index.js +0 -796
- package/dist/src/components/signature-pad/src/index.js.map +0 -7
- package/dist/src/components/smart-camera-web/src/SmartCameraWeb.js +0 -2754
- package/dist/src/components/smart-camera-web/src/SmartCameraWeb.js.map +0 -7
- package/dist/src/components/totp-consent/src/index.js +0 -1305
- package/dist/src/components/totp-consent/src/index.js.map +0 -7
- package/dist/src/index.js.map +0 -7
- package/dist/styles/README.md +0 -3
- package/dist/types/locale.d.ts +0 -19
|
@@ -4,6 +4,13 @@ class Navigation extends HTMLElement {
|
|
|
4
4
|
connectedCallback() {
|
|
5
5
|
const shadow = this.attachShadow({ mode: 'open' });
|
|
6
6
|
const direction = getDirection();
|
|
7
|
+
const hostPadding = '0px';
|
|
8
|
+
const buttonSize = '40px';
|
|
9
|
+
const buttonBackground = 'rgba(132, 130, 130, 0.9)';
|
|
10
|
+
const buttonBorder = '1px solid rgba(255, 255, 255, 0.1)';
|
|
11
|
+
const iconSize = '20px';
|
|
12
|
+
const iconColor = this.hasThemeColor ? this.themeColor : '#FFFFFF';
|
|
13
|
+
const focusColor = '#FFFFFF';
|
|
7
14
|
|
|
8
15
|
const style = document.createElement('style');
|
|
9
16
|
style.textContent = `
|
|
@@ -12,6 +19,8 @@ class Navigation extends HTMLElement {
|
|
|
12
19
|
max-inline-size: 100%;
|
|
13
20
|
justify-content: ${this.showBackButton ? 'space-between' : 'flex-end'};
|
|
14
21
|
direction: ${direction};
|
|
22
|
+
padding: var(--smileid-navigation-padding, ${hostPadding});
|
|
23
|
+
gap: 1rem;
|
|
15
24
|
}
|
|
16
25
|
|
|
17
26
|
:host([dir="rtl"]) .back-button svg,
|
|
@@ -20,50 +29,48 @@ class Navigation extends HTMLElement {
|
|
|
20
29
|
}
|
|
21
30
|
|
|
22
31
|
button {
|
|
23
|
-
--button-color: var(--color-default);
|
|
24
|
-
--flow-space: 3rem;
|
|
25
32
|
-webkit-appearance: none;
|
|
26
33
|
-moz-appearance: none;
|
|
27
|
-
align-items: center;
|
|
28
34
|
appearance: none;
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
border:
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
display:
|
|
35
|
-
font-size: 1rem;
|
|
36
|
-
font-weight: 500;
|
|
37
|
-
inline-size: 100%;
|
|
38
|
-
justify-content: center;
|
|
39
|
-
letter-spacing: 0.05ch;
|
|
40
|
-
line-height: 1;
|
|
41
|
-
padding: 1rem 2.5rem;
|
|
42
|
-
text-align: center;
|
|
43
|
-
text-decoration: none;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
button[data-type="icon"] {
|
|
35
|
+
width: ${buttonSize};
|
|
36
|
+
height: ${buttonSize};
|
|
37
|
+
border-radius: 50%;
|
|
38
|
+
background: var(--smileid-navigation-button-bg, ${buttonBackground});
|
|
39
|
+
border: ${buttonBorder};
|
|
40
|
+
display: flex;
|
|
47
41
|
align-items: center;
|
|
48
|
-
|
|
49
|
-
|
|
42
|
+
justify-content: center;
|
|
43
|
+
color: var(--smileid-navigation-icon-color, ${iconColor});
|
|
50
44
|
cursor: pointer;
|
|
51
|
-
display: flex;
|
|
52
45
|
padding: 0;
|
|
53
|
-
|
|
46
|
+
flex-shrink: 0;
|
|
47
|
+
transition: box-shadow 0.15s ease;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
button:hover {
|
|
51
|
+
box-shadow: inset 0 0 0 999px rgba(0, 0, 0, 0.15);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
button:focus-visible {
|
|
55
|
+
outline: 2px solid var(--smileid-navigation-focus-color, ${focusColor});
|
|
56
|
+
outline-offset: 3px;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
button svg {
|
|
60
|
+
width: ${iconSize};
|
|
61
|
+
height: ${iconSize};
|
|
54
62
|
}
|
|
55
63
|
|
|
56
64
|
:host::part(back-button) {
|
|
57
65
|
display: flex;
|
|
58
66
|
align-items: center;
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
:host::part(back-button-text) {
|
|
62
|
-
line-height: 1;
|
|
63
|
-
color: ${this.hasThemeColor ? this.themeColor : 'rgb(21, 31, 114)'} !important;
|
|
67
|
+
justify-content: center;
|
|
64
68
|
}
|
|
65
69
|
|
|
66
70
|
:host::part(close-button) {
|
|
71
|
+
display: flex;
|
|
72
|
+
align-items: center;
|
|
73
|
+
justify-content: center;
|
|
67
74
|
}
|
|
68
75
|
|
|
69
76
|
.visually-hidden {
|
|
@@ -79,57 +86,51 @@ button[data-type="icon"] {
|
|
|
79
86
|
|
|
80
87
|
const backButton = document.createElement('button');
|
|
81
88
|
backButton.setAttribute('class', 'back-button');
|
|
82
|
-
backButton.setAttribute('data-type', 'icon');
|
|
83
89
|
backButton.setAttribute('part', 'back-button');
|
|
84
90
|
backButton.setAttribute('type', 'button');
|
|
91
|
+
backButton.setAttribute('aria-label', t('navigation.back'));
|
|
85
92
|
backButton.innerHTML = `
|
|
86
93
|
<svg
|
|
87
|
-
|
|
88
|
-
width="24"
|
|
89
|
-
height="24"
|
|
94
|
+
aria-hidden="true"
|
|
90
95
|
viewBox="0 0 24 24"
|
|
91
96
|
fill="none"
|
|
97
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
92
98
|
data-rtl="${direction === 'rtl'}"
|
|
93
99
|
>
|
|
94
100
|
<path
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
fill="${this.themeColor}"
|
|
101
|
-
d="M15.5 11.25h-5.19l1.72-1.72c.29-.29.29-.77 0-1.06a.754.754 0 0 0-1.06 0l-3 3c-.29.29-.29.77 0 1.06l3 3c.15.15.34.22.53.22s.38-.07.53-.22c.29-.29.29-.77 0-1.06l-1.72-1.72h5.19c.41 0 .75-.34.75-.75s-.34-.75-.75-.75Z"
|
|
101
|
+
d="M19 12H5M5 12L12 19M5 12L12 5"
|
|
102
|
+
stroke="currentColor"
|
|
103
|
+
stroke-width="2"
|
|
104
|
+
stroke-linecap="round"
|
|
105
|
+
stroke-linejoin="round"
|
|
102
106
|
/>
|
|
103
107
|
</svg>
|
|
104
|
-
<span part="back-button-text">${t('navigation.back')}</span>
|
|
108
|
+
<span part="back-button-text" class="visually-hidden">${t('navigation.back')}</span>
|
|
105
109
|
`;
|
|
106
110
|
|
|
107
111
|
const closeButton = document.createElement('button');
|
|
108
112
|
closeButton.setAttribute('class', 'close-button');
|
|
109
|
-
closeButton.setAttribute('data-type', 'icon');
|
|
110
113
|
closeButton.setAttribute('part', 'close-button');
|
|
111
114
|
closeButton.setAttribute('type', 'button');
|
|
115
|
+
closeButton.setAttribute(
|
|
116
|
+
'aria-label',
|
|
117
|
+
t('navigation.closeVerificationFrame'),
|
|
118
|
+
);
|
|
112
119
|
closeButton.innerHTML = `
|
|
113
120
|
<svg
|
|
114
|
-
|
|
121
|
+
aria-hidden="true"
|
|
115
122
|
viewBox="0 0 24 24"
|
|
116
|
-
width="24"
|
|
117
|
-
height="24"
|
|
118
123
|
fill="none"
|
|
124
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
119
125
|
>
|
|
120
126
|
<path
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
fill="#91190F"
|
|
127
|
-
d="m13.06 12 2.3-2.3c.29-.29.29-.77 0-1.06a.754.754 0 0 0-1.06 0l-2.3 2.3-2.3-2.3a.754.754 0 0 0-1.06 0c-.29.29-.29.77 0 1.06l2.3 2.3-2.3 2.3c-.29.29-.29.77 0 1.06.15.15.34.22.53.22s.38-.07.53-.22l2.3-2.3 2.3 2.3c.15.15.34.22.53.22s.38-.07.53-.22c.29-.29.29-.77 0-1.06l-2.3-2.3Z"
|
|
127
|
+
d="M18 6L6 18M6 6L18 18"
|
|
128
|
+
stroke="currentColor"
|
|
129
|
+
stroke-width="2"
|
|
130
|
+
stroke-linecap="round"
|
|
131
|
+
stroke-linejoin="round"
|
|
128
132
|
/>
|
|
129
133
|
</svg>
|
|
130
|
-
<span class="visually-hidden"
|
|
131
|
-
>${t('navigation.closeVerificationFrame')}</span
|
|
132
|
-
>
|
|
133
134
|
`;
|
|
134
135
|
|
|
135
136
|
shadow.appendChild(style);
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import { useRef, useEffect } from 'preact/hooks';
|
|
2
|
-
import { useSignal } from '@preact/signals';
|
|
3
2
|
import register from 'preact-custom-element';
|
|
4
3
|
import type { FunctionComponent } from 'preact';
|
|
5
|
-
import throttle from 'lodash/throttle';
|
|
6
4
|
|
|
7
5
|
import { getBoolProp } from '../../../../utils/props';
|
|
8
6
|
import { useFaceCapture, useCamera } from './hooks';
|
|
@@ -50,13 +48,6 @@ const SmartSelfieCapture: FunctionComponent<Props> = ({
|
|
|
50
48
|
const initialFacingMode = allowAgentMode ? 'environment' : 'user';
|
|
51
49
|
const camera = useCamera(initialFacingMode);
|
|
52
50
|
|
|
53
|
-
const throttledMultipleFaces = useSignal(false);
|
|
54
|
-
const updateMultipleFacesUI = useRef(
|
|
55
|
-
throttle((value: boolean) => {
|
|
56
|
-
throttledMultipleFaces.value = value;
|
|
57
|
-
}, 100),
|
|
58
|
-
).current;
|
|
59
|
-
|
|
60
51
|
const faceCapture = useFaceCapture({
|
|
61
52
|
videoRef: camera.videoRef,
|
|
62
53
|
canvasRef,
|
|
@@ -106,14 +97,9 @@ const SmartSelfieCapture: FunctionComponent<Props> = ({
|
|
|
106
97
|
faceCapture.stopDetectionLoop();
|
|
107
98
|
camera.stopCamera();
|
|
108
99
|
faceCapture.cleanup();
|
|
109
|
-
updateMultipleFacesUI.cancel();
|
|
110
100
|
};
|
|
111
101
|
}, []);
|
|
112
102
|
|
|
113
|
-
useEffect(() => {
|
|
114
|
-
updateMultipleFacesUI(faceCapture.multipleFaces.value);
|
|
115
|
-
}, [faceCapture.multipleFaces.value]);
|
|
116
|
-
|
|
117
103
|
useEffect(() => {
|
|
118
104
|
const navigation = navigationRef.current;
|
|
119
105
|
|
|
@@ -160,7 +146,6 @@ const SmartSelfieCapture: FunctionComponent<Props> = ({
|
|
|
160
146
|
videoRef={camera.videoRef}
|
|
161
147
|
canvasRef={canvasRef}
|
|
162
148
|
facingMode={camera.facingMode}
|
|
163
|
-
multipleFaces={throttledMultipleFaces.value}
|
|
164
149
|
progress={
|
|
165
150
|
faceCapture.capturesTaken.value > 0
|
|
166
151
|
? faceCapture.capturesTaken.value / faceCapture.totalCaptures.value
|
|
@@ -178,6 +163,9 @@ const SmartSelfieCapture: FunctionComponent<Props> = ({
|
|
|
178
163
|
isCapturing={faceCapture.isCapturing.value}
|
|
179
164
|
hasFinishedCapture={faceCapture.hasFinishedCapture.value}
|
|
180
165
|
isReadyToCapture={faceCapture.isReadyToCapture.value}
|
|
166
|
+
captureButtonFallbackEnabled={
|
|
167
|
+
faceCapture.captureButtonFallbackEnabled.value
|
|
168
|
+
}
|
|
181
169
|
allowAgentMode={allowAgentMode}
|
|
182
170
|
agentSupported={camera.agentSupported}
|
|
183
171
|
showAgentModeForTests={showAgentModeForTests}
|
|
@@ -5,7 +5,6 @@ interface CameraPreviewProps {
|
|
|
5
5
|
videoRef: Ref<HTMLVideoElement>;
|
|
6
6
|
canvasRef: Ref<HTMLCanvasElement>;
|
|
7
7
|
facingMode: 'user' | 'environment';
|
|
8
|
-
multipleFaces: boolean;
|
|
9
8
|
progress: number;
|
|
10
9
|
interval: number;
|
|
11
10
|
themeColor: string;
|
|
@@ -15,7 +14,6 @@ export const CameraPreview: FunctionComponent<CameraPreviewProps> = ({
|
|
|
15
14
|
videoRef,
|
|
16
15
|
canvasRef,
|
|
17
16
|
facingMode,
|
|
18
|
-
multipleFaces,
|
|
19
17
|
progress,
|
|
20
18
|
interval,
|
|
21
19
|
themeColor,
|
|
@@ -25,7 +23,7 @@ export const CameraPreview: FunctionComponent<CameraPreviewProps> = ({
|
|
|
25
23
|
<div
|
|
26
24
|
className="video-wrapper"
|
|
27
25
|
style={{
|
|
28
|
-
clipPath:
|
|
26
|
+
clipPath: 'url(#selfie-clip-path)',
|
|
29
27
|
}}
|
|
30
28
|
>
|
|
31
29
|
<div className="video-container">
|
|
@@ -42,13 +40,11 @@ export const CameraPreview: FunctionComponent<CameraPreviewProps> = ({
|
|
|
42
40
|
/>
|
|
43
41
|
</div>
|
|
44
42
|
</div>
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
/>
|
|
51
|
-
)}
|
|
43
|
+
<OvalProgress
|
|
44
|
+
progress={progress}
|
|
45
|
+
duration={interval}
|
|
46
|
+
themeColor={themeColor}
|
|
47
|
+
/>
|
|
52
48
|
</div>
|
|
53
49
|
|
|
54
50
|
<style>{`
|
|
@@ -5,6 +5,7 @@ interface CaptureControlsProps {
|
|
|
5
5
|
isCapturing: boolean;
|
|
6
6
|
hasFinishedCapture: boolean;
|
|
7
7
|
isReadyToCapture: boolean;
|
|
8
|
+
captureButtonFallbackEnabled: boolean;
|
|
8
9
|
allowAgentMode: boolean;
|
|
9
10
|
agentSupported: boolean;
|
|
10
11
|
showAgentModeForTests: boolean;
|
|
@@ -18,6 +19,7 @@ export const CaptureControls: FunctionComponent<CaptureControlsProps> = ({
|
|
|
18
19
|
isCapturing,
|
|
19
20
|
hasFinishedCapture,
|
|
20
21
|
isReadyToCapture,
|
|
22
|
+
captureButtonFallbackEnabled,
|
|
21
23
|
allowAgentMode,
|
|
22
24
|
agentSupported,
|
|
23
25
|
showAgentModeForTests,
|
|
@@ -32,7 +34,11 @@ export const CaptureControls: FunctionComponent<CaptureControlsProps> = ({
|
|
|
32
34
|
id="start-image-capture"
|
|
33
35
|
class="btn-primary"
|
|
34
36
|
onClick={onStartCapture}
|
|
35
|
-
disabled={
|
|
37
|
+
disabled={
|
|
38
|
+
isCapturing ||
|
|
39
|
+
hasFinishedCapture ||
|
|
40
|
+
(!isReadyToCapture && !captureButtonFallbackEnabled)
|
|
41
|
+
}
|
|
36
42
|
>
|
|
37
43
|
{t('selfie.capture.button.startCapture')}
|
|
38
44
|
</button>
|
|
@@ -51,11 +51,11 @@ export const useFaceCapture = ({
|
|
|
51
51
|
const animationFrameRef = useRef<number | null>(null);
|
|
52
52
|
const captureTimerRef = useRef<NodeJS.Timeout | null>(null);
|
|
53
53
|
const resumeCaptureRef = useRef<(() => void) | null>(null);
|
|
54
|
+
const fallbackTimerRef = useRef<NodeJS.Timeout | null>(null);
|
|
54
55
|
|
|
55
56
|
const faceDetected = useSignal(false);
|
|
56
57
|
const faceInBounds = useSignal(false);
|
|
57
58
|
const faceProximity = useSignal<'too-close' | 'too-far' | 'good'>('good');
|
|
58
|
-
const multipleFaces = useSignal(false);
|
|
59
59
|
const videoAspectRatio = useSignal(16 / 9);
|
|
60
60
|
const faceLandmarks = useSignal<any[]>([]);
|
|
61
61
|
const currentSmileScore = useSignal(0);
|
|
@@ -64,6 +64,7 @@ export const useFaceCapture = ({
|
|
|
64
64
|
const lastSmileTime = useSignal(0);
|
|
65
65
|
const alertTitle = useSignal('');
|
|
66
66
|
const isInitializing = useSignal(true);
|
|
67
|
+
const captureButtonFallbackEnabled = useSignal(false);
|
|
67
68
|
|
|
68
69
|
const isCapturing = useSignal(false);
|
|
69
70
|
const isPaused = useSignal(false);
|
|
@@ -83,8 +84,7 @@ export const useFaceCapture = ({
|
|
|
83
84
|
() =>
|
|
84
85
|
faceDetected.value &&
|
|
85
86
|
faceInBounds.value &&
|
|
86
|
-
faceProximity.value === 'good'
|
|
87
|
-
!multipleFaces.value,
|
|
87
|
+
faceProximity.value === 'good',
|
|
88
88
|
);
|
|
89
89
|
|
|
90
90
|
const updateAlertImmediate = (messageKey: MessageKey | null) => {
|
|
@@ -101,6 +101,19 @@ export const useFaceCapture = ({
|
|
|
101
101
|
}, 600),
|
|
102
102
|
).current;
|
|
103
103
|
|
|
104
|
+
const CAPTURE_FALLBACK_TIMEOUT_MS = 10000;
|
|
105
|
+
|
|
106
|
+
const startFallbackTimer = () => {
|
|
107
|
+
if (fallbackTimerRef.current) {
|
|
108
|
+
clearTimeout(fallbackTimerRef.current);
|
|
109
|
+
}
|
|
110
|
+
fallbackTimerRef.current = setTimeout(() => {
|
|
111
|
+
if (!isReadyToCapture.value) {
|
|
112
|
+
captureButtonFallbackEnabled.value = true;
|
|
113
|
+
}
|
|
114
|
+
}, CAPTURE_FALLBACK_TIMEOUT_MS);
|
|
115
|
+
};
|
|
116
|
+
|
|
104
117
|
const initializeFaceLandmarker = async () => {
|
|
105
118
|
try {
|
|
106
119
|
const isAlreadyLoaded =
|
|
@@ -114,10 +127,15 @@ export const useFaceCapture = ({
|
|
|
114
127
|
|
|
115
128
|
faceLandmarkerRef.current = await getMediapipeInstance();
|
|
116
129
|
isInitializing.value = false;
|
|
130
|
+
startFallbackTimer();
|
|
117
131
|
} catch (error) {
|
|
118
132
|
console.error('Failed to initialize MediaPipe:', error);
|
|
119
133
|
isInitializing.value = false;
|
|
134
|
+
// MediaPipe failed — start the fallback timer so the button eventually
|
|
135
|
+
// enables and the user isn't permanently stuck.
|
|
136
|
+
startFallbackTimer();
|
|
120
137
|
}
|
|
138
|
+
startFallbackTimer();
|
|
121
139
|
};
|
|
122
140
|
|
|
123
141
|
const setupCanvas = () => {
|
|
@@ -165,8 +183,6 @@ export const useFaceCapture = ({
|
|
|
165
183
|
const updateAlerts = () => {
|
|
166
184
|
if (isInitializing.value) {
|
|
167
185
|
updateAlertImmediate('initializing');
|
|
168
|
-
} else if (multipleFaces.value) {
|
|
169
|
-
updateAlert('multiple-faces');
|
|
170
186
|
} else if (!faceDetected.value) {
|
|
171
187
|
updateAlert('no-face');
|
|
172
188
|
} else if (faceProximity.value === 'too-close') {
|
|
@@ -260,13 +276,12 @@ export const useFaceCapture = ({
|
|
|
260
276
|
|
|
261
277
|
// Check number of faces
|
|
262
278
|
const numFaces = results.faceLandmarks ? results.faceLandmarks.length : 0;
|
|
263
|
-
multipleFaces.value = numFaces > 1;
|
|
264
279
|
|
|
265
280
|
// Check if face is detected
|
|
266
281
|
const hasFace =
|
|
267
282
|
results.faceBlendshapes &&
|
|
268
283
|
results.faceBlendshapes.length > 0 &&
|
|
269
|
-
numFaces
|
|
284
|
+
numFaces >= 1;
|
|
270
285
|
faceDetected.value = hasFace;
|
|
271
286
|
|
|
272
287
|
if (hasFace && results.faceLandmarks) {
|
|
@@ -322,7 +337,7 @@ export const useFaceCapture = ({
|
|
|
322
337
|
}
|
|
323
338
|
}
|
|
324
339
|
} else {
|
|
325
|
-
// No face detected
|
|
340
|
+
// No face detected - reset values
|
|
326
341
|
currentSmileScore.value = 0;
|
|
327
342
|
currentFaceSize.value = 0;
|
|
328
343
|
currentMouthOpen.value = 0;
|
|
@@ -334,7 +349,6 @@ export const useFaceCapture = ({
|
|
|
334
349
|
} catch {
|
|
335
350
|
faceDetected.value = false;
|
|
336
351
|
faceInBounds.value = false;
|
|
337
|
-
multipleFaces.value = false;
|
|
338
352
|
faceProximity.value = 'good';
|
|
339
353
|
currentMouthOpen.value = 0;
|
|
340
354
|
|
|
@@ -417,7 +431,6 @@ export const useFaceCapture = ({
|
|
|
417
431
|
isPaused.value = true;
|
|
418
432
|
|
|
419
433
|
if (
|
|
420
|
-
!multipleFaces.value &&
|
|
421
434
|
faceDetected.value &&
|
|
422
435
|
faceInBounds.value &&
|
|
423
436
|
faceProximity.value === 'good'
|
|
@@ -437,11 +450,6 @@ export const useFaceCapture = ({
|
|
|
437
450
|
return;
|
|
438
451
|
}
|
|
439
452
|
|
|
440
|
-
if (multipleFaces.value) {
|
|
441
|
-
pauseCapture();
|
|
442
|
-
return;
|
|
443
|
-
}
|
|
444
|
-
|
|
445
453
|
if (!faceDetected.value) {
|
|
446
454
|
return;
|
|
447
455
|
}
|
|
@@ -474,8 +482,7 @@ export const useFaceCapture = ({
|
|
|
474
482
|
if (
|
|
475
483
|
faceDetected.value &&
|
|
476
484
|
faceProximity.value === 'good' &&
|
|
477
|
-
faceInBounds.value
|
|
478
|
-
!multipleFaces.value
|
|
485
|
+
faceInBounds.value
|
|
479
486
|
) {
|
|
480
487
|
const isInSmileZone = capturesTaken.value >= smileCheckpoint.value;
|
|
481
488
|
if (isInSmileZone) {
|
|
@@ -556,6 +563,9 @@ export const useFaceCapture = ({
|
|
|
556
563
|
if (captureTimerRef.current) {
|
|
557
564
|
clearInterval(captureTimerRef.current);
|
|
558
565
|
}
|
|
566
|
+
if (fallbackTimerRef.current) {
|
|
567
|
+
clearTimeout(fallbackTimerRef.current);
|
|
568
|
+
}
|
|
559
569
|
stopDetectionLoop();
|
|
560
570
|
updateAlert.cancel();
|
|
561
571
|
};
|
|
@@ -564,12 +574,16 @@ export const useFaceCapture = ({
|
|
|
564
574
|
faceDetected.value = false;
|
|
565
575
|
faceInBounds.value = false;
|
|
566
576
|
faceProximity.value = 'good';
|
|
567
|
-
multipleFaces.value = false;
|
|
568
577
|
faceLandmarks.value = [];
|
|
569
578
|
currentSmileScore.value = 0;
|
|
570
579
|
currentFaceSize.value = 0;
|
|
571
580
|
currentMouthOpen.value = 0;
|
|
572
581
|
lastSmileTime.value = 0;
|
|
582
|
+
captureButtonFallbackEnabled.value = false;
|
|
583
|
+
if (fallbackTimerRef.current) {
|
|
584
|
+
clearTimeout(fallbackTimerRef.current);
|
|
585
|
+
fallbackTimerRef.current = null;
|
|
586
|
+
}
|
|
573
587
|
|
|
574
588
|
if (canvasRef.current) {
|
|
575
589
|
clearCanvas(canvasRef.current);
|
|
@@ -580,7 +594,6 @@ export const useFaceCapture = ({
|
|
|
580
594
|
faceDetected,
|
|
581
595
|
faceInBounds,
|
|
582
596
|
faceProximity,
|
|
583
|
-
multipleFaces,
|
|
584
597
|
videoAspectRatio,
|
|
585
598
|
faceLandmarks,
|
|
586
599
|
currentSmileScore,
|
|
@@ -590,6 +603,7 @@ export const useFaceCapture = ({
|
|
|
590
603
|
alertTitle,
|
|
591
604
|
isInitializing,
|
|
592
605
|
isReadyToCapture,
|
|
606
|
+
captureButtonFallbackEnabled,
|
|
593
607
|
|
|
594
608
|
isCapturing,
|
|
595
609
|
isPaused,
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { t } from '../../../../../domain/localisation';
|
|
2
2
|
|
|
3
3
|
export const MESSAGES = {
|
|
4
|
-
'multiple-faces': () => t('selfie.smart.alert.multipleFaces'),
|
|
5
4
|
'no-face': () => t('selfie.smart.alert.noFace'),
|
|
6
5
|
'out-of-bounds': () => t('selfie.smart.alert.outOfBounds'),
|
|
7
6
|
'too-close': () => t('selfie.smart.alert.tooClose'),
|