index.js (5950B)
1 import L from 'leaflet'; 2 import ko from 'knockout'; 3 import './coordinates.css'; 4 import copyToClipboard from '~/lib/clipboardCopy'; 5 import Contextmenu from '~/lib/contextmenu'; 6 import {makeButtonWithBar} from '~/lib/leaflet.control.commons'; 7 import safeLocalStorage from '~/lib/safe-localstorage'; 8 import '~/lib/controls-styles/controls-styles.css'; 9 import * as formats from './formats'; 10 11 const DEFAULT_FORMAT = formats.DEGREES; 12 const UNKNOWN_COORDINATES = { 13 lat: '-------', 14 lng: '-------' 15 }; 16 17 L.Control.Coordinates = L.Control.extend({ 18 options: { 19 position: 'bottomleft' 20 }, 21 22 formats: [ 23 formats.SIGNED_DEGREES, 24 formats.DEGREES, 25 formats.DEGREES_AND_MINUTES, 26 formats.DEGREES_AND_MINUTES_AND_SECONDS 27 ], 28 29 initialize: function(options) { 30 L.Control.prototype.initialize.call(this, options); 31 32 this.latlng = ko.observable(); 33 this.format = ko.observable(DEFAULT_FORMAT); 34 this.formatCode = ko.pureComputed({ 35 read: () => this.format().code, 36 write: (value) => { 37 for (let format of this.formats) { 38 if (value === format.code) { 39 this.format(format); 40 break; 41 } 42 } 43 } 44 }); 45 46 this.formattedCoordinates = ko.pureComputed(() => { 47 if (this.latlng()) { 48 return formats.formatLatLng(this.latlng().wrap(), this.format()); 49 } 50 return UNKNOWN_COORDINATES; 51 }, this); 52 53 this.wrapperClass = ko.pureComputed(() => this.format().wrapperClass, this); 54 55 this.formatCode.subscribe(this.saveStateToStorage, this); 56 }, 57 58 onAdd: function(map) { 59 const {container, link, barContainer} = makeButtonWithBar( 60 'leaflet-contol-coordinates', 61 'Show coordinates at cursor', 62 'icon-coordinates' 63 ); 64 65 this._map = map; 66 this._container = container; 67 this._link = link; 68 69 barContainer.innerHTML = ` 70 <div data-bind="css: wrapperClass"> 71 <div class="leaflet-coordinates-container" data-bind="with: formattedCoordinates()"> 72 <span class="leaflet-coordinates-latitude" data-bind="html: lat"></span> 73 <span class="leaflet-coordinates-longitude" data-bind="html: lng"></span> 74 </div> 75 <hr class="leaflet-coordinates-divider" /> 76 <div data-bind="foreach: formats"> 77 <div> 78 <label title=""> 79 <input type="radio" data-bind="checked: $parent.formatCode, value: code" 80 class="leaflet-coordinates-format-radio"/> 81 <span data-bind="html: label"></span> 82 </label> 83 </div> 84 </div> 85 </div> 86 `; 87 88 barContainer.title = "Right click on map to copy coordinates"; 89 90 this.loadStateFromStorage(); 91 ko.applyBindings(this, container); 92 L.DomEvent.on(link, 'click', this.onClick, this); 93 94 return container; 95 }, 96 97 onRemove: function() { 98 L.DomEvent.off(this._link, 'click', this.onClick, this); 99 }, 100 101 loadStateFromStorage: function() { 102 const active = safeLocalStorage.leafletCoordinatesActive === '1'; 103 const code = safeLocalStorage.leafletCoordinatesFmt || DEFAULT_FORMAT.code; 104 105 this.formatCode(code); 106 this.setEnabled(active); 107 }, 108 109 saveStateToStorage: function() { 110 safeLocalStorage.leafletCoordinatesActive = this.isEnabled() ? '1' : '0'; 111 safeLocalStorage.leafletCoordinatesFmt = this.formatCode(); 112 }, 113 114 onMouseMove: function(e) { 115 this.latlng(e.latlng); 116 }, 117 118 setEnabled: function(enabled) { 119 if (Boolean(enabled) === this.isEnabled()) { 120 return; 121 } 122 const classFunc = enabled ? 'addClass' : 'removeClass'; 123 const eventFunc = enabled ? 'on' : 'off'; 124 L.DomUtil[classFunc](this._container, 'active'); 125 L.DomUtil[classFunc](this._container, 'highlight'); 126 L.DomUtil[classFunc](this._map._container, 'coordinates-control-active'); 127 this._map[eventFunc]('mousemove', this.onMouseMove, this); 128 this._map[eventFunc]('contextmenu', this.onMapRightClick, this); 129 this._isEnabled = Boolean(enabled); 130 this.latlng(null); 131 }, 132 133 onMapRightClick: function(e) { 134 L.DomEvent.stop(e); 135 136 function createItem(format, options = {}) { 137 const {lat, lng} = formats.formatLatLng(e.latlng.wrap(), format); 138 const coordinates = `${lat} ${lng}`; 139 140 return {text: `${coordinates} <span class="leaflet-coordinates-menu-fmt">${format.label}</span>`, 141 callback: () => copyToClipboard(coordinates, e.originalEvent), 142 ...options}; 143 } 144 145 const header = createItem(this.format(), { 146 text: '<b>Copy coordinates to clipboard</b>', 147 header: true, 148 }); 149 const items = this.formats.map((format) => createItem(format)); 150 items.unshift(header, '-'); 151 152 new Contextmenu(items).show(e); 153 }, 154 155 isEnabled: function() { 156 return Boolean(this._isEnabled); 157 }, 158 159 onClick: function() { 160 this.setEnabled(!this.isEnabled()); 161 this.saveStateToStorage(); 162 } 163 } 164 );