more style for interface; dark top

This commit is contained in:
Tykayn 2025-10-15 00:20:52 +02:00 committed by tykayn
parent e7a2d93d18
commit 2f9b91f2c7
4 changed files with 339 additions and 139 deletions

View file

@ -301,6 +301,7 @@
<input type="text" [(ngModel)]="guidePresetMoreInfoPseudo" placeholder="votre pseudo ou nom"> <input type="text" [(ngModel)]="guidePresetMoreInfoPseudo" placeholder="votre pseudo ou nom">
<textarea [(ngModel)]="presetMoreDetails" placeholder="tronc d'arbre dans le chemin"></textarea> <textarea [(ngModel)]="presetMoreDetails" placeholder="tronc d'arbre dans le chemin"></textarea>
<button class="btn btn-primary" (click)="submitPreset()">Envoyer</button> <button class="btn btn-primary" (click)="submitPreset()">Envoyer</button>
<button class="btn btn-secondary" (click)="cancelSubmitPreset()">Annuler</button>
</p> </p>
} }
} }

View file

@ -1,20 +1,22 @@
:host { :host {
display: block; display: block;
button{ button {
background: white; background: white;
border: 1px solid rgba(0,0,0,0.06); border: 1px solid rgba(0, 0, 0, 0.06);
border-radius: 10px; border-radius: 10px;
padding: 10px; padding: 10px;
cursor: pointer; cursor: pointer;
&:hover{
&:hover {
background-color: #f0f0f0; background-color: #f0f0f0;
} }
+ button{
+ button {
margin-left: 10px; margin-left: 10px;
margin-bottom: 10px; margin-bottom: 10px;
} }
} }
} }
@ -23,24 +25,26 @@
grid-template-columns: 400px 1fr; grid-template-columns: 400px 1fr;
grid-template-rows: 100vh; grid-template-rows: 100vh;
gap: 0; gap: 0;
&.is-small{
&.is-small {
grid-template-columns: 100px 1fr; grid-template-columns: 100px 1fr;
} }
/* Quand la zone principale est en plein écran */ /* Quand la zone principale est en plein écran */
&:has(.main.is-full){ &:has(.main.is-full) {
grid-template-columns: 1fr; grid-template-columns: 1fr;
} }
} }
.aside { .aside {
background: #ffffff; background: #ffffff;
border-right: 1px solid rgba(0,0,0,0.06); border-right: 1px solid rgba(0, 0, 0, 0.06);
box-shadow: 2px 0 12px rgba(0,0,0,0.03); box-shadow: 2px 0 12px rgba(0, 0, 0, 0.03);
padding: 16px; padding: 16px;
padding-bottom: 150px; padding-bottom: 150px;
overflow: auto; overflow: auto;
/* Masquer l'aside en mode plein écran */ /* Masquer l'aside en mode plein écran */
.layout:has(.main.is-full) &{ .layout:has(.main.is-full) & {
display: none; display: none;
} }
} }
@ -50,7 +54,8 @@
flex-direction: column; flex-direction: column;
height: 100vh; height: 100vh;
overflow: hidden; overflow: hidden;
&.is-full{
&.is-full {
flex-grow: 1; flex-grow: 1;
} }
} }
@ -103,7 +108,7 @@
&:focus { &:focus {
outline: none; outline: none;
border-color: #007bff; border-color: #007bff;
box-shadow: 0 0 0 2px rgba(0,123,255,0.25); box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.25);
} }
} }
@ -129,28 +134,82 @@
flex: 1 1 auto; flex: 1 1 auto;
min-height: 0; min-height: 0;
} }
.unlocated-layout{
.unlocated-layout {
display: grid; display: grid;
grid-template-columns: 360px 1fr; grid-template-columns: 360px 1fr;
height: 100%; height: 100%;
} }
.agenda-sidebar{
.agenda-sidebar {
overflow: auto; overflow: auto;
border-right: 1px solid #eee; border-right: 1px solid #eee;
padding: 10px; padding: 10px;
} }
.event-list{ list-style: none; padding: 0; margin: 0; }
.event-item{ display: flex; gap: 8px; padding: 8px; cursor: pointer; } .event-list {
.event-item.active{ background: #f0f9ff; } list-style: none;
.event-title{ font-weight: 600; } padding: 0;
.event-when{ font-size: 12px; color: #64748b; } margin: 0;
.agenda-main{ padding: 10px; } }
.event-edit-panel{ border: 1px solid #e2e8f0; border-radius: 8px; background: #fff; }
.panel-header{ display:flex; justify-content: space-between; align-items:center; padding: 8px 12px; border-bottom: 1px solid #e2e8f0; } .event-item {
.panel-content{ padding: 8px; } display: flex;
.btn-close{ background: #f5f5f5; border: 1px solid #ddd; border-radius: 4px; width: 32px; height: 32px; cursor: pointer; } gap: 8px;
.hint{ color: #64748b; padding: 20px; } padding: 8px;
.floating-actions{ cursor: pointer;
}
.event-item.active {
background: #f0f9ff;
}
.event-title {
font-weight: 600;
}
.event-when {
font-size: 12px;
color: #64748b;
}
.agenda-main {
padding: 10px;
}
.event-edit-panel {
border: 1px solid #e2e8f0;
border-radius: 8px;
background: #fff;
}
.panel-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 12px;
border-bottom: 1px solid #e2e8f0;
}
.panel-content {
padding: 8px;
}
.btn-close {
background: #f5f5f5;
border: 1px solid #ddd;
border-radius: 4px;
width: 32px;
height: 32px;
cursor: pointer;
}
.hint {
color: #64748b;
padding: 20px;
}
.floating-actions {
position: fixed; position: fixed;
right: 1rem; right: 1rem;
bottom: 16px; bottom: 16px;
@ -159,12 +218,12 @@
gap: 10px; gap: 10px;
z-index: 2000; z-index: 2000;
.fab{ .fab {
width: 48px; width: 48px;
height: 48px; height: 48px;
border-radius: 50%; border-radius: 50%;
border: none; border: none;
box-shadow: 0 4px 12px rgba(0,0,0,0.15); box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
cursor: pointer; cursor: pointer;
display: flex; display: flex;
align-items: center; align-items: center;
@ -174,27 +233,30 @@
background: #1976d2; background: #1976d2;
color: #fff; color: #fff;
} }
.fab.counter{
.fab.counter {
background: #0d9488; background: #0d9488;
margin-left: 0.5rem; margin-left: 0.5rem;
} }
.fab.plus{
.fab.plus {
background: #1976d2; background: #1976d2;
} }
} }
.filters{ .filters {
label{ label {
padding: 1rem; padding: 1rem;
display: block; display: block;
cursor: pointer; cursor: pointer;
&:hover{
&:hover {
background: lemonchiffon; background: lemonchiffon;
} }
} }
} }
.presets{ .presets {
position: fixed; position: fixed;
top: 63px; top: 63px;
margin-left: 397px; margin-left: 397px;
@ -203,20 +265,23 @@
display: block; display: block;
/* En plein écran, plus d'offset gauche */ /* En plein écran, plus d'offset gauche */
.layout:has(.main.is-full) &{ .layout:has(.main.is-full) & {
margin-left: 0; margin-left: 0;
} }
} }
.agenda-main{
app-edit-form{ .agenda-main {
app-edit-form {
right: 2rem; right: 2rem;
top: 3.5rem; top: 3.5rem;
.actions{
.actions {
right: 1rem !important; right: 1rem !important;
} }
} }
} }
app-edit-form{
app-edit-form {
position: fixed; position: fixed;
top: 135px; top: 135px;
margin-left: 397px; margin-left: 397px;
@ -226,14 +291,14 @@ app-edit-form{
display: block; display: block;
overflow: auto; overflow: auto;
background: rgba(228, 235, 255, 0.85); background: rgba(228, 235, 255, 0.85);
border: 1px solid rgba(0,0,0,0.06); border: 1px solid rgba(0, 0, 0, 0.06);
border-radius: 10px; border-radius: 10px;
padding: 10px; padding: 10px;
box-shadow: 0 0 10px rgba(0,0,0,0.1); box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
z-index: 1000; z-index: 1000;
padding-bottom: 150px; padding-bottom: 150px;
/* En plein écran, plus d'offset gauche */ /* En plein écran, plus d'offset gauche */
.layout:has(.main.is-full) &{ .layout:has(.main.is-full) & {
margin-left: 0; margin-left: 0;
} }
} }
@ -243,36 +308,63 @@ app-edit-form{
flex-direction: column; flex-direction: column;
gap: 8px; gap: 8px;
padding: 8px 12px; padding: 8px 12px;
.help { font-size: 12px; color: #64748b; }
.chips { display: flex; gap: 6px; flex-wrap: wrap; } .help {
.chip { border: 1px solid #e2e8f0; border-radius: 999px; padding: 6px 10px; background: #fff; cursor: pointer; } font-size: 12px;
.chip.active { background: #e3f2fd; border-color: #90caf9; } color: #64748b;
.emoji { margin-right: 6px; } }
.chips {
display: flex;
gap: 6px;
flex-wrap: wrap;
}
.chip {
border: 1px solid #e2e8f0;
border-radius: 999px;
padding: 6px 10px;
background: #fff;
cursor: pointer;
}
.chip.active {
background: #e3f2fd;
border-color: #90caf9;
}
.emoji {
margin-right: 6px;
}
} }
.toggle-options{ .toggle-options {
position: fixed; position: fixed;
bottom: 1rem; bottom: 1rem;
left: 1rem; left: 1rem;
z-index: 100; z-index: 100;
background: white; background: white;
border: 1px solid rgba(0,0,0,0.06); border: 1px solid rgba(0, 0, 0, 0.06);
border-radius: 10px; border-radius: 10px;
padding: 10px; padding: 2rem 1rem;
cursor: pointer; cursor: pointer;
&:hover{
&:hover {
background-color: #f0f0f0; background-color: #f0f0f0;
} }
&:active{
&:active {
background-color: #e0e0e0; background-color: #e0e0e0;
} }
&:focus{
&:focus {
outline: none; outline: none;
} }
} }
// presets de boutons // presets de boutons
.quick-actions{ .quick-actions {
margin-top: 8px; margin-top: 8px;
display: inline-block; display: inline-block;
gap: 6px; gap: 6px;
@ -281,9 +373,16 @@ app-edit-form{
right: 5rem; right: 5rem;
bottom: 1.1rem; bottom: 1.1rem;
z-index: 100; z-index: 100;
.btn {
padding: 1rem 1.25rem;
cursor: pointer;
height: 3.5rem;
}
} }
.guide-preset{ .guide-preset {
background: #4caf50; background: #4caf50;
color: #222; color: #222;
padding: 1rem; padding: 1rem;
@ -294,7 +393,8 @@ app-edit-form{
bottom: 2rem; bottom: 2rem;
right: 3.5rem; right: 3.5rem;
} }
.guide-preset-more{
.guide-preset-more {
background: white; background: white;
padding: 1rem; padding: 1rem;
position: fixed; position: fixed;
@ -302,11 +402,13 @@ app-edit-form{
bottom: 1rem; bottom: 1rem;
margin: 1rem; margin: 1rem;
width: 70%; width: 70%;
input, textarea{
input, textarea {
width: 90%; width: 90%;
} }
button{
button {
margin: 1rem; margin: 1rem;
display: block; display: block;
background: #4caf50; background: #4caf50;
@ -315,45 +417,57 @@ app-edit-form{
border-radius: 0.5rem; border-radius: 0.5rem;
} }
} }
table{
table {
width: 100%; width: 100%;
border-collapse: collapse; border-collapse: collapse;
border: 1px solid #eee; border: 1px solid #eee;
border-radius: 10px; border-radius: 10px;
overflow: hidden; overflow: hidden;
td:nth-of-type(1){
td:nth-of-type(1) {
font-weight: bold; font-weight: bold;
width: 100px; width: 100px;
} }
th, td{
th, td {
padding: 10px; padding: 10px;
} }
tr:nth-child(even){
tr:nth-child(even) {
background: #f0f0f0; background: #f0f0f0;
} }
tr:nth-child(odd){
tr:nth-child(odd) {
background: #fff; background: #fff;
} }
tr:hover{
tr:hover {
background: #f0f0f0; background: #f0f0f0;
} }
th{
th {
background: #f0f0f0; background: #f0f0f0;
} }
} }
.toaster{
position:fixed;
right:16px;
top:16px;
display:flex;
flex-direction:column;
gap:8px;
z-index:1000;
&.success{ .toaster {
position: fixed;
right: 16px;
top: 16px;
display: flex;
flex-direction: column;
gap: 8px;
z-index: 1000;
&.success {
background: greenyellow; background: greenyellow;
} }
&.info{
&.info {
background: cornflowerblue; background: cornflowerblue;
} }
} }

View file

@ -1,18 +1,19 @@
import { Component, inject, signal , OnDestroy, OnInit } from '@angular/core'; import {Component, inject, signal, OnDestroy, OnInit} from '@angular/core';
import { Router } from '@angular/router'; import {Router} from '@angular/router';
import { FormsModule } from '@angular/forms'; import {FormsModule} from '@angular/forms';
import {Menu} from './menu/menu'; import {Menu} from './menu/menu';
import { AllEvents } from '../../maps/all-events/all-events'; import {AllEvents} from '../../maps/all-events/all-events';
import { EditForm } from '../../forms/edit-form/edit-form'; import {EditForm} from '../../forms/edit-form/edit-form';
import { OedbApi } from '../../services/oedb-api'; import {OedbApi} from '../../services/oedb-api';
import { ActivatedRoute } from '@angular/router'; import {ActivatedRoute} from '@angular/router';
import oedb from '../../../oedb-types'; import oedb from '../../../oedb-types';
import { UnlocatedEvents } from '../../shared/unlocated-events/unlocated-events'; import {UnlocatedEvents} from '../../shared/unlocated-events/unlocated-events';
import { OsmAuth } from '../../services/osm-auth'; import {OsmAuth} from '../../services/osm-auth';
import { Osm } from '../../forms/osm/osm'; import {Osm} from '../../forms/osm/osm';
import { WhatFilterComponent } from '../../shared/what-filter/what-filter'; import {WhatFilterComponent} from '../../shared/what-filter/what-filter';
import {NgClass} from '@angular/common'; import {NgClass} from '@angular/common';
@Component({ @Component({
selector: 'app-home', selector: 'app-home',
standalone: true, standalone: true,
@ -42,7 +43,7 @@ export class Home implements OnInit, OnDestroy {
showTable = false; showTable = false;
showFilters = false; showFilters = false;
showEditForm = false; showEditForm = false;
showOptions = true; showOptions = false;
pleinAirMode = false; pleinAirMode = false;
civilianMode = false; civilianMode = false;
toasts: Array<{ id: number, type: 'success' | 'error' | 'info', message: string }> = []; toasts: Array<{ id: number, type: 'success' | 'error' | 'info', message: string }> = [];
@ -88,17 +89,20 @@ export class Home implements OnInit, OnDestroy {
unlocatedOrOnline: Array<any> = []; unlocatedOrOnline: Array<any> = [];
showUnlocatedList = false; showUnlocatedList = false;
protected showQuickActions: boolean = true; protected showQuickActions: boolean = true;
firstToastDone = false;
ngOnInit() { ngOnInit() {
// Écouteur global pour toasts // Écouteur global pour toasts
try { try {
(window as any).addEventListener('toast', (e: any) => { (window as any).addEventListener('toast', (e: any) => {
const d = e?.detail || {}; this.pushToast(d.type || 'info', d.message || ''); const d = e?.detail || {};
this.pushToast(d.type || 'info', d.message || '');
}); });
} catch {} } catch {
}
// Initialiser la bbox par défaut pour l'Île-de-France // Initialiser la bbox par défaut pour l'Île-de-France
this.currentBbox = { ...this.IDF_BBOX }; this.currentBbox = {...this.IDF_BBOX};
this.route.queryParamMap.subscribe(map => { this.route.queryParamMap.subscribe(map => {
const id = (map.get('id') || '').trim(); const id = (map.get('id') || '').trim();
@ -111,24 +115,31 @@ export class Home implements OnInit, OnDestroy {
if (id) { if (id) {
this.loadSingleEvent(id); this.loadSingleEvent(id);
} else { } else {
this.loadEvents({ what: what || undefined, limit: limit || undefined }); this.loadEvents({what: what || undefined, limit: limit || undefined});
} }
// Appliquer filtre par what côté client si fourni // Appliquer filtre par what côté client si fourni
if (what) { if (what) {
this.selectedWhatFilter = what; this.selectedWhatFilter = what;
} }
// Activer mode plein air via query param // Activer mode plein air via query param
if (pleinAir === '1' || pleinAir === 'true' || pleinAir === 'yes') { // if (pleinAir === '1' || pleinAir === 'true' || pleinAir === 'yes') {
this.enablePleinAirMode(); // this.enablePleinAirMode();
} // }
// Support: preset=plein_air // Support: preset=plein_air
if (preset === 'plein_air') { if (preset === 'plein_air') {
if (!this.firstToastDone) {
this.selectedWhatFilter = "traffic"
this.pushToast('info', "mode plein air activé")
this.useBboxFilter = true;
this.loadEvents({ what: 'traffic' });
}
this.firstToastDone = true
this.enablePleinAirMode(); this.enablePleinAirMode();
} }
}); });
this.startAutoReload(); this.startAutoReload();
this.loadEvents({ what: "culture" , limit: 100 }); this.loadEvents({what: "culture", limit: 100});
} }
ngOnDestroy() { ngOnDestroy() {
@ -227,7 +238,7 @@ export class Home implements OnInit, OnDestroy {
} }
onDaysAheadChange() { onDaysAheadChange() {
this.loadEvents({ daysAhead: this.daysAhead, what: this.selectedWhatFilter || undefined }); this.loadEvents({daysAhead: this.daysAhead, what: this.selectedWhatFilter || undefined});
} }
updateAvailableWhatTypes() { updateAvailableWhatTypes() {
@ -278,8 +289,8 @@ export class Home implements OnInit, OnDestroy {
const description = feature?.properties?.description || ''; const description = feature?.properties?.description || '';
const what = feature?.properties?.what || ''; const what = feature?.properties?.what || '';
return label.toLowerCase().includes(searchLower) || return label.toLowerCase().includes(searchLower) ||
description.toLowerCase().includes(searchLower) || description.toLowerCase().includes(searchLower) ||
what.toLowerCase().includes(searchLower); what.toLowerCase().includes(searchLower);
}); });
} }
@ -315,7 +326,7 @@ export class Home implements OnInit, OnDestroy {
} }
enablePleinAirMode() { enablePleinAirMode() {
this.pushToast('info', "mode plein air activé")
if (!this.pleinAirMode) { if (!this.pleinAirMode) {
this.pleinAirMode = true; this.pleinAirMode = true;
this.applyFilters(); this.applyFilters();
@ -326,7 +337,7 @@ export class Home implements OnInit, OnDestroy {
quickCreate(what: string) { quickCreate(what: string) {
const osmUsername = this.osmAuth.getUsername(); const osmUsername = this.osmAuth.getUsername();
this.selectedPreset = what; this.selectedPreset = what;
this.showGuidePresetPlace = true; this.showGuidePresetPlace = true;
this.selected = { this.selected = {
id: null, id: null,
properties: { properties: {
@ -334,18 +345,25 @@ export class Home implements OnInit, OnDestroy {
description: '', description: '',
what, what,
where: '', where: '',
...(osmUsername && { last_modified_by: osmUsername }) ...(osmUsername && {last_modified_by: osmUsername})
}, },
geometry: { type: 'Point', coordinates: [0, 0] } geometry: {type: 'Point', coordinates: [0, 0]}
}; };
// Ensuite, l'utilisateur clique sur la carte: voir onPickCoords() // Ensuite, l'utilisateur clique sur la carte: voir onPickCoords()
// this.showEditForm = true; // this.showEditForm = true;
} }
submitPreset(){ cancelSubmitPreset(){
this.selected = null;
this.presetMoreDetails = ''
this.showGuidePresetMoreInfo = false
this.showGuidePresetPlace = false;
this.showQuickActions = true;
}
submitPreset() {
const now = new Date(); const now = new Date();
const w = this.selected.properties.what; const w = this.selected.properties.what;
// fin du signalement par défaut dans 20 jours // fin du signalement par défaut dans 20 jours
const stop = new Date(now.getTime() + 20* 24 * 3600 * 1000); const stop = new Date(now.getTime() + 20 * 24 * 3600 * 1000);
const feature = { const feature = {
type: 'Feature', type: 'Feature',
properties: { properties: {
@ -359,7 +377,7 @@ export class Home implements OnInit, OnDestroy {
start: now.toISOString(), start: now.toISOString(),
stop: stop.toISOString() stop: stop.toISOString()
}, },
geometry: { type: 'Point', coordinates: [this.selected.lon, this.selected.lat] } geometry: {type: 'Point', coordinates: [this.selected.lon, this.selected.lat]}
} as any; } as any;
this.OedbApi.createEvent(feature).subscribe({ this.OedbApi.createEvent(feature).subscribe({
next: () => { next: () => {
@ -367,11 +385,14 @@ export class Home implements OnInit, OnDestroy {
this.selected = null; this.selected = null;
this.presetMoreDetails = '' this.presetMoreDetails = ''
this.showGuidePresetMoreInfo = false this.showGuidePresetMoreInfo = false
this.showGuidePresetPlace = false;
// Après création rapide en plein air: recharger uniquement ce type pour feedback instantané // Après création rapide en plein air: recharger uniquement ce type pour feedback instantané
this.selectedWhatFilter = w; this.selectedWhatFilter = w;
this.loadEvents({ what: 'traffic' }); this.loadEvents({what: 'traffic'});
}, },
error: () => { this.pushToast('error', 'Échec de création'); } error: () => {
this.pushToast('error', 'Échec de création');
}
}); });
} }
@ -382,6 +403,7 @@ export class Home implements OnInit, OnDestroy {
onSelect(feature: any) { onSelect(feature: any) {
this.selected = feature; this.selected = feature;
} }
onSelectFromCalendarView(feature: any) { onSelectFromCalendarView(feature: any) {
this.selected = feature; this.selected = feature;
this.showEditForm = false; this.showEditForm = false;
@ -392,19 +414,19 @@ export class Home implements OnInit, OnDestroy {
if (this.selected && this.selected.properties) { if (this.selected && this.selected.properties) {
this.selected = { this.selected = {
...this.selected, ...this.selected,
geometry: { type: 'Point', coordinates: [lon, lat] } geometry: {type: 'Point', coordinates: [lon, lat]}
}; };
// this.showOptions = true; // this.showOptions = true;
// En mode plein air, si c'est une création rapide, proposer l'envoi direct // En mode plein air, si c'est une création rapide, proposer l'envoi direct
if (this.pleinAirMode && (this.selected.id == null)) { if (this.pleinAirMode && (this.selected.id == null)) {
const w = this.selected.properties.what; const w = this.selected.properties.what;
const allowed = new Set(['traffic.contestation','traffic.interruption','traffic.wrong_way']); const allowed = new Set(['traffic.contestation', 'traffic.interruption', 'traffic.wrong_way']);
if (allowed.has(w)) { if (allowed.has(w)) {
this.showGuidePresetPlace = true; this.showGuidePresetPlace = true;
this.showGuidePresetMoreInfo = true; this.showGuidePresetMoreInfo = true;
this.showQuickActions = false; this.showQuickActions = false;
const self:this = this; const self: this = this;
const ok = typeof window !== 'undefined' ? window.confirm('Envoyer cet évènement maintenant ?') : true; const ok = typeof window !== 'undefined' ? window.confirm('Envoyer cet évènement maintenant ?') : true;
if (ok) { if (ok) {
self.submitPreset() self.submitPreset()
@ -430,9 +452,9 @@ export class Home implements OnInit, OnDestroy {
description: '', description: '',
what: whatKey || '', what: whatKey || '',
where: '', where: '',
...(osmUsername && { last_modified_by: osmUsername }) ...(osmUsername && {last_modified_by: osmUsername})
}, },
geometry: { type: 'Point', coordinates: [lon, lat] } geometry: {type: 'Point', coordinates: [lon, lat]}
}; };
} }
} }
@ -440,7 +462,7 @@ export class Home implements OnInit, OnDestroy {
private pushToast(type: 'success' | 'error' | 'info', message: string) { private pushToast(type: 'success' | 'error' | 'info', message: string) {
if (!message) return; if (!message) return;
const id = Date.now() + Math.random(); const id = Date.now() + Math.random();
this.toasts.push({ id, type, message }); this.toasts.push({id, type, message});
setTimeout(() => { setTimeout(() => {
this.toasts = this.toasts.filter(t => t.id !== id); this.toasts = this.toasts.filter(t => t.id !== id);
}, 6000); }, 6000);
@ -469,9 +491,11 @@ export class Home implements OnInit, OnDestroy {
startRectSelection() { startRectSelection() {
this.selectionMode = this.selectionMode === 'rectangle' ? 'none' : 'rectangle'; this.selectionMode = this.selectionMode === 'rectangle' ? 'none' : 'rectangle';
} }
startPolySelection() { startPolySelection() {
this.selectionMode = this.selectionMode === 'polygon' ? 'none' : 'polygon'; this.selectionMode = this.selectionMode === 'polygon' ? 'none' : 'polygon';
} }
clearSelection() { clearSelection() {
this.selectionMode = 'none'; this.selectionMode = 'none';
this.selectedIds = []; this.selectedIds = [];
@ -490,12 +514,21 @@ export class Home implements OnInit, OnDestroy {
const doUpdate = async (id: string | number, updater: (f: any) => any) => { const doUpdate = async (id: string | number, updater: (f: any) => any) => {
const feature = this.features.find(f => (f?.properties?.id ?? f?.id) === id); const feature = this.features.find(f => (f?.properties?.id ?? f?.id) === id);
if (!feature) { failed++; return; } if (!feature) {
failed++;
return;
}
const updated = updater(feature); const updated = updater(feature);
await new Promise<void>((resolve) => { await new Promise<void>((resolve) => {
this.OedbApi.updateEvent(id, updated).subscribe({ this.OedbApi.updateEvent(id, updated).subscribe({
next: () => { success++; resolve(); }, next: () => {
error: (err) => { (err?.status === 0 ? networkErrors++ : failed++); resolve(); } success++;
resolve();
},
error: (err) => {
(err?.status === 0 ? networkErrors++ : failed++);
resolve();
}
}); });
}); });
}; };
@ -503,24 +536,35 @@ export class Home implements OnInit, OnDestroy {
if (this.batchAction === 'delete') { if (this.batchAction === 'delete') {
for (const id of this.selectedIds) { for (const id of this.selectedIds) {
await new Promise<void>((resolve) => { await new Promise<void>((resolve) => {
this.OedbApi.deleteEvent(id).subscribe({ next: () => { success++; resolve(); }, error: (err) => { (err?.status === 0 ? networkErrors++ : failed++); resolve(); } }); this.OedbApi.deleteEvent(id).subscribe({
next: () => {
success++;
resolve();
}, error: (err) => {
(err?.status === 0 ? networkErrors++ : failed++);
resolve();
}
});
}); });
} }
} else if (this.batchAction === 'changeWhat') { } else if (this.batchAction === 'changeWhat') {
const what = this.batchWhat.trim(); const what = this.batchWhat.trim();
if (!what) return; if (!what) return;
for (const id of this.selectedIds) { for (const id of this.selectedIds) {
await doUpdate(id, (feature: any) => ({ ...feature, properties: { ...feature.properties, what } })); await doUpdate(id, (feature: any) => ({...feature, properties: {...feature.properties, what}}));
} }
} else if (this.batchAction === 'setField') { } else if (this.batchAction === 'setField') {
const key = this.batchFieldKey.trim(); const key = this.batchFieldKey.trim();
if (!key) return; if (!key) return;
for (const id of this.selectedIds) { for (const id of this.selectedIds) {
await doUpdate(id, (feature: any) => ({ ...feature, properties: { ...feature.properties, [key]: this.batchFieldValue } })); await doUpdate(id, (feature: any) => ({
...feature,
properties: {...feature.properties, [key]: this.batchFieldValue}
}));
} }
} }
this.batchSummary = { success, failed, networkErrors }; this.batchSummary = {success, failed, networkErrors};
this.loadEvents(); this.loadEvents();
} }
@ -569,21 +613,25 @@ export class Home implements OnInit, OnDestroy {
description: '', description: '',
what: 'traffic.mammoth', what: 'traffic.mammoth',
where: '', where: '',
...(osmUsername && { last_modified_by: osmUsername }) ...(osmUsername && {last_modified_by: osmUsername})
}, },
geometry: { type: 'Point', coordinates: [0, 0] } geometry: {type: 'Point', coordinates: [0, 0]}
}; };
this.showEditForm = true; this.showEditForm = true;
} }
private buildSubthemes() { private buildSubthemes() {
const t = this.theme(); const t = this.theme();
if (!t) { this.subthemes = []; this.activeSubtheme.set(null); return; } if (!t) {
this.subthemes = [];
this.activeSubtheme.set(null);
return;
}
const what = oedb.presets.what as Record<string, any>; const what = oedb.presets.what as Record<string, any>;
const list: Array<{ key: string, label: string, emoji: string }> = []; const list: Array<{ key: string, label: string, emoji: string }> = [];
Object.keys(what).forEach(k => { Object.keys(what).forEach(k => {
if (k === t || k.startsWith(`${t}.`)) { if (k === t || k.startsWith(`${t}.`)) {
list.push({ key: k, label: what[k].label || k, emoji: what[k].emoji || '' }); list.push({key: k, label: what[k].label || k, emoji: what[k].emoji || ''});
} }
}); });
this.subthemes = list.sort((a, b) => a.key.localeCompare(b.key)); this.subthemes = list.sort((a, b) => a.key.localeCompare(b.key));
@ -592,7 +640,10 @@ export class Home implements OnInit, OnDestroy {
} }
downloadGeoJSON() { downloadGeoJSON() {
const blob = new Blob([JSON.stringify({ type: 'FeatureCollection', features: this.filteredFeatures }, null, 2)], { type: 'application/geo+json' }); const blob = new Blob([JSON.stringify({
type: 'FeatureCollection',
features: this.filteredFeatures
}, null, 2)], {type: 'application/geo+json'});
const url = URL.createObjectURL(blob); const url = URL.createObjectURL(blob);
const a = document.createElement('a'); const a = document.createElement('a');
a.href = url; a.href = url;
@ -615,7 +666,7 @@ export class Home implements OnInit, OnDestroy {
JSON.stringify(f?.geometry?.coordinates?.[1] ?? '') JSON.stringify(f?.geometry?.coordinates?.[1] ?? '')
].join(',')); ].join(','));
const csv = [header.join(','), ...rows].join('\n'); const csv = [header.join(','), ...rows].join('\n');
const blob = new Blob([csv], { type: 'text/csv' }); const blob = new Blob([csv], {type: 'text/csv'});
const url = URL.createObjectURL(blob); const url = URL.createObjectURL(blob);
const a = document.createElement('a'); const a = document.createElement('a');
a.href = url; a.href = url;
@ -631,7 +682,7 @@ export class Home implements OnInit, OnDestroy {
const end = (this.endDateStr || '').trim() || undefined; const end = (this.endDateStr || '').trim() || undefined;
const days = this.daysAhead; const days = this.daysAhead;
const what = (this.selectedWhatFilter || '').trim() || undefined; const what = (this.selectedWhatFilter || '').trim() || undefined;
this.loadEvents({ start, end, daysAhead: days, what }); this.loadEvents({start, end, daysAhead: days, what});
} }
onBboxFilterToggle() { onBboxFilterToggle() {
@ -657,12 +708,13 @@ export class Home implements OnInit, OnDestroy {
} }
// Méthode pour recharger les événements quand la carte bouge // Méthode pour recharger les événements quand la carte bouge
showGuidePresetPlace: boolean = false; showGuidePresetPlace: boolean = false;
showGuidePresetMoreInfo: boolean= false; showGuidePresetMoreInfo: boolean = false;
guidePresetMoreInfoPseudo: string = ''; guidePresetMoreInfoPseudo: string = '';
presetMoreDetails: string = ''; presetMoreDetails: string = '';
selectedPreset: string = ''; selectedPreset: string = '';
onMapMove(bbox: { minLng: number, minLat: number, maxLng: number, maxLat: number }) { onMapMove(bbox: { minLng: number, minLat: number, maxLng: number, maxLat: number }) {
this.setCurrentBbox(bbox); this.setCurrentBbox(bbox);
} }

View file

@ -131,6 +131,13 @@ label { font-size: 0.85rem; color: $color-muted; }
.search{ .search{
width: 20%; width: 20%;
.btn{
padding: 1rem 1.25rem;
cursor: pointer;
height: 2.7rem;
line-height: 0.7rem;
margin-top: 0.15rem;
}
} }
.aside{ .aside{
@ -150,8 +157,8 @@ label { font-size: 0.85rem; color: $color-muted; }
} }
} }
.actions{ .actions{
position: fixed; position: fixed;
bottom: 10px; bottom: 10px;
left: 415px; left: 415px;
right: 10px; right: 10px;
@ -182,7 +189,7 @@ pre{
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
} }
} }
nav{ nav{
display: flex; display: flex;
flex-direction: row; flex-direction: row;
@ -226,7 +233,7 @@ nav{
.loading-indicator{ .loading-indicator{
color: #007bff; color: #007bff;
font-size: 12px; font-size: 12px;
position: fixed; position: fixed;
top: 10px; top: 10px;
left: 10px; left: 10px;
z-index: 1000; z-index: 1000;
@ -245,4 +252,30 @@ nav{
&:active{ &:active{
background: linear-gradient( 135deg, #5982b1, #6992c1); background: linear-gradient( 135deg, #5982b1, #6992c1);
} }
} }
.main{
nav{
background: $color-text ;
a {
color: white;
&:hover{
background: white;
color: $color-text;
}
}
}
.content {
main{
height: calc(100vh - 0.1rem);
overflow: hidden;
}
}
}
.maplibregl-ctrl-attrib{
position: fixed;
bottom: 0 ;
right: 0;
}