lib/live_admin/components/container.ex

defmodule LiveAdmin.Components.Container do
  use Phoenix.LiveView
  use Phoenix.HTML

  import LiveAdmin,
    only: [resource_title: 3, get_config: 3]

  alias __MODULE__.{Form, Index}
  alias LiveAdmin.{Resource, SessionStore}

  @impl true
  def mount(%{"resource_id" => key}, %{"id" => session_id}, socket) do
    {resource, config} = Map.fetch!(socket.assigns.resources, key)

    SessionStore.get_or_init(session_id)

    socket =
      assign(socket,
        resource: resource,
        key: key,
        config: config,
        session_id: session_id,
        loading: !connected?(socket)
      )

    {:ok, socket}
  end

  @impl true
  def handle_params(params, _uri, socket = %{assigns: %{live_action: :edit, loading: false}}) do
    socket = assign_prefix(socket, params["prefix"])

    record =
      params
      |> Map.fetch!("record_id")
      |> Resource.find!(socket.assigns.resource, socket.assigns.prefix)

    socket = assign(socket, record: record)

    {:noreply, socket}
  end

  @impl true
  def handle_params(params, _uri, socket = %{assigns: %{live_action: :list, loading: false}}) do
    page = String.to_integer(params["page"] || "1")

    sort = {
      String.to_existing_atom(params["sort-dir"] || "asc"),
      String.to_existing_atom(params["sort-attr"] || "id")
    }

    socket =
      socket
      |> assign(page: page)
      |> assign(sort: sort)
      |> assign(search: params["s"])
      |> assign_prefix(params["prefix"])

    {:noreply, socket}
  end

  @impl true
  def handle_params(params, _uri, socket), do: {:noreply, assign_prefix(socket, params["prefix"])}

  @impl true
  def handle_event("set_prefix", params, socket) do
    prefix = params["prefix"]

    if is_nil(prefix) do
      SessionStore.set(socket.assigns.session_id, :__prefix__, prefix)
    end

    {
      :noreply,
      push_redirect(socket,
        to: route_with_params(socket, [:list, socket.assigns.key], prefix: params["prefix"])
      )
    }
  end

  @impl true
  def handle_event("task", %{"task" => task}, socket) do
    task_name = String.to_existing_atom(task)

    session = SessionStore.lookup(socket.assigns.session_id)

    {m, f, a} =
      socket.assigns.config
      |> get_config(:tasks, [])
      |> Enum.find_value(fn
        {^task_name, mfa} -> mfa
        ^task_name -> {socket.assigns.resource, task_name, []}
      end)

    socket =
      case apply(m, f, [session] ++ a) do
        {:ok, result} ->
          push_event(socket, "success", %{
            msg: "Successfully completed #{task}: #{inspect(result)}"
          })

        {:error, error} ->
          push_event(socket, "error", %{msg: "#{task} failed: #{error}"})
      end

    {:noreply, socket}
  end

  def render(assigns = %{loading: true}), do: ~H"
"

  @impl true
  def render(assigns) do
    ~H"""
    <div class="resource__banner">
      <h1 class="resource__title">
        <%= resource_title(@resource, @config, @base_path) %>
      </h1>

      <div class="resource__actions">
        <div>
          <%= live_redirect("List",
            to: route_with_params(@socket, [:list, @key], prefix: @prefix),
            class: "resource__action--btn"
          ) %>
          <%= if get_config(@config, :create_with, true) do %>
            <%= live_redirect("New",
              to: route_with_params(@socket, [:new, @key], prefix: @prefix),
              class: "resource__action--btn"
            ) %>
          <% end %>
          <%= for key <- get_task_keys(@config) do %>
            <%= link(key |> to_string() |> humanize(),
              to: "#",
              "data-confirm": "Are you sure?",
              "phx-click": "task",
              "phx-value-task": key,
              class: "resource__action--btn"
            ) %>
          <% end %>
          |
          <%= if Application.get_env(:live_admin, :prefix_options) do %>
            <div class="resource__action--drop">
              <button class="resource__action--btn">
                <%= @prefix || "Set prefix" %>
              </button>
              <nav>
                <ul>
                  <%= if @prefix do %>
                    <li>
                      <a href="#" phx-click="set_prefix">clear</a>
                    </li>
                  <% end %>
                  <%= for option <- get_prefix_options!(), to_string(option) != @prefix do %>
                    <li>
                      <a href="#" phx-click="set_prefix" phx-value-prefix={option}><%= option %></a>
                    </li>
                  <% end %>
                </ul>
              </nav>
            </div>
          <% end %>
        </div>
      </div>
    </div>

    <%= render("#{@live_action}.html", assigns) %>
    """
  end

  def render("list.html", assigns) do
    mod = get_in(assigns, [:config, :components, :list]) || Index

    ~H"""
    <.live_component
      module={mod}
      id="list"
      socket={@socket}
      resources={@resources}
      resource={@resource}
      config={@config}
      key={@key}
      page={@page}
      sort={@sort}
      search={@search}
      prefix={@prefix}
      session_id={@session_id}
    />
    """
  end

  def render("new.html", assigns) do
    mod = get_in(assigns, [:config, :components, :new]) || Form

    ~H"""
    <.live_component
      module={mod}
      id="form"
      resource={@resource}
      config={@config}
      action="create"
      session_id={@session_id}
      key={@key}
      resources={@resources}
    />
    """
  end

  def render("edit.html", assigns) do
    mod = get_in(assigns, [:config, :components, :edit]) || Form

    ~H"""
    <.live_component
      module={mod}
      id="form"
      resource={@resource}
      config={@config}
      action="update"
      session_id={@session_id}
      key={@key}
      record={@record}
      resources={@resources}
    />
    """
  end

  def route_with_params(socket, segments, params \\ %{}) do
    params =
      Enum.flat_map(params, fn
        {:prefix, nil} -> []
        pair -> [pair]
      end)

    apply(socket.router.__helpers__(), :resource_path, [socket] ++ segments ++ [params])
  end

  def get_prefix_options!() do
    Application.fetch_env!(:live_admin, :prefix_options)
    |> case do
      {mod, func, args} -> apply(mod, func, args)
      list when is_list(list) -> list
    end
    |> Enum.sort()
  end

  def assign_prefix(_, prefix \\ nil)

  def assign_prefix(socket, nil) do
    case SessionStore.lookup(socket.assigns.session_id, :__prefix__) do
      nil ->
        assign(socket, :prefix, nil)

      prefix ->
        socket
        |> assign(:prefix, prefix)
        |> push_redirect(
          to: route_with_params(socket, [:list, socket.assigns.key], prefix: prefix)
        )
    end
  end

  def assign_prefix(socket, prefix) do
    SessionStore.set(socket.assigns.session_id, :__prefix__, prefix)

    assign(socket, :prefix, prefix)
  end

  defp get_task_keys(config) do
    config
    |> Map.get(:tasks, [])
    |> Enum.map(fn
      {key, _} -> key
      key -> key
    end)
  end
end