Add fileName property to models and update related components for file handling

This commit is contained in:
liosha84
2025-08-09 22:22:02 +03:00
parent ca73cf31df
commit d45fd83593
64 changed files with 1644 additions and 433 deletions
@@ -1,12 +1,16 @@
import {Component, CUSTOM_ELEMENTS_SCHEMA, inject} from '@angular/core';
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 {UserApiService} from '../user-api.service';
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',
@@ -18,17 +22,49 @@ import {MatDialog} from '@angular/material/dialog';
MatMiniFabButton,
MatToolbar,
MatToolbarRow,
MatTooltip
MatTooltip,
ImageCropperComponent,
MatChipListbox,
MatChipOption
],
templateUrl: './images.component.html',
styleUrl: './images.component.scss',
schemas: [CUSTOM_ELEMENTS_SCHEMA]
styleUrl: './images.component.scss'
})
export class ImagesComponent {
fileInfos?: FileInfo[] = [];
readonly dialog = inject(MatDialog);
constructor(private userApiService: UserApiService) {
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);
@@ -36,6 +72,214 @@ export class ImagesComponent {
}
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");
}
}
}