From 2d4ed8af79ac761096d01c3e2668d90c6a674d94 Mon Sep 17 00:00:00 2001 From: Joonas Date: Thu, 16 Oct 2025 15:25:12 +0300 Subject: [PATCH 1/2] Add sticky marker tooltip for touch devices --- cypress/e2e/tooltips.cy.js | 30 ++++++++++++++++++ src/assets/translations/en.json | 1 + src/assets/translations/fi.json | 1 + src/css/layers.css | 4 +++ src/js/Draw/L.PM.Draw.Marker.js | 56 ++++++++++++++++++++++++++++++++- src/js/helpers/index.js | 12 +++++++ 6 files changed, 103 insertions(+), 1 deletion(-) diff --git a/cypress/e2e/tooltips.cy.js b/cypress/e2e/tooltips.cy.js index 2d7e6149..b7a414b9 100644 --- a/cypress/e2e/tooltips.cy.js +++ b/cypress/e2e/tooltips.cy.js @@ -54,6 +54,36 @@ describe('Shows Tooltips', () => { cy.get('.leaflet-tooltip-bottom').should('not.exist'); }); + it('Has A Sticky Marker Tooltip If No Pointer Device Is Available', () => { + cy.window().then((win) => { + cy.stub(win, 'matchMedia').callsFake(() => ({ + matches: false, + })); + }); + + cy.get('.leaflet-tooltip-stickynote').should('not.exist'); + + cy.toolbarButton('marker').click(); + cy.get('.leaflet-tooltip-stickynote').should('exist'); + + cy.get('.leaflet-tooltip-stickynote').then((el) => { + expect(el).to.have.text('Place the markers by clicking the map'); + }); + + cy.get(mapSelector).click(290, 250); + + cy.wait(500); + + cy.get('.leaflet-tooltip-stickynote').then((el) => { + expect(el.length).to.eq(1); + expect(el).to.have.text('Place the markers by clicking the map'); + }); + + cy.toolbarButton('marker').click(); + + cy.get('.leaflet-tooltip-stickynote').should('not.exist'); + }); + it('Has Rectangle Tooltips', () => { cy.get('.leaflet-tooltip-bottom').should('not.exist'); cy.toolbarButton('rectangle').click(); diff --git a/src/assets/translations/en.json b/src/assets/translations/en.json index 7ab21066..4d0cd5b4 100644 --- a/src/assets/translations/en.json +++ b/src/assets/translations/en.json @@ -1,6 +1,7 @@ { "tooltips": { "placeMarker": "Click to place marker", + "placeMarkerTip": "Place the markers by clicking the map", "firstVertex": "Click to place first vertex", "continueLine": "Click to continue drawing", "finishLine": "Click any existing marker to finish", diff --git a/src/assets/translations/fi.json b/src/assets/translations/fi.json index 52290283..66bee0c4 100644 --- a/src/assets/translations/fi.json +++ b/src/assets/translations/fi.json @@ -1,6 +1,7 @@ { "tooltips": { "placeMarker": "Klikkaa asettaaksesi merkin", + "placeMarkerTip": "Aseta merkkejä kartalle klikkaamalla.", "firstVertex": "Klikkaa asettaakseni ensimmäisen osuuden", "continueLine": "Klikkaa jatkaaksesi piirtämistä", "finishLine": "Klikkaa olemassa olevaa merkkiä lopettaaksesi", diff --git a/src/css/layers.css b/src/css/layers.css index 7d878e99..ea5d7e24 100644 --- a/src/css/layers.css +++ b/src/css/layers.css @@ -90,3 +90,7 @@ .pm-textarea.pm-hasfocus { cursor: auto; } + +.leaflet-tooltip-stickynote::before { + display: none; +} diff --git a/src/js/Draw/L.PM.Draw.Marker.js b/src/js/Draw/L.PM.Draw.Marker.js index a7fa658d..22a6c88f 100644 --- a/src/js/Draw/L.PM.Draw.Marker.js +++ b/src/js/Draw/L.PM.Draw.Marker.js @@ -1,5 +1,5 @@ import Draw from './L.PM.Draw'; -import { getTranslation } from '../helpers'; +import { getTranslation, deviceHasFinePointer } from '../helpers'; Draw.Marker = Draw.extend({ initialize(map) { @@ -63,6 +63,8 @@ Draw.Marker = Draw.extend({ }); } + this._setupTouchScreenTips(); + // fire drawstart event this._fireDrawStart(); this._setGlobalDrawMode(); @@ -194,4 +196,56 @@ Draw.Marker = Draw.extend({ this._hintMarker?.setIcon(this.options.markerStyle.icon); } }, + _setupTouchScreenTips() { + let markerHintTip = null; + const updateHintPosition = () => { + if (!markerHintTip) { + return; + } + + const bounds = this._map.getBounds(); + + // Calculate the longitude distance of the map's visible area. This allows + // for an easy way to figure out the center point of the map. + const distance = this._map.distance( + bounds.getNorthWest(), + bounds.getNorthEast() + ); + + // Calculate new bounds with the current latitude distance of the visible + // map from the northwest point of the map. Within these new bounds, the + // east border is at the center of the map. + const newBounds = bounds.getNorthWest().toBounds(distance); + + // Set the hint at the top center of the map. + markerHintTip.setLatLng([bounds.getNorth(), newBounds.getEast()]); + }; + this._map.on('pm:drawstart', (event) => { + if (event.shape === 'Marker' && !deviceHasFinePointer()) { + // Hides the "hint marker" as that is confusing for touch screen users. + const drawMarker = this._map.pm.Draw.Marker; + drawMarker._hintMarker.setOpacity(0); + drawMarker._hintMarker.closeTooltip(); + + // Creates the touch screen hint "permanently" (while using the tool) + // sticked at the top of the map. + markerHintTip = L.tooltip([0, 0], { + className: 'leaflet-tooltip-stickynote', + content: getTranslation('tooltips.placeMarkerTip'), + direction: 'bottom', + permanent: true, + }).addTo(this._map); + + updateHintPosition(); + this._map.on('zoomlevelschange resize move', updateHintPosition, this); + } + }); + this._map.on('pm:drawend', (event) => { + if (event.shape === 'Marker' && markerHintTip) { + markerHintTip.remove(); + markerHintTip = null; + this._map.off('zoomlevelschange resize move', updateHintPosition, this); + } + }); + }, }); diff --git a/src/js/helpers/index.js b/src/js/helpers/index.js index 4dcabbe5..a3929c32 100644 --- a/src/js/helpers/index.js +++ b/src/js/helpers/index.js @@ -282,3 +282,15 @@ export function getRenderer(layer) { layer._renderer ); } + +/** + * Checks if the device has a "fine" pointer (such as a mouse) and can therefore + * display the "cursor marker", i.e. the marker that follows the mouse + * movements. + * + * @returns {Boolean} A boolean indicating whether the device has a pointer + * device with a visible cursor. + */ +export function deviceHasFinePointer() { + return window.matchMedia('(pointer:fine)').matches; +} From 5ab013622100363489133f25b25ae15fad77642d Mon Sep 17 00:00:00 2001 From: Joonas Date: Fri, 17 Oct 2025 14:09:35 +0300 Subject: [PATCH 2/2] Remove hintMarker in case of touch screen instead of hiding it / Create a separate class function for 'updateHintPosition' --- src/js/Draw/L.PM.Draw.Marker.js | 58 ++++++++++++++++++--------------- 1 file changed, 32 insertions(+), 26 deletions(-) diff --git a/src/js/Draw/L.PM.Draw.Marker.js b/src/js/Draw/L.PM.Draw.Marker.js index 22a6c88f..7821ce8d 100644 --- a/src/js/Draw/L.PM.Draw.Marker.js +++ b/src/js/Draw/L.PM.Draw.Marker.js @@ -198,34 +198,12 @@ Draw.Marker = Draw.extend({ }, _setupTouchScreenTips() { let markerHintTip = null; - const updateHintPosition = () => { - if (!markerHintTip) { - return; - } - - const bounds = this._map.getBounds(); - - // Calculate the longitude distance of the map's visible area. This allows - // for an easy way to figure out the center point of the map. - const distance = this._map.distance( - bounds.getNorthWest(), - bounds.getNorthEast() - ); - - // Calculate new bounds with the current latitude distance of the visible - // map from the northwest point of the map. Within these new bounds, the - // east border is at the center of the map. - const newBounds = bounds.getNorthWest().toBounds(distance); - // Set the hint at the top center of the map. - markerHintTip.setLatLng([bounds.getNorth(), newBounds.getEast()]); - }; this._map.on('pm:drawstart', (event) => { if (event.shape === 'Marker' && !deviceHasFinePointer()) { // Hides the "hint marker" as that is confusing for touch screen users. const drawMarker = this._map.pm.Draw.Marker; - drawMarker._hintMarker.setOpacity(0); - drawMarker._hintMarker.closeTooltip(); + drawMarker._hintMarker.remove(); // Creates the touch screen hint "permanently" (while using the tool) // sticked at the top of the map. @@ -236,16 +214,44 @@ Draw.Marker = Draw.extend({ permanent: true, }).addTo(this._map); - updateHintPosition(); - this._map.on('zoomlevelschange resize move', updateHintPosition, this); + const updateHandler = () => this._updateHintPosition(markerHintTip); + + updateHandler(); + this._map.on('zoomlevelschange resize move', updateHandler, this); } }); this._map.on('pm:drawend', (event) => { if (event.shape === 'Marker' && markerHintTip) { markerHintTip.remove(); markerHintTip = null; - this._map.off('zoomlevelschange resize move', updateHintPosition, this); + this._map.off( + 'zoomlevelschange resize move', + this._updateHintPosition, + this + ); } }); }, + _updateHintPosition(markerHintTip) { + if (!markerHintTip) { + return; + } + + const bounds = this._map.getBounds(); + + // Calculate the longitude distance of the map's visible area. This allows + // for an easy way to figure out the center point of the map. + const distance = this._map.distance( + bounds.getNorthWest(), + bounds.getNorthEast() + ); + + // Calculate new bounds with the current latitude distance of the visible + // map from the northwest point of the map. Within these new bounds, the + // east border is at the center of the map. + const newBounds = bounds.getNorthWest().toBounds(distance); + + // Set the hint at the top center of the map. + markerHintTip.setLatLng([bounds.getNorth(), newBounds.getEast()]); + }, });