diff --git a/api/src/app/decks.controller.ts b/api/src/app/decks.controller.ts
index dfb7ef5..2d92e7e 100644
--- a/api/src/app/decks.controller.ts
+++ b/api/src/app/decks.controller.ts
@@ -124,6 +124,15 @@ export class DecksController {
return this.drizzleService.updateImage(data, user);
}
+ @Put('image/:bildid/rename')
+ async renameImage(@Request() req, @Param('bildid') bildid: string, @Body() data: { newImageName: string }) {
+ if (!data.newImageName) {
+ throw new HttpException('New image name is required', HttpStatus.BAD_REQUEST);
+ }
+ const user: User = req['user'];
+ return this.drizzleService.renameImage(bildid, data.newImageName, user);
+ }
+
@Delete('image/:bildid')
async deleteImagesByBildId(@Request() req, @Param('bildid') bildid: string) {
const user: User = req['user'];
diff --git a/api/src/app/drizzle.service.ts b/api/src/app/drizzle.service.ts
index e99977d..07cfcf8 100644
--- a/api/src/app/drizzle.service.ts
+++ b/api/src/app/drizzle.service.ts
@@ -216,7 +216,32 @@ export class DrizzleService {
return { status: 'success', moved_entries: existingImages.length };
}
+ /**
+ * Methode zum Umbenennen eines Decks
+ */
+ async renameImage(bildId: string, newImagename: string, user: User) {
+ const existingImages = this.db
+ .select()
+ .from(Deck)
+ .where(and(eq(Deck.bildid, bildId), eq(Deck.user, user.email)));
+ if (existingImages.length === 0) {
+ throw new HttpException('Deck not found', HttpStatus.NOT_FOUND);
+ }
+ // const existingNewDeck = await this.getDeckByName(newDeckname, user);
+ // if (existingNewDeck.length > 0) {
+ // throw new HttpException('Deck with the new name already exists', HttpStatus.CONFLICT);
+ // }
+
+ await this.db
+ .update(Deck)
+ .set({
+ bildname: newImagename,
+ updated: sql`CURRENT_TIMESTAMP`, // Setze 'updated' auf CURRENT_TIMESTAMP
+ })
+ .where(and(eq(Deck.bildid, bildId), eq(Deck.user, user.email)));
+ return { status: 'success', message: 'Image Entries renamed successfully' };
+ }
/**
* Methode zum Aktualisieren einer Box
*/
diff --git a/src/app/app.component.ts b/src/app/app.component.ts
index 89eb238..3d9a255 100644
--- a/src/app/app.component.ts
+++ b/src/app/app.component.ts
@@ -9,25 +9,7 @@ import { PopoverService } from './services/popover.service';
@Component({
selector: 'app-root',
template: `
-
-
+
Master Your Learning
Learn smarter, not harder. Start your journey today
@@ -45,24 +27,6 @@ import { PopoverService } from './services/popover.service';
-
-
@@ -73,11 +37,6 @@ import { PopoverService } from './services/popover.service';
-
Spaced Repetition Training
diff --git a/src/app/deck-list.component.html b/src/app/deck-list.component.html
index 4144ff5..af3e3ea 100644
--- a/src/app/deck-list.component.html
+++ b/src/app/deck-list.component.html
@@ -89,6 +89,11 @@
+
@@ -97,10 +102,24 @@
-
+
+
+
+
+
diff --git a/src/app/deck-list.component.ts b/src/app/deck-list.component.ts
index 3234c59..0ed98fd 100644
--- a/src/app/deck-list.component.ts
+++ b/src/app/deck-list.component.ts
@@ -66,11 +66,20 @@ export class DeckListComponent implements OnInit {
this.deckService.getDecks().subscribe({
next: data => {
this.decks = data;
- // Set the first deck as active if none is selected
- if (!this.activeDeck && this.decks.length > 0) {
+ // Versuche, das zuvor gespeicherte aktive Deck zu laden
+ const storedActiveDeckName = localStorage.getItem('activeDeckName');
+ if (storedActiveDeckName) {
+ const foundDeck = this.decks.find(deck => deck.name === storedActiveDeckName);
+ if (foundDeck) {
+ this.activeDeck = foundDeck;
+ } else if (this.decks.length > 0) {
+ this.activeDeck = this.decks[0];
+ localStorage.setItem('activeDeckName', this.activeDeck.name);
+ }
+ } else if (this.decks.length > 0) {
this.activeDeck = this.decks[0];
- }
- if (this.decks.length === 0) {
+ localStorage.setItem('activeDeckName', this.activeDeck.name);
+ } else {
this.activeDeck = null;
}
},
@@ -81,6 +90,7 @@ export class DeckListComponent implements OnInit {
// Updated toggle method
toggleDeckExpansion(deck: Deck): void {
this.activeDeck = deck;
+ localStorage.setItem('activeDeckName', deck.name);
}
// Method to open the delete confirmation popover
@@ -138,6 +148,29 @@ export class DeckListComponent implements OnInit {
}
}
+ openRenameImagePopover(image: DeckImage): void {
+ this.popoverService.showWithInput({
+ title: 'Rename Deck',
+ message: 'Enter the new name for the deck:',
+ confirmText: 'Rename',
+ inputValue: image.name,
+ onConfirm: (inputValue?: string) => this.confirmRenameImage(image, inputValue),
+ });
+ }
+ // Method to confirm the renaming of a deck
+ confirmRenameImage(image: DeckImage, newName?: string): void {
+ if (newName && newName.trim() !== '' && newName !== image.name) {
+ this.deckService.renameImage(image.bildid, newName).subscribe({
+ next: () => {
+ this.loadDecks();
+ },
+ error: err => console.error('Error renaming image', err),
+ });
+ } else {
+ // Optional: Handle ungültigen neuen Namen
+ console.warn('Invalid new image name.');
+ }
+ }
// Delete-Image Methoden ersetzen
deleteImage(deck: Deck, image: DeckImage): void {
this.popoverService.show({
@@ -368,6 +401,97 @@ export class DeckListComponent implements OnInit {
}
}
+ /**
+ * Liest das aktuelle Bild aus der Zwischenablage und setzt imageData, sodass die Edit-Image-Komponente
+ * mit dem eingefügten Bild startet.
+ */
+ pasteImage(): void {
+ if (!navigator.clipboard || !navigator.clipboard.read) {
+ this.popoverService.show({
+ title: 'Fehler',
+ message: 'Das Clipboard-API wird in diesem Browser nicht unterstützt.',
+ });
+ return;
+ }
+
+ navigator.clipboard
+ .read()
+ .then(items => {
+ // Suche im Clipboard nach einem Element, das ein Bild enthält
+ for (const item of items) {
+ for (const type of item.types) {
+ if (type.startsWith('image/')) {
+ // Hole den Blob des Bildes
+ item.getType(type).then(blob => {
+ const reader = new FileReader();
+ this.loading = true;
+ reader.onload = async e => {
+ const imageSrc = e.target?.result;
+ // Extrahiere den Base64-String (ähnlich wie in onFileChange)
+ const imageBase64 = (imageSrc as string).split(',')[1];
+
+ try {
+ // Optional: OCR-Request wie im File-Upload
+ const response = await this.http.post('/api/ocr', { image: imageBase64 }).toPromise();
+ let deckImage: DeckImage;
+ if (response && response.results) {
+ const boxes: Box[] = [];
+ response.results.forEach((result: OcrResult) => {
+ const box = result.box;
+ const xs = box.map((point: number[]) => point[0]);
+ const ys = box.map((point: number[]) => point[1]);
+ const xMin = Math.min(...xs);
+ const xMax = Math.max(...xs);
+ const yMin = Math.min(...ys);
+ const yMax = Math.max(...ys);
+ boxes.push({ x1: xMin, x2: xMax, y1: yMin, y2: yMax, inserted: null, updated: null });
+ });
+ deckImage = {
+ name: 'Pasted Image',
+ bildid: response.results.length > 0 ? response.results[0].name : null,
+ boxes,
+ };
+ } else {
+ // Falls kein OCR-Ergebnis vorliegt, lege leere Boxen an
+ deckImage = {
+ name: 'Pasted Image',
+ bildid: null,
+ boxes: [],
+ };
+ }
+ // Setze imageData – dadurch wird in der Template der eingeblendet
+ this.imageData = { imageSrc, deckImage };
+ } catch (error) {
+ console.error('Error with OCR service:', error);
+ this.popoverService.show({
+ title: 'Error',
+ message: 'Error with OCR service.',
+ });
+ } finally {
+ this.loading = false;
+ }
+ };
+ reader.readAsDataURL(blob);
+ });
+ return; // Beende die Schleife, sobald ein Bild gefunden wurde.
+ }
+ }
+ }
+ // Falls kein Bild gefunden wurde:
+ this.popoverService.show({
+ title: 'Information',
+ message: 'Keine Bilddaten im Clipboard gefunden.',
+ });
+ })
+ .catch(err => {
+ console.error('Fehler beim Zugriff auf das Clipboard:', err);
+ this.popoverService.show({
+ title: 'Fehler',
+ message: 'Fehler beim Zugriff auf das Clipboard.',
+ });
+ });
+ }
+
// Methode zur Berechnung des nächsten Trainingsdatums
getNextTrainingDate(deck: Deck): Date {
const now = new Date();
diff --git a/src/app/deck.service.ts b/src/app/deck.service.ts
index 86e549c..03ca29d 100644
--- a/src/app/deck.service.ts
+++ b/src/app/deck.service.ts
@@ -117,6 +117,9 @@ export class DeckService {
renameDeck(oldDeckName: string, newDeckName: string): Observable {
return this.http.put(`${this.apiUrl}/${encodeURIComponent(oldDeckName)}/rename`, { newDeckName });
}
+ renameImage(bildid: string, newImageName: string): Observable {
+ return this.http.put(`${this.apiUrl}/image/${encodeURIComponent(bildid)}/rename`, { newImageName });
+ }
saveImageData(data: any): Observable {
return this.http.post(`${this.apiUrl}/image`, data);
}
diff --git a/src/app/edit-image-modal/edit-image-modal.component.html b/src/app/edit-image-modal/edit-image-modal.component.html
index 8c7ec2e..1b917c2 100644
--- a/src/app/edit-image-modal/edit-image-modal.component.html
+++ b/src/app/edit-image-modal/edit-image-modal.component.html
@@ -1,33 +1,42 @@
-
+
-
-
-
-
-
+
+
+
+
Edit Image 0">({{ boxes.length }} Box{{ boxes.length > 1 ? 'es' : '' }})
-
-
+
+
+
+
-
-
-
-
-
+
+
+
+
+
-
\ No newline at end of file
+
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 9bb6e58..c8dea92 100644
--- a/src/app/edit-image-modal/edit-image-modal.component.ts
+++ b/src/app/edit-image-modal/edit-image-modal.component.ts
@@ -37,6 +37,7 @@ export class EditImageModalComponent implements AfterViewInit, OnDestroy {
async ngAfterViewInit() {
this.modal = new Modal(this.modalElement.nativeElement, {
+ backdrop: 'static',
onHide: () => {
this.closed.emit();
},
diff --git a/src/index.html b/src/index.html
index 1dff7eb..cae2ee4 100644
--- a/src/index.html
+++ b/src/index.html
@@ -12,7 +12,7 @@
body {
margin: 0;
height: 100vh;
- background: linear-gradient(to bottom, #f8fafc, #fff7d6);
+ background: rgba(0, 119, 179, 0.1);
}