// src/app/edit-image-modal.component.ts import { CommonModule } from '@angular/common'; import { AfterViewInit, Component, ElementRef, EventEmitter, Input, OnDestroy, Output, ViewChild } from '@angular/core'; import { fabric } from 'fabric'; import { Modal } from 'flowbite'; import { DeckImage, DeckService } from '../services/deck.service'; import { PopoverService } from '../services/popover.service'; @Component({ selector: 'app-edit-image-modal', templateUrl: './edit-image-modal.component.html', standalone: true, imports: [CommonModule], }) export class EditImageModalComponent implements AfterViewInit, OnDestroy { // Constant for box color private readonly BOX_COLOR = 'rgba(255, 0, 0, 0.3)'; // Red with transparency @Input() deckName: string = ''; @Input() imageData: { imageSrc: string | ArrayBuffer | null; deckImage: DeckImage | null } = { imageSrc: null, deckImage: null }; @Output() imageSaved = new EventEmitter(); @Output() closed = new EventEmitter(); @ViewChild('editImageModal') modalElement!: ElementRef; @ViewChild('canvas') canvasElement!: ElementRef; detectedText: string = ''; boxes: { x1: number; x2: number; y1: number; y2: number; id: number; inserted: string; updated: string }[] = []; canvas!: fabric.Canvas; maxCanvasWidth: number = 0; maxCanvasHeight: number = 0; private keyDownHandler!: (e: KeyboardEvent) => void; modal: any; constructor(private deckService: DeckService, private popoverService: PopoverService) {} async ngAfterViewInit() { this.modal = new Modal(this.modalElement.nativeElement, { backdrop: 'static', onHide: () => { this.closed.emit(); }, }); this.maxCanvasWidth = window.innerWidth * 0.6; this.maxCanvasHeight = window.innerHeight * 0.6; this.keyDownHandler = this.onKeyDown.bind(this); document.addEventListener('keydown', this.keyDownHandler); await this.initializeCanvas(); this.modal.show(); } ngOnDestroy(): void { document.removeEventListener('keydown', this.keyDownHandler); if (this.canvas) { this.canvas.dispose(); } } open(): void { this.modal.show(); } closeModal(): void { this.modal.hide(); } async initializeCanvas() { await this.processImage(); } private loadFabricImage(url: string): Promise { return new Promise((resolve, reject) => { fabric.Image.fromURL( url, img => { resolve(img); }, { crossOrigin: 'anonymous', originX: 'left', originY: 'top', }, ); }); } async processImage(): Promise { try { if (!this.imageData) { return; } this.canvas = new fabric.Canvas(this.canvasElement.nativeElement); // Set background image const backgroundImage = await this.loadFabricImage(this.imageData.imageSrc as string); const originalWidth = backgroundImage.width!; const originalHeight = backgroundImage.height!; const scaleX = this.maxCanvasWidth / originalWidth; const scaleY = this.maxCanvasHeight / originalHeight; const scaleFactor = Math.min(scaleX, scaleY, 1); const canvasWidth = originalWidth * scaleFactor; const canvasHeight = originalHeight * scaleFactor; this.canvas.setWidth(canvasWidth); this.canvas.setHeight(canvasHeight); backgroundImage.set({ scaleX: scaleFactor, scaleY: scaleFactor, }); this.canvas.setBackgroundImage(backgroundImage, this.canvas.renderAll.bind(this.canvas)); this.boxes = []; // Add boxes this.imageData.deckImage?.boxes.forEach(box => { const rect = new fabric.Rect({ left: box.x1 * scaleFactor, top: box.y1 * scaleFactor, width: (box.x2 - box.x1) * scaleFactor, height: (box.y2 - box.y1) * scaleFactor, fill: this.BOX_COLOR, // Use the constant selectable: true, hasControls: true, hasBorders: true, objectCaching: false, data: { id: box.id, inserted: box.inserted, updated: box.updated }, }); rect.on('modified', () => { this.updateBoxCoordinates(); }); rect.on('moved', () => { this.updateBoxCoordinates(); }); rect.on('scaled', () => { this.updateBoxCoordinates(); }); rect.on('rotated', () => { this.updateBoxCoordinates(); }); rect.on('removed', () => { this.updateBoxCoordinates(); }); this.canvas.add(rect); }); this.updateBoxCoordinates(); // this.detectedText = ocrResults.map(result => result.text).join('\n'); } catch (error) { console.error('Error processing image:', error); } } onKeyDown(e: KeyboardEvent): void { if (e.key === 'Delete' || e.key === 'Del') { const activeObject = this.canvas.getActiveObject(); if (activeObject) { this.canvas.remove(activeObject); this.canvas.requestRenderAll(); this.updateBoxCoordinates(); } } } updateBoxCoordinates(): void { this.boxes = []; let scaleFactor = 1; const bgImage = this.canvas.backgroundImage; if (bgImage && bgImage instanceof fabric.Image) { scaleFactor = bgImage.get('scaleX') || 1; } this.canvas.getObjects('rect').forEach((rect: fabric.Rect) => { const left = rect.left!; const top = rect.top!; const width = rect.width! * rect.scaleX!; const height = rect.height! * rect.scaleY!; const x1 = left / scaleFactor; const y1 = top / scaleFactor; const x2 = (left + width) / scaleFactor; const y2 = (top + height) / scaleFactor; this.boxes.push({ x1: Math.round(x1), x2: Math.round(x2), y1: Math.round(y1), y2: Math.round(y2), id: rect.data?.id, inserted: rect.data?.inserted, updated: rect.data?.updated, }); }); this.canvas.requestRenderAll(); } addNewBox(): void { if (!this.canvas) { return; } const boxWidth = 100; const boxHeight = 50; const canvasWidth = this.canvas.getWidth(); const canvasHeight = this.canvas.getHeight(); const rect = new fabric.Rect({ left: (canvasWidth - boxWidth) / 2, top: (canvasHeight - boxHeight) / 2, width: boxWidth, height: boxHeight, fill: this.BOX_COLOR, // Use the constant selectable: true, hasControls: true, hasBorders: true, objectCaching: false, }); rect.on('modified', () => { this.updateBoxCoordinates(); }); rect.on('moved', () => { this.updateBoxCoordinates(); }); rect.on('scaled', () => { this.updateBoxCoordinates(); }); rect.on('rotated', () => { this.updateBoxCoordinates(); }); rect.on('removed', () => { this.updateBoxCoordinates(); }); this.canvas.add(rect); this.canvas.setActiveObject(rect); this.canvas.requestRenderAll(); this.updateBoxCoordinates(); } save(): void { // Implement the logic to save the image data here // For example, via a service or directly here const data = { deckname: this.deckName, bildname: this.imageData.deckImage?.name, // this.imageFile?.name, bildid: this.imageData.deckImage?.bildid, boxes: this.boxes, }; this.deckService.saveImageData(data).subscribe({ next: () => { this.imageSaved.emit(); this.closeModal(); }, error: err => { console.error('Error saving image:', err); this.popoverService.show({ title: 'Error', message: 'Error saving image.', }); this.closeModal(); }, }); } }