From 9d2253a4a235319ec46631aa153e8ae169ebd198 Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Wed, 21 Jan 2026 15:15:24 +0000 Subject: [PATCH] Get search map overlay working again. --- htmlpage/static/js/map-locator.js | 7 - htmlpage/static/js/map-multipoint.js | 220 +++++++++++++++++++++++++++ public-report/search.go | 6 +- public-report/template/search.html | 37 ++--- 4 files changed, 243 insertions(+), 27 deletions(-) create mode 100644 htmlpage/static/js/map-multipoint.js diff --git a/htmlpage/static/js/map-locator.js b/htmlpage/static/js/map-locator.js index d9c362b2..5c641110 100644 --- a/htmlpage/static/js/map-locator.js +++ b/htmlpage/static/js/map-locator.js @@ -206,10 +206,3 @@ class MapLocator extends HTMLElement { } customElements.define('map-locator', MapLocator); - - - -function mapLoad(MAPBOX_ACCESS_TOKEN) { - return new Promise((resolve, reject) => { - }); -} diff --git a/htmlpage/static/js/map-multipoint.js b/htmlpage/static/js/map-multipoint.js new file mode 100644 index 00000000..1e15b108 --- /dev/null +++ b/htmlpage/static/js/map-multipoint.js @@ -0,0 +1,220 @@ +var map = null; +// A map that shows multiple single point locations. +// Points have additional detail popups. +class MapMultipoint extends HTMLElement { + constructor() { + super(); + + // Create a shadow DOM + this.attachShadow({mode: "open" }); + + // Initial render + this.render(); + + this._map = null; + // markers shown on the map. Should be none or 1, generally. + this._markers = null; + } + + // Lifecycle: when element is added to the DOM + connectedCallback() { + // Initialize the map when the element is added to the DOM + setTimeout(() => this._initializeMap(), 0); + } + + disconnectedCallback() { + if (this._map) { + this._map.remove(); + } + } + + // Lifecycle: watch these attributes for changes + static get observedAttributes() { + return ['api-key', 'latitude', 'longitude', 'zoom']; + } + + // Lifecycle: respond to attribute changes + attributeChangedCallback(name, oldValue, newValue) { + // Only handle if map exists and values actually changed + if (!this._map || oldValue === newValue) return; + + if (name === 'latitude' || name === 'longitude') { + if (this.hasAttribute('latitude') && this.hasAttribute('longitude')) { + const lat = Number(this.getAttribute('latitude')); + const lng = Number(this.getAttribute('longitude')); + this._map.setCenter([lat, lng]); + } + } + + if (name === 'zoom') { + this._map.setZoom(Number(newValue)); + } + } + + _initializeMap() { + const apiKey = this.getAttribute("api-key"); + const lat = Number(this.getAttribute('latitude') || 36.2); + const lng = Number(this.getAttribute('longitude') || -119.2); + const organization_id = Number(this.getAttribute("organization-id") || 0); + const tegola = this.getAttribute("tegola") + const zoom = Number(this.getAttribute('zoom') || 15); + + mapboxgl.accessToken = apiKey; + const mapElement = this.shadowRoot.querySelector("#map"); + this._map = new mapboxgl.Map({ + container: mapElement, + center: { + lat: lat, + lng: lng, + }, + style: 'mapbox://styles/mapbox/streets-v12', // style URL + zoom: zoom, + }); + this._map.addControl(new mapboxgl.GeolocateControl({ + positionOptions: { + enableHighAccuracy: true + }, + trackUserLocation: true, + showUserHeading: true + })); + this._map.addControl(new mapboxgl.NavigationControl()); + this._map.on("load", () => { + this.dispatchEvent(new CustomEvent('load'), { + bubbles: true, + composed: true, // Allows event to cross shadow DOM boundary + detail: { + map: this + } + }); + }); + } + + async _fetchAddressSuggestions(text) { + try { + const url = `https://api.mapbox.com/search/geocode/v6/forward?q=${encodeURIComponent(text)}&access_token=${this._apiKey}`; + + const response = await fetch(url); + const data = await response.json(); + return data; + } catch (error) { + console.error('Error fetching geocoding suggestions:', error); + } + } + + _renderSuggestions(suggestions) { + console.log("Rendering suggestions", suggestions); + this._suggestions.innerHTML = suggestions.map((item, index) => { + if (item.properties.place_formatted != "") { + return ` +
+
${item.properties.name || item.properties.full_address}
+
${item.properties.place_formatted}
+
` + } else { + return ` +
+
${item.properties.name || item.properties.full_address}
+
${item.properties.place_formatted}
+
` + } + }).join(''); + + // Add click listeners to suggestions + this.shadowRoot.querySelectorAll('.suggestion-item').forEach(el => { + el.addEventListener('click', e => { + const index = parseInt(el.dataset.index); + const suggestion = suggestions[index]; + this.value = suggestion.properties.full_address; + this._suggestions.innerHTML = ''; + + // Dispatch custom event + this.dispatchEvent(new CustomEvent('address-selected', { + bubbles: true, + composed: true, // Allows event to cross shadow DOM boundary + detail: { + location: suggestion + } + })); + }); + }); + } + + // Initial render of component + render() { + this.shadowRoot.innerHTML = ` + + +
+
+
+ `; + } + + addLayer(a) { + return this._map.addLayer(a); + } + addSource(a, b) { + return this._map.addSource(a, b); + } + jumpTo(args) { + return this._map.jumpTo(args); + } + on(a, b) { + return this._map.on(a, b); + } + queryRenderedFeatures(a) { + return this._map.queryRenderedFeatures(a); + } + + setMarker(coords) { + console.log("Setting map marker", coords); + this._map.jumpTo({ + center: coords, + zoom: 14, + }); + this._markers.forEach((marker) => marker.remove()); + + const marker = new mapboxgl.Marker({ + color: "#FF0000", + draggable: true + }).setLngLat(coords).addTo(map); + marker.on('dragend', function(e) { + const markerDraggedEvent = new CustomEvent("markerdragend", { + detail: { + marker: marker + } + }); + mapContainer.dispatchEvent(markerDraggedEvent); + }); + this._markers = [marker]; + } +} + +customElements.define('map-multipoint', MapMultipoint); diff --git a/public-report/search.go b/public-report/search.go index d92748e0..1ed6a637 100644 --- a/public-report/search.go +++ b/public-report/search.go @@ -7,8 +7,9 @@ import ( "github.com/Gleipnir-Technology/nidus-sync/htmlpage" ) -type ContextSearch struct { +type ContentSearch struct { MapboxToken string + URLTegola string } var ( @@ -19,8 +20,9 @@ func getSearch(w http.ResponseWriter, r *http.Request) { htmlpage.RenderOrError( w, Search, - ContextSearch{ + ContentSearch{ MapboxToken: config.MapboxToken, + URLTegola: config.URLTegola, }, ) } diff --git a/public-report/template/search.html b/public-report/template/search.html index 725a764c..71e9f67b 100644 --- a/public-report/template/search.html +++ b/public-report/template/search.html @@ -6,7 +6,7 @@ - +