Compare commits

..

No commits in common. "b43044a0dd062acdceb3766a0e3be4619e79abc4" and "ab021cb4c291dd65062361681247358f8343671a" have entirely different histories.

6 changed files with 93 additions and 153 deletions

View File

@ -12,56 +12,45 @@
<div *ngFor="let deck of decks" class="bg-white shadow rounded-lg p-6 flex flex-col"> <div *ngFor="let deck of decks" class="bg-white shadow rounded-lg p-6 flex flex-col">
<div class="flex justify-between items-center mb-4"> <div class="flex justify-between items-center mb-4">
<h2 class="text-xl font-semibold">{{ deck.name }}</h2> <h2 class="text-xl font-semibold">{{ deck.name }}</h2>
<button (click)="deleteDeck(deck.name)" class="text-red-500 hover:text-red-700">
<!-- Anzahl der Bilder anzeigen, wenn ein Training aktiv ist -->
<span *ngIf="selectedDeck" class="text-gray-600">
{{ deck.images.length }} Bilder
</span>
<!-- Löschen-Button anzeigen, wenn kein Training aktiv ist -->
<button *ngIf="!selectedDeck" (click)="deleteDeck(deck.name)" class="text-red-500 hover:text-red-700" title="Deck löschen">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" /> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
</svg> </svg>
</button> </button>
</div> </div>
<!-- Bildliste und Action-Buttons nur anzeigen, wenn kein Training aktiv ist --> <!-- Liste der Bilder mit Anzahl der Boxen und Icons -->
<ng-container *ngIf="!selectedDeck"> <ul class="mb-4">
<!-- Liste der Bilder mit Anzahl der Boxen und Icons --> <li *ngFor="let image of deck.images" class="flex justify-between items-center py-2 border-b last:border-b-0">
<ul class="mb-4"> <div>
<li *ngFor="let image of deck.images" class="flex justify-between items-center py-2 border-b last:border-b-0"> <span class="font-medium">{{ image.name }}</span>
<div> <span class="text-gray-600">&nbsp;({{ image.boxes?.length }} Boxen)</span>
<span class="font-medium">{{ image.name }}</span> </div>
<span class="text-gray-600">({{ image.boxes.length }} Boxen)</span> <div class="flex space-x-2">
</div> <!-- Edit Icon -->
<div class="flex space-x-2"> <button class="text-blue-500 hover:text-blue-700" title="Bild bearbeiten">
<!-- Edit Icon --> <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<button class="text-blue-500 hover:text-blue-700" title="Bild bearbeiten"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 4H4v7m0 0l9-9 9 9M20 13v7h-7m0 0l-9-9-9 9" />
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"> </svg>
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 4H4v7m0 0l9-9 9 9M20 13v7h-7m0 0l-9-9-9 9" /> </button>
</svg> <!-- Delete Icon -->
</button> <button (click)="deleteImage(deck, image)" class="text-red-500 hover:text-red-700" title="Bild löschen">
<!-- Delete Icon --> <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<button (click)="deleteImage(deck, image)" class="text-red-500 hover:text-red-700" title="Bild löschen"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"> </svg>
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" /> </button>
</svg> </div>
</button> </li>
</div> </ul>
</li>
</ul>
<!-- Action-Buttons --> <div class="flex space-x-2">
<div class="flex space-x-2"> <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 (click)="openUploadImageModal(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>
</ng-container>
</div> </div>
</div> </div>

View File

@ -57,9 +57,9 @@ export class DeckListComponent implements OnInit {
// Hier gehen wir davon aus, dass das Deck eine eindeutige ID hat. Falls nicht, passe den Code entsprechend an. // Hier gehen wir davon aus, dass das Deck eine eindeutige ID hat. Falls nicht, passe den Code entsprechend an.
const deckId = deck.id; // Stelle sicher, dass das Deck eine `id` hat const deckId = deck.id; // Stelle sicher, dass das Deck eine `id` hat
const imageId = image.id; // Stelle sicher, dass das Bild eine `id` hat const imageName = image.name; // Stelle sicher, dass das Bild eine `id` hat
this.deckService.deleteImage(imageId).subscribe({ this.deckService.deleteImage(imageName).subscribe({
next: () => this.loadDecks(), next: () => this.loadDecks(),
error: (err) => console.error('Fehler beim Löschen des Bildes', err) error: (err) => console.error('Fehler beim Löschen des Bildes', err)
}); });

View File

@ -12,7 +12,6 @@ export interface Deck {
export interface DeckImage { export interface DeckImage {
boxes: Box[]; boxes: Box[];
name: string; name: string;
id:string;
} }
export interface Box { export interface Box {
x1:number; x1:number;
@ -52,14 +51,13 @@ export class DeckService {
const imageMap: { [key: string]: DeckImage } = {}; const imageMap: { [key: string]: DeckImage } = {};
images.forEach(image => { images.forEach(image => {
if (!imageMap[image.id]) { if (!imageMap[image.name]) {
imageMap[image.id] = { imageMap[image.name] = {
name: image.name, name: image.name,
id:image.id,
boxes: [] boxes: []
}; };
} }
imageMap[image.id].boxes.push({ imageMap[image.name].boxes.push({
x1: image.x1, x1: image.x1,
x2: image.x2, x2: image.x2,
y1: image.y1, y1: image.y1,
@ -72,32 +70,32 @@ export class DeckService {
getDeck(deckname:string): Observable<Deck> { getDeck(deckname:string): Observable<Deck> {
return this.http.get<Deck>(`${this.apiUrl}/${deckname}/images`); return this.http.get<Deck>(`${this.apiUrl}/${deckname}/images`);
} }
// getImage(name: string): Observable<DeckImage> { getImage(name: string): Observable<DeckImage> {
// return this.http.get<BackendBox[]>(`${this.apiUrl}/image/${name}`).pipe( return this.http.get<BackendBox[]>(`${this.apiUrl}/image/${name}`).pipe(
// map(response => { map(response => {
// if (response.length === 0) { if (response.length === 0) {
// // Falls keine Daten zurückgegeben werden, ein leeres DeckImage zurückgeben // Falls keine Daten zurückgegeben werden, ein leeres DeckImage zurückgeben
// return { name: name, boxes: [] }; return { name: name, boxes: [] };
// } }
// // Extrahiere den Bildnamen aus dem ersten Element des Arrays // Extrahiere den Bildnamen aus dem ersten Element des Arrays
// const imageName = response[0].bildname; const imageName = response[0].bildname;
// // Mape die Backend-Daten auf das Box-Interface // Mape die Backend-Daten auf das Box-Interface
// const boxes: Box[] = response.map(item => ({ const boxes: Box[] = response.map(item => ({
// x1: item.x1, x1: item.x1,
// x2: item.x2, x2: item.x2,
// y1: item.y1, y1: item.y1,
// y2: item.y2 y2: item.y2
// })); }));
// return { return {
// name: imageName, name: imageName,
// boxes: boxes boxes: boxes
// }; };
// }) })
// ); );
// } }
createDeck(deckname: string): Observable<any> { createDeck(deckname: string): Observable<any> {
return this.http.post(this.apiUrl, { deckname }); return this.http.post(this.apiUrl, { deckname });
} }

View File

@ -9,7 +9,7 @@
<button <button
(click)="showText()" (click)="showText()"
class="bg-green-500 disabled:bg-green-200 text-white py-2 px-4 rounded hover:bg-green-600" class="bg-green-500 disabled:bg-green-200 text-white py-2 px-4 rounded hover:bg-green-600"
[disabled]="isShowingBox || currentBoxIndex >= boxesToReview.length" [disabled]="isShowingBox || currentBoxIndex >= boxes.length"
> >
Anzeigen Anzeigen
</button> </button>
@ -18,7 +18,7 @@
<button <button
(click)="markKnown()" (click)="markKnown()"
class="bg-blue-500 disabled:bg-blue-200 text-white py-2 px-4 rounded hover:bg-blue-600" class="bg-blue-500 disabled:bg-blue-200 text-white py-2 px-4 rounded hover:bg-blue-600"
[disabled]="!isShowingBox || currentBoxIndex >= boxesToReview.length" [disabled]="!isShowingBox || currentBoxIndex >= boxes.length"
> >
Gewusst Gewusst
</button> </button>
@ -27,19 +27,10 @@
<button <button
(click)="markUnknown()" (click)="markUnknown()"
class="bg-red-500 disabled:bg-red-200 text-white py-2 px-4 rounded hover:bg-red-600" class="bg-red-500 disabled:bg-red-200 text-white py-2 px-4 rounded hover:bg-red-600"
[disabled]="!isShowingBox || currentBoxIndex >= boxesToReview.length" [disabled]="!isShowingBox || currentBoxIndex >= boxes.length"
> >
Nicht gewusst Nicht gewusst
</button> </button>
<!-- Nächstes Bild Button -->
<button
(click)="skipToNextImage()"
class="bg-yellow-500 disabled:bg-yellow-200 text-white py-2 px-4 rounded hover:bg-yellow-600"
[disabled]="currentImageIndex >= deck.images.length"
>
Nächstes Bild
</button>
</div> </div>
<p class="mt-2">{{ progress }}</p> <p class="mt-2">{{ progress }}</p>

View File

@ -15,13 +15,12 @@ export class TrainingComponent implements OnInit {
@Input() deck!: Deck; @Input() deck!: Deck;
@Output() close = new EventEmitter<void>(); @Output() close = new EventEmitter<void>();
@ViewChild('canvas',{static : false}) canvasRef!: ElementRef<HTMLCanvasElement>; @ViewChild('canvas') canvasRef!: ElementRef<HTMLCanvasElement>;
currentImageIndex: number = 0; currentImageIndex: number = 0;
currentImageData: DeckImage | null = null; currentImageData: DeckImage | null = null;
// Ändere currentBoxIndex zu boxesToReview als Array
currentBoxIndex: number = 0; currentBoxIndex: number = 0;
boxesToReview: Box[] = []; boxes: Box[] = [];
boxRevealed: boolean[] = []; boxRevealed: boolean[] = [];
knownCount: number = 0; knownCount: number = 0;
@ -33,9 +32,6 @@ export class TrainingComponent implements OnInit {
constructor(private deckService: DeckService) { } constructor(private deckService: DeckService) { }
ngOnInit(): void { ngOnInit(): void {
}
ngAfterViewInit(){
if (this.deck && this.deck.images.length > 0) { if (this.deck && this.deck.images.length > 0) {
this.loadImage(this.currentImageIndex); this.loadImage(this.currentImageIndex);
} else { } else {
@ -50,12 +46,22 @@ export class TrainingComponent implements OnInit {
return; return;
} }
this.currentImageData = this.deck.images[imageIndex]; const imageName = this.deck.images[imageIndex].name;
// Initialisiere boxesToReview mit allen Boxen, gemischt this.deckService.getImage(imageName).subscribe({
this.boxesToReview = this.shuffleArray([...this.currentImageData.boxes]); next: (imageData: DeckImage) => {
this.boxRevealed = new Array(this.boxesToReview.length).fill(false); this.currentImageData = imageData;
this.isShowingBox = false; this.boxes = imageData.boxes;
this.drawCanvas(); this.boxRevealed = new Array(this.boxes.length).fill(false);
this.currentBoxIndex = 0;
this.isShowingBox = false;
this.drawCanvas();
},
error: (err) => {
console.error('Fehler beim Laden des Bildes:', err);
alert('Fehler beim Laden des Bildes.');
this.close.emit();
}
});
} }
drawCanvas(): void { drawCanvas(): void {
@ -64,7 +70,7 @@ export class TrainingComponent implements OnInit {
if (!ctx || !this.currentImageData) return; if (!ctx || !this.currentImageData) return;
const img = new Image(); const img = new Image();
img.src = `/api/debug_image/${this.currentImageData.id}`; img.src = `/api/debug_image/${this.currentImageData.name}`;
img.onload = () => { img.onload = () => {
// Set canvas size to image size // Set canvas size to image size
canvas.width = img.width; canvas.width = img.width;
@ -75,7 +81,7 @@ export class TrainingComponent implements OnInit {
ctx.drawImage(img, 0, 0, canvas.width, canvas.height); ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
// Draw boxes // Draw boxes
this.boxesToReview.forEach((box, index) => { this.boxes.forEach((box, index) => {
if (this.boxRevealed[index]) { if (this.boxRevealed[index]) {
// Box ist bereits enthüllt, nichts zeichnen // Box ist bereits enthüllt, nichts zeichnen
return; return;
@ -89,7 +95,6 @@ export class TrainingComponent implements OnInit {
ctx.strokeStyle = 'black'; ctx.strokeStyle = 'black';
ctx.stroke(); ctx.stroke();
}); });
}; };
img.onerror = () => { img.onerror = () => {
@ -99,34 +104,8 @@ export class TrainingComponent implements OnInit {
}; };
} }
// Utility-Funktion zum Mischen eines Arrays
shuffleArray<T>(array: T[]): T[] {
let currentIndex = array.length, randomIndex;
// While there remain elements to shuffle.
while (currentIndex !== 0) {
// Pick a remaining element.
randomIndex = Math.floor(Math.random() * currentIndex);
currentIndex--;
// And swap it with the current element.
[array[currentIndex], array[randomIndex]] = [
array[randomIndex], array[currentIndex]];
}
return array;
}
get currentBox(): Box | null {
if (this.currentBoxIndex < this.boxesToReview.length) {
return this.boxesToReview[this.currentBoxIndex];
}
return null;
}
showText(): void { showText(): void {
if (this.currentBoxIndex >= this.boxesToReview.length) return; if (this.currentBoxIndex >= this.boxes.length) return;
this.boxRevealed[this.currentBoxIndex] = true; this.boxRevealed[this.currentBoxIndex] = true;
this.isShowingBox = true; this.isShowingBox = true;
this.drawCanvas(); this.drawCanvas();
@ -134,35 +113,25 @@ export class TrainingComponent implements OnInit {
markKnown(): void { markKnown(): void {
this.knownCount++; this.knownCount++;
// Entferne die aktuelle Box aus boxesToReview, da sie bekannt ist
this.boxesToReview.splice(this.currentBoxIndex, 1);
this.boxRevealed.splice(this.currentBoxIndex, 1);
this.nextBox(); this.nextBox();
} }
markUnknown(): void { markUnknown(): void {
this.unknownCount++; this.unknownCount++;
// Behalte die aktuelle Box in der Liste und verschiebe sie an eine zufällige Position am Ende
const box = this.boxesToReview.splice(this.currentBoxIndex, 1)[0];
this.boxesToReview.push(box);
this.boxRevealed.splice(this.currentBoxIndex, 1);
this.nextBox(); this.nextBox();
} }
nextBox(): void { nextBox(): void {
this.currentBoxIndex++;
this.isShowingBox = false; this.isShowingBox = false;
if (this.boxesToReview.length === 0) { if (this.currentBoxIndex >= this.boxes.length) {
// Alle Boxen für dieses Bild sind bearbeitet // Alle Boxen für dieses Bild sind bearbeitet
this.nextImage(); this.nextImage();
return; } else {
// Aktualisiere die Farben der Boxen
this.drawCanvas();
} }
if (this.currentBoxIndex >= this.boxesToReview.length) {
this.currentBoxIndex = 0;
}
this.drawCanvas();
} }
nextImage(): void { nextImage(): void {
@ -170,15 +139,6 @@ export class TrainingComponent implements OnInit {
this.loadImage(this.currentImageIndex); this.loadImage(this.currentImageIndex);
} }
skipToNextImage(): void {
if (this.currentImageIndex < this.deck.images.length - 1) {
this.currentImageIndex++;
this.loadImage(this.currentImageIndex);
} else {
alert('Dies ist das letzte Bild im Deck.');
}
}
endTraining(): void { endTraining(): void {
this.isTrainingFinished = true; this.isTrainingFinished = true;
alert(`Training beendet!\nGewusst: ${this.knownCount}\nNicht gewusst: ${this.unknownCount}`); alert(`Training beendet!\nGewusst: ${this.knownCount}\nNicht gewusst: ${this.unknownCount}`);

View File

@ -107,6 +107,7 @@ export class UploadImageModalComponent implements AfterViewInit, OnDestroy {
async onFileChange(event: any): Promise<void> { async onFileChange(event: any): Promise<void> {
const file: File = event.target.files[0]; const file: File = event.target.files[0];
if (!file) return; if (!file) return;
this.imageFile = file; this.imageFile = file;
this.processingStatus = 'Verarbeitung läuft...'; this.processingStatus = 'Verarbeitung läuft...';
this.detectedText = ''; this.detectedText = '';
@ -316,12 +317,12 @@ export class UploadImageModalComponent implements AfterViewInit, OnDestroy {
// Zum Beispiel: // Zum Beispiel:
const data = { const data = {
deckname: this.deckName, deckname: this.deckName,
bildname: this.imageFile?.name, image: this.imagename,
bildid: this.imagename,
boxes: this.boxes, boxes: this.boxes,
}; };
this.deckService.saveImageData(data).subscribe({ this.deckService.saveImageData(data).subscribe({
next: () => { next: () => {
alert('Bild gespeichert!');
this.imageUploaded.emit(); this.imageUploaded.emit();
this.closeModal(); this.closeModal();
}, },
@ -331,6 +332,7 @@ export class UploadImageModalComponent implements AfterViewInit, OnDestroy {
} }
}); });
// Temporäres Beispiel: // Temporäres Beispiel:
alert('Save button clicked');
this.imageUploaded.emit(); this.imageUploaded.emit();
this.closeModal(); this.closeModal();
} }