diff --git a/src/app/deck-list.component.html b/src/app/deck-list.component.html index 294a86d..f1dc878 100644 --- a/src/app/deck-list.component.html +++ b/src/app/deck-list.component.html @@ -57,6 +57,12 @@ + + + + + + @@ -83,4 +89,13 @@ + + + diff --git a/src/app/deck-list.component.ts b/src/app/deck-list.component.ts index 60761d2..cdbc3c4 100644 --- a/src/app/deck-list.component.ts +++ b/src/app/deck-list.component.ts @@ -7,6 +7,7 @@ import { TrainingComponent } from './training/training.component'; import { UploadImageModalComponent } from './upload-image-modal/upload-image-modal.component'; import { EditImageModalComponent } from './edit-image-modal/edit-image-modal.component'; import { firstValueFrom } from 'rxjs'; +import { MoveImageModalComponent } from './move-image-modal/move-image-modal.component'; @Component({ selector: 'app-deck-list', @@ -18,6 +19,7 @@ import { firstValueFrom } from 'rxjs'; UploadImageModalComponent, TrainingComponent, EditImageModalComponent, + MoveImageModalComponent, // Hinzufügen der neuen Komponente UploadImageModalComponent ] }) @@ -30,13 +32,16 @@ export class DeckListComponent implements OnInit { @ViewChild(EditImageModalComponent) editModal!: EditImageModalComponent; @ViewChild(UploadImageModalComponent) uploadModal!: UploadImageModalComponent; - imageData: { imageSrc: string | ArrayBuffer | null, deckImage:DeckImage} | null = null; + imageData: { imageSrc: string | ArrayBuffer | null, deckImage: DeckImage } | null = null; currentUploadDeckName: string = ''; // Set zur Verfolgung erweiterter Decks expandedDecks: Set = new Set(); + // State für das Verschieben von Bildern + imageToMove: { image: DeckImage, sourceDeck: Deck } | null = null; + constructor(private deckService: DeckService) { } ngOnInit(): void { @@ -73,8 +78,10 @@ export class DeckListComponent implements OnInit { error: (err) => console.error('Fehler beim Löschen des Bildes', err) }); } + editImage(deck: Deck, image: DeckImage): void { let imageSrc = null + this.currentUploadDeckName = deck.name; fetch(`/api/debug_image/${image.id}/original_compressed.jpg`) .then(response => { if (!response.ok) { @@ -86,7 +93,7 @@ export class DeckListComponent implements OnInit { const reader = new FileReader(); reader.onloadend = () => { imageSrc = reader.result; // Base64-String - this.imageData = {imageSrc,deckImage:image} + this.imageData = { imageSrc, deckImage: image } }; reader.readAsDataURL(blob); }) @@ -94,6 +101,7 @@ export class DeckListComponent implements OnInit { console.error('Fehler beim Laden des Bildes:', error); }); } + openTraining(deck: Deck): void { this.selectedDeck = deck; } @@ -139,18 +147,8 @@ export class DeckListComponent implements OnInit { console.error('Fehler beim Parsen der erweiterten Decks aus sessionStorage', e); } } else { - // Wenn keine Daten gespeichert sind, alle Decks standardmäßig erweitern + // Wenn keine Daten gespeichert sind, alle Decks standardmäßig nicht erweitern this.expandedDecks = new Set(); - // Dieser Teil wird nach dem Laden der Decks initialisiert - // Wir erweitern alle Decks, sobald sie geladen sind - this.deckService.getDecks().subscribe({ - next: (data) => { - data.forEach(deck => this.expandedDecks.add(deck.id)); - this.saveExpandedDecks(); - this.decks = data; - }, - error: (err) => console.error('Fehler beim Laden der Decks', err) - }); } } @@ -169,12 +167,25 @@ export class DeckListComponent implements OnInit { onImageUploaded(imageData: any): void { this.imageData = imageData; } + onClosed(){ this.imageData = null; } + async onImageSaved() { // Handle das Speichern der Bilddaten, z.B. aktualisiere die Liste der Bilder this.imageData = null; this.decks = await firstValueFrom(this.deckService.getDecks()) } + + // Methode zum Öffnen des MoveImageModal + openMoveImageModal(deck: Deck, image: DeckImage): void { + this.imageToMove = { image, sourceDeck: deck }; + } + + // Handler für das moveCompleted Event + onImageMoved(): void { + this.imageToMove = null; + this.loadDecks(); + } } diff --git a/src/app/deck.service.ts b/src/app/deck.service.ts index 2d7911c..3b015f9 100644 --- a/src/app/deck.service.ts +++ b/src/app/deck.service.ts @@ -88,32 +88,7 @@ export class DeckService { getDeck(deckname:string): Observable { return this.http.get(`${this.apiUrl}/${deckname}/images`); } - // getImage(name: string): Observable { - // return this.http.get(`${this.apiUrl}/image/${name}`).pipe( - // map(response => { - // if (response.length === 0) { - // // Falls keine Daten zurückgegeben werden, ein leeres DeckImage zurückgeben - // return { name: name, boxes: [] }; - // } - // // Extrahiere den Bildnamen aus dem ersten Element des Arrays - // const imageName = response[0].bildname; - - // // Mape die Backend-Daten auf das Box-Interface - // const boxes: Box[] = response.map(item => ({ - // x1: item.x1, - // x2: item.x2, - // y1: item.y1, - // y2: item.y2 - // })); - - // return { - // name: imageName, - // boxes: boxes - // }; - // }) - // ); - // } createDeck(deckname: string): Observable { return this.http.post(this.apiUrl, { deckname }); } @@ -130,4 +105,9 @@ export class DeckService { deleteImage(imageName: string): Observable { return this.http.delete(`${this.apiUrl}/image/${imageName}`); } + + // Neue Methode zum Verschieben eines Bildes + moveImage(imageId: string, targetDeckId: number): Observable { + return this.http.post(`${this.apiUrl}/images/${imageId}/move`, { targetDeckId }); + } } diff --git a/src/app/edit-image-modal/edit-image-modal.component.html b/src/app/edit-image-modal/edit-image-modal.component.html index b8e7901..93d7af4 100644 --- a/src/app/edit-image-modal/edit-image-modal.component.html +++ b/src/app/edit-image-modal/edit-image-modal.component.html @@ -9,21 +9,25 @@ Schließen - Bild bearbeiten + + + Bild bearbeiten 0">({{ boxes.length }} Box{{ boxes.length > 1 ? 'en' : '' }}) + - + - - Save - - - + + + + Save + + + Neue Box + + diff --git a/src/app/edit-image-modal/edit-image-modal.component.ts b/src/app/edit-image-modal/edit-image-modal.component.ts index fa0dabe..28d6959 100644 --- a/src/app/edit-image-modal/edit-image-modal.component.ts +++ b/src/app/edit-image-modal/edit-image-modal.component.ts @@ -12,6 +12,9 @@ import { DeckImage, DeckService, OcrResult } from '../deck.service'; imports: [CommonModule] }) export class EditImageModalComponent implements AfterViewInit, OnDestroy { + // Konstante für die Boxfarbe + private readonly BOX_COLOR = 'rgba(255, 0, 0, 0.3)'; // Rot mit Transparenz + @Input() deckName: string = ''; @Input() imageData : {imageSrc:string|ArrayBuffer|null, deckImage:DeckImage|null} = {imageSrc:null,deckImage:null}; @Output() imageSaved = new EventEmitter(); @@ -35,7 +38,7 @@ export class EditImageModalComponent implements AfterViewInit, OnDestroy { this.modal = new Modal(this.modalElement.nativeElement,{ onHide: () => { this.closed.emit(); - }}, + }}, ); this.maxCanvasWidth = window.innerWidth * 0.6; @@ -87,7 +90,7 @@ export class EditImageModalComponent implements AfterViewInit, OnDestroy { async processImage(): Promise { try { if (!this.imageData){ - return + return; } this.canvas = new fabric.Canvas(this.canvasElement.nativeElement); @@ -123,9 +126,9 @@ export class EditImageModalComponent implements AfterViewInit, OnDestroy { 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: 'rgba(255, 0, 0, 0.3)', + width: (box.x2 - box.x1) * scaleFactor, + height: (box.y2 - box.y1) * scaleFactor, + fill: this.BOX_COLOR, // Verwendung der Konstante selectable: true, hasControls: true, hasBorders: true, @@ -202,12 +205,58 @@ export class EditImageModalComponent implements AfterViewInit, OnDestroy { 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, // Verwendung der Konstante + 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 { // Hier implementierst du die Logik zum Speichern der Bilddaten // Zum Beispiel über einen Service oder direkt hier const data = { deckname: this.deckName, - bildname: this.imageData.deckImage?.name,//this.imageFile?.name, + bildname: this.imageData.deckImage?.name, // this.imageFile?.name, bildid: this.imageData.deckImage?.id, boxes: this.boxes, }; @@ -222,6 +271,5 @@ export class EditImageModalComponent implements AfterViewInit, OnDestroy { this.closeModal(); } }); - } } diff --git a/src/app/move-image-modal/move-image-modal.component.html b/src/app/move-image-modal/move-image-modal.component.html new file mode 100644 index 0000000..f8f249f --- /dev/null +++ b/src/app/move-image-modal/move-image-modal.component.html @@ -0,0 +1,24 @@ + + + + Bild verschieben + Wähle das Zieldeck für das Bild {{ image.name }} aus. + + + {{ deck.name }} + + + + + Abbrechen + + + Verschieben + + + + + \ No newline at end of file diff --git a/src/app/move-image-modal/move-image-modal.component.ts b/src/app/move-image-modal/move-image-modal.component.ts new file mode 100644 index 0000000..40e7fd6 --- /dev/null +++ b/src/app/move-image-modal/move-image-modal.component.ts @@ -0,0 +1,45 @@ +// src/app/move-image-modal.component.ts +import { Component, Input, Output, EventEmitter } from '@angular/core'; +import { CommonModule } from '@angular/common'; + +import { FormsModule } from '@angular/forms'; +import { DeckImage, Deck, DeckService } from '../deck.service'; + +@Component({ + selector: 'app-move-image-modal', + templateUrl: './move-image-modal.component.html', + standalone: true, + imports: [CommonModule, FormsModule] +}) +export class MoveImageModalComponent { + @Input() image!: DeckImage; + @Input() sourceDeck!: Deck; + @Input() decks: Deck[] = []; + @Output() moveCompleted = new EventEmitter(); + @Output() closed = new EventEmitter(); + + selectedDeckId: number | null = null; + + constructor(private deckService: DeckService) { } + + moveImage(): void { + if (this.selectedDeckId === null) { + return; + } + + this.deckService.moveImage(this.image.id, this.selectedDeckId).subscribe({ + next: () => { + this.moveCompleted.emit(); + this.close(); + }, + error: (err) => { + console.error('Fehler beim Verschieben des Bildes:', err); + alert('Fehler beim Verschieben des Bildes.'); + } + }); + } + + close(): void { + this.closed.emit(); + } +} diff --git a/src/app/upload-image-modal/upload-image-modal.component.html b/src/app/upload-image-modal/upload-image-modal.component.html index 713cd2e..f93ac74 100644 --- a/src/app/upload-image-modal/upload-image-modal.component.html +++ b/src/app/upload-image-modal/upload-image-modal.component.html @@ -14,7 +14,7 @@ Bild hochladen - + diff --git a/src/app/upload-image-modal/upload-image-modal.component.ts b/src/app/upload-image-modal/upload-image-modal.component.ts index fa0e094..285dc3a 100644 --- a/src/app/upload-image-modal/upload-image-modal.component.ts +++ b/src/app/upload-image-modal/upload-image-modal.component.ts @@ -16,6 +16,7 @@ export class UploadImageModalComponent implements AfterViewInit, OnDestroy { @Output() imageUploaded = new EventEmitter<{ imageSrc: string | ArrayBuffer | null | undefined, deckImage:DeckImage }>(); @ViewChild('uploadImageModal') modalElement!: ElementRef; + @ViewChild('imageFile') imageFileElement!: ElementRef; imageFile: File | null = null; processingStatus: string = ''; @@ -90,7 +91,7 @@ export class UploadImageModalComponent implements AfterViewInit, OnDestroy { }); const deckImage:DeckImage={name:bildname,id:bildid,boxes} this.imageUploaded.emit({ imageSrc, deckImage }); - + this.resetFileInput(); // Schließe das Upload-Modal this.closeModal(); } catch (error) { @@ -101,4 +102,12 @@ export class UploadImageModalComponent implements AfterViewInit, OnDestroy { }; reader.readAsDataURL(file); } + /** + * Setzt das Datei-Input-Feld zurück, sodass dieselbe Datei erneut ausgewählt werden kann. + */ + resetFileInput(): void { + if (this.imageFileElement && this.imageFileElement.nativeElement) { + this.imageFileElement.nativeElement.value = ''; + } + } }
Wähle das Zieldeck für das Bild {{ image.name }} aus.