defmodule AshHqWeb.Components.Search do
@moduledoc "The search overlay modal"
use Surface.LiveComponent
require Ash.Query
alias AshHqWeb.Components.{CalloutText, Catalogue}
alias AshHqWeb.DocRoutes
alias Phoenix.LiveView.JS
alias Surface.Components.{Form, LivePatch}
alias Surface.Components.Form.Checkbox
prop(close, :event, required: true)
prop(libraries, :list, required: true)
prop(selected_versions, :map, required: true)
prop(selected_types, :list, required: true)
prop(change_types, :event, required: true)
prop(change_versions, :event, required: true)
prop(remove_version, :event, required: true)
prop(uri, :string, required: true)
data(search, :string, default: "")
data(item_list, :list, default: [])
data(selected_item, :string)
data(selecting_packages, :boolean, default: false)
def render(assigns) do
~F"""
{render_items(assigns, @item_list)}
"""
end
defp render_items(assigns, items) do
~F"""
{#for item <- items}
{render_item_type(assigns, item)}
{#for {path_item, index} <- Enum.with_index(item_path(item))}
{#if index != 0}
{/if}
{path_item}
{/for}
{#if Map.get(item, :name_matches)}
{#else}
{item_name(item)}
{/if}
{raw(item.search_headline)}
{/for}
"""
end
defp render_item_type(assigns, item) do
icon_classes = "h-4 w-4 flex-none mt-1 mx-1"
case item_type(item) do
"Forum" ->
~F"""
"""
"Mix Task" ->
~F"""
"""
"Function" ->
case item.type do
type when type in [:function, :macro] ->
~F"""
"""
:callback ->
~F"""
"""
:type ->
~F"""
"""
end
"Module" ->
~F"""
"""
type when type in ["Dsl", "Option"] ->
AshHqWeb.Components.DocSidebar.render_icon(assigns, item.extension_type)
"Guide" ->
~F"""
"""
_ ->
~F"""
"""
end
end
defp toggle_libraries(js \\ %JS{}) do
js
|> JS.toggle(to: "#search-body")
|> JS.toggle(to: "#search-versions")
end
defp item_name(%{thread_name: thread_name, channel_name: channel_name}),
do: "#{String.capitalize(channel_name)} Forum: #{inspect(thread_name)}"
defp item_name(%{name: name}), do: name
defp item_name(%{version: version}), do: version
defp item_path(%{
library_name: library_name,
extension_name: extension_name,
path: path
}) do
[library_name, extension_name, path] |> List.flatten()
end
defp item_path(%{
library_name: library_name,
module_name: module_name
}) do
[library_name, module_name]
end
defp item_path(%{library_name: library_name}) do
[library_name]
end
defp item_path(%{library_version: %{library_name: library_name}}) do
[library_name]
end
defp item_path(_) do
[]
end
def mount(socket) do
{:ok, socket}
end
def update(assigns, socket) do
if assigns[:uri] != socket.assigns[:uri] do
{:ok, socket |> assign(:search, nil) |> assign(assigns) |> search()}
else
{:ok, socket |> assign(assigns) |> search()}
end
end
def handle_event("toggle_versions", _, socket) do
{:noreply, socket |> assign(:selecting_packages, !socket.assigns.selecting_packages)}
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", _data, 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,
socket
|> push_event("click-on-item", %{"id" => "result-#{item.id}"})}
end
end
defp item_type(%resource{}) do
AshHq.Docs.Extensions.Search.item_type(resource)
end
defp search(socket) do
if socket.assigns[:search] in [nil, ""] || socket.assigns[:selected_versions] in [nil, %{}] ||
socket.assigns[:selected_types] == [] do
assign(socket, results: %{}, item_list: [])
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 AshHqWeb.Helpers.latest_version(library) do
nil ->
nil
version ->
version.id
end
end
end)
{_, version_id} ->
version_id
end)
|> Enum.reject(&(&1 == "" || is_nil(&1)))
%{result: item_list} =
AshHq.Docs.Search.run!(
socket.assigns.search,
versions,
%{types: socket.assigns[:selected_types]}
)
selected_item = Enum.at(item_list, 0)
socket
|> 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: "result-#{selected_item.id}"})
end
end