neiam
/apollos_crib
It's Apollo's Crib
public · Issues · Pulls 1 · Labels · Forks · Compare · Actions failure · Badges · Packages
⭐
Log in to mark this repository.
Show a vision's tint on its own page and on a pythiae
ae36961 · Gabriel Morell · 2026-08-11 20:12
Message
{commit_body(@commit)}
Files changed
modified
apps/room_sanctum/lib/room_sanctum_web/components/core_components.ex
+18
−0
@@ -884,4 +884,22 @@ defmodule RoomSanctumWeb.CoreComponents do
| 884 | 884 | </div> |
| 885 | 885 | """ |
| 886 | 886 | end |
| 887 | + | |
| 888 | + @doc """ | |
| 889 | + The coloured dot marking a tinted source, query or vision. | |
| 890 | + | |
| 891 | + Renders nothing when there is no tint, so callers can hand it whatever a | |
| 892 | + meta embed holds -- including a nil embed -- without guarding first. | |
| 893 | + | |
| 894 | + <.tint_dot tint={@vision.meta && @vision.meta.tint} /> | |
| 895 | + """ | |
| 896 | + attr :tint, :string, default: nil | |
| 897 | + attr :class, :string, default: "" | |
| 898 | + | |
| 899 | + def tint_dot(assigns) do | |
| 900 | + ~H""" | |
| 901 | + <i :if={@tint not in [nil, ""]} class={"fa-solid fa-circle text-#{@tint}-500 #{@class}"}></i> | |
| 902 | + """ | |
| 903 | + end | |
| 904 | + | |
| 887 | 905 | end |
modified
apps/room_sanctum/lib/room_sanctum_web/live/cfg_pythiae_live/show.html.heex
+4
−2
@@ -37,8 +37,10 @@
| 37 | 37 | > |
| 38 | 38 | <i class={"fas fa-fw fa-star #{if v == @pythiae.curr_vision, do: "text-accent"}"}></i> |
| 39 | 39 | </button> |
| 40 | − <a href={Routes.vision_show_path(@socket, :show, v)} class="text-sm hover:underline btn btn-sm btn-secondary"> | |
| 41 | − <%= get_by_id(v, @visions).name %> | |
| 40 | + <a href={Routes.vision_show_path(@socket, :show, v)} class="text-sm hover:underline btn btn-sm btn-secondary gap-2"> | |
| 41 | + <% vision = get_by_id(v, @visions) %> | |
| 42 | + <.tint_dot tint={vision.meta && vision.meta.tint} /> | |
| 43 | + <%= vision.name %> | |
| 42 | 44 | </a> |
| 43 | 45 | </div> |
| 44 | 46 | <% end%> |
modified
apps/room_sanctum/lib/room_sanctum_web/live/cfg_query_live/show.html.heex
+2
−2
@@ -183,7 +183,7 @@
| 183 | 183 | <%= for v <- @visions do %> |
| 184 | 184 | <div class="flex gap-2 mx-2"> |
| 185 | 185 | <%= live_patch to: Routes.vision_show_path(@socket, :show, v.id), class: "btn btn-primary gap-2 grow" do %> |
| 186 | − <i :if={v.meta && v.meta.tint} class={"fa-solid fa-circle text-#{v.meta && v.meta.tint}-500"}></i> | |
| 186 | + <.tint_dot tint={v.meta && v.meta.tint} /> | |
| 187 | 187 | <%= v.name %> |
| 188 | 188 | <% end %> |
| 189 | 189 | <%!-- Only while the panel is open, so the usual view of "the |
@@ -207,7 +207,7 @@
| 207 | 207 | way the visions this query already belongs to do. --%> |
| 208 | 208 | <%= for v <- @avail_visions do %> |
| 209 | 209 | <div class="btn btn-secondary gap-2 float-left mx-2" phx-click="add-to" phx-value-vision={v.id}> |
| 210 | − <i :if={v.meta && v.meta.tint} class={"fa-solid fa-circle text-#{v.meta && v.meta.tint}-500"}></i> | |
| 210 | + <.tint_dot tint={v.meta && v.meta.tint} /> | |
| 211 | 211 | <%= v.name %> |
| 212 | 212 | </div> |
| 213 | 213 | <% end %> |
modified
apps/room_sanctum/lib/room_sanctum_web/live/cfg_vision_live/index.html.heex
+1
−1
@@ -35,7 +35,7 @@
| 35 | 35 | <%= for {id, vision} <- @streams.visions do %> |
| 36 | 36 | <tr id={"vision-#{vision.id}"}> |
| 37 | 37 | <td> |
| 38 | − <i :if={vision.meta && vision.meta.tint} class={"fa-solid fa-circle mr-2 text-#{vision.meta && vision.meta.tint}-500"}></i> | |
| 38 | + <.tint_dot tint={vision.meta && vision.meta.tint} class="mr-2" /> | |
| 39 | 39 | <%= vision.name %> |
| 40 | 40 | </td> |
| 41 | 41 | <td>$query</td> |
modified
apps/room_sanctum/lib/room_sanctum_web/live/cfg_vision_live/show.html.heex
+4
−1
@@ -18,7 +18,10 @@
| 18 | 18 | |
| 19 | 19 | <div class="mx-4 py-2"> |
| 20 | 20 | <div class="flex"> |
| 21 | − <h2 class="text-2xl font-bold text-accent-300"><%= @vision.name %></h2> | |
| 21 | + <h2 class="text-2xl font-bold text-accent-300"> | |
| 22 | + <.tint_dot tint={@vision.meta && @vision.meta.tint} class="mr-2" /> | |
| 23 | + <%= @vision.name %> | |
| 24 | + </h2> | |
| 22 | 25 | </div> |
| 23 | 26 | <div class="flex flex-col flex-col-reverse sm:flex-col-reverse md:flex-col"> |
| 24 | 27 | <div class="flex flex-wrap md:flex-nowrap flex-col-reverse md:flex-row"> |
added
apps/room_sanctum/test/room_sanctum_web/components/tint_dot_test.exs
+35
−0
@@ -0,0 +1,35 @@
| 1 | +defmodule RoomSanctumWeb.TintDotTest do | |
| 2 | + @moduledoc """ | |
| 3 | + The dot marking a tinted source, query or vision. Every surface that shows a | |
| 4 | + tint renders this, so it takes whatever a meta embed holds -- including a nil | |
| 5 | + embed -- without the caller guarding first. | |
| 6 | + """ | |
| 7 | + use ExUnit.Case, async: true | |
| 8 | + | |
| 9 | + import Phoenix.LiveViewTest | |
| 10 | + import RoomSanctumWeb.CoreComponents | |
| 11 | + | |
| 12 | + test "renders the colour it is given" do | |
| 13 | + html = render_component(&tint_dot/1, tint: "teal") | |
| 14 | + | |
| 15 | + assert html =~ "text-teal-500" | |
| 16 | + assert html =~ "fa-circle" | |
| 17 | + end | |
| 18 | + | |
| 19 | + test "renders nothing without a tint" do | |
| 20 | + for tint <- [nil, ""] do | |
| 21 | + assert render_component(&tint_dot/1, tint: tint) |> String.trim() == "", | |
| 22 | + "rendered something for #{inspect(tint)}" | |
| 23 | + end | |
| 24 | + end | |
| 25 | + | |
| 26 | + test "takes extra classes for spacing" do | |
| 27 | + assert render_component(&tint_dot/1, tint: "rose", class: "mr-2") =~ "mr-2" | |
| 28 | + end | |
| 29 | + | |
| 30 | + test "every colour in the palette renders its own class" do | |
| 31 | + for tint <- RoomSanctum.Tints.all() do | |
| 32 | + assert render_component(&tint_dot/1, tint: tint) =~ "text-#{tint}-500" | |
| 33 | + end | |
| 34 | + end | |
| 35 | +end |
modified
apps/room_sanctum/test/room_sanctum_web/live/vision_tint_test.exs
+15
−0
@@ -134,6 +134,21 @@ defmodule RoomSanctumWeb.VisionTintTest do
| 134 | 134 | assert html =~ "text-teal-500" |
| 135 | 135 | end |
| 136 | 136 | |
| 137 | + test "the vision's own page shows it beside the name", ctx do | |
| 138 | + v = vision(ctx.user, %{meta: %{tint: "teal"}}) | |
| 139 | + | |
| 140 | + {:ok, _live, html} = live(ctx.conn, Routes.vision_show_path(ctx.conn, :show, v)) | |
| 141 | + | |
| 142 | + assert html =~ "text-teal-500" | |
| 143 | + # beside the name, not somewhere else on the page | |
| 144 | + assert html =~ ~r/text-teal-500.{0,120}Dash/s | |
| 145 | + end | |
| 146 | + | |
| 147 | + # The pythiae page cannot be rendered from this app's tests: it reads | |
| 148 | + # users_rabbit, which belongs to room_hermes and is not migrated into | |
| 149 | + # room_sanctum's test database. Its dot comes from the same tint_dot | |
| 150 | + # component covered below. | |
| 151 | + | |
| 137 | 152 | test "an untinted vision gets no dot", ctx do |
| 138 | 153 | vision(ctx.user) |
| 139 | 154 |
Parents: fc5bc54