Tint labels: a subgroup within a colour

7cea53b · gmorell · 2026-11-09 05:32

56 files +1094 -106
Message
{commit_body(@commit)}

Files changed

modified apps/room_sanctum/lib/room_sanctum/configuration/foci.ex
+7 −1
@@ -9,6 +9,11 @@ defmodule RoomSanctum.Configuration.Foci do
9 9 # As on a source: a colour that says these belong together, without making
10 10 # a group out of it. A Plani can follow a tint of foci as its homes.
11 11 field :tint, :string
12 +
13 + # Which subgroup of that tint. A colour says "these belong together"; the
14 + # label says which corner of it, so a Plani can follow the homes and not
15 + # the errands without spending a second colour on them.
16 + field :tint_label, :string
12 17 belongs_to :user, RoomSanctum.Accounts.User
13 18
14 19 timestamps()
@@ -17,10 +22,11 @@ defmodule RoomSanctum.Configuration.Foci do
17 22 @doc false
18 23 def changeset(foci, attrs) do
19 24 foci
20 − |> cast(attrs, [:name, :place, :user_id, :tint])
25 + |> cast(attrs, [:name, :place, :user_id, :tint, :tint_label])
21 26 |> foreign_key_constraint(:user_id)
22 27 |> validate_required([:name, :place])
23 28 |> validate_tint()
29 + |> RoomSanctum.Tints.put_label()
24 30 end
25 31
26 32 # An unknown tint renders as an unstyled dot rather than failing, which is
modified apps/room_sanctum/lib/room_sanctum/configuration/plani.ex
+41 −11
@@ -28,6 +28,11 @@ defmodule RoomSanctum.Configuration.Plani do
28 28 field :home_foci_ids, {:array, :integer}, default: []
29 29 field :home_tint, :string
30 30
31 + # Narrowed to these subgroups of it, or the whole tint when empty. A
32 + # colour of foci that has grown to mean two things -- the places you sleep
33 + # and the places you shop -- can be followed a corner at a time.
34 + field :home_tint_labels, {:array, :string}, default: []
35 +
31 36 # How long a client may say nothing before the anchor goes home. Five
32 37 # minutes suits a phone in a pocket on a bus; half an hour suits one that
33 38 # only reports when it is opened.
@@ -45,6 +50,9 @@ defmodule RoomSanctum.Configuration.Plani do
45 50 # A source tinted later joins without the Plani being edited, which is
46 51 # what makes this a shortcut rather than a bulk select.
47 52 field :follow_tint, :string
53 +
54 + # As with the homes: the subgroups of that tint to take, or all of them.
55 + field :follow_tint_labels, {:array, :string}, default: []
48 56 field :radius, :integer, default: 800
49 57 field :limit, :integer, default: 5
50 58
@@ -76,11 +84,13 @@ defmodule RoomSanctum.Configuration.Plani do
76 84 :home_foci_id,
77 85 :home_foci_ids,
78 86 :home_tint,
87 + :home_tint_labels,
79 88 :home_after_mins,
80 89 :ankyra_id,
81 90 :client_id,
82 91 :sources,
83 92 :follow_tint,
93 + :follow_tint_labels,
84 94 :radius,
85 95 :limit,
86 96 :break_out,
@@ -100,6 +110,8 @@ defmodule RoomSanctum.Configuration.Plani do
100 110 |> validate_inclusion(:home_after_mins, [5, 10, 15, 20, 25, 30])
101 111 |> validate_tint()
102 112 |> validate_home_tint()
113 + |> RoomSanctum.Tints.put_labels(:follow_tint, :follow_tint_labels)
114 + |> RoomSanctum.Tints.put_labels(:home_tint, :home_tint_labels)
103 115 |> foreign_key_constraint(:user_id)
104 116 |> foreign_key_constraint(:home_foci_id)
105 117 end
@@ -126,7 +138,8 @@ defmodule RoomSanctum.Configuration.Plani do
126 138
127 139 @doc """
128 140 Every foci this Plani calls home: the first one, any others it names, and
129 − any wearing its home tint.
141 + any wearing its home tint -- narrowed, if it names any, to the subgroups of
142 + that tint it follows.
130 143
131 144 In that order and deduplicated, so `home_foci_id` stays the one a Plani with
132 145 no position falls back to -- which is what it has always been, and what
@@ -134,10 +147,9 @@ defmodule RoomSanctum.Configuration.Plani do
134 147 """
135 148 def homes_for(%__MODULE__{} = plani, all_foci) do
136 149 tinted =
137 − case plani.home_tint do
138 − blank when blank in [nil, ""] -> []
139 − tint -> all_foci |> Enum.filter(&(&1.tint == tint)) |> Enum.map(& &1.id)
140 − end
150 + all_foci
151 + |> Enum.filter(&follows_home?(plani, &1))
152 + |> Enum.map(& &1.id)
141 153
142 154 ([plani.home_foci_id] ++ (plani.home_foci_ids || []) ++ tinted)
143 155 |> Enum.reject(&is_nil/1)
@@ -146,21 +158,39 @@ defmodule RoomSanctum.Configuration.Plani do
146 158
147 159 @doc """
148 160 Every source this Plani asks: the ones it names, and the ones wearing its
149 − tint.
161 + tint -- narrowed, if it names any, to the subgroups of that tint it
162 + follows.
150 163
151 164 A union rather than a choice, so following a tint can be a shortcut to most
152 165 of a list with one or two named on top of it.
153 166 """
154 167 def sources_for(%__MODULE__{} = plani, all_sources) do
155 168 tinted =
156 − case plani.follow_tint do
157 − nil -> []
158 − "" -> []
159 − tint -> all_sources |> Enum.filter(&(tint_of(&1) == tint)) |> Enum.map(& &1.id)
160 − end
169 + all_sources
170 + |> Enum.filter(&follows_source?(plani, &1))
171 + |> Enum.map(& &1.id)
161 172
162 173 (plani.sources ++ tinted) |> Enum.uniq()
163 174 end
164 175
176 + defp follows_home?(plani, foci) do
177 + RoomSanctum.Tints.follows?(
178 + foci.tint,
179 + Map.get(foci, :tint_label),
180 + plani.home_tint,
181 + plani.home_tint_labels
182 + )
183 + end
184 +
185 + defp follows_source?(plani, source) do
186 + RoomSanctum.Tints.follows?(
187 + tint_of(source),
188 + label_of(source),
189 + plani.follow_tint,
190 + plani.follow_tint_labels
191 + )
192 + end
193 +
165 194 defp tint_of(source), do: source.meta && Map.get(source.meta, :tint)
195 + defp label_of(source), do: source.meta && Map.get(source.meta, :tint_label)
166 196 end
modified apps/room_sanctum/lib/room_sanctum/configuration/query.ex
+4 −1
@@ -41,6 +41,8 @@ defmodule RoomSanctum.Configuration.Query do
41 41
42 42 embeds_one :meta, Meta, on_replace: :delete, primary_key: :false do
43 43 field :tint, :string
44 + # Which subgroup of that tint -- see RoomSanctum.Tints.
45 + field :tint_label, :string
44 46 end
45 47
46 48 timestamps()
@@ -59,6 +61,7 @@ defmodule RoomSanctum.Configuration.Query do
59 61
60 62 def meta_changeset(meta, attrs \\ %{}) do
61 63 meta
62 − |> cast(attrs, [:tint])
64 + |> cast(attrs, [:tint, :tint_label])
65 + |> RoomSanctum.Tints.put_label()
63 66 end
64 67 end
modified apps/room_sanctum/lib/room_sanctum/configuration/source.ex
+4 −1
@@ -65,6 +65,8 @@ defmodule RoomSanctum.Configuration.Source do
65 65 field :last_run, :utc_datetime
66 66 field :run_period, :integer
67 67 field :tint, :string
68 + # Which subgroup of that tint -- see RoomSanctum.Tints.
69 + field :tint_label, :string
68 70 embeds_many :tracking, Tracking, on_replace: :delete, primary_key: :false do
69 71 field :number, :string
70 72 field :type, Ecto.Enum, values: [:ups, :fedex, :usps, :uniuni]
@@ -87,7 +89,8 @@ defmodule RoomSanctum.Configuration.Source do
87 89
88 90 def meta_changeset(meta, attrs \\ %{}) do
89 91 meta
90 − |> cast(attrs, [:last_run, :run_period, :tint])
92 + |> cast(attrs, [:last_run, :run_period, :tint, :tint_label])
93 + |> RoomSanctum.Tints.put_label()
91 94 |> cast_embed(:tracking, required: false, with: &tracking_changeset/2)
92 95 end
93 96
modified apps/room_sanctum/lib/room_sanctum/configuration/vision.ex
+4 −1
@@ -11,6 +11,8 @@ defmodule RoomSanctum.Configuration.Vision do
11 11
12 12 embeds_one :meta, Meta, on_replace: :delete, primary_key: :false do
13 13 field :tint, :string
14 + # Which subgroup of that tint -- see RoomSanctum.Tints.
15 + field :tint_label, :string
14 16 end
15 17
16 18 timestamps()
@@ -49,8 +51,9 @@ defmodule RoomSanctum.Configuration.Vision do
49 51
50 52 def meta_changeset(meta, attrs \\ %{}) do
51 53 meta
52 − |> cast(attrs, [:tint])
54 + |> cast(attrs, [:tint, :tint_label])
53 55 |> validate_tint()
56 + |> RoomSanctum.Tints.put_label()
54 57 end
55 58
56 59 # A tint that is not in the palette has no stylesheet behind it, so it would
modified apps/room_sanctum/lib/room_sanctum/tints.ex
+124 −1

Click to load diff…

modified apps/room_sanctum/lib/room_sanctum_web/components/core_components.ex
+85 −4

Click to load diff…

modified apps/room_sanctum/lib/room_sanctum_web/live/cfg_foci_live/form_component.html.heex
+11 −0

Click to load diff…

modified apps/room_sanctum/lib/room_sanctum_web/live/cfg_foci_live/index.html.heex
+4 −2

Click to load diff…

modified apps/room_sanctum/lib/room_sanctum_web/live/cfg_foci_live/show.html.heex
+3 −0

Click to load diff…

modified apps/room_sanctum/lib/room_sanctum_web/live/cfg_plani_live/form_component.ex
+71 −2

Click to load diff…

modified apps/room_sanctum/lib/room_sanctum_web/live/cfg_plani_live/show.ex
+9 −2

Click to load diff…

modified apps/room_sanctum/lib/room_sanctum_web/live/cfg_plani_live/show.html.heex
+4 −1

Click to load diff…

modified apps/room_sanctum/lib/room_sanctum_web/live/cfg_pythiae_live/show.html.heex
+2 −2

Click to load diff…

modified apps/room_sanctum/lib/room_sanctum_web/live/cfg_query_live/form_component.html.heex
+9 −0

Click to load diff…

modified apps/room_sanctum/lib/room_sanctum_web/live/cfg_query_live/index.html.heex
+18 −17

Click to load diff…

modified apps/room_sanctum/lib/room_sanctum_web/live/cfg_query_live/show.html.heex
+10 −19

Click to load diff…

modified apps/room_sanctum/lib/room_sanctum_web/live/cfg_source_live/form_component.html.heex
+18 −9

Click to load diff…

modified apps/room_sanctum/lib/room_sanctum_web/live/cfg_source_live/index.html.heex
+11 −3

Click to load diff…

modified apps/room_sanctum/lib/room_sanctum_web/live/cfg_source_live/show.ex
+8 −1

Click to load diff…

modified apps/room_sanctum/lib/room_sanctum_web/live/cfg_source_live/show.html.heex
+1 −3

Click to load diff…

modified apps/room_sanctum/lib/room_sanctum_web/live/cfg_vision_live/form_component.ex
+10 −0

Click to load diff…

modified apps/room_sanctum/lib/room_sanctum_web/live/cfg_vision_live/form_component.html.heex
+10 −0

Click to load diff…

modified apps/room_sanctum/lib/room_sanctum_web/live/cfg_vision_live/index.html.heex
+7 −4

Click to load diff…

modified apps/room_sanctum/lib/room_sanctum_web/live/cfg_vision_live/show.html.heex
+7 −18

Click to load diff…

added apps/room_sanctum/priv/repo/migrations/20260909120000_add_tint_labels.exs
+30 −0

Click to load diff…

added apps/room_sanctum/test/room_sanctum/plani_follow_tint_test.exs
+72 −0

Click to load diff…

modified apps/room_sanctum/test/room_sanctum/plani_homes_test.exs
+36 −3

Click to load diff…

added apps/room_sanctum/test/room_sanctum/tint_label_changeset_test.exs
+54 −0

Click to load diff…

modified apps/room_sanctum/test/room_sanctum/tints_test.exs
+62 −0

Click to load diff…

modified apps/room_sanctum/test/room_sanctum_web/components/tint_dot_test.exs
+29 −0

Click to load diff…

added apps/room_sanctum/test/room_sanctum_web/components/tint_nest_test.exs
+98 −0

Click to load diff…

modified apps/room_sanctum/test/room_sanctum_web/live/source_live_bulk_paint_test.exs
+42 −0

Click to load diff…

added apps/room_sanctum/test/room_sanctum_web/live/tint_label_show_test.exs
+189 −0

Click to load diff…

Parents: 07c2f71