🛂 Update actions on shared pins
continuous-integration/drone/push Build is passing Details

master
Alexis Feron 16 hours ago
parent 5a700b31c6
commit 2af63046da

@ -0,0 +1,71 @@
<div id="confirm-share-modal-{{ pinId }}">
<!-- Fond assombri -->
<div
class="fixed inset-0 bg-gray-900 bg-opacity-50 w-full h-full z-40 transition-opacity duration-300 ease-in-out"
[ngClass]="{
'opacity-0 pointer-events-none': !isOpen,
'opacity-100': isOpen
}"
(click)="cancel()"
></div>
<!-- Contenu principal -->
<div
class="fixed inset-0 z-50 flex justify-center items-center w-full h-full overflow-y-auto"
[ngClass]="{
'opacity-0 scale-50 pointer-events-none': !isOpen,
'opacity-100 scale-100': isOpen
}"
>
<div
class="bg-white dark:bg-gray-700 rounded-lg shadow p-6 w-full max-w-md transition-transform duration-300 ease-in-out my-8"
>
<!-- Modal header -->
<div
class="flex items-center justify-between border-b rounded-t dark:border-gray-600 mb-6 pb-2"
>
<h2 class="text-lg font-semibold text-gray-900 dark:text-white">
Confirmation
</h2>
<button
type="button"
(click)="closeModal()"
class="end-2.5 text-gray-400 bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm w-8 h-8 ms-auto inline-flex justify-center items-center dark:hover:bg-gray-600 dark:hover:text-white"
>
<svg
class="w-3 h-3"
aria-hidden="true"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 14 14"
>
<path
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="m1 1 6 6m0 0 6 6M7 7l6-6M7 7l-6 6"
/>
</svg>
<span class="sr-only">Fermer la modal</span>
</button>
</div>
<p class="text-sm text-gray-700 dark:text-gray-300 mb-6">{{ message }}</p>
<div class="flex justify-end space-x-4">
<button
class="px-4 py-2 text-white bg-red-600 hover:bg-red-700 rounded"
(click)="confirm()"
>
Supprimer
</button>
<button
class="px-4 py-2 bg-gray-300 dark:bg-gray-600 text-gray-800 dark:text-white rounded hover:bg-gray-400 dark:hover:bg-gray-500"
(click)="cancel()"
>
Annuler
</button>
</div>
</div>
</div>
</div>

@ -0,0 +1,22 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ConfirmShareModalComponent } from './confirm-share-modal.component';
describe('ConfirmShareModalComponent', () => {
let component: ConfirmShareModalComponent;
let fixture: ComponentFixture<ConfirmShareModalComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [ConfirmShareModalComponent],
}).compileComponents();
fixture = TestBed.createComponent(ConfirmShareModalComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

@ -0,0 +1,74 @@
import { CommonModule } from '@angular/common';
import {
Component,
EventEmitter,
Input,
OnDestroy,
OnInit,
Output,
} from '@angular/core';
import { Subscription } from 'rxjs';
import { ModalService } from '../../services/modal/modal.service';
@Component({
selector: 'app-confirm-share-modal',
standalone: true,
imports: [CommonModule],
templateUrl: './confirm-share-modal.component.html',
})
export class ConfirmShareModalComponent implements OnInit, OnDestroy {
@Input() message: string =
'Voulez-vous retirer ce pin partagé de votre carte ?';
@Input() pinId: string = '';
@Input() pinOpened!: EventEmitter<void>;
@Output() confirmed = new EventEmitter<void>();
@Output() cancelled = new EventEmitter<void>();
modalId: string = '';
isOpen = false;
private subscription!: Subscription;
constructor(private modalService: ModalService) {}
ngOnInit() {
this.modalId = 'confirm-share-modal-' + this.pinId;
this.subscription = this.modalService
.getModalState(this.modalId)
.subscribe((state) => {
this.isOpen = state;
});
this.pinOpened.subscribe(() => {
this.moveModalToBody();
});
}
ngOnDestroy() {
this.subscription?.unsubscribe();
}
confirm() {
this.confirmed.emit();
this.modalService.closeModal(this.modalId);
}
cancel() {
this.cancelled.emit();
this.modalService.closeModal(this.modalId);
}
closeModal() {
this.isOpen = false;
this.modalService.closeModal(this.modalId);
}
private moveModalToBody(): void {
const modal = document.getElementById(this.modalId);
if (modal && modal.parentElement !== document.body) {
document.body.appendChild(modal);
}
}
}

@ -43,11 +43,11 @@ export class EditPinPopupComponent implements OnInit, OnDestroy {
@Input() isHomePage: boolean = false;
@Input() pin!: Pin;
@Input() pinId!: string;
@Input() pinOpened!: EventEmitter<void>;
@Input() pinOpened!: EventEmitter<void>;
@ViewChild(DragDropComponent) dragDropComponent!: DragDropComponent;
private modalOpenSubscription!: Subscription;
form!: FormGroup;
@ -69,8 +69,14 @@ export class EditPinPopupComponent implements OnInit, OnDestroy {
) {
// Initialiser le formulaire avec des valeurs par défaut
this.form = this.fb.group({
title: new FormControl('', [Validators.required, Validators.minLength(3)]),
description: new FormControl('', [Validators.required, Validators.minLength(3)]),
title: new FormControl('', [
Validators.required,
Validators.minLength(3),
]),
description: new FormControl('', [
Validators.required,
Validators.minLength(3),
]),
location: new FormControl('', [Validators.required]),
complete_address: new FormControl('', [Validators.required]),
coordinates: new FormControl<number[]>([]),
@ -91,7 +97,7 @@ export class EditPinPopupComponent implements OnInit, OnDestroy {
ngOnInit(): void {
this.modalId = 'edit-pin-popup-' + this.pinId;
// S'abonner aux changements d'état du modal
this.modalOpenSubscription = this.modalService
.getModalState(this.modalId)
@ -111,7 +117,7 @@ export class EditPinPopupComponent implements OnInit, OnDestroy {
debounceTime(300), // Attendre 300ms après la dernière frappe
distinctUntilChanged(), // Ignorer si la nouvelle valeur est la même que la précédente
switchMap((query) => {
// Vérifier que query est une chaîne de caractères
// Vérifier que query est une chaîne de caractères
if (typeof query !== 'string') {
return of([]);
}
@ -172,14 +178,24 @@ export class EditPinPopupComponent implements OnInit, OnDestroy {
if (files.length > 0) {
try {
const data = await this.exifService.getLocation(files[0]);
if (data && data.latitude !== undefined && data.longitude !== undefined) {
this.autocompleteService.getAddressFromCoordinates(data.latitude, data.longitude).subscribe((address) => {
if (address) {
this.form.get('location')?.setValue(address.display_name);
this.form.get('complete_address')?.setValue(address.display_name);
this.form.get('coordinates')?.setValue([data.latitude, data.longitude]);
}
});
if (
data &&
data.latitude !== undefined &&
data.longitude !== undefined
) {
this.autocompleteService
.getAddressFromCoordinates(data.latitude, data.longitude)
.subscribe((address) => {
if (address) {
this.form.get('location')?.setValue(address.display_name);
this.form
.get('complete_address')
?.setValue(address.display_name);
this.form
.get('coordinates')
?.setValue([data.latitude, data.longitude]);
}
});
}
} catch (error) {
console.error(
@ -192,38 +208,42 @@ export class EditPinPopupComponent implements OnInit, OnDestroy {
async submitForm(): Promise<void> {
// Marquer tous les champs comme touched pour afficher les erreurs
Object.keys(this.form.controls).forEach(key => {
Object.keys(this.form.controls).forEach((key) => {
const control = this.form.get(key);
control?.markAsTouched();
});
if (this.form.valid) {
const uploadObservables = await Promise.all(this.files.map(async (file) => {
if(file.size === 0) {
if(file.name.includes("|")) {
return of({id: file.name.split("|")[1]});
} else {
this.uploadError = file.name + ' : ' + 'Image vide';
return of(null);
const uploadObservables = await Promise.all(
this.files.map(async (file) => {
if (file.size === 0) {
if (file.name.includes('|')) {
return of({ id: file.name.split('|')[1] });
} else {
this.uploadError = file.name + ' : ' + 'Image vide';
return of(null);
}
}
}
let fileDate = await this.exifService.getDateTime(file);
let fileDate = await this.exifService.getDateTime(file);
return this.imageService.postImage(file, fileDate).pipe(
catchError(async (error) => {
this.uploadError =
file.name + ' : ' + error.error.detail ||
"Erreur lors de l'upload de l'image";
if (this.dragDropComponent) {
this.dragDropComponent.errorMessage = this.uploadError;
}
return of(null);
})
);
})
);
return this.imageService.postImage(file, fileDate).pipe(
catchError(async error => {
this.uploadError = file.name + ' : ' + error.error.detail || 'Erreur lors de l\'upload de l\'image';
if (this.dragDropComponent) {
this.dragDropComponent.errorMessage = this.uploadError;
}
return of(null);
})
)
}));
forkJoin(uploadObservables).subscribe(async (responses) => {
// Vérifier si toutes les réponses sont valides
if (responses.some(response => response === null)) {
if (responses.some((response) => response === null)) {
return; // Ne pas continuer si une erreur s'est produite
}
@ -235,7 +255,9 @@ export class EditPinPopupComponent implements OnInit, OnDestroy {
files: this.files,
date: this.form.get('date')?.value || null,
location: coordinates || [0, 0],
complete_address: this.form.get('complete_address')?.value || this.form.get('location')?.value,
complete_address:
this.form.get('complete_address')?.value ||
this.form.get('location')?.value,
};
delete pinData.coordinates;
@ -263,13 +285,19 @@ export class EditPinPopupComponent implements OnInit, OnDestroy {
? new Date(this.pin.date).toISOString().split('T')[0]
: '',
});
this.pin.files.forEach((file) => {
this.imageService.getImageMetadata(file).subscribe((metadata) => {
this.files.push(new File([], metadata.metadata.original_filename + "|" + file.toString(), { type: metadata.metadata.content_type }));
this.files.push(
new File(
[],
metadata.metadata.original_filename + '|' + file.toString(),
{ type: metadata.metadata.content_type }
)
);
});
});
this.modalService.openModal(this.modalId);
}
@ -279,7 +307,9 @@ export class EditPinPopupComponent implements OnInit, OnDestroy {
}
removeFile(fileName: string): void {
const index = this.files.findIndex((file) => file.name === fileName || file.name.split("|")[0] === fileName);
const index = this.files.findIndex(
(file) => file.name === fileName || file.name.split('|')[0] === fileName
);
if (index > -1) {
this.files.splice(index, 1);
this.uploadError = ''; // Réinitialiser l'erreur lors de la suppression d'un fichier
@ -295,6 +325,6 @@ export class EditPinPopupComponent implements OnInit, OnDestroy {
}
getFileNames(): string[] {
return this.files.map((file) => file.name ? file.name.split("|")[0] : '');
return this.files.map((file) => (file.name ? file.name.split('|')[0] : ''));
}
}

@ -227,7 +227,7 @@ export class LeafletMapComponent implements OnInit {
// Pour chaque pin, récupérer ses partages
pins.forEach((pin) => {
if (!pin.is_poi) {
if (!pin.is_poi && pin.user_id === this.user_id) {
this.pinsService.getPinShares(pin.id).subscribe((response: any) => {
if (response && response.shares) {
response.shares.forEach((share: any) => {

@ -9,6 +9,12 @@
[pinOpened]="pinOpened"
></app-confirm-modal>
<app-share-modal [pinOpened]="pinOpened" [pinId]="pin.id"></app-share-modal>
<app-confirm-share-modal
(confirmed)="handleConfirm()"
(cancelled)="handleCancel()"
[pinId]="pin.id"
[pinOpened]="pinOpened"
></app-confirm-share-modal>
<!-- Boutons d'action -->
<div class="flex justify-between items-center flex-wrap mb-2">
@ -47,8 +53,8 @@
[pinOpened]="pinOpened"
></app-edit-pin-popup>
<button
*ngIf="!this.pin.is_poi"
<button
*ngIf="!this.pin.is_poi && this.is_creator"
class="p-2 text-green-500 rounded-full hover:bg-green-200 focus:outline-none flex items-center shadow-sm transition duration-200"
(click)="sharePin()"
>
@ -125,8 +131,7 @@
'h-32 sm:h-40 md:h-52 lg:h-60': true
}"
>
<div
<div
*ngFor="let imageId of pin.files; let index = index"
[class]="
'absolute inset-0 transition-opacity duration-700 ease-in-out' +
@ -215,10 +220,10 @@
</div>
</ng-template>
</div>
<div
class="text-lg mb-4 text-left"
[ngClass]="{
class="text-lg mb-4 text-left"
[ngClass]="{
'whitespace-nowrap overflow-hidden truncate': !pin.is_poi,
'text-justify': pin.is_poi
}"

@ -1,16 +1,17 @@
import { CommonModule, NgIf } from '@angular/common';
import { Component, EventEmitter, Input, Output } from '@angular/core';
import { DomSanitizer, SafeUrl } from '@angular/platform-browser';
import { Router, RouterModule } from '@angular/router';
import * as L from 'leaflet';
import { Pin } from '../../model/Pin';
import { AuthService } from '../../services/auth/auth.service';
import { ImageService } from '../../services/image/image.service';
import { ModalService } from '../../services/modal/modal.service';
import { PinService } from '../../services/pin/pin.service';
import { ConfirmModalComponent } from '../confirm-modal/confirm-modal.component';
import { ConfirmShareModalComponent } from '../confirm-share-modal/confirm-share-modal.component';
import { EditPinPopupComponent } from '../edit-pin-popup/edit-pin-popup.component';
import { ShareModalComponent } from '../share-modal/share-modal.component';
import { Router } from '@angular/router';
import { RouterModule } from '@angular/router';
@Component({
selector: 'app-pin-marker',
@ -21,7 +22,8 @@ import { RouterModule } from '@angular/router';
ConfirmModalComponent,
ShareModalComponent,
RouterModule,
NgIf
NgIf,
ConfirmShareModalComponent,
],
standalone: true,
})
@ -32,6 +34,7 @@ export class PinMarkerComponent {
currentIndex: number = 0;
imageUrls: SafeUrl[] = [];
imagesLoaded = false;
is_creator = false;
@Output() pinOpened = new EventEmitter<void>();
@ -40,10 +43,13 @@ export class PinMarkerComponent {
private modalService: ModalService,
private imageService: ImageService,
private sanitizer: DomSanitizer,
private router: Router
private router: Router,
private authService: AuthService
) {}
ngOnInit() {
this.is_creator = this.pin.user_id === this.authService.getUserId();
// Écouter l'événement d'ouverture du popup
this.marker.on('popupopen', () => {
if (!this.imagesLoaded) {
@ -75,18 +81,39 @@ export class PinMarkerComponent {
}
onDelete() {
this.modalService.openModal('confirm-modal-' + this.pin.id);
if (this.is_creator) {
this.modalService.openModal('confirm-modal-' + this.pin.id);
} else {
this.modalService.openModal('confirm-share-modal-' + this.pin.id);
}
}
handleConfirm() {
this.pinService.deletePin(this.pin.id).subscribe(() => {
this.marker.remove();
this.modalService.closeModal('confirm-modal-' + this.pin.id);
});
if (this.is_creator) {
this.pinService.deletePin(this.pin.id).subscribe(() => {
this.marker.remove();
this.modalService.closeModal('confirm-modal-' + this.pin.id);
});
} else {
this.handleConfirmShareDelete();
}
}
handleConfirmShareDelete() {
this.pinService
.deletePinShare(this.pin.id, this.authService.getUserId())
.subscribe(() => {
this.marker.remove();
this.modalService.closeModal('confirm-share-modal-' + this.pin.id);
});
}
handleCancel() {
this.modalService.closeModal('confirm-modal-' + this.pin.id);
if (this.is_creator) {
this.modalService.closeModal('confirm-modal-' + this.pin.id);
} else {
this.modalService.closeModal('confirm-share-modal-' + this.pin.id);
}
}
prevSlide(): void {

@ -38,7 +38,9 @@ export class PinService {
}
sharePin(pinId: string, friendId: string) {
return this.http.post<any>(`${this.apiURL}/pin/${pinId}/share`, { friend_id: friendId });
return this.http.post<any>(`${this.apiURL}/pin/${pinId}/share`, {
friend_id: friendId,
});
}
getPinShares(pinId: string) {
@ -46,12 +48,14 @@ export class PinService {
}
getSharedUsersForPin(pinId: string) {
return this.http.get<{ shares: any[] }>(`${this.apiURL}/pin/${pinId}/shares`).pipe(
map((response) => response.shares)
);
return this.http
.get<{ shares: any[] }>(`${this.apiURL}/pin/${pinId}/shares`)
.pipe(map((response) => response.shares));
}
deletePinShare(pinId: string, friendId: string) {
return this.http.delete<any>(`${this.apiURL}/pin/${pinId}/share/${friendId}`);
return this.http.delete<any>(
`${this.apiURL}/pin/${pinId}/share/${friendId}`
);
}
}

Loading…
Cancel
Save