From 8eebcea9f56dc2604cc2bff3a907eb4de7ce769c Mon Sep 17 00:00:00 2001 From: Maurice Date: Fri, 30 Oct 2026 21:15:37 +0100 Subject: [PATCH] fix(journey): keep GL photo markers absolutely positioned so thumbnails sit on their coordinates The journey map's photo thumbnails set position:relative inline on the marker element. That overrides the engine's .mapboxgl-marker/.maplibregl-marker position:absolute, so every thumbnail entered the normal flow below the ones drawn before it and the engine's translate was added on top: the further down the list, the further the photo drifted from its spot. The location dot had the same inline position. Both now leave position to the engine's class. Closes #2910 --- client/src/components/Journey/JourneyMapGL.test.tsx | 11 ++++++++++- client/src/components/Journey/JourneyMapGL.tsx | 5 ++++- .../src/components/Map/locationMarkerMapbox.test.ts | 2 ++ client/src/components/Map/locationMarkerMapbox.ts | 4 +++- 4 files changed, 19 insertions(+), 3 deletions(-) diff --git a/client/src/components/Journey/JourneyMapGL.test.tsx b/client/src/components/Journey/JourneyMapGL.test.tsx index 996b08d38..a9534ec4b 100644 --- a/client/src/components/Journey/JourneyMapGL.test.tsx +++ b/client/src/components/Journey/JourneyMapGL.test.tsx @@ -1,4 +1,4 @@ -// FE-COMP-JMAPGL-001 to FE-COMP-JMAPGL-048, FE-COMP-JMAPGL-2453 +// FE-COMP-JMAPGL-001 to FE-COMP-JMAPGL-048, FE-COMP-JMAPGL-2453, FE-COMP-JMAPGL-2910 type Spy = ReturnType @@ -644,6 +644,15 @@ describe('JourneyMapGL', () => { expect(gl.map.on).toHaveBeenCalledWith('moveend', expect.any(Function)) }) + it('FE-COMP-JMAPGL-2910: no marker element sets its own position, so the engine keeps it absolute', () => { + withToken() + const photos = [{ id: 'a', lat: 45, lng: 5, thumbUrl: '/api/photos/1/thumbnail' }] + const marks = [{ kind: 'stay', key: 's1', lat: 46, lng: 6, color: '#14b8a6', stay: {} }] + render() + expect(gl.markers.some(m => m.element.innerHTML.includes('/api/photos/'))).toBe(true) + for (const marker of gl.markers) expect(marker.element.style.position).toBe('') + }) + it('FE-COMP-JMAPGL-2889: panning keeps the photo and mark groups, only a zoom regroups them', () => { withToken() const photos = [ diff --git a/client/src/components/Journey/JourneyMapGL.tsx b/client/src/components/Journey/JourneyMapGL.tsx index b0f887481..8464cd872 100644 --- a/client/src/components/Journey/JourneyMapGL.tsx +++ b/client/src/components/Journey/JourneyMapGL.tsx @@ -557,7 +557,10 @@ function JourneyMapGL( if (!photos?.length) return for (const cluster of clusterPhotos(photos, glWorldPx(map))) { const el = document.createElement('div') - el.style.cssText = 'position:relative;width:48px;height:48px;cursor:pointer' + // No inline `position` (#2910): the engine's marker class positions the element + // absolutely, and `position:relative` would drop it into the normal flow, where + // every thumbnail sits below the ones drawn before it. + el.style.cssText = 'width:48px;height:48px;cursor:pointer' el.innerHTML = photoMarkerHtml(cluster.members[0].thumbUrl, cluster.members.length) el.addEventListener('click', (ev) => { ev.stopPropagation() diff --git a/client/src/components/Map/locationMarkerMapbox.test.ts b/client/src/components/Map/locationMarkerMapbox.test.ts index 7e11699aa..f1e8827d3 100644 --- a/client/src/components/Map/locationMarkerMapbox.test.ts +++ b/client/src/components/Map/locationMarkerMapbox.test.ts @@ -97,6 +97,8 @@ describe('attachLocationMarker', () => { expect(root.children).toHaveLength(3) // The cone starts hidden — there is no heading until the first fix. expect((root.children[1] as HTMLElement).style.display).toBe('none') + // The GL marker class owns `position`; an inline one would drop it into the flow. + expect(root.style.position).toBe('') }) it('FE-COMP-LOCMARKERGL-003: adds the accuracy source and fill layer when the map is already loaded', () => { diff --git a/client/src/components/Map/locationMarkerMapbox.ts b/client/src/components/Map/locationMarkerMapbox.ts index ff28173fe..a0b1857cd 100644 --- a/client/src/components/Map/locationMarkerMapbox.ts +++ b/client/src/components/Map/locationMarkerMapbox.ts @@ -13,7 +13,9 @@ type MarkerConstructor = new (options?: { element?: HTMLElement; anchor?: string // and mapbox doesn't get confused about which element to position. function buildLocationEl(): { root: HTMLDivElement; cone: HTMLDivElement } { const root = document.createElement('div') - root.style.cssText = 'width:28px;height:28px;position:relative;pointer-events:none;' + // No inline `position`: the GL marker class keeps the root absolute, which the + // children's insets resolve against (see the note on markers in MapViewGL). + root.style.cssText = 'width:28px;height:28px;pointer-events:none;' // Accuracy pulse behind the dot const pulse = document.createElement('div') pulse.style.cssText = `