diff --git a/public/js/module/map/map.js b/public/js/module/map/map.js index be3cd888..a480ed12 100644 --- a/public/js/module/map/map.js +++ b/public/js/module/map/map.js @@ -18,6 +18,14 @@ define([ minZoom: 3, maxZoom: 18, zoom: 17, + geolocationZoom: 12, + }; + + const geoStatus = { + READY: 'ready', + PENDING: 'pending', + ERROR: 'error', + DENIED: 'denied', }; return Cliche.extend({ @@ -119,7 +127,17 @@ define([ this.yearRefreshMarkersBind = this.yearRefreshMarkers.bind(this); this.yearRefreshMarkersTimeout = null; - this.infoShow = ko.observable(true); + // Geolocation + this.geolocationStatus = ko.observable(geoStatus.READY); + + if ('permissions' in navigator) { + navigator.permissions.query({ name: 'geolocation' }).then(result => { + if (result.state === 'denied') { + // Use of geolocation is already denied for this site. + this.geolocationStatus(geoStatus.DENIED); + } + }); + } this.layers.push({ id: 'osm', @@ -876,6 +894,39 @@ define([ return false; }, + isGeolocationSupported: function () { + return !!('geolocation' in navigator); + }, + showMyLocation: function () { + // Geolocate current position. Query position even if we know + // that user denied it already, in Chrome for example this will show + // location icon in status bar, making easier to find + // where to change this setting. Don't query if there is a pending + // request already. + if (this.geolocationStatus() !== geoStatus.PENDING) { + this.geolocationStatus(geoStatus.PENDING); + + const success = function (position) { + this.geolocationStatus(geoStatus.READY); + this.map.setView(new L.LatLng(position.coords.latitude, position.coords.longitude), + defaults.geolocationZoom, { animate: true }); + }.bind(this); + + const error = function error(err) { + if (err.code === err.PERMISSION_DENIED) { + // User denied geolocation. + this.geolocationStatus(geoStatus.DENIED); + } else { + // Position unavilable due to timeout or device internal error. + this.geolocationStatus(geoStatus.ERROR); + } + + console.warn(`Geolocation error: ${err.message}`); + }.bind(this); + + navigator.geolocation.getCurrentPosition(success, error, { maximumAge: 30000, timeout: 10000 }); + } + }, copyGeo: function (data, evt) { if (this.point.geo()) { // Temporaly hide custom tooltip so it does not overlap flashing one. diff --git a/public/style/map/map.less b/public/style/map/map.less index 9356b076..06915555 100644 --- a/public/style/map/map.less +++ b/public/style/map/map.less @@ -158,6 +158,21 @@ } } + .mapPosTools { + position: absolute; + right: 5px; + bottom: 52px; + width: max-content; + } + + .mapPosTool { + position: relative; + display: inline-block; + height: 25px; + margin-left: 3px; + vertical-align: top; + } + .button { width: 26px; padding-top: 1px; @@ -185,6 +200,18 @@ &.no .location-copy { content: url('@{iconLocationCopyI}'); } + + .my-location::before { + content: '\e55c'; + } + + &.nogeo .my-location::before { + content: '\e1b6'; + } + + &.pendinggeo .my-location::before { + content: '\e1b7'; + } } .mapYearSelector { diff --git a/views/module/map/map.pug b/views/module/map/map.pug index 5bf7e14c..f645311f 100644 --- a/views/module/map/map.pug +++ b/views/module/map/map.pug @@ -30,7 +30,12 @@ |  Координаты фотографии не указаны // /ko // /ko - + // ko if: isGeolocationSupported() + .mapPosTools.tltp-wrap + .mapPosTool.button.fringe(data-bind="click: showMyLocation, css: {no: geolocationStatus() === 'denied' || geolocationStatus() === 'error', nogeo: geolocationStatus() === 'denied', pendinggeo: geolocationStatus() === 'pending' }" aria-describedby="mylocation") + span.material-icons.my-location + .tltp.tltp-left.tltp-animate-opacity(id="mylocation" role="tooltip" style="white-space:nowrap" data-bind="text: (geolocationStatus() === 'denied' ? 'Определение местоположения запрещено браузером' : geolocationStatus() === 'error' ? 'Не удается определить местоположение' : 'Моё местоположение')") + // /ko .mapYearSelector .yearSlider .ui-slider-handle.L