diff --git a/lib/pinchflat_web/components/custom_components/table_components.ex b/lib/pinchflat_web/components/custom_components/table_components.ex index 166a313..464de2e 100644 --- a/lib/pinchflat_web/components/custom_components/table_components.ex +++ b/lib/pinchflat_web/components/custom_components/table_components.ex @@ -2,7 +2,8 @@ defmodule PinchflatWeb.CustomComponents.TableComponents do @moduledoc false use Phoenix.Component - alias PinchflatWeb.CoreComponents + import PinchflatWeb.CoreComponents + import PinchflatWeb.CustomComponents.TextComponents @doc """ Renders a table component with the given rows and columns. @@ -76,11 +77,13 @@ defmodule PinchflatWeb.CustomComponents.TableComponents do phx-click={@page_number != 1 && "page_change"} phx-value-direction="dec" > - + <.icon name="hero-chevron-left" />
  • - Page <%= @page_number %> of <%= @total_pages %> + + Page <.localized_number number={@page_number} /> of <.localized_number number={@total_pages} /> +
  • - + <.icon name="hero-chevron-right" />
  • diff --git a/lib/pinchflat_web/components/custom_components/text_components.ex b/lib/pinchflat_web/components/custom_components/text_components.ex index cba51ee..e5cc4eb 100644 --- a/lib/pinchflat_web/components/custom_components/text_components.ex +++ b/lib/pinchflat_web/components/custom_components/text_components.ex @@ -94,4 +94,28 @@ defmodule PinchflatWeb.CustomComponents.TextComponents do """ end + + @doc """ + Renders a localized number using the Intl.NumberFormat API, falling back to the raw number if needed + """ + attr :number, :any, required: true + + def localized_number(assigns) do + ~H""" + <%= @number %> + """ + end + + @doc """ + Renders a word with a suffix if the count is not 1 + """ + attr :word, :string, required: true + attr :count, :integer, required: true + attr :suffix, :string, default: "s" + + def pluralize(assigns) do + ~H""" + <%= @word %><%= if @count == 1, do: "", else: @suffix %> + """ + end end diff --git a/lib/pinchflat_web/controllers/pages/page_html.ex b/lib/pinchflat_web/controllers/pages/page_html.ex index bdd0b10..c96a6c9 100644 --- a/lib/pinchflat_web/controllers/pages/page_html.ex +++ b/lib/pinchflat_web/controllers/pages/page_html.ex @@ -5,9 +5,19 @@ defmodule PinchflatWeb.Pages.PageHTML do embed_templates "page_html/*" - def readable_media_filesize(media_filesize) do - {num, suffix} = NumberUtils.human_byte_size(media_filesize, precision: 1) + attr :media_filesize, :integer, required: true - "#{Float.round(num)} #{suffix}" + def readable_media_filesize(assigns) do + {num, suffix} = NumberUtils.human_byte_size(assigns.media_filesize, precision: 2) + + assigns = + Map.merge(assigns, %{ + num: num, + suffix: suffix + }) + + ~H""" + <.localized_number number={@num} /> <%= @suffix %> + """ end end diff --git a/lib/pinchflat_web/controllers/pages/page_html/history_table_live.ex b/lib/pinchflat_web/controllers/pages/page_html/history_table_live.ex index 97d570a..13919ab 100644 --- a/lib/pinchflat_web/controllers/pages/page_html/history_table_live.ex +++ b/lib/pinchflat_web/controllers/pages/page_html/history_table_live.ex @@ -22,7 +22,9 @@ defmodule Pinchflat.Pages.HistoryTableLive do
    <.icon_button icon_name="hero-arrow-path" class="h-10 w-10" phx-click="reload_page" tooltip="Refresh" /> - Showing <%= length(@records) %> of <%= @total_record_count %> + + Showing <.localized_number number={length(@records)} /> of <.localized_number number={@total_record_count} /> +
    <.table rows={@records} table_class="text-white"> diff --git a/lib/pinchflat_web/controllers/pages/page_html/home.html.heex b/lib/pinchflat_web/controllers/pages/page_html/home.html.heex index ea5b661..34a99cf 100644 --- a/lib/pinchflat_web/controllers/pages/page_html/home.html.heex +++ b/lib/pinchflat_web/controllers/pages/page_html/home.html.heex @@ -1,33 +1,39 @@
    -
    - - Media Profile(s) + -
    - - Source(s) + -
    - - Downloaded Media +
    + + + <.pluralize count={@media_item_count} word="Download" /> +

    - <%= @media_item_count %> + <.localized_number number={@media_item_count} />

    -
    - +
    + Library Size

    - <%= readable_media_filesize(@media_item_size) %> + <.readable_media_filesize media_filesize={@media_item_size} />

    diff --git a/lib/pinchflat_web/controllers/sources/source_html/index.html.heex b/lib/pinchflat_web/controllers/sources/source_html/index.html.heex index a064d53..5913d30 100644 --- a/lib/pinchflat_web/controllers/sources/source_html/index.html.heex +++ b/lib/pinchflat_web/controllers/sources/source_html/index.html.heex @@ -19,11 +19,12 @@ <:col :let={source} label="Type"><%= source.collection_type %> - <:col :let={source} label="Pending"><%= source.pending_count %> - <:col :let={source} label="Downloaded"><%= source.downloaded_count %> + <:col :let={source} label="Pending"><.localized_number number={source.pending_count} /> + <:col :let={source} label="Downloaded"><.localized_number number={source.downloaded_count} /> <:col :let={source} label="Retention"> <%= if source.retention_period_days && source.retention_period_days > 0 do %> - <%= source.retention_period_days %> day(s) + <.localized_number number={source.retention_period_days} /> + <.pluralize count={source.retention_period_days} word="day" /> <% else %> <% end %> diff --git a/lib/pinchflat_web/controllers/sources/source_html/media_item_table_live.ex b/lib/pinchflat_web/controllers/sources/source_html/media_item_table_live.ex index 1407e8c..fd14a3a 100644 --- a/lib/pinchflat_web/controllers/sources/source_html/media_item_table_live.ex +++ b/lib/pinchflat_web/controllers/sources/source_html/media_item_table_live.ex @@ -23,7 +23,9 @@ defmodule Pinchflat.Sources.MediaItemTableLive do
    <.icon_button icon_name="hero-arrow-path" class="h-10 w-10" phx-click="reload_page" tooltip="Refresh" /> - Showing <%= length(@records) %> of <%= @filtered_record_count %> + + Showing <.localized_number number={length(@records)} /> of <.localized_number number={@filtered_record_count} /> +
    @@ -49,12 +51,12 @@ defmodule Pinchflat.Sources.MediaItemTableLive do <%= StringUtils.truncate(media_item.title, 50) %> - <:col :let={media_item} label="Upload Date"> - <%= DateTime.to_date(media_item.uploaded_at) %> - <:col :let={media_item} :if={@media_state == "other"} label="Manually Ignored?"> <.icon name={if media_item.prevent_download, do: "hero-check", else: "hero-x-mark"} /> + <:col :let={media_item} label="Upload Date"> + <%= DateTime.to_date(media_item.uploaded_at) %> + <:col :let={media_item} label="" class="flex justify-end"> <.icon_link href={~p"/sources/#{@source.id}/media/#{media_item.id}/edit"} icon="hero-pencil-square" class="mr-4" /> @@ -117,14 +119,40 @@ defmodule Pinchflat.Sources.MediaItemTableLive do {:noreply, assign(socket, new_assigns)} end + defp fetch_pagination_attributes(base_query, page, ""), do: fetch_pagination_attributes(base_query, page, nil) + + defp fetch_pagination_attributes(base_query, page, nil) do + total_record_count = Repo.aggregate(base_query, :count, :id) + total_pages = max(ceil(total_record_count / @limit), 1) + page = NumberUtils.clamp(page, 1, total_pages) + + records = + fetch_records(base_query, page) + |> order_by(desc: :uploaded_at) + |> Repo.all() + + %{ + page: page, + total_pages: total_pages, + records: records, + search_term: nil, + total_record_count: total_record_count, + filtered_record_count: total_record_count + } + end + defp fetch_pagination_attributes(base_query, page, search_term) do - filtered_base_query = filter_base_query(base_query, search_term) + filtered_base_query = filtered_base_query(base_query, search_term) total_record_count = Repo.aggregate(base_query, :count, :id) filtered_record_count = Repo.aggregate(filtered_base_query, :count, :id) total_pages = max(ceil(filtered_record_count / @limit), 1) page = NumberUtils.clamp(page, 1, total_pages) - records = fetch_records(filtered_base_query, page) + + records = + fetch_records(filtered_base_query, page) + |> order_by(desc: fragment("rank"), desc: :uploaded_at) + |> Repo.all() %{ page: page, @@ -142,39 +170,41 @@ defmodule Pinchflat.Sources.MediaItemTableLive do base_query |> limit(^@limit) |> offset(^offset) - |> Repo.all() end defp generate_base_query(source, "pending") do MediaQuery.new() + |> select(^select_fields()) |> MediaQuery.require_assoc(:media_profile) - |> MediaQuery.require_assoc(:media_items_search_index) |> where(^dynamic(^MediaQuery.for_source(source) and ^MediaQuery.pending())) - |> order_by(desc: fragment("rank"), desc: :uploaded_at) end defp generate_base_query(source, "downloaded") do MediaQuery.new() - |> MediaQuery.require_assoc(:media_items_search_index) + |> select(^select_fields()) |> where(^dynamic(^MediaQuery.for_source(source) and ^MediaQuery.downloaded())) - |> order_by(desc: fragment("rank"), desc: :uploaded_at) end defp generate_base_query(source, "other") do MediaQuery.new() + |> select(^select_fields()) |> MediaQuery.require_assoc(:media_profile) - |> MediaQuery.require_assoc(:media_items_search_index) |> where( ^dynamic( ^MediaQuery.for_source(source) and (not (^MediaQuery.downloaded()) and not (^MediaQuery.pending())) ) ) - |> order_by(desc: fragment("rank"), desc: :uploaded_at) end - defp filter_base_query(base_query, search_term) do + defp filtered_base_query(base_query, search_term) do base_query + |> MediaQuery.require_assoc(:media_items_search_index) |> where(^MediaQuery.matches_search_term(search_term)) end + + # Selecting only what we need GREATLY speeds up queries on large tables + defp select_fields do + [:id, :title, :uploaded_at, :prevent_download] + end end diff --git a/lib/pinchflat_web/controllers/sources/source_html/source_form.html.heex b/lib/pinchflat_web/controllers/sources/source_html/source_form.html.heex index 6a53a3a..88b584d 100644 --- a/lib/pinchflat_web/controllers/sources/source_html/source_form.html.heex +++ b/lib/pinchflat_web/controllers/sources/source_html/source_form.html.heex @@ -95,7 +95,8 @@ type="number" label="Retention Period (days)" min="0" - help="Days between when media is *downloaded* and when it's deleted. Leave blank to keep media indefinitely" + help="Days between when media is downloaded and when it's deleted. Leave blank to keep media indefinitely" + html_help={true} />
    diff --git a/priv/repo/erd.png b/priv/repo/erd.png index 5f66b70..30d3e8a 100644 Binary files a/priv/repo/erd.png and b/priv/repo/erd.png differ diff --git a/priv/repo/migrations/20240529000015_modify_upload_date_index.exs b/priv/repo/migrations/20240529000015_modify_upload_date_index.exs new file mode 100644 index 0000000..a9be455 --- /dev/null +++ b/priv/repo/migrations/20240529000015_modify_upload_date_index.exs @@ -0,0 +1,8 @@ +defmodule Pinchflat.Repo.Migrations.ModifyUploadDateIndex do + use Ecto.Migration + + def change do + drop index("media_items", [:upload_date]) + create index("media_items", [:uploaded_at]) + end +end diff --git a/priv/repo/migrations/20240529153838_add_indexes_for_large_collections.exs b/priv/repo/migrations/20240529153838_add_indexes_for_large_collections.exs new file mode 100644 index 0000000..8c60aed --- /dev/null +++ b/priv/repo/migrations/20240529153838_add_indexes_for_large_collections.exs @@ -0,0 +1,19 @@ +defmodule Pinchflat.Repo.Migrations.AddIndexesForLargeCollections do + use Ecto.Migration + + def change do + create index( + "media_items", + [ + :source_id, + :media_filepath, + :uploaded_at, + :prevent_download, + :livestream, + :short_form_content, + :title + ], + name: "media_items_pending_and_downloaded_index" + ) + end +end diff --git a/test/pinchflat_web/controllers/sources/media_item_table_live_test.exs b/test/pinchflat_web/controllers/sources/media_item_table_live_test.exs index f584377..772e305 100644 --- a/test/pinchflat_web/controllers/sources/media_item_table_live_test.exs +++ b/test/pinchflat_web/controllers/sources/media_item_table_live_test.exs @@ -27,7 +27,7 @@ defmodule PinchflatWeb.Sources.MediaItemTableLiveTest do {:ok, _view, html} = live_isolated(conn, MediaItemTableLive, session: create_session(source)) - assert html =~ "Showing 1 of 1" + assert html =~ "Showing" assert html =~ "Title" assert html =~ media_item.title end