import { AfterViewInit, ChangeDetectionStrategy, Component, EventEmitter, Input, Output, QueryList, ViewChild, ViewChildren, } from '@angular/core'; import { Subject, takeUntil } from 'rxjs'; import { HttpClient } from '@angular/common/http'; import { CommonModule } from '@angular/common'; import { AngularSvgIconModule } from 'angular-svg-icon'; import { provideLottieOptions } from 'ngx-lottie'; import { NgbPopoverModule } from '@ng-bootstrap/ng-bootstrap'; //Components import { CaUploadDropzoneComponent } from './components/ca-upload-dropzone/ca-upload-dropzone.component'; import { CaUploadFileComponent } from './components/ca-upload-file/ca-upload-file.component'; import { CaUploadFilesCarouselComponent } from './components/ca-upload-files-carousel/ca-upload-files-carousel.component'; //Interfaces import { IDocumentReviewInputEvent, IReviewFileConfig, IUploadFilesConfig, } from './interfaces'; import { IFileConfig, IFileEvent, } from './components/ca-upload-dropzone/interfaces'; import { IFilesCarouselConfig } from './components/ca-upload-files-carousel/interfaces'; //Enums import { eFileAction, eFileSize, eFileType } from './enums'; import { eDocumentAction } from './components/ca-upload-file/enums'; //Services import { CaUploadFileService } from './services'; //Pipe import { MapFilePipe, UploadClassPipe } from './pipes'; //Component import { CaLogoChangeComponent } from '../ca-logo-change/ca-logo-change.component'; //Svg Routes import { FilesUploadSvgRoutes } from './utils/svg-routes'; @Component({ selector: 'app-ca-upload-files', imports: [ CommonModule, AngularSvgIconModule, NgbPopoverModule, //Component CaUploadDropzoneComponent, CaUploadFileComponent, CaUploadFilesCarouselComponent, CaLogoChangeComponent, //Pipe UploadClassPipe, MapFilePipe, ], providers: [ provideLottieOptions({ player: () => import('lottie-web'), }), ], templateUrl: './ca-upload-files.component.html', styleUrls: ['./ca-upload-files.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush }) export class CaUploadFilesComponent implements AfterViewInit { @ViewChild(CaUploadFilesCarouselComponent) public modalCarousel!: CaUploadFilesCarouselComponent; @ViewChildren(CaUploadFileComponent) uploadedFiles!: QueryList; @Input() set uploadFilesConfig(value: IUploadFilesConfig) { this._config = { ...value }; if (this._config.fileOptionsConfig?.type === eFileType.DETAILS) this.modalCarousel?.slideToFile(0); } @Output() documentReviewInputEvent = new EventEmitter(); @Output() onFileEvent: EventEmitter = new EventEmitter(); @Output() closeDropzone = new EventEmitter<{}>(); @Output() fileAdded = new EventEmitter(); public currentSlide: number = 0; public isDropzoneShown: boolean = true; public filesUploadSvgRoutes = FilesUploadSvgRoutes; private _config!: IUploadFilesConfig; private destroy$ = new Subject(); public get config(): IUploadFilesConfig { return this._config; } public get carouselFileConfig(): IFilesCarouselConfig { return { ...this._config.carouselConfig }; } public get reviewFileConfig(): IReviewFileConfig { return this._config.review; } constructor( private uploadFileService: CaUploadFileService, private http: HttpClient ) {} ngAfterViewInit(): void { this.subscribeToUploadedFiles(); } /** * * @param data - returned data from file action (one or multiple) */ public onFileAction(data: { file: IFileConfig; action: string }): void { switch (data.action) { case eDocumentAction.TAG: this.onFileEvent.emit({ files: !this._config.onlyOneTagFile ? this._config.files : data.file, action: data.action, }); break; case eDocumentAction.DELETE: let isLastDeleted = false; this._config.files.forEach((item, index) => { if ( item.fileName === data.file.fileName && [ this._config.files.length - 1, this._config.files.length - 2, ].includes(index) ) { isLastDeleted = true; } }); this._config.files = this._config.files.filter( (item) => item.fileName !== data.file.fileName ); this.onFileEvent.emit({ files: this._config.files, action: data.action, deleteId: data.file['fileId'] || undefined, }); this.currentSlide = this._config.files.length - 1; const sizeLimits = { [eFileSize.SMALL]: 0, [eFileSize.MEDIUM]: 0, [eFileSize.LARGE]: 0, [eFileSize.MODAL_LARGE]: 4, [eFileSize.MODAL_MEDIUM]: 3, }; if (this._config.files.length < sizeLimits[this._config.size]) { this.modalCarousel.currentSlide = 0; this.modalCarousel.translateXMultipleSlides = 0; this.modalCarousel.multipleCurrentSlide = 0; } if (!this._config.files.length) { this.currentSlide = 0; } if (isLastDeleted && this.modalCarousel) { const carouselSize = this.modalCarousel.carouselConfig .customClass as eFileSize; const slideTo = carouselSize === eFileSize.LARGE ? 3 : carouselSize === eFileSize.MEDIUM ? 2 : 1; const allowSlide = (carouselSize === eFileSize.LARGE && this._config.files.length > 2) || (carouselSize === eFileSize.MEDIUM && this._config.files.length > 1) || (carouselSize === eFileSize.SMALL && this._config.files.length > 0); if (allowSlide) { this.modalCarousel.slideToFile( this._config.files.length - slideTo ); } } break; case eDocumentAction.MARK_INCORRECT: let incorrectIndx: number = 0; this._config.files.forEach((item, index) => { if (item.fileName === data.file.fileName) { incorrectIndx = index; } }); this.onFileEvent.emit({ files: this._config.files, action: data.action, index: incorrectIndx, }); break; case eDocumentAction.MARK_CORRECT: let correctIndx: number = 0; this._config.files.forEach((item, index) => { if (item.fileName === data.file.fileName) { correctIndx = index; } }); this.onFileEvent.emit({ files: this._config.files, action: data.action, index: correctIndx, }); break; default: break; } } public onUploadFiles(data: { files: IFileConfig[]; action: string }): void { if ( data.action === eFileAction.ADD && this._config.hasCrop && this._config.files.length ) { const uploadedFiles = [...data.files]; if (this._config.hasCrop) this.isDropzoneShown = true; uploadedFiles.forEach((newFile) => { const existingFileIndex = this._config.files.findIndex( (existingFile) => existingFile.url ); if (existingFileIndex !== -1) { this._config.files[existingFileIndex] = newFile; this.fileAdded.emit(newFile.url); } else { console.warn( `File ${newFile.fileName} to replace not found` ); } }); this.onFileEvent.emit({ files: [], action: eFileAction.ADD }); } if (data.action === eFileAction.ADD) { const uploadedFiles = [...data.files]; const uniqueFiles = uploadedFiles.filter( (file) => !this._config.files.some( (existingFile) => existingFile.fileName === file.fileName ) ); this._config.files = [ ...new Set([...this._config.files, ...uniqueFiles]), ]; uniqueFiles.forEach((file) => { this.fileAdded.emit(file.realFile); }); if (this._config.hasCrop) this.isDropzoneShown = false; else this.onFileEvent.emit({ files: this._config.files, action: eFileAction.ADD, }); // carousel if (this.modalCarousel) { const carouselSize = this.modalCarousel.carouselConfig .customClass as eFileSize; const slideTo = carouselSize === eFileSize.LARGE ? 3 : carouselSize === eFileSize.MEDIUM ? 2 : 1; const allowSlide = (carouselSize === eFileSize.LARGE && this._config.files.length > 2) || (carouselSize === eFileSize.MEDIUM && this._config.files.length > 1) || (carouselSize === eFileSize.SMALL && this._config.files.length > 0); if (allowSlide) { this.modalCarousel.slideToFile( this._config.files.length - slideTo ); } } } else if (data.action === eFileAction.REPLACE) { const uploadedFiles = [...data.files]; if (this._config.hasCrop) this.isDropzoneShown = true; uploadedFiles.forEach((newFile) => { const existingFileIndex = this._config.files.findIndex( (existingFile) => existingFile.url ); if (existingFileIndex !== -1) { // Replace the existing file at the found index this._config.files[existingFileIndex] = newFile; // Emit the new file that replaced the old one this.fileAdded.emit(newFile.url); } else { console.warn( `File ${newFile.fileName} to replace not found` ); } }); this.onFileEvent.emit({ files: this._config.files, action: eFileAction.ADD, }); } else if (data.action === eFileAction.DELETE) { if (this._config.hasCrop) this.isDropzoneShown = true; this._config.files.splice(0, this._config.files.length); } } public documentReviewInputEventMethod(data: { file: IFileConfig; message: string; }): void { this.documentReviewInputEvent.emit({ file: data.file, message: data.message, }); } public downloadFile(fileUrl: string, fileName: string): void { this.http.get(fileUrl, { responseType: 'blob' }).subscribe( (fileBlob: Blob) => { const anchorElement = document.createElement('a'); const objectUrl = URL.createObjectURL(fileBlob); anchorElement.href = objectUrl; anchorElement.download = fileName; anchorElement.click(); URL.revokeObjectURL(objectUrl); }, (downloadError) => { console.error('Error downloading file:', downloadError); } ); } public onLandscapeCheck(landscape: boolean): void { if (landscape) this._config.fileOptionsConfig.customClassName = 'landscape-details-view'; } public dropZoneClose(): void { this.closeDropzone.emit(); } public fileHover(file: IFileConfig): void { if (this._config.fileOptionsConfig.customClassName === 'modals') { let checkById = file?.realFile ? false : true; this._config.files.forEach((item, index) => { if ( (checkById && file?.fileId === item.fileId) || (!checkById && file.realFile?.name === item.realFile?.name) ) { this.uploadedFiles['_results'][index].updateHover(true); } else { this.uploadedFiles['_results'][index].updateHover(false); } }); } } public hoverArrow(mod: boolean): void { this._config.files.forEach((item, index) => { this.uploadedFiles['_results'][index].hoverArrow(mod); }); } private subscribeToUploadedFiles(): void { this.uploadFileService.uploadedFiles$ .pipe(takeUntil(this.destroy$)) .subscribe((data: { files: IFileConfig[]; action: string }) => { if (data) { this.onUploadFiles(data); } }); } ngOnDestroy(): void { this.destroy$.next(); this.destroy$.complete(); } }