import {Component, ElementRef, inject, ViewChild} from '@angular/core'; import {MatCard, MatCardContent, MatCardHeader} from '@angular/material/card'; import {MatMiniFabButton} from '@angular/material/button'; import {MatToolbar, MatToolbarRow} from '@angular/material/toolbar'; import {MatTooltip} from '@angular/material/tooltip'; import {MatIcon} from '@angular/material/icon'; import {FileInfo} from '../../../models/file-info'; import {Base64ImagePayload, UploadImageResponse, UserApiService} from '../user-api.service'; import {CropService, ImageCroppedEvent, ImageCropperComponent, ImageTransform, LoadedImage} from 'ngx-image-cropper'; import {DomSanitizer, SafeUrl} from '@angular/platform-browser'; import {MatDialog} from '@angular/material/dialog'; import {DialogCroppImageComponent} from '../dialog-cropp-image.component/dialog-cropp-image.component'; import {MatChipListbox, MatChipOption, MatChipSelectionChange} from '@angular/material/chips'; @Component({ selector: 'app-images.component', imports: [ MatCard, MatCardContent, MatCardHeader, MatIcon, MatMiniFabButton, MatToolbar, MatToolbarRow, MatTooltip, ImageCropperComponent, MatChipListbox, MatChipOption ], templateUrl: './images.component.html', styleUrl: './images.component.scss' }) export class ImagesComponent { fileInfos?: FileInfo[] = []; readonly dialog = inject(MatDialog); imageChangedEvent: Event | null = null; croppedImage: string | null | undefined = ''; croppedImageBase64: string | null | undefined; @ViewChild('imagePreview') imagePreview!: ElementRef; canvasRotation = 0; transform: ImageTransform = { translateUnit: 'px', scale: 1, rotate: 0, flipH: false, flipV: false, translateH: 0, translateV: 0 }; cropperStaticWidth = 0; cropperStaticHeight = 0; loading = false; currentCroppedFile: FileInfo | undefined; constructor( private userApiService: UserApiService, private sanitizer: DomSanitizer, ) { this.userApiService.getImages().subscribe(data =>{ this.fileInfos = data; console.log(data); }); } downloadImage(url: string | undefined) { const link = document.createElement('a'); // @ts-ignore link.download = url; // @ts-ignore link.href = url; link.click(); } openCropImageDialog(imageUrl: string | undefined, enterAnimationDuration: string, exitAnimationDuration: string) { let dialogCropImageRef = this.dialog.open(DialogCroppImageComponent, { height: '500px', width: '800px', enterAnimationDuration, exitAnimationDuration, } ); dialogCropImageRef.componentInstance.cropImage(imageUrl); // dialogCropImageRef.componentInstance.uploadClicked.subscribe(result => { // dialogCropImageRef.close(); // this.openUploadImageDialog(enterAnimationDuration, exitAnimationDuration); // }) // const dialogSelectSubscription = dialogCropImageRef.componentInstance.selectClicked // .subscribe(result => { // console.log('Got the data!', result); // // if (result == null) { // return; // } // this.tutorial.titleimage = result.url; // // }); } cropImage(fileInfo: FileInfo) { // close the crop view for a previous cropped file if(this.currentCroppedFile !== undefined){ this.currentCroppedFile.mode = "View"; } fileInfo.mode = "CropView"; this.setBase64String(fileInfo); this.currentCroppedFile = fileInfo; } setBase64String(fileInfo:FileInfo) { if(fileInfo.url !== undefined){ this.userApiService.loadImageAsBase64(fileInfo.url).subscribe(data => { fileInfo.base64string = data; }) } } imageCropped(event: ImageCroppedEvent) { if (event.objectUrl != null) { // this.croppedImage = this.sanitizer.bypassSecurityTrustUrl(event.objectUrl); } else { this.croppedImageBase64 = event.base64; this.croppedImage = this.croppedImageBase64; } this.imagePreview.nativeElement.height = event.height; this.imagePreview.nativeElement.width = event.width; } imageLoaded(image: LoadedImage) { // show cropper } cropperReady() { // cropper ready } loadImageFailed() { // show message } zoomIn() { this.transform = { ...this.transform, scale: this.transform.scale! + .1 }; } zoomOut() { this.transform = { ...this.transform, scale: this.transform.scale! - .1 }; } moveLeft() { this.transform = { ...this.transform, translateH: this.transform.translateH! - 1 }; } moveRight() { this.transform = { ...this.transform, translateH: this.transform.translateH! + 1 }; } moveDown() { this.transform = { ...this.transform, translateV: this.transform.translateV! + 1 }; } moveUp() { this.transform = { ...this.transform, translateV: this.transform.translateV! - 1 }; } flipHorizontal() { this.transform = { ...this.transform, flipH: !this.transform.flipH }; } flipVertical() { this.transform = { ...this.transform, flipV: !this.transform.flipV }; } rotateLeft() { this.loading = true; setTimeout(() => { // Use timeout because rotating image is a heavy operation and will block the ui thread this.canvasRotation--; this.flipAfterRotate(); }); } rotateRight() { this.loading = true; setTimeout(() => { this.canvasRotation++; this.flipAfterRotate(); }); } private flipAfterRotate() { const flippedH = this.transform.flipH; const flippedV = this.transform.flipV; this.transform = { ...this.transform, flipH: flippedV, flipV: flippedH, translateH: 0, translateV: 0 }; } cropFixedSize($event: MatChipSelectionChange) { if ($event.source.selected) { this.cropperStaticWidth = 716; this.cropperStaticHeight = 400; } else { this.cropperStaticWidth = 0; this.cropperStaticHeight = 0; } } save() { const payload = this.getBase64ImagePayload(); this.userApiService.uploadUserBase64Image(payload).subscribe({ next: (res:UploadImageResponse) =>{ const fileInfo: FileInfo = { name: res.fileName, url: res.url, mode: "View", base64string: undefined } this.fileInfos?.push(fileInfo); console.log("Uploaded:", res); } , error: (err) => console.error("Upload failed", err), }); } private getBase64ImagePayload(): Base64ImagePayload { const match = this.croppedImageBase64?.match(/^data:(.+);base64,(.*)$/); if(match) { const [, mime, base64] = match; return { mime: mime, data: base64 }; } else { throw new Error("Invalid base64 string"); } } }