Search scrobble rules, improved layout
This commit is contained in:
@@ -14,6 +14,7 @@ defmodule MusicLibrary.ScrobbleRules do
|
|||||||
@type list_opts :: [
|
@type list_opts :: [
|
||||||
type: atom(),
|
type: atom(),
|
||||||
enabled: boolean(),
|
enabled: boolean(),
|
||||||
|
query: String.t(),
|
||||||
offset: non_neg_integer(),
|
offset: non_neg_integer(),
|
||||||
limit: non_neg_integer()
|
limit: non_neg_integer()
|
||||||
]
|
]
|
||||||
@@ -21,28 +22,8 @@ defmodule MusicLibrary.ScrobbleRules do
|
|||||||
@spec list_scrobble_rules(list_opts()) :: [ScrobbleRule.t()]
|
@spec list_scrobble_rules(list_opts()) :: [ScrobbleRule.t()]
|
||||||
def list_scrobble_rules(opts \\ []) do
|
def list_scrobble_rules(opts \\ []) do
|
||||||
query =
|
query =
|
||||||
from r in ScrobbleRule,
|
from(r in ScrobbleRule, order_by: [desc: r.inserted_at])
|
||||||
order_by: [desc: r.inserted_at]
|
|> filter_scrobble_rules(opts)
|
||||||
|
|
||||||
query =
|
|
||||||
case Keyword.get(opts, :type) do
|
|
||||||
nil ->
|
|
||||||
query
|
|
||||||
|
|
||||||
type ->
|
|
||||||
from r in query,
|
|
||||||
where: r.type == ^type
|
|
||||||
end
|
|
||||||
|
|
||||||
query =
|
|
||||||
case Keyword.get(opts, :enabled) do
|
|
||||||
nil ->
|
|
||||||
query
|
|
||||||
|
|
||||||
enabled ->
|
|
||||||
from r in query,
|
|
||||||
where: r.enabled == ^enabled
|
|
||||||
end
|
|
||||||
|
|
||||||
query =
|
query =
|
||||||
case Keyword.get(opts, :offset) do
|
case Keyword.get(opts, :offset) do
|
||||||
@@ -69,29 +50,9 @@ defmodule MusicLibrary.ScrobbleRules do
|
|||||||
|
|
||||||
@spec count_scrobble_rules(list_opts()) :: non_neg_integer()
|
@spec count_scrobble_rules(list_opts()) :: non_neg_integer()
|
||||||
def count_scrobble_rules(opts \\ []) do
|
def count_scrobble_rules(opts \\ []) do
|
||||||
query = from(r in ScrobbleRule)
|
from(r in ScrobbleRule)
|
||||||
|
|> filter_scrobble_rules(opts)
|
||||||
query =
|
|> Repo.aggregate(:count)
|
||||||
case Keyword.get(opts, :type) do
|
|
||||||
nil ->
|
|
||||||
query
|
|
||||||
|
|
||||||
type ->
|
|
||||||
from r in query,
|
|
||||||
where: r.type == ^type
|
|
||||||
end
|
|
||||||
|
|
||||||
query =
|
|
||||||
case Keyword.get(opts, :enabled) do
|
|
||||||
nil ->
|
|
||||||
query
|
|
||||||
|
|
||||||
enabled ->
|
|
||||||
from r in query,
|
|
||||||
where: r.enabled == ^enabled
|
|
||||||
end
|
|
||||||
|
|
||||||
Repo.aggregate(query, :count)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
@spec get_scrobble_rule!(integer()) :: ScrobbleRule.t()
|
@spec get_scrobble_rule!(integer()) :: ScrobbleRule.t()
|
||||||
@@ -497,6 +458,42 @@ defmodule MusicLibrary.ScrobbleRules do
|
|||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp filter_scrobble_rules(query, opts) do
|
||||||
|
query =
|
||||||
|
case Keyword.get(opts, :type) do
|
||||||
|
nil ->
|
||||||
|
query
|
||||||
|
|
||||||
|
type ->
|
||||||
|
from r in query,
|
||||||
|
where: r.type == ^type
|
||||||
|
end
|
||||||
|
|
||||||
|
query =
|
||||||
|
case Keyword.get(opts, :enabled) do
|
||||||
|
nil ->
|
||||||
|
query
|
||||||
|
|
||||||
|
enabled ->
|
||||||
|
from r in query,
|
||||||
|
where: r.enabled == ^enabled
|
||||||
|
end
|
||||||
|
|
||||||
|
case Keyword.get(opts, :query) do
|
||||||
|
q when q in [nil, ""] ->
|
||||||
|
query
|
||||||
|
|
||||||
|
q ->
|
||||||
|
like = "%#{q}%"
|
||||||
|
|
||||||
|
from r in query,
|
||||||
|
where:
|
||||||
|
like(r.match_value, ^like) or
|
||||||
|
like(r.target_musicbrainz_id, ^like) or
|
||||||
|
like(r.description, ^like)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
# column and json_path are hardcoded string literals from internal callers,
|
# column and json_path are hardcoded string literals from internal callers,
|
||||||
# never user input. All user-derived values use parameterized ? placeholders.
|
# never user input. All user-derived values use parameterized ? placeholders.
|
||||||
# sobelow_skip ["SQL.Query"]
|
# sobelow_skip ["SQL.Query"]
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ defmodule MusicLibraryWeb.ScrobbleRulesLive.Index do
|
|||||||
|
|
||||||
@default_list_params %{
|
@default_list_params %{
|
||||||
page: 1,
|
page: 1,
|
||||||
page_size: 20,
|
page_size: 50,
|
||||||
query: "",
|
query: "",
|
||||||
order: :inserted_at
|
order: :inserted_at
|
||||||
}
|
}
|
||||||
@@ -48,6 +48,8 @@ defmodule MusicLibraryWeb.ScrobbleRulesLive.Index do
|
|||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
|
<.search_form query={@list_params.query} />
|
||||||
|
|
||||||
<div class="mt-6 space-y-4">
|
<div class="mt-6 space-y-4">
|
||||||
<ul phx-update="stream" id="scrobble-rules-list" class="space-y-4">
|
<ul phx-update="stream" id="scrobble-rules-list" class="space-y-4">
|
||||||
<li
|
<li
|
||||||
@@ -63,31 +65,32 @@ defmodule MusicLibraryWeb.ScrobbleRulesLive.Index do
|
|||||||
<li
|
<li
|
||||||
:for={{dom_id, scrobble_rule} <- @streams.scrobble_rules}
|
:for={{dom_id, scrobble_rule} <- @streams.scrobble_rules}
|
||||||
id={dom_id}
|
id={dom_id}
|
||||||
class="flex justify-between"
|
class="flex items-center gap-2"
|
||||||
>
|
>
|
||||||
<div class="flex items-center">
|
<div class="grow min-w-0">
|
||||||
<.type_badge type={scrobble_rule.type} />
|
<div class="lg:flex lg:items-center lg:gap-2">
|
||||||
</div>
|
<p class="text-sm text-zinc-900 dark:text-zinc-100">
|
||||||
<div class="grow p-2">
|
{scrobble_rule.match_value}
|
||||||
<p class="text-sm text-zinc-900 dark:text-zinc-100">
|
</p>
|
||||||
{scrobble_rule.match_value}
|
<p class="text-xs font-mono text-zinc-500 dark:text-zinc-400 mt-1 lg:mt-0 truncate">
|
||||||
</p>
|
{scrobble_rule.target_musicbrainz_id}
|
||||||
<p class="text-xs font-mono text-zinc-500 dark:text-zinc-400 mt-2">
|
</p>
|
||||||
{scrobble_rule.target_musicbrainz_id}
|
</div>
|
||||||
</p>
|
<p
|
||||||
<p class="text-xs text-zinc-500 dark:text-zinc-400 mt-2">
|
:if={scrobble_rule.description}
|
||||||
|
class="text-xs text-zinc-500 dark:text-zinc-400 mt-1 truncate"
|
||||||
|
>
|
||||||
{scrobble_rule.description}
|
{scrobble_rule.description}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col justify-center text-right">
|
<div class="flex flex-col lg:flex-row items-end lg:items-center gap-1 lg:gap-2 shrink-0">
|
||||||
<span>
|
<.type_badge type={scrobble_rule.type} />
|
||||||
<.status_badge enabled={scrobble_rule.enabled} />
|
<.status_badge enabled={scrobble_rule.enabled} />
|
||||||
</span>
|
<span class="text-xs text-zinc-500 dark:text-zinc-400 text-nowrap">
|
||||||
<span class="text-xs text-zinc-500 dark:text-zinc-400 text-nowrap mt-2">
|
|
||||||
{Calendar.strftime(scrobble_rule.inserted_at, "%Y-%m-%d")}
|
{Calendar.strftime(scrobble_rule.inserted_at, "%Y-%m-%d")}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center p-2">
|
<div class="flex items-center shrink-0">
|
||||||
<.dropdown id={"actions-#{scrobble_rule.id}"} placement="bottom-end">
|
<.dropdown id={"actions-#{scrobble_rule.id}"} placement="bottom-end">
|
||||||
<:toggle>
|
<:toggle>
|
||||||
<.button variant="ghost">
|
<.button variant="ghost">
|
||||||
@@ -109,7 +112,7 @@ defmodule MusicLibraryWeb.ScrobbleRulesLive.Index do
|
|||||||
<.dropdown_link
|
<.dropdown_link
|
||||||
id={"actions-#{scrobble_rule.id}-edit"}
|
id={"actions-#{scrobble_rule.id}-edit"}
|
||||||
patch={
|
patch={
|
||||||
~p"/scrobble-rules/#{scrobble_rule}/edit?#{Map.take(@list_params, [:page, :page_size])}"
|
~p"/scrobble-rules/#{scrobble_rule}/edit?#{@list_params |> Map.take([:page, :page_size, :query]) |> Enum.filter(fn {_, v} -> v not in ["", nil] end)}"
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{gettext("Edit")}
|
{gettext("Edit")}
|
||||||
@@ -176,10 +179,13 @@ defmodule MusicLibraryWeb.ScrobbleRulesLive.Index do
|
|||||||
end
|
end
|
||||||
|
|
||||||
defp apply_action(socket, :index, params) do
|
defp apply_action(socket, :index, params) do
|
||||||
total_rules = ScrobbleRules.count_scrobble_rules()
|
query = params["query"]
|
||||||
|
|
||||||
|
total_rules = ScrobbleRules.count_scrobble_rules(query: query)
|
||||||
|
|
||||||
list_params =
|
list_params =
|
||||||
@default_list_params
|
@default_list_params
|
||||||
|
|> merge_query(query)
|
||||||
|> merge_pagination(params, total_rules)
|
|> merge_pagination(params, total_rules)
|
||||||
|
|
||||||
load_and_assign_rules(socket, list_params)
|
load_and_assign_rules(socket, list_params)
|
||||||
@@ -190,6 +196,7 @@ defmodule MusicLibraryWeb.ScrobbleRulesLive.Index do
|
|||||||
|
|
||||||
rules =
|
rules =
|
||||||
ScrobbleRules.list_scrobble_rules(
|
ScrobbleRules.list_scrobble_rules(
|
||||||
|
query: list_params.query,
|
||||||
offset: offset,
|
offset: offset,
|
||||||
limit: list_params.page_size
|
limit: list_params.page_size
|
||||||
)
|
)
|
||||||
@@ -204,7 +211,8 @@ defmodule MusicLibraryWeb.ScrobbleRulesLive.Index do
|
|||||||
def back_path(list_params) do
|
def back_path(list_params) do
|
||||||
qs =
|
qs =
|
||||||
list_params
|
list_params
|
||||||
|> Map.take([:page, :page_size])
|
|> Map.take([:page, :page_size, :query])
|
||||||
|
|> Enum.filter(fn {_, v} -> v not in ["", nil] end)
|
||||||
|
|
||||||
~p"/scrobble-rules?#{qs}"
|
~p"/scrobble-rules?#{qs}"
|
||||||
end
|
end
|
||||||
@@ -283,6 +291,15 @@ defmodule MusicLibraryWeb.ScrobbleRulesLive.Index do
|
|||||||
{:noreply, put_toast(socket, :info, message)}
|
{:noreply, put_toast(socket, :info, message)}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def handle_event("search", %{"query" => query}, socket) do
|
||||||
|
qs =
|
||||||
|
@default_list_params
|
||||||
|
|> Map.put(:query, query)
|
||||||
|
|> Map.take([:query, :page, :page_size])
|
||||||
|
|
||||||
|
{:noreply, push_patch(socket, to: ~p"/scrobble-rules?#{qs}")}
|
||||||
|
end
|
||||||
|
|
||||||
attr :type, :atom, required: true, values: [:album, :artist]
|
attr :type, :atom, required: true, values: [:album, :artist]
|
||||||
|
|
||||||
defp type_badge(assigns) do
|
defp type_badge(assigns) do
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ defmodule MusicLibraryWeb.ScrobbleRulesLiveTest do
|
|||||||
|> render_click() =~
|
|> render_click() =~
|
||||||
"Edit Scrobble Rule"
|
"Edit Scrobble Rule"
|
||||||
|
|
||||||
assert_patch(index_live, ~p"/scrobble-rules/#{scrobble_rule}/edit?page=1&page_size=20")
|
assert_patch(index_live, ~p"/scrobble-rules/#{scrobble_rule}/edit?page=1&page_size=50")
|
||||||
|
|
||||||
assert index_live
|
assert index_live
|
||||||
|> form("#scrobble_rule-form", scrobble_rule: @invalid_attrs)
|
|> form("#scrobble_rule-form", scrobble_rule: @invalid_attrs)
|
||||||
|
|||||||
Reference in New Issue
Block a user