update from mon table to hirarch. table
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
<div class="flex flex-col">
|
||||
<!-- Two-column layout -->
|
||||
<div *ngIf="!trainingsDeck" class="flex flex-col md:flex-row gap-4 mx-auto max-w-5xl">
|
||||
<div *ngIf="!trainingsDeck" class="flex flex-col md:flex-row gap-4 mx-auto max-w-6xl">
|
||||
<!-- Left column: List of decks -->
|
||||
<div class="w-auto">
|
||||
<div class="bg-white shadow rounded-lg p-4">
|
||||
@@ -47,7 +47,20 @@
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<div class="flex items-center space-x-2">
|
||||
<h2 class="text-xl font-semibold">{{ activeDeck.name }}</h2>
|
||||
<span class="text-gray-600">({{ activeDeck.images.length }} images)</span>
|
||||
<!-- <span class="text-gray-600">({{ activeDeck.images.length }} images)</span> -->
|
||||
<div class="flex items-center mx-2">
|
||||
<span class="text-sm mr-2">Shuffle</span>
|
||||
<label class="relative inline-flex items-center cursor-pointer">
|
||||
<input type="checkbox" class="sr-only peer" [ngModel]="activeDeck.boxOrder === 'position'" (ngModelChange)="changeBoxPosition($event)" />
|
||||
<div
|
||||
class="w-11 h-6 bg-gray-200 peer-focus:outline-none rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-blue-600"
|
||||
></div>
|
||||
<span class="ml-2 text-sm font-medium">
|
||||
<!-- {{ activeDeck.boxOrder === 'shuffle' ? 'Shuffle' : 'Position' }} -->
|
||||
Position
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex space-x-2">
|
||||
<button (click)="openDeletePopover(activeDeck.name)" class="text-red-500 hover:text-red-700" title="Delete Deck">
|
||||
@@ -62,7 +75,7 @@
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- <span class="text-gray-600">({{ activeDeck.images.length }} images)</span> -->
|
||||
<!-- Image list -->
|
||||
<ul class="mb-4">
|
||||
<li *ngFor="let image of activeDeck.images" class="flex justify-between items-center py-2 border-b last:border-b-0">
|
||||
@@ -138,7 +151,7 @@
|
||||
<!-- <app-upload-image-modal (imageUploaded)="onImageUploaded($event)"></app-upload-image-modal> -->
|
||||
<app-edit-image-modal *ngIf="imageData" [deckName]="activeDeck.name" [imageData]="imageData" (imageSaved)="onImageSaved()" (closed)="onClosed()"></app-edit-image-modal>
|
||||
<!-- TrainingComponent -->
|
||||
<app-training *ngIf="trainingsDeck" [deck]="trainingsDeck" (close)="closeTraining()"></app-training>
|
||||
<app-training *ngIf="trainingsDeck" [deck]="trainingsDeck" [boxOrder]="trainingsDeck.boxOrder" (close)="closeTraining()"></app-training>
|
||||
<!-- MoveImageModalComponent -->
|
||||
<app-move-image-modal *ngIf="imageToMove" [image]="imageToMove.image" [sourceDeck]="imageToMove.sourceDeck" [decks]="decks" (moveCompleted)="onImageMoved()" (closed)="imageToMove = null"> </app-move-image-modal>
|
||||
</div>
|
||||
|
||||
@@ -230,7 +230,15 @@ export class DeckListComponent implements OnInit {
|
||||
this.trainingsDeck = null;
|
||||
this.loadDecks();
|
||||
}
|
||||
|
||||
async changeBoxPosition(val: boolean) {
|
||||
this.activeDeck.boxOrder = val ? 'position' : 'shuffle';
|
||||
this.deckService.updateDeck(this.activeDeck.name, { boxOrder: this.activeDeck.boxOrder }).subscribe({
|
||||
next: () => {
|
||||
this.loadDecks();
|
||||
},
|
||||
error: err => console.error('Error renaming image', err),
|
||||
});
|
||||
}
|
||||
// Method to open the create deck modal
|
||||
openCreateDeckModal(): void {
|
||||
this.createDeckModal.open();
|
||||
@@ -502,7 +510,7 @@ export class DeckListComponent implements OnInit {
|
||||
//const futureDueDates = dueDates.filter(date => date && date >= now);
|
||||
if (dueDates.length > 0) {
|
||||
const nextDate = dueDates.reduce((a, b) => (a < b ? a : b));
|
||||
return nextDate<today?today:nextDate;
|
||||
return nextDate < today ? today : nextDate;
|
||||
}
|
||||
return today;
|
||||
}
|
||||
@@ -514,7 +522,7 @@ export class DeckListComponent implements OnInit {
|
||||
getWordsToReview(deck: Deck): number {
|
||||
const nextTraining = this.getNextTrainingDate(deck);
|
||||
const today = this.getTodayInDays();
|
||||
return deck.images.flatMap(image => image.boxes.map(box => (box.due ? box.due : null))).filter(e=>e<=nextTraining).length;
|
||||
return deck.images.flatMap(image => image.boxes.map(box => (box.due ? box.due : null))).filter(e => e <= nextTraining).length;
|
||||
}
|
||||
getTodayInDays(): number {
|
||||
const epoch = new Date(1970, 0, 1); // Anki uses UNIX epoch
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { map, Observable } from 'rxjs';
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
export interface Deck {
|
||||
name: string;
|
||||
boxOrder: 'shuffle' | 'position';
|
||||
images: DeckImage[];
|
||||
}
|
||||
|
||||
@@ -62,14 +63,7 @@ export class DeckService {
|
||||
constructor(private http: HttpClient) {}
|
||||
|
||||
getDecks(): Observable<Deck[]> {
|
||||
return this.http.get<any[]>(this.apiUrl).pipe(
|
||||
map(decks =>
|
||||
decks.map(deck => ({
|
||||
name: deck.name,
|
||||
images: this.groupImagesByName(deck.images),
|
||||
})),
|
||||
),
|
||||
);
|
||||
return this.http.get<any[]>(this.apiUrl);
|
||||
}
|
||||
|
||||
private groupImagesByName(images: any[]): DeckImage[] {
|
||||
@@ -103,9 +97,9 @@ export class DeckService {
|
||||
return Object.values(imageMap);
|
||||
}
|
||||
|
||||
getDeck(deckname: string): Observable<Deck> {
|
||||
return this.http.get<Deck>(`${this.apiUrl}/${deckname}/images`);
|
||||
}
|
||||
// getDeck(deckname: string): Observable<Deck> {
|
||||
// return this.http.get<Deck>(`${this.apiUrl}/${deckname}/images`);
|
||||
// }
|
||||
|
||||
createDeck(deckname: string): Observable<any> {
|
||||
return this.http.post(this.apiUrl, { deckname });
|
||||
@@ -117,6 +111,9 @@ export class DeckService {
|
||||
renameDeck(oldDeckName: string, newDeckName: string): Observable<any> {
|
||||
return this.http.put(`${this.apiUrl}/${encodeURIComponent(oldDeckName)}/rename`, { newDeckName });
|
||||
}
|
||||
updateDeck(oldDeckName: string, updateData: { newName?: string; boxOrder?: 'shuffle' | 'position' }): Observable<any> {
|
||||
return this.http.put(`${this.apiUrl}/${encodeURIComponent(oldDeckName)}/update`, updateData);
|
||||
}
|
||||
renameImage(bildid: string, newImageName: string): Observable<any> {
|
||||
return this.http.put(`${this.apiUrl}/image/${encodeURIComponent(bildid)}/rename`, { newImageName });
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<div class="mt-10 mx-auto max-w-5xl">
|
||||
<h2 class="text-2xl font-bold mb-4">Training: {{ deck.name }}</h2>
|
||||
<div class="rounded-lg p-6 flex flex-col items-center">
|
||||
<canvas #canvas class="mb-4 border max-h-[50vh]"></canvas>
|
||||
<canvas #canvas class="mb-4 border max-h-[70vh]"></canvas>
|
||||
|
||||
<div class="flex space-x-4 mb-4">
|
||||
<!-- Show Button -->
|
||||
|
||||
@@ -25,6 +25,7 @@ const EASY_INTERVAL = 4 * 1440; // 4 days in minutes
|
||||
})
|
||||
export class TrainingComponent implements OnInit {
|
||||
@Input() deck!: Deck;
|
||||
@Input() boxOrder: 'shuffle' | 'position' = 'shuffle'; // Standardmäßig shuffle
|
||||
@Output() close = new EventEmitter<void>();
|
||||
|
||||
@ViewChild('canvas', { static: false }) canvasRef!: ElementRef<HTMLCanvasElement>;
|
||||
@@ -92,8 +93,13 @@ export class TrainingComponent implements OnInit {
|
||||
return;
|
||||
}
|
||||
|
||||
// Shuffle the boxes randomly
|
||||
this.boxesToReview = this.shuffleArray(this.boxesToReview);
|
||||
// Order the boxes based on the input parameter
|
||||
if (this.boxOrder === 'position') {
|
||||
this.boxesToReview = this.sortBoxesByPosition(this.boxesToReview);
|
||||
} else {
|
||||
// Default: shuffle
|
||||
this.boxesToReview = this.shuffleArray(this.boxesToReview);
|
||||
}
|
||||
|
||||
// Initialize the array to track revealed boxes
|
||||
this.boxRevealed = new Array(this.boxesToReview.length).fill(false);
|
||||
@@ -165,7 +171,43 @@ export class TrainingComponent implements OnInit {
|
||||
this.close.emit();
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Sorts boxes by their position (left to right, top to bottom)
|
||||
*/
|
||||
/**
|
||||
* Sorts boxes by their position (left to right, top to bottom) with better row detection
|
||||
*/
|
||||
sortBoxesByPosition(boxes: Box[]): Box[] {
|
||||
// First create a copy of the array
|
||||
const boxesCopy = [...boxes];
|
||||
|
||||
// Determine the average box height to use for row grouping tolerance
|
||||
const avgHeight = boxesCopy.reduce((sum, box) => sum + (box.y2 - box.y1), 0) / boxesCopy.length;
|
||||
const rowTolerance = avgHeight * 0.4; // 40% of average box height as tolerance
|
||||
|
||||
// Group boxes into rows
|
||||
const rows: Box[][] = [];
|
||||
|
||||
boxesCopy.forEach(box => {
|
||||
// Find an existing row within tolerance
|
||||
const row = rows.find(r => Math.abs(r[0].y1 - box.y1) < rowTolerance || Math.abs(r[0].y2 - box.y2) < rowTolerance);
|
||||
|
||||
if (row) {
|
||||
row.push(box);
|
||||
} else {
|
||||
rows.push([box]);
|
||||
}
|
||||
});
|
||||
|
||||
// Sort each row by x1 (left to right)
|
||||
rows.forEach(row => row.sort((a, b) => a.x1 - b.x1));
|
||||
|
||||
// Sort rows by y1 (top to bottom)
|
||||
rows.sort((a, b) => a[0].y1 - b[0].y1);
|
||||
|
||||
// Flatten the rows into a single array
|
||||
return rows.flat();
|
||||
}
|
||||
/**
|
||||
* Shuffles an array randomly.
|
||||
* @param array The array to shuffle
|
||||
|
||||
Reference in New Issue
Block a user