Show topbar loading indicator while scores are loading

This commit is contained in:
Graham McIntire 2026-03-31 09:30:20 -05:00
parent d97de91978
commit d5140267b1
No known key found for this signature in database
GPG key ID: F4ABF488E6029E59
8 changed files with 213 additions and 2 deletions

View file

@ -105,4 +105,156 @@
/* Make LiveView wrapper divs transparent for layout */
[data-phx-session], [data-phx-teleported-src] { display: contents }
/* Markdown rendered content */
.markdown-content {
max-width: 64rem;
margin: 2rem auto;
padding: 0 1rem;
line-height: 1.75;
}
.markdown-content h1 {
font-size: 2rem;
font-weight: 700;
margin: 2rem 0 1rem;
border-bottom: 1px solid oklch(80% 0 0);
padding-bottom: 0.5rem;
}
.markdown-content h2 {
font-size: 1.5rem;
font-weight: 700;
margin: 1.75rem 0 0.75rem;
border-bottom: 1px solid oklch(85% 0 0);
padding-bottom: 0.25rem;
}
.markdown-content h3 {
font-size: 1.25rem;
font-weight: 600;
margin: 1.5rem 0 0.5rem;
}
.markdown-content h4 {
font-size: 1.1rem;
font-weight: 600;
margin: 1.25rem 0 0.5rem;
}
.markdown-content p {
margin: 0.75rem 0;
}
.markdown-content ul, .markdown-content ol {
margin: 0.75rem 0;
padding-left: 2rem;
}
.markdown-content ul { list-style-type: disc; }
.markdown-content ol { list-style-type: decimal; }
.markdown-content li {
margin: 0.25rem 0;
}
.markdown-content code {
font-family: ui-monospace, monospace;
font-size: 0.875em;
background: oklch(92% 0 0);
padding: 0.15em 0.35em;
border-radius: 0.25rem;
}
.markdown-content pre {
margin: 1rem 0;
padding: 1rem;
background: oklch(20% 0.01 260);
color: oklch(95% 0 0);
border-radius: 0.5rem;
overflow-x: auto;
}
.markdown-content pre code {
background: none;
padding: 0;
font-size: 0.85em;
color: inherit;
}
.markdown-content table {
width: 100%;
border-collapse: collapse;
margin: 1rem 0;
font-size: 0.9em;
overflow-x: auto;
display: block;
}
.markdown-content th, .markdown-content td {
border: 1px solid oklch(80% 0 0);
padding: 0.5rem 0.75rem;
text-align: left;
}
.markdown-content th {
background: oklch(94% 0 0);
font-weight: 600;
}
.markdown-content tr:nth-child(even) {
background: oklch(97% 0 0);
}
.markdown-content hr {
border: none;
border-top: 2px solid oklch(85% 0 0);
margin: 2rem 0;
}
.markdown-content strong {
font-weight: 700;
}
.markdown-content blockquote {
border-left: 4px solid oklch(70% 0.15 260);
margin: 1rem 0;
padding: 0.5rem 1rem;
background: oklch(96% 0.005 260);
}
.markdown-content a {
color: oklch(55% 0.2 260);
text-decoration: underline;
}
:where([data-theme=dark]) .markdown-content code {
background: oklch(30% 0.01 260);
}
:where([data-theme=dark]) .markdown-content th {
background: oklch(30% 0.01 260);
}
:where([data-theme=dark]) .markdown-content tr:nth-child(even) {
background: oklch(25% 0.01 260);
}
:where([data-theme=dark]) .markdown-content th,
:where([data-theme=dark]) .markdown-content td {
border-color: oklch(40% 0 0);
}
:where([data-theme=dark]) .markdown-content h1,
:where([data-theme=dark]) .markdown-content h2 {
border-color: oklch(40% 0 0);
}
:where([data-theme=dark]) .markdown-content hr {
border-color: oklch(40% 0 0);
}
:where([data-theme=dark]) .markdown-content blockquote {
background: oklch(25% 0.01 260);
}
/* This file is for your main application CSS */

View file

@ -1,3 +1,5 @@
import topbar from "../vendor/topbar"
export const PropagationMap = {
mounted() {
this.map = L.map(this.el, {
@ -24,9 +26,13 @@ export const PropagationMap = {
]
this.handleEvent("update_scores", ({ scores }) => {
topbar.hide()
this.renderScores(scores)
})
// Show topbar whenever the server is processing a band change
this.el.addEventListener("show-loading", () => topbar.show(300))
// Send bounds to server on load and on pan/zoom
this.sendBounds()
this.map.on("moveend", () => this.sendBounds())
@ -168,6 +174,7 @@ export const PropagationMap = {
},
sendBounds() {
topbar.show(300)
const b = this.map.getBounds()
this.pushEvent("map_bounds", {
south: b.getSouth(),

View file

@ -0,0 +1,25 @@
defmodule MicrowavepropWeb.AlgoLive do
@moduledoc false
use MicrowavepropWeb, :live_view
@algo_path Path.join(:code.priv_dir(:microwaveprop), "../algo.md")
@impl true
def mount(_params, _session, socket) do
content =
@algo_path
|> File.read!()
|> Earmark.as_html!()
{:ok, assign(socket, page_title: "Algorithm", content: content)}
end
@impl true
def render(assigns) do
~H"""
<div class="markdown-content">
{raw(@content)}
</div>
"""
end
end

View file

@ -117,7 +117,8 @@ defmodule MicrowavepropWeb.MapLive do
<li :for={band <- @bands}>
<button
phx-click={
JS.push("select_band", value: %{value: band.freq_mhz})
JS.dispatch("show-loading", to: "#propagation-map")
|> JS.push("select_band", value: %{value: band.freq_mhz})
|> JS.dispatch("click", to: "#propagation-map")
}
class={[if(@selected_band == band.freq_mhz, do: "active")]}

View file

@ -23,6 +23,7 @@ defmodule MicrowavepropWeb.Router do
live "/map", MapLive
live "/qsos", QsoLive.Index
live "/qsos/:id", QsoLive.Show
live "/algo", AlgoLive
end
# Other scopes may use custom stacks.

View file

@ -63,7 +63,8 @@ defmodule Microwaveprop.MixProject do
{:bandit, "~> 1.5"},
{:styler, "~> 1.11", only: [:dev, :test], runtime: false},
{:oban, "~> 2.19"},
{:credo, "~> 1.7", only: [:dev, :test], runtime: false}
{:credo, "~> 1.7", only: [:dev, :test], runtime: false},
{:earmark, "~> 1.4"}
]
end

View file

@ -6,6 +6,7 @@
"db_connection": {:hex, :db_connection, "2.9.0", "a6a97c5c958a2d7091a58a9be40caf41ab496b0701d21e1d1abff3fa27a7f371", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "17d502eacaf61829db98facf6f20808ed33da6ccf495354a41e64fe42f9c509c"},
"decimal": {:hex, :decimal, "2.3.0", "3ad6255aa77b4a3c4f818171b12d237500e63525c2fd056699967a3e7ea20f62", [:mix], [], "hexpm", "a4d66355cb29cb47c3cf30e71329e58361cfcb37c34235ef3bf1d7bf3773aeac"},
"dns_cluster": {:hex, :dns_cluster, "0.2.0", "aa8eb46e3bd0326bd67b84790c561733b25c5ba2fe3c7e36f28e88f384ebcb33", [:mix], [], "hexpm", "ba6f1893411c69c01b9e8e8f772062535a4cf70f3f35bcc964a324078d8c8240"},
"earmark": {:hex, :earmark, "1.4.48", "5f41e579d85ef812351211842b6e005f6e0cef111216dea7d4b9d58af4608434", [:mix], [], "hexpm", "a461a0ddfdc5432381c876af1c86c411fd78a25790c75023c7a4c035fdc858f9"},
"ecto": {:hex, :ecto, "3.13.5", "9d4a69700183f33bf97208294768e561f5c7f1ecf417e0fa1006e4a91713a834", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "df9efebf70cf94142739ba357499661ef5dbb559ef902b68ea1f3c1fabce36de"},
"ecto_sql": {:hex, :ecto_sql, "3.13.5", "2f8282b2ad97bf0f0d3217ea0a6fff320ead9e2f8770f810141189d182dc304e", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.13.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.7", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.19 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "aa36751f4e6a2b56ae79efb0e088042e010ff4935fc8684e74c23b1f49e25fdc"},
"elixir_make": {:hex, :elixir_make, "0.9.0", "6484b3cd8c0cee58f09f05ecaf1a140a8c97670671a6a0e7ab4dc326c3109726", [:mix], [], "hexpm", "db23d4fd8b757462ad02f8aa73431a426fe6671c80b200d9710caf3d1dd0ffdb"},

View file

@ -0,0 +1,23 @@
defmodule MicrowavepropWeb.AlgoLiveTest do
use MicrowavepropWeb.ConnCase, async: true
import Phoenix.LiveViewTest
describe "mount" do
test "renders algo page with markdown content as HTML", %{conn: conn} do
{:ok, _lv, html} = live(conn, ~p"/algo")
# algo.md starts with this heading
assert html =~ "Microwave Propagation Algorithm"
# Earmark should convert ## headings to h2 tags
assert html =~ "<h2>"
# Should contain code blocks rendered as HTML
assert html =~ "<code"
end
test "sets page title", %{conn: conn} do
{:ok, _lv, html} = live(conn, ~p"/algo")
assert html =~ "Algorithm"
end
end
end