ajout mode plein air v1

This commit is contained in:
Tykayn 2025-10-14 17:29:37 +02:00 committed by tykayn
parent 11151bc91d
commit cc92e2b5d7
3 changed files with 168 additions and 4 deletions

View file

@ -101,10 +101,58 @@ export class AllEvents implements OnInit, OnDestroy {
const lat = parseFloat(this.route.snapshot.queryParams['lat']) || 48.8566;
const lon = parseFloat(this.route.snapshot.queryParams['lon']) || 2.3522;
const zoom = parseFloat(this.route.snapshot.queryParams['zoom']) || 8;
const basemap = (this.route.snapshot.queryParams['basemap'] || '').toLowerCase();
// Déterminer le style de fond de carte selon le query param "basemap"
// valeurs supportées: "vector" (défaut), "raster", "waymarked"
const style = (() => {
if (basemap === 'raster') {
return {
version: 8,
sources: {
osm: {
type: 'raster',
tiles: ['https://tile.openstreetmap.org/{z}/{x}/{y}.png'],
tileSize: 256,
attribution: '© OpenStreetMap contributors'
}
},
layers: [
{ id: 'osm-tiles', type: 'raster', source: 'osm', minzoom: 0, maxzoom: 19 }
]
} as any;
}
if (basemap === 'waymarked') {
return {
version: 8,
sources: {
osm: {
type: 'raster',
tiles: ['https://tile.openstreetmap.org/{z}/{x}/{y}.png'],
tileSize: 256,
attribution: '© OpenStreetMap contributors'
},
waymarked: {
type: 'raster',
// Waymarked Trails randonnée (hiking)
tiles: ['https://tile.waymarkedtrails.org/hiking/{z}/{x}/{y}.png'],
tileSize: 256,
attribution: '© waymarkedtrails.org'
}
},
layers: [
{ id: 'osm-tiles', type: 'raster', source: 'osm', minzoom: 0, maxzoom: 19 },
{ id: 'waymarked-overlay', type: 'raster', source: 'waymarked', minzoom: 0, maxzoom: 19 }
]
} as any;
}
// vector (défaut)
return 'https://tiles.openfreemap.org/styles/liberty';
})();
this.map = new maplibregl.Map({
container: this.mapContainer.nativeElement,
style: 'https://tiles.openfreemap.org/styles/liberty',
style,
center: [lon, lat],
zoom: zoom
});

View file

@ -23,6 +23,20 @@
<span></span>
}
</label>
<div class="control-group" id="pleinAirMode">
<label>
<input type="checkbox" [(ngModel)]="pleinAirMode" (change)="togglePleinAir()">
Mode plein air
</label>
@if (pleinAirMode) {
<div class="quick-actions" style="margin-top:8px; display:flex; gap:6px; flex-wrap:wrap;">
<button class="btn" (click)="quickCreate('traffic.contestation')">🚩 Contester</button>
<button class="btn" (click)="quickCreate('traffic.interruption')">⛓️ Interruption</button>
<button class="btn" (click)="quickCreate('traffic.wrong_way')">⛖ Détourné</button>
</div>
}
</div>
<div class="filters-group">
@if (showFilters) {
@ -78,11 +92,12 @@
<div class="control-group">
<app-what-filter [label]="'Filtrer par type d\'événement'" [available]="availableWhatTypes" [selected]="selectedWhatFilter" (selectedChange)="selectedWhatFilter = $event; onWhatFilterChange()"></app-what-filter>
</div>
<app-osm></app-osm>
<!-- <app-osm></app-osm>
<app-menu></app-menu>
<app-menu></app-menu> -->
<hr>
}
</div>
@ -100,7 +115,9 @@
<li> Mettre à jour un évènement: Sélectionnez un évènement sur la carte ou dans la liste pour le modifier.</li>
</ul>
</div>
@if(!pleinAirMode){
<app-edit-form [selected]="selected" (saved)="onSaved($event)" (created)="onCreated($event)" (deleted)="onDeleted($event)" (canceled)="onCanceled()"></app-edit-form>
}
}
<div id="fixed_actions">
@ -174,6 +191,15 @@
<button class="button toggle-options" (click)="showOptions = !showOptions">
Options
</button>
@if (toasts.length) {
<div class="toaster" style="position:fixed;right:16px;top:16px;display:flex;flex-direction:column;gap:8px;z-index:1000;">
@for (t of toasts; track t.id) {
<div class="toast" [class.success]="t.type==='success'" [class.error]="t.type==='error'" [class.info]="t.type==='info'" style="padding:10px 12px;border-radius:6px;box-shadow:0 2px 8px rgba(0,0,0,0.15);background:#fff;min-width:200px;">
{{t.message}}
</div>
}
</div>
}
@if (theme()) {
<div class="subtheme-bar">
<div class="help">Thème: {{ theme() }} — Cliquez sur la carte pour définir des coordonnées puis créez un évènement du sous-thème choisi.</div>

View file

@ -41,6 +41,8 @@ export class Home implements OnInit, OnDestroy {
showFilters = false;
showEditForm = false;
showOptions = false;
pleinAirMode = false;
toasts: Array<{ id: number, type: 'success' | 'error' | 'info', message: string }> = [];
selectionMode: 'none' | 'rectangle' | 'polygon' = 'none';
selectedIds: Array<string | number> = [];
@ -76,9 +78,16 @@ export class Home implements OnInit, OnDestroy {
showUnlocatedList = false;
ngOnInit() {
// Écouteur global pour toasts
try {
(window as any).addEventListener('toast', (e: any) => {
const d = e?.detail || {}; this.pushToast(d.type || 'info', d.message || '');
});
} catch {}
this.route.queryParamMap.subscribe(map => {
const id = (map.get('id') || '').trim();
const what = (map.get('what') || '').trim();
const pleinAir = (map.get('pleinair') || '').trim().toLowerCase();
const limitParam = map.get('limit');
const limit = limitParam ? Number(limitParam) : null;
// Charger selon les query params
@ -91,6 +100,10 @@ export class Home implements OnInit, OnDestroy {
if (what) {
this.selectedWhatFilter = what;
}
// Activer mode plein air via query param
if (pleinAir === '1' || pleinAir === 'true' || pleinAir === 'yes') {
this.enablePleinAirMode();
}
});
this.startAutoReload();
}
@ -260,9 +273,49 @@ export class Home implements OnInit, OnDestroy {
});
}
// Mode plein air: ne garder que certains types
if (this.pleinAirMode) {
const allowed = new Set<string>([
'traffic.contestation',
'traffic.interruption',
'traffic.wrong_way'
]);
filtered = filtered.filter(f => allowed.has(f?.properties?.what || ''));
}
this.filteredFeatures = filtered;
}
togglePleinAir() {
this.pleinAirMode = !this.pleinAirMode;
this.applyFilters();
}
enablePleinAirMode() {
if (!this.pleinAirMode) {
this.pleinAirMode = true;
this.applyFilters();
}
}
// Actions rapides plein air
quickCreate(what: 'traffic.contestation' | 'traffic.interruption' | 'traffic.wrong_way') {
const osmUsername = this.osmAuth.getUsername();
this.selected = {
id: null,
properties: {
label: '',
description: '',
what,
where: '',
...(osmUsername && { last_modified_by: osmUsername })
},
geometry: { type: 'Point', coordinates: [0, 0] }
};
// Ensuite, l'utilisateur clique sur la carte: voir onPickCoords()
this.showEditForm = true;
}
goToNewCategories() {
this.router.navigate(['/nouvelles-categories']);
}
@ -279,7 +332,35 @@ export class Home implements OnInit, OnDestroy {
geometry: { type: 'Point', coordinates: [lon, lat] }
};
this.showOptions = true;
// En mode plein air, si c'est une création rapide, proposer l'envoi direct
if (this.pleinAirMode && (this.selected.id == null)) {
const w = this.selected.properties.what;
const allowed = new Set(['traffic.contestation','traffic.interruption','traffic.wrong_way']);
if (allowed.has(w)) {
const ok = typeof window !== 'undefined' ? window.confirm('Envoyer cet évènement maintenant ?') : true;
if (ok) {
const now = new Date();
const stop = new Date(now.getTime() + 6 * 3600 * 1000);
const feature = {
type: 'Feature',
properties: {
type: 'unscheduled',
label: this.selected.properties.label || (oedb.presets.what as any)[w]?.label || 'Évènement',
description: this.selected.properties.description || (oedb.presets.what as any)[w]?.description || '',
what: w,
where: this.selected.properties.where || '',
start: now.toISOString(),
stop: stop.toISOString()
},
geometry: { type: 'Point', coordinates: [lon, lat] }
} as any;
this.OedbApi.createEvent(feature).subscribe({
next: () => { this.pushToast('success', 'Évènement créé'); this.selected = null; this.loadEvents(); },
error: () => { this.pushToast('error', 'Échec de création'); }
});
}
}
}
} else {
const osmUsername = this.osmAuth.getUsername();
const whatKey = this.activeSubtheme();
@ -306,6 +387,15 @@ export class Home implements OnInit, OnDestroy {
}
}
private pushToast(type: 'success' | 'error' | 'info', message: string) {
if (!message) return;
const id = Date.now() + Math.random();
this.toasts.push({ id, type, message });
setTimeout(() => {
this.toasts = this.toasts.filter(t => t.id !== id);
}, 3000);
}
onSaved(_res: any) {
// refresh list after update
this.loadEvents();