Enable quick actions from universal search
This commit is contained in:
@@ -168,7 +168,7 @@ defmodule MusicLibraryWeb.SearchComponents do
|
|||||||
"""
|
"""
|
||||||
attr :label, :string, required: true
|
attr :label, :string, required: true
|
||||||
attr :icon, :string, required: true
|
attr :icon, :string, required: true
|
||||||
attr :rest, :global, include: ~w(phx-click phx-value-path)
|
attr :rest, :global, include: ~w(phx-click phx-value-path phx-target)
|
||||||
|
|
||||||
def search_result_navigation(assigns) do
|
def search_result_navigation(assigns) do
|
||||||
~H"""
|
~H"""
|
||||||
@@ -273,18 +273,22 @@ defmodule MusicLibraryWeb.SearchComponents do
|
|||||||
<.results_footer />
|
<.results_footer />
|
||||||
"""
|
"""
|
||||||
attr :total_results, :integer, default: 0
|
attr :total_results, :integer, default: 0
|
||||||
|
attr :has_navigable_items, :boolean, default: false
|
||||||
|
|
||||||
def results_footer(assigns) do
|
def results_footer(assigns) do
|
||||||
|
assigns =
|
||||||
|
assign(assigns, :show_nav_hints, assigns.total_results > 0 or assigns.has_navigable_items)
|
||||||
|
|
||||||
~H"""
|
~H"""
|
||||||
<div class="rounded-b-lg border-t border-zinc-200 bg-zinc-50 p-2 dark:border-zinc-700 dark:bg-zinc-900">
|
<div class="rounded-b-lg border-t border-zinc-200 bg-zinc-50 p-2 dark:border-zinc-700 dark:bg-zinc-900">
|
||||||
<div class="flex items-center justify-between text-xs text-zinc-500 dark:text-zinc-400">
|
<div class="flex items-center justify-between text-xs text-zinc-500 dark:text-zinc-400">
|
||||||
<div class="flex items-center space-x-4">
|
<div class="flex items-center space-x-4">
|
||||||
<div :if={@total_results > 0} class="flex items-center gap-1">
|
<div :if={@show_nav_hints} class="flex items-center gap-1">
|
||||||
<kbd class="rounded bg-zinc-200 px-2 py-1 dark:bg-zinc-700">↑</kbd>
|
<kbd class="rounded bg-zinc-200 px-2 py-1 dark:bg-zinc-700">↑</kbd>
|
||||||
<kbd class="rounded bg-zinc-200 px-2 py-1 dark:bg-zinc-700">↓</kbd>
|
<kbd class="rounded bg-zinc-200 px-2 py-1 dark:bg-zinc-700">↓</kbd>
|
||||||
<span>{gettext("Navigate")}</span>
|
<span>{gettext("Navigate")}</span>
|
||||||
</div>
|
</div>
|
||||||
<div :if={@total_results > 0} class="flex items-center gap-1">
|
<div :if={@show_nav_hints} class="flex items-center gap-1">
|
||||||
<kbd class="rounded bg-zinc-200 px-2 py-1 dark:bg-zinc-700">Enter</kbd>
|
<kbd class="rounded bg-zinc-200 px-2 py-1 dark:bg-zinc-700">Enter</kbd>
|
||||||
<span>{gettext("Select")}</span>
|
<span>{gettext("Select")}</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -328,21 +332,39 @@ defmodule MusicLibraryWeb.SearchComponents do
|
|||||||
<.no_results query="radiohead" />
|
<.no_results query="radiohead" />
|
||||||
"""
|
"""
|
||||||
attr :query, :string, required: true
|
attr :query, :string, required: true
|
||||||
|
attr :target, :any, required: true
|
||||||
|
|
||||||
def no_results(assigns) do
|
def no_results(assigns) do
|
||||||
~H"""
|
~H"""
|
||||||
<div class="p-8 text-center">
|
<div class="p-4 text-center">
|
||||||
<.icon name="hero-face-frown" class="mx-auto mb-4 size-12 text-zinc-400" />
|
<.icon name="hero-face-frown" class="mx-auto mb-4 size-12 text-zinc-400" />
|
||||||
<p class="text-zinc-600 dark:text-zinc-400">
|
<p class="text-zinc-600 dark:text-zinc-400">
|
||||||
{gettext("No results found for '%{query}'", query: @query)}
|
{gettext("No results found for '%{query}'", query: @query)}
|
||||||
</p>
|
</p>
|
||||||
<.link
|
|
||||||
class="truncate text-sm font-medium text-zinc-900 dark:text-zinc-100"
|
|
||||||
navigate={~p"/wishlist/import?#{[import_query: @query]}"}
|
|
||||||
>
|
|
||||||
{gettext("Add a record instead", query: @query)}
|
|
||||||
</.link>
|
|
||||||
</div>
|
</div>
|
||||||
|
<.search_result_group title={gettext("Quick actions")} count={3}>
|
||||||
|
<.search_result_navigation
|
||||||
|
label={gettext("Add to wishlist")}
|
||||||
|
icon="hero-star"
|
||||||
|
phx-click="navigate_to_link"
|
||||||
|
phx-value-path={~p"/wishlist/import?#{[import_query: @query]}"}
|
||||||
|
phx-target={@target}
|
||||||
|
/>
|
||||||
|
<.search_result_navigation
|
||||||
|
label={gettext("Add to collection")}
|
||||||
|
icon="hero-plus-circle"
|
||||||
|
phx-click="navigate_to_link"
|
||||||
|
phx-value-path={~p"/collection/import?#{[import_query: @query]}"}
|
||||||
|
phx-target={@target}
|
||||||
|
/>
|
||||||
|
<.search_result_navigation
|
||||||
|
label={gettext("Search to scrobble")}
|
||||||
|
icon="hero-play"
|
||||||
|
phx-click="navigate_to_link"
|
||||||
|
phx-value-path={~p"/scrobble?#{[query: @query]}"}
|
||||||
|
phx-target={@target}
|
||||||
|
/>
|
||||||
|
</.search_result_group>
|
||||||
"""
|
"""
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -169,7 +169,7 @@ defmodule MusicLibraryWeb.CollectionLive.Index do
|
|||||||
action={@live_action}
|
action={@live_action}
|
||||||
record={@record}
|
record={@record}
|
||||||
patch={back_path(@record_list_params)}
|
patch={back_path(@record_list_params)}
|
||||||
initial_query=""
|
initial_query={@import_query}
|
||||||
icon_name="hero-plus"
|
icon_name="hero-plus"
|
||||||
/>
|
/>
|
||||||
</.structured_modal>
|
</.structured_modal>
|
||||||
@@ -198,6 +198,7 @@ defmodule MusicLibraryWeb.CollectionLive.Index do
|
|||||||
{:ok,
|
{:ok,
|
||||||
socket
|
socket
|
||||||
|> assign(:current_section, :collection)
|
|> assign(:current_section, :collection)
|
||||||
|
|> assign(:import_query, "")
|
||||||
|> assign(:display, :grid)}
|
|> assign(:display, :grid)}
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -207,9 +208,12 @@ defmodule MusicLibraryWeb.CollectionLive.Index do
|
|||||||
end
|
end
|
||||||
|
|
||||||
defp apply_action(socket, :import, params) do
|
defp apply_action(socket, :import, params) do
|
||||||
|
import_query = params["import_query"] || ""
|
||||||
|
|
||||||
socket
|
socket
|
||||||
|> apply_fallback_index(params, :records, &apply_action/3)
|
|> apply_fallback_index(params, :records, &apply_action/3)
|
||||||
|> assign(:page_title, gettext("Add new Record · Collection"))
|
|> assign(:page_title, gettext("Add new Record · Collection"))
|
||||||
|
|> assign(:import_query, import_query)
|
||||||
|> assign(:record, nil)
|
|> assign(:record, nil)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -168,9 +168,20 @@ defmodule MusicLibraryWeb.ScrobbleLive.Index do
|
|||||||
{:noreply, apply_action(socket, socket.assigns.live_action, params)}
|
{:noreply, apply_action(socket, socket.assigns.live_action, params)}
|
||||||
end
|
end
|
||||||
|
|
||||||
defp apply_action(socket, :index, _params) do
|
defp apply_action(socket, :index, params) do
|
||||||
socket
|
query = params["query"] || ""
|
||||||
|> assign(:page_title, gettext("Scrobble"))
|
|
||||||
|
socket =
|
||||||
|
socket
|
||||||
|
|> assign(:page_title, gettext("Scrobble"))
|
||||||
|
|> assign(:search_query, query)
|
||||||
|
|
||||||
|
if String.trim(query) != "" do
|
||||||
|
send(self(), {:perform_search, query})
|
||||||
|
assign(socket, :loading, true)
|
||||||
|
else
|
||||||
|
socket
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
|
|||||||
@@ -36,7 +36,11 @@ defmodule MusicLibraryWeb.UniversalSearchLive.Index do
|
|||||||
|
|
||||||
<.empty_state :if={@search_query == ""} />
|
<.empty_state :if={@search_query == ""} />
|
||||||
|
|
||||||
<.no_results :if={@search_query != "" and @total_results == 0} query={@search_query} />
|
<.no_results
|
||||||
|
:if={@search_query != "" and @total_results == 0}
|
||||||
|
query={@search_query}
|
||||||
|
target={@myself}
|
||||||
|
/>
|
||||||
|
|
||||||
<div :if={@total_results > 0} class="mt-4 max-h-148 overflow-y-auto md:max-h-164">
|
<div :if={@total_results > 0} class="mt-4 max-h-148 overflow-y-auto md:max-h-164">
|
||||||
<.search_result_group
|
<.search_result_group
|
||||||
@@ -145,7 +149,10 @@ defmodule MusicLibraryWeb.UniversalSearchLive.Index do
|
|||||||
/>
|
/>
|
||||||
</:actions>
|
</:actions>
|
||||||
</.search_result_group>
|
</.search_result_group>
|
||||||
<.results_footer total_results={@total_results} />
|
<.results_footer
|
||||||
|
total_results={@total_results}
|
||||||
|
has_navigable_items={@search_query != ""}
|
||||||
|
/>
|
||||||
</.structured_modal>
|
</.structured_modal>
|
||||||
</div>
|
</div>
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -1327,11 +1327,6 @@ msgstr ""
|
|||||||
msgid "Scrobble Anything"
|
msgid "Scrobble Anything"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/music_library_web/components/search_components.ex
|
|
||||||
#, elixir-autogen, elixir-format
|
|
||||||
msgid "Add a record instead"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: lib/music_library_web/components/stats_components.ex
|
#: lib/music_library_web/components/stats_components.ex
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Today"
|
msgid "Today"
|
||||||
@@ -2345,3 +2340,23 @@ msgstr ""
|
|||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Re-connect to Last.fm"
|
msgid "Re-connect to Last.fm"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: lib/music_library_web/components/search_components.ex
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgid "Add to collection"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: lib/music_library_web/components/search_components.ex
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgid "Add to wishlist"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: lib/music_library_web/components/search_components.ex
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgid "Quick actions"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: lib/music_library_web/components/search_components.ex
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgid "Search to scrobble"
|
||||||
|
msgstr ""
|
||||||
|
|||||||
@@ -1327,11 +1327,6 @@ msgstr ""
|
|||||||
msgid "Scrobble Anything"
|
msgid "Scrobble Anything"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: lib/music_library_web/components/search_components.ex
|
|
||||||
#, elixir-autogen, elixir-format
|
|
||||||
msgid "Add a record instead"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: lib/music_library_web/components/stats_components.ex
|
#: lib/music_library_web/components/stats_components.ex
|
||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Today"
|
msgid "Today"
|
||||||
@@ -2345,3 +2340,23 @@ msgstr ""
|
|||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Re-connect to Last.fm"
|
msgid "Re-connect to Last.fm"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: lib/music_library_web/components/search_components.ex
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgid "Add to collection"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: lib/music_library_web/components/search_components.ex
|
||||||
|
#, elixir-autogen, elixir-format, fuzzy
|
||||||
|
msgid "Add to wishlist"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: lib/music_library_web/components/search_components.ex
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgid "Quick actions"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: lib/music_library_web/components/search_components.ex
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgid "Search to scrobble"
|
||||||
|
msgstr ""
|
||||||
|
|||||||
@@ -253,6 +253,16 @@ defmodule MusicLibraryWeb.CollectionLive.IndexTest do
|
|||||||
|> assert_path(~p"/collection/import")
|
|> assert_path(~p"/collection/import")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "pre-fills the search query from import_query param", %{conn: conn} do
|
||||||
|
Req.Test.stub(MusicBrainz.API, fn conn ->
|
||||||
|
Req.Test.json(conn, %{"release-groups" => [], "count" => 0})
|
||||||
|
end)
|
||||||
|
|
||||||
|
conn
|
||||||
|
|> visit(~p"/collection/import?#{[import_query: "test query"]}")
|
||||||
|
|> assert_has("input[value='test query']")
|
||||||
|
end
|
||||||
|
|
||||||
test "imports a record when selected", %{conn: conn} do
|
test "imports a record when selected", %{conn: conn} do
|
||||||
release_group_search_results = Map.get(release_group_search_results(), "release-groups")
|
release_group_search_results = Map.get(release_group_search_results(), "release-groups")
|
||||||
|
|
||||||
|
|||||||
@@ -51,6 +51,19 @@ defmodule MusicLibraryWeb.ScrobbleLive.IndexTest do
|
|||||||
|> assert_has("a", "Connect your Last.fm account")
|
|> assert_has("a", "Connect your Last.fm account")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "pre-fills search and loads results from query param", %{conn: conn} do
|
||||||
|
session = visit(conn, ~p"/scrobble?#{[query: "marbles"]}")
|
||||||
|
|
||||||
|
session
|
||||||
|
|> unwrap(fn view ->
|
||||||
|
# Process the {:perform_search, query} message
|
||||||
|
render(view)
|
||||||
|
end)
|
||||||
|
|> assert_has("input[value='marbles']")
|
||||||
|
|> assert_has("h3", "Release Groups")
|
||||||
|
|> assert_has("p", "Marbles")
|
||||||
|
end
|
||||||
|
|
||||||
test "search with results shows release groups", %{conn: conn} do
|
test "search with results shows release groups", %{conn: conn} do
|
||||||
session = visit(conn, ~p"/scrobble")
|
session = visit(conn, ~p"/scrobble")
|
||||||
|
|
||||||
|
|||||||
@@ -43,13 +43,20 @@ defmodule MusicLibraryWeb.UniversalSearchLive.IndexTest do
|
|||||||
end
|
end
|
||||||
|
|
||||||
describe "Search functionality" do
|
describe "Search functionality" do
|
||||||
test "shows no results message when query has no matches", %{conn: conn} do
|
test "shows no results message with quick action links when query has no matches", %{
|
||||||
|
conn: conn
|
||||||
|
} do
|
||||||
conn
|
conn
|
||||||
|> visit(~p"/collection")
|
|> visit(~p"/collection")
|
||||||
|> click_button("Search (Cmd/Ctrl+K)")
|
|> click_button("Search (Cmd/Ctrl+K)")
|
||||||
|> fill_in("Universal Search", with: "nonexistent query xyz")
|
|> fill_in("Universal Search", with: "nonexistent query xyz")
|
||||||
|> assert_has("p", "No results found for 'nonexistent query xyz'")
|
|> assert_has("p", "No results found for 'nonexistent query xyz'")
|
||||||
|> assert_has("a", "Add a record instead")
|
|> assert_has("[role='option']", "Add to wishlist")
|
||||||
|
|> assert_has("[role='option']", "Add to collection")
|
||||||
|
|> assert_has("[role='option']", "Search to scrobble")
|
||||||
|
|> assert_has("kbd", "↑")
|
||||||
|
|> assert_has("kbd", "↓")
|
||||||
|
|> assert_has("span", "Navigate")
|
||||||
end
|
end
|
||||||
|
|
||||||
test "resets results when query is cleared", %{conn: conn} do
|
test "resets results when query is cleared", %{conn: conn} do
|
||||||
|
|||||||
Reference in New Issue
Block a user