facecog-liveness-showcase 0.0.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/.browserslistrc +15 -0
- package/.dockerignore +48 -0
- package/.editorconfig +16 -0
- package/.eslintrc.json +47 -0
- package/.vercelignore +7 -0
- package/.vscode/extensions.json +5 -0
- package/.vscode/settings.json +3 -0
- package/DOCKER.md +221 -0
- package/Dockerfile +33 -0
- package/README.md +268 -0
- package/angular.json +156 -0
- package/capacitor.config.ts +9 -0
- package/docker-compose.dev.yml +20 -0
- package/docker-compose.yml +18 -0
- package/ionic.config.json +7 -0
- package/jest.config.js +38 -0
- package/nginx.conf +50 -0
- package/package.json +131 -0
- package/patches/ng-packagr+20.3.2.patch +60 -0
- package/projects/facecog-liveness-verification/README.md +295 -0
- package/projects/facecog-liveness-verification/ng-package.json +7 -0
- package/projects/facecog-liveness-verification/package.json +48 -0
- package/projects/facecog-liveness-verification/scripts/build-with-wrapper-copy.js +38 -0
- package/projects/facecog-liveness-verification/scripts/copy-wrapper-after-ngc.js +35 -0
- package/projects/facecog-liveness-verification/sources/FaceLivenessReactWrapper.tsx +320 -0
- package/projects/facecog-liveness-verification/src/lib/components/aws-face-liveness/FaceLivenessReactWrapper.generated.d.ts +28 -0
- package/projects/facecog-liveness-verification/src/lib/components/aws-face-liveness/FaceLivenessReactWrapper.generated.js +247 -0
- package/projects/facecog-liveness-verification/src/lib/components/aws-face-liveness/FaceLivenessReactWrapper.generated.js.map +1 -0
- package/projects/facecog-liveness-verification/src/lib/components/aws-face-liveness/FaceLivenessReactWrapper.js.map +1 -0
- package/projects/facecog-liveness-verification/src/lib/components/aws-face-liveness/FaceLivenessReactWrapper.ts +5 -0
- package/projects/facecog-liveness-verification/src/lib/components/aws-face-liveness/aws-face-liveness.component.ts +500 -0
- package/projects/facecog-liveness-verification/src/lib/components/camera-permission/camera-permission.component.html +41 -0
- package/projects/facecog-liveness-verification/src/lib/components/camera-permission/camera-permission.component.scss +234 -0
- package/projects/facecog-liveness-verification/src/lib/components/camera-permission/camera-permission.component.spec.ts +158 -0
- package/projects/facecog-liveness-verification/src/lib/components/camera-permission/camera-permission.component.ts +58 -0
- package/projects/facecog-liveness-verification/src/lib/components/camera-verification/camera-verification.component.html +34 -0
- package/projects/facecog-liveness-verification/src/lib/components/camera-verification/camera-verification.component.ts +210 -0
- package/projects/facecog-liveness-verification/src/lib/components/dialogs/save-custom-pose-dialog.component.ts +174 -0
- package/projects/facecog-liveness-verification/src/lib/components/facetec-scan/facetec-scan.component.html +45 -0
- package/projects/facecog-liveness-verification/src/lib/components/facetec-scan/facetec-scan.component.scss +87 -0
- package/projects/facecog-liveness-verification/src/lib/components/facetec-scan/facetec-scan.component.ts +182 -0
- package/projects/facecog-liveness-verification/src/lib/components/intro/intro.component.html +394 -0
- package/projects/facecog-liveness-verification/src/lib/components/intro/intro.component.scss +1567 -0
- package/projects/facecog-liveness-verification/src/lib/components/intro/intro.component.spec.ts +699 -0
- package/projects/facecog-liveness-verification/src/lib/components/intro/intro.component.ts +721 -0
- package/projects/facecog-liveness-verification/src/lib/components/live-preview/live-preview.component.html +120 -0
- package/projects/facecog-liveness-verification/src/lib/components/live-preview/live-preview.component.scss +611 -0
- package/projects/facecog-liveness-verification/src/lib/components/live-preview/live-preview.component.spec.ts +605 -0
- package/projects/facecog-liveness-verification/src/lib/components/live-preview/live-preview.component.ts +524 -0
- package/projects/facecog-liveness-verification/src/lib/components/liveness-flow/liveness-flow.component.html +73 -0
- package/projects/facecog-liveness-verification/src/lib/components/liveness-flow/liveness-flow.component.scss +19 -0
- package/projects/facecog-liveness-verification/src/lib/components/liveness-flow/liveness-flow.component.spec.ts +673 -0
- package/projects/facecog-liveness-verification/src/lib/components/liveness-flow/liveness-flow.component.ts +963 -0
- package/projects/facecog-liveness-verification/src/lib/components/liveness-verification/liveness-verification.component.html +38 -0
- package/projects/facecog-liveness-verification/src/lib/components/liveness-verification/liveness-verification.component.scss +10 -0
- package/projects/facecog-liveness-verification/src/lib/components/liveness-verification/liveness-verification.component.ts +233 -0
- package/projects/facecog-liveness-verification/src/lib/components/pose-selection/pose-selection.component.html +17 -0
- package/projects/facecog-liveness-verification/src/lib/components/pose-selection/pose-selection.component.spec.ts +35 -0
- package/projects/facecog-liveness-verification/src/lib/components/pose-selection/pose-selection.component.ts +33 -0
- package/projects/facecog-liveness-verification/src/lib/components/processing/processing.component.html +17 -0
- package/projects/facecog-liveness-verification/src/lib/components/processing/processing.component.scss +156 -0
- package/projects/facecog-liveness-verification/src/lib/components/processing/processing.component.spec.ts +46 -0
- package/projects/facecog-liveness-verification/src/lib/components/processing/processing.component.ts +18 -0
- package/projects/facecog-liveness-verification/src/lib/components/verification-result/verification-result.component.html +190 -0
- package/projects/facecog-liveness-verification/src/lib/components/verification-result/verification-result.component.scss +534 -0
- package/projects/facecog-liveness-verification/src/lib/components/verification-result/verification-result.component.spec.ts +286 -0
- package/projects/facecog-liveness-verification/src/lib/components/verification-result/verification-result.component.ts +155 -0
- package/projects/facecog-liveness-verification/src/lib/interfaces/analyze-response.interface.ts +16 -0
- package/projects/facecog-liveness-verification/src/lib/interfaces/aws-face-liveness.interface.ts +46 -0
- package/projects/facecog-liveness-verification/src/lib/interfaces/backend-adapter.interface.ts +21 -0
- package/projects/facecog-liveness-verification/src/lib/interfaces/backend-http-client.interface.ts +93 -0
- package/projects/facecog-liveness-verification/src/lib/interfaces/backend-response.interface.ts +9 -0
- package/projects/facecog-liveness-verification/src/lib/interfaces/camera-provider.interface.ts +107 -0
- package/projects/facecog-liveness-verification/src/lib/interfaces/category-info.interface.ts +9 -0
- package/projects/facecog-liveness-verification/src/lib/interfaces/custom-pose-data.interface.ts +14 -0
- package/projects/facecog-liveness-verification/src/lib/interfaces/custom-pose-repository.interface.ts +48 -0
- package/projects/facecog-liveness-verification/src/lib/interfaces/custom-pose-response.interface.ts +14 -0
- package/projects/facecog-liveness-verification/src/lib/interfaces/index.ts +52 -0
- package/projects/facecog-liveness-verification/src/lib/interfaces/liveness-action-result.interface.ts +13 -0
- package/projects/facecog-liveness-verification/src/lib/interfaces/liveness-config.interface.ts +17 -0
- package/projects/facecog-liveness-verification/src/lib/interfaces/liveness-metadata.interface.ts +17 -0
- package/projects/facecog-liveness-verification/src/lib/interfaces/liveness-result.interface.ts +24 -0
- package/projects/facecog-liveness-verification/src/lib/interfaces/liveness-verification-config.interface.ts +41 -0
- package/projects/facecog-liveness-verification/src/lib/interfaces/multi-backend-analyze-response.interface.ts +21 -0
- package/projects/facecog-liveness-verification/src/lib/interfaces/multi-backend-liveness-result.interface.ts +14 -0
- package/projects/facecog-liveness-verification/src/lib/interfaces/pose-definition.interface.ts +35 -0
- package/projects/facecog-liveness-verification/src/lib/interfaces/pose-keypoint.interface.ts +12 -0
- package/projects/facecog-liveness-verification/src/lib/interfaces/pose-match-result.interface.ts +9 -0
- package/projects/facecog-liveness-verification/src/lib/interfaces/pose-verify-response.interface.ts +8 -0
- package/projects/facecog-liveness-verification/src/lib/interfaces/scan-results.interface.ts +29 -0
- package/projects/facecog-liveness-verification/src/lib/interfaces/verification-plan.interface.ts +42 -0
- package/projects/facecog-liveness-verification/src/lib/interfaces/verification-progress-event.interface.ts +12 -0
- package/projects/facecog-liveness-verification/src/lib/interfaces/verification-session.interface.ts +72 -0
- package/projects/facecog-liveness-verification/src/lib/interfaces/verification-step-change-event.interface.ts +11 -0
- package/projects/facecog-liveness-verification/src/lib/interfaces/video-recording.interface.ts +9 -0
- package/projects/facecog-liveness-verification/src/lib/liveness-verification.module.ts +123 -0
- package/projects/facecog-liveness-verification/src/lib/models/constants/aws-face-liveness-component.token.ts +23 -0
- package/projects/facecog-liveness-verification/src/lib/models/constants/category-info.constant.ts +14 -0
- package/projects/facecog-liveness-verification/src/lib/models/constants/default-liveness-config.constant.ts +18 -0
- package/projects/facecog-liveness-verification/src/lib/models/constants/index.ts +5 -0
- package/projects/facecog-liveness-verification/src/lib/models/constants/liveness-verification-config.token.ts +16 -0
- package/projects/facecog-liveness-verification/src/lib/models/constants/pose-definitions.constant.ts +377 -0
- package/projects/facecog-liveness-verification/src/lib/models/index.ts +5 -0
- package/projects/facecog-liveness-verification/src/lib/models/utils/index.ts +2 -0
- package/projects/facecog-liveness-verification/src/lib/models/utils/pose.utils.spec.ts +76 -0
- package/projects/facecog-liveness-verification/src/lib/models/utils/pose.utils.ts +59 -0
- package/projects/facecog-liveness-verification/src/lib/services/aws-face-liveness.service.ts +49 -0
- package/projects/facecog-liveness-verification/src/lib/services/backend-http.service.spec.ts +111 -0
- package/projects/facecog-liveness-verification/src/lib/services/backend-http.service.ts +130 -0
- package/projects/facecog-liveness-verification/src/lib/services/backends/azure-backend.service.spec.ts +69 -0
- package/projects/facecog-liveness-verification/src/lib/services/backends/azure-backend.service.ts +72 -0
- package/projects/facecog-liveness-verification/src/lib/services/backends/facetec-backend.service.spec.ts +24 -0
- package/projects/facecog-liveness-verification/src/lib/services/backends/facetec-backend.service.ts +35 -0
- package/projects/facecog-liveness-verification/src/lib/services/backends/mock-backend.service.spec.ts +36 -0
- package/projects/facecog-liveness-verification/src/lib/services/backends/mock-backend.service.ts +39 -0
- package/projects/facecog-liveness-verification/src/lib/services/backends/openpose-backend.service.spec.ts +81 -0
- package/projects/facecog-liveness-verification/src/lib/services/backends/openpose-backend.service.ts +72 -0
- package/projects/facecog-liveness-verification/src/lib/services/backends/rekognition-analysis-backend.service.spec.ts +69 -0
- package/projects/facecog-liveness-verification/src/lib/services/backends/rekognition-analysis-backend.service.ts +83 -0
- package/projects/facecog-liveness-verification/src/lib/services/camera.service.spec.ts +200 -0
- package/projects/facecog-liveness-verification/src/lib/services/camera.service.ts +155 -0
- package/projects/facecog-liveness-verification/src/lib/services/custom-poses-api.service.ts +117 -0
- package/projects/facecog-liveness-verification/src/lib/services/index.ts +18 -0
- package/projects/facecog-liveness-verification/src/lib/services/liveness-backend.service.spec.ts +103 -0
- package/projects/facecog-liveness-verification/src/lib/services/liveness-backend.service.ts +61 -0
- package/projects/facecog-liveness-verification/src/lib/services/liveness-config.service.spec.ts +109 -0
- package/projects/facecog-liveness-verification/src/lib/services/liveness-config.service.ts +70 -0
- package/projects/facecog-liveness-verification/src/lib/services/liveness-orchestrator.service.spec.ts +144 -0
- package/projects/facecog-liveness-verification/src/lib/services/liveness-orchestrator.service.ts +162 -0
- package/projects/facecog-liveness-verification/src/lib/services/pose-detection/hand-gesture-detection.service.ts +315 -0
- package/projects/facecog-liveness-verification/src/lib/services/pose-detection/index.ts +5 -0
- package/projects/facecog-liveness-verification/src/lib/services/pose-detection/openpose.service.ts +287 -0
- package/projects/facecog-liveness-verification/src/lib/services/pose-detection/pose-comparison.service.ts +353 -0
- package/projects/facecog-liveness-verification/src/lib/services/pose-detection/pose-matching.service.ts +2370 -0
- package/projects/facecog-liveness-verification/src/lib/services/pose-detection/reference-pose.service.ts +271 -0
- package/projects/facecog-liveness-verification/src/lib/services/pose-selection.service.spec.ts +183 -0
- package/projects/facecog-liveness-verification/src/lib/services/pose-selection.service.ts +179 -0
- package/projects/facecog-liveness-verification/src/lib/services/verification-api.service.spec.ts +159 -0
- package/projects/facecog-liveness-verification/src/lib/services/verification-api.service.ts +151 -0
- package/projects/facecog-liveness-verification/src/lib/services/verification-plan.service.spec.ts +184 -0
- package/projects/facecog-liveness-verification/src/lib/services/verification-plan.service.ts +94 -0
- package/projects/facecog-liveness-verification/src/lib/services/video-recorder.service.spec.ts +52 -0
- package/projects/facecog-liveness-verification/src/lib/services/video-recorder.service.ts +117 -0
- package/projects/facecog-liveness-verification/src/lib/types/detection-strategy.type.ts +5 -0
- package/projects/facecog-liveness-verification/src/lib/types/index.ts +7 -0
- package/projects/facecog-liveness-verification/src/lib/types/liveness-action.type.ts +31 -0
- package/projects/facecog-liveness-verification/src/lib/types/liveness-backend.type.ts +5 -0
- package/projects/facecog-liveness-verification/src/lib/types/pose-category.type.ts +5 -0
- package/projects/facecog-liveness-verification/src/lib/types/pose-difficulty.type.ts +5 -0
- package/projects/facecog-liveness-verification/src/lib/types/verification-flow-step.type.ts +5 -0
- package/projects/facecog-liveness-verification/src/lib/types/verification-step-kind.type.ts +4 -0
- package/projects/facecog-liveness-verification/src/public-api.ts +150 -0
- package/projects/facecog-liveness-verification/tsconfig.lib.json +20 -0
- package/projects/facecog-liveness-verification/tsconfig.lib.prod.json +11 -0
- package/projects/facecog-liveness-verification/tsconfig.spec.json +13 -0
- package/projects/facecog-liveness-verification/tsconfig.wrapper.json +15 -0
- package/projects/facecog-liveness-verification-test/src/app/app-routing.module.ts +22 -0
- package/projects/facecog-liveness-verification-test/src/app/app.component.html +3 -0
- package/projects/facecog-liveness-verification-test/src/app/app.component.scss +0 -0
- package/projects/facecog-liveness-verification-test/src/app/app.component.ts +11 -0
- package/projects/facecog-liveness-verification-test/src/app/app.module.ts +27 -0
- package/projects/facecog-liveness-verification-test/src/app/home/home-routing.module.ts +16 -0
- package/projects/facecog-liveness-verification-test/src/app/home/home.module.ts +19 -0
- package/projects/facecog-liveness-verification-test/src/app/home/home.page.html +39 -0
- package/projects/facecog-liveness-verification-test/src/app/home/home.page.scss +97 -0
- package/projects/facecog-liveness-verification-test/src/app/home/home.page.spec.ts +24 -0
- package/projects/facecog-liveness-verification-test/src/app/home/home.page.ts +92 -0
- package/projects/facecog-liveness-verification-test/src/app/home/verification-modal.component.ts +106 -0
- package/projects/facecog-liveness-verification-test/src/assets/fonts/gilroy/Gilroy-Bold_0.ttf +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/fonts/gilroy/Gilroy-Medium_0.ttf +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/fonts/gilroy/Gilroy-Regular_0.ttf +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/fonts/gilroy/Gilroy-SemiBold_0.ttf +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/fonts/gilroy/Gilroy-Thin_0.ttf +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/icon/favicon.png +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/Five_Fingers_Left.jpg +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/Left_Palm.jpg +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/Ok_Sign_Right.jpg +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/Peace_Sign_Left.jpg +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/README.md +77 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/Right_Palm.jpg +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/Speak_Phrase.jpg +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/Three_Fingers_Right.jpg +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/Thumbs_Up_Left.jpg +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/Thumbs_Up_Right.jpg +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/Wave_Right.jpg +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/blink.jpeg +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/blink_twice.jpeg +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/center_face.png +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/clap.jpeg +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/cover_mouth.png +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/cover_right_eye.png +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/cross_arms.png +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/face_straight.png +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/follow_dot.png +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/look_down.png +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/look_up.png +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/move_closer.png +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/nod.png +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/open_mouth.png +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/raise_eyebrow.png +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/rotate_face.jpeg +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/shake_head.jpeg +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/smile.png +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/tilt_left.png +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/tilt_right.png +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/touch_chin_left.jpg +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/touch_left_cheek.jpeg +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/touch_nose_right.png +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/touch_right_cheek.jpeg +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/turn_left.png +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/turn_right.png +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/wink.jpeg +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/reference-pose.jpg +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/shapes.svg +1 -0
- package/projects/facecog-liveness-verification-test/src/environments/environment.prod.ts +4 -0
- package/projects/facecog-liveness-verification-test/src/environments/environment.ts +17 -0
- package/projects/facecog-liveness-verification-test/src/global.scss +288 -0
- package/projects/facecog-liveness-verification-test/src/index.html +31 -0
- package/projects/facecog-liveness-verification-test/src/main.ts +6 -0
- package/projects/facecog-liveness-verification-test/src/polyfills.ts +55 -0
- package/projects/facecog-liveness-verification-test/src/theme/nextsapien-theme.scss +174 -0
- package/projects/facecog-liveness-verification-test/src/theme/variables.scss +2 -0
- package/projects/facecog-liveness-verification-test/src/zone-flags.ts +6 -0
- package/projects/facecog-liveness-verification-test/tsconfig.app.json +15 -0
- package/projects/facecog-liveness-verification-test/tsconfig.spec.json +14 -0
- package/setup-jest.ts +118 -0
- package/tsconfig.json +41 -0
- package/tsconfig.spec.json +15 -0
- package/vercel.json +24 -0
|
@@ -0,0 +1,721 @@
|
|
|
1
|
+
import { Component, EventEmitter, Input, Output, OnInit, Optional, Inject } from '@angular/core';
|
|
2
|
+
import { IonicModule, ModalController, AlertController, ToastController, LoadingController } from '@ionic/angular';
|
|
3
|
+
import { CommonModule } from '@angular/common';
|
|
4
|
+
import { FormsModule } from '@angular/forms';
|
|
5
|
+
import { ReferencePoseService, ReferencePose } from '../../services/pose-detection/reference-pose.service';
|
|
6
|
+
import { OpenposeService } from '../../services/pose-detection/openpose.service';
|
|
7
|
+
import { PoseSelectionService, SelectedPoseInfo } from '../../services/pose-selection.service';
|
|
8
|
+
import { LivenessConfigService } from '../../services/liveness-config.service';
|
|
9
|
+
import { ICustomPoseRepository, CUSTOM_POSE_REPOSITORY } from '../../interfaces/custom-pose-repository.interface';
|
|
10
|
+
import { PoseDefinition } from '../../interfaces/pose-definition.interface';
|
|
11
|
+
import { LivenessBackend } from '../../types/liveness-backend.type';
|
|
12
|
+
import { POSE_DEFINITIONS } from '../../models/constants/pose-definitions.constant';
|
|
13
|
+
import { CATEGORY_INFO } from '../../models/constants/category-info.constant';
|
|
14
|
+
import { mergeCustomPoses, customPoseToPoseDefinition } from '../../models/utils/pose.utils';
|
|
15
|
+
import { SaveCustomPoseDialogComponent } from '../dialogs/save-custom-pose-dialog.component';
|
|
16
|
+
|
|
17
|
+
@Component({
|
|
18
|
+
selector: 'lib-intro',
|
|
19
|
+
standalone: true,
|
|
20
|
+
imports: [CommonModule, IonicModule, FormsModule],
|
|
21
|
+
templateUrl: './intro.component.html',
|
|
22
|
+
styleUrls: ['./intro.component.scss']
|
|
23
|
+
})
|
|
24
|
+
export class IntroComponent implements OnInit {
|
|
25
|
+
@Input() exampleImageUrl?: string;
|
|
26
|
+
@Input() exampleImageBase64?: string;
|
|
27
|
+
/** When true (pose-specific flow from host), hide backend selection, cancel, and pose-choice text. */
|
|
28
|
+
@Input() poseSpecificMode = false;
|
|
29
|
+
@Output() startClicked = new EventEmitter<void>();
|
|
30
|
+
@Output() cancelClicked = new EventEmitter<void>();
|
|
31
|
+
@Output() referencePoseUploaded = new EventEmitter<ReferencePose>();
|
|
32
|
+
@Output() poseSelected = new EventEmitter<SelectedPoseInfo>();
|
|
33
|
+
@Output() awsFaceLivenessToggled = new EventEmitter<boolean>();
|
|
34
|
+
|
|
35
|
+
// Pose selection state
|
|
36
|
+
allPoses: PoseDefinition[] = POSE_DEFINITIONS;
|
|
37
|
+
selectedPose: SelectedPoseInfo | null = null;
|
|
38
|
+
categoryInfo = CATEGORY_INFO;
|
|
39
|
+
|
|
40
|
+
// Filter state
|
|
41
|
+
selectedCategory: PoseDefinition['category'] | 'all' = 'all';
|
|
42
|
+
selectedDifficulty: PoseDefinition['difficulty'] | 'all' = 'all';
|
|
43
|
+
searchQuery: string = '';
|
|
44
|
+
|
|
45
|
+
// UI state
|
|
46
|
+
viewMode: 'grid' | 'list' = 'grid';
|
|
47
|
+
showFilters: boolean = false;
|
|
48
|
+
|
|
49
|
+
// Pose selection mode: 'browse' (grid) or 'upload' (custom image)
|
|
50
|
+
poseSelectionMode: 'browse' | 'upload' = 'browse';
|
|
51
|
+
|
|
52
|
+
// Reference pose upload state
|
|
53
|
+
referencePoseImage: string | null = null;
|
|
54
|
+
referencePoseDescription: string | null = null;
|
|
55
|
+
isProcessingImage = false;
|
|
56
|
+
poseDetectionStatus: { success: boolean; message: string } | null = null;
|
|
57
|
+
isDragging = false;
|
|
58
|
+
isSavingCustomPose = false; // Prevent double-submission
|
|
59
|
+
|
|
60
|
+
// Backend selection state
|
|
61
|
+
selectedBackends: { [key: string]: boolean } = {
|
|
62
|
+
amazon: true,
|
|
63
|
+
openpose: true,
|
|
64
|
+
facetec: false
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
// Available backend options with descriptions
|
|
68
|
+
backendOptions = [
|
|
69
|
+
{ id: 'amazon', name: 'Amazon Rekognition', description: 'Face detection & image quality analysis' },
|
|
70
|
+
{ id: 'openpose', name: 'OpenPose/MoveNet', description: 'Pose detection with TensorFlow' },
|
|
71
|
+
{ id: 'facetec', name: 'FaceTec 3D', description: '3D face liveness detection' }
|
|
72
|
+
];
|
|
73
|
+
|
|
74
|
+
showBackendSettings: boolean = false;
|
|
75
|
+
|
|
76
|
+
// AWS Face Liveness (Anti-Spoofing) toggle
|
|
77
|
+
useAwsFaceLiveness: boolean = false;
|
|
78
|
+
|
|
79
|
+
// Browser detection for AWS Face Liveness compatibility
|
|
80
|
+
isSafariBrowser: boolean = false;
|
|
81
|
+
|
|
82
|
+
// Custom pose feature availability
|
|
83
|
+
get customPosesEnabled(): boolean {
|
|
84
|
+
return !!this.customPoseRepo;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
get displayImage(): string | undefined {
|
|
88
|
+
return this.exampleImageBase64 || this.exampleImageUrl;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
get filteredPoses(): PoseDefinition[] {
|
|
92
|
+
let filtered = this.allPoses;
|
|
93
|
+
|
|
94
|
+
// Filter by category
|
|
95
|
+
if (this.selectedCategory !== 'all') {
|
|
96
|
+
filtered = filtered.filter(pose => pose.category === this.selectedCategory);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Filter by difficulty
|
|
100
|
+
if (this.selectedDifficulty !== 'all') {
|
|
101
|
+
filtered = filtered.filter(pose => pose.difficulty === this.selectedDifficulty);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Filter by search query
|
|
105
|
+
if (this.searchQuery.trim()) {
|
|
106
|
+
const query = this.searchQuery.toLowerCase();
|
|
107
|
+
filtered = filtered.filter(pose =>
|
|
108
|
+
pose.name.toLowerCase().includes(query) ||
|
|
109
|
+
pose.description.toLowerCase().includes(query)
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return filtered;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
get categories(): Array<{ key: PoseDefinition['category'] | 'all'; label: string }> {
|
|
117
|
+
return [
|
|
118
|
+
{ key: 'all', label: 'All Poses' },
|
|
119
|
+
{ key: 'face', label: 'Face Expressions' },
|
|
120
|
+
{ key: 'head', label: 'Head Movements' },
|
|
121
|
+
{ key: 'hand', label: 'Hand Gestures' },
|
|
122
|
+
{ key: 'gesture', label: 'Gestures' },
|
|
123
|
+
{ key: 'combined', label: 'Combined' }
|
|
124
|
+
];
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
get difficulties(): Array<{ key: PoseDefinition['difficulty'] | 'all'; label: string }> {
|
|
128
|
+
return [
|
|
129
|
+
{ key: 'all', label: 'All Levels' },
|
|
130
|
+
{ key: 'easy', label: 'Easy' },
|
|
131
|
+
{ key: 'medium', label: 'Medium' },
|
|
132
|
+
{ key: 'hard', label: 'Hard' }
|
|
133
|
+
];
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Store reference pose for saving
|
|
137
|
+
private referencePose: ReferencePose | null = null;
|
|
138
|
+
|
|
139
|
+
constructor(
|
|
140
|
+
private referencePoseService: ReferencePoseService,
|
|
141
|
+
private openposeService: OpenposeService,
|
|
142
|
+
private poseSelectionService: PoseSelectionService,
|
|
143
|
+
private configService: LivenessConfigService,
|
|
144
|
+
private modalCtrl: ModalController,
|
|
145
|
+
private alertCtrl: AlertController,
|
|
146
|
+
private toastCtrl: ToastController,
|
|
147
|
+
private loadingCtrl: LoadingController,
|
|
148
|
+
@Optional() @Inject(CUSTOM_POSE_REPOSITORY) private customPoseRepo?: ICustomPoseRepository
|
|
149
|
+
) {
|
|
150
|
+
// Apply initial backend configuration
|
|
151
|
+
this.applyBackendConfig();
|
|
152
|
+
|
|
153
|
+
// Detect Safari browser for AWS Face Liveness compatibility warning
|
|
154
|
+
this.detectSafariBrowser();
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Detect if the browser is Safari (not compatible with AWS Face Liveness)
|
|
159
|
+
*/
|
|
160
|
+
private detectSafariBrowser(): void {
|
|
161
|
+
const userAgent = navigator.userAgent.toLowerCase();
|
|
162
|
+
this.isSafariBrowser = /safari/.test(userAgent) && !/chrome/.test(userAgent) && !/chromium/.test(userAgent);
|
|
163
|
+
if (this.isSafariBrowser) {
|
|
164
|
+
console.log('[IntroComponent] Safari browser detected - AWS Face Liveness may not work');
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Apply the selected backends to the config service
|
|
170
|
+
*/
|
|
171
|
+
applyBackendConfig(): void {
|
|
172
|
+
const backends = Object.entries(this.selectedBackends)
|
|
173
|
+
.filter(([_, enabled]) => enabled)
|
|
174
|
+
.map(([id]) => id as LivenessBackend);
|
|
175
|
+
|
|
176
|
+
if (backends.length === 0) {
|
|
177
|
+
// Default to amazon if nothing selected
|
|
178
|
+
backends.push('amazon');
|
|
179
|
+
this.selectedBackends['amazon'] = true;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
console.log('[IntroComponent] Configuring backends:', backends);
|
|
183
|
+
this.configService.enableBackends(backends);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Handle backend checkbox change
|
|
188
|
+
*/
|
|
189
|
+
onBackendChange(): void {
|
|
190
|
+
this.applyBackendConfig();
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Get count of selected backends
|
|
195
|
+
*/
|
|
196
|
+
getSelectedBackendCount(): number {
|
|
197
|
+
return Object.values(this.selectedBackends).filter(v => v).length;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Get name of the single selected backend
|
|
202
|
+
*/
|
|
203
|
+
getSelectedBackendName(): string {
|
|
204
|
+
const selectedId = Object.entries(this.selectedBackends)
|
|
205
|
+
.find(([_, enabled]) => enabled)?.[0];
|
|
206
|
+
return this.backendOptions.find(b => b.id === selectedId)?.name || 'Unknown';
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Toggle backend settings visibility
|
|
211
|
+
*/
|
|
212
|
+
toggleBackendSettings(): void {
|
|
213
|
+
this.showBackendSettings = !this.showBackendSettings;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Handle AWS Face Liveness toggle change
|
|
218
|
+
*/
|
|
219
|
+
onAwsFaceLivenessChange(): void {
|
|
220
|
+
console.log('[IntroComponent] AWS Face Liveness toggled:', this.useAwsFaceLiveness);
|
|
221
|
+
this.awsFaceLivenessToggled.emit(this.useAwsFaceLiveness);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
async ngOnInit() {
|
|
225
|
+
try {
|
|
226
|
+
// Load OpenPose model on component init
|
|
227
|
+
console.log('[Intro] Loading OpenPose model...');
|
|
228
|
+
await this.openposeService.loadModel();
|
|
229
|
+
console.log('[Intro] OpenPose model loaded successfully');
|
|
230
|
+
|
|
231
|
+
// Load custom poses from backend if available
|
|
232
|
+
if (this.customPoseRepo) {
|
|
233
|
+
this.customPoseRepo.loadCustomPoses().subscribe({
|
|
234
|
+
next: (response) => {
|
|
235
|
+
if (response.success && response.poses) {
|
|
236
|
+
// Merge custom poses with static poses
|
|
237
|
+
this.allPoses = mergeCustomPoses(response.poses);
|
|
238
|
+
console.log('[Intro] Loaded custom poses:', response.poses.length);
|
|
239
|
+
}
|
|
240
|
+
},
|
|
241
|
+
error: (error) => {
|
|
242
|
+
console.error('[Intro] Failed to load custom poses:', error);
|
|
243
|
+
// Continue with static poses only
|
|
244
|
+
this.allPoses = POSE_DEFINITIONS;
|
|
245
|
+
}
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Subscribe to pose selection changes
|
|
250
|
+
this.poseSelectionService.selectedPose$.subscribe(selectedPose => {
|
|
251
|
+
this.selectedPose = selectedPose;
|
|
252
|
+
});
|
|
253
|
+
} catch (error) {
|
|
254
|
+
console.error('[Intro] Failed to load OpenPose model:', error);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Handle mode change between browse and upload
|
|
260
|
+
*/
|
|
261
|
+
onModeChange(event: any): void {
|
|
262
|
+
this.poseSelectionMode = event.detail.value;
|
|
263
|
+
console.log('[Intro] Pose selection mode changed to:', this.poseSelectionMode);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Handle drag over event
|
|
268
|
+
*/
|
|
269
|
+
onDragOver(event: DragEvent): void {
|
|
270
|
+
event.preventDefault();
|
|
271
|
+
event.stopPropagation();
|
|
272
|
+
this.isDragging = true;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Handle drag leave event
|
|
277
|
+
*/
|
|
278
|
+
onDragLeave(event: DragEvent): void {
|
|
279
|
+
event.preventDefault();
|
|
280
|
+
event.stopPropagation();
|
|
281
|
+
this.isDragging = false;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Handle file drop event
|
|
286
|
+
*/
|
|
287
|
+
async onDrop(event: DragEvent): Promise<void> {
|
|
288
|
+
event.preventDefault();
|
|
289
|
+
event.stopPropagation();
|
|
290
|
+
this.isDragging = false;
|
|
291
|
+
|
|
292
|
+
const files = event.dataTransfer?.files;
|
|
293
|
+
if (!files || files.length === 0) {
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
const file = files[0];
|
|
298
|
+
|
|
299
|
+
// Validate file type
|
|
300
|
+
if (!file.type.startsWith('image/')) {
|
|
301
|
+
this.poseDetectionStatus = {
|
|
302
|
+
success: false,
|
|
303
|
+
message: 'Please drop a valid image file'
|
|
304
|
+
};
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// Validate file size (max 10MB)
|
|
309
|
+
const maxSize = 10 * 1024 * 1024; // 10MB
|
|
310
|
+
if (file.size > maxSize) {
|
|
311
|
+
this.poseDetectionStatus = {
|
|
312
|
+
success: false,
|
|
313
|
+
message: 'Image file is too large. Maximum size is 10MB.'
|
|
314
|
+
};
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
await this.processReferenceImage(file);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Handle file selection from file input element
|
|
323
|
+
*/
|
|
324
|
+
async onFileSelected(event: Event): Promise<void> {
|
|
325
|
+
const input = event.target as HTMLInputElement;
|
|
326
|
+
|
|
327
|
+
if (!input.files || input.files.length === 0) {
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
const file = input.files[0];
|
|
332
|
+
|
|
333
|
+
// Validate file type
|
|
334
|
+
if (!file.type.startsWith('image/')) {
|
|
335
|
+
this.poseDetectionStatus = {
|
|
336
|
+
success: false,
|
|
337
|
+
message: 'Please select a valid image file'
|
|
338
|
+
};
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// Validate file size (max 10MB)
|
|
343
|
+
const maxSize = 10 * 1024 * 1024; // 10MB
|
|
344
|
+
if (file.size > maxSize) {
|
|
345
|
+
this.poseDetectionStatus = {
|
|
346
|
+
success: false,
|
|
347
|
+
message: 'Image file is too large. Maximum size is 10MB.'
|
|
348
|
+
};
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
await this.processReferenceImage(file);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* Process uploaded reference image
|
|
357
|
+
*/
|
|
358
|
+
private async processReferenceImage(file: File): Promise<void> {
|
|
359
|
+
try {
|
|
360
|
+
// Clear any selected pose from grid (mutual exclusivity)
|
|
361
|
+
if (this.selectedPose) {
|
|
362
|
+
this.clearPoseSelection();
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
this.isProcessingImage = true;
|
|
366
|
+
this.poseDetectionStatus = null;
|
|
367
|
+
|
|
368
|
+
console.log('[Intro] Processing reference image:', file.name);
|
|
369
|
+
|
|
370
|
+
// Extract pose from image
|
|
371
|
+
const referencePose = await this.referencePoseService.extractPoseFromImage(file);
|
|
372
|
+
|
|
373
|
+
// Store reference pose for later saving
|
|
374
|
+
this.referencePose = referencePose;
|
|
375
|
+
|
|
376
|
+
// Update UI
|
|
377
|
+
this.referencePoseImage = referencePose.imageData;
|
|
378
|
+
this.referencePoseDescription = referencePose.poseDescription;
|
|
379
|
+
this.poseDetectionStatus = {
|
|
380
|
+
success: true,
|
|
381
|
+
message: 'Pose detected successfully!'
|
|
382
|
+
};
|
|
383
|
+
|
|
384
|
+
// Emit event to parent component
|
|
385
|
+
this.referencePoseUploaded.emit(referencePose);
|
|
386
|
+
|
|
387
|
+
console.log('[Intro] Reference pose processed successfully');
|
|
388
|
+
} catch (error: any) {
|
|
389
|
+
console.error('[Intro] Failed to process reference image:', error);
|
|
390
|
+
|
|
391
|
+
this.poseDetectionStatus = {
|
|
392
|
+
success: false,
|
|
393
|
+
message: error.message || 'Failed to detect pose in the image'
|
|
394
|
+
};
|
|
395
|
+
|
|
396
|
+
this.referencePoseImage = null;
|
|
397
|
+
this.referencePoseDescription = null;
|
|
398
|
+
} finally {
|
|
399
|
+
this.isProcessingImage = false;
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* Clear reference pose and revert to default
|
|
405
|
+
*/
|
|
406
|
+
clearReferencePose(): void {
|
|
407
|
+
this.referencePoseImage = null;
|
|
408
|
+
this.referencePoseDescription = null;
|
|
409
|
+
this.poseDetectionStatus = null;
|
|
410
|
+
this.referencePose = null;
|
|
411
|
+
this.isSavingCustomPose = false; // Reset save flag
|
|
412
|
+
this.referencePoseService.clearReferencePose();
|
|
413
|
+
this.poseSelectionMode = 'browse'; // Switch back to browse mode
|
|
414
|
+
|
|
415
|
+
console.log('[Intro] Reference pose cleared');
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/**
|
|
419
|
+
* Select a pose from the grid
|
|
420
|
+
*/
|
|
421
|
+
onSelectPose(pose: PoseDefinition): void {
|
|
422
|
+
// Clear any uploaded custom pose (mutual exclusivity)
|
|
423
|
+
if (this.referencePoseImage) {
|
|
424
|
+
this.clearReferencePose();
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
this.poseSelectionService.selectPoseByObject(pose);
|
|
428
|
+
const selectedPoseInfo = this.poseSelectionService.getSelectedPose();
|
|
429
|
+
|
|
430
|
+
if (selectedPoseInfo) {
|
|
431
|
+
console.log('[Intro] Pose selected:', pose.name);
|
|
432
|
+
console.log('[Intro] Pose metadata:', {
|
|
433
|
+
id: pose.id,
|
|
434
|
+
category: pose.category,
|
|
435
|
+
detectionStrategy: pose.detectionStrategy,
|
|
436
|
+
difficulty: pose.difficulty
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
// Simply emit the pose selection
|
|
440
|
+
// The live-preview component will handle detection based on pose metadata
|
|
441
|
+
// The pose image is only used for display, not for OpenPose comparison
|
|
442
|
+
this.poseSelected.emit(selectedPoseInfo);
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
/**
|
|
447
|
+
* Clear pose selection
|
|
448
|
+
*/
|
|
449
|
+
clearPoseSelection(): void {
|
|
450
|
+
this.poseSelectionService.clearSelection();
|
|
451
|
+
this.selectedPose = null;
|
|
452
|
+
console.log('[Intro] Pose selection cleared');
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
/**
|
|
456
|
+
* Check if a pose is currently selected
|
|
457
|
+
*/
|
|
458
|
+
isPoseSelected(pose: PoseDefinition): boolean {
|
|
459
|
+
return this.selectedPose?.pose.id === pose.id;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
/**
|
|
463
|
+
* Toggle filters visibility
|
|
464
|
+
*/
|
|
465
|
+
toggleFilters(): void {
|
|
466
|
+
this.showFilters = !this.showFilters;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
/**
|
|
470
|
+
* Change view mode
|
|
471
|
+
*/
|
|
472
|
+
setViewMode(mode: 'grid' | 'list'): void {
|
|
473
|
+
this.viewMode = mode;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
/**
|
|
477
|
+
* Filter by category
|
|
478
|
+
*/
|
|
479
|
+
filterByCategory(category: PoseDefinition['category'] | 'all'): void {
|
|
480
|
+
this.selectedCategory = category;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
/**
|
|
484
|
+
* Filter by difficulty
|
|
485
|
+
*/
|
|
486
|
+
filterByDifficulty(difficulty: PoseDefinition['difficulty'] | 'all'): void {
|
|
487
|
+
this.selectedDifficulty = difficulty;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
/**
|
|
491
|
+
* Search poses
|
|
492
|
+
*/
|
|
493
|
+
onSearchChange(event: any): void {
|
|
494
|
+
this.searchQuery = event.target.value || '';
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
/**
|
|
498
|
+
* Clear all filters
|
|
499
|
+
*/
|
|
500
|
+
clearFilters(): void {
|
|
501
|
+
this.selectedCategory = 'all';
|
|
502
|
+
this.selectedDifficulty = 'all';
|
|
503
|
+
this.searchQuery = '';
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
/**
|
|
507
|
+
* Get icon for pose category
|
|
508
|
+
*/
|
|
509
|
+
getCategoryIcon(category: PoseDefinition['category']): string {
|
|
510
|
+
return this.categoryInfo[category]?.icon || 'star-outline';
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
/**
|
|
514
|
+
* Get color for pose category
|
|
515
|
+
*/
|
|
516
|
+
getCategoryColor(category: PoseDefinition['category']): string {
|
|
517
|
+
return this.categoryInfo[category]?.color || 'medium';
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
/**
|
|
521
|
+
* Get difficulty badge color
|
|
522
|
+
*/
|
|
523
|
+
getDifficultyColor(difficulty: PoseDefinition['difficulty']): string {
|
|
524
|
+
const colors = {
|
|
525
|
+
easy: 'success',
|
|
526
|
+
medium: 'warning',
|
|
527
|
+
hard: 'danger'
|
|
528
|
+
};
|
|
529
|
+
return colors[difficulty] || 'medium';
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
/**
|
|
533
|
+
* Handle image load error (fallback to icon)
|
|
534
|
+
*/
|
|
535
|
+
onImageError(event: any): void {
|
|
536
|
+
console.warn('[Intro] Failed to load pose image:', event.target.src);
|
|
537
|
+
event.target.style.display = 'none';
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
/**
|
|
541
|
+
* Save uploaded pose as custom pose
|
|
542
|
+
*/
|
|
543
|
+
async onSaveCustomPose(): Promise<void> {
|
|
544
|
+
// Prevent double-submission
|
|
545
|
+
if (this.isSavingCustomPose) {
|
|
546
|
+
console.log('[Intro] Save already in progress, ignoring duplicate click');
|
|
547
|
+
return;
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
if (!this.referencePoseImage || !this.referencePose) {
|
|
551
|
+
await this.showToast('No pose to save', 'warning');
|
|
552
|
+
return;
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
if (!this.customPosesEnabled) {
|
|
556
|
+
await this.showToast('Custom pose saving not available', 'warning');
|
|
557
|
+
return;
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
try {
|
|
561
|
+
// Show dialog to get pose details
|
|
562
|
+
const poseDetails = await this.showSaveCustomPoseDialog();
|
|
563
|
+
|
|
564
|
+
if (!poseDetails) {
|
|
565
|
+
return; // User cancelled
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
// Set flag to prevent double-submission
|
|
569
|
+
this.isSavingCustomPose = true;
|
|
570
|
+
|
|
571
|
+
// Show loading indicator
|
|
572
|
+
const loading = await this.loadingCtrl.create({
|
|
573
|
+
message: 'Saving custom pose...',
|
|
574
|
+
spinner: 'circles'
|
|
575
|
+
});
|
|
576
|
+
await loading.present();
|
|
577
|
+
|
|
578
|
+
// Create custom pose via API
|
|
579
|
+
const { pose: customPose, isNew } = await this.customPoseRepo!.createCustomPoseFromBase64(
|
|
580
|
+
poseDetails.name,
|
|
581
|
+
poseDetails.description,
|
|
582
|
+
poseDetails.category,
|
|
583
|
+
poseDetails.difficulty,
|
|
584
|
+
this.referencePoseImage,
|
|
585
|
+
this.referencePose.keypoints
|
|
586
|
+
);
|
|
587
|
+
|
|
588
|
+
// Close loading indicator
|
|
589
|
+
await loading.dismiss();
|
|
590
|
+
|
|
591
|
+
if (isNew) {
|
|
592
|
+
// Convert to PoseDefinition and add to list
|
|
593
|
+
const poseDefinition = customPoseToPoseDefinition(customPose);
|
|
594
|
+
this.allPoses = [...this.allPoses, poseDefinition];
|
|
595
|
+
await this.showToast('Custom pose saved successfully!', 'success');
|
|
596
|
+
} else {
|
|
597
|
+
// Duplicate found - show warning with existing pose name
|
|
598
|
+
await this.showToast(`This pose already exists as "${customPose.name}"`, 'warning');
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
// Clear upload state
|
|
602
|
+
this.clearReferencePose();
|
|
603
|
+
} catch (error: any) {
|
|
604
|
+
console.error('[Intro] Failed to save custom pose:', error);
|
|
605
|
+
await this.showToast(error.message || 'Failed to save custom pose', 'danger');
|
|
606
|
+
} finally {
|
|
607
|
+
// Reset flag
|
|
608
|
+
this.isSavingCustomPose = false;
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
/**
|
|
613
|
+
* Delete custom pose
|
|
614
|
+
*/
|
|
615
|
+
async onDeleteCustomPose(pose: PoseDefinition, event: Event): Promise<void> {
|
|
616
|
+
event.stopPropagation(); // Prevent pose selection
|
|
617
|
+
|
|
618
|
+
if (!pose.isCustom || !this.customPosesEnabled) {
|
|
619
|
+
return;
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
try {
|
|
623
|
+
// Show confirmation dialog
|
|
624
|
+
const confirmed = await this.showDeleteConfirmation(pose);
|
|
625
|
+
|
|
626
|
+
if (!confirmed) {
|
|
627
|
+
return;
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
// Show loading indicator
|
|
631
|
+
const loading = await this.loadingCtrl.create({
|
|
632
|
+
message: 'Deleting pose...',
|
|
633
|
+
spinner: 'circles'
|
|
634
|
+
});
|
|
635
|
+
await loading.present();
|
|
636
|
+
|
|
637
|
+
// Delete via API (remove the offset to get original ID)
|
|
638
|
+
const originalId = pose.id - 10000;
|
|
639
|
+
await this.customPoseRepo!.deleteCustomPose(originalId).toPromise();
|
|
640
|
+
|
|
641
|
+
// Remove from local list
|
|
642
|
+
this.allPoses = this.allPoses.filter(p => p.id !== pose.id);
|
|
643
|
+
|
|
644
|
+
// Close loading indicator
|
|
645
|
+
await loading.dismiss();
|
|
646
|
+
|
|
647
|
+
await this.showToast('Custom pose deleted', 'success');
|
|
648
|
+
} catch (error: any) {
|
|
649
|
+
console.error('[Intro] Failed to delete custom pose:', error);
|
|
650
|
+
await this.showToast(error.message || 'Failed to delete custom pose', 'danger');
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
/**
|
|
655
|
+
* Show save custom pose dialog
|
|
656
|
+
*/
|
|
657
|
+
private async showSaveCustomPoseDialog(): Promise<any> {
|
|
658
|
+
const modal = await this.modalCtrl.create({
|
|
659
|
+
component: SaveCustomPoseDialogComponent,
|
|
660
|
+
componentProps: {
|
|
661
|
+
imageData: this.referencePoseImage,
|
|
662
|
+
suggestedDescription: this.referencePoseDescription
|
|
663
|
+
}
|
|
664
|
+
});
|
|
665
|
+
|
|
666
|
+
await modal.present();
|
|
667
|
+
const { data } = await modal.onWillDismiss();
|
|
668
|
+
return data;
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
/**
|
|
672
|
+
* Show delete confirmation dialog
|
|
673
|
+
*/
|
|
674
|
+
private async showDeleteConfirmation(pose: PoseDefinition): Promise<boolean> {
|
|
675
|
+
const alert = await this.alertCtrl.create({
|
|
676
|
+
header: 'Delete Custom Pose',
|
|
677
|
+
message: `Are you sure you want to delete "${pose.name}"? This action cannot be undone.`,
|
|
678
|
+
buttons: [
|
|
679
|
+
{
|
|
680
|
+
text: 'Cancel',
|
|
681
|
+
role: 'cancel'
|
|
682
|
+
},
|
|
683
|
+
{
|
|
684
|
+
text: 'Delete',
|
|
685
|
+
role: 'destructive',
|
|
686
|
+
handler: () => true
|
|
687
|
+
}
|
|
688
|
+
]
|
|
689
|
+
});
|
|
690
|
+
|
|
691
|
+
await alert.present();
|
|
692
|
+
const { role } = await alert.onDidDismiss();
|
|
693
|
+
return role !== 'cancel';
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
/**
|
|
697
|
+
* Show toast notification
|
|
698
|
+
*/
|
|
699
|
+
private async showToast(message: string, color: string = 'primary'): Promise<void> {
|
|
700
|
+
const toast = await this.toastCtrl.create({
|
|
701
|
+
message,
|
|
702
|
+
duration: 3000,
|
|
703
|
+
color,
|
|
704
|
+
position: 'bottom'
|
|
705
|
+
});
|
|
706
|
+
await toast.present();
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
onStart(): void {
|
|
710
|
+
// Check if a pose is selected
|
|
711
|
+
if (!this.selectedPose) {
|
|
712
|
+
console.warn('[Intro] No pose selected, starting with default actions');
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
this.startClicked.emit();
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
onCancel(): void {
|
|
719
|
+
this.cancelClicked.emit();
|
|
720
|
+
}
|
|
721
|
+
}
|