+
-
+
+
+
diff --git a/src/app/deck-list.component.ts b/src/app/deck-list.component.ts
index 67d12b6..9921f69 100644
--- a/src/app/deck-list.component.ts
+++ b/src/app/deck-list.component.ts
@@ -1,11 +1,13 @@
import { CommonModule } from '@angular/common';
import { HttpClient } from '@angular/common/http';
import { ChangeDetectorRef, Component, ElementRef, OnInit, ViewChild } from '@angular/core';
+import { FormsModule } from '@angular/forms';
import { firstValueFrom } from 'rxjs';
import { CreateDeckModalComponent } from './create-deck-modal/create-deck-modal.component';
import { Box, Deck, DeckImage, DeckService, OcrResult } from './deck.service';
import { EditImageModalComponent } from './edit-image-modal/edit-image-modal.component';
import { MoveImageModalComponent } from './move-image-modal/move-image-modal.component';
+import { PopoverService } from './services/popover.service';
import { TrainingComponent } from './training/training.component';
@Component({
@@ -28,14 +30,13 @@ import { TrainingComponent } from './training/training.component';
TrainingComponent,
EditImageModalComponent,
MoveImageModalComponent, // Adding the new component
+ FormsModule,
],
})
export class DeckListComponent implements OnInit {
decks: Deck[] = [];
trainingsDeck: Deck | null = null;
activeDeck: Deck | null = null;
- private deckToDelete: string | null = null;
- deckToRename: Deck | null = null;
loading: boolean = false;
@ViewChild(CreateDeckModalComponent)
@@ -54,7 +55,7 @@ export class DeckListComponent implements OnInit {
// State for moving images
imageToMove: { image: DeckImage; sourceDeck: Deck } | null = null;
- constructor(private deckService: DeckService, private cdr: ChangeDetectorRef, private http: HttpClient) {}
+ constructor(private deckService: DeckService, private cdr: ChangeDetectorRef, private http: HttpClient, private popoverService: PopoverService) {}
ngOnInit(): void {
this.loadExpandedDecks();
@@ -84,11 +85,12 @@ export class DeckListComponent implements OnInit {
// Method to open the delete confirmation popover
openDeletePopover(deckName: string): void {
- this.deckToDelete = deckName;
- const deletePopover = document.getElementById('deletePopover');
- if (deletePopover) {
- deletePopover.showPopover();
- }
+ this.popoverService.show({
+ title: 'Delete Deck',
+ message: 'Are you sure you want to delete this deck?',
+ confirmText: 'Delete',
+ onConfirm: () => this.confirmDelete(deckName),
+ });
}
// Method to check if a deck is active
@@ -97,113 +99,56 @@ export class DeckListComponent implements OnInit {
}
// Method to confirm the deletion of a deck
- confirmDelete(): void {
- if (this.deckToDelete) {
- this.deckService.deleteDeck(this.deckToDelete).subscribe({
- next: () => {
- this.loadDecks();
- this.closeDeletePopover();
- this.activeDeck = this.decks.length > 0 ? this.decks[0] : null;
- },
- error: err => console.error('Error deleting deck', err),
- });
- }
- }
-
- // Method to cancel the deletion of a deck
- cancelDelete(): void {
- this.closeDeletePopover();
- }
-
- // Method to close the delete confirmation popover
- closeDeletePopover(): void {
- const deletePopover = document.getElementById('deletePopover');
- if (deletePopover) {
- deletePopover.hidePopover();
- }
- this.deckToDelete = null;
- }
-
- // Method to open the rename popover
- openRenamePopover(deck: Deck): void {
- this.deckToRename = deck;
- const renamePopover = document.getElementById('renamePopover');
- if (renamePopover) {
- renamePopover.showPopover();
- // Set the input value to the current deck name and select it
- setTimeout(() => {
- const newDeckNameInput = document.getElementById('newDeckNameInput') as HTMLInputElement;
- if (newDeckNameInput) {
- newDeckNameInput.value = deck.name;
- newDeckNameInput.select();
- }
- }, 0);
- }
- }
-
- // Method to confirm the renaming of a deck
- confirmRename(): void {
- const newDeckNameInput = document.getElementById('newDeckNameInput') as HTMLInputElement;
- if (newDeckNameInput && this.deckToRename) {
- const newDeckName = newDeckNameInput.value.trim();
- if (newDeckName && newDeckName !== this.deckToRename.name) {
- this.deckService.renameDeck(this.deckToRename.name, newDeckName).subscribe({
- next: () => {
- // Aktualisiere den Namen im activeDeck, falls dieser der umbenannte Deck ist
- if (this.activeDeck && this.activeDeck.name === this.deckToRename?.name) {
- this.activeDeck.name = newDeckName;
- }
- this.loadDecks(); // Lade die Decks neu, um die Liste zu aktualisieren
- this.closeRenamePopover();
- },
- error: err => console.error('Error renaming deck', err),
- });
- }
- }
- }
-
- // Method to cancel the renaming of a deck
- cancelRename(): void {
- this.closeRenamePopover();
- }
-
- // Method to close the rename popover
- closeRenamePopover(): void {
- const renamePopover = document.getElementById('renamePopover');
- if (renamePopover) {
- renamePopover.hidePopover();
- }
- this.deckToRename = null;
- }
-
- // Method to delete a deck
- deleteDeck(deckName: string): void {
- if (!confirm(`Are you sure you want to delete the deck "${deckName}"?`)) {
- return;
- }
+ confirmDelete(deckName: string): void {
this.deckService.deleteDeck(deckName).subscribe({
- next: () => this.loadDecks(),
+ next: () => {
+ this.loadDecks();
+ this.activeDeck = this.decks.length > 0 ? this.decks[0] : null;
+ },
error: err => console.error('Error deleting deck', err),
});
}
- // Method to rename a deck
- renameDeck(deck: Deck): void {
- const newDeckName = prompt('Enter the new name for the deck:', deck.name);
- if (newDeckName && newDeckName !== deck.name) {
- this.deckService.renameDeck(deck.name, newDeckName).subscribe({
- next: () => this.loadDecks(),
+ // Method to open the rename popover
+ openRenamePopover(deck: Deck): void {
+ this.popoverService.showWithInput({
+ title: 'Rename Deck',
+ message: 'Enter the new name for the deck:',
+ confirmText: 'Rename',
+ inputValue: deck.name,
+ onConfirm: (inputValue?: string) => this.confirmRename(deck, inputValue),
+ });
+ }
+
+ // Method to confirm the renaming of a deck
+ confirmRename(deck: Deck, newName?: string): void {
+ if (newName && newName.trim() !== '' && newName !== deck.name) {
+ this.deckService.renameDeck(deck.name, newName).subscribe({
+ next: () => {
+ if (this.activeDeck?.name === deck.name) {
+ this.activeDeck.name = newName;
+ }
+ this.loadDecks();
+ },
error: err => console.error('Error renaming deck', err),
});
+ } else {
+ // Optional: Handle ungültigen neuen Namen
+ console.warn('Invalid new deck name.');
}
}
- // Method to delete an image from a deck
+ // Delete-Image Methoden ersetzen
deleteImage(deck: Deck, image: DeckImage): void {
- if (!confirm(`Are you sure you want to delete the image "${image.name}"?`)) {
- return;
- }
+ this.popoverService.show({
+ title: 'Delete Image',
+ message: `Are you sure you want to delete the image ${image.name}?`,
+ confirmText: 'Delete',
+ onConfirm: () => this.confirmImageDelete(deck, image),
+ });
+ }
+ confirmImageDelete(deck: Deck, image: DeckImage): void {
const imageId = image.id;
this.deckService.deleteImage(imageId).subscribe({
@@ -211,7 +156,7 @@ export class DeckListComponent implements OnInit {
this.loadDecks();
if (this.activeDeck) {
this.activeDeck.images = this.activeDeck.images.filter(img => img.id !== imageId);
- this.cdr.detectChanges(); // Erzwingt einen UI-Update
+ this.cdr.detectChanges();
}
},
error: err => console.error('Error deleting image', err),
@@ -416,6 +361,6 @@ export class DeckListComponent implements OnInit {
// Methode zur Berechnung der Anzahl der zu bearbeitenden Wörter
getWordsToReview(deck: Deck): number {
const nextTraining = this.getNextTrainingDate(deck);
- return deck.images.flatMap(image => image.boxes.filter(box => box.due && new Date(box.due * 86400000) <= new Date(nextTraining))).length;
+ return deck.images.flatMap(image => image.boxes.filter(box => (box.due && new Date(box.due * 86400000) <= new Date(nextTraining)) || !box.due)).length;
}
}
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 321dbb3..adc0ca4 100644
--- a/src/app/edit-image-modal/edit-image-modal.component.ts
+++ b/src/app/edit-image-modal/edit-image-modal.component.ts
@@ -1,22 +1,23 @@
// src/app/edit-image-modal.component.ts
-import { Component, Input, Output, EventEmitter, AfterViewInit, ViewChild, ElementRef, OnDestroy } from '@angular/core';
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, OcrResult } from '../deck.service';
+import { DeckImage, DeckService } from '../deck.service';
+import { PopoverService } from '../services/popover.service';
@Component({
selector: 'app-edit-image-modal',
templateUrl: './edit-image-modal.component.html',
standalone: true,
- imports: [CommonModule]
+ 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 };
+ @Input() imageData: { imageSrc: string | ArrayBuffer | null; deckImage: DeckImage | null } = { imageSrc: null, deckImage: null };
@Output() imageSaved = new EventEmitter();
@Output() closed = new EventEmitter();
@ViewChild('editImageModal') modalElement!: ElementRef;
@@ -32,13 +33,13 @@ export class EditImageModalComponent implements AfterViewInit, OnDestroy {
private keyDownHandler!: (e: KeyboardEvent) => void;
modal: any;
- constructor(private deckService: DeckService) { }
+ constructor(private deckService: DeckService, private popoverService: PopoverService) {}
async ngAfterViewInit() {
this.modal = new Modal(this.modalElement.nativeElement, {
onHide: () => {
this.closed.emit();
- }
+ },
});
this.maxCanvasWidth = window.innerWidth * 0.6;
@@ -75,14 +76,14 @@ export class EditImageModalComponent implements AfterViewInit, OnDestroy {
return new Promise((resolve, reject) => {
fabric.Image.fromURL(
url,
- (img) => {
+ img => {
resolve(img);
},
{
crossOrigin: 'anonymous',
originX: 'left',
originY: 'top',
- }
+ },
);
});
}
@@ -122,7 +123,6 @@ export class EditImageModalComponent implements AfterViewInit, OnDestroy {
// Add boxes
this.imageData.deckImage?.boxes.forEach(box => {
-
const rect = new fabric.Rect({
left: box.x1 * scaleFactor,
top: box.y1 * scaleFactor,
@@ -157,7 +157,6 @@ export class EditImageModalComponent implements AfterViewInit, OnDestroy {
this.updateBoxCoordinates();
// this.detectedText = ocrResults.map(result => result.text).join('\n');
-
} catch (error) {
console.error('Error processing image:', error);
}
@@ -198,7 +197,7 @@ export class EditImageModalComponent implements AfterViewInit, OnDestroy {
x1: Math.round(x1),
x2: Math.round(x2),
y1: Math.round(y1),
- y2: Math.round(y2)
+ y2: Math.round(y2),
});
});
@@ -265,11 +264,14 @@ export class EditImageModalComponent implements AfterViewInit, OnDestroy {
this.imageSaved.emit();
this.closeModal();
},
- error: (err) => {
+ error: err => {
console.error('Error saving image:', err);
- alert('Error saving image.');
+ this.popoverService.show({
+ title: 'Error',
+ message: 'Error saving image.',
+ });
this.closeModal();
- }
+ },
});
}
-}
\ 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
index 2af29f1..c7ea156 100644
--- a/src/app/move-image-modal/move-image-modal.component.ts
+++ b/src/app/move-image-modal/move-image-modal.component.ts
@@ -1,13 +1,14 @@
-import { Component, Input, Output, EventEmitter } from '@angular/core';
import { CommonModule } from '@angular/common';
+import { Component, EventEmitter, Input, Output } from '@angular/core';
import { FormsModule } from '@angular/forms';
-import { DeckImage, Deck, DeckService } from '../deck.service';
+import { Deck, DeckImage, DeckService } from '../deck.service';
+import { PopoverService } from '../services/popover.service';
@Component({
selector: 'app-move-image-modal',
templateUrl: './move-image-modal.component.html',
standalone: true,
- imports: [CommonModule, FormsModule]
+ imports: [CommonModule, FormsModule],
})
export class MoveImageModalComponent {
@Input() image!: DeckImage;
@@ -18,7 +19,7 @@ export class MoveImageModalComponent {
selectedDeckId: number | null = null;
- constructor(private deckService: DeckService) { }
+ constructor(private deckService: DeckService, private popoverService: PopoverService) {}
moveImage(): void {
if (this.selectedDeckId === null) {
@@ -30,14 +31,17 @@ export class MoveImageModalComponent {
this.moveCompleted.emit();
this.close();
},
- error: (err) => {
+ error: err => {
console.error('Error moving image:', err);
- alert('Error moving image.');
- }
+ this.popoverService.show({
+ title: 'Error',
+ message: 'Error moving image.',
+ });
+ },
});
}
close(): void {
this.closed.emit();
}
-}
\ No newline at end of file
+}
diff --git a/src/app/services/popover.service.ts b/src/app/services/popover.service.ts
new file mode 100644
index 0000000..49700a7
--- /dev/null
+++ b/src/app/services/popover.service.ts
@@ -0,0 +1,34 @@
+// popover.service.ts
+import { Injectable } from '@angular/core';
+import { Subject } from 'rxjs';
+
+@Injectable({ providedIn: 'root' })
+export class PopoverService {
+ private showPopoverSource = new Subject<{
+ title: string;
+ message: string;
+ showInput: boolean;
+ inputValue: string;
+ confirmText: string;
+ onConfirm: () => void;
+ onCancel?: () => void;
+ }>();
+
+ popoverState$ = this.showPopoverSource.asObservable();
+
+ show(options: { title: string; message: string; confirmText?: string; onConfirm?: (inputValue?: string) => void; onCancel?: () => void }) {
+ this.showPopoverSource.next({
+ showInput: false,
+ inputValue: null,
+ confirmText: 'Ok',
+ onConfirm: (inputValue?: string) => {},
+ ...options,
+ });
+ }
+ showWithInput(options: { title: string; message: string; confirmText: string; inputValue: string; onConfirm: (inputValue?: string) => void; onCancel?: () => void }) {
+ this.showPopoverSource.next({
+ showInput: true,
+ ...options,
+ });
+ }
+}
diff --git a/src/app/training/training.component.ts b/src/app/training/training.component.ts
index 0888223..ced51f8 100644
--- a/src/app/training/training.component.ts
+++ b/src/app/training/training.component.ts
@@ -2,25 +2,26 @@ import { CommonModule } from '@angular/common';
import { Component, ElementRef, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core';
import { lastValueFrom } from 'rxjs';
import { Box, Deck, DeckImage, DeckService } from '../deck.service';
+import { PopoverService } from '../services/popover.service';
const LEARNING_STEPS = {
- AGAIN: 1, // 1 minute
- GOOD: 10, // 10 minutes
- GRADUATION: 1440 // 1 day (in minutes)
+ AGAIN: 1, // 1 minute
+ GOOD: 10, // 10 minutes
+ GRADUATION: 1440, // 1 day (in minutes)
};
const FACTOR_CHANGES = {
- AGAIN: 0.85, // Reduce factor by 15%
- EASY: 1.15, // Increase factor by 15%
- MIN: 1.3, // Minimum factor allowed
- MAX: 2.9 // Maximum factor allowed
+ AGAIN: 0.85, // Reduce factor by 15%
+ EASY: 1.15, // Increase factor by 15%
+ MIN: 1.3, // Minimum factor allowed
+ MAX: 2.9, // Maximum factor allowed
};
const EASY_INTERVAL = 4 * 1440; // 4 days in minutes
@Component({
selector: 'app-training',
templateUrl: './training.component.html',
standalone: true,
- imports: [CommonModule]
+ imports: [CommonModule],
})
export class TrainingComponent implements OnInit {
@Input() deck!: Deck;
@@ -37,7 +38,7 @@ export class TrainingComponent implements OnInit {
isShowingBox: boolean = false;
isTrainingFinished: boolean = false;
- constructor(private deckService: DeckService) { }
+ constructor(private deckService: DeckService, private popoverService: PopoverService) {}
ngOnInit(): void {
// Initialization was done in ngAfterViewInit
@@ -48,7 +49,10 @@ export class TrainingComponent implements OnInit {
if (this.deck && this.deck.images.length > 0) {
this.loadImage(this.currentImageIndex);
} else {
- alert('No deck or images available.');
+ this.popoverService.show({
+ title: 'Information',
+ message: 'No deck or images available.',
+ });
this.close.emit();
}
}
@@ -135,7 +139,7 @@ export class TrainingComponent implements OnInit {
this.boxesToReview.forEach((box, index) => {
ctx.beginPath();
ctx.rect(box.x1, box.y1, box.x2 - box.x1, box.y2 - box.y1);
- if (this.currentBoxIndex === index && this.isShowingBox || (box.due && box.due - this.getTodayInDays() > 0)) {
+ if ((this.currentBoxIndex === index && this.isShowingBox) || (box.due && box.due - this.getTodayInDays() > 0)) {
// Box is currently revealed, no overlay
return;
} else if (this.currentBoxIndex === index && !this.isShowingBox) {
@@ -154,7 +158,10 @@ export class TrainingComponent implements OnInit {
img.onerror = () => {
console.error('Error loading image for canvas.');
- alert('Error loading image for canvas.');
+ this.popoverService.show({
+ title: 'Error',
+ message: 'Error loading image for canvas.',
+ });
this.close.emit();
};
}
@@ -165,7 +172,8 @@ export class TrainingComponent implements OnInit {
* @returns The shuffled array
*/
shuffleArray(array: T[]): T[] {
- let currentIndex = array.length, randomIndex;
+ let currentIndex = array.length,
+ randomIndex;
// While there are elements to shuffle
while (currentIndex !== 0) {
@@ -174,8 +182,7 @@ export class TrainingComponent implements OnInit {
currentIndex--;
// Swap it with the current element
- [array[currentIndex], array[randomIndex]] = [
- array[randomIndex], array[currentIndex]];
+ [array[currentIndex], array[randomIndex]] = [array[randomIndex], array[currentIndex]];
}
return array;
@@ -257,7 +264,10 @@ export class TrainingComponent implements OnInit {
await lastValueFrom(this.deckService.updateBox(box));
} catch (error) {
console.error('Error updating box:', error);
- alert('Error updating box.');
+ this.popoverService.show({
+ title: 'Error',
+ message: 'Error updating box.',
+ });
}
}
@@ -267,33 +277,36 @@ export class TrainingComponent implements OnInit {
* @param action The action ('again', 'good', 'easy')
* @returns An object with the new values for ivl, factor, reps, and lapses
*/
- calculateNewInterval(box: Box, action: 'again' | 'good' | 'easy'): {
- newIvl: number,
- newFactor: number,
- newReps: number,
- newLapses: number,
- newIsGraduated: boolean
+ calculateNewInterval(
+ box: Box,
+ action: 'again' | 'good' | 'easy',
+ ): {
+ newIvl: number;
+ newFactor: number;
+ newReps: number;
+ newLapses: number;
+ newIsGraduated: boolean;
} {
const LEARNING_STEPS = {
- AGAIN: 1, // 1 minute
- GOOD: 10, // 10 minutes
- GRADUATION: 1440 // 1 day (1440 minutes)
+ AGAIN: 1, // 1 minute
+ GOOD: 10, // 10 minutes
+ GRADUATION: 1440, // 1 day (1440 minutes)
};
-
- const EASY_BONUS = 1.3; // Zusätzlicher Easy-Multiplikator
-/* const FACTOR_CHANGES = {
+
+ const EASY_BONUS = 1.3; // Zusätzlicher Easy-Multiplikator
+ /* const FACTOR_CHANGES = {
MIN: 1.3,
MAX: 2.5,
AGAIN: 0.85,
EASY: 1.15
}; */
-
+
let newIvl = box.ivl || 0;
let newFactor = box.factor || 2.5;
let newReps = box.reps || 0;
let newLapses = box.lapses || 0;
let newIsGraduated = box.isGraduated || false;
-
+
if (action === 'again') {
newLapses++;
newReps = 0;
@@ -302,13 +315,13 @@ export class TrainingComponent implements OnInit {
newFactor = Math.max(FACTOR_CHANGES.MIN, newFactor * FACTOR_CHANGES.AGAIN);
return { newIvl, newFactor, newReps, newLapses, newIsGraduated };
}
-
+
if (action === 'easy') {
newReps++;
-
+
// Faktor zuerst aktualisieren
const updatedFactor = Math.min(FACTOR_CHANGES.MAX, newFactor * FACTOR_CHANGES.EASY);
-
+
if (!newIsGraduated) {
// Direkte Graduierung mit Easy
newIsGraduated = true;
@@ -317,14 +330,14 @@ export class TrainingComponent implements OnInit {
// Anki-Formel für Easy in der Review-Phase
newIvl = Math.round((newIvl + LEARNING_STEPS.GRADUATION / 2) * updatedFactor * EASY_BONUS);
}
-
+
newFactor = updatedFactor;
return { newIvl, newFactor, newReps, newLapses, newIsGraduated };
}
-
+
// Handle 'good' action
newReps++;
-
+
if (!newIsGraduated) {
if (newReps === 1) {
newIvl = LEARNING_STEPS.GOOD; // 10 Minuten
@@ -336,7 +349,7 @@ export class TrainingComponent implements OnInit {
// Standard-SR-Formel
newIvl = Math.round(newIvl * newFactor);
}
-
+
return { newIvl, newFactor, newReps, newLapses, newIsGraduated };
}
@@ -347,8 +360,7 @@ export class TrainingComponent implements OnInit {
* @returns The next interval as a string (e.g., "10 min", "2 d")
*/
getNextInterval(box: Box | null, action: 'again' | 'good' | 'easy'): string {
- if (!box)
- return '';
+ if (!box) return '';
const { newIvl } = this.calculateNewInterval(box, action);
@@ -407,7 +419,10 @@ export class TrainingComponent implements OnInit {
this.currentImageIndex++;
this.loadImage(this.currentImageIndex);
} else {
- alert('This is the last image in the deck.');
+ this.popoverService.show({
+ title: 'Information',
+ message: 'This is the last image in the deck.',
+ });
}
}
@@ -416,7 +431,10 @@ export class TrainingComponent implements OnInit {
*/
endTraining(): void {
this.isTrainingFinished = true;
- alert(`Training completed!`);
+ this.popoverService.show({
+ title: 'Information',
+ message: 'Training completed!',
+ });
this.close.emit();
}
@@ -424,9 +442,12 @@ export class TrainingComponent implements OnInit {
* Asks the user if they want to end the training and closes it if confirmed.
*/
closeTraining(): void {
- if (confirm('Do you really want to end the training?')) {
- this.close.emit();
- }
+ this.popoverService.show({
+ title: 'End Training',
+ message: 'Do you really want to end the training?',
+ confirmText: 'End Training',
+ onConfirm: (inputValue?: string) => this.close.emit(),
+ });
}
/**
@@ -435,4 +456,4 @@ export class TrainingComponent implements OnInit {
get progress(): string {
return `Image ${this.currentImageIndex + 1} of ${this.deck.images.length}`;
}
-}
\ No newline at end of file
+}