1// maps.js (ES6 module) 2 3import {lib} from './lib.js'; // must expose loadExt on lib 4 5let initialized = false; 6 7let readyPromiseResolve; 8export const ready = new Promise((resolve) => { 9 readyPromiseResolve = resolve; 10}); 11 12/** 13 * Lazy loader based on IntersectionObserver + fallback 14 */ 15function initLazyLoader() { 16 const callback = (entries) => { 17 if (initialized) return; 18 if (entries[0].isIntersecting) { 19 load(); 20 } 21 }; 22 23 if (window.IntersectionObserver) { 24 const observer = new IntersectionObserver(callback); 25 document.querySelectorAll('.module.module-map') 26 .forEach(map => observer.observe(map)); 27 } 28 29 // fallback you already use 30 onViewport('.module.module-map', null, null, function (visible) { 31 if (visible && !initialized) { 32 load(); 33 } 34 }); 35} 36 37function load() { 38 if (initialized) return; 39 initialized = true; 40 41 lib.loadExt([ 42 'https://unpkg.com/[email protected]/dist/leaflet.css', 43 'https://api.mapbox.com/mapbox.js/plugins/leaflet-fullscreen/v1.0.1/leaflet.fullscreen.css' 44 ], () => { 45 lib.loadExt([ 46 'https://unpkg.com/[email protected]/dist/leaflet.js', 47 'https://api.mapbox.com/mapbox.js/plugins/leaflet-fullscreen/v1.0.1/Leaflet.fullscreen.min.js', 48 'https://unpkg.com/[email protected]/leaflet.responsive.popup.js', 49 'https://unpkg.com/[email protected]/leaflet.responsive.popup.css' 50 ], () => { 51 document.body.dispatchEvent( 52 new CustomEvent('maps:scripts_loaded', { 53 bubbles: true, 54 cancelable: true, 55 detail: {init} 56 }) 57 ); 58 init(); 59 }); 60 }); 61} 62 63function init_zoom(map) { 64 // kept intentionally empty (your commented logic removed) 65} 66 67export const Maps = { 68 create(node, map_src, mapWidth, mapHeight, minZoom, maxZoom) { 69 let width = 100; 70 let height = 100; 71 72 const map = new L.FreeMap(node, { 73 width: mapWidth, 74 height: mapHeight, 75 scrollWheelZoom: true, 76 crs: L.extend({}, L.CRS.Simple, { 77 zoom(scale) { 78 return Math.log(scale / (node.clientWidth / width * 0.5)) / Math.LN2; 79 }, 80 scale(zoom) { 81 return Math.pow(2, zoom) * (node.clientWidth / width * 0.5); 82 }, 83 transformation: new L.Transformation(1, 0, mapHeight / mapWidth, 0), 84 }), 85 zoomSnap: 0, 86 zoomControl: false, 87 minZoom, 88 maxZoom, 89 attributionControl: false 90 }); 91 92 const lt = [0, 0]; 93 const rb = [height, width]; 94 const mapBounds = new L.LatLngBounds(lt, rb); 95
96 map.setView([height / 2, width / 2], 1); 97 map.setMaxBounds(map.getBounds()); 98 99 const overlay = L.imageOverlay(map_src, mapBounds).addTo(map); 100 const wrapper = node.firstElementChild; 101 102 //let lastWidth = wrapper.clientWidth; 103 104 const ro = new ResizeObserver(() => { 105 const w = wrapper.clientWidth; 106 const h = Math.round(w * (mapHeight / mapWidth)); 107 requestAnimationFrame(() => { 108 wrapper.style.height = h + 'px'; 109 const center = map.getCenter(); 110 const zoom = map.getZoom(); 111 112 // overlay bounds stay consistent with normalized CRS 113 overlay.setBounds(L.latLngBounds([0, 0], [height, width])); 114 115 map.invalidateSize({animate: false}); 116 map.setView(center, zoom, {animate: false}); 117 }); 118 }); 119 120 ro.observe(wrapper); 121 122 L.control.zoom({position: 'bottomright'}).addTo(map); 123 124 const toggleFullscreen = map.toggleFullscreen; 125 map.toggleFullscreen = function (options) { 126 toggleFullscreen.call(map, options);
127 map.setView([height / 2, width / 2], 1.000001); 128 }; 129 130 map.addControl(new L.Control.Fullscreen({ 131 position: 'topright', 132 title: { 133 false: 'View Fullscreen', 134 true: 'Exit Fullscreen' 135 } 136 })); 137 138 node.addEventListener('keydown', function (e) { 139 if (e.key === 'Escape' && map._popup && map._popup.options.closeOnEscapeKey) { 140 e.preventDefault(); 141 e.stopPropagation(); 142 map.closePopup(); 143 } 144 }, true); 145 146 map.markers = L.featureGroup().addTo(map); 147 148 init_zoom(map); 149 return map; 150 } 151}; 152 153function init() { 154 // document.dispatchEvent( 155 // new CustomEvent('MapLoaded', {bubbles: true, cancelable: true}) 156 // ); 157 158 // Grayscale tile layer 159 L.TileLayer.Grayscale = L.TileLayer.extend({ 160 options: { 161 quotaRed: 21, 162 quotaGreen: 71, 163 quotaBlue: 8, 164 quotaDividerTune: 0, 165 quotaDivider() { 166 return this.quotaRed + this.quotaGreen + this.quotaBlue + this.quotaDividerTune; 167 } 168 }, 169 initialize(url, options = {}) { 170 options.crossOrigin = true; 171 L.TileLayer.prototype.initialize.call(this, url, options); 172 this.on('tileload', e => this._makeGrayscale(e.tile)); 173 }, 174 _createTile() { 175 const tile = L.TileLayer.prototype._createTile.call(this); 176 tile.crossOrigin = 'Anonymous'; 177 return tile; 178 }, 179 _makeGrayscale(tile) { 180 if (tile.getAttribute('data-grayscaled')) return; 181 182 tile.crossOrigin = ''; 183 const canvas = document.createElement('canvas'); 184 canvas.width = tile.width; 185 canvas.height = tile.height; 186 187 const ctx = canvas.getContext('2d'); 188 ctx.drawImage(tile, 0, 0); 189 190 const imgData = ctx.getImageData(0, 0, canvas.width, canvas.height); 191 const data = imgData.data; 192 193 for (let i = 0; i < data.length; i += 4) { 194 const gray = ( 195 this.options.quotaRed * data[i] + 196 this.options.quotaGreen * data[i + 1] + 197 this.options.quotaBlue * data[i + 2] 198 ) / this.options.quotaDivider(); 199 200 data[i] = data[i + 1] = data[i + 2] = gray; 201 } 202 203 ctx.putImageData(imgData, 0, 0); 204 tile.setAttribute('data-grayscaled', 'true'); 205 tile.src = canvas.toDataURL(); 206 } 207 }); 208 209 L.tileLayer.grayscale = function (url, options) { 210 return new L.TileLayer.Grayscale(url, options); 211 }; 212 213 // FreeMap 214 L.FreeMap = L.Map.extend({ 215 initialize(element, options) { 216 element.style.position = 'relative'; 217 element.style.overflow = 'hidden'; 218 console.log(options); 219 const wrapper = document.createElement('div'); 220 const container = document.createElement('div'); 221 const popup_container = document.createElement('div'); 222 223 const aspect = wrapper.appendChild(document.createElement('div')); 224 aspect.style.position = 'relative'; 225 aspect.style.paddingBottom = Math.ceil(options.height / options.width * 100) + '%'; 226 227 setTimeout(() => { 228 this.invalidateSize({animate: false}); 229 }, 0); 230 231 element.appendChild(wrapper); 232 wrapper.appendChild(container); 233 wrapper.appendChild(popup_container); 234 235 Object.assign(popup_container.style, { 236 position: 'absolute', 237 left: 0, 238 top: 0, 239 zIndex: 1000, 240 overflow: 'visible' 241 }); 242 243 L.DomUtil.addClass( 244 popup_container, 245 'leaflet-freepopup-pane leaflet-container' 246 ); 247 248 Object.assign(container.style, { 249 position: 'absolute', 250 left: 0, 251 top: '0', 252 width: '100%', 253 height: '100%' 254 }); 255 256 L.Map.prototype.initialize.call(this, container, options); 257 } 258 });
259 260 // FreePopup 261 L.FreePopup = L.ResponsivePopup.extend({ 262 getEvents() { 263 const events = L.Popup.prototype.getEvents.call(this); 264 events.move = this._adjustPan; 265 return events; 266 }, 267 onAdd(map) { 268 L.Popup.prototype.onAdd.call(this, map); 269 map.getContainer() 270 .parentElement 271 .querySelector('.leaflet-freepopup-pane') 272 .appendChild(this.getElement()); 273 }, 274 _adjustPan() { 275 const pos = this._map.latLngToContainerPoint( 276 this._source.getLatLng() 277 ); 278 const anchor = this._getAnchor(); 279 const newPos = pos.add(anchor); 280 281 L.DomUtil.setPosition(this._container, newPos); 282 283 if (!this._map.getBounds().contains(this._source.getLatLng())) { 284 L.DomUtil.addClass(this._container, 'hidden'); 285 } else if (L.DomUtil.hasClass(this._container, 'hidden')) { 286 L.DomUtil.removeClass(this._container, 'hidden'); 287 } 288 } 289 }); 290 291 // document.dispatchEvent( 292 // new CustomEvent('maps_ready', { 293 // bubbles: true, 294 // cancelable: false, 295 // detail: {maps: Maps} 296 // }) 297 // ); 298 // 299 readyPromiseResolve(Maps); 300} 301 302// start 303initLazyLoader();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.