|
|
@@ -0,0 +1,88 @@
|
|
|
+import { Component, ViewChild, ElementRef, AfterViewInit, Inject } from '@angular/core';
|
|
|
+import { MatDialogRef, MAT_DIALOG_DATA, MatDialogModule } from '@angular/material/dialog';
|
|
|
+import { CommonModule } from '@angular/common';
|
|
|
+import { FormsModule } from '@angular/forms';
|
|
|
+import { HttpClient, HttpClientModule } from '@angular/common/http';
|
|
|
+import { MatButtonModule } from '@angular/material/button';
|
|
|
+import { MatInputModule } from '@angular/material/input';
|
|
|
+import { MatSnackBar, MatSnackBarModule } from '@angular/material/snack-bar';
|
|
|
+import { webConfig } from '../../config';
|
|
|
+
|
|
|
+@Component({
|
|
|
+ selector: 'app-enroll-dialog',
|
|
|
+ standalone: true,
|
|
|
+ imports: [
|
|
|
+ CommonModule,
|
|
|
+ FormsModule,
|
|
|
+ HttpClientModule,
|
|
|
+ MatDialogModule,
|
|
|
+ MatButtonModule,
|
|
|
+ MatInputModule,
|
|
|
+ MatSnackBarModule
|
|
|
+ ],
|
|
|
+ templateUrl: './enroll-dialog.component.html',
|
|
|
+ styleUrls: ['./enroll-dialog.component.css']
|
|
|
+})
|
|
|
+export class EnrollDialogComponent implements AfterViewInit {
|
|
|
+ @ViewChild('video') videoRef!: ElementRef<HTMLVideoElement>;
|
|
|
+ @ViewChild('canvas') canvasRef!: ElementRef<HTMLCanvasElement>;
|
|
|
+
|
|
|
+ name: string = '';
|
|
|
+ loading: boolean = false;
|
|
|
+
|
|
|
+ constructor(
|
|
|
+ private http: HttpClient,
|
|
|
+ private snack: MatSnackBar,
|
|
|
+ private dialogRef: MatDialogRef<EnrollDialogComponent>,
|
|
|
+ @Inject(MAT_DIALOG_DATA) public data: any
|
|
|
+ ) {
|
|
|
+ if (data?.defaultName) {
|
|
|
+ this.name = data.defaultName;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ ngAfterViewInit() {
|
|
|
+ navigator.mediaDevices.getUserMedia({ video: true })
|
|
|
+ .then(stream => this.videoRef.nativeElement.srcObject = stream)
|
|
|
+ .catch(err => console.error('Webcam error:', err));
|
|
|
+ }
|
|
|
+
|
|
|
+ private captureImage(): string {
|
|
|
+ const canvas = this.canvasRef.nativeElement;
|
|
|
+ const video = this.videoRef.nativeElement;
|
|
|
+ canvas.width = video.videoWidth;
|
|
|
+ canvas.height = video.videoHeight;
|
|
|
+ canvas.getContext('2d')?.drawImage(video, 0, 0);
|
|
|
+ return canvas.toDataURL('image/jpeg').split(',')[1]; // base64 only
|
|
|
+ }
|
|
|
+
|
|
|
+ enroll() {
|
|
|
+ if (!this.name.trim()) {
|
|
|
+ this.snack.open('Please enter a name', 'Close', { duration: 2000 });
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ this.loading = true;
|
|
|
+ const base64Image = this.captureImage();
|
|
|
+
|
|
|
+ this.http.post<any>(`${webConfig.exposedUrl}/api/face/enroll`, {
|
|
|
+ imageBase64: base64Image,
|
|
|
+ name: this.name
|
|
|
+ }).subscribe({
|
|
|
+ next: res => {
|
|
|
+ this.loading = false;
|
|
|
+ this.snack.open(`Enrolled ${this.name} successfully!`, 'Close', { duration: 3000 });
|
|
|
+ this.dialogRef.close(res);
|
|
|
+ },
|
|
|
+ error: err => {
|
|
|
+ console.error(err);
|
|
|
+ this.loading = false;
|
|
|
+ this.snack.open('Failed to enroll face', 'Close', { duration: 3000 });
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ cancel() {
|
|
|
+ this.dialogRef.close();
|
|
|
+ }
|
|
|
+}
|