diff --git a/components/content/Overview.vue b/components/content/Overview.vue index 33ba7b7a..4b5bec7c 100644 --- a/components/content/Overview.vue +++ b/components/content/Overview.vue @@ -64,7 +64,11 @@ const { data: geojson } = await useAsyncData(`geojson-${path}`, () => { .findOne(); }); -const features = geojson.value.features; +const features = geojson.value.features.map((feature, index) => ({ + id: index, + ...feature +})); + const doneFeatures = features.filter(feature => feature.properties.status === 'done'); const doneDistance = getDistance({ features: doneFeatures }); const avancement = Math.round(doneDistance / voie.distance * 100); diff --git a/composables/useMap.ts b/composables/useMap.ts index c4ff5139..4095d41f 100644 --- a/composables/useMap.ts +++ b/composables/useMap.ts @@ -112,13 +112,12 @@ export const useMap = () => { type: 'line', source: 'done-sections', paint: { - 'line-width': 4, + 'line-width': ['case', ['boolean', ['feature-state', 'hover'], false], 6, 4], 'line-color': ['get', 'color'] } }); - map.on('mouseenter', 'done-sections', () => (map.getCanvas().style.cursor = 'pointer')); - map.on('mouseleave', 'done-sections', () => (map.getCanvas().style.cursor = '')); + applyHoverEffect({ map, layer: 'done-sections' }); } function plotWipSections({ map, features }: { map: any; features: LineStringFeature[] }) { @@ -148,7 +147,7 @@ export const useMap = () => { type: 'line', source: 'wip-sections', paint: { - 'line-width': 4, + 'line-width': ['case', ['boolean', ['feature-state', 'hover'], false], 6, 4], 'line-color': ['get', 'color'], 'line-dasharray': [0, 2, 2] } @@ -180,8 +179,7 @@ export const useMap = () => { } animateDashArray(0); - map.on('mouseenter', 'wip-sections', () => (map.getCanvas().style.cursor = 'pointer')); - map.on('mouseleave', 'wip-sections', () => (map.getCanvas().style.cursor = '')); + applyHoverEffect({ map, layer: 'wip-sections' }); } function plotPlannedSections({ map, features }: { map: any; features: LineStringFeature[] }) { @@ -203,23 +201,22 @@ export const useMap = () => { if (sections.length === 0) { return; } - map.addSource('not-done-sections', { + map.addSource('planned-sections', { type: 'geojson', data: { type: 'FeatureCollection', features: sections } }); map.addLayer({ - id: 'not-done-sections', + id: 'planned-sections', type: 'line', - source: 'not-done-sections', + source: 'planned-sections', paint: { - 'line-width': 4, + 'line-width': ['case', ['boolean', ['feature-state', 'hover'], false], 6, 4], 'line-color': ['get', 'color'], 'line-dasharray': [2, 2] } }); - map.on('mouseenter', 'not-done-sections', () => (map.getCanvas().style.cursor = 'pointer')); - map.on('mouseleave', 'not-done-sections', () => (map.getCanvas().style.cursor = '')); + applyHoverEffect({ map, layer: 'planned-sections' }); } function plotVarianteSections({ map, features }: { map: any; features: LineStringFeature[] }) { @@ -599,6 +596,32 @@ export const useMap = () => { } } + function applyHoverEffect({ map, layer }) { + let hoveredLineId = null; + map.on('mousemove', layer, e => { + map.getCanvas().style.cursor = 'pointer'; + + if (e.features.length > 0) { + if (hoveredLineId !== null) { + map.setFeatureState({ source: layer, id: hoveredLineId }, { hover: false }); + } + if (e.features[0].id !== undefined) { + hoveredLineId = e.features[0].id; + if (hoveredLineId !== null) { + map.setFeatureState({ source: layer, id: hoveredLineId }, { hover: true }); + } + } + } + }); + map.on('mouseleave', layer, () => { + map.getCanvas().style.cursor = ''; + if (hoveredLineId !== null) { + map.setFeatureState({ source: layer, id: hoveredLineId }, { hover: false }); + } + hoveredLineId = null; + }); + } + return { plotDoneSections, plotWipSections, diff --git a/pages/[_slug]/carte.vue b/pages/[_slug]/carte.vue index 195f4e60..9caa7ca6 100644 --- a/pages/[_slug]/carte.vue +++ b/pages/[_slug]/carte.vue @@ -5,35 +5,38 @@ diff --git a/pages/carte-interactive/index.vue b/pages/carte-interactive/index.vue index c53dc92a..3d3af2c6 100644 --- a/pages/carte-interactive/index.vue +++ b/pages/carte-interactive/index.vue @@ -15,7 +15,10 @@ const { data: voies } = await useAsyncData(() => { return queryContent('voies-lyonnaises').where({ _type: 'json' }).find(); }); -const features = voies.value.map(voie => voie.features).flat(); +const features = voies.value.map(voie => voie.features).flat().map((feature, index) => ({ + id: index, + ...feature +})); const description = 'Découvrez la carte interactive des Voies Lyonnaises. Itinéraires rue par rue. Plan régulièrement mis à jour pour une information complète.'; const COVER_IMAGE_URL = 'https://cyclopolis.lavilleavelo.org/cyclopolis.png';