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:
2026-04-09 16:31:52 +02:00
parent d6c667ae78
commit e51b93d59e
5 changed files with 131 additions and 116 deletions

View File

@@ -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}

View File

@@ -1,8 +1,7 @@
import { Settings, PanelLeftOpen, PanelRightOpen } from 'lucide-react'
import { PanelLeftOpen, PanelRightOpen } from 'lucide-react'
import muliLogo from '../../assets/muli-logo.png'
interface TopBarProps {
onOpenSettings: () => void
leftSidebarOpen: boolean
rightSidebarOpen: boolean
onExpandLeft: () => void
@@ -21,7 +20,6 @@ interface TopBarProps {
* collapse twin lives in the panel's own header.
*/
export function TopBar({
onOpenSettings,
leftSidebarOpen,
rightSidebarOpen,
onExpandLeft,
@@ -54,13 +52,6 @@ export function TopBar({
<PanelRightOpen className="h-4 w-4" />
</button>
)}
<button
onClick={onOpenSettings}
className="rounded p-1.5 text-text-muted transition-colors hover:bg-surface-2 hover:text-text"
title="Settings"
>
<Settings className="h-4 w-4" />
</button>
</div>
</header>
)