4.8 KiB · text 5af55d9
defmodule GitGudWeb.IssueLive.Index do
use GitGudWeb, :live_view
alias GitGud.Issues
alias GitGud.Labels
alias GitGud.Repositories
alias GitGud.Repositories.Storage
@impl true
def mount(%{"owner" => owner, "name" => name}, _session, socket) do
repo =
Repositories.get_repository_by_path!(owner, name)
|> GitGud.Repo.preload([:owner, :organization])
{:ok,
socket
|> assign(:repo, repo)
|> assign(:handle, Storage.repo_handle(repo))
|> GitGudWeb.RepoLive.Header.assign_chrome(repo)
|> assign(:labels, Labels.list_labels(repo))
|> assign(:state_filter, "open")
|> assign(:search, "")
|> assign(:selected_labels, [])
|> stream(:issues, [])
|> load_issues()
|> assign(:page_title, "Issues — #{owner}/#{name}")}
end
defp load_issues(socket) do
issues =
Issues.list_issues(socket.assigns.repo,
state: socket.assigns.state_filter,
search: socket.assigns.search,
label_ids: socket.assigns.selected_labels
)
stream(socket, :issues, issues, reset: true)
end
@impl true
def handle_event("set_state", %{"state" => state}, socket),
do: {:noreply, socket |> assign(:state_filter, state) |> load_issues()}
def handle_event("search", %{"q" => q}, socket),
do: {:noreply, socket |> assign(:search, q) |> load_issues()}
def handle_event("toggle_label", %{"id" => id}, socket) do
id = String.to_integer(id)
selected =
if id in socket.assigns.selected_labels,
do: List.delete(socket.assigns.selected_labels, id),
else: [id | socket.assigns.selected_labels]
{:noreply, socket |> assign(:selected_labels, selected) |> load_issues()}
end
@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}
/>
<header class="flex items-center justify-between">
<h1 class="text-xl font-semibold">Issues</h1>
<.link navigate={~p"/r/#{@handle}/#{@repo.name}/issues/new"} class="btn btn-primary btn-sm">
New issue
</.link>
</header>
<form phx-change="search" class="flex gap-2">
<input
type="text"
name="q"
value={@search}
placeholder="Search…"
class="input input-sm input-bordered flex-1"
/>
</form>
<div class="flex flex-wrap items-center gap-2 text-sm">
<span>State:</span>
<button
:for={s <- ["open", "closed", "all"]}
type="button"
phx-click="set_state"
phx-value-state={s}
class={["btn btn-xs", s == @state_filter && "btn-primary"]}
>
{s}
</button>
<span class="ml-4">Labels:</span>
<button
:for={l <- @labels}
type="button"
phx-click="toggle_label"
phx-value-id={l.id}
class={["badge", l.id in @selected_labels && "badge-primary"]}
style={"background-color: " <> l.color <> "; color: white"}
>
{l.name}
</button>
</div>
<ul id="issues" phx-update="stream" class="divide-y divide-base-300">
<li class="hidden only:block py-6 opacity-60 text-sm">No issues yet.</li>
<li :for={{id, issue} <- @streams.issues} id={id} class="py-3">
<div class="flex items-baseline justify-between gap-3">
<.link
navigate={~p"/r/#{@handle}/#{@repo.name}/issues/#{issue.number}"}
class="font-medium link link-hover"
>
#{issue.number}{issue.title}
</.link>
<span class={["badge badge-sm", state_class(issue.state)]}>{issue.state}</span>
</div>
<div class="flex flex-wrap gap-1 mt-1">
<span
:for={l <- issue.labels}
class="badge badge-xs"
style={"background-color: " <> l.color <> "; color: white"}
>
{l.name}
</span>
</div>
<p class="text-xs opacity-60 mt-1">
{author_name(issue.author)} · {format_dt(issue.inserted_at)}
</p>
</li>
</ul>
</div>
</Layouts.app>
"""
end
defp author_name(%{email: e}), do: e |> String.split("@") |> hd()
defp author_name(_), do: "anonymous"
defp state_class("open"), do: "badge-success"
defp state_class("closed"), do: "badge-neutral"
defp state_class(_), do: ""
defp format_dt(dt), do: Calendar.strftime(dt, "%Y-%m-%d %H:%M")
end