diff --git a/web/src/lib/components/layout/HeapConvertDialog.svelte b/web/src/lib/components/layout/HeapConvertDialog.svelte index 983b6ca..971cc0a 100644 --- a/web/src/lib/components/layout/HeapConvertDialog.svelte +++ b/web/src/lib/components/layout/HeapConvertDialog.svelte @@ -24,7 +24,7 @@ type PpFolder } from '$lib/services/photoprism'; import { filters, setSection } from '$lib/stores/filters.svelte'; - import { isAuthenticated } from '$lib/stores/session.svelte'; + import { isAuthenticated, toOriginalsPath } from '$lib/stores/session.svelte'; import FolderTree, { buildTree } from './FolderTree.svelte'; interface Props { @@ -95,10 +95,13 @@ // pickedPath === '' is the root selection; falsy check would // wrongly block it. Distinguish `null` (nothing picked) from `''`. if (!heap || pickedPath === null) return; + // pickedPath is user-relative (listFolders strips BasePath). The + // sidecar moves files on disk so it needs a server-absolute path — + // translate before submitting. convertMut.mutate({ uid: heap.UID, body: { - targetFolder: pickedPath, + targetFolder: toOriginalsPath(pickedPath), mode, subfolder: subfolder.trim() || null, deleteHeap: mode === 'move' && deleteHeap diff --git a/web/src/lib/components/layout/LeftSidebar.svelte b/web/src/lib/components/layout/LeftSidebar.svelte index f0e3417..762ea96 100644 --- a/web/src/lib/components/layout/LeftSidebar.svelte +++ b/web/src/lib/components/layout/LeftSidebar.svelte @@ -34,7 +34,7 @@ setSection, type Section } from '$lib/stores/filters.svelte'; - import { isAuthenticated, session } from '$lib/stores/session.svelte'; + import { isAuthenticated, session, userBasePath } from '$lib/stores/session.svelte'; import FolderTree, { buildTree } from './FolderTree.svelte'; import GeneralSettingsDialog from './GeneralSettingsDialog.svelte'; import HeapConvertDialog from './HeapConvertDialog.svelte'; @@ -158,15 +158,24 @@ })); const folderCounts = $derived(folderCountsQuery.data ?? {}); - // Root entry shows the whole library — see applyFolderScope() on / - // timeline. PhotoPrism's `count.all` from /api/v1/config is the - // authoritative library total (kept in sync server-side), so use it - // directly. Earlier this subtracted Σ(folderCounts) from total, which - // worked when folderCounts were direct-child only; now that the - // sidecar fan-out recurses (see handlers_folders.go), every photo - // gets summed once per ancestor — the subtraction would double-count - // and drive rootCount to 0. - const rootCount = $derived(configQuery.data?.count?.all ?? 0); + // Root entry shows "the user's library" — for admins without a + // BasePath that's still the whole library, served cheaply from + // /api/v1/config's `count.all`. For any user with a non-empty + // BasePath the precomputed total is wrong (it's library-wide), so we + // ask the sidecar for a recursive count rooted at the user's + // BasePath — listFolderCounts maps `""` through toOriginalsPath, which + // resolves to the BasePath itself, and the sidecar fan-out recurses. + const scopedRootCountQuery = createQuery>(() => ({ + queryKey: ['photos', 'root-count', userBasePath()], + queryFn: () => listFolderCounts(['']), + enabled: isAuthenticated() && userBasePath() !== '', + staleTime: 60_000 + })); + const rootCount = $derived( + userBasePath() === '' + ? (configQuery.data?.count?.all ?? 0) + : (scopedRootCountQuery.data?.[''] ?? 0) + ); const createMut = createMutation(() => ({ mutationFn: (title: string) => createHeap(title), @@ -565,18 +574,6 @@ counts={folderCounts} /> {/if} - {#if filters.folderPath && filters.folderPath !== '/'} - - {/if}