diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..59d9a3a --- /dev/null +++ b/.editorconfig @@ -0,0 +1,16 @@ +# Editor configuration, see https://editorconfig.org +root = true + +[*] +charset = utf-8 +indent_style = space +indent_size = 2 +insert_final_newline = true +trim_trailing_whitespace = true + +[*.ts] +quote_type = single + +[*.md] +max_line_length = off +trim_trailing_whitespace = false diff --git a/.prettierrc b/.prettierrc index 544138b..9c49078 100644 --- a/.prettierrc +++ b/.prettierrc @@ -1,3 +1,18 @@ { - "singleQuote": true -} + "arrowParens": "avoid", + "embeddedLanguageFormatting": "auto", + "htmlWhitespaceSensitivity": "css", + "insertPragma": false, + "jsxBracketSameLine": false, + "jsxSingleQuote": false, + "printWidth": 220, + "proseWrap": "always", + "quoteProps": "as-needed", + "requirePragma": false, + "semi": true, + "singleQuote": true, + "tabWidth": 2, + "trailingComma": "all", + "useTabs": false, + "vueIndentScriptAndStyle": false +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..c3f52a3 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,29 @@ +{ + "editor.suggestSelection": "first", + "vsintellicode.modify.editor.suggestSelection": "automaticallyOverrodeDefaultValue", + "explorer.confirmDelete": false, + "typescript.updateImportsOnFileMove.enabled": "always", + "[typescript]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "[html]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "[json]": { + "editor.defaultFormatter": "vscode.json-language-features" + }, + "[scss]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "[jsonc]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "editor.formatOnSave": true, + "editor.codeActionsOnSave": { + "source.organizeImports": "explicit" + }, + "prettier.printWidth": 240, + "git.autofetch": false, + "git.autorefresh": true + } + \ No newline at end of file diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 6b08cc4..46a184e 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -1,21 +1,46 @@ import { CommonModule } from '@angular/common'; import { Component } from '@angular/core'; -import { RouterOutlet } from '@angular/router'; import { DeckListComponent } from './deck-list.component'; @Component({ selector: 'app-root', template: ` -
+
+
+

Master Your Vocabulary

+

Learn smarter, not harder. Start your journey today.

+ +
+
+ +

Vocabulary Training

`, standalone: true, - imports: [CommonModule, DeckListComponent] + imports: [CommonModule, DeckListComponent], }) export class AppComponent { - title = 'vocabulary-training'; - ngOnInit(): void { + isLoggedIn = false; // Zustand für den Login-Status + + // Mock-Funktion für Google Login + loginWithGoogle() { + // Hier würde die eigentliche Google Login-Logik stehen + // Zum Beispiel mit Angular Fire oder einer anderen Bibliothek + // Für dieses Beispiel simulieren wir den Login: + this.isLoggedIn = true; + console.log('Logged in with Google'); } -} \ No newline at end of file +} diff --git a/src/app/deck-list.component.html b/src/app/deck-list.component.html index 5aa9f72..8ee4bef 100644 --- a/src/app/deck-list.component.html +++ b/src/app/deck-list.component.html @@ -1,29 +1,31 @@
-
-

Decks

- -
+

Decks

+ +
-
-
-

{{ deck.name }}

- ({{ deck.images.length }}) +
+
+
+

{{ deck.name }}

+ ({{ deck.images.length }}) +
+
+
Next training: {{ getNextTrainingString(deck) }}
+
Words to review: {{ getWordsToReview(deck) }}
+
- +
- +
-
+
@@ -122,14 +120,7 @@ - - +
@@ -148,4 +139,4 @@
-
\ No newline at end of file +
diff --git a/src/app/deck-list.component.ts b/src/app/deck-list.component.ts index fc60c9c..4113bb6 100644 --- a/src/app/deck-list.component.ts +++ b/src/app/deck-list.component.ts @@ -1,19 +1,19 @@ -import { ChangeDetectorRef, Component, ElementRef, OnInit, signal, ViewChild, WritableSignal } from '@angular/core'; -import { DeckService, Deck, DeckImage, Box, OcrResult } from './deck.service'; import { CommonModule } from '@angular/common'; +import { HttpClient } from '@angular/common/http'; +import { ChangeDetectorRef, Component, ElementRef, OnInit, ViewChild } from '@angular/core'; +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 { TrainingComponent } from './training/training.component'; import { UploadImageModalComponent } from './upload-image-modal/upload-image-modal.component'; -import { EditImageModalComponent } from './edit-image-modal/edit-image-modal.component'; -import { firstValueFrom } from 'rxjs'; -import { MoveImageModalComponent } from './move-image-modal/move-image-modal.component'; -import { HttpClient } from '@angular/common/http'; @Component({ selector: 'app-deck-list', templateUrl: './deck-list.component.html', standalone: true, - styles:` + styles: ` .popover { padding: 1rem; border: 1px solid #ccc; @@ -21,7 +21,7 @@ import { HttpClient } from '@angular/common/http'; background-color: white; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); max-width: 300px; - } + } `, imports: [ CommonModule, @@ -30,8 +30,8 @@ import { HttpClient } from '@angular/common/http'; TrainingComponent, EditImageModalComponent, MoveImageModalComponent, // Adding the new component - UploadImageModalComponent - ] + UploadImageModalComponent, + ], }) export class DeckListComponent implements OnInit { decks: Deck[] = []; @@ -41,21 +41,26 @@ export class DeckListComponent implements OnInit { deckToRename: Deck | null = null; loading: boolean = false; - @ViewChild(CreateDeckModalComponent) createDeckModal!: CreateDeckModalComponent; - @ViewChild(UploadImageModalComponent) uploadImageModal!: UploadImageModalComponent; + @ViewChild(CreateDeckModalComponent) + createDeckModal!: CreateDeckModalComponent; + @ViewChild(UploadImageModalComponent) + uploadImageModal!: UploadImageModalComponent; @ViewChild(EditImageModalComponent) editModal!: EditImageModalComponent; @ViewChild(UploadImageModalComponent) uploadModal!: UploadImageModalComponent; @ViewChild('imageFile') imageFileElement!: ElementRef; - imageData: { imageSrc: string | ArrayBuffer | null, deckImage: DeckImage } | null = null; + imageData: { + imageSrc: string | ArrayBuffer | null; + deckImage: DeckImage; + } | null = null; // Set to track expanded decks expandedDecks: Set = new Set(); // 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) {} ngOnInit(): void { this.loadExpandedDecks(); @@ -64,14 +69,14 @@ export class DeckListComponent implements OnInit { loadDecks(): void { this.deckService.getDecks().subscribe({ - next: (data) => { + next: data => { this.decks = data; // Set the first deck as active if none is selected if (!this.activeDeck && this.decks.length > 0) { this.activeDeck = this.decks[0]; } }, - error: (err) => console.error('Error loading decks', err) + error: err => console.error('Error loading decks', err), }); } @@ -103,7 +108,7 @@ export class DeckListComponent implements OnInit { this.closeDeletePopover(); this.activeDeck = this.decks[0]; }, - error: (err) => console.error('Error deleting deck', err), + error: err => console.error('Error deleting deck', err), }); } } @@ -154,7 +159,7 @@ export class DeckListComponent implements OnInit { this.loadDecks(); // Lade die Decks neu, um die Liste zu aktualisieren this.closeRenamePopover(); }, - error: (err) => console.error('Error renaming deck', err), + error: err => console.error('Error renaming deck', err), }); } } @@ -181,21 +186,21 @@ export class DeckListComponent implements OnInit { } this.deckService.deleteDeck(deckName).subscribe({ next: () => this.loadDecks(), - error: (err) => console.error('Error deleting deck', err) + 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) + error: err => console.error('Error renaming deck', err), }); } } - + // Method to delete an image from a deck deleteImage(deck: Deck, image: DeckImage): void { if (!confirm(`Are you sure you want to delete the image "${image.name}"?`)) { @@ -212,7 +217,7 @@ export class DeckListComponent implements OnInit { this.cdr.detectChanges(); // Erzwingt einen UI-Update } }, - error: (err) => console.error('Error deleting image', err) + error: err => console.error('Error deleting image', err), }); } @@ -305,10 +310,10 @@ export class DeckListComponent implements OnInit { async onImageSaved() { // Handle saving the image data, e.g., update the list of images this.imageData = null; - + // Lade die Decks neu this.decks = await firstValueFrom(this.deckService.getDecks()); - + // Aktualisiere den activeDeck, falls dieser der aktuelle Deck ist if (this.activeDeck) { const updatedDeck = this.decks.find(deck => deck.name === this.activeDeck?.name); @@ -335,12 +340,12 @@ export class DeckListComponent implements OnInit { const fileNameElement = document.getElementById('fileName'); if (fileNameElement) { fileNameElement.textContent = file.name; - } + } // this.imageFile = file; this.loading = true; const reader = new FileReader(); - reader.onload = async (e) => { + reader.onload = async e => { const imageSrc = e.target?.result; // Image as Base64 string without prefix (data:image/...) @@ -382,13 +387,35 @@ export class DeckListComponent implements OnInit { } }; reader.readAsDataURL(file); - } + } /** - * Resets the file input field so the same file can be selected again. - */ + * Resets the file input field so the same file can be selected again. + */ resetFileInput(): void { if (this.imageFileElement && this.imageFileElement.nativeElement) { this.imageFileElement.nativeElement.value = ''; } } -} \ No newline at end of file + + // Methode zur Berechnung des nächsten Trainingsdatums + getNextTrainingDate(deck: Deck): Date { + const now = new Date(); + now.setHours(0, 0, 0, 0); + const dueDates = deck.images.flatMap(image => image.boxes.map(box => (box.due ? new Date(box.due * 86400000) : null))); + const futureDueDates = dueDates.filter(date => date && date >= now); + if (futureDueDates.length > 0) { + const nextDate = futureDueDates.reduce((a, b) => (a < b ? a : b)); + return nextDate; + } + return now; + } + getNextTrainingString(deck: Deck): string { + return this.getNextTrainingDate(deck).toLocaleDateString(); + } + + // 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; + } +}