feat(rover-locations): inline satellite map per location
Click a row's info area to expand a Leaflet mini-map centered on that pin, defaulting to ESRI World_Imagery satellite (zoom up to 21) with OSM/Topo toggles in a top-right layer control.
This commit is contained in:
parent
18e6088aa8
commit
f63e679e34
3 changed files with 88 additions and 3 deletions
|
|
@ -20,6 +20,7 @@ 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 {LocationMap} from "./location_map_hook"
|
||||
import {RoverSlider} from "./rover_slider_hook"
|
||||
import {HorizontalWheelScroll} from "./horizontal_wheel_scroll_hook"
|
||||
import {BeaconMap} from "./beacon_map_hook"
|
||||
|
|
@ -313,7 +314,7 @@ const csrfToken = document.querySelector("meta[name='csrf-token']")!.getAttribut
|
|||
const liveSocket = new LiveSocket("/live", Socket, {
|
||||
longPollFallbackMs: 2500,
|
||||
params: initLiveStash({_csrf_token: csrfToken}),
|
||||
hooks: {...colocatedHooks, PropagationMap, PathForecast, UtcClock, ContactMap, ContactsMap, ElevationProfile, WeatherMap, LocateMe, CopyLink, RoverMap, RoverSlider, HorizontalWheelScroll, BeaconMap, BeaconsListMap, CommaNumber, ImportConfetti, EmeGlobe, Skewt},
|
||||
hooks: {...colocatedHooks, PropagationMap, PathForecast, UtcClock, ContactMap, ContactsMap, ElevationProfile, WeatherMap, LocateMe, CopyLink, RoverMap, LocationMap, RoverSlider, HorizontalWheelScroll, BeaconMap, BeaconsListMap, CommaNumber, ImportConfetti, EmeGlobe, Skewt},
|
||||
})
|
||||
|
||||
// live_table rows are dead on their own — wire up whole-row clicks to
|
||||
|
|
|
|||
64
assets/js/location_map_hook.ts
Normal file
64
assets/js/location_map_hook.ts
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
import type { ViewHook } from "phoenix_live_view"
|
||||
|
||||
interface LocationMapHook extends ViewHook {
|
||||
map: L.Map
|
||||
marker: L.Marker
|
||||
visibilityHandler: (() => void) | null
|
||||
}
|
||||
|
||||
export const LocationMap: Partial<LocationMapHook> = {
|
||||
mounted(this: LocationMapHook) {
|
||||
const lat = parseFloat(this.el.dataset.lat || "0")
|
||||
const lon = parseFloat(this.el.dataset.lon || "0")
|
||||
|
||||
const osm = L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {
|
||||
maxZoom: 19
|
||||
})
|
||||
const satellite = L.tileLayer(
|
||||
"https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}",
|
||||
{ maxZoom: 21, maxNativeZoom: 19 }
|
||||
)
|
||||
const topo = L.tileLayer("https://{s}.tile.opentopomap.org/{z}/{x}/{y}.png", {
|
||||
maxZoom: 17
|
||||
})
|
||||
|
||||
const map = L.map(this.el, {
|
||||
center: [lat, lon],
|
||||
zoom: 16,
|
||||
zoomControl: true,
|
||||
attributionControl: false
|
||||
})
|
||||
this.map = map
|
||||
|
||||
satellite.addTo(map)
|
||||
|
||||
L.control.layers(
|
||||
{ "Satellite": satellite, "OSM": osm, "Topo": topo },
|
||||
{},
|
||||
{ position: "topright", collapsed: false }
|
||||
).addTo(map)
|
||||
|
||||
this.marker = L.marker([lat, lon]).addTo(map)
|
||||
this.marker.bindPopup(`${lat.toFixed(6)}, ${lon.toFixed(6)}`)
|
||||
|
||||
// LiveView reconnect / tab visibility — Leaflet needs invalidateSize.
|
||||
this.visibilityHandler = () => {
|
||||
if (document.visibilityState === "visible") this.map.invalidateSize()
|
||||
}
|
||||
document.addEventListener("visibilitychange", this.visibilityHandler)
|
||||
|
||||
setTimeout(() => map.invalidateSize(), 50)
|
||||
},
|
||||
|
||||
reconnected(this: LocationMapHook) {
|
||||
this.map?.invalidateSize()
|
||||
},
|
||||
|
||||
destroyed(this: LocationMapHook) {
|
||||
if (this.visibilityHandler) {
|
||||
document.removeEventListener("visibilitychange", this.visibilityHandler)
|
||||
this.visibilityHandler = null
|
||||
}
|
||||
this.map?.remove()
|
||||
}
|
||||
}
|
||||
|
|
@ -18,11 +18,17 @@ defmodule MicrowavepropWeb.RoverLocationsLive do
|
|||
page_title: "Rover Locations",
|
||||
locations: Rover.list_locations(),
|
||||
form: nil,
|
||||
editing_id: nil
|
||||
editing_id: nil,
|
||||
expanded_id: nil
|
||||
)}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_event("toggle_map", %{"id" => id}, socket) do
|
||||
new_id = if socket.assigns.expanded_id == id, do: nil, else: id
|
||||
{:noreply, assign(socket, expanded_id: new_id)}
|
||||
end
|
||||
|
||||
def handle_event("new", _params, socket) do
|
||||
if user = current_user(socket) do
|
||||
form = blank_form(user)
|
||||
|
|
@ -237,7 +243,11 @@ defmodule MicrowavepropWeb.RoverLocationsLive do
|
|||
class="card bg-base-100 border border-base-300 p-4"
|
||||
>
|
||||
<div class="flex items-start justify-between gap-3">
|
||||
<div class="flex-1 min-w-0">
|
||||
<div
|
||||
class="flex-1 min-w-0 cursor-pointer"
|
||||
phx-click="toggle_map"
|
||||
phx-value-id={loc.id}
|
||||
>
|
||||
<div class="flex items-center gap-2 mb-1 flex-wrap">
|
||||
<span class={status_class(loc.status)}>{status_label(loc.status)}</span>
|
||||
<span class="font-mono text-sm font-semibold">
|
||||
|
|
@ -274,6 +284,16 @@ defmodule MicrowavepropWeb.RoverLocationsLive do
|
|||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
:if={@expanded_id == loc.id}
|
||||
id={"location-map-#{loc.id}"}
|
||||
phx-hook="LocationMap"
|
||||
phx-update="ignore"
|
||||
data-lat={loc.lat}
|
||||
data-lon={loc.lon}
|
||||
class="mt-3 h-80 rounded border border-base-300 z-0"
|
||||
>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue