feat(views): in-view folder scope switching

Timeline, Explore, and Duplicates get a folder-scope dropdown in their
headers (Timeline via the shared Toolbar), so changing scope no longer
means leaving the feature and bouncing through the sidebar. The new
setViewFolderScope store action updates selectedFolderId while
preserving activeView: Timeline/Gallery reload images, Explore reloads
via its existing selectedFolderId effect, and Duplicates loads the
cached results for the new scope (fresh scans remain an explicit
Rescan). Sidebar behavior is unchanged — clicking a library still
opens its gallery.
This commit is contained in:
2026-06-12 12:39:04 +01:00
parent cd7dd89f00
commit b02bf1da2b
5 changed files with 154 additions and 17 deletions
+29
View File
@@ -337,6 +337,7 @@ interface GalleryState {
renameFolder: (folderId: number, newName: string) => Promise<void>;
updateFolderPath: (folderId: number, newPath: string) => Promise<void>;
selectFolder: (folderId: number | null) => void;
setViewFolderScope: (folderId: number | null) => void;
loadImages: (reset?: boolean) => Promise<void>;
loadMoreImages: () => Promise<void>;
setSearch: (search: string) => void;
@@ -755,6 +756,34 @@ export const useGalleryStore = create<GalleryState>((set, get) => ({
void get().loadImages(true);
},
// Change folder scope from inside a feature view (Timeline/Explore/Duplicates)
// without leaving it — unlike selectFolder, activeView is preserved.
setViewFolderScope: (folderId) => {
const { activeView, selectedFolderId } = get();
if (folderId === selectedFolderId) return;
set({ selectedFolderId: folderId, images: [], loadedCount: 0, collectionTitle: null, similarSourceImageId: null, similarHasMore: false, imageLoadError: null });
if (activeView === "duplicates") {
const { duplicateScanFolderId } = get();
if (duplicateScanFolderId !== folderId) {
set({
duplicateGroups: [],
duplicateLastScanned: null,
duplicateScanFolderId: undefined,
duplicateScanWarning: null,
});
void get().loadDuplicateScanCache(folderId);
}
return;
}
// Explore reloads itself via TagCloud's useEffect on selectedFolderId.
if (activeView === "explore") return;
void get().loadImages(true);
},
loadImages: async (reset = false) => {
const { selectedFolderId, search, sort, loadedCount, mediaFilter, favoritesOnly, minimumRating, failedEmbeddingsOnly } = get();
const parsedSearch = parseSearchValue(search);