Scribus: scale a resolution's snappers up and down in place

1869d95 · gmorell · 2026-11-09 15:46

13 files +67 -2
Message
{commit_body(@commit)}

Files changed

modified apps/room_sanctum/lib/room_sanctum/mutants.ex
+22 −0
@@ -15,6 +15,9 @@ defmodule RoomSanctum.Mutants do
15 15 require Logger
16 16
17 17 @env "SNAP_RES"
18 + # A ceiling for the crib's +/- controls, so a run of clicks cannot fill the
19 + # cluster with snappers.
20 + @max_snappers 10
18 21
19 22 defmodule Resolution do
20 23 @moduledoc "One running (or starting) browser-ctl variant."
@@ -88,6 +91,25 @@ defmodule RoomSanctum.Mutants do
88 91 end
89 92 end
90 93
94 + @doc "The most snappers one resolution may be scaled to from the crib."
95 + def max_snappers, do: @max_snappers
96 +
97 + @doc """
98 + Scales a resolution to `instances` snappers in place, keeping the ones
99 + already running. Between 1 and `max_snappers/0`: none at all is stopping
100 + it, which `delete_resolution/1` does.
101 + """
102 + @spec scale_resolution(String.t(), integer()) :: :ok | {:error, String.t()}
103 + def scale_resolution(label, instances)
104 + when is_integer(instances) and instances >= 1 and instances <= @max_snappers do
105 + with {:ok, _} <-
106 + request(:patch, "/variants/" <> URI.encode_www_form(label), %{instances: instances}),
107 + do: :ok
108 + end
109 +
110 + def scale_resolution(_label, instances),
111 + do: {:error, "snappers must be between 1 and #{@max_snappers}, got #{inspect(instances)}"}
112 +
91 113 @doc "Stops a resolution started through `create_resolution/4`."
92 114 @spec delete_resolution(String.t()) :: :ok | {:error, String.t()}
93 115 def delete_resolution(label) do
modified apps/room_sanctum/lib/room_sanctum_web/live/cfg_scribus_live/index.ex
+16 −1
@@ -93,8 +93,23 @@ defmodule RoomSanctumWeb.ScribusLive.Index do
93 93 {:noreply, load_resolutions(socket)}
94 94 end
95 95
96 + def handle_event("scale-resolution", %{"label" => label, "delta" => delta}, %{assigns: %{admin?: true}} = socket) do
97 + socket =
98 + with %{} = r <- Enum.find(socket.assigns.resolutions, &(&1.label == label)),
99 + {delta, ""} <- Integer.parse(to_string(delta)),
100 + :ok <- Mutants.scale_resolution(label, r.instances + delta) do
101 + put_flash(socket, :info, "#{label}: #{r.instances + delta} snappers")
102 + else
103 + {:error, msg} -> put_flash(socket, :error, msg)
104 + _ -> put_flash(socket, :error, "No resolution #{label} to scale")
105 + end
106 +
107 + {:noreply, load_resolutions(socket)}
108 + end
109 +
96 110 # The buttons are only rendered for admins; this covers a crafted event.
97 − def handle_event(event, _params, socket) when event in ["add-resolution", "delete-resolution"] do
111 + def handle_event(event, _params, socket)
112 + when event in ["add-resolution", "delete-resolution", "scale-resolution"] do
98 113 {:noreply, put_flash(socket, :error, "Only admins can change resolutions")}
99 114 end
100 115
modified apps/room_sanctum/lib/room_sanctum_web/live/cfg_scribus_live/index.html.heex
+23 −1
@@ -81,7 +81,29 @@
81 81 <span :if={r.read_only} class="badge badge-sm badge-ghost">git</span>
82 82 </td>
83 83 <td><%= r.resolution %></td>
84 − <td><%= r.ready %>/<%= r.instances %></td>
84 + <td class="whitespace-nowrap">
85 + <button
86 + :if={@admin? and not r.read_only}
87 + type="button"
88 + phx-click={JS.push("scale-resolution", value: %{label: r.label, delta: -1})}
89 + disabled={r.instances <= 1}
90 + class="btn btn-ghost btn-xs"
91 + title="One snapper fewer"
92 + >
93 + <i class="fas fa-fw fa-minus"></i>
94 + </button>
95 + <%= r.ready %>/<%= r.instances %>
96 + <button
97 + :if={@admin? and not r.read_only}
98 + type="button"
99 + phx-click={JS.push("scale-resolution", value: %{label: r.label, delta: 1})}
100 + disabled={r.instances >= RoomSanctum.Mutants.max_snappers()}
101 + class="btn btn-ghost btn-xs"
102 + title="One snapper more"
103 + >
104 + <i class="fas fa-fw fa-plus"></i>
105 + </button>
106 + </td>
85 107 <td>
86 108 <span class={[
87 109 "badge badge-sm",
modified apps/room_sanctum/test/room_sanctum/mutants_test.exs
+6 −0
@@ -11,6 +11,12 @@ defmodule RoomSanctum.MutantsTest do
11 11 assert String.length(Mutants.label_for(String.duplicate("a", 80))) == 63
12 12 end
13 13
14 + test "scale_resolution/2 stays between 1 and max_snappers before calling the operator" do
15 + assert {:error, _} = Mutants.scale_resolution("pinenote-v", 0)
16 + assert {:error, _} = Mutants.scale_resolution("pinenote-v", Mutants.max_snappers() + 1)
17 + assert {:error, _} = Mutants.scale_resolution("pinenote-v", "2")
18 + end
19 +
14 20 test "create_resolution/4 validates before calling the operator" do
15 21 assert {:error, _} = Mutants.create_resolution("!!!", 10, 10)
16 22 assert {:error, _} = Mutants.create_resolution("ok", 0, 10)

Parents: 65c16bb