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 { flushSync } from 'react-dom';
|
||||||
import { X, Search, Loader2, RefreshCw, FolderSearch, SlidersHorizontal, ArrowUp, ArrowDown, ArrowUpDown, HardDrive, FolderOpen } from 'lucide-react';
|
import { X, Search, Loader2, RefreshCw, FolderSearch, SlidersHorizontal, ArrowUp, ArrowDown, ArrowUpDown, HardDrive, FolderOpen } from 'lucide-react';
|
||||||
import { motion, AnimatePresence } from 'motion/react';
|
import { motion, AnimatePresence } from 'motion/react';
|
||||||
|
|
@ -138,6 +138,7 @@ const _store = {
|
||||||
filterLanguage: null as string | null,
|
filterLanguage: null as string | null,
|
||||||
sortField: 'name' as SortField,
|
sortField: 'name' as SortField,
|
||||||
sortDir: 'asc' as SortDir,
|
sortDir: 'asc' as SortDir,
|
||||||
|
scrollTop: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
function FilterChips({
|
function FilterChips({
|
||||||
|
|
@ -166,6 +167,7 @@ function FilterChips({
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function SearchOverlay({ config, setConfig, onClose, onOpenFolder }: SearchOverlayProps) {
|
export default function SearchOverlay({ config, setConfig, onClose, onOpenFolder }: SearchOverlayProps) {
|
||||||
|
const scrollRef = useRef<HTMLDivElement>(null);
|
||||||
const [query, setQuery] = useState(() => _store.query);
|
const [query, setQuery] = useState(() => _store.query);
|
||||||
const [isSearching, setIsSearching] = useState(false);
|
const [isSearching, setIsSearching] = useState(false);
|
||||||
const [isScanning, setIsScanning] = useState(false);
|
const [isScanning, setIsScanning] = useState(false);
|
||||||
|
|
@ -202,6 +204,26 @@ export default function SearchOverlay({ config, setConfig, onClose, onOpenFolder
|
||||||
_store.sortDir = sortDir;
|
_store.sortDir = sortDir;
|
||||||
}, [query, results, hasSearched, showFilter, filterSystem, filterVideo, filterLanguage, sortField, 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 () => {
|
const handleSearch = async () => {
|
||||||
if (!query.trim()) { toast.error('Please enter a search term'); return; }
|
if (!query.trim()) { toast.error('Please enter a search term'); return; }
|
||||||
setIsSearching(true);
|
setIsSearching(true);
|
||||||
|
|
@ -426,17 +448,14 @@ export default function SearchOverlay({ config, setConfig, onClose, onOpenFolder
|
||||||
</div>
|
</div>
|
||||||
</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>
|
</div>
|
||||||
|
|
||||||
{/* Body */}
|
{/* 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 */}
|
{/* Scanning progress */}
|
||||||
{isScanning && scanPhase && (
|
{isScanning && scanPhase && (
|
||||||
<div className="flex flex-col items-center justify-center py-16 gap-3">
|
<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)}
|
entry={resultToEntry(result)}
|
||||||
onPrimaryClick={() => setMountEntry(result)}
|
onPrimaryClick={() => setMountEntry(result)}
|
||||||
onActionsClick={e => { e.stopPropagation(); setActionEntry(result); }}
|
onActionsClick={e => { e.stopPropagation(); setActionEntry(result); }}
|
||||||
|
selected={mountedPaths.has(result.path)}
|
||||||
nameSlot={
|
nameSlot={
|
||||||
<>
|
<>
|
||||||
<div className="flex items-center gap-1.5 flex-wrap">
|
<div className="flex items-center gap-1.5 flex-wrap">
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user