init release
This commit is contained in:
parent
435753880c
commit
21cde45999
|
|
@ -86,6 +86,9 @@
|
||||||
},
|
},
|
||||||
"serve": {
|
"serve": {
|
||||||
"builder": "@angular-devkit/build-angular:dev-server",
|
"builder": "@angular-devkit/build-angular:dev-server",
|
||||||
|
"options": {
|
||||||
|
"proxyConfig": "src/proxy.conf.json"
|
||||||
|
},
|
||||||
"configurations": {
|
"configurations": {
|
||||||
"production": {
|
"production": {
|
||||||
"buildTarget": "vokabeltraining:build:production"
|
"buildTarget": "vokabeltraining:build:production"
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -17,6 +17,7 @@
|
||||||
"@angular/platform-browser": "^18.2.0",
|
"@angular/platform-browser": "^18.2.0",
|
||||||
"@angular/platform-browser-dynamic": "^18.2.0",
|
"@angular/platform-browser-dynamic": "^18.2.0",
|
||||||
"@angular/router": "^18.2.0",
|
"@angular/router": "^18.2.0",
|
||||||
|
"flowbite": "^2.5.2",
|
||||||
"rxjs": "~7.8.0",
|
"rxjs": "~7.8.0",
|
||||||
"tslib": "^2.3.0",
|
"tslib": "^2.3.0",
|
||||||
"zone.js": "~0.14.10"
|
"zone.js": "~0.14.10"
|
||||||
|
|
@ -25,6 +26,7 @@
|
||||||
"@angular-devkit/build-angular": "^18.2.12",
|
"@angular-devkit/build-angular": "^18.2.12",
|
||||||
"@angular/cli": "^18.2.12",
|
"@angular/cli": "^18.2.12",
|
||||||
"@angular/compiler-cli": "^18.2.0",
|
"@angular/compiler-cli": "^18.2.0",
|
||||||
|
"tailwindcss": "^3.4.15",
|
||||||
"typescript": "~5.5.2"
|
"typescript": "~5.5.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,23 @@
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
import { Component } from '@angular/core';
|
import { Component } from '@angular/core';
|
||||||
import { RouterOutlet } from '@angular/router';
|
import { RouterOutlet } from '@angular/router';
|
||||||
|
import { initFlowbite } from 'flowbite';
|
||||||
|
import { DeckListComponent } from './deck-list.component';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-root',
|
selector: 'app-root',
|
||||||
standalone: true,
|
|
||||||
imports: [RouterOutlet],
|
|
||||||
template: `
|
template: `
|
||||||
<h1>Welcome to {{title}}!</h1>
|
<div class="container mx-auto p-4">
|
||||||
|
<h1 class="text-3xl font-bold text-center mb-8">Vokabeltraining</h1>
|
||||||
<router-outlet />
|
<app-deck-list></app-deck-list>
|
||||||
|
</div>
|
||||||
`,
|
`,
|
||||||
styles: [],
|
standalone: true,
|
||||||
|
imports: [ CommonModule, DeckListComponent]
|
||||||
})
|
})
|
||||||
export class AppComponent {
|
export class AppComponent {
|
||||||
title = 'vokabeltraining';
|
title = 'vokabeltraining';
|
||||||
|
ngOnInit(): void {
|
||||||
|
initFlowbite();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,8 @@ import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core';
|
||||||
import { provideRouter } from '@angular/router';
|
import { provideRouter } from '@angular/router';
|
||||||
|
|
||||||
import { routes } from './app.routes';
|
import { routes } from './app.routes';
|
||||||
|
import { provideHttpClient } from '@angular/common/http';
|
||||||
|
|
||||||
export const appConfig: ApplicationConfig = {
|
export const appConfig: ApplicationConfig = {
|
||||||
providers: [provideZoneChangeDetection({ eventCoalescing: true }), provideRouter(routes)]
|
providers: [provideZoneChangeDetection({ eventCoalescing: true }), provideRouter(routes),provideHttpClient()]
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
<!-- src/app/create-deck-modal.component.html -->
|
||||||
|
<div id="createDeckModal" tabindex="-1" aria-hidden="true" class="fixed top-0 left-0 right-0 z-50 hidden w-full p-4 overflow-x-hidden overflow-y-auto md:inset-0 h-modal md:h-full">
|
||||||
|
<div class="relative w-full h-full max-w-md md:h-auto">
|
||||||
|
<div class="relative bg-white rounded-lg shadow">
|
||||||
|
<button type="button" class="absolute top-3 right-2.5 text-gray-400 bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm p-1.5 ml-auto inline-flex items-center" data-modal-hide="createDeckModal">
|
||||||
|
<svg aria-hidden="true" class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
|
||||||
|
<path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd"></path>
|
||||||
|
</svg>
|
||||||
|
<span class="sr-only">Schließen</span>
|
||||||
|
</button>
|
||||||
|
<div class="p-6">
|
||||||
|
<h3 class="mb-4 text-xl font-medium text-gray-900">Neues Deck erstellen</h3>
|
||||||
|
<form (submit)="createDeck($event)">
|
||||||
|
<div class="mb-4">
|
||||||
|
<label for="deckName" class="block text-sm font-medium text-gray-700">Deck-Name</label>
|
||||||
|
<input type="text" id="deckName" [(ngModel)]="deckName" name="deckName" required class="mt-1 block w-full border border-gray-300 rounded-md shadow-sm p-2" />
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="w-full bg-blue-500 text-white py-2 px-4 rounded hover:bg-blue-600">
|
||||||
|
Erstellen
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
// src/app/create-deck-modal.component.ts
|
||||||
|
import { Component, Output, EventEmitter } from '@angular/core';
|
||||||
|
import { DeckService } from '../deck.service';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { FormsModule } from '@angular/forms';
|
||||||
|
import { Modal } from 'flowbite';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-create-deck-modal',
|
||||||
|
templateUrl: './create-deck-modal.component.html',
|
||||||
|
standalone: true,
|
||||||
|
imports: [CommonModule,FormsModule]
|
||||||
|
})
|
||||||
|
export class CreateDeckModalComponent {
|
||||||
|
@Output() deckCreated = new EventEmitter<void>();
|
||||||
|
deckName: string = '';
|
||||||
|
|
||||||
|
constructor(private deckService: DeckService) { }
|
||||||
|
|
||||||
|
createDeck(event: Event): void {
|
||||||
|
event.preventDefault();
|
||||||
|
if (this.deckName.trim() === '') {
|
||||||
|
alert('Bitte einen Deck-Namen eingeben.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.deckService.createDeck(this.deckName).subscribe({
|
||||||
|
next: () => {
|
||||||
|
this.deckName = '';
|
||||||
|
this.deckCreated.emit();
|
||||||
|
// Modal schließen
|
||||||
|
const modalElement = document.getElementById('createDeckModal');
|
||||||
|
if (modalElement) {
|
||||||
|
const modal = new Modal(modalElement);
|
||||||
|
modal.hide();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: (err) => {
|
||||||
|
console.error('Fehler beim Erstellen des Decks', err);
|
||||||
|
alert('Fehler beim Erstellen des Decks.');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,43 @@
|
||||||
|
<!-- src/app/deck-list.component.html -->
|
||||||
|
<div>
|
||||||
|
<!-- Button zum Erstellen eines neuen Decks -->
|
||||||
|
<div class="flex justify-end mb-4">
|
||||||
|
<button data-modal-target="createDeckModal" data-modal-toggle="createDeckModal" class="bg-green-500 text-white py-2 px-4 rounded hover:bg-green-600">
|
||||||
|
Neues Deck erstellen
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Decks anzeigen -->
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||||
|
<div *ngFor="let deck of decks" class="bg-white shadow rounded-lg p-6 flex flex-col">
|
||||||
|
<div class="flex justify-between items-center mb-4">
|
||||||
|
<h2 class="text-xl font-semibold">{{ deck.name }}</h2>
|
||||||
|
<button (click)="deleteDeck(deck.name)" class="text-red-500 hover:text-red-700">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="flex-1 mb-4">
|
||||||
|
<p class="text-gray-600">{{ deck.images.length }} Bilder</p>
|
||||||
|
</div>
|
||||||
|
<div class="flex space-x-2">
|
||||||
|
<button (click)="openTraining(deck)" class="flex-1 bg-blue-500 text-white py-2 px-4 rounded hover:bg-blue-600">
|
||||||
|
Training starten
|
||||||
|
</button>
|
||||||
|
<button [attr.data-modal-target]="'uploadImageModal'" [attr.data-modal-toggle]="'uploadImageModal'" [attr.data-deck-name]="deck.name" class="flex-1 bg-green-500 text-white py-2 px-4 rounded hover:bg-green-600">
|
||||||
|
Bild hinzufügen
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<!-- UploadImageModalComponent wird mit deck.name als Input geöffnet -->
|
||||||
|
<app-upload-image-modal [deckName]="deck.name" (imageUploaded)="loadDecks()"></app-upload-image-modal>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- CreateDeckModalComponent -->
|
||||||
|
<app-create-deck-modal (deckCreated)="loadDecks()"></app-create-deck-modal>
|
||||||
|
|
||||||
|
<!-- TrainingComponent -->
|
||||||
|
<app-training *ngIf="selectedDeck" [deck]="selectedDeck" (close)="closeTraining()"></app-training>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
@ -0,0 +1,58 @@
|
||||||
|
// src/app/deck-list.component.ts
|
||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import { DeckService, Deck } from './deck.service';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { HttpClientModule } from '@angular/common/http';
|
||||||
|
import { CreateDeckModalComponent } from './create-deck-modal/create-deck-modal.component';
|
||||||
|
import { TrainingComponent } from './training/training.component';
|
||||||
|
import { UploadImageModalComponent } from './upload-image-modal/upload-image-modal.component';
|
||||||
|
import { initFlowbite } from 'flowbite';
|
||||||
|
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-deck-list',
|
||||||
|
templateUrl: './deck-list.component.html',
|
||||||
|
standalone: true,
|
||||||
|
imports: [
|
||||||
|
CommonModule,
|
||||||
|
CreateDeckModalComponent,
|
||||||
|
UploadImageModalComponent,
|
||||||
|
TrainingComponent
|
||||||
|
]
|
||||||
|
})
|
||||||
|
export class DeckListComponent implements OnInit {
|
||||||
|
decks: Deck[] = [];
|
||||||
|
selectedDeck: Deck | null = null;
|
||||||
|
|
||||||
|
constructor(private deckService: DeckService) { }
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.loadDecks();
|
||||||
|
}
|
||||||
|
|
||||||
|
loadDecks(): void {
|
||||||
|
this.deckService.getDecks().subscribe({
|
||||||
|
next: (data) => this.decks = data,
|
||||||
|
error: (err) => console.error('Fehler beim Laden der Decks', err)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteDeck(deckName: string): void {
|
||||||
|
if (!confirm(`Bist du sicher, dass du das Deck "${deckName}" löschen möchtest?`)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.deckService.deleteDeck(deckName).subscribe({
|
||||||
|
next: () => this.loadDecks(),
|
||||||
|
error: (err) => console.error('Fehler beim Löschen des Decks', err)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
openTraining(deck: Deck): void {
|
||||||
|
this.selectedDeck = deck;
|
||||||
|
}
|
||||||
|
|
||||||
|
closeTraining(): void {
|
||||||
|
this.selectedDeck = null;
|
||||||
|
this.loadDecks();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,42 @@
|
||||||
|
// src/app/deck.service.ts
|
||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { HttpClient } from '@angular/common/http';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
|
||||||
|
export interface Deck {
|
||||||
|
name: string;
|
||||||
|
images: DeckImage[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DeckImage {
|
||||||
|
image: string;
|
||||||
|
text: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class DeckService {
|
||||||
|
private apiUrl = '/api/decks';
|
||||||
|
|
||||||
|
constructor(private http: HttpClient) { }
|
||||||
|
|
||||||
|
getDecks(): Observable<Deck[]> {
|
||||||
|
return this.http.get<Deck[]>(this.apiUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
createDeck(deckname: string): Observable<any> {
|
||||||
|
return this.http.post(this.apiUrl, { deckname });
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteDeck(deckName: string): Observable<any> {
|
||||||
|
return this.http.delete(`${this.apiUrl}/${encodeURIComponent(deckName)}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
uploadImage(deckName: string, image: File, text: string): Observable<any> {
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('image', image);
|
||||||
|
formData.append('text', text);
|
||||||
|
return this.http.post(`${this.apiUrl}/${encodeURIComponent(deckName)}/images`, formData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
<!-- src/app/training.component.html -->
|
||||||
|
<div class="mt-10">
|
||||||
|
<h2 class="text-2xl font-bold mb-4">Training: {{ deck.name }}</h2>
|
||||||
|
<div class="bg-white shadow rounded-lg p-6 flex flex-col items-center">
|
||||||
|
<img [src]="currentImage?.image" alt="Vokabelbild" class="w-64 h-64 object-contain mb-4">
|
||||||
|
<div *ngIf="showTextFlag" class="bg-black bg-opacity-50 text-white text-lg p-2 rounded mb-4">
|
||||||
|
{{ currentImage?.text }}
|
||||||
|
</div>
|
||||||
|
<div class="flex space-x-4">
|
||||||
|
<button (click)="showText()" class="bg-green-500 text-white py-2 px-4 rounded hover:bg-green-600" [disabled]="showTextFlag">
|
||||||
|
Anzeigen
|
||||||
|
</button>
|
||||||
|
<button (click)="markKnown()" class="bg-blue-500 text-white py-2 px-4 rounded hover:bg-blue-600">
|
||||||
|
Gewusst
|
||||||
|
</button>
|
||||||
|
<button (click)="markUnknown()" class="bg-red-500 text-white py-2 px-4 rounded hover:bg-red-600">
|
||||||
|
Nicht gewusst
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<p class="mt-4">{{ progress }}</p>
|
||||||
|
<button (click)="closeTraining()" class="mt-4 text-gray-500 hover:text-gray-700 underline">Training beenden</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
@ -0,0 +1,62 @@
|
||||||
|
// src/app/training.component.ts
|
||||||
|
import { Component, Input, Output, EventEmitter } from '@angular/core';
|
||||||
|
import { Deck, DeckImage } from '../deck.service';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-training',
|
||||||
|
templateUrl: './training.component.html',
|
||||||
|
standalone: true,
|
||||||
|
imports: [CommonModule]
|
||||||
|
})
|
||||||
|
export class TrainingComponent {
|
||||||
|
@Input() deck!: Deck;
|
||||||
|
@Output() close = new EventEmitter<void>();
|
||||||
|
|
||||||
|
currentIndex: number = 0;
|
||||||
|
knownCount: number = 0;
|
||||||
|
unknownCount: number = 0;
|
||||||
|
showTextFlag: boolean = false;
|
||||||
|
|
||||||
|
get currentImage(): DeckImage | null {
|
||||||
|
if (this.currentIndex < this.deck.images.length) {
|
||||||
|
return this.deck.images[this.currentIndex];
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
get progress(): string {
|
||||||
|
return `Fortschritt: ${this.currentIndex} / ${this.deck.images.length}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
showText(): void {
|
||||||
|
this.showTextFlag = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
markKnown(): void {
|
||||||
|
this.knownCount++;
|
||||||
|
this.nextImage();
|
||||||
|
}
|
||||||
|
|
||||||
|
markUnknown(): void {
|
||||||
|
this.unknownCount++;
|
||||||
|
this.nextImage();
|
||||||
|
}
|
||||||
|
|
||||||
|
nextImage(): void {
|
||||||
|
this.currentIndex++;
|
||||||
|
this.showTextFlag = false;
|
||||||
|
if (this.currentIndex >= this.deck.images.length) {
|
||||||
|
this.endTraining();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
endTraining(): void {
|
||||||
|
alert(`Training beendet!\nGewusst: ${this.knownCount}\nNicht gewusst: ${this.unknownCount}`);
|
||||||
|
this.close.emit();
|
||||||
|
}
|
||||||
|
|
||||||
|
closeTraining(): void {
|
||||||
|
this.close.emit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
<!-- src/app/upload-image-modal.component.html -->
|
||||||
|
<div id="uploadImageModal" tabindex="-1" aria-hidden="true" class="fixed top-0 left-0 right-0 z-50 hidden w-full p-4 overflow-x-hidden overflow-y-auto md:inset-0 h-modal md:h-full">
|
||||||
|
<div class="relative w-full h-full max-w-md md:h-auto">
|
||||||
|
<div class="relative bg-white rounded-lg shadow">
|
||||||
|
<button type="button" class="absolute top-3 right-2.5 text-gray-400 bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm p-1.5 ml-auto inline-flex items-center" data-modal-hide="uploadImageModal">
|
||||||
|
<svg aria-hidden="true" class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
|
||||||
|
<path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd"></path>
|
||||||
|
</svg>
|
||||||
|
<span class="sr-only">Schließen</span>
|
||||||
|
</button>
|
||||||
|
<div class="p-6">
|
||||||
|
<h3 class="mb-4 text-xl font-medium text-gray-900">Bild zu Deck hinzufügen</h3>
|
||||||
|
<form (submit)="uploadImage($event)">
|
||||||
|
<div class="mb-4">
|
||||||
|
<label for="imageFile" class="block text-sm font-medium text-gray-700">Bild hochladen</label>
|
||||||
|
<input type="file" id="imageFile" (change)="onFileChange($event)" accept="image/*" required class="mt-1 block w-full border border-gray-300 rounded-md shadow-sm p-2" />
|
||||||
|
</div>
|
||||||
|
<div class="mb-4">
|
||||||
|
<label for="imageText" class="block text-sm font-medium text-gray-700">Text zur Vokabel</label>
|
||||||
|
<input type="text" id="imageText" [(ngModel)]="imageText" name="imageText" required class="mt-1 block w-full border border-gray-300 rounded-md shadow-sm p-2" />
|
||||||
|
</div>
|
||||||
|
<input type="hidden" [value]="deckName" />
|
||||||
|
<button type="submit" class="w-full bg-blue-500 text-white py-2 px-4 rounded hover:bg-blue-600">
|
||||||
|
Hochladen
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
@ -0,0 +1,54 @@
|
||||||
|
// src/app/upload-image-modal.component.ts
|
||||||
|
import { Component, Input, Output, EventEmitter } from '@angular/core';
|
||||||
|
import { DeckService } from '../deck.service';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { FormsModule } from '@angular/forms';
|
||||||
|
import { Modal } from 'flowbite';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-upload-image-modal',
|
||||||
|
templateUrl: './upload-image-modal.component.html',
|
||||||
|
standalone: true,
|
||||||
|
imports: [CommonModule,FormsModule]
|
||||||
|
})
|
||||||
|
export class UploadImageModalComponent {
|
||||||
|
@Input() deckName: string = '';
|
||||||
|
@Output() imageUploaded = new EventEmitter<void>();
|
||||||
|
imageFile: File | null = null;
|
||||||
|
imageText: string = '';
|
||||||
|
|
||||||
|
constructor(private deckService: DeckService) { }
|
||||||
|
|
||||||
|
onFileChange(event: Event): void {
|
||||||
|
const input = event.target as HTMLInputElement;
|
||||||
|
if (input.files && input.files.length) {
|
||||||
|
this.imageFile = input.files[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uploadImage(event: Event): void {
|
||||||
|
event.preventDefault();
|
||||||
|
if (!this.imageFile || this.imageText.trim() === '') {
|
||||||
|
alert('Bitte ein Bild und den zugehörigen Text angeben.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.deckService.uploadImage(this.deckName, this.imageFile, this.imageText).subscribe({
|
||||||
|
next: () => {
|
||||||
|
this.imageFile = null;
|
||||||
|
this.imageText = '';
|
||||||
|
this.imageUploaded.emit();
|
||||||
|
// Modal schließen
|
||||||
|
const modalElement = document.getElementById('uploadImageModal');
|
||||||
|
if (modalElement) {
|
||||||
|
const modal = new Modal(modalElement);
|
||||||
|
modal.hide();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: (err) => {
|
||||||
|
console.error('Fehler beim Hochladen des Bildes', err);
|
||||||
|
alert('Fehler beim Hochladen des Bildes.');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"/api": {
|
||||||
|
"target": "http://localhost:5000",
|
||||||
|
"secure": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1 +1,4 @@
|
||||||
/* You can add global styles to this file, and also import other style files */
|
/* You can add global styles to this file, and also import other style files */
|
||||||
|
@tailwind base;
|
||||||
|
@tailwind components;
|
||||||
|
@tailwind utilities;
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
/** @type {import('tailwindcss').Config} */
|
||||||
|
module.exports = {
|
||||||
|
content: [
|
||||||
|
"./src/**/*.{html,ts}",
|
||||||
|
"./node_modules/flowbite/**/*.js" // add this line
|
||||||
|
],
|
||||||
|
theme: {
|
||||||
|
extend: {},
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
require('flowbite/plugin')
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */
|
|
||||||
/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */
|
|
||||||
{
|
{
|
||||||
"compileOnSave": false,
|
"compileOnSave": false,
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue