defmodule GitGudWeb.RepoLive.Tree do
use GitGudWeb, :live_view
alias GitGud.Git
alias GitGud.Repositories
alias GitGud.Repositories.Storage
@impl true
def mount(%{"owner" => owner, "name" => name, "ref" => ref} = params, _session, socket) do
repo =
Repositories.get_repository_by_path!(owner, name)
|> GitGud.Repo.preload([:owner, :organization])
path = params |> Map.get("path", []) |> path_list_to_string()
{:ok, head_sha} = Repositories.resolve(repo, ref)
{:ok, commit} = Repositories.get_commit(repo, head_sha)
{:ok, entry} =
Repositories.get_tree_entry_at(repo, commit.tree_sha, path)
{:ok, entries} = Repositories.get_tree(repo, entry.sha)
{:ok,
socket
|> assign(:repo, repo)
|> assign(:handle, Storage.repo_handle(repo))
|> assign(:ref, ref)
|> assign(:path, path)
|> assign(:head_sha, head_sha)
|> assign(:entries, entries)
|> GitGudWeb.RepoLive.Header.assign_chrome(repo)
|> assign(:page_title, "#{owner}/#{name}: #{path}")}
end
defp path_list_to_string(list) when is_list(list), do: Enum.join(list, "/")
defp path_list_to_string(s) when is_binary(s), do: s
@impl true
def render(assigns) do
~H"""
<Layouts.app flash={@flash} current_scope={@current_scope}>
<div class="space-y-4">
<GitGudWeb.RepoLive.Header.header
repo={@repo}
handle={@handle}
current_scope={@current_scope}
has_packages?={@has_packages?}
can_admin?={@can_admin?}
latest_run={@latest_run}
/>
<nav class="text-sm font-mono">
<.link navigate={~p"/r/#{@handle}/#{@repo.name}"} class="link link-hover">
{@repo.name}
</.link>
<span class="opacity-50"> @ </span>
<span>{@ref}</span>
<span :if={@path != ""} class="opacity-50"> / </span>
{build_breadcrumbs(assigns)}
</nav>
<table class="table table-sm">
<tbody>
<tr :if={@path != ""}>
<td class="w-8"><.icon name="hero-arrow-uturn-up" class="size-4" /></td>
<td colspan="2">
<.link navigate={parent_link(@handle, @repo, @ref, @path)} class="link link-hover">
..
</.link>
</td>
</tr>
<tr :for={e <- sorted(@entries)} id={"entry-#{e.name}"}>
<td class="w-8">
<.icon name={icon_for(e)} class="size-4 opacity-70" />
</td>
<td class="font-mono">
<.link navigate={child_link(@handle, @repo, @ref, @path, e)} class="link link-hover">
{e.name}
</.link>
</td>
<td class="text-xs opacity-60">{format_size(e.size)}</td>
</tr>
</tbody>
</table>
<p class="text-xs opacity-60 font-mono">{Git.short(@head_sha)}</p>
</div>
</Layouts.app>
"""
end
defp build_breadcrumbs(assigns) do
parts =
assigns.path
|> String.split("/", trim: true)
|> Enum.with_index()
assigns = assign(assigns, :parts, parts)
~H"""
<%= for {seg, i} <- @parts do %>
<.link
navigate={~p"/r/#{@handle}/#{@repo.name}/tree/#{@ref}/#{prefix_segs(@parts, i)}"}
class="link link-hover"
>
{seg}
</.link>
<span :if={i < length(@parts) - 1} class="opacity-50"> / </span>
<% end %>
"""
end
defp prefix_segs(parts, upto_index) do
parts |> Enum.take(upto_index + 1) |> Enum.map(&elem(&1, 0))
end
defp parent_link(handle, repo, ref, path) do
case path |> String.split("/", trim: true) |> Enum.drop(-1) do
[] -> ~p"/r/#{handle}/#{repo.name}?ref=#{ref}"
segs -> ~p"/r/#{handle}/#{repo.name}/tree/#{ref}/#{segs}"
end
end
defp child_link(handle, repo, ref, path, %{type: "tree", name: n}) do
segs = path_segments(path) ++ [n]
~p"/r/#{handle}/#{repo.name}/tree/#{ref}/#{segs}"
end
defp child_link(handle, repo, ref, path, %{type: "blob", name: n}) do
segs = path_segments(path) ++ [n]
~p"/r/#{handle}/#{repo.name}/blob/#{ref}/#{segs}"
end
defp child_link(_, _, _, _, _), do: "#"
defp path_segments(""), do: []
defp path_segments(path) when is_binary(path), do: String.split(path, "/", trim: true)
defp sorted(entries),
do: Enum.sort_by(entries, fn e -> {e.type != "tree", String.downcase(e.name)} end)
defp icon_for(%{type: "tree"}), do: "hero-folder"
defp icon_for(%{type: "blob"}), do: "hero-document"
defp icon_for(_), do: "hero-question-mark-circle"
defp format_size(nil), do: ""
defp format_size(b) when b < 1024, do: "#{b} B"
defp format_size(b) when b < 1_000_000, do: "#{Float.round(b / 1024, 1)} KiB"
defp format_size(b), do: "#{Float.round(b / 1_048_576, 1)} MiB"
end
4.7 KiB · text
5af55d9