include fabric.js + Bugs

This commit is contained in:
Andreas Knuth 2024-11-17 16:49:46 +01:00
parent 21cde45999
commit 3be7a9cdec
8 changed files with 1540 additions and 157 deletions

1152
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -17,6 +17,7 @@
"@angular/platform-browser": "^18.2.0", "@angular/platform-browser": "^18.2.0",
"@angular/platform-browser-dynamic": "^18.2.0", "@angular/platform-browser-dynamic": "^18.2.0",
"@angular/router": "^18.2.0", "@angular/router": "^18.2.0",
"fabric": "^5.4.1",
"flowbite": "^2.5.2", "flowbite": "^2.5.2",
"rxjs": "~7.8.0", "rxjs": "~7.8.0",
"tslib": "^2.3.0", "tslib": "^2.3.0",
@ -26,6 +27,7 @@
"@angular-devkit/build-angular": "^18.2.12", "@angular-devkit/build-angular": "^18.2.12",
"@angular/cli": "^18.2.12", "@angular/cli": "^18.2.12",
"@angular/compiler-cli": "^18.2.0", "@angular/compiler-cli": "^18.2.0",
"@types/fabric": "^5.3.9",
"tailwindcss": "^3.4.15", "tailwindcss": "^3.4.15",
"typescript": "~5.5.2" "typescript": "~5.5.2"
} }

View File

@ -1,8 +1,8 @@
<!-- src/app/create-deck-modal.component.html --> <!-- src/app/create-deck-modal.component.html -->
<div id="createDeckModal" tabindex="-1" aria-hidden="true" class="fixed top-0 left-0 right-0 z-50 hidden w-full p-4 overflow-x-hidden overflow-y-auto md:inset-0 h-modal md:h-full"> <div #createDeckModal id="createDeckModal" tabindex="-1" aria-hidden="true" class="fixed top-0 left-0 right-0 z-50 hidden w-full p-4 overflow-x-hidden overflow-y-auto md:inset-0 h-modal md:h-full">
<div class="relative w-full h-full max-w-md md:h-auto"> <div class="relative w-full h-full max-w-md md:h-auto">
<div class="relative bg-white rounded-lg shadow"> <div class="relative bg-white rounded-lg shadow">
<button type="button" class="absolute top-3 right-2.5 text-gray-400 bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm p-1.5 ml-auto inline-flex items-center" data-modal-hide="createDeckModal"> <button type="button" class="absolute top-3 right-2.5 text-gray-400 bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm p-1.5 ml-auto inline-flex items-center" (click)="closeModal()">
<svg aria-hidden="true" class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20"> <svg aria-hidden="true" class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd"></path> <path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd"></path>
</svg> </svg>
@ -23,4 +23,3 @@
</div> </div>
</div> </div>
</div> </div>

View File

@ -1,9 +1,9 @@
// src/app/create-deck-modal.component.ts // src/app/create-deck-modal.component.ts
import { Component, Output, EventEmitter } from '@angular/core'; import { Component, Output, EventEmitter, AfterViewInit, ViewChild, ElementRef } from '@angular/core';
import { DeckService } from '../deck.service'; import { DeckService } from '../deck.service';
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { Modal } from 'flowbite'; import { Modal } from 'flowbite';
import { FormsModule } from '@angular/forms';
@Component({ @Component({
selector: 'app-create-deck-modal', selector: 'app-create-deck-modal',
@ -11,12 +11,22 @@ import { Modal } from 'flowbite';
standalone: true, standalone: true,
imports: [CommonModule, FormsModule] imports: [CommonModule, FormsModule]
}) })
export class CreateDeckModalComponent { export class CreateDeckModalComponent implements AfterViewInit {
@Output() deckCreated = new EventEmitter<void>(); @Output() deckCreated = new EventEmitter<void>();
@ViewChild('createDeckModal') modalElement!: ElementRef;
deckName: string = ''; deckName: string = '';
modal!: Modal;
constructor(private deckService: DeckService) { } constructor(private deckService: DeckService) { }
ngAfterViewInit(): void {
this.modal = new Modal(this.modalElement.nativeElement);
}
open(): void {
this.modal.show();
}
createDeck(event: Event): void { createDeck(event: Event): void {
event.preventDefault(); event.preventDefault();
if (this.deckName.trim() === '') { if (this.deckName.trim() === '') {
@ -28,12 +38,7 @@ export class CreateDeckModalComponent {
next: () => { next: () => {
this.deckName = ''; this.deckName = '';
this.deckCreated.emit(); this.deckCreated.emit();
// Modal schließen this.modal.hide();
const modalElement = document.getElementById('createDeckModal');
if (modalElement) {
const modal = new Modal(modalElement);
modal.hide();
}
}, },
error: (err) => { error: (err) => {
console.error('Fehler beim Erstellen des Decks', err); console.error('Fehler beim Erstellen des Decks', err);
@ -41,4 +46,9 @@ export class CreateDeckModalComponent {
} }
}); });
} }
closeModal(): void {
this.modal.hide();
} }
}

View File

@ -2,7 +2,7 @@
<div> <div>
<!-- Button zum Erstellen eines neuen Decks --> <!-- Button zum Erstellen eines neuen Decks -->
<div class="flex justify-end mb-4"> <div class="flex justify-end mb-4">
<button data-modal-target="createDeckModal" data-modal-toggle="createDeckModal" class="bg-green-500 text-white py-2 px-4 rounded hover:bg-green-600"> <button (click)="openCreateDeckModal()" class="bg-green-500 text-white py-2 px-4 rounded hover:bg-green-600">
Neues Deck erstellen Neues Deck erstellen
</button> </button>
</div> </div>
@ -25,19 +25,19 @@
<button (click)="openTraining(deck)" class="flex-1 bg-blue-500 text-white py-2 px-4 rounded hover:bg-blue-600"> <button (click)="openTraining(deck)" class="flex-1 bg-blue-500 text-white py-2 px-4 rounded hover:bg-blue-600">
Training starten Training starten
</button> </button>
<button [attr.data-modal-target]="'uploadImageModal'" [attr.data-modal-toggle]="'uploadImageModal'" [attr.data-deck-name]="deck.name" class="flex-1 bg-green-500 text-white py-2 px-4 rounded hover:bg-green-600"> <button (click)="openUploadImageModal(deck.name)" class="flex-1 bg-green-500 text-white py-2 px-4 rounded hover:bg-green-600">
Bild hinzufügen Bild hinzufügen
</button> </button>
</div> </div>
<!-- UploadImageModalComponent wird mit deck.name als Input geöffnet -->
<app-upload-image-modal [deckName]="deck.name" (imageUploaded)="loadDecks()"></app-upload-image-modal>
</div> </div>
</div> </div>
<!-- CreateDeckModalComponent --> <!-- CreateDeckModalComponent -->
<app-create-deck-modal (deckCreated)="loadDecks()"></app-create-deck-modal> <app-create-deck-modal (deckCreated)="loadDecks()"></app-create-deck-modal>
<!-- UploadImageModalComponent -->
<app-upload-image-modal [deckName]="currentUploadDeckName" (imageUploaded)="loadDecks()"></app-upload-image-modal>
<!-- TrainingComponent --> <!-- TrainingComponent -->
<app-training *ngIf="selectedDeck" [deck]="selectedDeck" (close)="closeTraining()"></app-training> <app-training *ngIf="selectedDeck" [deck]="selectedDeck" (close)="closeTraining()"></app-training>
</div> </div>

View File

@ -1,12 +1,10 @@
// src/app/deck-list.component.ts // src/app/deck-list.component.ts
import { Component, OnInit } from '@angular/core'; import { Component, OnInit, ViewChild } from '@angular/core';
import { DeckService, Deck } from './deck.service'; import { DeckService, Deck } from './deck.service';
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { HttpClientModule } from '@angular/common/http';
import { CreateDeckModalComponent } from './create-deck-modal/create-deck-modal.component'; import { CreateDeckModalComponent } from './create-deck-modal/create-deck-modal.component';
import { TrainingComponent } from './training/training.component'; import { TrainingComponent } from './training/training.component';
import { UploadImageModalComponent } from './upload-image-modal/upload-image-modal.component'; import { UploadImageModalComponent } from './upload-image-modal/upload-image-modal.component';
import { initFlowbite } from 'flowbite';
@Component({ @Component({
@ -24,6 +22,11 @@ export class DeckListComponent implements OnInit {
decks: Deck[] = []; decks: Deck[] = [];
selectedDeck: Deck | null = null; selectedDeck: Deck | null = null;
@ViewChild(CreateDeckModalComponent) createDeckModal!: CreateDeckModalComponent;
@ViewChild(UploadImageModalComponent) uploadImageModal!: UploadImageModalComponent;
currentUploadDeckName: string = '';
constructor(private deckService: DeckService) { } constructor(private deckService: DeckService) { }
ngOnInit(): void { ngOnInit(): void {
@ -55,4 +58,15 @@ export class DeckListComponent implements OnInit {
this.selectedDeck = null; this.selectedDeck = null;
this.loadDecks(); this.loadDecks();
} }
openCreateDeckModal(): void {
this.createDeckModal.open();
} }
openUploadImageModal(deckName: string): void {
this.currentUploadDeckName = deckName;
this.uploadImageModal.deckName = deckName;
this.uploadImageModal.open();
}
}

View File

@ -1,8 +1,8 @@
<!-- src/app/upload-image-modal.component.html --> <!-- src/app/upload-image-modal.component.html -->
<div id="uploadImageModal" tabindex="-1" aria-hidden="true" class="fixed top-0 left-0 right-0 z-50 hidden w-full p-4 overflow-x-hidden overflow-y-auto md:inset-0 h-modal md:h-full"> <div #uploadImageModal id="uploadImageModal" tabindex="-1" aria-hidden="true" class="fixed top-0 left-0 right-0 z-50 hidden w-full p-4 overflow-x-hidden overflow-y-auto md:inset-0 h-modal md:h-full">
<div class="relative w-full h-full max-w-md md:h-auto"> <div class="relative w-full h-full max-w-2xl md:h-auto">
<div class="relative bg-white rounded-lg shadow"> <div class="relative bg-white rounded-lg shadow">
<button type="button" class="absolute top-3 right-2.5 text-gray-400 bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm p-1.5 ml-auto inline-flex items-center" data-modal-hide="uploadImageModal"> <button type="button" class="absolute top-3 right-2.5 text-gray-400 bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm p-1.5 ml-auto inline-flex items-center" (click)="closeModal()">
<svg aria-hidden="true" class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20"> <svg aria-hidden="true" class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd"></path> <path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd"></path>
</svg> </svg>
@ -10,22 +10,30 @@
</button> </button>
<div class="p-6"> <div class="p-6">
<h3 class="mb-4 text-xl font-medium text-gray-900">Bild zu Deck hinzufügen</h3> <h3 class="mb-4 text-xl font-medium text-gray-900">Bild zu Deck hinzufügen</h3>
<form (submit)="uploadImage($event)">
<!-- Formular sichtbar, solange formVisible true ist -->
<div *ngIf="formVisible">
<div class="mb-4"> <div class="mb-4">
<label for="imageFile" class="block text-sm font-medium text-gray-700">Bild hochladen</label> <label for="imageFile" class="block text-sm font-medium text-gray-700">Bild hochladen</label>
<input type="file" id="imageFile" (change)="onFileChange($event)" accept="image/*" required class="mt-1 block w-full border border-gray-300 rounded-md shadow-sm p-2" /> <input type="file" id="imageFile" (change)="onFileChange($event)" accept="image/*" required class="mt-1 block w-full border border-gray-300 rounded-md shadow-sm p-2" />
</div> </div>
<div class="mb-4">
<label for="imageText" class="block text-sm font-medium text-gray-700">Text zur Vokabel</label>
<input type="text" id="imageText" [(ngModel)]="imageText" name="imageText" required class="mt-1 block w-full border border-gray-300 rounded-md shadow-sm p-2" />
</div>
<input type="hidden" [value]="deckName" />
<button type="submit" class="w-full bg-blue-500 text-white py-2 px-4 rounded hover:bg-blue-600">
Hochladen
</button>
</form>
</div>
</div>
</div>
</div> </div>
<!-- Canvas sichtbar, sobald upload erfolgreich ist -->
<div *ngIf="canvasVisible">
<canvas #canvas class="border border-gray-300 rounded"></canvas>
</div>
<!-- Statusanzeige -->
<div *ngIf="processingStatus" class="mt-4">
<p class="text-sm text-gray-700">{{ processingStatus }}</p>
</div>
<!-- Ladeanzeige -->
<div *ngIf="loading" class="mt-4">
<p class="text-sm text-gray-700">Verarbeitung läuft...</p>
</div>
</div>
</div>
</div>
</div>

View File

@ -1,7 +1,9 @@
// src/app/upload-image-modal.component.ts // src/app/upload-image-modal.component.ts
import { Component, Input, Output, EventEmitter } from '@angular/core'; import { Component, Input, Output, EventEmitter, AfterViewInit, ViewChild, ElementRef, OnDestroy } from '@angular/core';
import { DeckService } from '../deck.service'; import { DeckService } from '../deck.service';
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { HttpClient } from '@angular/common/http';
import { fabric } from 'fabric';
import { FormsModule } from '@angular/forms'; import { FormsModule } from '@angular/forms';
import { Modal } from 'flowbite'; import { Modal } from 'flowbite';
@ -11,44 +13,306 @@ import { Modal } from 'flowbite';
standalone: true, standalone: true,
imports: [CommonModule, FormsModule] imports: [CommonModule, FormsModule]
}) })
export class UploadImageModalComponent { export class UploadImageModalComponent implements AfterViewInit, OnDestroy {
@Input() deckName: string = ''; @Input() deckName: string = '';
@Output() imageUploaded = new EventEmitter<void>(); @Output() imageUploaded = new EventEmitter<void>();
@ViewChild('uploadImageModal') modalElement!: ElementRef;
@ViewChild('canvas') canvasElement!: ElementRef<HTMLCanvasElement>;
imageFile: File | null = null; imageFile: File | null = null;
imageText: string = ''; imageText: string = '';
modal!: any; // Typ kann je nach Flowbite-Version angepasst werden
canvas!: fabric.Canvas;
constructor(private deckService: DeckService) { } // Fabric.js Related Variables
originalImageSrc: string | ArrayBuffer | undefined | null = null;
detectedText: string = '';
processingStatus: string = '';
loading: boolean = false;
onFileChange(event: Event): void { // Maximal erlaubte Größe
const input = event.target as HTMLInputElement; maxCanvasWidth: number = 0;
if (input.files && input.files.length) { maxCanvasHeight: number = 0;
this.imageFile = input.files[0];
boxes: { x1: number; x2: number; y1: number; y2: number }[] = [];
originalImageWidth: number = 0;
originalImageHeight: number = 0;
// Referenz zum Keydown-Eventhandler
private keyDownHandler!: (e: KeyboardEvent) => void;
constructor(private deckService: DeckService, private http: HttpClient) { }
ngAfterViewInit(): void {
// Initialisiere die Flowbite Modal
this.modal = new Modal(this.modalElement.nativeElement);
// Initialisiere Fabric.js Canvas
this.canvas = new fabric.Canvas(this.canvasElement.nativeElement);
// Berechne die maximal erlaubten Abmessungen basierend auf dem Viewport
this.maxCanvasWidth = window.innerWidth * 0.6; // Passe nach Bedarf an
this.maxCanvasHeight = window.innerHeight * 0.6; // Passe nach Bedarf an
// Keydown-Eventlistener hinzufügen
this.keyDownHandler = this.onKeyDown.bind(this);
document.addEventListener('keydown', this.keyDownHandler);
}
ngOnDestroy(): void {
// Keydown-Eventlistener entfernen
document.removeEventListener('keydown', this.keyDownHandler);
// Fabric.js Canvas zerstören
if (this.canvas) {
this.canvas.dispose();
} }
} }
uploadImage(event: Event): void { open(): void {
event.preventDefault(); this.resetState();
if (!this.imageFile || this.imageText.trim() === '') { this.modal.show();
alert('Bitte ein Bild und den zugehörigen Text angeben.'); }
closeModal(): void {
this.modal.hide();
}
resetState(): void {
this.imageFile = null;
this.imageText = '';
this.originalImageSrc = null;
this.detectedText = '';
this.processingStatus = '';
this.loading = false;
this.boxes = [];
this.originalImageWidth = 0;
this.originalImageHeight = 0;
this.canvas.clear();
}
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();
}
}
}
async onFileChange(event: any): Promise<void> {
const file: File = event.target.files[0];
if (!file) return;
// Status zurücksetzen
this.processingStatus = 'Verarbeitung läuft...';
this.detectedText = '';
this.loading = true;
// Bild als Base64 laden
const reader = new FileReader();
reader.onload = async (e) => {
this.originalImageSrc = e.target?.result;
// Bild als Base64-String ohne Präfix (data:image/...)
const imageBase64 = (this.originalImageSrc as string).split(',')[1];
try {
// Anfrage an den Backend-Service senden
const response = await this.http.post<any>('/api/ocr', { image: imageBase64 }).toPromise();
if (!response || !response.results) {
this.processingStatus = 'Ungültige Antwort vom OCR-Service';
this.loading = false;
return; return;
} }
this.deckService.uploadImage(this.deckName, this.imageFile, this.imageText).subscribe({ // Bildverarbeitung im Frontend durchführen
next: () => { await this.processImage(response.results);
this.imageFile = null; } catch (error) {
this.imageText = ''; console.error('Fehler beim OCR-Service:', error);
this.imageUploaded.emit(); this.processingStatus = 'Fehler beim OCR-Service';
// Modal schließen this.loading = false;
const modalElement = document.getElementById('uploadImageModal');
if (modalElement) {
const modal = new Modal(modalElement);
modal.hide();
} }
};
reader.readAsDataURL(file);
}
private loadFabricImage(url: string): Promise<fabric.Image> {
return new Promise((resolve, reject) => {
fabric.Image.fromURL(
url,
(img) => {
resolve(img);
}, },
error: (err) => { {
console.error('Fehler beim Hochladen des Bildes', err); crossOrigin: 'anonymous',
alert('Fehler beim Hochladen des Bildes.'); originX: 'left',
originY: 'top',
} }
);
}); });
} }
async processImage(ocrResults: any[]): Promise<void> {
// Canvas zurücksetzen
this.canvas.clear();
this.boxes = [];
// Hintergrundbild setzen
try {
const backgroundImage = await this.loadFabricImage(this.originalImageSrc as string);
// Speichere die Originalbildgröße
this.originalImageWidth = backgroundImage.width!;
this.originalImageHeight = backgroundImage.height!;
// Berechne Skalierungsfaktor basierend auf maximal erlaubter Größe
const scaleX = this.maxCanvasWidth / backgroundImage.width!;
const scaleY = this.maxCanvasHeight / backgroundImage.height!;
const scaleFactor = Math.min(scaleX, scaleY, 1); // Vermeide Vergrößerung
// Neue Größe des Canvas
const canvasWidth = backgroundImage.width! * scaleFactor;
const canvasHeight = backgroundImage.height! * scaleFactor;
// Canvas-Größe anpassen
this.canvas.setWidth(canvasWidth);
this.canvas.setHeight(canvasHeight);
// Hintergrundbild skalieren
backgroundImage.set({
scaleX: scaleFactor,
scaleY: scaleFactor,
});
// Hintergrundbild setzen
this.canvas.setBackgroundImage(backgroundImage, this.canvas.renderAll.bind(this.canvas));
// Boxen hinzufügen
ocrResults.forEach(result => {
const box = result.box;
// Grenzen berechnen
const xs = box.map((point: number[]) => point[0]);
const ys = box.map((point: number[]) => point[1]);
const xMin = Math.min(...xs);
const xMax = Math.max(...xs);
const yMin = Math.min(...ys);
const yMax = Math.max(...ys);
// Skalierung anwenden
const left = xMin * scaleFactor;
const top = yMin * scaleFactor;
const width = (xMax - xMin) * scaleFactor;
const height = (yMax - yMin) * scaleFactor;
// Rechteck erstellen
const rect = new fabric.Rect({
left: left,
top: top,
width: width,
height: height,
fill: 'rgba(0, 0, 0, 0.5)',
selectable: true,
hasControls: true,
hasBorders: true,
objectCaching: false,
});
// Event-Listener hinzufügen
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);
});
// Initiale Box-Koordinaten aktualisieren
this.updateBoxCoordinates();
// Erkannten Text anzeigen
this.detectedText = ocrResults.map(result => result.text).join('\n');
this.processingStatus = 'Verarbeitung abgeschlossen';
this.loading = false;
// Eingabefelder und Button ausblenden
this.hideFormElements();
} catch (error) {
console.error('Fehler beim Setzen des Hintergrundbildes:', error);
this.processingStatus = 'Fehler bei der Bildverarbeitung';
this.loading = false;
} }
}
updateBoxCoordinates(): void {
// Leere die aktuelle Box-Liste
this.boxes = [];
// Skalierungsfaktor ermitteln (sollte der gleiche sein wie zuvor)
let scaleFactor = 1;
const bgImage = this.canvas.backgroundImage;
if (bgImage && bgImage instanceof fabric.Image) {
scaleFactor = bgImage.get('scaleX') || 1;
}
// Alle Rechtecke durchgehen
this.canvas.getObjects('rect').forEach((rect: fabric.Rect) => {
// Aktuelle Position und Größe des Rechtecks
const left = rect.left!;
const top = rect.top!;
const width = rect.width! * rect.scaleX!;
const height = rect.height! * rect.scaleY!;
// Umrechnung auf Originalbildgröße
const x1 = left / scaleFactor;
const y1 = top / scaleFactor;
const x2 = (left + width) / scaleFactor;
const y2 = (top + height) / scaleFactor;
// Werte runden
this.boxes.push({
x1: Math.round(x1),
x2: Math.round(x2),
y1: Math.round(y1),
y2: Math.round(y2)
});
});
// Trigger Angular Change Detection
this.canvas.requestRenderAll();
}
hideFormElements(): void {
// Implementiere die Logik, um die Eingabefelder und den Button auszublenden
// Dies kann durch eine zusätzliche Variable gesteuert werden
this.formVisible = false;
this.canvasVisible = true;
}
showFormElements(): void {
this.formVisible = true;
this.canvasVisible = false;
}
// Neue Variable zur Steuerung der Sichtbarkeit
formVisible: boolean = true;
canvasVisible: boolean = true;
}