feat(library): tighten duplicates scope, drop in-app upload, polish loaders
- duplicates: restrict the /library/duplicates/groups query to photos whose folder path actually lives under an active SourceRoot in the user's settings. Nextcloud's "move to trash" flow was leaving .delete/purge-1 Folder rows wired to the original source_root_id, leaking those entries into the Duplicates view as ghost paths that the user never opted into. - discard: add a spinner to the "Delete N" and "Empty discard pile" buttons (and their confirm dialogs) while the destructive mutation is in flight, so the user gets immediate feedback for a slow operation. - timeline: render a bottom-of-grid "Loading more photos…" indicator while usePhotosQuery's background cursor loop is still pulling pages. Backed by a tiny Zustand store the loop drives via a balanced start/stop (counter, not boolean, so rapid filter changes can't flip the flag false while a fresh loop is alive). - remove client-side upload UI + /upload endpoint. Nextcloud is the authoritative ingress now; the duplicate path created confusion and the backend route is gone too. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -23,7 +23,6 @@ import {
|
||||
LogOut,
|
||||
Shield,
|
||||
Clock,
|
||||
Upload as UploadIcon,
|
||||
Download as DownloadIcon,
|
||||
} from 'lucide-react'
|
||||
import { DateRangePicker } from '../filter/DateRangePicker'
|
||||
@@ -48,7 +47,6 @@ import { formatApiError } from '../../lib/apiError'
|
||||
import type { Photo } from '../../types/photo'
|
||||
import { DeleteFolderDialog } from '../dialogs/DeleteFolderDialog'
|
||||
import { ShareDialog } from '../sharing/ShareDialog'
|
||||
import { UploadModal } from '../upload/UploadModal'
|
||||
import { sharing as sharingApi } from '../../services/api'
|
||||
import {
|
||||
useSharedFoldersQuery,
|
||||
@@ -134,14 +132,6 @@ export function LeftSidebar() {
|
||||
id: string
|
||||
name: string
|
||||
} | null>(null)
|
||||
// Upload modal state. `uploadTarget` stores the pre-selected
|
||||
// destination Folder/SourceRoot id so "Upload here…" on a folder row
|
||||
// drops files straight into that folder; null means the general
|
||||
// Library-level button (defaults to the first source root).
|
||||
const [uploadTarget, setUploadTarget] = useState<{ open: boolean; folderId: string | null }>({
|
||||
open: false,
|
||||
folderId: null,
|
||||
})
|
||||
const { data: sharedFolders = [] } = useSharedFoldersQuery()
|
||||
|
||||
// Bulk discard mutation for the drag-onto-Discarded interaction.
|
||||
@@ -715,14 +705,6 @@ export function LeftSidebar() {
|
||||
className="min-w-[180px]"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<DropdownMenuItem
|
||||
onClick={() =>
|
||||
setUploadTarget({ open: true, folderId })
|
||||
}
|
||||
>
|
||||
<UploadIcon className="h-3.5 w-3.5 text-text-muted" />
|
||||
Upload here…
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
onClick={() => {
|
||||
setCreatingUnder(folderId)
|
||||
@@ -861,14 +843,6 @@ export function LeftSidebar() {
|
||||
<div className="flex min-h-0 flex-1 flex-col">
|
||||
<div className="group flex h-9 flex-shrink-0 items-center gap-2 border-b border-border px-3 text-[11px] font-semibold uppercase tracking-[0.14em] text-text-muted">
|
||||
<span className="flex-1 truncate">Library</span>
|
||||
<button
|
||||
onClick={() => setUploadTarget({ open: true, folderId: null })}
|
||||
className="rounded p-0.5 text-text-muted hover:bg-surface-2 hover:text-text"
|
||||
title="Upload photos"
|
||||
aria-label="Upload photos"
|
||||
>
|
||||
<UploadIcon className="h-3.5 w-3.5" />
|
||||
</button>
|
||||
</div>
|
||||
<div className="min-h-0 flex-1 overflow-y-auto pb-2">
|
||||
{libraryTree.map((item) => renderTreeItem(item))}
|
||||
@@ -1027,11 +1001,6 @@ export function LeftSidebar() {
|
||||
targetName={sharingFolder?.name ?? ''}
|
||||
onClose={() => setSharingFolder(null)}
|
||||
/>
|
||||
<UploadModal
|
||||
isOpen={uploadTarget.open}
|
||||
initialFolderId={uploadTarget.folderId}
|
||||
onClose={() => setUploadTarget({ open: false, folderId: null })}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user