lib/live_admin/components/nav.ex

defmodule LiveAdmin.Components.Nav do
  use Phoenix.LiveComponent
  use PhoenixHTMLHelpers

  import LiveAdmin,
    only: [resource_title: 2, route_with_params: 2, trans: 1]

  @impl true
  def update(assigns, socket) do
    base_path = assigns.base_path

    extra_pages =
      socket.router
      |> Phoenix.Router.routes()
      |> Enum.filter(fn r ->
        match?(
          %{
            metadata: %{
              phoenix_live_view: {_, _, _, %{extra: %{session: {_, _, [^base_path, _]}}}}
            }
          },
          r
        ) && is_nil(r.metadata[:resource]) &&
          !String.match?(r.helper, ~r/(home|session)/)
      end)

    socket =
      socket
      |> assign(assigns)
      |> assign(extra_pages: extra_pages)

    {:ok, socket}
  end

  @impl true
  def render(assigns) do
    nested_resources =
      Enum.reduce(assigns.resources, %{}, fn {key, resource}, groups ->
        path =
          resource
          |> LiveAdmin.fetch_config(:schema, assigns.config)
          |> Module.split()
          |> case do
            list when length(list) == 1 -> list
            list -> Enum.drop(list, -1)
          end
          |> Enum.map(&Access.key(&1, %{}))

        update_in(groups, path, fn subs -> Map.put(subs, {key, resource}, %{}) end)
      end)

    assigns = assign(assigns, :nested_resources, nested_resources)

    ~H"""
    <div class="nav">
      <ul class="nav__list">
        <li class="nav__item--group">
          <span>
            <%= Keyword.fetch!(@config, :title) %>
          </span>
        </li>
        <li class="nav__item--group">
          <.link navigate={@base_path}><%= trans("Home") %></.link>
        </li>
        <li class="nav__item--group">
          <.nav_group
            items={@nested_resources}
            base_path={@base_path}
            current_resource={assigns[:resource]}
            config={@config}
          />
        </li>
        <%= if Enum.any?(@extra_pages) do %>
          <li class="nav__item--group">
            <%= for route <- @extra_pages do %>
              <.link navigate={route.path}>
                <%= humanize(route.helper) %>
              </.link>
            <% end %>
          </li>
        <% end %>
        <li class="nav__item--group">
          <.link navigate={route_with_params(assigns, resource_path: "session")}>
            <%= trans("Session") %>
          </.link>
        </li>
      </ul>
    </div>
    """
  end

  defp nav_group(assigns) do
    ~H"""
    <ul>
      <%= for {parent, children} <- Enum.sort(@items) do %>
        <%= if match?({_key, _resource}, parent) do %>
          <li class={"nav__item#{if elem(parent, 1) == @current_resource, do: "--selected"}"}>
            <.link navigate={route_with_params(assigns, resource_path: elem(parent, 0))}>
              <%= resource_title(elem(parent, 1), @config) %>
            </.link>
          </li>
        <% else %>
          <li class="nav__item--drop">
            <input type="checkbox" id={"menu-group-#{parent}"} checked={open?(assigns, parent)} />
            <label for={"menu-group-#{parent}"}><%= parent %></label>
            <.nav_group
              items={children}
              base_path={@base_path}
              current_resource={@current_resource}
              config={@config}
            />
          </li>
        <% end %>
      <% end %>
    </ul>
    """
  end

  defp open?(assigns, schema) do
    assigns.current_resource
    |> case do
      nil ->
        true

      resource ->
        resource.__live_admin_config__()
        |> Keyword.fetch!(:schema)
        |> Module.split()
        |> Enum.drop(-1)
        |> Enum.member?(schema)
    end
  end
end