lib/openagents_web/live/label_index_live.ex

436d37facc96 · 5 KB

defmodule OpenAgentsWeb.LabelIndexLive do
  @moduledoc """
  Renders repository labels and a form to create them.
  """
  use OpenAgentsWeb, :live_view

  alias OpenAgents.Labels
  alias OpenAgents.Labels.Label
  alias OpenAgents.Repositories
  alias OpenAgentsWeb.LiveRefresh

  def mount(%{"owner" => owner, "repo" => repo}, _session, socket) do
    repository = visible_repository!(owner, repo, socket.assigns.current_user)

    # A label created from the API, the CLI, or another tab belongs on this
    # page without a reload, and this page is where a person watches for it.
    if connected?(socket), do: Labels.subscribe_labels(repository.id)

    {:ok,
     socket
     |> LiveRefresh.init()
     |> assign(:current_scope, socket.assigns[:current_scope])
     |> assign(:owner, owner)
     |> assign(:repo, repo)
     |> assign(:repository, repository)
     |> assign(:form, to_form(Labels.change_label(%Label{})))
     |> refresh_panel(:labels)}
  end

  def handle_info({:labels_changed, repository_id}, socket) do
    if repository_id == socket.assigns.repository.id,
      do: {:noreply, LiveRefresh.mark_stale(socket, :labels, &refresh_panel/2)},
      else: {:noreply, socket}
  end

  def handle_info(:live_refresh, socket),
    do: {:noreply, LiveRefresh.run(socket, &refresh_panel/2)}

  # The list and the authority to change it are read together, so the page
  # cannot offer a delete button to a viewer whose membership was withdrawn
  # while they were looking at it.
  defp refresh_panel(socket, :labels) do
    socket
    |> refresh_authority()
    |> assign(:labels, Labels.list_labels(socket.assigns.repository))
  end

  def handle_event("save", %{"label" => label_params}, socket) do
    socket = refresh_authority(socket)

    if socket.assigns.can_write do
      case Labels.create_label(
             socket.assigns.repository,
             label_params,
             socket.assigns.current_user
           ) do
        {:ok, _label} ->
          {:noreply,
           socket
           |> refresh_panel(:labels)
           |> assign(:form, to_form(Labels.change_label(%Label{})))
           |> put_flash(:info, "Label created")}

        {:error, changeset} ->
          {:noreply, assign(socket, :form, to_form(changeset))}
      end
    else
      {:noreply, put_flash(socket, :error, "Only repository members can create labels.")}
    end
  end

  def handle_event("delete", %{"id" => id}, socket) do
    socket = refresh_authority(socket)

    if socket.assigns.can_write do
      label = Labels.get_label!(socket.assigns.repository, String.to_integer(id))
      {:ok, _} = Labels.delete_label(label)

      {:noreply,
       socket
       |> refresh_panel(:labels)
       |> put_flash(:info, "Label deleted")}
    else
      {:noreply, put_flash(socket, :error, "Only repository members can delete labels.")}
    end
  end

  defp refresh_authority(socket) do
    assign(
      socket,
      :can_write,
      Repositories.writable?(socket.assigns.repository, socket.assigns.current_user)
    )
  end

  defp visible_repository!(owner, repo, user) do
    Repositories.get_visible_by_path!(owner, repo, user)
  rescue
    Ecto.NoResultsError ->
      raise OpenAgentsWeb.PublicNotFoundError, message: "repository not found"
  end

  def render(assigns) do
    ~H"""
    <Layouts.app
      flash={@flash}
      sidebar_sections={assigns[:sidebar_sections]}
      current_scope={@current_scope}
    >
      <div class="flex items-center justify-between mb-4">
        <h1 id="labels-title" class="text-2xl font-bold">Labels</h1>
      </div>

      <.form
        :if={@can_write}
        for={@form}
        id="new-label-form"
        phx-submit="save"
        class="card !mx-0 !mt-0 mb-6"
      >
        <div class="grid grid-cols-1 md:grid-cols-3 gap-4 items-end">
          <.input field={@form[:name]} label="Name" required />
          <.input field={@form[:color]} label="Color" required />
          <.input field={@form[:description]} label="Description" />
        </div>
        <footer class="flex justify-end mt-2">
          <.button type="submit" variant={:primary}>Add label</.button>
        </footer>
      </.form>

      <%= if @labels == [] do %>
        <div id="labels-empty" class="alert" data-variant="info" role="status">
          <.icon name="info-circle" class="size-5" />
          <section>No labels yet.</section>
        </div>
      <% else %>
        <.table id="labels" rows={@labels}>
          <:col :let={label} label="Label">
            <span
              class="badge rounded-full px-2 py-0.5"
              style={"background-color: ##{label.color}; color: #000;"}
            >
              {label.name}
            </span>
          </:col>
          <:col :let={label} label="Description">{label.description || "—"}</:col>
          <:col :let={label} label="">
            <button
              :if={@can_write}
              class="btn"
              data-variant="ghost"
              data-size="sm"
              data-tone="danger"
              phx-click="delete"
              phx-value-id={label.id}
              data-confirm="Delete this label?"
            >
              Delete
            </button>
          </:col>
        </.table>
      <% end %>
    </Layouts.app>
    """
  end
end