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.
Scribus: scale a resolution's snappers up and down in place
1869d95 · gmorell · 2026-11-09 15:46
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