From 50ecb6472bcca3fbdbea5e0e6849a4f71f9ac6f1 Mon Sep 17 00:00:00 2001 From: Tom Konidas Date: Mon, 19 Aug 2024 19:05:42 -0400 Subject: [PATCH] Implement admin search --- lib/plexus_web/components/core_components.ex | 6 + lib/plexus_web/live/admin/app_live/index.ex | 103 ++++++++++++++--- .../live/admin/app_live/index.html.heex | 106 +++++++++++------- 3 files changed, 157 insertions(+), 58 deletions(-) diff --git a/lib/plexus_web/components/core_components.ex b/lib/plexus_web/components/core_components.ex index 2c55fe94..e1a830c6 100644 --- a/lib/plexus_web/components/core_components.ex +++ b/lib/plexus_web/components/core_components.ex @@ -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 @@ -485,6 +488,9 @@ defmodule PlexusWeb.CoreComponents do diff --git a/lib/plexus_web/live/admin/app_live/index.ex b/lib/plexus_web/live/admin/app_live/index.ex index 769b832c..229f7bc7 100644 --- a/lib/plexus_web/live/admin/app_live/index.ex +++ b/lib/plexus_web/live/admin/app_live/index.ex @@ -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 @@ -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) @@ -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 diff --git a/lib/plexus_web/live/admin/app_live/index.html.heex b/lib/plexus_web/live/admin/app_live/index.html.heex index 70c96b6d..707282b9 100644 --- a/lib/plexus_web/live/admin/app_live/index.html.heex +++ b/lib/plexus_web/live/admin/app_live/index.html.heex @@ -1,5 +1,5 @@ <.header> - Apps (<%= @page_metadata.total_entries %> entries) + Apps (<%= @total_entries %> entries) <:actions> <.link patch={~p"/admin/apps/new"}> <.button>New App @@ -7,47 +7,71 @@ -<.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" /> + + + +
- <:col :let={{_id, app}} label="Icon"> -
- {app.name " Icon"} - class="h-full w-full object-cover object-center" - /> -
- - <:col :let={{_dom_id, app}} label="Name"> -

<%= app.name %>

- - <:col :let={{_dom_id, app}} label="Package"> -

<%= app.package %>

- - <:col :let={{_dom_id, app}} label="Native"> - <.badge score={app.scores.native} /> - - <:col :let={{_dom_id, app}} label="MicroG"> - <.badge score={app.scores.micro_g} /> - - <:action :let={{_dom_id, app}}> -
- <.link navigate={~p"/admin/apps/#{app}"}>Show -
- <.link patch={~p"/admin/apps/#{app}/edit"}>Edit - - <:action :let={{dom_id, app}}> - <.link - phx-click={JS.push("delete", value: %{package: app.package}) |> hide("##{dom_id}")} - data-confirm="Are you sure?" - > - Delete - - - + <.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"> +
+ {app.name " Icon"} + class="h-full w-full object-cover object-center" + /> +
+ + <:col :let={{_dom_id, app}} label="Name"> +

<%= app.name %>

+ + <:col :let={{_dom_id, app}} label="Package"> +

<%= app.package %>

+ + <:col :let={{_dom_id, app}} label="De-Googled"> + <.badge score={app.scores.native} /> + + <:col :let={{_dom_id, app}} label="MicroG"> + <.badge score={app.scores.micro_g} /> + + <:action :let={{_dom_id, app}}> +
+ <.link navigate={~p"/admin/apps/#{app}"}>Show +
+ <.link patch={~p"/admin/apps/#{app}/edit"}>Edit + + <:action :let={{dom_id, app}}> + <.link + phx-click={JS.push("delete", value: %{package: app.package}) |> hide("##{dom_id}")} + data-confirm="Are you sure?" + > + Delete + + + + +
+ No apps found
😭 +
+ +
+ End of list
🤭 +
+
<.modal :if={@live_action in [:new, :edit]}