Beacon page improvements: comma formatting, sorting, rounding
- Add CommaNumber JS hook for live comma formatting while typing frequency MHz in the beacon form - Strip commas server-side before changeset validation - Order beacon list by most recently added (desc inserted_at) - Round lat/lon to 6 decimal places in changeset and display - Round height_ft to integer in changeset and display - Display coords at 6 decimal places on show page
This commit is contained in:
parent
9451b7f451
commit
0059317043
6 changed files with 54 additions and 11 deletions
|
|
@ -55,11 +55,35 @@ const UtcClock = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const CommaNumber = {
|
||||||
|
mounted() {
|
||||||
|
this.el.addEventListener("input", () => this.format())
|
||||||
|
this.format()
|
||||||
|
},
|
||||||
|
updated() { this.format() },
|
||||||
|
format() {
|
||||||
|
const input = this.el.querySelector("input") || this.el
|
||||||
|
const raw = input.value.replace(/,/g, "")
|
||||||
|
if (raw === "" || raw === "-") return
|
||||||
|
const cursor = input.selectionStart
|
||||||
|
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 csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content")
|
||||||
const liveSocket = new LiveSocket("/live", Socket, {
|
const liveSocket = new LiveSocket("/live", Socket, {
|
||||||
longPollFallbackMs: 2500,
|
longPollFallbackMs: 2500,
|
||||||
params: initLiveStash({_csrf_token: csrfToken}),
|
params: initLiveStash({_csrf_token: csrfToken}),
|
||||||
hooks: {...colocatedHooks, PropagationMap, UtcClock, ContactMap, ContactsMap, ElevationProfile, WeatherMap, LocateMe, CopyLink, RoverMap, BeaconMap},
|
hooks: {...colocatedHooks, PropagationMap, UtcClock, ContactMap, ContactsMap, ElevationProfile, WeatherMap, LocateMe, CopyLink, RoverMap, BeaconMap, CommaNumber},
|
||||||
})
|
})
|
||||||
|
|
||||||
// Show progress bar on live navigation and form submits
|
// Show progress bar on live navigation and form submits
|
||||||
|
|
|
||||||
|
|
@ -28,12 +28,12 @@ defmodule Microwaveprop.Beacons do
|
||||||
Phoenix.PubSub.broadcast(Microwaveprop.PubSub, @topic, message)
|
Phoenix.PubSub.broadcast(Microwaveprop.PubSub, @topic, message)
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc "Returns approved beacons ordered by frequency."
|
@doc "Returns approved beacons ordered by most recently added."
|
||||||
def list_beacons do
|
def list_beacons do
|
||||||
Repo.all(
|
Repo.all(
|
||||||
from b in Beacon,
|
from b in Beacon,
|
||||||
where: b.approved == true,
|
where: b.approved == true,
|
||||||
order_by: [asc: b.frequency_mhz, asc: b.callsign]
|
order_by: [desc: b.inserted_at]
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -166,6 +166,9 @@ defmodule Microwaveprop.Beacons.Beacon do
|
||||||
:notes
|
:notes
|
||||||
])
|
])
|
||||||
|> update_change(:callsign, fn cs -> cs && String.upcase(String.trim(cs)) end)
|
|> update_change(:callsign, fn cs -> cs && String.upcase(String.trim(cs)) end)
|
||||||
|
|> update_change(:lat, &round_coord/1)
|
||||||
|
|> update_change(:lon, &round_coord/1)
|
||||||
|
|> update_change(:height_ft, &round_int/1)
|
||||||
|> maybe_fill_latlon()
|
|> maybe_fill_latlon()
|
||||||
|> maybe_fill_grid()
|
|> maybe_fill_grid()
|
||||||
|> normalize_bearing_change()
|
|> normalize_bearing_change()
|
||||||
|
|
@ -182,6 +185,14 @@ defmodule Microwaveprop.Beacons.Beacon do
|
||||||
|> validate_grid_format()
|
|> validate_grid_format()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp round_coord(nil), do: nil
|
||||||
|
defp round_coord(v) when is_float(v), do: Float.round(v, 6)
|
||||||
|
defp round_coord(v), do: v
|
||||||
|
|
||||||
|
defp round_int(nil), do: nil
|
||||||
|
defp round_int(v) when is_float(v), do: Float.round(v, 0)
|
||||||
|
defp round_int(v), do: v
|
||||||
|
|
||||||
# If grid is blank but lat/lon are valid, derive it.
|
# If grid is blank but lat/lon are valid, derive it.
|
||||||
defp maybe_fill_grid(changeset) do
|
defp maybe_fill_grid(changeset) do
|
||||||
grid = get_field(changeset, :grid)
|
grid = get_field(changeset, :grid)
|
||||||
|
|
|
||||||
|
|
@ -20,9 +20,10 @@ defmodule MicrowavepropWeb.BeaconLive.Form do
|
||||||
<.form for={@form} id="beacon-form" phx-change="validate" phx-submit="save">
|
<.form for={@form} id="beacon-form" phx-change="validate" phx-submit="save">
|
||||||
<.input
|
<.input
|
||||||
field={@form[:frequency_mhz]}
|
field={@form[:frequency_mhz]}
|
||||||
type="number"
|
type="text"
|
||||||
label="Frequency (MHz)"
|
label="Frequency (MHz)"
|
||||||
step="any"
|
inputmode="decimal"
|
||||||
|
phx-hook="CommaNumber"
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
<.input field={@form[:callsign]} type="text" label="Call" required />
|
<.input field={@form[:callsign]} type="text" label="Call" required />
|
||||||
|
|
@ -105,12 +106,19 @@ defmodule MicrowavepropWeb.BeaconLive.Form do
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def handle_event("validate", %{"beacon" => beacon_params}, socket) do
|
def handle_event("validate", %{"beacon" => beacon_params}, socket) do
|
||||||
changeset = Beacons.change_beacon(socket.assigns.beacon, beacon_params)
|
changeset = Beacons.change_beacon(socket.assigns.beacon, strip_commas(beacon_params))
|
||||||
{:noreply, assign(socket, form: to_form(changeset, action: :validate))}
|
{:noreply, assign(socket, form: to_form(changeset, action: :validate))}
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_event("save", %{"beacon" => beacon_params}, socket) do
|
def handle_event("save", %{"beacon" => beacon_params}, socket) do
|
||||||
save_beacon(socket, socket.assigns.live_action, beacon_params)
|
save_beacon(socket, socket.assigns.live_action, strip_commas(beacon_params))
|
||||||
|
end
|
||||||
|
|
||||||
|
defp strip_commas(params) do
|
||||||
|
Map.update(params, "frequency_mhz", nil, fn
|
||||||
|
v when is_binary(v) -> String.replace(v, ",", "")
|
||||||
|
v -> v
|
||||||
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp save_beacon(socket, :edit, beacon_params) do
|
defp save_beacon(socket, :edit, beacon_params) do
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ defmodule MicrowavepropWeb.BeaconLive.Index do
|
||||||
<:col :let={{_id, beacon}} label="Lat">{beacon.lat}</:col>
|
<:col :let={{_id, beacon}} label="Lat">{beacon.lat}</:col>
|
||||||
<:col :let={{_id, beacon}} label="Lon">{beacon.lon}</:col>
|
<:col :let={{_id, beacon}} label="Lon">{beacon.lon}</:col>
|
||||||
<:col :let={{_id, beacon}} label="EIRP (mW)">{Beacon.format_mw(beacon.power_mw)}</:col>
|
<:col :let={{_id, beacon}} label="EIRP (mW)">{Beacon.format_mw(beacon.power_mw)}</:col>
|
||||||
<:col :let={{_id, beacon}} label="Height AGL (ft)">{beacon.height_ft}</:col>
|
<:col :let={{_id, beacon}} label="Height AGL (ft)">{if beacon.height_ft, do: round(beacon.height_ft)}</:col>
|
||||||
<:col :let={{_id, beacon}} label="Keying">{Beacon.keying_label(beacon.keying)}</:col>
|
<:col :let={{_id, beacon}} label="Keying">{Beacon.keying_label(beacon.keying)}</:col>
|
||||||
<:col :let={{_id, beacon}} label="On air">
|
<:col :let={{_id, beacon}} label="On air">
|
||||||
<span class={["badge badge-sm", (beacon.on_the_air && "badge-success") || "badge-ghost"]}>
|
<span class={["badge badge-sm", (beacon.on_the_air && "badge-success") || "badge-ghost"]}>
|
||||||
|
|
@ -69,7 +69,7 @@ defmodule MicrowavepropWeb.BeaconLive.Index do
|
||||||
<:col :let={{_id, beacon}} label="Call">{beacon.callsign}</:col>
|
<:col :let={{_id, beacon}} label="Call">{beacon.callsign}</:col>
|
||||||
<:col :let={{_id, beacon}} label="Grid">{beacon.grid}</:col>
|
<:col :let={{_id, beacon}} label="Grid">{beacon.grid}</:col>
|
||||||
<:col :let={{_id, beacon}} label="EIRP (mW)">{Beacon.format_mw(beacon.power_mw)}</:col>
|
<:col :let={{_id, beacon}} label="EIRP (mW)">{Beacon.format_mw(beacon.power_mw)}</:col>
|
||||||
<:col :let={{_id, beacon}} label="Height AGL (ft)">{beacon.height_ft}</:col>
|
<:col :let={{_id, beacon}} label="Height AGL (ft)">{if beacon.height_ft, do: round(beacon.height_ft)}</:col>
|
||||||
<:col :let={{_id, beacon}} label="Submitted">
|
<:col :let={{_id, beacon}} label="Submitted">
|
||||||
{Calendar.strftime(beacon.inserted_at, "%Y-%m-%d %H:%M UTC")}
|
{Calendar.strftime(beacon.inserted_at, "%Y-%m-%d %H:%M UTC")}
|
||||||
</:col>
|
</:col>
|
||||||
|
|
|
||||||
|
|
@ -118,7 +118,7 @@ defmodule MicrowavepropWeb.BeaconLive.Show do
|
||||||
<.stat_field label="Longitude">{format_coord(@beacon.lon)}</.stat_field>
|
<.stat_field label="Longitude">{format_coord(@beacon.lon)}</.stat_field>
|
||||||
<.stat_field label="EIRP">{Beacon.format_mw(@beacon.power_mw)} mW</.stat_field>
|
<.stat_field label="EIRP">{Beacon.format_mw(@beacon.power_mw)} mW</.stat_field>
|
||||||
|
|
||||||
<.stat_field label="Height AGL">{Beacon.format_mw(@beacon.height_ft)} ft</.stat_field>
|
<.stat_field label="Height AGL">{if @beacon.height_ft, do: round(@beacon.height_ft)} ft</.stat_field>
|
||||||
<.stat_field label="Bearing">{bearing_label(@beacon.bearing)}</.stat_field>
|
<.stat_field label="Bearing">{bearing_label(@beacon.bearing)}</.stat_field>
|
||||||
<.stat_field :if={@beacon.beamwidth_deg} label="Beamwidth">
|
<.stat_field :if={@beacon.beamwidth_deg} label="Beamwidth">
|
||||||
{Beacon.format_mw(@beacon.beamwidth_deg)}°
|
{Beacon.format_mw(@beacon.beamwidth_deg)}°
|
||||||
|
|
@ -199,7 +199,7 @@ defmodule MicrowavepropWeb.BeaconLive.Show do
|
||||||
defp bearing_label("omni"), do: "Omni"
|
defp bearing_label("omni"), do: "Omni"
|
||||||
defp bearing_label(value), do: "#{value}°"
|
defp bearing_label(value), do: "#{value}°"
|
||||||
|
|
||||||
defp format_coord(value) when is_float(value), do: :erlang.float_to_binary(value, decimals: 5)
|
defp format_coord(value) when is_float(value), do: :erlang.float_to_binary(value, decimals: 6)
|
||||||
defp format_coord(value), do: to_string(value)
|
defp format_coord(value), do: to_string(value)
|
||||||
|
|
||||||
attr :label, :string, required: true
|
attr :label, :string, required: true
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue