🐛 Fix edit modal and drag-drop files
continuous-integration/drone/push Build is passing Details

timeline
Alexis Feron 2 weeks ago
parent e030dadca4
commit 26cf6d2c91

@ -119,7 +119,9 @@
>Images</label >Images</label
> >
<app-drag-drop <app-drag-drop
[initialFiles]="getFileNames()"
(filesSelected)="onFilesReceived($event)" (filesSelected)="onFilesReceived($event)"
(fileRemoved)="removeFile($event)"
></app-drag-drop> ></app-drag-drop>
</div> </div>

@ -1,5 +1,11 @@
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { Component, Input, OnInit } from '@angular/core'; import {
AfterViewInit,
Component,
Input,
OnInit,
ViewChild,
} from '@angular/core';
import { import {
FormBuilder, FormBuilder,
FormControl, FormControl,
@ -28,11 +34,12 @@ import { DragDropComponent } from '../drag-drop/drag-drop.component';
templateUrl: './add-pin-popup.component.html', templateUrl: './add-pin-popup.component.html',
}) })
export class AddPinPopupComponent implements OnInit { export class AddPinPopupComponent implements OnInit {
@ViewChild(DragDropComponent) dragDropComponent!: DragDropComponent;
form: FormGroup; form: FormGroup;
suggestions: any[] = []; suggestions: any[] = [];
inputFocused: boolean = false; inputFocused: boolean = false;
@Input() isHomePage: boolean = false; @Input() isHomePage: boolean = false;
files: any[] = []; files: File[] = [];
isPinModalOpen: boolean = false; isPinModalOpen: boolean = false;
modalId: string = 'add-pin-modal'; modalId: string = 'add-pin-modal';
private modalSub!: Subscription; private modalSub!: Subscription;
@ -76,6 +83,13 @@ export class AddPinPopupComponent implements OnInit {
if (images && images.length > 0) { if (images && images.length > 0) {
this.files = images; this.files = images;
this.form.patchValue({ files: images }); this.form.patchValue({ files: images });
// Convertir les fichiers en FileList pour le composant drag-drop
const dataTransfer = new DataTransfer();
images.forEach((file) => dataTransfer.items.add(file));
const fileList = dataTransfer.files;
if (this.dragDropComponent) {
this.dragDropComponent.updateFileNamesFromFileList(fileList);
}
} }
} }
}); });
@ -113,6 +127,12 @@ export class AddPinPopupComponent implements OnInit {
async onFilesReceived(files: FileList): Promise<void> { async onFilesReceived(files: FileList): Promise<void> {
this.files = Array.from(files); this.files = Array.from(files);
if (this.dragDropComponent) {
this.dragDropComponent.updateFileNamesFromFileList(files);
} else {
console.warn('AddPinPopupComponent - dragDropComponent not available');
}
for (let i = 0; i < this.files.length; i++) { for (let i = 0; i < this.files.length; i++) {
try { try {
const data = await this.exifService.getLocation(this.files[i]); const data = await this.exifService.getLocation(this.files[i]);
@ -121,15 +141,21 @@ export class AddPinPopupComponent implements OnInit {
.getAddressFromCoordinates(data.latitude, data.longitude) .getAddressFromCoordinates(data.latitude, data.longitude)
.toPromise(); .toPromise();
if (address) { if (address) {
console.error('Data : ' + JSON.stringify(address));
this.form.get('location')?.setValue(address.display_name); this.form.get('location')?.setValue(address.display_name);
break; break;
} }
} }
} catch (error) { } catch (error) {
console.error('Error : ' + error); console.error(
'AddPinPopupComponent - Error processing EXIF data:',
error
);
}
} }
} }
getFileNames(): string[] {
return this.files.map((file) => file.name);
} }
ngOnDestroy() { ngOnDestroy() {
@ -137,17 +163,6 @@ export class AddPinPopupComponent implements OnInit {
} }
submitForm(): void { submitForm(): void {
// if (this.form.valid) {
// this.files = this.files.map((file) => {
// // return file.name; //TODO: Mettre le hash du fichier
// this.imageService.postImage(file).subscribe((response) => {
// // console.log('Image uploaded:', response);
// let imageId = response.json();
// return imageId.id;
// });
// });
// console.log('Files : ' + JSON.stringify(this.files));
if (this.form.valid) { if (this.form.valid) {
const uploadObservables = this.files.map((file) => const uploadObservables = this.files.map((file) =>
this.imageService.postImage(file) this.imageService.postImage(file)
@ -163,9 +178,6 @@ export class AddPinPopupComponent implements OnInit {
date: this.form.get('date')?.value || null, date: this.form.get('date')?.value || null,
}; };
console.log('Files : ' + JSON.stringify(this.files));
console.log('Pin Data : ' + JSON.stringify(pinData));
this.pinService.addPin(pinData)?.subscribe(() => { this.pinService.addPin(pinData)?.subscribe(() => {
this.mapReloadService.requestReload(); // Demander le rechargement de la carte this.mapReloadService.requestReload(); // Demander le rechargement de la carte
this.closePinModal(); this.closePinModal();
@ -190,4 +202,16 @@ export class AddPinPopupComponent implements OnInit {
getImagePreview(file: File): string { getImagePreview(file: File): string {
return URL.createObjectURL(file); return URL.createObjectURL(file);
} }
removeFile(fileName: string): void {
const index = this.files.findIndex((file) => file.name === fileName);
if (index > -1) {
this.files.splice(index, 1);
// Mettre à jour le form control
const dataTransfer = new DataTransfer();
this.files.forEach((file) => dataTransfer.items.add(file));
this.form.patchValue({ files: dataTransfer.files });
}
}
} }

@ -50,7 +50,7 @@
<button <button
type="button" type="button"
class="end-2.5 text-gray-400 bg-transparent hover:text-gray-900 rounded-lg text-sm w-6 h-6 ms-auto inline-flex justify-center items-center dark:hover:text-white" class="end-2.5 text-gray-400 bg-transparent hover:text-gray-900 rounded-lg text-sm w-6 h-6 ms-auto inline-flex justify-center items-center dark:hover:text-white"
(click)="removeFile(fileName)" (click)="removeFile(fileName, $event)"
> >
<svg <svg
class="w-2 h-2" class="w-2 h-2"

@ -1,14 +1,35 @@
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { Component, EventEmitter, Output } from '@angular/core'; import {
Component,
EventEmitter,
Input,
OnChanges,
Output,
SimpleChanges,
} from '@angular/core';
@Component({ @Component({
selector: 'app-drag-drop', selector: 'app-drag-drop',
imports: [CommonModule], imports: [CommonModule],
templateUrl: './drag-drop.component.html', templateUrl: './drag-drop.component.html',
}) })
export class DragDropComponent { export class DragDropComponent implements OnChanges {
@Input() initialFiles: string[] = [];
fileNames: string[] = []; fileNames: string[] = [];
@Output() filesSelected = new EventEmitter<FileList>(); @Output() filesSelected = new EventEmitter<FileList>();
@Output() fileRemoved = new EventEmitter<string>();
ngOnChanges(changes: SimpleChanges) {
if (changes['initialFiles']) {
this.fileNames = [...this.initialFiles];
}
}
ngOnInit() {
if (this.initialFiles && this.initialFiles.length > 0) {
this.fileNames = [...this.initialFiles];
}
}
onFilesSelected(event: Event): void { onFilesSelected(event: Event): void {
const input = event.target as HTMLInputElement; const input = event.target as HTMLInputElement;
@ -35,14 +56,22 @@ export class DragDropComponent {
event.preventDefault(); event.preventDefault();
} }
updateFileNamesFromFileList(files: FileList): void {
this.fileNames = Array.from(files).map((file) => file.name);
}
private updateFileNames(files: FileList): void { private updateFileNames(files: FileList): void {
for (let i = 0; i < files.length; i++) { for (let i = 0; i < files.length; i++) {
this.fileNames.push(files[i].name); this.fileNames.push(files[i].name);
} }
} }
removeFile(fileName: string): void { removeFile(fileName: string, event: Event): void {
event.stopPropagation(); // Empêcher la propagation du clic
const index = this.fileNames.indexOf(fileName); const index = this.fileNames.indexOf(fileName);
if (index > -1) {
this.fileNames.splice(index, 1); this.fileNames.splice(index, 1);
this.fileRemoved.emit(fileName);
}
} }
} }

@ -136,7 +136,9 @@
>Images</label >Images</label
> >
<app-drag-drop <app-drag-drop
[initialFiles]="form.get('files')?.value"
(filesSelected)="onFilesReceived($event)" (filesSelected)="onFilesReceived($event)"
(fileRemoved)="removeFile($event)"
></app-drag-drop> ></app-drag-drop>
</div> </div>

@ -13,7 +13,7 @@ import {
ReactiveFormsModule, ReactiveFormsModule,
} from '@angular/forms'; } from '@angular/forms';
import { NavigationEnd, Router } from '@angular/router'; import { NavigationEnd, Router } from '@angular/router';
import { of, Subscription } from 'rxjs'; import { forkJoin, of, Subscription } from 'rxjs';
import { import {
catchError, catchError,
debounceTime, debounceTime,
@ -25,6 +25,7 @@ import {
import { Pin } from '../../model/Pin'; import { Pin } from '../../model/Pin';
import { AutocompleteService } from '../../services/auto-complete/auto-complete.service'; import { AutocompleteService } from '../../services/auto-complete/auto-complete.service';
import { ExifService } from '../../services/exif/exif.service'; import { ExifService } from '../../services/exif/exif.service';
import { ImageService } from '../../services/image/image.service';
import { MapReloadService } from '../../services/map-reload/map-reload.service'; import { MapReloadService } from '../../services/map-reload/map-reload.service';
import { ModalService } from '../../services/modal/modal.service'; import { ModalService } from '../../services/modal/modal.service';
import { PinService } from '../../services/pin/pin.service'; import { PinService } from '../../services/pin/pin.service';
@ -58,7 +59,8 @@ export class EditPinPopupComponent implements OnInit, AfterViewInit, OnDestroy {
private exifService: ExifService, private exifService: ExifService,
private modalService: ModalService, private modalService: ModalService,
private router: Router, private router: Router,
private mapReloadService: MapReloadService private mapReloadService: MapReloadService,
private imageService: ImageService
) { ) {
// Initialiser le formulaire avec des valeurs par défaut // Initialiser le formulaire avec des valeurs par défaut
this.form = this.fb.group({ this.form = this.fb.group({
@ -86,9 +88,15 @@ export class EditPinPopupComponent implements OnInit, AfterViewInit, OnDestroy {
title: this.pin?.title || '', title: this.pin?.title || '',
description: this.pin?.description || '', description: this.pin?.description || '',
location: "Chargement de l'adresse...", location: "Chargement de l'adresse...",
date: this.pin?.date || '', files: this.pin?.files || [],
date: this.pin?.date
? new Date(this.pin.date).toISOString().split('T')[0]
: '',
}); });
// Initialiser les fichiers existants
this.files = this.pin?.files || [];
// Vérifier si nous avons des coordonnées valides dans pin.location // Vérifier si nous avons des coordonnées valides dans pin.location
if ( if (
this.pin?.location && this.pin?.location &&
@ -209,14 +217,14 @@ export class EditPinPopupComponent implements OnInit, AfterViewInit, OnDestroy {
} }
async onFilesReceived(files: FileList): Promise<void> { async onFilesReceived(files: FileList): Promise<void> {
this.files = Array.from(files); // Ajouter les nouveaux fichiers à la liste existante
this.files = [...this.files, ...Array.from(files)];
for (let i = 0; i < this.files.length; i++) { for (let i = 0; i < files.length; i++) {
try { try {
const data = await this.exifService.getLocation(this.files[i]); const data = await this.exifService.getLocation(files[i]);
if (data.latitude !== undefined && data.longitude !== undefined) { if (data.latitude !== undefined && data.longitude !== undefined) {
try { try {
// Utiliser pipe(take(1)) pour s'assurer que l'observable se termine
const address = await this.autocompleteService const address = await this.autocompleteService
.getAddressFromCoordinates(data.latitude, data.longitude) .getAddressFromCoordinates(data.latitude, data.longitude)
.pipe(take(1)) .pipe(take(1))
@ -231,7 +239,6 @@ export class EditPinPopupComponent implements OnInit, AfterViewInit, OnDestroy {
"Erreur lors de la récupération de l'adresse:", "Erreur lors de la récupération de l'adresse:",
addressError addressError
); );
// Utiliser les coordonnées brutes en cas d'échec
this.form this.form
.get('location') .get('location')
?.setValue(`${data.latitude}, ${data.longitude}`); ?.setValue(`${data.latitude}, ${data.longitude}`);
@ -245,21 +252,57 @@ export class EditPinPopupComponent implements OnInit, AfterViewInit, OnDestroy {
submitForm(): void { submitForm(): void {
if (this.form.valid) { if (this.form.valid) {
this.files = this.files.map((file) => { // Filtrer les fichiers qui sont déjà des IDs (images existantes)
return file.name; //TODO: Mettre le hash du fichier const existingFiles = this.files.filter(
}); (file) => typeof file === 'string'
);
const newFiles = this.files.filter((file) => file instanceof File);
// Récupérer la date et la formater correctement
const dateValue = this.form.get('date')?.value;
const formattedDate = dateValue
? new Date(dateValue).toISOString()
: null;
if (newFiles.length > 0) {
// Upload des nouveaux fichiers
const uploadObservables = newFiles.map((file) =>
this.imageService.postImage(file)
);
forkJoin(uploadObservables).subscribe((responses) => {
// Combiner les IDs des nouvelles images avec les IDs existants
const allFileIds = [
...existingFiles,
...responses.map((res: any) => res.id),
];
const pinData = { const pinData = {
...this.form.value, ...this.form.getRawValue(),
files: this.files, files: allFileIds,
user_id: this.pin.user_id, user_id: this.pin.user_id,
date: this.form.get('date')?.value || null, date: formattedDate,
}; };
this.pinService.updatePin(this.pin.id, pinData).subscribe(() => { this.pinService.updatePin(this.pin.id, pinData).subscribe(() => {
this.mapReloadService.requestReload(); // Demander le rechargement de la carte this.mapReloadService.requestReload();
this.closePinModal(); this.closePinModal();
}); });
});
} else {
// Si pas de nouveaux fichiers, mettre à jour directement avec les fichiers existants
const pinData = {
...this.form.getRawValue(),
files: existingFiles,
user_id: this.pin.user_id,
date: formattedDate,
};
this.pinService.updatePin(this.pin.id, pinData).subscribe(() => {
this.mapReloadService.requestReload();
this.closePinModal();
});
}
} else { } else {
console.error('Le formulaire est invalide'); console.error('Le formulaire est invalide');
} }
@ -272,4 +315,18 @@ export class EditPinPopupComponent implements OnInit, AfterViewInit, OnDestroy {
closePinModal() { closePinModal() {
this.modalService.closeModal(this.modalId); this.modalService.closeModal(this.modalId);
} }
removeFile(fileName: string): void {
const index = this.files.findIndex((file) => {
if (typeof file === 'string') {
return file === fileName;
}
return file.name === fileName;
});
if (index > -1) {
this.files.splice(index, 1);
this.form.patchValue({ files: this.files });
}
}
} }

@ -12,9 +12,9 @@ import * as L from 'leaflet';
import { Pin } from '../../model/Pin'; import { Pin } from '../../model/Pin';
import { AutocompleteService } from '../../services/auto-complete/auto-complete.service'; import { AutocompleteService } from '../../services/auto-complete/auto-complete.service';
import { MapReloadService } from '../../services/map-reload/map-reload.service'; import { MapReloadService } from '../../services/map-reload/map-reload.service';
import { ModalService } from '../../services/modal/modal.service';
import { PinService } from '../../services/pin/pin.service'; import { PinService } from '../../services/pin/pin.service';
import { PinMarkerComponent } from '../pin-marker/pin-marker.component'; import { PinMarkerComponent } from '../pin-marker/pin-marker.component';
import { ModalService } from '../../services/modal/modal.service';
@Component({ @Component({
selector: 'app-leaflet-map', selector: 'app-leaflet-map',
@ -241,8 +241,6 @@ export class LeafletMapComponent implements OnInit {
}); });
} }
async onFilesSelected(event: Event): Promise<void> { async onFilesSelected(event: Event): Promise<void> {
const input = event.target as HTMLInputElement; const input = event.target as HTMLInputElement;
if (input.files && input.files.length > 0) { if (input.files && input.files.length > 0) {
@ -259,11 +257,9 @@ export class LeafletMapComponent implements OnInit {
onDrop(event: DragEvent) { onDrop(event: DragEvent) {
event.preventDefault(); event.preventDefault();
if (event.dataTransfer && event.dataTransfer.files.length > 0) { if (event.dataTransfer && event.dataTransfer.files.length > 0) {
this.filesSelected.emit(event.dataTransfer.files); const files = event.dataTransfer.files;
const images: File[] = Array.from(event.dataTransfer.files); this.filesSelected.emit(files);
this.modalService.openModal('add-pin-modal', images); this.modalService.openModal('add-pin-modal', Array.from(files));
} }
console.log('Image dropped on map : ', event.dataTransfer?.files);
} }
} }

@ -68,6 +68,7 @@ export class PinService {
location: string; location: string;
files: any[]; files: any[];
user_id: string; user_id: string;
date: string;
} }
) { ) {
const url = `${this.apiURL}/pin/${id}`; const url = `${this.apiURL}/pin/${id}`;
@ -88,6 +89,7 @@ export class PinService {
location: coords, location: coords,
files: pin.files, files: pin.files,
user_id: pin.user_id, user_id: pin.user_id,
date: pin.date,
}, },
{ headers } { headers }
); );

Loading…
Cancel
Save