f4727bdcbd
Use a combination of ghost and soft, with button groups where appropriate. Also remove unnecessary negative margins, and pad around for ease of use on mobile.
254 lines
8.4 KiB
Elixir
254 lines
8.4 KiB
Elixir
defmodule MusicLibraryWeb.StatsComponents do
|
|
use MusicLibraryWeb, :live_component
|
|
|
|
import MusicLibraryWeb.RecordComponents,
|
|
only: [format_label: 1, type_label: 1, artist_links: 1, record_cover: 1]
|
|
|
|
alias MusicLibrary.Records
|
|
|
|
attr :record, Records.Record, required: true
|
|
attr :title, :string, required: true
|
|
attr :class, :string
|
|
|
|
def album_preview(assigns) do
|
|
~H"""
|
|
<div
|
|
class={[
|
|
"flex items-center rounded-md bg-white dark:bg-zinc-800 px-4 pb-5 pt-5 shadow-sm sm:px-6 sm:pt-6 cursor-pointer",
|
|
@class
|
|
]}
|
|
phx-click={JS.navigate(~p"/collection/#{@record}")}
|
|
>
|
|
<div>
|
|
<.record_cover
|
|
record={@record}
|
|
class="w-16 md:w-20 rounded-md shadow-sm"
|
|
width={160}
|
|
/>
|
|
</div>
|
|
<div class="ml-4">
|
|
<p class="truncate text-xs sm:text-sm font-medium text-zinc-500 dark:text-zinc-400">
|
|
{@title}
|
|
</p>
|
|
<p class="font-semibold">
|
|
<span class="text-sm md:text-base lg:text-2xl block text-zinc-900 dark:text-zinc-300">
|
|
{@record.title}
|
|
</span>
|
|
<.link
|
|
:for={artist <- @record.artists}
|
|
class="text-sm md:text-base text-zinc-600 dark:text-zinc-200 hover:text-zinc-500 dark:text-zinc-300 dark:hover:text-zinc-200"
|
|
navigate={~p"/artists/#{artist.musicbrainz_id}"}
|
|
>
|
|
{artist.name}
|
|
</.link>
|
|
</p>
|
|
</div>
|
|
</div>
|
|
"""
|
|
end
|
|
|
|
attr :title, :string, required: true
|
|
attr :count, :integer, required: true
|
|
attr :path, :string, required: false, default: nil
|
|
attr :tooltip, :any, required: false, default: nil
|
|
|
|
def counter(assigns) do
|
|
~H"""
|
|
<div class="overflow-hidden rounded-md bg-white dark:bg-zinc-800 shadow-sm flex justify-center items-center">
|
|
<div class="p-4 md:p-0">
|
|
<dt>
|
|
<p class="truncate text-sm font-medium text-center text-zinc-500 dark:text-zinc-400">
|
|
{@title}
|
|
</p>
|
|
</dt>
|
|
<dd :if={!@tooltip} class="mt-1">
|
|
<.link
|
|
:if={@path}
|
|
navigate={@path}
|
|
class="block text-2xl sm:text-3xl font-semibold text-center text-zinc-900 hover:text-zinc-500 dark:text-zinc-300 dark:hover:text-zinc-200"
|
|
>
|
|
{@count}
|
|
</.link>
|
|
<p
|
|
:if={!@path}
|
|
class="block cursor-default text-2xl sm:text-3xl font-semibold text-center text-zinc-900 dark:text-zinc-300"
|
|
>
|
|
{@count}
|
|
</p>
|
|
</dd>
|
|
<dd :if={@tooltip} class="mt-1">
|
|
<.tooltip>
|
|
<:content>
|
|
<span id={"#{@title}-counter-tooltip"} phx-hook="FormatNumber">{@tooltip}</span>
|
|
</:content>
|
|
<.link
|
|
:if={@path}
|
|
navigate={@path}
|
|
class="block text-2xl sm:text-3xl font-semibold text-center text-zinc-900 hover:text-zinc-500 dark:text-zinc-300 dark:hover:text-zinc-200"
|
|
>
|
|
{@count}
|
|
</.link>
|
|
<p
|
|
:if={!@path}
|
|
class="block cursor-default text-2xl sm:text-3xl font-semibold text-center text-zinc-900 dark:text-zinc-300"
|
|
>
|
|
{@count}
|
|
</p>
|
|
</.tooltip>
|
|
</dd>
|
|
</div>
|
|
</div>
|
|
"""
|
|
end
|
|
|
|
attr :categories_with_counts, :list, required: true
|
|
attr :category_format_fn, :any, required: true
|
|
attr :category_path_fn, :any, required: true
|
|
|
|
def counters_by_category(assigns) do
|
|
~H"""
|
|
<%!-- TODO: replace with OSS version --%>
|
|
<dl class={[
|
|
"mt-5 grid divide-zinc-200 dark:divide-zinc-900 overflow-hidden rounded-md bg-white dark:bg-zinc-800 shadow-sm divide-x-1",
|
|
stats_class(@categories_with_counts)
|
|
]}>
|
|
<div :for={{category, count} <- @categories_with_counts} class="px-2 py-5 sm:px-4">
|
|
<dt class="text-sm font-medium text-zinc-500 dark:text-zinc-400 text-center max-sm:text-xs break-keep">
|
|
{@category_format_fn.(category)}
|
|
</dt>
|
|
<dd class="mt-1 text-center">
|
|
<.link
|
|
class="text-xl lg:text-2xl font-semibold hover:text-zinc-500 dark:text-zinc-300 dark:hover:text-zinc-200"
|
|
navigate={@category_path_fn.(category)}
|
|
>
|
|
{count}
|
|
</.link>
|
|
</dd>
|
|
</div>
|
|
</dl>
|
|
"""
|
|
end
|
|
|
|
attr :record_show_path, :any, required: true
|
|
attr :records, :list, required: true
|
|
attr :current_date, Date, required: true
|
|
|
|
def records_on_this_day(assigns) do
|
|
~H"""
|
|
<ul
|
|
class="mt-5 p-4"
|
|
role="list"
|
|
id="records"
|
|
phx-update="stream"
|
|
>
|
|
<li
|
|
id="no-records-on-this-day"
|
|
class="hidden py-8 only:flex items-center justify-center text-sm text-zinc-500 dark:text-zinc-400"
|
|
>
|
|
{gettext("No records released on this day.")}
|
|
</li>
|
|
<li
|
|
:for={{id, record} <- @records}
|
|
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-800 px-2 -mx-2 md:px-4 md:-mx-4 cursor-pointer"
|
|
id={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} />
|
|
<span
|
|
:if={Records.Record.included_release_groups_count(record) > 0}
|
|
class={[
|
|
"absolute right-0 bottom-0 rounded-br-lg rounded-tl-lg px-1",
|
|
"text-xs font-medium",
|
|
"bg-zinc-200/80 dark:bg-zinc-500/70",
|
|
"text-zinc-700 dark:text-zinc-200",
|
|
"border-1 border-zinc-600/20 dark:border-zinc-500/20"
|
|
]}
|
|
>
|
|
{Records.Record.included_release_groups_count(record)}
|
|
</span>
|
|
</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>
|
|
</ul>
|
|
"""
|
|
end
|
|
|
|
attr :record, Records.Record, required: true
|
|
attr :current_date, Date, required: true
|
|
|
|
defp released_how_long_ago(assigns) do
|
|
assigns =
|
|
assign(
|
|
assigns,
|
|
:years,
|
|
Records.Record.released_how_long_ago?(assigns.record, assigns.current_date)
|
|
)
|
|
|
|
~H"""
|
|
<span class={[
|
|
"text-xs leading-5",
|
|
normal_year?(@years) && "text-zinc-500 dark:text-zinc-400",
|
|
gold_year?(@years) &&
|
|
"font-semibold bg-gradient-to-r bg-clip-text text-transparent from-yellow-500 via-yellow-200 to-yellow-700 animate-shine",
|
|
silver_year?(@years) &&
|
|
"font-semibold bg-gradient-to-r bg-clip-text text-transparent from-gray-500 via-gray-200 to-gray-700 animate-shine"
|
|
]}>
|
|
{ngettext(
|
|
"1 year ago",
|
|
"%{count} years ago",
|
|
@years
|
|
)}
|
|
</span>
|
|
"""
|
|
end
|
|
|
|
defp gold_year?(year), do: rem(year, 10) == 0
|
|
defp silver_year?(year), do: rem(year, 5) == 0
|
|
defp normal_year?(year), do: !gold_year?(year) && !silver_year?(year)
|
|
|
|
# The Tailwind build step requires all needed classes to be explicitly referenced
|
|
# in the source code, and not dynamically generated. This implies that one cannot
|
|
# (for example) interpolate a number in a class name.
|
|
defp stats_class(collection) do
|
|
case Enum.count(collection) do
|
|
1 -> "grid-cols-1"
|
|
2 -> "grid-cols-2"
|
|
3 -> "grid-cols-3"
|
|
4 -> "grid-cols-4"
|
|
5 -> "grid-cols-5"
|
|
6 -> "grid-cols-6"
|
|
7 -> "grid-cols-7"
|
|
8 -> "grid-cols-8"
|
|
9 -> "grid-cols-9"
|
|
_other -> ""
|
|
end
|
|
end
|
|
end
|