@@ -13,7 +13,7 @@
|
|||||||
<button class="client-image-button" mat-raised-button (click)="doAction('Upload Image')" matTooltip="Upload Image">
|
<button class="client-image-button" mat-raised-button (click)="doAction('Upload Image')" matTooltip="Upload Image">
|
||||||
<i class="fa fa-upload"></i>
|
<i class="fa fa-upload"></i>
|
||||||
</button>
|
</button>
|
||||||
<button class="client-image-button" mat-raised-button matTooltip="Capture Image">
|
<button class="client-image-button" mat-raised-button (click)="doAction('Capture Image')" matTooltip="Capture Image">
|
||||||
<i class="fa fa-camera"></i>
|
<i class="fa fa-camera"></i>
|
||||||
</button>
|
</button>
|
||||||
<button class="client-image-button" mat-raised-button (click)="doAction('Delete Image')" matTooltip="Delete Image">
|
<button class="client-image-button" mat-raised-button (click)="doAction('Delete Image')" matTooltip="Delete Image">
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import { ViewSignatureDialogComponent } from './custom-dialogs/view-signature-di
|
|||||||
import { DeleteSignatureDialogComponent } from './custom-dialogs/delete-signature-dialog/delete-signature-dialog.component';
|
import { DeleteSignatureDialogComponent } from './custom-dialogs/delete-signature-dialog/delete-signature-dialog.component';
|
||||||
import { DeleteDialogComponent } from 'app/shared/delete-dialog/delete-dialog.component';
|
import { DeleteDialogComponent } from 'app/shared/delete-dialog/delete-dialog.component';
|
||||||
import { UploadImageDialogComponent } from './custom-dialogs/upload-image-dialog/upload-image-dialog.component';
|
import { UploadImageDialogComponent } from './custom-dialogs/upload-image-dialog/upload-image-dialog.component';
|
||||||
|
import { CaptureImageDialogComponent } from './custom-dialogs/capture-image-dialog/capture-image-dialog.component';
|
||||||
|
|
||||||
/** Custom Services */
|
/** Custom Services */
|
||||||
import { ClientsService } from '../clients.service';
|
import { ClientsService } from '../clients.service';
|
||||||
@@ -89,6 +90,9 @@ export class ClientsViewComponent implements OnInit {
|
|||||||
case 'Delete Signature':
|
case 'Delete Signature':
|
||||||
this.deleteSignature();
|
this.deleteSignature();
|
||||||
break;
|
break;
|
||||||
|
case 'Capture Image':
|
||||||
|
this.captureProfileImage();
|
||||||
|
break;
|
||||||
case 'Upload Image':
|
case 'Upload Image':
|
||||||
this.uploadProfileImage();
|
this.uploadProfileImage();
|
||||||
break;
|
break;
|
||||||
@@ -205,6 +209,21 @@ export class ClientsViewComponent implements OnInit {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Captures clients profile image.
|
||||||
|
*/
|
||||||
|
private captureProfileImage() {
|
||||||
|
const captureImageDialogRef = this.dialog.open(CaptureImageDialogComponent);
|
||||||
|
captureImageDialogRef.afterClosed().subscribe((imageURL: string) => {
|
||||||
|
if (imageURL) {
|
||||||
|
this.clientsService.uploadCapturedClientProfileImage(this.clientViewData.id, imageURL)
|
||||||
|
.subscribe(() => {
|
||||||
|
this.reload();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Uploads the clients image.
|
* Uploads the clients image.
|
||||||
*/
|
*/
|
||||||
|
|||||||
+25
@@ -0,0 +1,25 @@
|
|||||||
|
<div fxLayout="column" fxLayoutGap="2%">
|
||||||
|
|
||||||
|
<h1 mat-dialog-title align="center">Capture Client Image</h1>
|
||||||
|
|
||||||
|
<video #video width="540" height="480" autoplay></video>
|
||||||
|
|
||||||
|
<canvas #canvas class="canvas"></canvas>
|
||||||
|
|
||||||
|
<p #fallback class="fallback" align="center"></p>
|
||||||
|
|
||||||
|
<div fxFlex="20%" align="center">
|
||||||
|
<button #captureButton *ngIf="!isCaptured" mat-stroked-button color="primary" (click)="capture()">
|
||||||
|
Capture
|
||||||
|
</button>
|
||||||
|
<button *ngIf="isCaptured" mat-stroked-button color="primary" (click)="recapture()">
|
||||||
|
<fa-icon icon="undo" size="sm"></fa-icon> Recapture
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<mat-dialog-actions align="end">
|
||||||
|
<button mat-raised-button mat-dialog-close>Cancel</button>
|
||||||
|
<button [disabled]="!isCaptured" mat-raised-button color="primary" [mat-dialog-close]="clientImageDataURL">Upload</button>
|
||||||
|
</mat-dialog-actions>
|
||||||
|
|
||||||
|
</div>
|
||||||
+7
@@ -0,0 +1,7 @@
|
|||||||
|
.fallback {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.canvas {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
+25
@@ -0,0 +1,25 @@
|
|||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { CaptureImageDialogComponent } from './capture-image-dialog.component';
|
||||||
|
|
||||||
|
describe('CaptureImageDialogComponent', () => {
|
||||||
|
let component: CaptureImageDialogComponent;
|
||||||
|
let fixture: ComponentFixture<CaptureImageDialogComponent>;
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
declarations: [ CaptureImageDialogComponent ]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(CaptureImageDialogComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
+108
@@ -0,0 +1,108 @@
|
|||||||
|
/** Angular Imports */
|
||||||
|
import { Component, ViewChild, ElementRef, AfterViewInit, Renderer2, OnDestroy } from '@angular/core';
|
||||||
|
import { MatDialogRef } from '@angular/material';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Capture image dialog component
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: 'mifosx-capture-image-dialog',
|
||||||
|
templateUrl: './capture-image-dialog.component.html',
|
||||||
|
styleUrls: ['./capture-image-dialog.component.scss']
|
||||||
|
})
|
||||||
|
export class CaptureImageDialogComponent implements AfterViewInit, OnDestroy {
|
||||||
|
|
||||||
|
/** Video element reference */
|
||||||
|
@ViewChild('video') video: ElementRef;
|
||||||
|
/** Canvas element reference */
|
||||||
|
@ViewChild('canvas') canvas: ElementRef;
|
||||||
|
/** Fallback element reference */
|
||||||
|
@ViewChild('fallback') fallback: ElementRef;
|
||||||
|
/** Capture button element reference */
|
||||||
|
@ViewChild('captureButton') captureButton: ElementRef;
|
||||||
|
|
||||||
|
/** Toggles button states */
|
||||||
|
isCaptured = false;
|
||||||
|
/** Client image file */
|
||||||
|
clientImageDataURL: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {MatDialogRef} dialogRef Mat Dialog Reference
|
||||||
|
* @param {Renderer2} renderer Template Renderer
|
||||||
|
*/
|
||||||
|
constructor(public dialogRef: MatDialogRef<CaptureImageDialogComponent>,
|
||||||
|
private renderer: Renderer2) {}
|
||||||
|
|
||||||
|
ngAfterViewInit() {
|
||||||
|
this.startCamera();
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy() {
|
||||||
|
this.stopCamera();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes camera video stream once user grants permission.
|
||||||
|
* Sets fallback if permission not granted.
|
||||||
|
* See https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices for configuration details.
|
||||||
|
*/
|
||||||
|
startCamera() {
|
||||||
|
if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
|
||||||
|
navigator.mediaDevices.getUserMedia({ video: true })
|
||||||
|
.then((stream: MediaStream) => {
|
||||||
|
this.renderer.setProperty(this.video.nativeElement, 'srcObject', stream);
|
||||||
|
this.video.nativeElement.play();
|
||||||
|
})
|
||||||
|
.catch((error: Error) => {
|
||||||
|
this.handleError(error);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
throw new Error('Cannot connect to camera');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shows fallback message.
|
||||||
|
* @param {Error} error Error
|
||||||
|
*/
|
||||||
|
handleError(error: Error) {
|
||||||
|
this.renderer.removeStyle(this.fallback.nativeElement, 'display');
|
||||||
|
const fallbackMessage = this.renderer.createText(`${error.name}: ${error.message}`);
|
||||||
|
this.renderer.appendChild(this.fallback.nativeElement, fallbackMessage);
|
||||||
|
this.renderer.setStyle(this.video.nativeElement, 'display', 'none');
|
||||||
|
this.renderer.setProperty(this.captureButton, 'disabled', 'true');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stops video stream and closes the camera.
|
||||||
|
* See https://developer.mozilla.org/en-US/docs/Web/API/MediaStreamTrack for `MediaStreamTrack` properties.
|
||||||
|
*/
|
||||||
|
stopCamera() {
|
||||||
|
const stream: MediaStream = this.video.nativeElement.srcObject;
|
||||||
|
if (stream) {
|
||||||
|
const videoStream: MediaStreamTrack = stream.getTracks()[0];
|
||||||
|
videoStream.stop();
|
||||||
|
}
|
||||||
|
this.renderer.setProperty(this.video.nativeElement, 'srcObject', null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Captures the image by drawing video state on canvas, then converts canvas state to data URL.
|
||||||
|
* See https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toDataURL for details.
|
||||||
|
*/
|
||||||
|
capture() {
|
||||||
|
this.isCaptured = true;
|
||||||
|
this.video.nativeElement.pause();
|
||||||
|
this.canvas.nativeElement.getContext('2d').drawImage(this.video.nativeElement, 0, 0, 150, 150);
|
||||||
|
this.clientImageDataURL = this.canvas.nativeElement.toDataURL();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allows user to capture image again.
|
||||||
|
*/
|
||||||
|
recapture() {
|
||||||
|
this.isCaptured = false;
|
||||||
|
this.video.nativeElement.play();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -56,6 +56,7 @@ import { ClientFamilyMembersStepComponent } from './client-stepper/client-family
|
|||||||
import { ClientPreviewStepComponent } from './client-stepper/client-preview-step/client-preview-step.component';
|
import { ClientPreviewStepComponent } from './client-stepper/client-preview-step/client-preview-step.component';
|
||||||
import { ClientAddressStepComponent } from './client-stepper/client-address-step/client-address-step.component';
|
import { ClientAddressStepComponent } from './client-stepper/client-address-step/client-address-step.component';
|
||||||
import { ClientFamilyMemberDialogComponent } from './client-stepper/client-family-members-step/client-family-member-dialog/client-family-member-dialog.component';
|
import { ClientFamilyMemberDialogComponent } from './client-stepper/client-family-members-step/client-family-member-dialog/client-family-member-dialog.component';
|
||||||
|
import { CaptureImageDialogComponent } from './clients-view/custom-dialogs/capture-image-dialog/capture-image-dialog.component';
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -117,7 +118,8 @@ import { ClientFamilyMemberDialogComponent } from './client-stepper/client-famil
|
|||||||
ClientFamilyMembersStepComponent,
|
ClientFamilyMembersStepComponent,
|
||||||
ClientPreviewStepComponent,
|
ClientPreviewStepComponent,
|
||||||
ClientAddressStepComponent,
|
ClientAddressStepComponent,
|
||||||
ClientFamilyMemberDialogComponent
|
ClientFamilyMemberDialogComponent,
|
||||||
|
CaptureImageDialogComponent
|
||||||
],
|
],
|
||||||
entryComponents: [
|
entryComponents: [
|
||||||
UploadDocumentDialogComponent,
|
UploadDocumentDialogComponent,
|
||||||
@@ -127,7 +129,8 @@ import { ClientFamilyMemberDialogComponent } from './client-stepper/client-famil
|
|||||||
UploadSignatureDialogComponent,
|
UploadSignatureDialogComponent,
|
||||||
DeleteSignatureDialogComponent,
|
DeleteSignatureDialogComponent,
|
||||||
UploadImageDialogComponent,
|
UploadImageDialogComponent,
|
||||||
ClientFamilyMemberDialogComponent
|
ClientFamilyMemberDialogComponent,
|
||||||
|
CaptureImageDialogComponent
|
||||||
],
|
],
|
||||||
providers: [DatePipe]
|
providers: [DatePipe]
|
||||||
|
|
||||||
|
|||||||
@@ -166,6 +166,10 @@ export class ClientsService {
|
|||||||
return this.http.post(`/clients/${clientId}/images`, formData);
|
return this.http.post(`/clients/${clientId}/images`, formData);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uploadCapturedClientProfileImage(clientId: string, imageURL: string) {
|
||||||
|
return this.http.post(`/clients/${clientId}/images`, imageURL);
|
||||||
|
}
|
||||||
|
|
||||||
deleteClientProfileImage(clientId: string) {
|
deleteClientProfileImage(clientId: string) {
|
||||||
return this.http.delete(`/clients/${clientId}/images`);
|
return this.http.delete(`/clients/${clientId}/images`);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user