From fba7ffd40e6c768cda021444f393e8193daed328 Mon Sep 17 00:00:00 2001 From: Mika Cohen Date: Sat, 30 May 2026 10:42:51 -0600 Subject: [PATCH 1/4] Improve videos page responsiveness and filter behavior Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- lib/reencodarr/media.ex | 1 - lib/reencodarr/videos/state.ex | 15 +- lib/reencodarr_web/live/dashboard_live.ex | 26 +++- lib/reencodarr_web/live/videos_live.ex | 146 +++++++++--------- nix/package.nix | 34 +++- test/reencodarr_web/live/videos_live_test.exs | 47 ++++-- 6 files changed, 173 insertions(+), 96 deletions(-) diff --git a/lib/reencodarr/media.ex b/lib/reencodarr/media.ex index 182e6a32..20828608 100644 --- a/lib/reencodarr/media.ex +++ b/lib/reencodarr/media.ex @@ -2353,7 +2353,6 @@ defmodule Reencodarr.Media do case Flop.validate_and_run(base_query, flop_params, for: Video) do {:ok, {videos, meta}} -> - videos = Repo.preload(videos, :chosen_vmaf) {videos, meta} {:error, _meta} -> diff --git a/lib/reencodarr/videos/state.ex b/lib/reencodarr/videos/state.ex index b46d4f1f..60fd7838 100644 --- a/lib/reencodarr/videos/state.ex +++ b/lib/reencodarr/videos/state.ex @@ -3,8 +3,10 @@ defmodule Reencodarr.Videos.State do alias Reencodarr.Media - @spec load(map()) :: map() - def load(assigns) do + @spec load(map(), keyword()) :: map() + def load(assigns, opts \\ []) do + include_state_counts? = Keyword.get(opts, :include_state_counts, true) + {videos, meta} = Media.list_videos_paginated( page: assigns.page, @@ -17,13 +19,20 @@ defmodule Reencodarr.Videos.State do sort_dir: assigns.sort_dir ) + state_counts = + if include_state_counts? do + Media.count_videos_by_state() + else + Map.get(assigns, :state_counts, %{}) + end + %{ videos: videos, meta: meta, total: meta.total_count || 0, page: meta.current_page || assigns.page, per_page: meta.page_size || assigns.per_page, - state_counts: Media.count_videos_by_state() + state_counts: state_counts } end end diff --git a/lib/reencodarr_web/live/dashboard_live.ex b/lib/reencodarr_web/live/dashboard_live.ex index 4101f912..2946d500 100644 --- a/lib/reencodarr_web/live/dashboard_live.ex +++ b/lib/reencodarr_web/live/dashboard_live.ex @@ -620,13 +620,24 @@ defmodule ReencodarrWeb.DashboardLive do <% end %> <% else %> - +
Queue: {@queue_count} <%= if @status == :idle do %> • Idle <% end %>
+ + <%= if @status == :paused do %> +
+ <.active_job_controls + status={@status} + suspend_event="suspend_crf_search" + resume_event="resume_crf_search" + fail_event="fail_crf_search" + /> +
+ <% end %> <% end %> @@ -730,13 +741,24 @@ defmodule ReencodarrWeb.DashboardLive do /> <% else %> - +
Queue: {@queue_count} <%= if @status == :idle do %> • Idle <% end %>
+ + <%= if @status == :paused do %> +
+ <.active_job_controls + status={@status} + suspend_event="suspend_encode" + resume_event="resume_encode" + fail_event="fail_encode" + /> +
+ <% end %> <% end %> diff --git a/lib/reencodarr_web/live/videos_live.ex b/lib/reencodarr_web/live/videos_live.ex index e7f39713..fe99d301 100644 --- a/lib/reencodarr_web/live/videos_live.ex +++ b/lib/reencodarr_web/live/videos_live.ex @@ -77,7 +77,7 @@ defmodule ReencodarrWeb.VideosLive do |> assign(filters) |> then(fn s -> if connected?(s) and s.assigns.loaded_once and filters_changed?, - do: load_data(s), + do: load_data(s, include_state_counts: false), else: s end) @@ -114,33 +114,20 @@ defmodule ReencodarrWeb.VideosLive do # --------------------------------------------------------------------------- @impl true - def handle_event("search", %{"search" => q}, socket) do - {:noreply, push_patch(socket, to: patch_path(socket.assigns, search: q, page: 1))} - end - - @impl true - def handle_event("filter_state", %{"state" => state}, socket) do - {:noreply, - push_patch(socket, - to: patch_path(socket.assigns, state: nilify_empty(state), page: 1) - )} - end - - @impl true - def handle_event("filter_service", %{"service" => svc}, socket) do - {:noreply, - push_patch(socket, - to: patch_path(socket.assigns, service: nilify_empty(svc), page: 1) - )} - end + def handle_event("set_filters", params, socket) do + search = Map.get(params, "search", socket.assigns.search) + state = params |> Map.get("state") |> nilify_empty() + service = params |> Map.get("service") |> nilify_empty() + hdr = params |> Map.get("hdr") |> nilify_empty() |> parse_hdr_param() |> hdr_to_param() - @impl true - def handle_event("filter_hdr", %{"hdr" => hdr}, socket) do {:noreply, push_patch(socket, to: patch_path(socket.assigns, - hdr: hdr_to_param(parse_hdr_param(nilify_empty(hdr))), + search: search, + state: state, + service: service, + hdr: hdr, page: 1 ) )} @@ -281,8 +268,8 @@ defmodule ReencodarrWeb.VideosLive do def handle_event("reset_selected", _params, socket) do ids = MapSet.to_list(socket.assigns.selected) Enum.each(ids, &reset_video_by_id/1) - - socket = socket |> assign(selected: MapSet.new()) |> load_data() + videos = apply_video_state_on_page(socket.assigns.videos, ids, :needs_analysis) + socket = socket |> assign(selected: MapSet.new(), videos: videos) {:noreply, put_flash(socket, :info, "Reset #{length(ids)} video(s) to needs_analysis")} end @@ -299,7 +286,7 @@ defmodule ReencodarrWeb.VideosLive do put_flash(socket, :error, "No selected videos were eligible for queue prioritization")} {:ok, count} -> - socket = socket |> assign(selected: MapSet.new()) |> load_data() + socket = socket |> assign(selected: MapSet.new()) {:noreply, put_flash(socket, :info, "Prioritized #{count} video(s)")} {:error, _reason} -> @@ -316,7 +303,8 @@ defmodule ReencodarrWeb.VideosLive do with {:ok, id} <- Parsers.parse_integer_exact(id_str), video when not is_nil(video) <- Media.get_video(id), {:ok, _} <- Media.mark_as_needs_analysis(video) do - {:noreply, socket |> put_flash(:info, "Reset to needs_analysis") |> load_data()} + videos = apply_video_state_on_page(socket.assigns.videos, [id], :needs_analysis) + {:noreply, socket |> assign(:videos, videos) |> put_flash(:info, "Reset to needs_analysis")} else nil -> {:noreply, put_flash(socket, :error, "Video not found")} @@ -333,7 +321,10 @@ defmodule ReencodarrWeb.VideosLive do case Parsers.parse_integer_exact(id_str) do {:ok, id} -> Media.force_reanalyze_video(id) - {:noreply, socket |> put_flash(:info, "Queued for re-analysis") |> load_data()} + videos = apply_video_state_on_page(socket.assigns.videos, [id], :needs_analysis) + + {:noreply, + socket |> assign(:videos, videos) |> put_flash(:info, "Queued for re-analysis")} _ -> {:noreply, socket} @@ -345,7 +336,7 @@ defmodule ReencodarrWeb.VideosLive do with {:ok, id} <- Parsers.parse_integer_exact(id_str), {:ok, count} <- Media.prioritize_video(id), true <- count > 0 do - {:noreply, socket |> put_flash(:info, "Prioritized video") |> load_data()} + {:noreply, socket |> put_flash(:info, "Prioritized video")} else {:ok, 0} -> {:noreply, put_flash(socket, :error, "Video is not currently queueable")} false -> {:noreply, put_flash(socket, :error, "Video is not currently queueable")} @@ -356,20 +347,22 @@ defmodule ReencodarrWeb.VideosLive do @impl true def handle_event("fail_video", %{"id" => id_str}, socket) do - result = - with {:ok, id} <- Parsers.parse_integer_exact(id_str) do - fail_video_by_id(id) - end + case Parsers.parse_integer_exact(id_str) do + {:ok, id} -> + case fail_video_by_id(id) do + :ok -> + videos = apply_video_state_on_page(socket.assigns.videos, [id], :failed) + {:noreply, socket |> assign(:videos, videos) |> put_flash(:info, "Job stopped")} - case result do - :ok -> - {:noreply, socket |> put_flash(:info, "Job stopped") |> load_data()} + {:error, :active_mismatch} -> + {:noreply, socket |> put_flash(:error, "That video is not the active job")} - {:error, :active_mismatch} -> - {:noreply, socket |> put_flash(:error, "That video is not the active job") |> load_data()} + _ -> + {:noreply, socket |> put_flash(:error, "Unable to stop job")} + end _ -> - {:noreply, socket |> put_flash(:error, "Unable to stop job") |> load_data()} + {:noreply, socket |> put_flash(:error, "Unable to stop job")} end end @@ -398,8 +391,7 @@ defmodule ReencodarrWeb.VideosLive do |> put_flash( :info, "Prioritized #{count} #{Path.basename(season_dir)} video(s)" - ) - |> load_data()} + )} {:error, _reason} -> {:noreply, put_flash(socket, :error, "Failed to prioritize season videos")} @@ -420,7 +412,11 @@ defmodule ReencodarrWeb.VideosLive do with {:ok, id} <- Parsers.parse_integer_exact(id_str), {:ok, video} <- Media.fetch_video(id), {:ok, _} <- Media.delete_video(video) do - {:noreply, socket |> put_flash(:info, "Video deleted") |> load_data()} + videos = Enum.reject(socket.assigns.videos, &(&1.id == id)) + total = max(socket.assigns.total - 1, 0) + + {:noreply, + socket |> assign(videos: videos, total: total) |> put_flash(:info, "Video deleted")} else :not_found -> {:noreply, put_flash(socket, :error, "Video not found")} {:error, _} -> {:noreply, put_flash(socket, :error, "Delete failed")} @@ -442,8 +438,7 @@ defmodule ReencodarrWeb.VideosLive do {:noreply, socket |> assign(:expanded_bad_forms, List.delete(socket.assigns.expanded_bad_forms, id)) - |> put_flash(:info, "Marked as bad") - |> load_data()} + |> put_flash(:info, "Marked as bad")} else :not_found -> {:noreply, put_flash(socket, :error, "Video not found")} {:error, _} -> {:noreply, put_flash(socket, :error, "Mark bad failed")} @@ -455,18 +450,22 @@ defmodule ReencodarrWeb.VideosLive do # Private helpers # --------------------------------------------------------------------------- - defp load_data(socket) do + defp load_data(socket, opts \\ []) do page_state = - VideosState.load(%{ - page: socket.assigns.page, - per_page: socket.assigns.per_page, - state_filter: socket.assigns.state_filter, - service_filter: socket.assigns.service_filter, - hdr_filter: socket.assigns.hdr_filter, - search: socket.assigns.search, - sort_by: socket.assigns.sort_by, - sort_dir: socket.assigns.sort_dir - }) + VideosState.load( + %{ + state_counts: socket.assigns.state_counts, + page: socket.assigns.page, + per_page: socket.assigns.per_page, + state_filter: socket.assigns.state_filter, + service_filter: socket.assigns.service_filter, + hdr_filter: socket.assigns.hdr_filter, + search: socket.assigns.search, + sort_by: socket.assigns.sort_by, + sort_dir: socket.assigns.sort_dir + }, + opts + ) assign_changed(socket, Map.put(page_state, :loading, false)) end @@ -477,6 +476,14 @@ defmodule ReencodarrWeb.VideosLive do |> assign(:loaded_once, true) end + defp apply_video_state_on_page(videos, ids, new_state) do + ids = MapSet.new(ids) + + Enum.map(videos, fn video -> + if MapSet.member?(ids, video.id), do: %{video | state: new_state}, else: video + end) + end + defp reset_video_by_id(id) do case Media.get_video(id) do nil -> :ok @@ -750,18 +757,17 @@ defmodule ReencodarrWeb.VideosLive do
-
- -
- -
+ +
+ +
-
- -
-
- -