diff --git a/lib/pinchflat/media/media_query.ex b/lib/pinchflat/media/media_query.ex index c3a1a65..1412ab8 100644 --- a/lib/pinchflat/media/media_query.ex +++ b/lib/pinchflat/media/media_query.ex @@ -125,6 +125,15 @@ defmodule Pinchflat.Media.MediaQuery do ) end + def matches_search_term(nil), do: dynamic([mi], true) + + def matches_search_term(term) do + case String.trim(term) do + "" -> dynamic([mi], true) + term -> dynamic([mi], fragment("media_items_search_index MATCH ?", ^term)) + end + end + def require_assoc(query, identifier) do if has_named_binding?(query, identifier) do query @@ -133,6 +142,10 @@ defmodule Pinchflat.Media.MediaQuery do end end + defp do_require_assoc(query, :media_items_search_index) do + from(mi in query, join: s in assoc(mi, :media_items_search_index), as: :media_items_search_index) + end + defp do_require_assoc(query, :source) do from(mi in query, join: s in assoc(mi, :source), as: :source) end diff --git a/lib/pinchflat_web/components/layouts/partials/header.html.heex b/lib/pinchflat_web/components/layouts/partials/header.html.heex index 1c03f08..ebbfd8d 100644 --- a/lib/pinchflat_web/components/layouts/partials/header.html.heex +++ b/lib/pinchflat_web/components/layouts/partials/header.html.heex @@ -22,7 +22,7 @@ type="text" name="q" value={@params["q"]} - placeholder="Type to search..." + placeholder="Search all media..." class="w-full bg-transparent pl-9 pr-4 border-0 focus:ring-0 focus:outline-none" /> diff --git a/lib/pinchflat_web/controllers/sources/source_html/media_item_table_live.ex b/lib/pinchflat_web/controllers/sources/source_html/media_item_table_live.ex index 48e766c..3890ffa 100644 --- a/lib/pinchflat_web/controllers/sources/source_html/media_item_table_live.ex +++ b/lib/pinchflat_web/controllers/sources/source_html/media_item_table_live.ex @@ -8,7 +8,7 @@ defmodule Pinchflat.Sources.MediaItemTableLive do @limit 10 - def render(%{records: []} = assigns) do + def render(%{total_record_count: 0} = assigns) do ~H"""
<.icon_button icon_name="hero-arrow-path" class="h-10 w-10" phx-click="reload_page" /> @@ -17,13 +17,34 @@ defmodule Pinchflat.Sources.MediaItemTableLive do """ end + # TODO: make search results clickable for main search + def render(assigns) do ~H"""
- - <.icon_button icon_name="hero-arrow-path" class="h-10 w-10" phx-click="reload_page" tooltip="Refresh" /> - Showing <%= length(@records) %> of <%= @total_record_count %> - +
+ + <.icon_button icon_name="hero-arrow-path" class="h-10 w-10" phx-click="reload_page" tooltip="Refresh" /> + Showing <%= length(@records) %> of <%= @filtered_record_count %> total + +
+
+ + <.icon name="hero-magnifying-glass" /> + +
+ +
+
+
+
<.table rows={@records} table_class="text-white"> <:col :let={media_item} label="Title"> <.subtle_link href={~p"/sources/#{@source.id}/media/#{media_item.id}"}> @@ -51,7 +72,7 @@ defmodule Pinchflat.Sources.MediaItemTableLive do media_state = session["media_state"] source = Sources.get_source!(session["source_id"]) base_query = generate_base_query(source, media_state) - pagination_attrs = fetch_pagination_attributes(base_query, page) + pagination_attrs = fetch_pagination_attributes(base_query, page, "friendly") new_assigns = Map.merge( @@ -69,7 +90,14 @@ defmodule Pinchflat.Sources.MediaItemTableLive do def handle_event("page_change", %{"direction" => direction}, %{assigns: assigns} = socket) do direction = if direction == "inc", do: 1, else: -1 new_page = assigns.page + direction - new_assigns = fetch_pagination_attributes(assigns.base_query, new_page) + new_assigns = fetch_pagination_attributes(assigns.base_query, new_page, assigns.search_term) + + {:noreply, assign(socket, new_assigns)} + end + + def handle_event("search_term", params, socket) do + search_term = Map.get(params, "q", nil) + new_assigns = fetch_pagination_attributes(socket.assigns.base_query, 1, search_term) {:noreply, assign(socket, new_assigns)} end @@ -83,18 +111,28 @@ defmodule Pinchflat.Sources.MediaItemTableLive do end def handle_info(%{topic: "media_table", event: "reload"}, %{assigns: assigns} = socket) do - new_assigns = fetch_pagination_attributes(assigns.base_query, assigns.page) + new_assigns = fetch_pagination_attributes(assigns.base_query, assigns.page, assigns.search_term) {:noreply, assign(socket, new_assigns)} end - defp fetch_pagination_attributes(base_query, page) do - total_record_count = Repo.aggregate(base_query, :count, :id) - total_pages = max(ceil(total_record_count / @limit), 1) - page = NumberUtils.clamp(page, 1, total_pages) - records = fetch_records(base_query, page) + defp fetch_pagination_attributes(base_query, page, search_term) do + filtered_base_query = filter_base_query(base_query, search_term) - %{page: page, total_pages: total_pages, records: records, total_record_count: total_record_count} + total_record_count = Repo.aggregate(base_query, :count, :id) + filtered_record_count = Repo.aggregate(filtered_base_query, :count, :id) + total_pages = max(ceil(filtered_record_count / @limit), 1) + page = NumberUtils.clamp(page, 1, total_pages) + records = fetch_records(filtered_base_query, page) + + %{ + page: page, + total_pages: total_pages, + records: records, + search_term: search_term, + total_record_count: total_record_count, + filtered_record_count: filtered_record_count + } end defp fetch_records(base_query, page) do @@ -109,25 +147,33 @@ defmodule Pinchflat.Sources.MediaItemTableLive do defp generate_base_query(source, "pending") do MediaQuery.new() |> MediaQuery.require_assoc(:media_profile) + |> MediaQuery.require_assoc(:media_items_search_index) |> where(^dynamic(^MediaQuery.for_source(source) and ^MediaQuery.pending())) - |> order_by(desc: :id) + |> order_by(desc: fragment("rank"), desc: :id) end defp generate_base_query(source, "downloaded") do MediaQuery.new() + |> MediaQuery.require_assoc(:media_items_search_index) |> where(^dynamic(^MediaQuery.for_source(source) and ^MediaQuery.downloaded())) - |> order_by(desc: :id) + |> order_by(desc: fragment("rank"), desc: :id) end defp generate_base_query(source, "other") do MediaQuery.new() |> MediaQuery.require_assoc(:media_profile) + |> MediaQuery.require_assoc(:media_items_search_index) |> where( ^dynamic( ^MediaQuery.for_source(source) and (not (^MediaQuery.downloaded()) and not (^MediaQuery.pending())) ) ) - |> order_by(desc: :id) + |> order_by(desc: fragment("rank"), desc: :id) + end + + defp filter_base_query(base_query, search_term) do + base_query + |> where(^MediaQuery.matches_search_term(search_term)) end end