Group records in records on this day widget
This commit is contained in:
@@ -61,6 +61,31 @@ defmodule MusicLibrary.Collection do
|
|||||||
Repo.all(q)
|
Repo.all(q)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@type grouped_record ::
|
||||||
|
{:single, SearchIndex.t()}
|
||||||
|
| {:group, %{representative: SearchIndex.t(), records: [SearchIndex.t()]}}
|
||||||
|
|
||||||
|
@spec group_records_by_release_group([SearchIndex.t()]) :: [grouped_record()]
|
||||||
|
def group_records_by_release_group(records) do
|
||||||
|
records
|
||||||
|
|> Enum.group_by(& &1.musicbrainz_id)
|
||||||
|
|> Enum.map(fn
|
||||||
|
{_mbid, [single]} ->
|
||||||
|
{:single, single}
|
||||||
|
|
||||||
|
{_mbid, [first | _] = group} ->
|
||||||
|
sorted = Enum.sort_by(group, & &1.purchased_at, DateTime)
|
||||||
|
{:group, %{representative: first, records: sorted}}
|
||||||
|
end)
|
||||||
|
|> Enum.sort_by(
|
||||||
|
fn
|
||||||
|
{:single, r} -> r.release_date
|
||||||
|
{:group, %{representative: r}} -> r.release_date
|
||||||
|
end,
|
||||||
|
:desc
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
@spec get_latest_record() :: SearchIndex.t() | nil
|
@spec get_latest_record() :: SearchIndex.t() | nil
|
||||||
def get_latest_record do
|
def get_latest_record do
|
||||||
q =
|
q =
|
||||||
|
|||||||
@@ -163,48 +163,122 @@ defmodule MusicLibraryWeb.StatsComponents do
|
|||||||
>
|
>
|
||||||
{gettext("No records released on this day.")}
|
{gettext("No records released on this day.")}
|
||||||
</li>
|
</li>
|
||||||
<li
|
<%= for entry <- @records do %>
|
||||||
:for={record <- @records}
|
<%= case entry do %>
|
||||||
phx-click={JS.navigate(@record_show_path.(record))}
|
<% {:single, record} -> %>
|
||||||
class="flex justify-between gap-x-6 py-2 hover:bg-zinc-50 dark:hover:bg-zinc-700 px-2 md:px-4 cursor-pointer"
|
<.record_on_this_day_item
|
||||||
id={record.id}
|
record={record}
|
||||||
>
|
current_date={@current_date}
|
||||||
<div class="flex min-w-0 gap-x-4 items-center">
|
record_show_path={@record_show_path}
|
||||||
<div class="relative w-12 flex-none">
|
/>
|
||||||
<.record_cover record={record} width={96} />
|
<% {:group, %{representative: rep, records: records}} -> %>
|
||||||
<.release_groups_badge record={record} />
|
<li id={"group-#{rep.musicbrainz_id}"} class="py-2 px-2 md:px-4">
|
||||||
</div>
|
<details class="group/details">
|
||||||
<div class="min-w-0 flex-auto">
|
<summary class="flex justify-between gap-x-6 cursor-pointer list-none [&::-webkit-details-marker]:hidden hover:bg-zinc-50 dark:hover:bg-zinc-700 rounded-md -mx-2 px-2 py-1">
|
||||||
<h1 class="text-sm leading-6 text-zinc-700">
|
<div class="flex min-w-0 gap-x-4 items-center">
|
||||||
<.artist_links joinphrase_class="text-xs" artists={record.artists} />
|
<div class="relative w-12 flex-none">
|
||||||
</h1>
|
<.record_cover record={rep} width={96} />
|
||||||
<h2 class="mt-1 flex font-semibold text-sm sm:text-base leading-5 text-zinc-700 dark:text-zinc-300 text-wrap">
|
<.release_groups_badge record={rep} />
|
||||||
{record.title}
|
</div>
|
||||||
</h2>
|
<div class="min-w-0 flex-auto">
|
||||||
<p class="mt-1 text-xs leading-5 text-zinc-500 dark:text-zinc-400">
|
<h1 class="text-sm leading-6 text-zinc-700">
|
||||||
<.released_how_long_ago record={record} current_date={@current_date} />
|
<.artist_links joinphrase_class="text-xs" artists={rep.artists} />
|
||||||
· {format_label(record.format)} · {type_label(record.type)}
|
</h1>
|
||||||
<span :if={record.purchased_at}>
|
<h2 class="mt-1 flex font-semibold text-sm sm:text-base leading-5 text-zinc-700 dark:text-zinc-300 text-wrap">
|
||||||
·
|
{rep.title}
|
||||||
<span class="sr-only">
|
</h2>
|
||||||
{gettext("Purchased on")}
|
<p class="mt-1 text-xs leading-5 text-zinc-500 dark:text-zinc-400">
|
||||||
</span>
|
<.released_how_long_ago record={rep} current_date={@current_date} />
|
||||||
<.icon
|
· {ngettext("1 release", "%{count} releases", length(records))}
|
||||||
name="hero-banknotes"
|
</p>
|
||||||
class="h-4 w-4"
|
</div>
|
||||||
aria-hidden="true"
|
</div>
|
||||||
data-slot="icon"
|
<div class="flex items-center">
|
||||||
/>
|
<.icon
|
||||||
{Records.Record.format_as_date(record.purchased_at)}
|
name="hero-chevron-right"
|
||||||
</span>
|
class="h-4 w-4 text-zinc-400 transition-transform group-open/details:rotate-90"
|
||||||
</p>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</summary>
|
||||||
</li>
|
<ul class="ml-16 mt-1 border-l border-zinc-200 dark:border-zinc-700 pl-4">
|
||||||
|
<li
|
||||||
|
:for={record <- records}
|
||||||
|
phx-click={JS.navigate(@record_show_path.(record))}
|
||||||
|
class="flex justify-between gap-x-6 py-1.5 hover:bg-zinc-50 dark:hover:bg-zinc-700 px-2 rounded-md cursor-pointer"
|
||||||
|
id={record.id}
|
||||||
|
>
|
||||||
|
<p class="text-xs leading-5 text-zinc-500 dark:text-zinc-400">
|
||||||
|
{format_label(record.format)} · {type_label(record.type)}
|
||||||
|
<span :if={record.purchased_at}>
|
||||||
|
·
|
||||||
|
<span class="sr-only">
|
||||||
|
{gettext("Purchased on")}
|
||||||
|
</span>
|
||||||
|
<.icon
|
||||||
|
name="hero-banknotes"
|
||||||
|
class="h-4 w-4"
|
||||||
|
aria-hidden="true"
|
||||||
|
data-slot="icon"
|
||||||
|
/>
|
||||||
|
{Records.Record.format_as_date(record.purchased_at)}
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</details>
|
||||||
|
</li>
|
||||||
|
<% end %>
|
||||||
|
<% end %>
|
||||||
</ul>
|
</ul>
|
||||||
"""
|
"""
|
||||||
end
|
end
|
||||||
|
|
||||||
|
attr :record, Records.Record, required: true
|
||||||
|
attr :current_date, Date, required: true
|
||||||
|
attr :record_show_path, :any, required: true
|
||||||
|
|
||||||
|
defp record_on_this_day_item(assigns) do
|
||||||
|
~H"""
|
||||||
|
<li
|
||||||
|
phx-click={JS.navigate(@record_show_path.(@record))}
|
||||||
|
class="flex justify-between gap-x-6 py-2 hover:bg-zinc-50 dark:hover:bg-zinc-700 px-2 md:px-4 cursor-pointer"
|
||||||
|
id={@record.id}
|
||||||
|
>
|
||||||
|
<div class="flex min-w-0 gap-x-4 items-center">
|
||||||
|
<div class="relative w-12 flex-none">
|
||||||
|
<.record_cover record={@record} width={96} />
|
||||||
|
<.release_groups_badge record={@record} />
|
||||||
|
</div>
|
||||||
|
<div class="min-w-0 flex-auto">
|
||||||
|
<h1 class="text-sm leading-6 text-zinc-700">
|
||||||
|
<.artist_links joinphrase_class="text-xs" artists={@record.artists} />
|
||||||
|
</h1>
|
||||||
|
<h2 class="mt-1 flex font-semibold text-sm sm:text-base leading-5 text-zinc-700 dark:text-zinc-300 text-wrap">
|
||||||
|
{@record.title}
|
||||||
|
</h2>
|
||||||
|
<p class="mt-1 text-xs leading-5 text-zinc-500 dark:text-zinc-400">
|
||||||
|
<.released_how_long_ago record={@record} current_date={@current_date} />
|
||||||
|
· {format_label(@record.format)} · {type_label(@record.type)}
|
||||||
|
<span :if={@record.purchased_at}>
|
||||||
|
·
|
||||||
|
<span class="sr-only">
|
||||||
|
{gettext("Purchased on")}
|
||||||
|
</span>
|
||||||
|
<.icon
|
||||||
|
name="hero-banknotes"
|
||||||
|
class="h-4 w-4"
|
||||||
|
aria-hidden="true"
|
||||||
|
data-slot="icon"
|
||||||
|
/>
|
||||||
|
{Records.Record.format_as_date(@record.purchased_at)}
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
"""
|
||||||
|
end
|
||||||
|
|
||||||
attr :record, Records.Record, required: true
|
attr :record, Records.Record, required: true
|
||||||
attr :current_date, Date, required: true
|
attr :current_date, Date, required: true
|
||||||
|
|
||||||
|
|||||||
@@ -348,7 +348,11 @@ defmodule MusicLibraryWeb.StatsLive.Index do
|
|||||||
latest_record = Collection.get_latest_record()
|
latest_record = Collection.get_latest_record()
|
||||||
records_by_artists = Collection.count_records_by_artist(limit: 20)
|
records_by_artists = Collection.count_records_by_artist(limit: 20)
|
||||||
records_by_genre = Collection.count_records_by_genre(limit: 20)
|
records_by_genre = Collection.count_records_by_genre(limit: 20)
|
||||||
records_on_this_day = Collection.get_records_on_this_day(current_date)
|
|
||||||
|
records_on_this_day =
|
||||||
|
current_date
|
||||||
|
|> Collection.get_records_on_this_day()
|
||||||
|
|> Collection.group_records_by_release_group()
|
||||||
|
|
||||||
if connected?(socket) do
|
if connected?(socket) do
|
||||||
LastFm.subscribe_to_feed()
|
LastFm.subscribe_to_feed()
|
||||||
@@ -413,7 +417,10 @@ defmodule MusicLibraryWeb.StatsLive.Index do
|
|||||||
def handle_event("set_current_date", %{"current_date" => current_date}, socket) do
|
def handle_event("set_current_date", %{"current_date" => current_date}, socket) do
|
||||||
case Date.from_iso8601(current_date) do
|
case Date.from_iso8601(current_date) do
|
||||||
{:ok, date} ->
|
{:ok, date} ->
|
||||||
records_on_this_day = Collection.get_records_on_this_day(date)
|
records_on_this_day =
|
||||||
|
date
|
||||||
|
|> Collection.get_records_on_this_day()
|
||||||
|
|> Collection.group_records_by_release_group()
|
||||||
|
|
||||||
{:noreply,
|
{:noreply,
|
||||||
socket
|
socket
|
||||||
|
|||||||
@@ -2284,3 +2284,10 @@ msgstr ""
|
|||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Notes"
|
msgid "Notes"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: lib/music_library_web/components/stats_components.ex
|
||||||
|
#, elixir-autogen, elixir-format
|
||||||
|
msgid "1 release"
|
||||||
|
msgid_plural "%{count} releases"
|
||||||
|
msgstr[0] ""
|
||||||
|
msgstr[1] ""
|
||||||
|
|||||||
@@ -2284,3 +2284,10 @@ msgstr ""
|
|||||||
#, elixir-autogen, elixir-format
|
#, elixir-autogen, elixir-format
|
||||||
msgid "Notes"
|
msgid "Notes"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: lib/music_library_web/components/stats_components.ex
|
||||||
|
#, elixir-autogen, elixir-format, fuzzy
|
||||||
|
msgid "1 release"
|
||||||
|
msgid_plural "%{count} releases"
|
||||||
|
msgstr[0] ""
|
||||||
|
msgstr[1] ""
|
||||||
|
|||||||
@@ -87,6 +87,75 @@ defmodule MusicLibrary.CollectionTest do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "group_records_by_release_group/1" do
|
||||||
|
test "wraps single records in {:single, record} tuples" do
|
||||||
|
record =
|
||||||
|
record_with_artist("Marillion", %{
|
||||||
|
title: "Brave",
|
||||||
|
release_date: "2020",
|
||||||
|
purchased_at: ~U[2024-12-27 16:50:57Z]
|
||||||
|
})
|
||||||
|
|
||||||
|
assert [{:single, ^record}] = Collection.group_records_by_release_group([record])
|
||||||
|
end
|
||||||
|
|
||||||
|
test "groups records sharing the same musicbrainz_id" do
|
||||||
|
shared_mbid = Ecto.UUID.generate()
|
||||||
|
|
||||||
|
cd =
|
||||||
|
record_with_artist("Marillion", %{
|
||||||
|
title: "Brave",
|
||||||
|
musicbrainz_id: shared_mbid,
|
||||||
|
format: :cd,
|
||||||
|
release_date: "2020",
|
||||||
|
purchased_at: ~U[2024-12-27 16:50:57Z]
|
||||||
|
})
|
||||||
|
|
||||||
|
vinyl =
|
||||||
|
record_with_artist("Marillion", %{
|
||||||
|
title: "Brave",
|
||||||
|
musicbrainz_id: shared_mbid,
|
||||||
|
format: :vinyl,
|
||||||
|
release_date: "2020",
|
||||||
|
purchased_at: ~U[2024-12-28 16:50:57Z]
|
||||||
|
})
|
||||||
|
|
||||||
|
result = Collection.group_records_by_release_group([cd, vinyl])
|
||||||
|
|
||||||
|
assert [{:group, %{representative: rep, records: records}}] = result
|
||||||
|
assert rep.id == cd.id
|
||||||
|
assert length(records) == 2
|
||||||
|
# Sorted by purchased_at ascending
|
||||||
|
assert Enum.map(records, & &1.id) == [cd.id, vinyl.id]
|
||||||
|
end
|
||||||
|
|
||||||
|
test "sorts groups by release_date descending" do
|
||||||
|
older =
|
||||||
|
record_with_artist("Marillion", %{
|
||||||
|
title: "Script for a Jester's Tear",
|
||||||
|
release_date: "1983",
|
||||||
|
purchased_at: ~U[2024-12-27 16:50:57Z]
|
||||||
|
})
|
||||||
|
|
||||||
|
newer =
|
||||||
|
record_with_artist("Marillion", %{
|
||||||
|
title: "Brave",
|
||||||
|
release_date: "1994",
|
||||||
|
purchased_at: ~U[2024-12-28 16:50:57Z]
|
||||||
|
})
|
||||||
|
|
||||||
|
result = Collection.group_records_by_release_group([older, newer])
|
||||||
|
|
||||||
|
assert [{:single, first}, {:single, second}] = result
|
||||||
|
assert first.id == newer.id
|
||||||
|
assert second.id == older.id
|
||||||
|
end
|
||||||
|
|
||||||
|
test "returns empty list for empty input" do
|
||||||
|
assert [] == Collection.group_records_by_release_group([])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe "get_latest_record!/0" do
|
describe "get_latest_record!/0" do
|
||||||
setup [:fill_collection]
|
setup [:fill_collection]
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user