1import { Controller } from '@hotwired/stimulus' 2import { ExpandControl, groundRadiusStops, loadMapLibre, MAPS_STYLE_URL, OSM_ATTRIBUTION, showMapUnavailable } from 'utils/maplibre' 3 4/* global IntersectionObserver */ 5 6// Connects to data-controller='registrations--show--map' 7// Renders a map centered on the coordinates, marking them with a pin (point) or 8// a translucent red circle (approximate area). 9 10// A translucent circle covering the approximate area 11const CIRCLE_PAINT = (radiusMeters, latitude) => ({ 12 'circle-radius': groundRadiusStops(radiusMeters, latitude), 13 'circle-color': 'red', 14 'circle-opacity': 0.4 15}) 16 17export default class extends Controller { 18 static targets = ['canvas', 'unavailable', 'pin'] 19 static values = { 20 latitude: Number, 21 longitude: Number, 22 radiusMeters: Number 23 } 24 25 // Load only once on screen â it can sit in a collapsed panel or below the fold 26 connect () { 27 this.observer = new IntersectionObserver((entries) => { 28 if (!entries.some((entry) => entry.isIntersecting)) return 29 this.observer.disconnect() 30 this.#load() 31 }, { rootMargin: '200px' }) 32 this.observer.observe(this.element) 33 } 34 35 async #load () { 36 try { 37 const maplibregl = await loadMapLibre() 38 if (!this.element.isConnected) return // disconnected while loading 39 40 this.#render(maplibregl) 41 } catch (error) { 42 showMapUnavailable(error, { 43 source: this.identifier, 44 map: this.map, 45 canvas: this.canvasTarget, 46 message: this.hasUnavailableTarget ? this.unavailableTarget : null 47 }) 48 this.map = null 49 } 50 } 51 52 disconnect () { 53 this.observer?.disconnect() 54 this.map?.remove() 55 this.map = null 56 } 57 58 #render (maplibregl) { 59 const center = [this.longitudeValue, this.latitudeValue] 60 this.map = new maplibregl.Map({ 61 container: this.canvasTarget, 62 style: MAPS_STYLE_URL, 63 center, 64 zoom: 13, 65 maxZoom: 16, 66 attributionControl: { customAttribution: OSM_ATTRIBUTION } 67 }) 68 this.map.addControl(new ExpandControl(), 'top-right') 69 70 if (this.hasPinTarget) { 71 const element = this.pinTarget.content.firstElementChild.cloneNode(true) 72 new maplibregl.Marker({ element, anchor: 'bottom' }).setLngLat(center).addTo(this.map) 73 return 74 } 75 76 this.map.on('load', () => { 77 this.map.addSource('location', { 78 type: 'geojson', 79 data: { type: 'Feature', geometry: { type: 'Point', coordinates: center } } 80 }) 81 this.map.addLayer({ 82 id: 'location', 83 type: 'circle', 84 source: 'location', 85 paint: CIRCLE_PAINT(this.radiusMetersValue, this.latitudeValue) 86 }) 87 }) 88 } 89};
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.