286 lines
6.8 KiB
TypeScript
286 lines
6.8 KiB
TypeScript
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");
|
|
}
|
|
}
|
|
}
|