Skip to content

Commit 329632a

Browse files
committed
Implement landing page search
1 parent cd726be commit 329632a

File tree

6 files changed

+85
-21
lines changed

6 files changed

+85
-21
lines changed

lib/plexus/apps.ex

+22
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ defmodule Plexus.Apps do
2828
App
2929
|> with_scores(opts)
3030
|> QueryHelpers.merge_opts(opts)
31+
|> filter(opts)
3132
|> Repo.paginate(page_opts)
3233
end
3334

@@ -120,6 +121,27 @@ defmodule Plexus.Apps do
120121
})
121122
end
122123

124+
defp filter(query, opts) do
125+
Enum.reduce(opts, query, fn
126+
{_, ""}, query ->
127+
query
128+
129+
{:search_term, search_term}, query ->
130+
pattern = "%#{search_term}%"
131+
132+
from q in query,
133+
where:
134+
fragment("SIMILARITY(?, ?) > .30", q.name, ^search_term) or
135+
ilike(q.name, ^pattern) or
136+
fragment("SIMILARITY(?, ?) > .30", q.package, ^search_term) or
137+
ilike(q.package, ^pattern),
138+
order_by: fragment("LEVENSHTEIN(?, ?)", q.name, ^search_term)
139+
140+
_, query ->
141+
query
142+
end)
143+
end
144+
123145
@spec subscribe :: :ok
124146
def subscribe do
125147
Phoenix.PubSub.subscribe(Plexus.PubSub, "apps")

lib/plexus_web/components/core_components.ex

+1-1
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,7 @@ defmodule PlexusWeb.CoreComponents do
201201
def simple_form(assigns) do
202202
~H"""
203203
<.form :let={f} for={@for} as={@as} {@rest}>
204-
<div class="mt-10 space-y-8 bg-white">
204+
<div class="mt-10 space-y-8">
205205
<%= render_slot(@inner_block, f) %>
206206
<div :for={action <- @actions} class="mt-2 flex items-center justify-between gap-6">
207207
<%= render_slot(action, f) %>

lib/plexus_web/components/layouts/app.html.heex

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<header class="px-4 sm:px-6 lg:px-8">
1+
<header class="bg-white px-4 sm:px-6 lg:px-8">
22
<div class="flex items-center justify-between border-b border-zinc-100 py-3 text-sm">
33
<div class="flex items-center gap-4">
44
<a href="/" class="flex items-center">
@@ -20,7 +20,7 @@
2020
</div>
2121
</div>
2222
</header>
23-
<main class="px-4 py-20 sm:px-6 lg:px-8">
23+
<main class="bg-slate-100 px-4 py-20 sm:px-6 lg:px-8">
2424
<div class="max-w-6xl mx-auto px-4 sm:px-6 lg:px-8">
2525
<.flash_group flash={@flash} />
2626
<%= @inner_content %>

lib/plexus_web/components/layouts/root.html.heex

+1-1
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@
4343
<script defer phx-track-static type="text/javascript" src={~p"/assets/app.js"}>
4444
</script>
4545
</head>
46-
<body class="bg-white antialiased">
46+
<body class="bg-slate-100 antialiased">
4747
<%= @inner_content %>
4848
</body>
4949
</html>

lib/plexus_web/live/app_live/index.ex

+46-14
Original file line numberDiff line numberDiff line change
@@ -7,43 +7,75 @@ defmodule PlexusWeb.AppLive.Index do
77
def mount(_params, _session, socket) do
88
{:ok,
99
socket
10+
|> assign(:page_title, "Crowdsourced de-Googled Android apps status ratings")
1011
|> assign(:page, 1)
11-
|> stream_configure(:apps, dom_id: & &1.package)
12-
|> paginate_apps(1)}
12+
|> assign(:form, to_form(changeset(), as: :form))
13+
|> assign(:no_results?, false)
14+
|> assign(:end_of_timeline?, false)
15+
|> stream_configure(:apps, dom_id: & &1.package)}
16+
end
17+
18+
defp changeset(params \\ %{}) do
19+
types = %{search: :string}
20+
data = %{}
21+
Ecto.Changeset.cast({data, types}, params, Map.keys(types))
1322
end
1423

1524
defp paginate_apps(socket, new_page) when new_page >= 1 do
1625
%Scrivener.Page{
1726
total_entries: total_entries,
1827
total_pages: total_pages,
1928
entries: apps
20-
} = Apps.list_apps(page: new_page, scores: true, order_by: :name)
29+
} =
30+
Apps.list_apps(
31+
search_term: socket.assigns.search_term,
32+
page: new_page,
33+
scores: true,
34+
order_by: :name,
35+
page_size: 50
36+
)
2137

22-
case apps do
23-
[] ->
38+
case {apps, new_page} do
39+
{[], page} when page != 1 ->
2440
assign(socket, end_of_timeline?: total_pages == new_page)
2541

26-
[_ | _] = apps ->
42+
{apps, _} ->
43+
opts = if new_page == 1, do: [reset: true], else: []
44+
end_of_timeline? = new_page >= total_pages
45+
2746
socket
28-
|> assign(end_of_timeline?: false)
47+
|> assign(end_of_timeline?: end_of_timeline?)
48+
|> assign(no_results?: apps == [])
2949
|> assign(:page, new_page)
3050
|> assign(:total_entries, total_entries)
31-
|> stream(:apps, apps)
51+
|> stream(:apps, apps, opts)
3252
end
3353
end
3454

3555
@impl Phoenix.LiveView
3656
def handle_params(params, _url, socket) do
37-
{:noreply, apply_action(socket, socket.assigns.live_action, params)}
57+
{:noreply,
58+
socket
59+
|> assign(:search_term, params["q"])
60+
|> assign(:form, to_form(changeset(%{search: params["q"]}), as: :form))
61+
|> paginate_apps(1)}
3862
end
3963

40-
defp apply_action(socket, :index, _params) do
41-
socket
42-
|> assign(:page_title, "Crowdsourced de-Googled Android apps status ratings")
43-
|> assign(:app, nil)
64+
@impl Phoenix.LiveView
65+
def handle_event("search", %{"form" => form}, socket) do
66+
params =
67+
form
68+
|> Map.get("search", "")
69+
|> String.trim()
70+
|> case do
71+
"" -> %{}
72+
"*" -> %{}
73+
term -> %{q: term}
74+
end
75+
76+
{:noreply, push_patch(socket, to: ~p"/?#{params}")}
4477
end
4578

46-
@impl Phoenix.LiveView
4779
def handle_event("next-page", _, socket) do
4880
{:noreply, paginate_apps(socket, socket.assigns.page + 1)}
4981
end

lib/plexus_web/live/app_live/index.html.heex

+13-3
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,20 @@
22
Apps (<%= @total_entries %> entries)
33
</.header>
44

5+
<.simple_form for={@form} id="search-form" phx-change="search" phx-submit="search">
6+
<.focus_wrap id="focus-first-search">
7+
<.input field={@form[:search]} label="Search" phx-debounce="300" />
8+
</.focus_wrap>
9+
</.simple_form>
10+
511
<ul
612
id="apps"
713
phx-update="stream"
814
phx-viewport-top={@page > 1 && "prev-page"}
915
phx-viewport-bottom={!@end_of_timeline? && "next-page"}
1016
phx-page-loading
1117
class={[
12-
"mt-3 grid grid-cols-1 gap-4 sm:gap-5 sm:grid-cols-2 lg:grid-cols-3",
18+
"mt-1 grid grid-cols-1 gap-4 sm:gap-5 sm:grid-cols-2 lg:grid-cols-3",
1319
if(@end_of_timeline?, do: "pb-10", else: "pb-[calc(200vh)]"),
1420
if(@page == 1, do: "pt-10", else: "pt-[calc(200vh)]")
1521
]}
@@ -19,6 +25,10 @@
1925
</li>
2026
</ul>
2127

22-
<div :if={@end_of_timeline?} class="mt-5 text-[50px] text-center">
23-
🎉 You made it to the end of the list 🎉
28+
<div :if={@no_results?} class="mt-5 text-[50px] text-center">
29+
No apps found<br />😭
30+
</div>
31+
32+
<div :if={@end_of_timeline? and not @no_results?} class="mt-5 text-[50px] text-center">
33+
End of list<br />🤭
2434
</div>

0 commit comments

Comments
 (0)