prop/assets/js/contact_map_hook.ts
Graham McIntire 1f368f9b61 Convert all JavaScript to TypeScript with type annotations
- Rename all .js files to .ts in assets/js/
- Add interfaces for hook state, data structures, and event payloads
- Add type annotations to function parameters and return types
- Create type declarations for vendor deps (Leaflet, Chart.js, Phoenix, topbar)
- Update tsconfig.json for strict TypeScript checking
- Update esbuild entry point to app.ts
2026-04-11 16:59:28 -05:00

86 lines
2.9 KiB
TypeScript

import { updateGridOverlay } from "./maidenhead_grid"
interface ContactMapHook extends ViewHook {
gridLayer: L.LayerGroup
gridVisible: boolean
initMap(): void
}
export const ContactMap = {
mounted(this: ContactMapHook) {
// Defer init until container has dimensions (flex layout timing)
requestAnimationFrame(() => this.initMap())
},
initMap(this: ContactMapHook) {
const pos1 = JSON.parse(this.el.dataset.pos1!) as { lat: number; lon?: number; lng?: number }
const pos2 = JSON.parse(this.el.dataset.pos2!) as { lat: number; lon?: number; lng?: number }
const lat1 = pos1.lat
const lon1 = (pos1.lon || pos1.lng)!
const lat2 = pos2.lat
const lon2 = (pos2.lon || pos2.lng)!
const bounds = L.latLngBounds([[lat1, lon1], [lat2, lon2]])
const map = L.map(this.el, {
zoomControl: true,
attributionControl: false,
scrollWheelZoom: false
}).fitBounds(bounds, {padding: [40, 40], maxZoom: 10})
L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {
maxZoom: 19
}).addTo(map)
// Grid overlay
this.gridLayer = L.layerGroup().addTo(map)
this.gridVisible = true
updateGridOverlay(map, this.gridLayer)
map.on("moveend", () => {
if (this.gridVisible) updateGridOverlay(map, this.gridLayer)
})
// Grid toggle control
const GridToggle = L.Control.extend({
options: { position: "topright" as const },
onAdd: () => {
const btn = L.DomUtil.create("button", "leaflet-bar leaflet-control")
btn.innerHTML = "Grid"
btn.title = "Toggle Maidenhead grid"
btn.style.cssText = "background:#fff;padding:4px 8px;font-size:12px;cursor:pointer;font-weight:600;"
L.DomEvent.disableClickPropagation(btn)
btn.onclick = () => {
this.gridVisible = !this.gridVisible
if (this.gridVisible) {
this.gridLayer.addTo(map)
updateGridOverlay(map, this.gridLayer)
btn.style.opacity = "1"
} else {
this.gridLayer.remove()
btn.style.opacity = "0.5"
}
}
return btn
}
})
new GridToggle().addTo(map)
const marker1 = L.circleMarker([lat1, lon1], {
radius: 6, color: "#2563eb", fillColor: "#3b82f6", fillOpacity: 0.9, weight: 2
}).addTo(map)
const marker2 = L.circleMarker([lat2, lon2], {
radius: 6, color: "#dc2626", fillColor: "#ef4444", fillOpacity: 0.9, weight: 2
}).addTo(map)
const station1 = this.el.dataset.station1
const station2 = this.el.dataset.station2
if (station1) marker1.bindTooltip(station1, {permanent: true, direction: "top", offset: [0, -8]})
if (station2) marker2.bindTooltip(station2, {permanent: true, direction: "top", offset: [0, -8]})
L.polyline([[lat1, lon1], [lat2, lon2]], {
color: "#6366f1", weight: 2, dashArray: "6 4", opacity: 0.8
}).addTo(map)
}
}