diff --git a/src/components/DuplicateFinder.tsx b/src/components/DuplicateFinder.tsx index 67b361b..ce9c344 100644 --- a/src/components/DuplicateFinder.tsx +++ b/src/components/DuplicateFinder.tsx @@ -1,6 +1,7 @@ import { useState } from "react"; import { convertFileSrc } from "@tauri-apps/api/core"; import { DuplicateGroup, useGalleryStore } from "../store"; +import { FolderScopeDropdown } from "./FolderScopeDropdown"; function formatBytes(bytes: number): string { if (bytes >= 1_073_741_824) return `${(bytes / 1_073_741_824).toFixed(1)} GB`; @@ -188,6 +189,7 @@ export function DuplicateFinder() { )}
+ {/* Batch select — only shown when there are groups and nothing is selected yet */} {hasResults && selectedCount === 0 && !deleting && ( + {open ? ( +
+ + {folders.map((folder) => { + const active = selectedFolderId === folder.id; + return ( + + ); + })} +
+ ) : null} +
+ ); +} diff --git a/src/components/TagCloud.tsx b/src/components/TagCloud.tsx index 43d2fcc..fffae2f 100644 --- a/src/components/TagCloud.tsx +++ b/src/components/TagCloud.tsx @@ -2,6 +2,7 @@ import { useEffect, useLayoutEffect, useMemo, useRef, useState } from "react"; import { motion } from "framer-motion"; import { convertFileSrc } from "@tauri-apps/api/core"; import { ExploreTagEntry, TagCloudEntry, useGalleryStore } from "../store"; +import { FolderScopeDropdown } from "./FolderScopeDropdown"; const ACCENTS = [ "#60a5fa", @@ -317,23 +318,26 @@ export function TagCloud() { : "No tags — run the AI tagger or add tags manually"}

-
- - +
+ +
+ + +
diff --git a/src/components/Toolbar.tsx b/src/components/Toolbar.tsx index 02f4041..c276410 100644 --- a/src/components/Toolbar.tsx +++ b/src/components/Toolbar.tsx @@ -1,6 +1,7 @@ import { useEffect, useRef, useState } from "react"; import { invoke } from "@tauri-apps/api/core"; import { tileSizeForZoom, useGalleryStore, SortOrder, MediaFilter, SearchCommand, parseSearchValue, searchModeLabel, ExploreTagEntry } from "../store"; +import { FolderScopeDropdown } from "./FolderScopeDropdown"; const BASE_SORT_OPTIONS: { value: SortOrder; label: string }[] = [ { value: "date_desc", label: "Newest first" }, @@ -162,6 +163,7 @@ export function Toolbar() { const mediaJobProgress = useGalleryStore((state) => state.mediaJobProgress); const zoomPreset = useGalleryStore((state) => state.zoomPreset); const setZoomPreset = useGalleryStore((state) => state.setZoomPreset); + const activeView = useGalleryStore((state) => state.activeView); const hasAnyFailedEmbeddings = Object.values(mediaJobProgress).some((p) => p.embedding_failed > 0); @@ -269,6 +271,8 @@ export function Toolbar() { )} + {activeView === "timeline" ? : null} +
{/* Search */} diff --git a/src/store.ts b/src/store.ts index 1fd5eb6..f5364b9 100644 --- a/src/store.ts +++ b/src/store.ts @@ -337,6 +337,7 @@ interface GalleryState { renameFolder: (folderId: number, newName: string) => Promise; updateFolderPath: (folderId: number, newPath: string) => Promise; selectFolder: (folderId: number | null) => void; + setViewFolderScope: (folderId: number | null) => void; loadImages: (reset?: boolean) => Promise; loadMoreImages: () => Promise; setSearch: (search: string) => void; @@ -755,6 +756,34 @@ export const useGalleryStore = create((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);