diff --git a/components/NavMapV2.vue b/components/NavMapV2.vue index bc94ab5..d923b33 100644 --- a/components/NavMapV2.vue +++ b/components/NavMapV2.vue @@ -21,10 +21,14 @@ const FAMILLE_COLORS: Record = { const props = defineProps<{ structures: StructureV2[] selectedId?: string | null + /** Fiche survolée dans la liste (B11) : épingle cerclée de safran, sans recentrer. */ + survolId?: string | null }>() const emit = defineEmits<{ 'select-structure': [id: string] + /** Survol d'une épingle (B11) : la liste allume la carte-fiche. */ + survol: [id: string | null] }>() const mapContainer = ref(null) @@ -32,17 +36,18 @@ let mapInstance: Map | null = null let clusterGroup: any = null const markers = new Map() let tileLayerInstance: any = null +let resizeObserver: ResizeObserver | null = null function getFamilleColor(famille: number): string { return FAMILLE_COLORS[famille] ?? '#888888' } -function createPinIcon(famille: number, isSelected = false): DivIcon { +function createPinIcon(famille: number, isSelected = false, isSurvol = false): DivIcon { const L = (window as any).L const bg = getFamilleColor(famille) - const size = isSelected ? 20 : 14 - const border = isSelected ? 'white' : 'rgba(255,255,255,0.7)' - const shadow = isSelected ? `0 0 0 4px ${bg}55` : 'none' + const size = isSelected || isSurvol ? 20 : 14 + const border = isSurvol ? '#f5b342' : isSelected ? 'white' : 'rgba(255,255,255,0.7)' + const shadow = isSurvol ? '0 0 0 4px rgba(245,179,66,0.6)' : isSelected ? `0 0 0 4px ${bg}55` : 'none' return L.divIcon({ className: '', @@ -76,6 +81,7 @@ async function initMap() { ;(window as any).L = L // @ts-ignore await import('leaflet.markercluster') + if (!mapContainer.value?.isConnected) return // page quittée pendant le chargement (ex. ?random=1) const MarkerClusterGroup = L.MarkerClusterGroup mapInstance = L.map(mapContainer.value, { @@ -121,6 +127,14 @@ async function initMap() { mapInstance.addLayer(clusterGroup) updateMarkers(L) + + // B11 : la carte change de largeur sans que la fenêtre bouge (Mixte ↔ Carte ↔ Fiches) + resizeObserver = new ResizeObserver(() => mapInstance?.invalidateSize({ pan: false })) + resizeObserver.observe(mapContainer.value) +} + +function iconeDe(s: StructureV2): DivIcon { + return createPinIcon(s.famille_principale, s.id === props.selectedId, s.id === props.survolId) } function updateMarkers(L?: any) { @@ -136,10 +150,7 @@ function updateMarkers(L?: any) { ) structuresWithCoords.forEach((structure) => { - const isSelected = structure.id === props.selectedId - const icon = createPinIcon(structure.famille_principale, isSelected) - - const marker = leaflet.marker([structure.latitude!, structure.longitude!], { icon }) + const marker = leaflet.marker([structure.latitude!, structure.longitude!], { icon: iconeDe(structure), title: structure.nom }) const hashtagsHtml = structure.hashtags.slice(0, 2) .map(h => `${h}`) @@ -165,6 +176,8 @@ function updateMarkers(L?: any) { `, { maxWidth: 260 }) marker.on('click', () => emit('select-structure', structure.id)) + marker.on('mouseover', () => emit('survol', structure.id)) + marker.on('mouseout', () => emit('survol', null)) markers.set(structure.id, marker) clusterGroup.addLayer(marker) @@ -193,14 +206,14 @@ watch( const oldMarker = markers.get(oldId) const oldStructure = props.structures.find(s => s.id === oldId) if (oldMarker && oldStructure) { - oldMarker.setIcon(createPinIcon(oldStructure.famille_principale, false)) + oldMarker.setIcon(iconeDe(oldStructure)) } } if (newId != null) { const newMarker = markers.get(newId) const newStructure = props.structures.find(s => s.id === newId) if (newMarker && newStructure) { - newMarker.setIcon(createPinIcon(newStructure.famille_principale, true)) + newMarker.setIcon(iconeDe(newStructure)) const latLng = newMarker.getLatLng() mapInstance.panTo(latLng, { animate: true }) } @@ -208,6 +221,27 @@ watch( } ) +// Survol depuis la liste : épingle cerclée de safran, sans recentrer la carte +watch( + () => props.survolId, + (newId, oldId) => { + for (const id of [oldId, newId]) { + if (id == null) continue + const m = markers.get(id) + const s = props.structures.find(x => x.id === id) + if (m && s) m.setIcon(iconeDe(s)) + allumerCluster(m, id === newId) + } + } +) + +// Épingle regroupée dans un cluster : c'est le cluster visible qui s'allume +function allumerCluster(m: Marker | undefined, on: boolean) { + const parent = m && clusterGroup?.getVisibleParent(m) + const el = parent && parent !== m ? (parent as any)._icon?.firstElementChild as HTMLElement | undefined : undefined + if (el) el.style.boxShadow = on ? '0 0 0 4px #f5b342' : '' +} + function updateTileTheme(dark: boolean) { if (!mapInstance || !tileLayerInstance) return const L = (window as any).L @@ -232,6 +266,7 @@ onMounted(() => { onUnmounted(() => { document.removeEventListener('nav-v2-select', onNavV2Select as EventListener) themeObserver?.disconnect() + resizeObserver?.disconnect() if (mapInstance) { mapInstance.remove() mapInstance = null diff --git a/pages/agences.vue b/pages/agences.vue index c4e0647..1c71c1a 100644 --- a/pages/agences.vue +++ b/pages/agences.vue @@ -1,433 +1,71 @@