feat(SearchOverlay): add scroll position restoration and mounted paths tracking
This commit is contained in:
parent
6d12cebc05
commit
9a0268a6b4
|
|
@ -1,4 +1,4 @@
|
|||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { flushSync } from 'react-dom';
|
||||
import { X, Search, Loader2, RefreshCw, FolderSearch, SlidersHorizontal, ArrowUp, ArrowDown, ArrowUpDown, HardDrive, FolderOpen } from 'lucide-react';
|
||||
import { motion, AnimatePresence } from 'motion/react';
|
||||
|
|
@ -138,6 +138,7 @@ const _store = {
|
|||
filterLanguage: null as string | null,
|
||||
sortField: 'name' as SortField,
|
||||
sortDir: 'asc' as SortDir,
|
||||
scrollTop: 0,
|
||||
};
|
||||
|
||||
function FilterChips({
|
||||
|
|
@ -166,6 +167,7 @@ function FilterChips({
|
|||
}
|
||||
|
||||
export default function SearchOverlay({ config, setConfig, onClose, onOpenFolder }: SearchOverlayProps) {
|
||||
const scrollRef = useRef<HTMLDivElement>(null);
|
||||
const [query, setQuery] = useState(() => _store.query);
|
||||
const [isSearching, setIsSearching] = useState(false);
|
||||
const [isScanning, setIsScanning] = useState(false);
|
||||
|
|
@ -202,6 +204,26 @@ export default function SearchOverlay({ config, setConfig, onClose, onOpenFolder
|
|||
_store.sortDir = sortDir;
|
||||
}, [query, results, hasSearched, showFilter, filterSystem, filterVideo, filterLanguage, sortField, sortDir]);
|
||||
|
||||
// Restore scroll position after results render.
|
||||
useEffect(() => {
|
||||
if (_store.scrollTop > 0 && scrollRef.current) {
|
||||
scrollRef.current.scrollTop = _store.scrollTop;
|
||||
}
|
||||
}, []);
|
||||
|
||||
// Build a set of fully-resolved paths currently mounted on any IEC device.
|
||||
const mountedPaths = useMemo(() => {
|
||||
const paths = new Set<string>();
|
||||
for (const d of Object.values(config?.devices?.iec ?? {})) {
|
||||
const dev = d as any;
|
||||
if (!dev?.url) continue;
|
||||
const base = (dev.base_url ?? '').replace(/\/$/, '');
|
||||
const url = dev.url.startsWith('/') ? dev.url : '/' + dev.url;
|
||||
paths.add(base ? base + url : dev.url);
|
||||
}
|
||||
return paths;
|
||||
}, [config]);
|
||||
|
||||
const handleSearch = async () => {
|
||||
if (!query.trim()) { toast.error('Please enter a search term'); return; }
|
||||
setIsSearching(true);
|
||||
|
|
@ -426,17 +448,14 @@ export default function SearchOverlay({ config, setConfig, onClose, onOpenFolder
|
|||
</div>
|
||||
</div>
|
||||
|
||||
{/* DB status chip */}
|
||||
{dbPhase === 'ready' && !busy && (
|
||||
<p className="text-xs text-green-600 mt-2 flex items-center gap-1">
|
||||
<span className="w-1.5 h-1.5 rounded-full bg-green-500 inline-block" />
|
||||
Database ready
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Body */}
|
||||
<div className="flex-1 overflow-y-auto">
|
||||
<div
|
||||
ref={scrollRef}
|
||||
className="flex-1 overflow-y-auto"
|
||||
onScroll={e => { _store.scrollTop = (e.currentTarget as HTMLDivElement).scrollTop; }}
|
||||
>
|
||||
{/* Scanning progress */}
|
||||
{isScanning && scanPhase && (
|
||||
<div className="flex flex-col items-center justify-center py-16 gap-3">
|
||||
|
|
@ -477,6 +496,7 @@ export default function SearchOverlay({ config, setConfig, onClose, onOpenFolder
|
|||
entry={resultToEntry(result)}
|
||||
onPrimaryClick={() => setMountEntry(result)}
|
||||
onActionsClick={e => { e.stopPropagation(); setActionEntry(result); }}
|
||||
selected={mountedPaths.has(result.path)}
|
||||
nameSlot={
|
||||
<>
|
||||
<div className="flex items-center gap-1.5 flex-wrap">
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user