@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.
@@ -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.reset();
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 dynamic from 'next/dynamic';
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(Component, { ...props });
5
+ return _jsx(SelfieImageField__client, { ...props });
11
6
  }
@@ -20,4 +20,5 @@ export declare function useSelfieImageField(props: UseSelfieImageFieldProps): {
20
20
  guideRef: import("react").RefObject<HTMLDivElement | null>;
21
21
  maskRef: import("react").RefObject<HTMLDivElement | null>;
22
22
  faceFound: boolean;
23
+ detectorReady: boolean;
23
24
  };
@@ -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
- setFaceFound(await validateFaceFromVideo(camera.videoRef.current, guideRef.current));
126
- }, disclosure.open && !camera.data && !camera.error && !camera.loading
127
- ? 300
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 __image_face_detector__ = null;
144
- let __video_face_detector__ = null;
163
+ let __face_detector__ = null;
145
164
  let __vision__ = null;
146
- let __image_face_detector_promise__ = null;
147
- let __video_face_detector_promise__ = null;
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 getImageFaceDetector() {
168
- if (__image_face_detector__)
169
- return __image_face_detector__;
170
- if (__image_face_detector_promise__)
171
- return __image_face_detector_promise__;
172
- __image_face_detector_promise__ = (async () => {
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
- __image_face_detector__ = detector;
202
+ __face_detector__ = detector;
203
+ __current_running_mode__ = 'IMAGE';
184
204
  return detector;
185
205
  }
186
206
  catch (e) {
187
- __image_face_detector_promise__ = null;
207
+ __face_detector_promise__ = null;
188
208
  throw e;
189
209
  }
190
210
  })();
191
- return __image_face_detector_promise__;
211
+ return __face_detector_promise__;
192
212
  }
193
- async function getVideoFaceDetector() {
194
- if (__video_face_detector__)
195
- return __video_face_detector__;
196
- if (__video_face_detector_promise__)
197
- return __video_face_detector_promise__;
198
- __video_face_detector_promise__ = (async () => {
199
- try {
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 getImageFaceDetector();
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 getVideoFaceDetector();
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)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opexa/portal-components",
3
- "version": "0.0.969",
3
+ "version": "0.0.971",
4
4
  "exports": {
5
5
  "./ui/*": {
6
6
  "types": "./dist/ui/*/index.d.ts",