// Leaflet map library (vendored) import * as L from "../vendor/leaflet/leaflet.js" window.L = L // Include phoenix_html to handle method=PUT/DELETE in forms and buttons. import "phoenix_html" // Establish Phoenix Socket and LiveView configuration. import {Socket} from "phoenix" import {LiveSocket} from "phoenix_live_view" import {hooks as colocatedHooks} from "phoenix-colocated/microwaveprop" import topbar from "../vendor/topbar" import initLiveStash from "../../deps/live_stash/assets/js/live-stash.js" import {PropagationMap} from "./propagation_map_hook" import {ContactMap} from "./contact_map_hook" import {ContactsMap} from "./contacts_map_hook" import {ElevationProfile} from "./elevation_profile_hook" import {WeatherMap} from "./weather_map_hook" import {LocateMe, CopyLink} from "./locate_me_hook" import {RoverMap} from "./rover_map_hook" import {BeaconMap} from "./beacon_map_hook" import {BeaconsListMap} from "./beacons_list_map_hook" interface UtcClockHook extends ViewHook { timer: ReturnType tick(): void } const UtcClock = { mounted(this: UtcClockHook) { this.tick() this.timer = setInterval(() => this.tick(), 10_000) }, tick(this: UtcClockHook) { const now = new Date() const h = now.getUTCHours().toString().padStart(2, "0") const m = now.getUTCMinutes().toString().padStart(2, "0") this.el.textContent = `${h}:${m} UTC` }, destroyed(this: UtcClockHook) { clearInterval(this.timer) } } interface CommaNumberHook extends ViewHook { format(): void } const CommaNumber = { mounted(this: CommaNumberHook) { this.el.addEventListener("input", () => this.format()) this.format() }, updated(this: CommaNumberHook) { this.format() }, format(this: CommaNumberHook) { const input = (this.el.querySelector("input") || this.el) as HTMLInputElement const raw = input.value.replace(/,/g, "") if (raw === "" || raw === "-") return const cursor = input.selectionStart ?? 0 const commasBefore = (input.value.slice(0, cursor).match(/,/g) || []).length const parts = raw.split(".") parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ",") const formatted = parts.join(".") if (formatted !== input.value) { input.value = formatted const commasAfter = (formatted.slice(0, cursor).match(/,/g) || []).length const newCursor = cursor + (commasAfter - commasBefore) input.setSelectionRange(newCursor, newCursor) } } } const csrfToken = document.querySelector("meta[name='csrf-token']")!.getAttribute("content") const liveSocket = new LiveSocket("/live", Socket, { longPollFallbackMs: 2500, params: initLiveStash({_csrf_token: csrfToken}), hooks: {...colocatedHooks, PropagationMap, UtcClock, ContactMap, ContactsMap, ElevationProfile, WeatherMap, LocateMe, CopyLink, RoverMap, BeaconMap, BeaconsListMap, CommaNumber}, }) // live_table rows are dead on their own — wire up whole-row clicks to // trigger the first inside that row (typically the Actions "View" // link). Clicks on interactive elements are ignored so inner links/buttons // still win. document.addEventListener("click", (e: MouseEvent) => { const target = e.target as HTMLElement | null if (!target) return if (target.closest("a, button, input, select, label, [role='option'], [role='menuitem']")) return const tr = target.closest("#live-table tbody tr[id]") as HTMLTableRowElement | null if (!tr || tr.id === "empty-placeholder") return const link = tr.querySelector("a[href]") as HTMLAnchorElement | null if (link) link.click() }) // Show progress bar on live navigation and form submits topbar.config({barColors: {0: "#29d"}, shadowColor: "rgba(0, 0, 0, .3)"}) window.addEventListener("phx:page-loading-start", _info => topbar.show(300)) window.addEventListener("phx:page-loading-stop", _info => topbar.hide()) // Delay disconnect alerts by 3 seconds so brief hiccups don't flash errors let disconnectTimer: ReturnType | null = null const DISCONNECT_DELAY_MS = 3000 function showDisconnectAlert(id: string) { const el = document.getElementById(id) if (el) el.removeAttribute("hidden") } function hideDisconnectAlert(id: string) { const el = document.getElementById(id) if (el) el.setAttribute("hidden", "") } new MutationObserver(() => { const root = document.querySelector("[data-phx-main]") if (!root) return const hasError = root.classList.contains("phx-client-error") || root.classList.contains("phx-server-error") if (hasError && !disconnectTimer) { disconnectTimer = setTimeout(() => { const r = document.querySelector("[data-phx-main]") if (r?.classList.contains("phx-client-error")) showDisconnectAlert("client-error") if (r?.classList.contains("phx-server-error")) showDisconnectAlert("server-error") }, DISCONNECT_DELAY_MS) } else if (!hasError && disconnectTimer) { clearTimeout(disconnectTimer) disconnectTimer = null hideDisconnectAlert("client-error") hideDisconnectAlert("server-error") } }).observe(document.body, {subtree: true, attributes: true, attributeFilter: ["class"]}) // connect if there are any LiveViews on the page liveSocket.connect() // expose liveSocket on window for web console debug logs and latency simulation: // >> liveSocket.enableDebug() // >> liveSocket.enableLatencySim(1000) // enabled for duration of browser session // >> liveSocket.disableLatencySim() window.liveSocket = liveSocket // The lines below enable quality of life phoenix_live_reload // development features: // // 1. stream server logs to the browser console // 2. click on elements to jump to their definitions in your code editor // if (process.env.NODE_ENV === "development") { window.addEventListener("phx:live_reload:attached", ({detail: reloader}: any) => { // Enable server log streaming to client. // Disable with reloader.disableServerLogs() reloader.enableServerLogs() // Open configured PLUG_EDITOR at file:line of the clicked element's HEEx component // // * click with "c" key pressed to open at caller location // * click with "d" key pressed to open at function component definition location let keyDown: string | null window.addEventListener("keydown", (e: KeyboardEvent) => keyDown = e.key) window.addEventListener("keyup", (_e: KeyboardEvent) => keyDown = null) window.addEventListener("click", (e: MouseEvent) => { if(keyDown === "c"){ e.preventDefault() e.stopImmediatePropagation() reloader.openEditorAtCaller(e.target) } else if(keyDown === "d"){ e.preventDefault() e.stopImmediatePropagation() reloader.openEditorAtDef(e.target) } }, true) window.liveReloader = reloader }) }