replace alerts und confirms with modals #15
This commit is contained in:
parent
47b393e134
commit
2f35648264
|
|
@ -0,0 +1,53 @@
|
|||
// popover.component.ts
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { Component, EventEmitter, Input, Output } from '@angular/core';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
|
||||
@Component({
|
||||
selector: 'app-popover',
|
||||
standalone: true,
|
||||
imports: [CommonModule, FormsModule],
|
||||
template: `
|
||||
<div class="fixed inset-0 bg-gray-500 bg-opacity-50 flex items-center justify-center z-50" *ngIf="visible">
|
||||
<div class="bg-white rounded-lg p-6 max-w-xs w-full shadow-lg border border-gray-200">
|
||||
<h3 class="text-lg font-semibold mb-4">{{ title }}</h3>
|
||||
|
||||
<p *ngIf="message" class="mb-4">{{ message }}</p>
|
||||
|
||||
<input *ngIf="showInput" type="text" class="w-full p-2 border rounded mb-4 focus:ring-2 focus:ring-blue-500 focus:border-transparent" [(ngModel)]="inputValue" (keyup.enter)="onConfirm()" autofocus />
|
||||
|
||||
<div class="flex justify-end space-x-2">
|
||||
<button (click)="onCancel()" class="px-4 py-2 rounded bg-gray-100 text-gray-700 hover:bg-gray-200 focus:outline-none focus:ring-2 focus:ring-gray-400">Cancel</button>
|
||||
<button (click)="onConfirm()" class="px-4 py-2 rounded bg-blue-500 text-white hover:bg-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-500">
|
||||
{{ confirmText }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
})
|
||||
export class PopoverComponent {
|
||||
@Input() title: string = '';
|
||||
@Input() message: string = '';
|
||||
@Input() showInput: boolean = false;
|
||||
@Input() confirmText: string = 'Confirm';
|
||||
@Input() inputValue: string = '';
|
||||
@Input() visible: boolean = false;
|
||||
@Output() confirmed = new EventEmitter<string>();
|
||||
@Output() canceled = new EventEmitter<void>();
|
||||
|
||||
onConfirm() {
|
||||
this.confirmed.emit(this.inputValue);
|
||||
//this.reset();
|
||||
}
|
||||
|
||||
onCancel() {
|
||||
this.canceled.emit();
|
||||
//this.reset();
|
||||
}
|
||||
|
||||
private reset() {
|
||||
this.visible = false;
|
||||
this.inputValue = '';
|
||||
}
|
||||
}
|
||||
|
|
@ -1,14 +1,15 @@
|
|||
import { Component, Output, EventEmitter, AfterViewInit, ViewChild, ElementRef } from '@angular/core';
|
||||
import { DeckService } from '../deck.service';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { Modal } from 'flowbite';
|
||||
import { AfterViewInit, Component, ElementRef, EventEmitter, Output, ViewChild } from '@angular/core';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { Modal } from 'flowbite';
|
||||
import { DeckService } from '../deck.service';
|
||||
import { PopoverService } from '../services/popover.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-create-deck-modal',
|
||||
templateUrl: './create-deck-modal.component.html',
|
||||
standalone: true,
|
||||
imports: [CommonModule, FormsModule]
|
||||
imports: [CommonModule, FormsModule],
|
||||
})
|
||||
export class CreateDeckModalComponent implements AfterViewInit {
|
||||
@Output() deckCreated = new EventEmitter<void>();
|
||||
|
|
@ -16,7 +17,7 @@ export class CreateDeckModalComponent implements AfterViewInit {
|
|||
deckName: string = '';
|
||||
modal!: Modal;
|
||||
|
||||
constructor(private deckService: DeckService) { }
|
||||
constructor(private deckService: DeckService, private popoverService: PopoverService) {}
|
||||
|
||||
ngAfterViewInit(): void {
|
||||
this.modal = new Modal(this.modalElement.nativeElement);
|
||||
|
|
@ -29,7 +30,10 @@ export class CreateDeckModalComponent implements AfterViewInit {
|
|||
createDeck(event: Event): void {
|
||||
event.preventDefault();
|
||||
if (this.deckName.trim() === '') {
|
||||
alert('Please enter a deck name.');
|
||||
this.popoverService.show({
|
||||
title: 'Information',
|
||||
message: 'Please enter a deck name !',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -39,10 +43,13 @@ export class CreateDeckModalComponent implements AfterViewInit {
|
|||
this.deckCreated.emit();
|
||||
this.modal.hide();
|
||||
},
|
||||
error: (err) => {
|
||||
error: err => {
|
||||
console.error('Error creating deck', err);
|
||||
alert('Error creating deck.');
|
||||
}
|
||||
this.popoverService.show({
|
||||
title: 'Error',
|
||||
message: 'Error creating deck.',
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@
|
|||
<div class="flex flex-col space-y-1">
|
||||
<div class="flex items-center space-x-2 whitespace-nowrap">
|
||||
<h3 class="font-medium">{{ deck.name }}</h3>
|
||||
<span class="text-gray-600">({{ deck.images.length }})</span>
|
||||
<span class="text-gray-600">({{ deck.images.length }} Pics)</span>
|
||||
</div>
|
||||
<div class="text-sm text-gray-600">
|
||||
<div>Next training: {{ getNextTrainingString(deck) }}</div>
|
||||
|
|
@ -123,20 +123,40 @@
|
|||
<app-move-image-modal *ngIf="imageToMove" [image]="imageToMove.image" [sourceDeck]="imageToMove.sourceDeck" [decks]="decks" (moveCompleted)="onImageMoved()" (closed)="imageToMove = null"> </app-move-image-modal>
|
||||
</div>
|
||||
<!-- Popover für die Löschbestätigung -->
|
||||
<div id="deletePopover" popover="manual" class="popover">
|
||||
<!-- <div id="deletePopover" popover="manual" class="popover">
|
||||
<p>Are you sure you want to delete this deck?</p>
|
||||
<div class="flex justify-end space-x-2">
|
||||
<button (click)="confirmDelete()" class="bg-red-500 text-white px-4 py-2 rounded">Delete</button>
|
||||
<button (click)="cancelDelete()" class="bg-gray-500 text-white px-4 py-2 rounded">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
</div> -->
|
||||
|
||||
<!-- Popover für die Eingabe des neuen Decknamens -->
|
||||
<div id="renamePopover" popover="manual" class="popover">
|
||||
<!-- <div id="renamePopover" popover="manual" class="popover">
|
||||
<p>Enter the new name for the deck:</p>
|
||||
<input type="text" id="newDeckNameInput" class="w-full p-2 border rounded mb-2" [value]="deckToRename?.name" />
|
||||
<div class="flex justify-end space-x-2">
|
||||
<button (click)="confirmRename()" class="bg-blue-500 text-white px-4 py-2 rounded">Rename</button>
|
||||
<button (click)="cancelRename()" class="bg-gray-500 text-white px-4 py-2 rounded">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
</div> -->
|
||||
<!-- Statt der alten Popover-Divs -->
|
||||
<!-- <app-popover [visible]="showDeletePopover" title="Delete Deck" message="Are you sure you want to delete this deck?" confirmText="Delete" (confirmed)="confirmDelete()" (canceled)="cancelDelete()"></app-popover>
|
||||
|
||||
<app-popover
|
||||
[visible]="showRenamePopover"
|
||||
title="Rename Deck"
|
||||
[showInput]="true"
|
||||
[inputValue]="deckToRename?.name || ''"
|
||||
confirmText="Rename"
|
||||
(confirmed)="confirmRename($event)"
|
||||
(canceled)="cancelRename()"
|
||||
></app-popover>
|
||||
<app-popover
|
||||
[visible]="showDeleteImagePopover"
|
||||
title="Delete Image"
|
||||
message="Are you sure you want to delete this image?"
|
||||
confirmText="Delete"
|
||||
(confirmed)="confirmImageDelete()"
|
||||
(canceled)="cancelImageDelete()"
|
||||
></app-popover> -->
|
||||
|
|
|
|||
|
|
@ -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({
|
||||
confirmDelete(deckName: string): void {
|
||||
this.deckService.deleteDeck(deckName).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);
|
||||
}
|
||||
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(): 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({
|
||||
confirmRename(deck: Deck, newName?: string): void {
|
||||
if (newName && newName.trim() !== '' && newName !== deck.name) {
|
||||
this.deckService.renameDeck(deck.name, newName).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;
|
||||
if (this.activeDeck?.name === deck.name) {
|
||||
this.activeDeck.name = newName;
|
||||
}
|
||||
this.loadDecks(); // Lade die Decks neu, um die Liste zu aktualisieren
|
||||
this.closeRenamePopover();
|
||||
this.loadDecks();
|
||||
},
|
||||
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;
|
||||
}
|
||||
this.deckService.deleteDeck(deckName).subscribe({
|
||||
next: () => this.loadDecks(),
|
||||
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(),
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<void>();
|
||||
@Output() closed = new EventEmitter<void>();
|
||||
@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();
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -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,10 +31,13 @@ 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.',
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
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
|
||||
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<T>(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,21 +277,24 @@ 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)
|
||||
GRADUATION: 1440, // 1 day (1440 minutes)
|
||||
};
|
||||
|
||||
const EASY_BONUS = 1.3; // Zusätzlicher Easy-Multiplikator
|
||||
/* const FACTOR_CHANGES = {
|
||||
/* const FACTOR_CHANGES = {
|
||||
MIN: 1.3,
|
||||
MAX: 2.5,
|
||||
AGAIN: 0.85,
|
||||
|
|
@ -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(),
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
Loading…
Reference in New Issue