feat(rover): live slider labels via RoverSlider hook for forecast hour + drive time

This commit is contained in:
Graham McIntire 2026-04-25 16:30:38 -05:00
parent db1539086b
commit 10fcc764f6
No known key found for this signature in database
GPG key ID: F4ABF488E6029E59
3 changed files with 59 additions and 3 deletions

View file

@ -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 {RoverSlider} from "./rover_slider_hook"
import {BeaconMap} from "./beacon_map_hook"
import {BeaconsListMap} from "./beacons_list_map_hook"
@ -311,7 +312,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, BeaconMap, BeaconsListMap, CommaNumber, ImportConfetti, EmeGlobe, Skewt},
hooks: {...colocatedHooks, PropagationMap, PathForecast, UtcClock, ContactMap, ContactsMap, ElevationProfile, WeatherMap, LocateMe, CopyLink, RoverMap, RoverSlider, BeaconMap, BeaconsListMap, CommaNumber, ImportConfetti, EmeGlobe, Skewt},
})
// live_table rows are dead on their own — wire up whole-row clicks to

View file

@ -0,0 +1,42 @@
import type { ViewHook } from "phoenix_live_view"
interface RoverSliderHook extends ViewHook {
labelEl: HTMLElement | null
formatter: (v: number) => string
inputHandler: () => void
}
const formatters: Record<string, (v: number) => string> = {
forecast: (v) => `+${v}h`,
drive: (v) => {
const hours = Math.round((v / 60) * 10) / 10
return `${hours}h`
}
}
export const RoverSlider: Partial<RoverSliderHook> = {
mounted(this: RoverSliderHook) {
const labelId = this.el.dataset.labelId || ""
const format = this.el.dataset.format || "forecast"
this.formatter = formatters[format] || formatters.forecast
this.labelEl = document.getElementById(labelId)
this.inputHandler = () => {
if (this.labelEl) {
this.labelEl.textContent = this.formatter(Number((this.el as HTMLInputElement).value))
}
}
this.el.addEventListener("input", this.inputHandler)
this.inputHandler()
},
updated(this: RoverSliderHook) {
if (this.inputHandler) this.inputHandler()
},
destroyed(this: RoverSliderHook) {
if (this.inputHandler) this.el.removeEventListener("input", this.inputHandler)
}
}

View file

@ -630,10 +630,17 @@ defmodule MicrowavepropWeb.RoverLive do
value={@forecast_hour}
phx-change="set_forecast_hour"
phx-debounce="200"
phx-hook="RoverSlider"
phx-update="ignore"
id="rover-forecast-slider"
data-label-id="rover-forecast-label"
data-format="forecast"
name="value"
class="range range-xs range-primary"
/>
<div class="text-xs opacity-70 mt-1">+{@forecast_hour}h</div>
<div class="text-xs opacity-70 mt-1">
<span id="rover-forecast-label">+{@forecast_hour}h</span>
</div>
</.sidebar_section>
<.sidebar_section title="BAND">
@ -662,11 +669,17 @@ defmodule MicrowavepropWeb.RoverLive do
value={@max_drive_min}
phx-change="set_max_drive_time"
phx-debounce="200"
phx-hook="RoverSlider"
phx-update="ignore"
id="rover-drive-slider"
data-label-id="rover-drive-label"
data-format="drive"
name="value"
class="range range-xs range-primary"
/>
<div class="text-xs opacity-70 mt-1">
{Float.round(@max_drive_min / 60, 1)}h from {@home.label}
<span id="rover-drive-label">{Float.round(@max_drive_min / 60, 1)}h</span>
from {@home.label}
</div>
</.sidebar_section>