From 576689a3edf9caca36a3d483538d1a358d0d44c8 Mon Sep 17 00:00:00 2001 From: Your Name Date: Sun, 2 Feb 2025 17:28:20 +0100 Subject: [PATCH] #25 rename image, #24 Paste Image form Clipboard, keep selekted Deck --- api/src/app/decks.controller.ts | 9 ++ api/src/app/drizzle.service.ts | 25 ++++ src/app/app.component.ts | 43 +----- src/app/deck-list.component.html | 21 ++- src/app/deck-list.component.ts | 132 +++++++++++++++++- src/app/deck.service.ts | 3 + .../edit-image-modal.component.html | 55 +++++--- .../edit-image-modal.component.ts | 1 + src/index.html | 2 +- 9 files changed, 220 insertions(+), 71 deletions(-) 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'; 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 @@ -