perf: speed up settings dialog + relocate settings entry point
- Parallelize the six celery inspect.*() calls in /library/maintenance/ worker-status via asyncio.gather + to_thread, and drop per-call timeout from 1.0s to 0.5s. Endpoint goes from ~6.1s to ~0.54s — it was the sole bottleneck on opening the Settings dialog. - SettingsDialog now fetches through React Query with enabled:isOpen, so reopening shows cached data instantly while a background refetch updates. Worker polling moved to refetchInterval. Loading spinners only show when there's no cached data yet, so background refetches don't keep them spinning. - Move the Settings entry point from the TopBar to a pinned row at the bottom of the LeftSidebar so it sits alongside the other library controls. TopBar no longer takes onOpenSettings. - Remove the "Scan all folders" bottom action from LeftSidebar — the same control already lives in Settings → Library → Re-scan. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -8,16 +8,16 @@ import {
|
||||
Star,
|
||||
Trash2,
|
||||
HardDrive,
|
||||
RefreshCw,
|
||||
Copy,
|
||||
Tag as TagIcon,
|
||||
Layers2,
|
||||
MoreHorizontal,
|
||||
Pencil,
|
||||
PanelLeftClose,
|
||||
Settings,
|
||||
} from 'lucide-react'
|
||||
import clsx from 'clsx'
|
||||
import { sourceFolders, library, photos as photosApi, type FolderTreeNode } from '../../services/api'
|
||||
import { sourceFolders, photos as photosApi, type FolderTreeNode } from '../../services/api'
|
||||
import { useMutation, useQueryClient } from '@tanstack/react-query'
|
||||
import { toast } from '../ToastContainer'
|
||||
import { useFilterStore } from '../../store/filterStore'
|
||||
@@ -44,11 +44,11 @@ interface TreeItem {
|
||||
|
||||
interface LeftSidebarProps {
|
||||
onCollapse: () => void
|
||||
onOpenSettings: () => void
|
||||
}
|
||||
|
||||
export function LeftSidebar({ onCollapse }: LeftSidebarProps) {
|
||||
export function LeftSidebar({ onCollapse, onOpenSettings }: LeftSidebarProps) {
|
||||
const [expandedItems, setExpandedItems] = useState<Set<string>>(new Set(['library', 'folders', 'heaps']))
|
||||
const [isScanning, setIsScanning] = useState(false)
|
||||
// Inline rename state for source-root rows. Stores the id being edited
|
||||
// and the draft name. Double-click a folder row to start.
|
||||
const [renamingId, setRenamingId] = useState<string | null>(null)
|
||||
@@ -309,30 +309,6 @@ export function LeftSidebar({ onCollapse }: LeftSidebarProps) {
|
||||
toast.error('Delete failed', e?.response?.data?.detail || e.message || 'Unknown error'),
|
||||
})
|
||||
|
||||
// Mutation for scanning all folders
|
||||
const scanLibraryMutation = useMutation({
|
||||
mutationFn: library.scan,
|
||||
onMutate: () => {
|
||||
setIsScanning(true)
|
||||
toast.info('Scan Started', 'Scanning all folders for new photos...')
|
||||
},
|
||||
onSuccess: () => {
|
||||
toast.success('Scan Complete', 'All folders have been scanned')
|
||||
},
|
||||
onError: (error: any) => {
|
||||
toast.error('Scan Failed', error.message || 'Failed to scan folders')
|
||||
},
|
||||
onSettled: () => {
|
||||
setIsScanning(false)
|
||||
// Refetch photos after scan
|
||||
queryClient.invalidateQueries({ queryKey: ['photos'] })
|
||||
},
|
||||
})
|
||||
|
||||
const handleScanAll = () => {
|
||||
scanLibraryMutation.mutate()
|
||||
}
|
||||
|
||||
const toggleExpanded = (id: string) => {
|
||||
const newExpanded = new Set(expandedItems)
|
||||
if (newExpanded.has(id)) {
|
||||
@@ -698,19 +674,18 @@ export function LeftSidebar({ onCollapse }: LeftSidebarProps) {
|
||||
<HeapsPanel />
|
||||
</div>
|
||||
|
||||
{/* Bottom Actions */}
|
||||
{folderTree.length > 0 && (
|
||||
<div className="border-t border-border p-3">
|
||||
<button
|
||||
onClick={handleScanAll}
|
||||
disabled={isScanning}
|
||||
className="flex w-full items-center gap-2 rounded bg-surface-2 px-3 py-2 text-sm text-text hover:bg-surface-offset disabled:opacity-50"
|
||||
>
|
||||
<RefreshCw className={clsx('h-4 w-4', isScanning && 'animate-spin')} />
|
||||
{isScanning ? 'Scanning...' : 'Scan all folders'}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
{/* Settings entry point — pinned to the bottom of the panel so it
|
||||
* sits out of the way of the library tree but is always reachable. */}
|
||||
<div className="border-t border-border p-2">
|
||||
<button
|
||||
onClick={onOpenSettings}
|
||||
className="flex w-full items-center gap-2 rounded px-2 py-1.5 text-sm text-text-muted hover:bg-surface-2 hover:text-text"
|
||||
title="Settings"
|
||||
>
|
||||
<Settings className="h-4 w-4" />
|
||||
Settings
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<DeleteFolderDialog
|
||||
isOpen={!!deletingFolder}
|
||||
|
||||
Reference in New Issue
Block a user