Compare commits
2 Commits
ab021cb4c2
...
b43044a0dd
| Author | SHA1 | Date |
|---|---|---|
|
|
b43044a0dd | |
|
|
c116b56934 |
|
|
@ -12,19 +12,28 @@
|
||||||
<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 -->
|
||||||
|
<ng-container *ngIf="!selectedDeck">
|
||||||
<!-- Liste der Bilder mit Anzahl der Boxen und Icons -->
|
<!-- Liste der Bilder mit Anzahl der Boxen und Icons -->
|
||||||
<ul class="mb-4">
|
<ul class="mb-4">
|
||||||
<li *ngFor="let image of deck.images" class="flex justify-between items-center py-2 border-b last:border-b-0">
|
<li *ngFor="let image of deck.images" class="flex justify-between items-center py-2 border-b last:border-b-0">
|
||||||
<div>
|
<div>
|
||||||
<span class="font-medium">{{ image.name }}</span>
|
<span class="font-medium">{{ image.name }}</span>
|
||||||
<span class="text-gray-600"> ({{ image.boxes?.length }} Boxen)</span>
|
<span class="text-gray-600">({{ image.boxes.length }} Boxen)</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex space-x-2">
|
<div class="flex space-x-2">
|
||||||
<!-- Edit Icon -->
|
<!-- Edit Icon -->
|
||||||
|
|
@ -43,6 +52,7 @@
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</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
|
||||||
|
|
@ -51,6 +61,7 @@
|
||||||
Bild hinzufügen
|
Bild hinzufügen
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
</ng-container>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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 imageName = image.name; // Stelle sicher, dass das Bild eine `id` hat
|
const imageId = image.id; // Stelle sicher, dass das Bild eine `id` hat
|
||||||
|
|
||||||
this.deckService.deleteImage(imageName).subscribe({
|
this.deckService.deleteImage(imageId).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)
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ 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;
|
||||||
|
|
@ -51,13 +52,14 @@ export class DeckService {
|
||||||
const imageMap: { [key: string]: DeckImage } = {};
|
const imageMap: { [key: string]: DeckImage } = {};
|
||||||
|
|
||||||
images.forEach(image => {
|
images.forEach(image => {
|
||||||
if (!imageMap[image.name]) {
|
if (!imageMap[image.id]) {
|
||||||
imageMap[image.name] = {
|
imageMap[image.id] = {
|
||||||
name: image.name,
|
name: image.name,
|
||||||
|
id:image.id,
|
||||||
boxes: []
|
boxes: []
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
imageMap[image.name].boxes.push({
|
imageMap[image.id].boxes.push({
|
||||||
x1: image.x1,
|
x1: image.x1,
|
||||||
x2: image.x2,
|
x2: image.x2,
|
||||||
y1: image.y1,
|
y1: image.y1,
|
||||||
|
|
@ -70,32 +72,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 });
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 >= boxes.length"
|
[disabled]="isShowingBox || currentBoxIndex >= boxesToReview.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 >= boxes.length"
|
[disabled]="!isShowingBox || currentBoxIndex >= boxesToReview.length"
|
||||||
>
|
>
|
||||||
Gewusst
|
Gewusst
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -27,10 +27,19 @@
|
||||||
<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 >= boxes.length"
|
[disabled]="!isShowingBox || currentBoxIndex >= boxesToReview.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>
|
||||||
|
|
|
||||||
|
|
@ -15,12 +15,13 @@ export class TrainingComponent implements OnInit {
|
||||||
@Input() deck!: Deck;
|
@Input() deck!: Deck;
|
||||||
@Output() close = new EventEmitter<void>();
|
@Output() close = new EventEmitter<void>();
|
||||||
|
|
||||||
@ViewChild('canvas') canvasRef!: ElementRef<HTMLCanvasElement>;
|
@ViewChild('canvas',{static : false}) 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;
|
||||||
boxes: Box[] = [];
|
boxesToReview: Box[] = [];
|
||||||
boxRevealed: boolean[] = [];
|
boxRevealed: boolean[] = [];
|
||||||
|
|
||||||
knownCount: number = 0;
|
knownCount: number = 0;
|
||||||
|
|
@ -32,6 +33,9 @@ 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 {
|
||||||
|
|
@ -46,22 +50,12 @@ export class TrainingComponent implements OnInit {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const imageName = this.deck.images[imageIndex].name;
|
this.currentImageData = this.deck.images[imageIndex];
|
||||||
this.deckService.getImage(imageName).subscribe({
|
// Initialisiere boxesToReview mit allen Boxen, gemischt
|
||||||
next: (imageData: DeckImage) => {
|
this.boxesToReview = this.shuffleArray([...this.currentImageData.boxes]);
|
||||||
this.currentImageData = imageData;
|
this.boxRevealed = new Array(this.boxesToReview.length).fill(false);
|
||||||
this.boxes = imageData.boxes;
|
|
||||||
this.boxRevealed = new Array(this.boxes.length).fill(false);
|
|
||||||
this.currentBoxIndex = 0;
|
|
||||||
this.isShowingBox = false;
|
this.isShowingBox = false;
|
||||||
this.drawCanvas();
|
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 {
|
||||||
|
|
@ -70,7 +64,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.name}`;
|
img.src = `/api/debug_image/${this.currentImageData.id}`;
|
||||||
img.onload = () => {
|
img.onload = () => {
|
||||||
// Set canvas size to image size
|
// Set canvas size to image size
|
||||||
canvas.width = img.width;
|
canvas.width = img.width;
|
||||||
|
|
@ -81,7 +75,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.boxes.forEach((box, index) => {
|
this.boxesToReview.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;
|
||||||
|
|
@ -95,6 +89,7 @@ export class TrainingComponent implements OnInit {
|
||||||
ctx.strokeStyle = 'black';
|
ctx.strokeStyle = 'black';
|
||||||
ctx.stroke();
|
ctx.stroke();
|
||||||
});
|
});
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
img.onerror = () => {
|
img.onerror = () => {
|
||||||
|
|
@ -104,8 +99,34 @@ 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.boxes.length) return;
|
if (this.currentBoxIndex >= this.boxesToReview.length) return;
|
||||||
this.boxRevealed[this.currentBoxIndex] = true;
|
this.boxRevealed[this.currentBoxIndex] = true;
|
||||||
this.isShowingBox = true;
|
this.isShowingBox = true;
|
||||||
this.drawCanvas();
|
this.drawCanvas();
|
||||||
|
|
@ -113,25 +134,35 @@ 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.currentBoxIndex >= this.boxes.length) {
|
if (this.boxesToReview.length === 0) {
|
||||||
// Alle Boxen für dieses Bild sind bearbeitet
|
// Alle Boxen für dieses Bild sind bearbeitet
|
||||||
this.nextImage();
|
this.nextImage();
|
||||||
} else {
|
return;
|
||||||
// Aktualisiere die Farben der Boxen
|
|
||||||
this.drawCanvas();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.currentBoxIndex >= this.boxesToReview.length) {
|
||||||
|
this.currentBoxIndex = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.drawCanvas();
|
||||||
}
|
}
|
||||||
|
|
||||||
nextImage(): void {
|
nextImage(): void {
|
||||||
|
|
@ -139,6 +170,15 @@ 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}`);
|
||||||
|
|
|
||||||
|
|
@ -107,7 +107,6 @@ 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 = '';
|
||||||
|
|
@ -317,12 +316,12 @@ export class UploadImageModalComponent implements AfterViewInit, OnDestroy {
|
||||||
// Zum Beispiel:
|
// Zum Beispiel:
|
||||||
const data = {
|
const data = {
|
||||||
deckname: this.deckName,
|
deckname: this.deckName,
|
||||||
image: this.imagename,
|
bildname: this.imageFile?.name,
|
||||||
|
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();
|
||||||
},
|
},
|
||||||
|
|
@ -332,7 +331,6 @@ 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();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue