@opexa/portal-components 0.0.969 → 0.0.971
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/client/hooks/useCamera.js +5 -1
- package/dist/components/shared/SelfieImageField/SelfieImageField.client.js +1 -1
- package/dist/components/shared/SelfieImageField/SelfieImageField.js +2 -7
- package/dist/components/shared/SelfieImageField/useSelfieImageField.d.ts +1 -0
- package/dist/components/shared/SelfieImageField/useSelfieImageField.js +50 -48
- package/package.json +1 -1
|
@@ -142,7 +142,10 @@ export function useCamera(options = {}) {
|
|
|
142
142
|
canvas.toBlob((blob) => {
|
|
143
143
|
if (!blob) {
|
|
144
144
|
resolve(null);
|
|
145
|
-
return
|
|
145
|
+
return setError({
|
|
146
|
+
name: 'CameraError',
|
|
147
|
+
message: 'Failed to snap photo',
|
|
148
|
+
});
|
|
146
149
|
}
|
|
147
150
|
const url = URL.createObjectURL(blob);
|
|
148
151
|
const file = new File([blob], `${crypto.randomUUID()}.jpeg`, {
|
|
@@ -168,6 +171,7 @@ export function useCamera(options = {}) {
|
|
|
168
171
|
const reopen = useCallback(async () => {
|
|
169
172
|
setSnapping(false);
|
|
170
173
|
setLoading(true);
|
|
174
|
+
setData(null);
|
|
171
175
|
await close();
|
|
172
176
|
await sleep();
|
|
173
177
|
await open();
|
|
@@ -95,7 +95,7 @@ function Camera() {
|
|
|
95
95
|
context.mutation.mutate({ file });
|
|
96
96
|
context.disclosure.setOpen(false);
|
|
97
97
|
}, className: "w-full lg:w-[10rem]", children: [_jsx(CheckCircleIcon, { className: "size-5" }), "Use Photo"] }), _jsxs(Button, { variant: "outline", colorScheme: "gray", onClick: () => {
|
|
98
|
-
context.camera.
|
|
98
|
+
context.camera.reopen();
|
|
99
99
|
}, className: "w-full lg:w-[10rem]", children: [_jsx(RefreshCcw01Icon, { className: "size-5" }), " Retake"] })] }))] }) })] }) }));
|
|
100
100
|
}
|
|
101
101
|
/*
|
|
@@ -1,11 +1,6 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
-
import
|
|
4
|
-
import { Spinner02Icon } from '../../../icons/Spinner02Icon.js';
|
|
5
|
-
const Component = dynamic(() => import('./SelfieImageField.client.js').then((mod) => mod.SelfieImageField__client), {
|
|
6
|
-
ssr: false,
|
|
7
|
-
loading: () => (_jsx("div", { className: "flex aspect-[352/180] w-full shrink-0 items-center justify-center rounded-xl border border-border-primary bg-bg-primary", children: _jsx(Spinner02Icon, { className: "size-8 text-text-quinary" }) })),
|
|
8
|
-
});
|
|
3
|
+
import { SelfieImageField__client } from './SelfieImageField.client.js';
|
|
9
4
|
export function SelfieImageField(props) {
|
|
10
|
-
return _jsx(
|
|
5
|
+
return _jsx(SelfieImageField__client, { ...props });
|
|
11
6
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { useFieldContext } from '@ark-ui/react';
|
|
2
2
|
import { FaceDetector, FilesetResolver } from '@mediapipe/tasks-vision';
|
|
3
|
-
import { useRef, useState } from 'react';
|
|
3
|
+
import { useEffect, useRef, useState } from 'react';
|
|
4
4
|
import invariant from 'tiny-invariant';
|
|
5
5
|
import { useInterval } from 'usehooks-ts';
|
|
6
6
|
import { useCamera } from '../../../client/hooks/useCamera.js';
|
|
@@ -111,20 +111,39 @@ export function useSelfieImageField(props) {
|
|
|
111
111
|
if (open) {
|
|
112
112
|
camera.open();
|
|
113
113
|
}
|
|
114
|
-
else {
|
|
115
|
-
setFaceFound(false);
|
|
116
|
-
camera.close();
|
|
117
|
-
}
|
|
118
114
|
},
|
|
119
115
|
});
|
|
120
116
|
const [faceFound, setFaceFound] = useState(false);
|
|
117
|
+
const [detectorReady, setDetectorReady] = useState(false);
|
|
118
|
+
// Preload detector when disclosure opens
|
|
119
|
+
useEffect(() => {
|
|
120
|
+
if (disclosure.open) {
|
|
121
|
+
setDetectorReady(false);
|
|
122
|
+
getFaceDetector()
|
|
123
|
+
.then(() => setDetectorReady(true))
|
|
124
|
+
.catch((e) => {
|
|
125
|
+
console.error('Failed to load face detector:', e);
|
|
126
|
+
setDetectorReady(false);
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
}, [disclosure.open]);
|
|
130
|
+
// Reset faceFound when camera is reset or reopened
|
|
131
|
+
// biome-ignore lint/correctness/useExhaustiveDependencies: Reset faceFound on state change
|
|
132
|
+
useEffect(() => {
|
|
133
|
+
setFaceFound(false);
|
|
134
|
+
}, [camera.loading, camera.data]);
|
|
121
135
|
useInterval(async () => {
|
|
122
136
|
if (!camera.videoRef.current || !guideRef.current) {
|
|
123
137
|
return setFaceFound(false);
|
|
124
138
|
}
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
139
|
+
const found = await validateFaceFromVideo(camera.videoRef.current, guideRef.current);
|
|
140
|
+
setFaceFound(found);
|
|
141
|
+
}, disclosure.open &&
|
|
142
|
+
detectorReady &&
|
|
143
|
+
!camera.data &&
|
|
144
|
+
!camera.error &&
|
|
145
|
+
!camera.loading
|
|
146
|
+
? 100
|
|
128
147
|
: null);
|
|
129
148
|
return {
|
|
130
149
|
field,
|
|
@@ -138,13 +157,13 @@ export function useSelfieImageField(props) {
|
|
|
138
157
|
guideRef,
|
|
139
158
|
maskRef,
|
|
140
159
|
faceFound,
|
|
160
|
+
detectorReady,
|
|
141
161
|
};
|
|
142
162
|
}
|
|
143
|
-
let
|
|
144
|
-
let __video_face_detector__ = null;
|
|
163
|
+
let __face_detector__ = null;
|
|
145
164
|
let __vision__ = null;
|
|
146
|
-
let
|
|
147
|
-
let
|
|
165
|
+
let __current_running_mode__ = 'IMAGE';
|
|
166
|
+
let __face_detector_promise__ = null;
|
|
148
167
|
let __vision_promise__ = null;
|
|
149
168
|
async function getVision() {
|
|
150
169
|
if (__vision__)
|
|
@@ -164,12 +183,12 @@ async function getVision() {
|
|
|
164
183
|
})();
|
|
165
184
|
return __vision_promise__;
|
|
166
185
|
}
|
|
167
|
-
async function
|
|
168
|
-
if (
|
|
169
|
-
return
|
|
170
|
-
if (
|
|
171
|
-
return
|
|
172
|
-
|
|
186
|
+
async function getFaceDetector() {
|
|
187
|
+
if (__face_detector__)
|
|
188
|
+
return __face_detector__;
|
|
189
|
+
if (__face_detector_promise__)
|
|
190
|
+
return __face_detector_promise__;
|
|
191
|
+
__face_detector_promise__ = (async () => {
|
|
173
192
|
try {
|
|
174
193
|
const vision = await getVision();
|
|
175
194
|
const detector = await FaceDetector.createFromOptions(vision, {
|
|
@@ -180,45 +199,28 @@ async function getImageFaceDetector() {
|
|
|
180
199
|
modelAssetPath: 'https://storage.googleapis.com/mediapipe-models/face_detector/blaze_face_short_range/float16/1/blaze_face_short_range.tflite',
|
|
181
200
|
},
|
|
182
201
|
});
|
|
183
|
-
|
|
202
|
+
__face_detector__ = detector;
|
|
203
|
+
__current_running_mode__ = 'IMAGE';
|
|
184
204
|
return detector;
|
|
185
205
|
}
|
|
186
206
|
catch (e) {
|
|
187
|
-
|
|
207
|
+
__face_detector_promise__ = null;
|
|
188
208
|
throw e;
|
|
189
209
|
}
|
|
190
210
|
})();
|
|
191
|
-
return
|
|
211
|
+
return __face_detector_promise__;
|
|
192
212
|
}
|
|
193
|
-
async function
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
const vision = await getVision();
|
|
201
|
-
const detector = await FaceDetector.createFromOptions(vision, {
|
|
202
|
-
runningMode: 'VIDEO',
|
|
203
|
-
minDetectionConfidence: 0.5,
|
|
204
|
-
baseOptions: {
|
|
205
|
-
delegate: 'GPU',
|
|
206
|
-
modelAssetPath: 'https://storage.googleapis.com/mediapipe-models/face_detector/blaze_face_short_range/float16/1/blaze_face_short_range.tflite',
|
|
207
|
-
},
|
|
208
|
-
});
|
|
209
|
-
__video_face_detector__ = detector;
|
|
210
|
-
return detector;
|
|
211
|
-
}
|
|
212
|
-
catch (e) {
|
|
213
|
-
__video_face_detector_promise__ = null;
|
|
214
|
-
throw e;
|
|
215
|
-
}
|
|
216
|
-
})();
|
|
217
|
-
return __video_face_detector_promise__;
|
|
213
|
+
async function ensureRunningMode(mode) {
|
|
214
|
+
const detector = await getFaceDetector();
|
|
215
|
+
if (__current_running_mode__ !== mode) {
|
|
216
|
+
await detector.setOptions({ runningMode: mode });
|
|
217
|
+
__current_running_mode__ = mode;
|
|
218
|
+
}
|
|
219
|
+
return detector;
|
|
218
220
|
}
|
|
219
221
|
async function validateFaceFromImage(image) {
|
|
220
222
|
try {
|
|
221
|
-
const detector = await
|
|
223
|
+
const detector = await ensureRunningMode('IMAGE');
|
|
222
224
|
const result = detector.detect(image);
|
|
223
225
|
return result.detections.length > 0;
|
|
224
226
|
}
|
|
@@ -231,7 +233,7 @@ async function validateFaceFromVideo(video, guide) {
|
|
|
231
233
|
if (video.readyState < 2)
|
|
232
234
|
return false;
|
|
233
235
|
try {
|
|
234
|
-
const detector = await
|
|
236
|
+
const detector = await ensureRunningMode('VIDEO');
|
|
235
237
|
const result = detector.detectForVideo(video, performance.now());
|
|
236
238
|
const detection = result.detections.at(0);
|
|
237
239
|
if (!detection)
|