Training + Liste erneuert
This commit is contained in:
parent
2c7fbac29c
commit
ab021cb4c2
|
|
@ -18,9 +18,31 @@
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-1 mb-4">
|
|
||||||
<p class="text-gray-600">{{ deck.images.length }} Bilder</p>
|
<!-- Liste der Bilder mit Anzahl der Boxen und Icons -->
|
||||||
</div>
|
<ul class="mb-4">
|
||||||
|
<li *ngFor="let image of deck.images" class="flex justify-between items-center py-2 border-b last:border-b-0">
|
||||||
|
<div>
|
||||||
|
<span class="font-medium">{{ image.name }}</span>
|
||||||
|
<span class="text-gray-600"> ({{ image.boxes?.length }} Boxen)</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex space-x-2">
|
||||||
|
<!-- Edit Icon -->
|
||||||
|
<button class="text-blue-500 hover:text-blue-700" title="Bild bearbeiten">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 4H4v7m0 0l9-9 9 9M20 13v7h-7m0 0l-9-9-9 9" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<!-- Delete Icon -->
|
||||||
|
<button (click)="deleteImage(deck, image)" class="text-red-500 hover:text-red-700" title="Bild löschen">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" 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>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
<div class="flex space-x-2">
|
<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">
|
<button (click)="openTraining(deck)" class="flex-1 bg-blue-500 text-white py-2 px-4 rounded hover:bg-blue-600">
|
||||||
Training starten
|
Training starten
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,11 @@
|
||||||
// src/app/deck-list.component.ts
|
// src/app/deck-list.component.ts
|
||||||
import { Component, OnInit, ViewChild } from '@angular/core';
|
import { Component, OnInit, ViewChild } from '@angular/core';
|
||||||
import { DeckService, Deck } from './deck.service';
|
import { DeckService, Deck, DeckImage } from './deck.service';
|
||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
import { CreateDeckModalComponent } from './create-deck-modal/create-deck-modal.component';
|
import { CreateDeckModalComponent } from './create-deck-modal/create-deck-modal.component';
|
||||||
import { TrainingComponent } from './training/training.component';
|
import { TrainingComponent } from './training/training.component';
|
||||||
import { UploadImageModalComponent } from './upload-image-modal/upload-image-modal.component';
|
import { UploadImageModalComponent } from './upload-image-modal/upload-image-modal.component';
|
||||||
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-deck-list',
|
selector: 'app-deck-list',
|
||||||
templateUrl: './deck-list.component.html',
|
templateUrl: './deck-list.component.html',
|
||||||
|
|
@ -50,6 +49,22 @@ export class DeckListComponent implements OnInit {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Neue Methode zum Löschen eines Bildes
|
||||||
|
deleteImage(deck: Deck, image: DeckImage): void {
|
||||||
|
if (!confirm(`Bist du sicher, dass du das Bild "${image.name}" löschen möchtest?`)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hier gehen wir davon aus, dass das Deck eine eindeutige ID hat. Falls nicht, passe den Code entsprechend an.
|
||||||
|
const deckId = deck.id; // Stelle sicher, dass das Deck eine `id` hat
|
||||||
|
const imageName = image.name; // Stelle sicher, dass das Bild eine `id` hat
|
||||||
|
|
||||||
|
this.deckService.deleteImage(imageName).subscribe({
|
||||||
|
next: () => this.loadDecks(),
|
||||||
|
error: (err) => console.error('Fehler beim Löschen des Bildes', err)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
openTraining(deck: Deck): void {
|
openTraining(deck: Deck): void {
|
||||||
this.selectedDeck = deck;
|
this.selectedDeck = deck;
|
||||||
}
|
}
|
||||||
|
|
@ -69,4 +84,3 @@ export class DeckListComponent implements OnInit {
|
||||||
this.uploadImageModal.open();
|
this.uploadImageModal.open();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,10 @@
|
||||||
// src/app/deck.service.ts
|
// src/app/deck.service.ts
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { HttpClient } from '@angular/common/http';
|
import { HttpClient } from '@angular/common/http';
|
||||||
import { Observable } from 'rxjs';
|
import { map, Observable, switchMap } from 'rxjs';
|
||||||
|
|
||||||
export interface Deck {
|
export interface Deck {
|
||||||
|
id: number; // Hinzugefügt
|
||||||
name: string;
|
name: string;
|
||||||
images: DeckImage[];
|
images: DeckImage[];
|
||||||
}
|
}
|
||||||
|
|
@ -18,6 +19,17 @@ export interface Box {
|
||||||
y1:number;
|
y1:number;
|
||||||
y2:number;
|
y2:number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface BackendBox {
|
||||||
|
bildname: string;
|
||||||
|
deckid: number;
|
||||||
|
iconindex: number;
|
||||||
|
id: number;
|
||||||
|
x1: number;
|
||||||
|
x2: number;
|
||||||
|
y1: number;
|
||||||
|
y2: number;
|
||||||
|
}
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: 'root'
|
||||||
})
|
})
|
||||||
|
|
@ -27,10 +39,62 @@ export class DeckService {
|
||||||
constructor(private http: HttpClient) { }
|
constructor(private http: HttpClient) { }
|
||||||
|
|
||||||
getDecks(): Observable<Deck[]> {
|
getDecks(): Observable<Deck[]> {
|
||||||
return this.http.get<Deck[]>(this.apiUrl);
|
return this.http.get<any[]>(this.apiUrl).pipe(
|
||||||
|
map(decks => decks.map(deck => ({
|
||||||
|
id: deck.id, // Annahme: Jeder Deck hat eine eindeutige ID
|
||||||
|
name: deck.name,
|
||||||
|
images: this.groupImagesByName(deck.images)
|
||||||
|
})))
|
||||||
|
);
|
||||||
}
|
}
|
||||||
getImage(name:string): Observable<DeckImage> {
|
private groupImagesByName(images: any[]): DeckImage[] {
|
||||||
return this.http.get<DeckImage>(`${this.apiUrl}/image/${name}`);
|
const imageMap: { [key: string]: DeckImage } = {};
|
||||||
|
|
||||||
|
images.forEach(image => {
|
||||||
|
if (!imageMap[image.name]) {
|
||||||
|
imageMap[image.name] = {
|
||||||
|
name: image.name,
|
||||||
|
boxes: []
|
||||||
|
};
|
||||||
|
}
|
||||||
|
imageMap[image.name].boxes.push({
|
||||||
|
x1: image.x1,
|
||||||
|
x2: image.x2,
|
||||||
|
y1: image.y1,
|
||||||
|
y2: image.y2
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return Object.values(imageMap);
|
||||||
|
}
|
||||||
|
getDeck(deckname:string): Observable<Deck> {
|
||||||
|
return this.http.get<Deck>(`${this.apiUrl}/${deckname}/images`);
|
||||||
|
}
|
||||||
|
getImage(name: string): Observable<DeckImage> {
|
||||||
|
return this.http.get<BackendBox[]>(`${this.apiUrl}/image/${name}`).pipe(
|
||||||
|
map(response => {
|
||||||
|
if (response.length === 0) {
|
||||||
|
// Falls keine Daten zurückgegeben werden, ein leeres DeckImage zurückgeben
|
||||||
|
return { name: name, boxes: [] };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extrahiere den Bildnamen aus dem ersten Element des Arrays
|
||||||
|
const imageName = response[0].bildname;
|
||||||
|
|
||||||
|
// Mape die Backend-Daten auf das Box-Interface
|
||||||
|
const boxes: Box[] = response.map(item => ({
|
||||||
|
x1: item.x1,
|
||||||
|
x2: item.x2,
|
||||||
|
y1: item.y1,
|
||||||
|
y2: item.y2
|
||||||
|
}));
|
||||||
|
|
||||||
|
return {
|
||||||
|
name: imageName,
|
||||||
|
boxes: boxes
|
||||||
|
};
|
||||||
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
createDeck(deckname: string): Observable<any> {
|
createDeck(deckname: string): Observable<any> {
|
||||||
return this.http.post(this.apiUrl, { deckname });
|
return this.http.post(this.apiUrl, { deckname });
|
||||||
|
|
@ -43,4 +107,9 @@ export class DeckService {
|
||||||
saveImageData(data:any): Observable<any> {
|
saveImageData(data:any): Observable<any> {
|
||||||
return this.http.post(`${this.apiUrl}/image`, data);
|
return this.http.post(`${this.apiUrl}/image`, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Neue Methode zum Löschen eines Bildes
|
||||||
|
deleteImage(imageName: string): Observable<any> {
|
||||||
|
return this.http.delete(`${this.apiUrl}/image/${imageName}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,24 +1,46 @@
|
||||||
<!-- src/app/training.component.html -->
|
<!-- src/app/training.component.html -->
|
||||||
<div class="mt-10">
|
<div class="mt-10">
|
||||||
<h2 class="text-2xl font-bold mb-4">Training: {{ deck.name }}</h2>
|
<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">
|
<div class="bg-white shadow rounded-lg p-6 flex flex-col items-center">
|
||||||
<img [src]="currentImage" alt="Vokabelbild" class="max-h-[50vh] object-contain mb-4">
|
<canvas #canvas class="mb-4 border max-h-[50vh]"></canvas>
|
||||||
<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>
|
|
||||||
|
|
||||||
|
<div class="flex space-x-4 mb-4">
|
||||||
|
<!-- Anzeigen Button -->
|
||||||
|
<button
|
||||||
|
(click)="showText()"
|
||||||
|
class="bg-green-500 disabled:bg-green-200 text-white py-2 px-4 rounded hover:bg-green-600"
|
||||||
|
[disabled]="isShowingBox || currentBoxIndex >= boxes.length"
|
||||||
|
>
|
||||||
|
Anzeigen
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<!-- Gewusst Button -->
|
||||||
|
<button
|
||||||
|
(click)="markKnown()"
|
||||||
|
class="bg-blue-500 disabled:bg-blue-200 text-white py-2 px-4 rounded hover:bg-blue-600"
|
||||||
|
[disabled]="!isShowingBox || currentBoxIndex >= boxes.length"
|
||||||
|
>
|
||||||
|
Gewusst
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<!-- Nicht gewusst Button -->
|
||||||
|
<button
|
||||||
|
(click)="markUnknown()"
|
||||||
|
class="bg-red-500 disabled:bg-red-200 text-white py-2 px-4 rounded hover:bg-red-600"
|
||||||
|
[disabled]="!isShowingBox || currentBoxIndex >= boxes.length"
|
||||||
|
>
|
||||||
|
Nicht gewusst
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p class="mt-2">{{ progress }}</p>
|
||||||
|
<p class="mt-2">Gewusst: {{ knownCount }} | Nicht gewusst: {{ unknownCount }}</p>
|
||||||
|
|
||||||
|
<button
|
||||||
|
(click)="closeTraining()"
|
||||||
|
class="mt-4 text-gray-500 hover:text-gray-700 underline"
|
||||||
|
>
|
||||||
|
Training beenden
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
// src/app/training.component.ts
|
// src/app/training.component.ts
|
||||||
import { Component, Input, Output, EventEmitter } from '@angular/core';
|
import { Component, Input, Output, EventEmitter, OnInit, ViewChild, ElementRef } from '@angular/core';
|
||||||
import { Deck, DeckImage } from '../deck.service';
|
import { Deck, DeckImage, DeckService, Box } from '../deck.service';
|
||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { switchMap } from 'rxjs/operators';
|
||||||
|
import { forkJoin } from 'rxjs';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-training',
|
selector: 'app-training',
|
||||||
|
|
@ -9,54 +11,148 @@ import { CommonModule } from '@angular/common';
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [CommonModule]
|
imports: [CommonModule]
|
||||||
})
|
})
|
||||||
export class TrainingComponent {
|
export class TrainingComponent implements OnInit {
|
||||||
@Input() deck!: Deck;
|
@Input() deck!: Deck;
|
||||||
@Output() close = new EventEmitter<void>();
|
@Output() close = new EventEmitter<void>();
|
||||||
|
|
||||||
currentIndex: number = 0;
|
@ViewChild('canvas') canvasRef!: ElementRef<HTMLCanvasElement>;
|
||||||
|
|
||||||
|
currentImageIndex: number = 0;
|
||||||
|
currentImageData: DeckImage | null = null;
|
||||||
|
currentBoxIndex: number = 0;
|
||||||
|
boxes: Box[] = [];
|
||||||
|
boxRevealed: boolean[] = [];
|
||||||
|
|
||||||
knownCount: number = 0;
|
knownCount: number = 0;
|
||||||
unknownCount: number = 0;
|
unknownCount: number = 0;
|
||||||
showTextFlag: boolean = false;
|
|
||||||
|
|
||||||
get currentImage(): string | null {
|
isShowingBox: boolean = false;
|
||||||
if (this.currentIndex < this.deck.images.length) {
|
isTrainingFinished: boolean = false;
|
||||||
return `/api/debug_image/${this.deck.images[this.currentIndex].name}`;
|
|
||||||
|
constructor(private deckService: DeckService) { }
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
if (this.deck && this.deck.images.length > 0) {
|
||||||
|
this.loadImage(this.currentImageIndex);
|
||||||
|
} else {
|
||||||
|
alert('Kein Deck oder keine Bilder vorhanden.');
|
||||||
|
this.close.emit();
|
||||||
}
|
}
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get progress(): string {
|
loadImage(imageIndex: number): void {
|
||||||
return `Fortschritt: ${this.currentIndex} / ${this.deck.images.length}`;
|
if (imageIndex >= this.deck.images.length) {
|
||||||
|
this.endTraining();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const imageName = this.deck.images[imageIndex].name;
|
||||||
|
this.deckService.getImage(imageName).subscribe({
|
||||||
|
next: (imageData: DeckImage) => {
|
||||||
|
this.currentImageData = imageData;
|
||||||
|
this.boxes = imageData.boxes;
|
||||||
|
this.boxRevealed = new Array(this.boxes.length).fill(false);
|
||||||
|
this.currentBoxIndex = 0;
|
||||||
|
this.isShowingBox = false;
|
||||||
|
this.drawCanvas();
|
||||||
|
},
|
||||||
|
error: (err) => {
|
||||||
|
console.error('Fehler beim Laden des Bildes:', err);
|
||||||
|
alert('Fehler beim Laden des Bildes.');
|
||||||
|
this.close.emit();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
drawCanvas(): void {
|
||||||
|
const canvas = this.canvasRef.nativeElement;
|
||||||
|
const ctx = canvas.getContext('2d');
|
||||||
|
if (!ctx || !this.currentImageData) return;
|
||||||
|
|
||||||
|
const img = new Image();
|
||||||
|
img.src = `/api/debug_image/${this.currentImageData.name}`;
|
||||||
|
img.onload = () => {
|
||||||
|
// Set canvas size to image size
|
||||||
|
canvas.width = img.width;
|
||||||
|
canvas.height = img.height;
|
||||||
|
|
||||||
|
// Draw image
|
||||||
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||||
|
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
|
||||||
|
|
||||||
|
// Draw boxes
|
||||||
|
this.boxes.forEach((box, index) => {
|
||||||
|
if (this.boxRevealed[index]) {
|
||||||
|
// Box ist bereits enthüllt, nichts zeichnen
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.rect(box.x1, box.y1, box.x2 - box.x1, box.y2 - box.y1);
|
||||||
|
ctx.fillStyle = index === this.currentBoxIndex ? 'rgba(0, 255, 0, 0.99)' : 'rgba(255, 0, 0, 0.99)';
|
||||||
|
ctx.fill();
|
||||||
|
ctx.lineWidth = 2;
|
||||||
|
ctx.strokeStyle = 'black';
|
||||||
|
ctx.stroke();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
img.onerror = () => {
|
||||||
|
console.error('Fehler beim Laden des Bildes für Canvas.');
|
||||||
|
alert('Fehler beim Laden des Bildes für Canvas.');
|
||||||
|
this.close.emit();
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
showText(): void {
|
showText(): void {
|
||||||
this.showTextFlag = true;
|
if (this.currentBoxIndex >= this.boxes.length) return;
|
||||||
|
this.boxRevealed[this.currentBoxIndex] = true;
|
||||||
|
this.isShowingBox = true;
|
||||||
|
this.drawCanvas();
|
||||||
}
|
}
|
||||||
|
|
||||||
markKnown(): void {
|
markKnown(): void {
|
||||||
this.knownCount++;
|
this.knownCount++;
|
||||||
this.nextImage();
|
this.nextBox();
|
||||||
}
|
}
|
||||||
|
|
||||||
markUnknown(): void {
|
markUnknown(): void {
|
||||||
this.unknownCount++;
|
this.unknownCount++;
|
||||||
this.nextImage();
|
this.nextBox();
|
||||||
}
|
}
|
||||||
|
|
||||||
nextImage(): void {
|
nextBox(): void {
|
||||||
this.currentIndex++;
|
this.currentBoxIndex++;
|
||||||
this.showTextFlag = false;
|
this.isShowingBox = false;
|
||||||
if (this.currentIndex >= this.deck.images.length) {
|
|
||||||
this.endTraining();
|
if (this.currentBoxIndex >= this.boxes.length) {
|
||||||
|
// Alle Boxen für dieses Bild sind bearbeitet
|
||||||
|
this.nextImage();
|
||||||
|
} else {
|
||||||
|
// Aktualisiere die Farben der Boxen
|
||||||
|
this.drawCanvas();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
nextImage(): void {
|
||||||
|
this.currentImageIndex++;
|
||||||
|
this.loadImage(this.currentImageIndex);
|
||||||
|
}
|
||||||
|
|
||||||
endTraining(): void {
|
endTraining(): void {
|
||||||
|
this.isTrainingFinished = true;
|
||||||
alert(`Training beendet!\nGewusst: ${this.knownCount}\nNicht gewusst: ${this.unknownCount}`);
|
alert(`Training beendet!\nGewusst: ${this.knownCount}\nNicht gewusst: ${this.unknownCount}`);
|
||||||
this.close.emit();
|
this.close.emit();
|
||||||
}
|
}
|
||||||
|
|
||||||
closeTraining(): void {
|
closeTraining(): void {
|
||||||
this.close.emit();
|
if (confirm('Möchtest du das Training wirklich beenden?')) {
|
||||||
|
this.close.emit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get progress(): string {
|
||||||
|
return `Bild ${this.currentImageIndex + 1} von ${this.deck.images.length}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
<!-- src/app/upload-image-modal.component.html -->
|
<!-- src/app/upload-image-modal.component.html -->
|
||||||
<div #uploadImageModal 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 #uploadImageModal 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 md:h-auto">
|
<div class="relative h-full contents">
|
||||||
<div class="relative bg-white rounded-lg shadow">
|
<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" (click)="closeModal()">
|
<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" (click)="closeModal()">
|
||||||
<svg aria-hidden="true" class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
|
<svg aria-hidden="true" class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue