[Enhancement] Add media count to sources page (#251)
* [WIP] Got query kinda working, now to refactor other queries * Refactored all query methods to use dynamic snippets * Refactored tab layout to grab tabs by name * Removed standalone show buttons from in-app tables * Removed unneeded comment
This commit is contained in:
@@ -6,25 +6,32 @@ defmodule PinchflatWeb.CustomComponents.TabComponents do
|
||||
Takes a list of tabs and renders them in a tabbed layout.
|
||||
"""
|
||||
slot :tab, required: true do
|
||||
attr :id, :string, required: true
|
||||
attr :title, :string, required: true
|
||||
end
|
||||
|
||||
slot :tab_append, required: false
|
||||
|
||||
def tabbed_layout(assigns) do
|
||||
assigns = Map.put(assigns, :first_tab_id, hd(assigns.tab).id)
|
||||
|
||||
~H"""
|
||||
<div
|
||||
x-data="{ openTab: getTabIndex(0), activeClasses: 'text-meta-5 border-meta-5', inactiveClasses: 'border-transparent' }"
|
||||
@hashchange.window="openTab = getTabIndex(openTab)"
|
||||
x-data={"{
|
||||
openTab: getTabFromHash('#{@first_tab_id}', '#{@first_tab_id}'),
|
||||
activeClasses: 'text-meta-5 border-meta-5',
|
||||
inactiveClasses: 'border-transparent'
|
||||
}"}
|
||||
@hashchange.window={"openTab = getTabFromHash(openTab, '#{@first_tab_id}')"}
|
||||
class="w-full"
|
||||
>
|
||||
<header class="flex flex-col md:flex-row md:justify-between border-b border-strokedark">
|
||||
<div class="flex flex-wrap gap-5 sm:gap-10">
|
||||
<a
|
||||
:for={{tab, idx} <- Enum.with_index(@tab)}
|
||||
:for={tab <- @tab}
|
||||
href="#"
|
||||
@click.prevent={"openTab = setTabIndex(#{idx})"}
|
||||
x-bind:class={"openTab === #{idx} ? activeClasses : inactiveClasses"}
|
||||
@click.prevent={"openTab = setTabByName('#{tab.id}')"}
|
||||
x-bind:class={"openTab === '#{tab.id}' ? activeClasses : inactiveClasses"}
|
||||
class="border-b-2 py-4 w-full sm:w-fit text-sm font-medium hover:text-meta-5 md:text-base"
|
||||
>
|
||||
<span class="text-xl"><%= tab.title %></span>
|
||||
@@ -35,7 +42,7 @@ defmodule PinchflatWeb.CustomComponents.TabComponents do
|
||||
</div>
|
||||
</header>
|
||||
<div class="mt-4 min-h-60">
|
||||
<div :for={{tab, idx} <- Enum.with_index(@tab)} x-show={"openTab === #{idx}"} class="font-medium leading-relaxed">
|
||||
<div :for={tab <- @tab} x-show={"openTab === '#{tab.id}'"} class="font-medium leading-relaxed">
|
||||
<%= render_slot(tab) %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -36,7 +36,7 @@ defmodule PinchflatWeb.CustomComponents.TableComponents do
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr :for={row <- @rows} class="border-b border-boxdark hover:border-strokedark">
|
||||
<tr :for={row <- @rows} class="hover:bg-strokedark">
|
||||
<td
|
||||
:for={col <- @col}
|
||||
class={[
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
<.actions_dropdown media_item={@media_item} />
|
||||
</:tab_append>
|
||||
|
||||
<:tab title="Media">
|
||||
<:tab title="Media" id="media">
|
||||
<div class="flex flex-col gap-10 dark:text-white">
|
||||
<%= if media_file_exists?(@media_item) do %>
|
||||
<section class="grid grid-cols-1 xl:grid-cols-2 xl:gap-6 mt-6">
|
||||
@@ -52,7 +52,7 @@
|
||||
</section>
|
||||
</div>
|
||||
</:tab>
|
||||
<:tab title="Tasks">
|
||||
<:tab title="Tasks" id="tasks">
|
||||
<%= if match?([_|_], @media_item.tasks) do %>
|
||||
<.table rows={@media_item.tasks} table_class="text-black dark:text-white">
|
||||
<:col :let={task} label="Worker">
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
defmodule PinchflatWeb.MediaProfiles.MediaProfileController do
|
||||
use PinchflatWeb, :controller
|
||||
|
||||
import Ecto.Query, warn: false
|
||||
use Pinchflat.Sources.SourcesQuery
|
||||
|
||||
alias Pinchflat.Repo
|
||||
alias Pinchflat.Profiles
|
||||
alias Pinchflat.Sources.SourcesQuery
|
||||
alias Pinchflat.Profiles.MediaProfile
|
||||
|
||||
def index(conn, _params) do
|
||||
@@ -43,7 +41,7 @@ defmodule PinchflatWeb.MediaProfiles.MediaProfileController do
|
||||
|
||||
sources =
|
||||
SourcesQuery.new()
|
||||
|> SourcesQuery.for_media_profile(media_profile)
|
||||
|> where(^SourcesQuery.for_media_profile(media_profile))
|
||||
|> order_by(asc: :custom_name)
|
||||
|> Repo.all()
|
||||
|
||||
|
||||
@@ -23,9 +23,8 @@
|
||||
<:col :let={media_profile} label="Preferred Resolution">
|
||||
<%= media_profile.preferred_resolution %>
|
||||
</:col>
|
||||
<:col :let={media_profile} label="" class="flex place-content-evenly">
|
||||
<.icon_link href={~p"/media_profiles/#{media_profile.id}"} icon="hero-eye" class="mx-1" />
|
||||
<.icon_link href={~p"/media_profiles/#{media_profile.id}/edit"} icon="hero-pencil-square" class="mx-1" />
|
||||
<:col :let={media_profile} label="" class="flex justify-end">
|
||||
<.icon_link href={~p"/media_profiles/#{media_profile.id}/edit"} icon="hero-pencil-square" class="mr-4" />
|
||||
</:col>
|
||||
</.table>
|
||||
</div>
|
||||
|
||||
@@ -23,13 +23,13 @@
|
||||
<.actions_dropdown media_profile={@media_profile} />
|
||||
</:tab_append>
|
||||
|
||||
<:tab title="Media Profile">
|
||||
<:tab title="Media Profile" id="media-profile">
|
||||
<div class="flex flex-col gap-10 text-white">
|
||||
<h3 class="font-bold text-xl mt-6">Raw Attributes</h3>
|
||||
<.list_items_from_map map={Map.from_struct(@media_profile)} />
|
||||
</div>
|
||||
</:tab>
|
||||
<:tab title="Sources">
|
||||
<:tab title="Sources" id="sources">
|
||||
<.table rows={@sources} table_class="text-black dark:text-white">
|
||||
<:col :let={source} label="Name">
|
||||
<.subtle_link href={~p"/sources/#{source.id}"}>
|
||||
@@ -40,9 +40,8 @@
|
||||
<:col :let={source} label="Should Download?">
|
||||
<.icon name={if source.download_media, do: "hero-check", else: "hero-x-mark"} />
|
||||
</:col>
|
||||
<:col :let={source} label="" class="flex place-content-evenly">
|
||||
<.icon_link href={~p"/sources/#{source.id}"} icon="hero-eye" class="mx-1" />
|
||||
<.icon_link href={~p"/sources/#{source.id}/edit"} icon="hero-pencil-square" class="mx-1" />
|
||||
<:col :let={source} label="" class="flex justify-end">
|
||||
<.icon_link href={~p"/sources/#{source.id}/edit"} icon="hero-pencil-square" class="mr-4" />
|
||||
</:col>
|
||||
</.table>
|
||||
</:tab>
|
||||
|
||||
@@ -26,7 +26,7 @@ defmodule PinchflatWeb.Pages.PageController do
|
||||
source_count: Repo.aggregate(Source, :count, :id),
|
||||
media_item_count:
|
||||
MediaQuery.new()
|
||||
|> MediaQuery.with_media_downloaded_at()
|
||||
|> where(^MediaQuery.downloaded())
|
||||
|> Repo.aggregate(:count, :id)
|
||||
)
|
||||
end
|
||||
|
||||
@@ -97,7 +97,8 @@ defmodule Pinchflat.Pages.HistoryTableLive do
|
||||
|
||||
defp generate_base_query do
|
||||
MediaQuery.new()
|
||||
|> MediaQuery.where_pending_or_downloaded()
|
||||
|> MediaQuery.require_assoc(:media_profile)
|
||||
|> where(^dynamic(^MediaQuery.downloaded() or ^MediaQuery.pending()))
|
||||
|> order_by(desc: :id)
|
||||
end
|
||||
|
||||
|
||||
@@ -26,8 +26,7 @@ defmodule PinchflatWeb.Podcasts.PodcastController do
|
||||
# if the source doesn't have any usable images
|
||||
media_items =
|
||||
MediaQuery.new()
|
||||
|> MediaQuery.for_source(source)
|
||||
|> MediaQuery.with_media_filepath()
|
||||
|> where(^dynamic(^MediaQuery.for_source(source) and ^MediaQuery.downloaded()))
|
||||
|> Repo.maybe_limit(1)
|
||||
|> Repo.all()
|
||||
|
||||
|
||||
@@ -1,25 +1,44 @@
|
||||
defmodule PinchflatWeb.Sources.SourceController do
|
||||
use PinchflatWeb, :controller
|
||||
|
||||
import Ecto.Query, warn: false
|
||||
use Pinchflat.Media.MediaQuery
|
||||
|
||||
alias Pinchflat.Repo
|
||||
alias Pinchflat.Tasks
|
||||
alias Pinchflat.Sources
|
||||
alias Pinchflat.Sources.Source
|
||||
alias Pinchflat.Media.MediaItem
|
||||
alias Pinchflat.Profiles.MediaProfile
|
||||
alias Pinchflat.Downloading.DownloadingHelpers
|
||||
alias Pinchflat.SlowIndexing.SlowIndexingHelpers
|
||||
alias Pinchflat.Metadata.SourceMetadataStorageWorker
|
||||
|
||||
def index(conn, _params) do
|
||||
sources =
|
||||
Source
|
||||
|> order_by(asc: :custom_name)
|
||||
|> Repo.all()
|
||||
|> Repo.preload(:media_profile)
|
||||
source_query =
|
||||
from s in Source,
|
||||
as: :source,
|
||||
inner_join: mp in assoc(s, :media_profile),
|
||||
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)
|
||||
)
|
||||
}
|
||||
|
||||
render(conn, :index, sources: sources)
|
||||
render(conn, :index, sources: Repo.all(source_query))
|
||||
end
|
||||
|
||||
def new(conn, _params) do
|
||||
|
||||
@@ -15,13 +15,12 @@
|
||||
<.table rows={@sources} table_class="text-black dark:text-white">
|
||||
<:col :let={source} label="Name">
|
||||
<.subtle_link href={~p"/sources/#{source.id}"}>
|
||||
<%= source.custom_name || source.collection_name %>
|
||||
<%= StringUtils.truncate(source.custom_name || source.collection_name, 35) %>
|
||||
</.subtle_link>
|
||||
</:col>
|
||||
<:col :let={source} label="Type"><%= source.collection_type %></:col>
|
||||
<:col :let={source} label="Should Download?">
|
||||
<.icon name={if source.download_media, do: "hero-check", else: "hero-x-mark"} />
|
||||
</:col>
|
||||
<:col :let={source} label="Pending"><%= source.pending_count %></:col>
|
||||
<:col :let={source} label="Downloaded"><%= source.downloaded_count %></:col>
|
||||
<:col :let={source} label="Retention">
|
||||
<%= if source.retention_period_days && source.retention_period_days > 0 do %>
|
||||
<%= source.retention_period_days %> day(s)
|
||||
@@ -35,7 +34,6 @@
|
||||
</.subtle_link>
|
||||
</:col>
|
||||
<:col :let={source} label="" class="flex place-content-evenly">
|
||||
<.icon_link href={~p"/sources/#{source.id}"} icon="hero-eye" class="mx-1" />
|
||||
<.icon_link href={~p"/sources/#{source.id}/edit"} icon="hero-pencil-square" class="mx-1" />
|
||||
</:col>
|
||||
</.table>
|
||||
|
||||
@@ -30,9 +30,8 @@ defmodule Pinchflat.Sources.MediaItemTableLive do
|
||||
<%= StringUtils.truncate(media_item.title, 50) %>
|
||||
</.subtle_link>
|
||||
</:col>
|
||||
<:col :let={media_item} label="" class="flex place-content-evenly">
|
||||
<.icon_link href={~p"/sources/#{@source.id}/media/#{media_item.id}"} icon="hero-eye" class="mx-1" />
|
||||
<.icon_link href={~p"/sources/#{@source.id}/media/#{media_item.id}/edit"} icon="hero-pencil-square" class="mx-1" />
|
||||
<: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" />
|
||||
</:col>
|
||||
</.table>
|
||||
<section class="flex justify-center mt-5">
|
||||
@@ -86,15 +85,14 @@ defmodule Pinchflat.Sources.MediaItemTableLive do
|
||||
|
||||
defp generate_base_query(source, "pending") do
|
||||
MediaQuery.new()
|
||||
|> MediaQuery.for_source(source)
|
||||
|> MediaQuery.where_pending_download()
|
||||
|> MediaQuery.require_assoc(:media_profile)
|
||||
|> where(^dynamic(^MediaQuery.for_source(source) and ^MediaQuery.pending()))
|
||||
|> order_by(desc: :id)
|
||||
end
|
||||
|
||||
defp generate_base_query(source, "downloaded") do
|
||||
MediaQuery.new()
|
||||
|> MediaQuery.for_source(source)
|
||||
|> MediaQuery.with_media_filepath()
|
||||
|> where(^dynamic(^MediaQuery.for_source(source) and ^MediaQuery.downloaded()))
|
||||
|> order_by(desc: :id)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
<.actions_dropdown source={@source} conn={@conn} />
|
||||
</:tab_append>
|
||||
|
||||
<:tab title="Source">
|
||||
<:tab title="Source" id="source">
|
||||
<div class="flex flex-col gap-10 text-white">
|
||||
<h3 class="font-bold text-xl mt-6">Raw Attributes</h3>
|
||||
<section>
|
||||
@@ -36,21 +36,21 @@
|
||||
<.list_items_from_map map={Map.from_struct(@source)} />
|
||||
</div>
|
||||
</:tab>
|
||||
<:tab title="Pending Media">
|
||||
<:tab title="Pending Media" id="pending">
|
||||
<%= live_render(
|
||||
@conn,
|
||||
Pinchflat.Sources.MediaItemTableLive,
|
||||
session: %{"source_id" => @source.id, "media_state" => "pending"}
|
||||
) %>
|
||||
</:tab>
|
||||
<:tab title="Downloaded Media">
|
||||
<:tab title="Downloaded Media" id="downloaded">
|
||||
<%= live_render(
|
||||
@conn,
|
||||
Pinchflat.Sources.MediaItemTableLive,
|
||||
session: %{"source_id" => @source.id, "media_state" => "downloaded"}
|
||||
) %>
|
||||
</:tab>
|
||||
<:tab title="Pending Tasks">
|
||||
<:tab title="Pending Tasks" id="tasks">
|
||||
<%= if match?([_|_], @pending_tasks) do %>
|
||||
<.table rows={@pending_tasks} table_class="text-black dark:text-white">
|
||||
<:col :let={task} label="Worker">
|
||||
|
||||
Reference in New Issue
Block a user