vokabeltraining/src/app/edit-image-modal/edit-image-modal.component.ts

281 lines
7.7 KiB
TypeScript

// 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<void>();
@Output() closed = new EventEmitter<void>();
@ViewChild('editImageModal') modalElement!: ElementRef;
@ViewChild('canvas') canvasElement!: ElementRef<HTMLCanvasElement>;
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<fabric.Image> {
return new Promise((resolve, reject) => {
fabric.Image.fromURL(
url,
img => {
resolve(img);
},
{
crossOrigin: 'anonymous',
originX: 'left',
originY: 'top',
},
);
});
}
async processImage(): Promise<void> {
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();
},
});
}
}