lib/swoosh/gallery/layout.ex

defmodule Swoosh.Gallery.Layout do
  @moduledoc """
  This is the base layout of the Gallery. This is used both on Swoosh.Gallery.Plug and
  Mix.Tasks.Swoosh.Gallery.Html.
  """

  alias Swoosh.Email.Render
  require EEx

  @external_resource index_file = Path.join([__DIR__, "templates", "index.html.eex"])
  @external_resource table_file = Path.join([__DIR__, "templates", "table.html.eex"])
  @external_resource preview_file = Path.join([__DIR__, "templates", "preview.html.eex"])
  @external_resource styles_file = Path.join([__DIR__, "app.css"])

  @styles File.read!(styles_file)
  # Colors need to be declared entirely. Otherwise, the Tailwind CLI will ignore them.
  @tag_bg_colors ~w(bg-orange-100 bg-fuchsia-100 bg-amber-100 bg-red-100 bg-green-100 bg-neutral-100 bg-pink-100 bg-indigo-100 bg-emerald-100 bg-rose-100 bg-sky-100 bg-zinc-100 bg-yellow-100 bg-teal-100 bg-purple-100 bg-violet-100 bg-lime-100 bg-stone-100 bg-blue-100 bg-slate-100 bg-cyan-100 bg-gray-100)
  @tag_text_colors ~w(text-orange-800 text-fuchsia-800 text-amber-800 text-red-800 text-green-800 text-neutral-800 text-pink-800 text-indigo-800 text-emerald-800 text-rose-800 text-sky-800 text-zinc-800 text-yellow-800 text-teal-800 text-purple-800 text-violet-800 text-lime-800 text-stone-800 text-blue-800 text-slate-800 text-cyan-800 text-gray-800)

  EEx.function_from_file(:defp, :render_page, index_file, [:assigns])
  EEx.function_from_file(:defp, :render_table, table_file, [:assigns])
  EEx.function_from_file(:defp, :render_preview, preview_file, [:assigns])

  @doc ~S"""
  Renders a layout. See template.html.eex to find all the properties used by this.
  """
  @spec render(atom, keyword) :: binary
  def render(gallery, assigns \\ []) do
    ungrouped_previews =
      gallery.previews
      |> ungrouped_previews()
      |> sort_previews(gallery.sort)

    groups = grouped_previews(gallery.groups, gallery.previews, gallery.sort)

    assigns
    |> Keyword.put(:ungrouped_previews, ungrouped_previews)
    |> Keyword.put(:groups, groups)
    |> Keyword.put(:styles, @styles)
    |> Keyword.put_new(:format, nil)
    |> render_page()
  end

  defp preview_path(preview, base_path, opts) do
    path = Keyword.get(opts, :path, "")
    format = Keyword.get(opts, :format, nil)

    add_format(Path.join([base_path, preview.path, path]), format)
  end

  defp add_format(string, nil), do: string
  defp add_format(string, format), do: "#{string}#{format}"

  defp to_absolute_url(base_path, path) do
    Path.join(base_path, path)
  end

  defp render_recipient(recipient) do
    case Render.render_recipient(recipient) do
      "" -> nil
      recipient -> Plug.HTML.html_escape(recipient)
    end
  end

  defp to_tag_color(tag_name) do
    index = :erlang.phash2(tag_name, length(@tag_bg_colors))
    {Enum.at(@tag_bg_colors, index), Enum.at(@tag_text_colors, index)}
  end

  defp ungrouped_previews(previews) do
    Enum.filter(previews, &is_nil(&1.group))
  end

  defp grouped_previews(groups, previews, sort) do
    groups
    |> Enum.map(fn group ->
      grouped_previews =
        previews
        |> Enum.filter(fn %{group: path} -> group.path == path end)
        |> sort_previews(sort)

      Map.put(group, :previews, grouped_previews)
    end)
    |> Enum.sort_by(fn %{title: title} -> title end)
  end

  defp sort_previews(previews, sort_fun) when is_function(sort_fun) do
    sort_fun.(previews)
  end

  defp sort_previews(previews, true), do: sort_by_title(previews)

  defp sort_previews(previews, false), do: Enum.reverse(previews)

  defp sort_by_title(previews) when is_list(previews) do
    Enum.sort_by(previews, fn %{preview_details: %{title: title}} -> title end)
  end
end