intro for phone
continuous-integration/drone/push Build is passing Details

tutorial
Maxence JOUANNET 3 days ago
commit f92f412fcd

@ -1,6 +1,6 @@
<!-- Modal toggle -->
<button
class="p-1 text-blue-500 rounded hover:bg-blue-100 focus:outline-none flex items-center"
class="p-2 text-gray-600 rounded-full hover:bg-gray-100 transition duration-200 shadow-sm"
aria-label="Edit"
(click)="openPinModal()"
>
@ -97,9 +97,16 @@ aria-label="Edit"
placeholder="Mont Saint-Michel"
required
/>
<div *ngIf="form.get('title')?.invalid && form.get('title')?.touched" class="text-red-500 text-sm mt-1">
<span *ngIf="form.get('title')?.errors?.['required']">Le titre est requis</span>
<span *ngIf="form.get('title')?.errors?.['minlength']">Le titre doit contenir au moins 3 caractères</span>
<div
*ngIf="form.get('title')?.invalid && form.get('title')?.touched"
class="text-red-500 text-sm mt-1"
>
<span *ngIf="form.get('title')?.errors?.['required']"
>Le titre est requis</span
>
<span *ngIf="form.get('title')?.errors?.['minlength']"
>Le titre doit contenir au moins 3 caractères</span
>
</div>
</div>
@ -118,8 +125,15 @@ aria-label="Edit"
(focus)="onFocus()"
(blur)="onBlur()"
/>
<div *ngIf="form.get('location')?.invalid && form.get('location')?.touched" class="text-red-500 text-sm mt-1">
<span *ngIf="form.get('location')?.errors?.['required']">La localisation est requise</span>
<div
*ngIf="
form.get('location')?.invalid && form.get('location')?.touched
"
class="text-red-500 text-sm mt-1"
>
<span *ngIf="form.get('location')?.errors?.['required']"
>La localisation est requise</span
>
</div>
<ul
*ngIf="suggestions.length > 0 && inputFocused"
@ -163,9 +177,19 @@ aria-label="Edit"
class="block p-2.5 w-full text-sm text-gray-900 bg-gray-50 rounded-lg border border-gray-300 focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
placeholder="Décrit ton souvenir..."
></textarea>
<div *ngIf="form.get('description')?.invalid && form.get('description')?.touched" class="text-red-500 text-sm mt-1">
<span *ngIf="form.get('description')?.errors?.['required']">La description est requise</span>
<span *ngIf="form.get('description')?.errors?.['minlength']">La description doit contenir au moins 3 caractères</span>
<div
*ngIf="
form.get('description')?.invalid &&
form.get('description')?.touched
"
class="text-red-500 text-sm mt-1"
>
<span *ngIf="form.get('description')?.errors?.['required']"
>La description est requise</span
>
<span *ngIf="form.get('description')?.errors?.['minlength']"
>La description doit contenir au moins 3 caractères</span
>
</div>
</div>

@ -29,7 +29,6 @@ export class LeafletMapComponent implements OnInit {
private allPins: Pin[] = [];
private pinCountries: { [pinId: string]: string } = {};
private contextMenu: L.Popup | null = null;
private showTutorial: boolean = false;
availableCountries: string[] = [];
availablePersons: string[] = [];
@ -71,9 +70,10 @@ export class LeafletMapComponent implements OnInit {
}
}
});
this.introService.startIntro();
this.showTutorial = this.route.snapshot.paramMap.get('tutorial') === "true";
if(this.route.snapshot.paramMap.get('tutorial') === "true") {
this.introService.startIntro();
}
}
private initializeMap(): void {
@ -282,7 +282,12 @@ export class LeafletMapComponent implements OnInit {
componentRef.instance.marker = marker;
popupDiv.appendChild(componentRef.location.nativeElement);
marker.bindPopup(popupDiv, { closeButton: false, minWidth: 200 });
marker.bindPopup(popupDiv, {
closeButton: false,
minWidth: 300,
maxHeight: 350,
className: 'custom-popup-fixed-size',
});
this.markersMap[pin.id] = marker;

@ -24,6 +24,7 @@ import { PinService } from '../../services/pin/pin.service';
import { AddPinPopupComponent } from '../add-pin-popup/add-pin-popup.component';
import { FriendPageComponent } from '../friend-page/friend-page.component';
import { AuthService } from '../../services/auth/auth.service';
import { NavbarService } from '../../services/navbar/navbar.service';
@Component({
selector: 'app-navbar',
@ -39,20 +40,9 @@ import { AuthService } from '../../services/auth/auth.service';
})
export class NavbarComponent implements OnInit {
showTimeline: boolean = false;
isSearchOpen: boolean = false;
isNavbarOpen: boolean = false;
toggleNavbar() {
this.isNavbarOpen = !this.isNavbarOpen;
this.isSearchOpen = false;
}
toggleSearch() {
this.isSearchOpen = !this.isSearchOpen;
this.isNavbarOpen = false;
}
pins: Pin[] = [];
pinsFiltered: Pin[] = [];
inputFocus: Boolean = false;
@ -63,11 +53,30 @@ export class NavbarComponent implements OnInit {
private route: ActivatedRoute,
private pinService: PinService,
private fb: FormBuilder,
private authService: AuthService
private authService: AuthService,
private navbarService: NavbarService
) {
this.searchForm = this.fb.group({
searchControl: new FormControl(''),
});
this.navbarService.isSearchOpen$.subscribe(isOpen => {
this.isSearchOpen = isOpen;
this.isNavbarOpen = false;
});
this.navbarService.isNavbarOpen$.subscribe(isOpen => {
this.isNavbarOpen = isOpen;
this.isSearchOpen = false;
});
}
toggleSearch(): void {
this.navbarService.toggleSearch();
}
toggleNavbar(): void {
this.navbarService.toggleNavbar();
}
ngOnInit(): void {

@ -4,24 +4,25 @@
[pinId]="pin.id"
[pinOpened]="pinOpened"
></app-confirm-modal>
<app-share-modal [pinOpened]="pinOpened" [pinId]="pin.id"></app-share-modal>
<div class="flex mb-2 justify-end items-center">
<!-- Boutons d'action -->
<div class="flex mb-4 justify-end items-center gap-2 rounded-xl">
<app-edit-pin-popup
[pin]="pin"
[pinId]="pin.id"
[pinOpened]="pinOpened"
></app-edit-pin-popup>
<button
class="p-1 text-gray-500 rounded hover:bg-gray-200 focus:outline-none ml-2 flex items-center"
class="p-2 text-gray-600 rounded-full hover:bg-gray-100 transition duration-200 shadow-sm"
(click)="sharePin()"
>
<svg
class="w-[1.125rem] h-[1.125rem] text-gray-800"
class="w-5 h-5 text-gray-800"
aria-hidden="true"
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
fill="none"
viewBox="0 0 24 24"
>
@ -33,18 +34,16 @@
/>
</svg>
</button>
<button
class="p-1 text-red-500 rounded hover:bg-red-100 focus:outline-none ml-2 flex items-center"
class="p-2 text-red-600 rounded-full hover:bg-red-100 transition duration-200 shadow-sm"
aria-label="Delete"
(click)="onDelete()"
>
<!-- Modale si demandée -->
<svg
class="w-[1.125rem] h-[1.125rem] text-gray-800"
class="w-5 h-5 text-gray-800"
aria-hidden="true"
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
fill="none"
viewBox="0 0 24 24"
>
@ -57,17 +56,16 @@
/>
</svg>
</button>
<button
class="p-1 text-gray-500 rounded hover:bg-gray-200 focus:outline-none ml-2 flex items-center"
class="p-2 text-gray-600 rounded-full hover:bg-gray-100 transition duration-200 shadow-sm"
aria-label="Close"
(click)="onClosePopup()"
>
<svg
class="w-[1.125rem] h-[1.125rem] text-gray-800"
class="w-5 h-5 text-gray-800"
aria-hidden="true"
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
fill="none"
viewBox="0 0 24 24"
>
@ -81,15 +79,16 @@
</svg>
</button>
</div>
<div class="text-center">
<strong>{{ pin.title }}</strong>
<ng-container
*ngIf="pin.files.length > 0; else noImagesPlaceholder"
class="relative carousel overflow-hidden"
>
<!-- Carousel wrapper -->
<!-- Titre -->
<div class="text-center mb-4">
<h2 class="text-xl font-semibold text-gray-800">{{ pin.title }}</h2>
</div>
<!-- Carousel -->
<ng-container *ngIf="pin.files.length > 0; else noImagesPlaceholder">
<div
class="relative h-40 mt-2 overflow-hidden rounded-lg flex items-center justify-center"
class="relative h-56 mt-2 overflow-hidden rounded-xl flex items-center justify-center"
>
<div
*ngFor="let imageId of pin.files; let index = index"
@ -97,37 +96,32 @@
'absolute inset-0 transition-opacity duration-700 ease-in-out' +
(index === currentIndex ? ' opacity-100' : ' opacity-0')
"
>
<div
class="relative h-40 overflow-hidden rounded-lg flex items-center justify-center"
>
<img
[src]="imageUrls[index]"
[hidden]="!imagesLoaded"
class="object-contain max-h-full max-w-full h-full w-auto mx-auto"
class="object-contain h-56 w-auto mx-auto transition-opacity duration-300"
/>
<div
*ngIf="!imagesLoaded"
class="relative inset-0 bg-gray-200 w-52 h-40 mt-2 overflow-hidden rounded-lg flex items-center justify-center"
class="absolute inset-0 flex items-center justify-center"
>
<span class="text-gray-500">Loading image...</span>
</div>
</div>
</div>
</div>
<!-- Slider controls -->
<div *ngIf="pin.files.length > 1">
<button
type="button"
class="absolute top-0 left-0 z-30 flex items-center justify-center h-full cursor-pointer group focus:outline-none"
class="absolute inset-y-0 left-0 z-30 flex items-center justify-center px-2 group focus:outline-none"
(click)="prevSlide()"
>
<span
class="inline-flex items-center justify-center w-8 h-8 rounded-full bg-black/30 dark:bg-black-800/30 group-hover:bg-black/50 dark:group-hover:bg-black-800/60"
class="inline-flex items-center justify-center w-8 h-8 rounded-full bg-black/30 group-hover:bg-black/50 transition"
>
<svg
class="w-4 h-4 text-white rtl:rotate-180"
class="w-4 h-4 text-white"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 6 10"
@ -143,16 +137,17 @@
<span class="sr-only">Previous</span>
</span>
</button>
<button
type="button"
class="absolute top-0 right-0 z-30 flex items-center justify-center h-full cursor-pointer group focus:outline-none"
class="absolute inset-y-0 right-0 z-30 flex items-center justify-center px-2 group focus:outline-none"
(click)="nextSlide()"
>
<span
class="inline-flex items-center justify-center w-8 h-8 rounded-full bg-black/30 dark:bg-black-800/30 group-hover:bg-black/50 dark:group-hover:bg-black-800/60"
class="inline-flex items-center justify-center w-8 h-8 rounded-full bg-black/30 group-hover:bg-black/50 transition"
>
<svg
class="w-4 h-4 text-white rtl:rotate-180"
class="w-4 h-4 text-white"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 6 10"
@ -169,32 +164,32 @@
</span>
</button>
</div>
</div>
</ng-container>
<!-- Placeholder s'il n'y a pas d'image -->
<ng-template #noImagesPlaceholder>
<div
class="relative h-40 mt-2 overflow-hidden rounded-lg flex items-center justify-center bg-gray-200"
class="relative h-36 mt-2 overflow-hidden rounded-xl flex items-center justify-center bg-gray-100 border border-gray-300"
>
<span class="text-gray-500">No images available</span>
<span class="text-gray-400 text-sm">Aucune image disponible</span>
</div>
</ng-template>
<!-- Description justifiée tronquée -->
<!-- Description -->
<div
class="text-lg mb-4 text-justify transition-all duration-300"
[ngClass]="{
'line-clamp-5 overflow-hidden': !expandedDescriptions[0]
}"
class="text-base leading-relaxed text-justify text-gray-700 transition-all duration-300 mt-2"
[ngClass]="{ 'line-clamp-5 overflow-hidden': !expandedDescriptions[0] }"
>
{{ pin.description || "Aucune description" }}
</div>
<!-- Bouton "voir plus / moins" -->
<div *ngIf="pin.description.length > 200" class="text-right mb-6">
<!-- Bouton "Voir plus / moins" -->
<div *ngIf="pin.description.length > 200" class="text-right mt-2 mb-4">
<button
class="text-blue-600 font-semibold hover:underline"
class="text-blue-600 font-medium hover:underline transition"
(click)="toggleDescription(0)"
>
{{ expandedDescriptions[0] ? "Voir moins" : "Voir plus" }}
</button>
</div>
</div>

@ -1,12 +1,16 @@
import { Injectable } from '@angular/core';
import introJs from 'intro.js';
import { ModalService } from '../modal/modal.service';
import { NavbarService } from '../navbar/navbar.service';
@Injectable({
providedIn: 'root'
})
export class IntroService {
constructor(private modalService: ModalService) {}
constructor(
private modalService: ModalService,
private navbarService: NavbarService
) {}
private sleep(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
@ -20,6 +24,23 @@ export class IntroService {
steps: [
{ intro: 'Bienvenue sur MemoryMap ! ' },
{ intro: 'Ensemble nous allons explorer les différentes fonctionnalitées disponible !'},
],
exitOnOverlayClick: false,
disableInteraction: false,
});
intro.oncomplete(() => {
resolve();
});
intro.start();
});
await this.sleep(300);
await new Promise<void>((resolve) => {
const intro = introJs();
intro.setOptions({
tooltipClass: 'custom-tooltip-with-avatar',
steps: [
{ element: '#timeline', intro: 'Ici retrouvez tous vos souvenirs grâce à une frise chronologique de vos voyages!' },
{ element: '#quete', intro: "N'hésitez pas à réaliser les différentes quètes, pour un petit plaisir personnel, que vous pourrez retrouvez ici !" },
],
@ -27,6 +48,11 @@ export class IntroService {
disableInteraction: false,
});
intro.onstart(async (element) => {
this.navbarService.toggleNavbar();
await this.sleep(100);
});
intro.oncomplete(() => {
resolve();
});
@ -102,6 +128,7 @@ export class IntroService {
intro.oncomplete(() => {
this.modalService.closeModal('friend-modal');
this.navbarService.toggleNavbar();
resolve();
});

@ -0,0 +1,21 @@
import { Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class NavbarService {
private isSearchOpenSubject = new BehaviorSubject<boolean>(false);
private isNavbarOpenSubject = new BehaviorSubject<boolean>(false);
isSearchOpen$ = this.isSearchOpenSubject.asObservable();
isNavbarOpen$ = this.isNavbarOpenSubject.asObservable();
toggleSearch(): void {
this.isSearchOpenSubject.next(!this.isSearchOpenSubject.value);
}
toggleNavbar(): void {
this.isNavbarOpenSubject.next(!this.isNavbarOpenSubject.value);
}
}

@ -53,6 +53,16 @@
height: 100%;
}
.custom-popup-fixed-size .leaflet-popup-content {
width: 300px !important;
height: 350px !important;
}
.custom-popup-fixed-size .leaflet-popup-content {
width: 300px !important;
height: 350px !important;
}
.custom-tooltip-with-avatar {
position: relative;
padding-left: 6rem !important;

Loading…
Cancel
Save