From f7d9cb0e01e9c1aaa8ca40889f5135a16bb22229 Mon Sep 17 00:00:00 2001 From: hmjn Date: Sun, 2 Aug 2026 14:05:32 +0900 Subject: [PATCH 01/12] perf(ui): improve v2 media grid loading Avoid mounting the full result set while virtual grid metrics are pending, prioritize the first two rows, and prefetch V2 filter metadata like the existing routes. --- apps/server/src/routes/search.tsx | 4 +- .../components/source-media-page.tsx | 1 + apps/server/src/routes/v2/search.tsx | 13 ++++ .../v2/sources/$mediaSourceId/index.tsx | 20 ++++++ apps/tauri/src/routes/search.tsx | 3 +- .../components/source-media-page.tsx | 3 +- packages/ui/src/screens/search-screen.tsx | 10 ++- .../ui/src/screens/source-media-screen.tsx | 1 + packages/ui/src/source-media-grid.tsx | 61 +++++++++++++------ 9 files changed, 92 insertions(+), 24 deletions(-) diff --git a/apps/server/src/routes/search.tsx b/apps/server/src/routes/search.tsx index 4c992f29..9f72ad77 100644 --- a/apps/server/src/routes/search.tsx +++ b/apps/server/src/routes/search.tsx @@ -119,7 +119,9 @@ function SearchRoute() { onSelectSource={(id) => setSearchState("selectedSource", id)} page={page} presetClient={PresetClient} - renderMediaItem={(media) => } + renderMediaItem={(media, options) => ( + + )} renderNavActions={({ openMobileFilters }) => ( + +
+ {manager().taggingStatus()} +
+
+ + {(progress) => ( +
+
+ Batch progress + + {progress().processed} / {progress().total} + +
+ 0 + ? (progress().processed / progress().total) * 100 + : 0 + } + /> +
+ )} +
+ + + + diff --git a/packages/ui/src/screens/search-screen.tsx b/packages/ui/src/screens/search-screen.tsx index 9c71d05d..7b318256 100644 --- a/packages/ui/src/screens/search-screen.tsx +++ b/packages/ui/src/screens/search-screen.tsx @@ -10,6 +10,7 @@ import type { UseSearchPageResult, } from "../hooks/use-search-page"; import type { SourceMediaPagePresetClient } from "../hooks/use-source-media-page"; +import type { MediaGridImageLoadPolicy } from "../media-grid-item"; import { MobileSearchFilterDialog } from "../mobile-search-filter-dialog"; import { SearchControlPanel } from "../search-control-panel"; import { LoadingRegion, MediaGridSkeleton } from "../skeleton"; @@ -32,6 +33,7 @@ export type SearchScreenProps = { renderMediaItem: ( media: Media, options?: { + imageLoadPolicy?: MediaGridImageLoadPolicy; onPreviewSelect?: () => void; isPreviewSelected?: boolean; priority?: boolean; diff --git a/packages/ui/src/screens/source-media-screen.tsx b/packages/ui/src/screens/source-media-screen.tsx index 847c6c04..f0c772ad 100644 --- a/packages/ui/src/screens/source-media-screen.tsx +++ b/packages/ui/src/screens/source-media-screen.tsx @@ -17,6 +17,7 @@ import type { UploadOptions, UseSourceMediaPageResult, } from "../hooks/use-source-media-page"; +import type { MediaGridImageLoadPolicy } from "../media-grid-item"; import { MobileSearchFilterDialog } from "../mobile-search-filter-dialog"; import { SearchControlPanel } from "../search-control-panel"; import { LoadingRegion, MediaGridSkeleton } from "../skeleton"; @@ -33,6 +34,7 @@ export type SourceMediaScreenProps = { renderItem: ( media: Media, options: { + imageLoadPolicy?: MediaGridImageLoadPolicy; onContextMenu: () => void; priority?: boolean; isBulkSelectMode?: boolean; diff --git a/packages/ui/src/screens/v2-manager-screen.tsx b/packages/ui/src/screens/v2-manager-screen.tsx index 5450337b..c9c13999 100644 --- a/packages/ui/src/screens/v2-manager-screen.tsx +++ b/packages/ui/src/screens/v2-manager-screen.tsx @@ -127,6 +127,12 @@ const MANAGER_CATEGORIES: ManagerCategory[] = [ label: "Vector extraction", value: "vectors", }, + { + description: "Warm responsive image cache", + group: "Tools", + label: "Thumbnails", + value: "thumbnails", + }, { description: "Review matching media", group: "Tools", @@ -201,6 +207,8 @@ function ManagerCategoryIcon(props: { value: V2ManagerCategory }) { return