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 { CommonModule } from '@angular/common';
|
||||||
import { Modal } from 'flowbite';
|
import { AfterViewInit, Component, ElementRef, EventEmitter, Output, ViewChild } from '@angular/core';
|
||||||
import { FormsModule } from '@angular/forms';
|
import { FormsModule } from '@angular/forms';
|
||||||
|
import { Modal } from 'flowbite';
|
||||||
|
import { DeckService } from '../deck.service';
|
||||||
|
import { PopoverService } from '../services/popover.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-create-deck-modal',
|
selector: 'app-create-deck-modal',
|
||||||
templateUrl: './create-deck-modal.component.html',
|
templateUrl: './create-deck-modal.component.html',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [CommonModule, FormsModule]
|
imports: [CommonModule, FormsModule],
|
||||||
})
|
})
|
||||||
export class CreateDeckModalComponent implements AfterViewInit {
|
export class CreateDeckModalComponent implements AfterViewInit {
|
||||||
@Output() deckCreated = new EventEmitter<void>();
|
@Output() deckCreated = new EventEmitter<void>();
|
||||||
|
|
@ -16,7 +17,7 @@ export class CreateDeckModalComponent implements AfterViewInit {
|
||||||
deckName: string = '';
|
deckName: string = '';
|
||||||
modal!: Modal;
|
modal!: Modal;
|
||||||
|
|
||||||
constructor(private deckService: DeckService) { }
|
constructor(private deckService: DeckService, private popoverService: PopoverService) {}
|
||||||
|
|
||||||
ngAfterViewInit(): void {
|
ngAfterViewInit(): void {
|
||||||
this.modal = new Modal(this.modalElement.nativeElement);
|
this.modal = new Modal(this.modalElement.nativeElement);
|
||||||
|
|
@ -29,7 +30,10 @@ export class CreateDeckModalComponent implements AfterViewInit {
|
||||||
createDeck(event: Event): void {
|
createDeck(event: Event): void {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
if (this.deckName.trim() === '') {
|
if (this.deckName.trim() === '') {
|
||||||
alert('Please enter a deck name.');
|
this.popoverService.show({
|
||||||
|
title: 'Information',
|
||||||
|
message: 'Please enter a deck name !',
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -39,10 +43,13 @@ export class CreateDeckModalComponent implements AfterViewInit {
|
||||||
this.deckCreated.emit();
|
this.deckCreated.emit();
|
||||||
this.modal.hide();
|
this.modal.hide();
|
||||||
},
|
},
|
||||||
error: (err) => {
|
error: err => {
|
||||||
console.error('Error creating deck', 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 flex-col space-y-1">
|
||||||
<div class="flex items-center space-x-2 whitespace-nowrap">
|
<div class="flex items-center space-x-2 whitespace-nowrap">
|
||||||
<h3 class="font-medium">{{ deck.name }}</h3>
|
<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>
|
||||||
<div class="text-sm text-gray-600">
|
<div class="text-sm text-gray-600">
|
||||||
<div>Next training: {{ getNextTrainingString(deck) }}</div>
|
<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>
|
<app-move-image-modal *ngIf="imageToMove" [image]="imageToMove.image" [sourceDeck]="imageToMove.sourceDeck" [decks]="decks" (moveCompleted)="onImageMoved()" (closed)="imageToMove = null"> </app-move-image-modal>
|
||||||
</div>
|
</div>
|
||||||
<!-- Popover für die Löschbestätigung -->
|
<!-- 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>
|
<p>Are you sure you want to delete this deck?</p>
|
||||||
<div class="flex justify-end space-x-2">
|
<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)="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>
|
<button (click)="cancelDelete()" class="bg-gray-500 text-white px-4 py-2 rounded">Cancel</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div> -->
|
||||||
|
|
||||||
<!-- Popover für die Eingabe des neuen Decknamens -->
|
<!-- 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>
|
<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" />
|
<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">
|
<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)="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>
|
<button (click)="cancelRename()" class="bg-gray-500 text-white px-4 py-2 rounded">Cancel</button>
|
||||||
</div>
|
</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 { CommonModule } from '@angular/common';
|
||||||
import { HttpClient } from '@angular/common/http';
|
import { HttpClient } from '@angular/common/http';
|
||||||
import { ChangeDetectorRef, Component, ElementRef, OnInit, ViewChild } from '@angular/core';
|
import { ChangeDetectorRef, Component, ElementRef, OnInit, ViewChild } from '@angular/core';
|
||||||
|
import { FormsModule } from '@angular/forms';
|
||||||
import { firstValueFrom } from 'rxjs';
|
import { firstValueFrom } from 'rxjs';
|
||||||
import { CreateDeckModalComponent } from './create-deck-modal/create-deck-modal.component';
|
import { CreateDeckModalComponent } from './create-deck-modal/create-deck-modal.component';
|
||||||
import { Box, Deck, DeckImage, DeckService, OcrResult } from './deck.service';
|
import { Box, Deck, DeckImage, DeckService, OcrResult } from './deck.service';
|
||||||
import { EditImageModalComponent } from './edit-image-modal/edit-image-modal.component';
|
import { EditImageModalComponent } from './edit-image-modal/edit-image-modal.component';
|
||||||
import { MoveImageModalComponent } from './move-image-modal/move-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';
|
import { TrainingComponent } from './training/training.component';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
|
|
@ -28,14 +30,13 @@ import { TrainingComponent } from './training/training.component';
|
||||||
TrainingComponent,
|
TrainingComponent,
|
||||||
EditImageModalComponent,
|
EditImageModalComponent,
|
||||||
MoveImageModalComponent, // Adding the new component
|
MoveImageModalComponent, // Adding the new component
|
||||||
|
FormsModule,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class DeckListComponent implements OnInit {
|
export class DeckListComponent implements OnInit {
|
||||||
decks: Deck[] = [];
|
decks: Deck[] = [];
|
||||||
trainingsDeck: Deck | null = null;
|
trainingsDeck: Deck | null = null;
|
||||||
activeDeck: Deck | null = null;
|
activeDeck: Deck | null = null;
|
||||||
private deckToDelete: string | null = null;
|
|
||||||
deckToRename: Deck | null = null;
|
|
||||||
loading: boolean = false;
|
loading: boolean = false;
|
||||||
|
|
||||||
@ViewChild(CreateDeckModalComponent)
|
@ViewChild(CreateDeckModalComponent)
|
||||||
|
|
@ -54,7 +55,7 @@ export class DeckListComponent implements OnInit {
|
||||||
// State for moving images
|
// State for moving images
|
||||||
imageToMove: { image: DeckImage; sourceDeck: Deck } | null = null;
|
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 {
|
ngOnInit(): void {
|
||||||
this.loadExpandedDecks();
|
this.loadExpandedDecks();
|
||||||
|
|
@ -84,11 +85,12 @@ export class DeckListComponent implements OnInit {
|
||||||
|
|
||||||
// Method to open the delete confirmation popover
|
// Method to open the delete confirmation popover
|
||||||
openDeletePopover(deckName: string): void {
|
openDeletePopover(deckName: string): void {
|
||||||
this.deckToDelete = deckName;
|
this.popoverService.show({
|
||||||
const deletePopover = document.getElementById('deletePopover');
|
title: 'Delete Deck',
|
||||||
if (deletePopover) {
|
message: 'Are you sure you want to delete this deck?',
|
||||||
deletePopover.showPopover();
|
confirmText: 'Delete',
|
||||||
}
|
onConfirm: () => this.confirmDelete(deckName),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Method to check if a deck is active
|
// 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
|
// Method to confirm the deletion of a deck
|
||||||
confirmDelete(): void {
|
confirmDelete(deckName: string): void {
|
||||||
if (this.deckToDelete) {
|
this.deckService.deleteDeck(deckName).subscribe({
|
||||||
this.deckService.deleteDeck(this.deckToDelete).subscribe({
|
|
||||||
next: () => {
|
next: () => {
|
||||||
this.loadDecks();
|
this.loadDecks();
|
||||||
this.closeDeletePopover();
|
|
||||||
this.activeDeck = this.decks.length > 0 ? this.decks[0] : null;
|
this.activeDeck = this.decks.length > 0 ? this.decks[0] : null;
|
||||||
},
|
},
|
||||||
error: err => console.error('Error deleting deck', err),
|
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
|
// Method to open the rename popover
|
||||||
openRenamePopover(deck: Deck): void {
|
openRenamePopover(deck: Deck): void {
|
||||||
this.deckToRename = deck;
|
this.popoverService.showWithInput({
|
||||||
const renamePopover = document.getElementById('renamePopover');
|
title: 'Rename Deck',
|
||||||
if (renamePopover) {
|
message: 'Enter the new name for the deck:',
|
||||||
renamePopover.showPopover();
|
confirmText: 'Rename',
|
||||||
// Set the input value to the current deck name and select it
|
inputValue: deck.name,
|
||||||
setTimeout(() => {
|
onConfirm: (inputValue?: string) => this.confirmRename(deck, inputValue),
|
||||||
const newDeckNameInput = document.getElementById('newDeckNameInput') as HTMLInputElement;
|
});
|
||||||
if (newDeckNameInput) {
|
|
||||||
newDeckNameInput.value = deck.name;
|
|
||||||
newDeckNameInput.select();
|
|
||||||
}
|
|
||||||
}, 0);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Method to confirm the renaming of a deck
|
// Method to confirm the renaming of a deck
|
||||||
confirmRename(): void {
|
confirmRename(deck: Deck, newName?: string): void {
|
||||||
const newDeckNameInput = document.getElementById('newDeckNameInput') as HTMLInputElement;
|
if (newName && newName.trim() !== '' && newName !== deck.name) {
|
||||||
if (newDeckNameInput && this.deckToRename) {
|
this.deckService.renameDeck(deck.name, newName).subscribe({
|
||||||
const newDeckName = newDeckNameInput.value.trim();
|
|
||||||
if (newDeckName && newDeckName !== this.deckToRename.name) {
|
|
||||||
this.deckService.renameDeck(this.deckToRename.name, newDeckName).subscribe({
|
|
||||||
next: () => {
|
next: () => {
|
||||||
// Aktualisiere den Namen im activeDeck, falls dieser der umbenannte Deck ist
|
if (this.activeDeck?.name === deck.name) {
|
||||||
if (this.activeDeck && this.activeDeck.name === this.deckToRename?.name) {
|
this.activeDeck.name = newName;
|
||||||
this.activeDeck.name = newDeckName;
|
|
||||||
}
|
}
|
||||||
this.loadDecks(); // Lade die Decks neu, um die Liste zu aktualisieren
|
this.loadDecks();
|
||||||
this.closeRenamePopover();
|
|
||||||
},
|
},
|
||||||
error: err => console.error('Error renaming deck', err),
|
error: err => console.error('Error renaming deck', err),
|
||||||
});
|
});
|
||||||
}
|
} else {
|
||||||
}
|
// Optional: Handle ungültigen neuen Namen
|
||||||
}
|
console.warn('Invalid new deck name.');
|
||||||
|
|
||||||
// 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),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Method to delete an image from a deck
|
// Delete-Image Methoden ersetzen
|
||||||
deleteImage(deck: Deck, image: DeckImage): void {
|
deleteImage(deck: Deck, image: DeckImage): void {
|
||||||
if (!confirm(`Are you sure you want to delete the image "${image.name}"?`)) {
|
this.popoverService.show({
|
||||||
return;
|
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;
|
const imageId = image.id;
|
||||||
|
|
||||||
this.deckService.deleteImage(imageId).subscribe({
|
this.deckService.deleteImage(imageId).subscribe({
|
||||||
|
|
@ -211,7 +156,7 @@ export class DeckListComponent implements OnInit {
|
||||||
this.loadDecks();
|
this.loadDecks();
|
||||||
if (this.activeDeck) {
|
if (this.activeDeck) {
|
||||||
this.activeDeck.images = this.activeDeck.images.filter(img => img.id !== imageId);
|
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),
|
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
|
// Methode zur Berechnung der Anzahl der zu bearbeitenden Wörter
|
||||||
getWordsToReview(deck: Deck): number {
|
getWordsToReview(deck: Deck): number {
|
||||||
const nextTraining = this.getNextTrainingDate(deck);
|
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
|
// 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 { CommonModule } from '@angular/common';
|
||||||
|
import { AfterViewInit, Component, ElementRef, EventEmitter, Input, OnDestroy, Output, ViewChild } from '@angular/core';
|
||||||
import { fabric } from 'fabric';
|
import { fabric } from 'fabric';
|
||||||
import { Modal } from 'flowbite';
|
import { Modal } from 'flowbite';
|
||||||
import { DeckImage, DeckService, OcrResult } from '../deck.service';
|
import { DeckImage, DeckService } from '../deck.service';
|
||||||
|
import { PopoverService } from '../services/popover.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-edit-image-modal',
|
selector: 'app-edit-image-modal',
|
||||||
templateUrl: './edit-image-modal.component.html',
|
templateUrl: './edit-image-modal.component.html',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [CommonModule]
|
imports: [CommonModule],
|
||||||
})
|
})
|
||||||
export class EditImageModalComponent implements AfterViewInit, OnDestroy {
|
export class EditImageModalComponent implements AfterViewInit, OnDestroy {
|
||||||
// Constant for box color
|
// Constant for box color
|
||||||
private readonly BOX_COLOR = 'rgba(255, 0, 0, 0.3)'; // Red with transparency
|
private readonly BOX_COLOR = 'rgba(255, 0, 0, 0.3)'; // Red with transparency
|
||||||
|
|
||||||
@Input() deckName: string = '';
|
@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() imageSaved = new EventEmitter<void>();
|
||||||
@Output() closed = new EventEmitter<void>();
|
@Output() closed = new EventEmitter<void>();
|
||||||
@ViewChild('editImageModal') modalElement!: ElementRef;
|
@ViewChild('editImageModal') modalElement!: ElementRef;
|
||||||
|
|
@ -32,13 +33,13 @@ export class EditImageModalComponent implements AfterViewInit, OnDestroy {
|
||||||
private keyDownHandler!: (e: KeyboardEvent) => void;
|
private keyDownHandler!: (e: KeyboardEvent) => void;
|
||||||
modal: any;
|
modal: any;
|
||||||
|
|
||||||
constructor(private deckService: DeckService) { }
|
constructor(private deckService: DeckService, private popoverService: PopoverService) {}
|
||||||
|
|
||||||
async ngAfterViewInit() {
|
async ngAfterViewInit() {
|
||||||
this.modal = new Modal(this.modalElement.nativeElement, {
|
this.modal = new Modal(this.modalElement.nativeElement, {
|
||||||
onHide: () => {
|
onHide: () => {
|
||||||
this.closed.emit();
|
this.closed.emit();
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
this.maxCanvasWidth = window.innerWidth * 0.6;
|
this.maxCanvasWidth = window.innerWidth * 0.6;
|
||||||
|
|
@ -75,14 +76,14 @@ export class EditImageModalComponent implements AfterViewInit, OnDestroy {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
fabric.Image.fromURL(
|
fabric.Image.fromURL(
|
||||||
url,
|
url,
|
||||||
(img) => {
|
img => {
|
||||||
resolve(img);
|
resolve(img);
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
crossOrigin: 'anonymous',
|
crossOrigin: 'anonymous',
|
||||||
originX: 'left',
|
originX: 'left',
|
||||||
originY: 'top',
|
originY: 'top',
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -122,7 +123,6 @@ export class EditImageModalComponent implements AfterViewInit, OnDestroy {
|
||||||
|
|
||||||
// Add boxes
|
// Add boxes
|
||||||
this.imageData.deckImage?.boxes.forEach(box => {
|
this.imageData.deckImage?.boxes.forEach(box => {
|
||||||
|
|
||||||
const rect = new fabric.Rect({
|
const rect = new fabric.Rect({
|
||||||
left: box.x1 * scaleFactor,
|
left: box.x1 * scaleFactor,
|
||||||
top: box.y1 * scaleFactor,
|
top: box.y1 * scaleFactor,
|
||||||
|
|
@ -157,7 +157,6 @@ export class EditImageModalComponent implements AfterViewInit, OnDestroy {
|
||||||
this.updateBoxCoordinates();
|
this.updateBoxCoordinates();
|
||||||
|
|
||||||
// this.detectedText = ocrResults.map(result => result.text).join('\n');
|
// this.detectedText = ocrResults.map(result => result.text).join('\n');
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error processing image:', error);
|
console.error('Error processing image:', error);
|
||||||
}
|
}
|
||||||
|
|
@ -198,7 +197,7 @@ export class EditImageModalComponent implements AfterViewInit, OnDestroy {
|
||||||
x1: Math.round(x1),
|
x1: Math.round(x1),
|
||||||
x2: Math.round(x2),
|
x2: Math.round(x2),
|
||||||
y1: Math.round(y1),
|
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.imageSaved.emit();
|
||||||
this.closeModal();
|
this.closeModal();
|
||||||
},
|
},
|
||||||
error: (err) => {
|
error: err => {
|
||||||
console.error('Error saving image:', err);
|
console.error('Error saving image:', err);
|
||||||
alert('Error saving image.');
|
this.popoverService.show({
|
||||||
|
title: 'Error',
|
||||||
|
message: 'Error saving image.',
|
||||||
|
});
|
||||||
this.closeModal();
|
this.closeModal();
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,13 +1,14 @@
|
||||||
import { Component, Input, Output, EventEmitter } from '@angular/core';
|
|
||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { Component, EventEmitter, Input, Output } from '@angular/core';
|
||||||
import { FormsModule } from '@angular/forms';
|
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({
|
@Component({
|
||||||
selector: 'app-move-image-modal',
|
selector: 'app-move-image-modal',
|
||||||
templateUrl: './move-image-modal.component.html',
|
templateUrl: './move-image-modal.component.html',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [CommonModule, FormsModule]
|
imports: [CommonModule, FormsModule],
|
||||||
})
|
})
|
||||||
export class MoveImageModalComponent {
|
export class MoveImageModalComponent {
|
||||||
@Input() image!: DeckImage;
|
@Input() image!: DeckImage;
|
||||||
|
|
@ -18,7 +19,7 @@ export class MoveImageModalComponent {
|
||||||
|
|
||||||
selectedDeckId: number | null = null;
|
selectedDeckId: number | null = null;
|
||||||
|
|
||||||
constructor(private deckService: DeckService) { }
|
constructor(private deckService: DeckService, private popoverService: PopoverService) {}
|
||||||
|
|
||||||
moveImage(): void {
|
moveImage(): void {
|
||||||
if (this.selectedDeckId === null) {
|
if (this.selectedDeckId === null) {
|
||||||
|
|
@ -30,10 +31,13 @@ export class MoveImageModalComponent {
|
||||||
this.moveCompleted.emit();
|
this.moveCompleted.emit();
|
||||||
this.close();
|
this.close();
|
||||||
},
|
},
|
||||||
error: (err) => {
|
error: err => {
|
||||||
console.error('Error moving image:', 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 { Component, ElementRef, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core';
|
||||||
import { lastValueFrom } from 'rxjs';
|
import { lastValueFrom } from 'rxjs';
|
||||||
import { Box, Deck, DeckImage, DeckService } from '../deck.service';
|
import { Box, Deck, DeckImage, DeckService } from '../deck.service';
|
||||||
|
import { PopoverService } from '../services/popover.service';
|
||||||
|
|
||||||
const LEARNING_STEPS = {
|
const LEARNING_STEPS = {
|
||||||
AGAIN: 1, // 1 minute
|
AGAIN: 1, // 1 minute
|
||||||
GOOD: 10, // 10 minutes
|
GOOD: 10, // 10 minutes
|
||||||
GRADUATION: 1440 // 1 day (in minutes)
|
GRADUATION: 1440, // 1 day (in minutes)
|
||||||
};
|
};
|
||||||
|
|
||||||
const FACTOR_CHANGES = {
|
const FACTOR_CHANGES = {
|
||||||
AGAIN: 0.85, // Reduce factor by 15%
|
AGAIN: 0.85, // Reduce factor by 15%
|
||||||
EASY: 1.15, // Increase factor by 15%
|
EASY: 1.15, // Increase factor by 15%
|
||||||
MIN: 1.3, // Minimum factor allowed
|
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
|
const EASY_INTERVAL = 4 * 1440; // 4 days in minutes
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-training',
|
selector: 'app-training',
|
||||||
templateUrl: './training.component.html',
|
templateUrl: './training.component.html',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [CommonModule]
|
imports: [CommonModule],
|
||||||
})
|
})
|
||||||
export class TrainingComponent implements OnInit {
|
export class TrainingComponent implements OnInit {
|
||||||
@Input() deck!: Deck;
|
@Input() deck!: Deck;
|
||||||
|
|
@ -37,7 +38,7 @@ export class TrainingComponent implements OnInit {
|
||||||
isShowingBox: boolean = false;
|
isShowingBox: boolean = false;
|
||||||
isTrainingFinished: boolean = false;
|
isTrainingFinished: boolean = false;
|
||||||
|
|
||||||
constructor(private deckService: DeckService) { }
|
constructor(private deckService: DeckService, private popoverService: PopoverService) {}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
// Initialization was done in ngAfterViewInit
|
// Initialization was done in ngAfterViewInit
|
||||||
|
|
@ -48,7 +49,10 @@ export class TrainingComponent implements OnInit {
|
||||||
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 {
|
||||||
alert('No deck or images available.');
|
this.popoverService.show({
|
||||||
|
title: 'Information',
|
||||||
|
message: 'No deck or images available.',
|
||||||
|
});
|
||||||
this.close.emit();
|
this.close.emit();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -135,7 +139,7 @@ export class TrainingComponent implements OnInit {
|
||||||
this.boxesToReview.forEach((box, index) => {
|
this.boxesToReview.forEach((box, index) => {
|
||||||
ctx.beginPath();
|
ctx.beginPath();
|
||||||
ctx.rect(box.x1, box.y1, box.x2 - box.x1, box.y2 - box.y1);
|
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
|
// Box is currently revealed, no overlay
|
||||||
return;
|
return;
|
||||||
} else if (this.currentBoxIndex === index && !this.isShowingBox) {
|
} else if (this.currentBoxIndex === index && !this.isShowingBox) {
|
||||||
|
|
@ -154,7 +158,10 @@ export class TrainingComponent implements OnInit {
|
||||||
|
|
||||||
img.onerror = () => {
|
img.onerror = () => {
|
||||||
console.error('Error loading image for canvas.');
|
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();
|
this.close.emit();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
@ -165,7 +172,8 @@ export class TrainingComponent implements OnInit {
|
||||||
* @returns The shuffled array
|
* @returns The shuffled array
|
||||||
*/
|
*/
|
||||||
shuffleArray<T>(array: T[]): T[] {
|
shuffleArray<T>(array: T[]): T[] {
|
||||||
let currentIndex = array.length, randomIndex;
|
let currentIndex = array.length,
|
||||||
|
randomIndex;
|
||||||
|
|
||||||
// While there are elements to shuffle
|
// While there are elements to shuffle
|
||||||
while (currentIndex !== 0) {
|
while (currentIndex !== 0) {
|
||||||
|
|
@ -174,8 +182,7 @@ export class TrainingComponent implements OnInit {
|
||||||
currentIndex--;
|
currentIndex--;
|
||||||
|
|
||||||
// Swap it with the current element
|
// Swap it with the current element
|
||||||
[array[currentIndex], array[randomIndex]] = [
|
[array[currentIndex], array[randomIndex]] = [array[randomIndex], array[currentIndex]];
|
||||||
array[randomIndex], array[currentIndex]];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return array;
|
return array;
|
||||||
|
|
@ -257,7 +264,10 @@ export class TrainingComponent implements OnInit {
|
||||||
await lastValueFrom(this.deckService.updateBox(box));
|
await lastValueFrom(this.deckService.updateBox(box));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error updating box:', 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')
|
* @param action The action ('again', 'good', 'easy')
|
||||||
* @returns An object with the new values for ivl, factor, reps, and lapses
|
* @returns An object with the new values for ivl, factor, reps, and lapses
|
||||||
*/
|
*/
|
||||||
calculateNewInterval(box: Box, action: 'again' | 'good' | 'easy'): {
|
calculateNewInterval(
|
||||||
newIvl: number,
|
box: Box,
|
||||||
newFactor: number,
|
action: 'again' | 'good' | 'easy',
|
||||||
newReps: number,
|
): {
|
||||||
newLapses: number,
|
newIvl: number;
|
||||||
newIsGraduated: boolean
|
newFactor: number;
|
||||||
|
newReps: number;
|
||||||
|
newLapses: number;
|
||||||
|
newIsGraduated: boolean;
|
||||||
} {
|
} {
|
||||||
const LEARNING_STEPS = {
|
const LEARNING_STEPS = {
|
||||||
AGAIN: 1, // 1 minute
|
AGAIN: 1, // 1 minute
|
||||||
GOOD: 10, // 10 minutes
|
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 EASY_BONUS = 1.3; // Zusätzlicher Easy-Multiplikator
|
||||||
/* const FACTOR_CHANGES = {
|
/* const FACTOR_CHANGES = {
|
||||||
MIN: 1.3,
|
MIN: 1.3,
|
||||||
MAX: 2.5,
|
MAX: 2.5,
|
||||||
AGAIN: 0.85,
|
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")
|
* @returns The next interval as a string (e.g., "10 min", "2 d")
|
||||||
*/
|
*/
|
||||||
getNextInterval(box: Box | null, action: 'again' | 'good' | 'easy'): string {
|
getNextInterval(box: Box | null, action: 'again' | 'good' | 'easy'): string {
|
||||||
if (!box)
|
if (!box) return '';
|
||||||
return '';
|
|
||||||
|
|
||||||
const { newIvl } = this.calculateNewInterval(box, action);
|
const { newIvl } = this.calculateNewInterval(box, action);
|
||||||
|
|
||||||
|
|
@ -407,7 +419,10 @@ export class TrainingComponent implements OnInit {
|
||||||
this.currentImageIndex++;
|
this.currentImageIndex++;
|
||||||
this.loadImage(this.currentImageIndex);
|
this.loadImage(this.currentImageIndex);
|
||||||
} else {
|
} 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 {
|
endTraining(): void {
|
||||||
this.isTrainingFinished = true;
|
this.isTrainingFinished = true;
|
||||||
alert(`Training completed!`);
|
this.popoverService.show({
|
||||||
|
title: 'Information',
|
||||||
|
message: 'Training completed!',
|
||||||
|
});
|
||||||
this.close.emit();
|
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.
|
* Asks the user if they want to end the training and closes it if confirmed.
|
||||||
*/
|
*/
|
||||||
closeTraining(): void {
|
closeTraining(): void {
|
||||||
if (confirm('Do you really want to end the training?')) {
|
this.popoverService.show({
|
||||||
this.close.emit();
|
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