Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement Admin Search #359

Merged
merged 3 commits into from
Aug 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions config/config.exs
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ config :plexus,
config :plexus, :generators, api_prefix: "/api/v1"

config :plexus, :basic_auth,
username: "plexus",
password: "plexus"
username: "admin",
password: "admin"

config :plexus, Plexus.Repo,
migration_primary_key: [id: :uuid, type: :binary_id],
Expand Down
6 changes: 6 additions & 0 deletions lib/plexus_web/components/core_components.ex
Original file line number Diff line number Diff line change
Expand Up @@ -459,6 +459,9 @@ defmodule PlexusWeb.CoreComponents do
default: &Function.identity/1,
doc: "the function for mapping each row before calling the :col and :action slots"

attr :viewport_top, :string
attr :viewport_bottom, :string

slot :col, required: true do
attr :label, :string
end
Expand All @@ -485,6 +488,9 @@ defmodule PlexusWeb.CoreComponents do
<tbody
id={@id}
phx-update={match?(%Phoenix.LiveView.LiveStream{}, @rows) && "stream"}
phx-viewport-top={match?(%Phoenix.LiveView.LiveStream{}, @rows) && @viewport_top}
phx-viewport-bottom={match?(%Phoenix.LiveView.LiveStream{}, @rows) && @viewport_bottom}
phx-page-loading
class="relative divide-y divide-zinc-100 border-t border-zinc-200 text-sm leading-6 text-zinc-700"
>
<tr :for={row <- @rows} id={@row_id && @row_id.(row)} class="group hover:bg-zinc-50">
Expand Down
103 changes: 86 additions & 17 deletions lib/plexus_web/live/admin/app_live/index.ex
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,60 @@ defmodule PlexusWeb.Admin.AppLive.Index do
def mount(_params, _session, socket) do
if connected?(socket), do: Apps.subscribe()

{entries, page_metadata} =
[scores: true, order_by: :name, page_size: 9999]
|> Apps.list_apps()
|> Map.pop(:entries)

{:ok,
socket
|> assign(:page_metadata, page_metadata)
|> stream_configure(:apps, dom_id: &"apps-#{&1.package}")
|> stream(:apps, entries)}
|> assign(:page, 1)
|> assign(:form, to_form(changeset(), as: :form))
|> assign(:no_results?, false)
|> assign(:end_of_timeline?, false)
|> stream_configure(:apps, dom_id: &"apps-#{&1.package}")}
end

defp changeset(params \\ %{}) do
types = %{search: :string}
data = %{}
Ecto.Changeset.cast({data, types}, params, Map.keys(types))
end

defp paginate_apps(socket, new_page) when new_page >= 1 do
%Scrivener.Page{
total_entries: total_entries,
total_pages: total_pages,
entries: apps
} =
Apps.list_apps(
search_term: socket.assigns.search_term,
page: new_page,
scores: true,
order_by: :name,
page_size: 50
)

case {apps, new_page} do
{[], page} when page != 1 ->
assign(socket, end_of_timeline?: total_pages == new_page)

{apps, _} ->
opts = if new_page == 1, do: [reset: true], else: []
end_of_timeline? = new_page >= total_pages

socket
|> assign(end_of_timeline?: end_of_timeline?)
|> assign(no_results?: apps == [])
|> assign(:page, new_page)
|> assign(:total_entries, total_entries)
|> stream(:apps, apps, opts)
end
end

@impl Phoenix.LiveView
def handle_params(params, _url, socket) do
{:noreply, apply_action(socket, socket.assigns.live_action, params)}
{:noreply,
socket
|> assign(:search_term, params["q"])
|> assign(:form, to_form(changeset(%{search: params["q"]}), as: :form))
|> paginate_apps(1)
|> apply_action(socket.assigns.live_action, params)}
end

defp apply_action(socket, :edit, %{"package" => package}) do
Expand All @@ -43,6 +82,44 @@ defmodule PlexusWeb.Admin.AppLive.Index do
|> assign(:app, nil)
end

@impl Phoenix.LiveView
def handle_event("search", %{"form" => form}, socket) do
params =
form
|> Map.get("search", "")
|> String.trim()
|> case do
"" -> %{}
"*" -> %{}
term -> %{q: term}
end

{:noreply, push_patch(socket, to: ~p"/admin/apps?#{params}")}
end

def handle_event("next-page", _, socket) do
{:noreply, paginate_apps(socket, socket.assigns.page + 1)}
end

def handle_event("prev-page", %{"_overran" => true}, socket) do
{:noreply, paginate_apps(socket, 1)}
end

def handle_event("prev-page", _, socket) do
if socket.assigns.page > 1 do
{:noreply, paginate_apps(socket, socket.assigns.page - 1)}
else
{:noreply, socket}
end
end

def handle_event("delete", %{"package" => package}, socket) do
app = Apps.get_app!(package)
{:ok, _} = Apps.delete_app(app)

{:noreply, stream_delete(socket, :apps, app)}
end

@impl Phoenix.LiveView
def handle_info({:app_created, app}, socket) do
app = Apps.get_app!(app.package, scores: true)
Expand Down Expand Up @@ -79,12 +156,4 @@ defmodule PlexusWeb.Admin.AppLive.Index do
|> put_flash(:info, "'#{app.name}' Rating Updated")
|> stream_insert(:apps, app)}
end

@impl Phoenix.LiveView
def handle_event("delete", %{"package" => package}, socket) do
app = Apps.get_app!(package)
{:ok, _} = Apps.delete_app(app)

{:noreply, stream_delete(socket, :apps, app)}
end
end
106 changes: 65 additions & 41 deletions lib/plexus_web/live/admin/app_live/index.html.heex
Original file line number Diff line number Diff line change
@@ -1,53 +1,77 @@
<.header>
Apps (<%= @page_metadata.total_entries %> entries)
Apps (<%= @total_entries %> entries)
<:actions>
<.link patch={~p"/admin/apps/new"}>
<.button>New App</.button>
</.link>
</:actions>
</.header>

<.table
id="apps"
rows={@streams.apps}
row_click={fn {_id, app} -> JS.navigate(~p"/admin/apps/#{app}") end}
<.simple_form for={@form} id="search-form" phx-change="search" phx-submit="search">
<.focus_wrap id="focus-first-search">
<.input field={@form[:search]} label="Search" phx-debounce="300" />
</.focus_wrap>
</.simple_form>

<div
id="apps-container"
class={[
if(@end_of_timeline?, do: "pb-10", else: "pb-[calc(200vh)]"),
if(@page == 1, do: "pt-10", else: "pt-[calc(200vh)]")
]}
>
<:col :let={{_id, app}} label="Icon">
<div class="aspect-h-1 aspect-w-1 w-16 h-16 overflow-hidden">
<img
src={app.icon_url}
alt={app.name <> " Icon"}
class="h-full w-full object-cover object-center"
/>
</div>
</:col>
<:col :let={{_dom_id, app}} label="Name">
<h3 class="text-sm text-gray-700"><%= app.name %></h3>
</:col>
<:col :let={{_dom_id, app}} label="Package">
<p class="text-sm text-gray-700"><%= app.package %></p>
</:col>
<:col :let={{_dom_id, app}} label="Native">
<.badge score={app.scores.native} />
</:col>
<:col :let={{_dom_id, app}} label="MicroG">
<.badge score={app.scores.micro_g} />
</:col>
<:action :let={{_dom_id, app}}>
<div class="sr-only">
<.link navigate={~p"/admin/apps/#{app}"}>Show</.link>
</div>
<.link patch={~p"/admin/apps/#{app}/edit"}>Edit</.link>
</:action>
<:action :let={{dom_id, app}}>
<.link
phx-click={JS.push("delete", value: %{package: app.package}) |> hide("##{dom_id}")}
data-confirm="Are you sure?"
>
Delete
</.link>
</:action>
</.table>
<.table
id="apps"
viewport_top={@page > 1 && "prev-page"}
viewport_bottom={!@end_of_timeline? && "next-page"}
rows={@streams.apps}
row_click={fn {_id, app} -> JS.navigate(~p"/admin/apps/#{app}") end}
>
<:col :let={{_id, app}} label="Icon">
<div class="aspect-h-1 aspect-w-1 w-16 h-16 overflow-hidden">
<img
src={app.icon_url}
alt={app.name <> " Icon"}
class="h-full w-full object-cover object-center"
/>
</div>
</:col>
<:col :let={{_dom_id, app}} label="Name">
<h3 class="text-sm text-gray-700"><%= app.name %></h3>
</:col>
<:col :let={{_dom_id, app}} label="Package">
<p class="text-sm text-gray-700"><%= app.package %></p>
</:col>
<:col :let={{_dom_id, app}} label="De-Googled">
<.badge score={app.scores.native} />
</:col>
<:col :let={{_dom_id, app}} label="MicroG">
<.badge score={app.scores.micro_g} />
</:col>
<:action :let={{_dom_id, app}}>
<div class="sr-only">
<.link navigate={~p"/admin/apps/#{app}"}>Show</.link>
</div>
<.link patch={~p"/admin/apps/#{app}/edit"}>Edit</.link>
</:action>
<:action :let={{dom_id, app}}>
<.link
phx-click={JS.push("delete", value: %{package: app.package}) |> hide("##{dom_id}")}
data-confirm="Are you sure?"
>
Delete
</.link>
</:action>
</.table>

<div :if={@no_results?} class="mt-5 text-[50px] text-center">
No apps found<br />😭
</div>

<div :if={@end_of_timeline? and not @no_results?} class="mt-5 text-[50px] text-center">
End of list<br />🤭
</div>
</div>

<.modal
:if={@live_action in [:new, :edit]}
Expand Down
4 changes: 2 additions & 2 deletions priv/static/robots.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# See https://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file
#
# To ban all spiders from the entire site uncomment the next two lines:
# User-agent: *
# Disallow: /
User-agent: *
Disallow: /admin/
Loading