defmodule AshHqWeb.Components.Search do
use Surface.LiveComponent
require Ash.Query
alias AshHqWeb.Routes
alias Surface.Components.{Form, LiveRedirect}
alias Surface.Components.Form.{Checkbox, Label, Select}
prop open, :boolean, default: false
prop close, :event, required: true
prop libraries, :list, required: true
prop selected_versions, :map, required: true
prop change_versions, :event, required: true
prop selected_types, :list, required: true
prop change_types, :event, required: true
prop uri, :string, required: true
data versions, :map, default: %{}
data search, :string, default: ""
data results, :map, default: %{}
data selected_item, :string
def render(assigns) do
~F"""
{render_groups(assigns, @results)}
"""
end
defp render_groups(assigns, results, first? \\ true) do
~F"""
{#for {group, results} <- results}
{#if first?}
{group}
{/if}
{#if Enum.empty?(results.items)}
{render_results(assigns, results)}
{#else}
{render_results(assigns, results)}
{/if}
{/for}
"""
end
defp render_results(assigns, results) do
~F"""
{#if Map.get(results, :path, []) != []}
{#for path_item <- Map.get(results, :path, [])}
{path_item}
{/for}
{/if}
{#for item <- results.items}
{#if item.__struct__ != AshHq.Docs.LibraryVersion &&
item.name != List.last(Map.get(results, :path, []))}
{item.name}
{/if}
{#if item.__struct__ == AshHq.Docs.LibraryVersion}
{item.version}
{/if}
{raw(item.search_headline)}
{/for}
{render_groups(assigns, results.further, false)}
"""
end
def mount(socket) do
{:ok, socket}
end
def update(assigns, socket) do
{:ok, socket |> assign(assigns) |> search()}
end
def handle_event("search", %{"search" => search}, socket) do
{:noreply, socket |> assign(:search, search) |> search()}
end
def handle_event("select-next", _, socket) do
if socket.assigns[:selected_item] && socket.assigns[:item_list] do
next =
socket.assigns.item_list
|> Enum.drop_while(&(&1.id != socket.assigns.selected_item.id))
|> Enum.at(1)
{:noreply, set_selected_item(socket, next)}
else
{:noreply, socket}
end
end
def handle_event("select-previous", _, socket) do
if socket.assigns[:selected_item] && socket.assigns[:item_list] do
next =
socket.assigns.item_list
|> Enum.reverse()
|> Enum.drop_while(&(&1.id != socket.assigns.selected_item.id))
|> Enum.at(1)
{:noreply, set_selected_item(socket, next)}
else
{:noreply, socket}
end
end
def handle_event("go-to-doc", _, socket) do
case Enum.find(socket.assigns.item_list, fn item ->
item.id == socket.assigns.selected_item.id
end) do
nil ->
{:noreply, socket}
item ->
{:noreply, push_redirect(socket, to: Routes.doc_link(item))}
end
end
defp search(socket) do
if socket.assigns[:search] in [nil, ""] || socket.assigns[:selected_versions] in [nil, %{}] do
assign(socket, :results, %{})
else
versions =
Enum.map(socket.assigns.selected_versions, fn
{library_id, "latest"} ->
Enum.find_value(socket.assigns.libraries, fn library ->
if library.id == library_id do
case Enum.find(library.versions, &String.contains?(&1.version, ".")) ||
Enum.at(library.versions, 0) do
nil ->
nil
version ->
version.id
end
end
end)
{_, version_id} ->
version_id
end)
|> Enum.reject(&is_nil/1)
%{results: results, item_list: item_list} =
AshHq.Docs.Search.run!(
socket.assigns.search,
versions
)
selected_item = Enum.at(item_list, 0)
socket
|> assign(:results, results)
|> assign(:item_list, item_list)
|> set_selected_item(selected_item)
end
end
defp set_selected_item(socket, nil), do: socket
defp set_selected_item(socket, selected_item) do
socket
|> assign(:selected_item, selected_item)
|> push_event("js:scroll-to", %{id: selected_item.id, boundary_id: socket.assigns[:id]})
end
end