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..7821ce8d 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,62 @@ Draw.Marker = Draw.extend({ this._hintMarker?.setIcon(this.options.markerStyle.icon); } }, + _setupTouchScreenTips() { + let markerHintTip = null; + + 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.remove(); + + // 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); + + 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', + 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()]); + }, }); 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; +}