WIP - started improving handling of sorting for sources index table
This commit is contained in:
parent
5371b5d236
commit
5af3ea4b36
7 changed files with 185 additions and 105 deletions
|
|
@ -24,6 +24,7 @@ defmodule PinchflatWeb.CustomComponents.TableComponents do
|
||||||
slot :col, required: true do
|
slot :col, required: true do
|
||||||
attr :label, :string
|
attr :label, :string
|
||||||
attr :class, :string
|
attr :class, :string
|
||||||
|
attr :sort_key, :string
|
||||||
end
|
end
|
||||||
|
|
||||||
def table(assigns) do
|
def table(assigns) do
|
||||||
|
|
@ -31,7 +32,12 @@ defmodule PinchflatWeb.CustomComponents.TableComponents do
|
||||||
<table class={["w-full table-auto bg-boxdark", @table_class]}>
|
<table class={["w-full table-auto bg-boxdark", @table_class]}>
|
||||||
<thead>
|
<thead>
|
||||||
<tr class="text-left bg-meta-4">
|
<tr class="text-left bg-meta-4">
|
||||||
<th :for={col <- @col} class="px-4 py-4 font-medium text-white xl:pl-11">
|
<th
|
||||||
|
:for={col <- @col}
|
||||||
|
class={["px-4 py-4 font-medium text-white xl:pl-11", col[:sort_key] && "cursor-pointer"]}
|
||||||
|
phx-click="sort_update"
|
||||||
|
phx-value-sort_key={col[:sort_key]}
|
||||||
|
>
|
||||||
{col[:label]}
|
{col[:label]}
|
||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
@ -53,6 +59,11 @@ defmodule PinchflatWeb.CustomComponents.TableComponents do
|
||||||
"""
|
"""
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# attr :
|
||||||
|
# def my_table(assigns) do
|
||||||
|
|
||||||
|
# end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Renders simple pagination controls for a table in a liveview.
|
Renders simple pagination controls for a table in a liveview.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,12 @@
|
||||||
<div class="rounded-sm border border-stroke bg-white shadow-default dark:border-strokedark dark:bg-boxdark">
|
<div class="rounded-sm border border-stroke bg-white shadow-default dark:border-strokedark dark:bg-boxdark">
|
||||||
<div class="max-w-full overflow-x-auto">
|
<div class="max-w-full overflow-x-auto">
|
||||||
<div class="flex flex-col gap-10 min-w-max">
|
<div class="flex flex-col gap-10 min-w-max">
|
||||||
{live_render(@conn, PinchflatWeb.Sources.IndexTableLive)}
|
{live_render(@conn, PinchflatWeb.Sources.SourceLive.IndexTableLive,
|
||||||
|
session: %{
|
||||||
|
"initial_sort_key" => :custom_name,
|
||||||
|
"initial_sort_direction" => :asc
|
||||||
|
}
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,103 +0,0 @@
|
||||||
defmodule PinchflatWeb.Sources.IndexTableLive do
|
|
||||||
use PinchflatWeb, :live_view
|
|
||||||
use Pinchflat.Media.MediaQuery
|
|
||||||
use Pinchflat.Sources.SourcesQuery
|
|
||||||
|
|
||||||
alias Pinchflat.Repo
|
|
||||||
alias Pinchflat.Sources
|
|
||||||
alias Pinchflat.Sources.Source
|
|
||||||
alias Pinchflat.Media.MediaItem
|
|
||||||
|
|
||||||
def render(assigns) do
|
|
||||||
~H"""
|
|
||||||
<.table rows={@sources} table_class="text-white">
|
|
||||||
<:col :let={source} label="Name">
|
|
||||||
<.subtle_link href={~p"/sources/#{source.id}"}>
|
|
||||||
{StringUtils.truncate(source.custom_name || source.collection_name, 35)}
|
|
||||||
</.subtle_link>
|
|
||||||
</:col>
|
|
||||||
<:col :let={source} label="Pending">
|
|
||||||
<.subtle_link href={~p"/sources/#{source.id}/#tab-pending"}>
|
|
||||||
<.localized_number number={source.pending_count} />
|
|
||||||
</.subtle_link>
|
|
||||||
</:col>
|
|
||||||
<:col :let={source} label="Downloaded">
|
|
||||||
<.subtle_link href={~p"/sources/#{source.id}/#tab-downloaded"}>
|
|
||||||
<.localized_number number={source.downloaded_count} />
|
|
||||||
</.subtle_link>
|
|
||||||
</:col>
|
|
||||||
<:col :let={source} label="Retention">
|
|
||||||
<%= if source.retention_period_days && source.retention_period_days > 0 do %>
|
|
||||||
<.localized_number number={source.retention_period_days} />
|
|
||||||
<.pluralize count={source.retention_period_days} word="day" />
|
|
||||||
<% else %>
|
|
||||||
<span class="text-lg">∞</span>
|
|
||||||
<% end %>
|
|
||||||
</:col>
|
|
||||||
<:col :let={source} label="Media Profile">
|
|
||||||
<.subtle_link href={~p"/media_profiles/#{source.media_profile_id}"}>
|
|
||||||
{source.media_profile.name}
|
|
||||||
</.subtle_link>
|
|
||||||
</:col>
|
|
||||||
<:col :let={source} label="Enabled?">
|
|
||||||
<.input
|
|
||||||
name={"source[#{source.id}][enabled]"}
|
|
||||||
value={source.enabled}
|
|
||||||
id={"source_#{source.id}_enabled"}
|
|
||||||
phx-hook="formless-input"
|
|
||||||
data-subscribe="change"
|
|
||||||
data-event-name="toggle_enabled"
|
|
||||||
data-identifier={source.id}
|
|
||||||
type="toggle"
|
|
||||||
/>
|
|
||||||
</:col>
|
|
||||||
<:col :let={source} label="" class="flex place-content-evenly">
|
|
||||||
<.icon_link href={~p"/sources/#{source.id}/edit"} icon="hero-pencil-square" class="mx-1" />
|
|
||||||
</:col>
|
|
||||||
</.table>
|
|
||||||
"""
|
|
||||||
end
|
|
||||||
|
|
||||||
def mount(_params, _session, socket) do
|
|
||||||
{:ok, assign(socket, %{sources: get_sources()})}
|
|
||||||
end
|
|
||||||
|
|
||||||
def handle_event("formless-input", %{"event" => "toggle_enabled"} = params, socket) do
|
|
||||||
source = Sources.get_source!(params["id"])
|
|
||||||
should_enable = params["value"] == "true"
|
|
||||||
|
|
||||||
{:ok, _} = Sources.update_source(source, %{enabled: should_enable})
|
|
||||||
|
|
||||||
{:noreply, assign(socket, %{sources: get_sources()})}
|
|
||||||
end
|
|
||||||
|
|
||||||
defp get_sources do
|
|
||||||
query =
|
|
||||||
from s in Source,
|
|
||||||
as: :source,
|
|
||||||
inner_join: mp in assoc(s, :media_profile),
|
|
||||||
where: is_nil(s.marked_for_deletion_at) and is_nil(mp.marked_for_deletion_at),
|
|
||||||
preload: [media_profile: mp],
|
|
||||||
order_by: [asc: s.custom_name],
|
|
||||||
select: map(s, ^Source.__schema__(:fields)),
|
|
||||||
select_merge: %{
|
|
||||||
downloaded_count:
|
|
||||||
subquery(
|
|
||||||
from m in MediaItem,
|
|
||||||
where: m.source_id == parent_as(:source).id,
|
|
||||||
where: ^MediaQuery.downloaded(),
|
|
||||||
select: count(m.id)
|
|
||||||
),
|
|
||||||
pending_count:
|
|
||||||
subquery(
|
|
||||||
from m in MediaItem,
|
|
||||||
join: s in assoc(m, :source),
|
|
||||||
where: m.source_id == parent_as(:source).id,
|
|
||||||
where: ^MediaQuery.pending(),
|
|
||||||
select: count(m.id)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
Repo.all(query)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
@ -0,0 +1,100 @@
|
||||||
|
defmodule PinchflatWeb.Sources.SourceLive.IndexTableLive do
|
||||||
|
use PinchflatWeb, :live_view
|
||||||
|
use Pinchflat.Media.MediaQuery
|
||||||
|
use Pinchflat.Sources.SourcesQuery
|
||||||
|
|
||||||
|
import PinchflatWeb.Helpers.SortingHelpers
|
||||||
|
|
||||||
|
alias Pinchflat.Repo
|
||||||
|
alias Pinchflat.Sources
|
||||||
|
alias Pinchflat.Sources.Source
|
||||||
|
alias Pinchflat.Media.MediaItem
|
||||||
|
|
||||||
|
def mount(_params, session, socket) do
|
||||||
|
initial_params = %{
|
||||||
|
sort_key: session["initial_sort_key"],
|
||||||
|
sort_direction: session["initial_sort_direction"]
|
||||||
|
}
|
||||||
|
|
||||||
|
socket
|
||||||
|
|> assign(initial_params)
|
||||||
|
|> set_sources()
|
||||||
|
|> then(&{:ok, &1})
|
||||||
|
end
|
||||||
|
|
||||||
|
# def handle_event("formless-input", %{"event" => "toggle_enabled"} = params, socket) do
|
||||||
|
# source = Sources.get_source!(params["id"])
|
||||||
|
# should_enable = params["value"] == "true"
|
||||||
|
|
||||||
|
# {:ok, _} = Sources.update_source(source, %{enabled: should_enable})
|
||||||
|
|
||||||
|
# {:noreply, assign(socket, %{sources: get_sources(socket.assigns)})}
|
||||||
|
# end
|
||||||
|
|
||||||
|
def handle_event("sort_update", %{"sort_key" => sort_key}, %{assigns: assigns} = socket) do
|
||||||
|
new_sort_key = String.to_existing_atom(sort_key)
|
||||||
|
|
||||||
|
new_params = %{
|
||||||
|
sort_key: new_sort_key,
|
||||||
|
sort_direction: get_sort_direction(assigns.sort_key, new_sort_key, assigns.sort_direction)
|
||||||
|
}
|
||||||
|
|
||||||
|
socket
|
||||||
|
|> assign(new_params)
|
||||||
|
|> set_sources()
|
||||||
|
|> then(&{:noreply, &1})
|
||||||
|
end
|
||||||
|
|
||||||
|
defp set_sources(%{assigns: assigns} = socket) do
|
||||||
|
sources =
|
||||||
|
sources_query()
|
||||||
|
|> order_by(^[{assigns.sort_direction, sort_attr(assigns.sort_key)}])
|
||||||
|
|> Repo.all()
|
||||||
|
|
||||||
|
assign(socket, %{sources: sources})
|
||||||
|
end
|
||||||
|
|
||||||
|
defp sort_attr(:downloaded_count), do: dynamic([s, mp, dl], field(dl, :downloaded_count))
|
||||||
|
defp sort_attr(:custom_name), do: dynamic([s], field(s, :custom_name))
|
||||||
|
defp sort_attr(_), do: sort_attr(:custom_name)
|
||||||
|
|
||||||
|
defp sources_query do
|
||||||
|
downloaded_subquery =
|
||||||
|
from(
|
||||||
|
m in MediaItem,
|
||||||
|
select: %{downloaded_count: count(m.id), source_id: m.source_id},
|
||||||
|
where: ^MediaQuery.downloaded(),
|
||||||
|
group_by: m.source_id
|
||||||
|
)
|
||||||
|
|
||||||
|
from s in Source,
|
||||||
|
as: :source,
|
||||||
|
inner_join: mp in assoc(s, :media_profile),
|
||||||
|
left_join: d in subquery(downloaded_subquery),
|
||||||
|
on: d.source_id == s.id,
|
||||||
|
where: is_nil(s.marked_for_deletion_at) and is_nil(mp.marked_for_deletion_at),
|
||||||
|
preload: [media_profile: mp],
|
||||||
|
select: map(s, ^Source.__schema__(:fields)),
|
||||||
|
select_merge: %{
|
||||||
|
downloaded_count: coalesce(d.downloaded_count, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
# select_merge: %{
|
||||||
|
# downloaded_count:
|
||||||
|
# subquery(
|
||||||
|
# from m in MediaItem,
|
||||||
|
# where: m.source_id == parent_as(:source).id,
|
||||||
|
# where: ^MediaQuery.downloaded(),
|
||||||
|
# select: count(m.id)
|
||||||
|
# ),
|
||||||
|
# pending_count:
|
||||||
|
# subquery(
|
||||||
|
# from m in MediaItem,
|
||||||
|
# join: s in assoc(m, :source),
|
||||||
|
# where: m.source_id == parent_as(:source).id,
|
||||||
|
# where: ^MediaQuery.pending(),
|
||||||
|
# select: count(m.id)
|
||||||
|
# )
|
||||||
|
# }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
<.table rows={@sources} table_class="text-white">
|
||||||
|
<:col :let={source} label="Name" sort_key="custom_name">
|
||||||
|
<.subtle_link href={~p"/sources/#{source.id}"}>
|
||||||
|
{source.custom_name}
|
||||||
|
</.subtle_link>
|
||||||
|
</:col>
|
||||||
|
<:col :let={source} label="Downloaded" sort_key="downloaded_count">
|
||||||
|
<.subtle_link href={~p"/sources/#{source.id}/#tab-downloaded"}>
|
||||||
|
<.localized_number number={source.downloaded_count} />
|
||||||
|
</.subtle_link>
|
||||||
|
</:col>
|
||||||
|
</.table>
|
||||||
|
|
@ -0,0 +1,45 @@
|
||||||
|
<.table rows={@sources} table_class="text-white">
|
||||||
|
<:col :let={source} label="Name">
|
||||||
|
<.subtle_link href={~p"/sources/#{source.id}"}>
|
||||||
|
{StringUtils.truncate(source.custom_name || source.collection_name, 35)}
|
||||||
|
</.subtle_link>
|
||||||
|
</:col>
|
||||||
|
<:col :let={source} label="Pending">
|
||||||
|
<.subtle_link href={~p"/sources/#{source.id}/#tab-pending"}>
|
||||||
|
<.localized_number number={source.pending_count} />
|
||||||
|
</.subtle_link>
|
||||||
|
</:col>
|
||||||
|
<:col :let={source} label="Downloaded">
|
||||||
|
<.subtle_link href={~p"/sources/#{source.id}/#tab-downloaded"}>
|
||||||
|
<.localized_number number={source.downloaded_count} />
|
||||||
|
</.subtle_link>
|
||||||
|
</:col>
|
||||||
|
<:col :let={source} label="Retention">
|
||||||
|
<%= if source.retention_period_days && source.retention_period_days > 0 do %>
|
||||||
|
<.localized_number number={source.retention_period_days} />
|
||||||
|
<.pluralize count={source.retention_period_days} word="day" />
|
||||||
|
<% else %>
|
||||||
|
<span class="text-lg">∞</span>
|
||||||
|
<% end %>
|
||||||
|
</:col>
|
||||||
|
<:col :let={source} label="Media Profile">
|
||||||
|
<.subtle_link href={~p"/media_profiles/#{source.media_profile_id}"}>
|
||||||
|
{source.media_profile.name}
|
||||||
|
</.subtle_link>
|
||||||
|
</:col>
|
||||||
|
<:col :let={source} label="Enabled?">
|
||||||
|
<.input
|
||||||
|
name={"source[#{source.id}][enabled]"}
|
||||||
|
value={source.enabled}
|
||||||
|
id={"source_#{source.id}_enabled"}
|
||||||
|
phx-hook="formless-input"
|
||||||
|
data-subscribe="change"
|
||||||
|
data-event-name="toggle_enabled"
|
||||||
|
data-identifier={source.id}
|
||||||
|
type="toggle"
|
||||||
|
/>
|
||||||
|
</:col>
|
||||||
|
<:col :let={source} label="" class="flex place-content-evenly">
|
||||||
|
<.icon_link href={~p"/sources/#{source.id}/edit"} icon="hero-pencil-square" class="mx-1" />
|
||||||
|
</:col>
|
||||||
|
</.table>
|
||||||
10
lib/pinchflat_web/helpers/sorting_helpers.ex
Normal file
10
lib/pinchflat_web/helpers/sorting_helpers.ex
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
defmodule PinchflatWeb.Helpers.SortingHelpers do
|
||||||
|
# TODO: test
|
||||||
|
def get_sort_direction(old_sort_attr, new_sort_attr, old_sort_direction) do
|
||||||
|
case {new_sort_attr, old_sort_direction} do
|
||||||
|
{^old_sort_attr, :desc} -> :asc
|
||||||
|
{^old_sort_attr, _} -> :desc
|
||||||
|
_ -> :asc
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
Loading…
Reference in a new issue