mirror of
https://github.com/xCyanGrizzly/DragonsStash.git
synced 2026-05-11 06:11:15 +00:00
feat: highlight matching files in package drawer when opened from search
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -52,6 +52,7 @@ interface PackageFilesDrawerProps {
|
|||||||
pkg: PackageRow | null;
|
pkg: PackageRow | null;
|
||||||
open: boolean;
|
open: boolean;
|
||||||
onOpenChange: (open: boolean) => void;
|
onOpenChange: (open: boolean) => void;
|
||||||
|
highlightTerm?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatBytes(bytesStr: string): string {
|
function formatBytes(bytesStr: string): string {
|
||||||
@@ -81,6 +82,15 @@ function getExtBadgeClass(ext: string | null): string {
|
|||||||
return EXTENSION_COLORS[ext.toLowerCase()] ?? "bg-zinc-500/15 text-zinc-400 border-zinc-500/30";
|
return EXTENSION_COLORS[ext.toLowerCase()] ?? "bg-zinc-500/15 text-zinc-400 border-zinc-500/30";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function fileMatchesHighlight(file: FileItem, term: string): boolean {
|
||||||
|
if (!term) return false;
|
||||||
|
const lower = term.toLowerCase();
|
||||||
|
return (
|
||||||
|
file.fileName.toLowerCase().includes(lower) ||
|
||||||
|
file.path.toLowerCase().includes(lower)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Build a tree structure from flat file paths.
|
* Build a tree structure from flat file paths.
|
||||||
*/
|
*/
|
||||||
@@ -120,11 +130,13 @@ function TreeNodeView({
|
|||||||
depth,
|
depth,
|
||||||
search,
|
search,
|
||||||
defaultOpen,
|
defaultOpen,
|
||||||
|
highlightTerm,
|
||||||
}: {
|
}: {
|
||||||
node: TreeNode;
|
node: TreeNode;
|
||||||
depth: number;
|
depth: number;
|
||||||
search: string;
|
search: string;
|
||||||
defaultOpen: boolean;
|
defaultOpen: boolean;
|
||||||
|
highlightTerm?: string;
|
||||||
}) {
|
}) {
|
||||||
const [open, setOpen] = useState(defaultOpen);
|
const [open, setOpen] = useState(defaultOpen);
|
||||||
|
|
||||||
@@ -137,10 +149,22 @@ function TreeNodeView({
|
|||||||
});
|
});
|
||||||
}, [node.children]);
|
}, [node.children]);
|
||||||
|
|
||||||
// If searching, force all open
|
const hasHighlightedDescendant = useMemo(() => {
|
||||||
|
if (!highlightTerm) return false;
|
||||||
|
function check(n: TreeNode): boolean {
|
||||||
|
if (n.file && fileMatchesHighlight(n.file, highlightTerm!)) return true;
|
||||||
|
for (const child of n.children.values()) {
|
||||||
|
if (check(child)) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return check(node);
|
||||||
|
}, [node, highlightTerm]);
|
||||||
|
|
||||||
|
// If searching or has highlighted descendants, force all open
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (search) setOpen(true);
|
if (search || hasHighlightedDescendant) setOpen(true);
|
||||||
}, [search]);
|
}, [search, hasHighlightedDescendant]);
|
||||||
|
|
||||||
if (node.isFolder && node.children.size > 0) {
|
if (node.isFolder && node.children.size > 0) {
|
||||||
return (
|
return (
|
||||||
@@ -177,6 +201,7 @@ function TreeNodeView({
|
|||||||
depth={depth + 1}
|
depth={depth + 1}
|
||||||
search={search}
|
search={search}
|
||||||
defaultOpen={depth < 1} // Auto-expand first 2 levels
|
defaultOpen={depth < 1} // Auto-expand first 2 levels
|
||||||
|
highlightTerm={highlightTerm}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
@@ -185,9 +210,15 @@ function TreeNodeView({
|
|||||||
|
|
||||||
// File node
|
// File node
|
||||||
if (node.file) {
|
if (node.file) {
|
||||||
|
const isHighlighted = highlightTerm ? fileMatchesHighlight(node.file, highlightTerm) : false;
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="flex items-center gap-2 rounded-md px-1 py-1 hover:bg-muted/50 transition-colors"
|
className={cn(
|
||||||
|
"flex items-center gap-2 rounded-md px-1 py-1 transition-colors",
|
||||||
|
isHighlighted
|
||||||
|
? "bg-amber-500/15 hover:bg-amber-500/20"
|
||||||
|
: "hover:bg-muted/50"
|
||||||
|
)}
|
||||||
style={{ paddingLeft: `${Math.max(0, depth) * 16 + 4}px` }}
|
style={{ paddingLeft: `${Math.max(0, depth) * 16 + 4}px` }}
|
||||||
>
|
>
|
||||||
<FileText className="h-3.5 w-3.5 shrink-0 text-muted-foreground" />
|
<FileText className="h-3.5 w-3.5 shrink-0 text-muted-foreground" />
|
||||||
@@ -223,7 +254,7 @@ function countFiles(node: TreeNode): number {
|
|||||||
|
|
||||||
const PAGE_SIZE = 100;
|
const PAGE_SIZE = 100;
|
||||||
|
|
||||||
export function PackageFilesDrawer({ pkg, open, onOpenChange }: PackageFilesDrawerProps) {
|
export function PackageFilesDrawer({ pkg, open, onOpenChange, highlightTerm }: PackageFilesDrawerProps) {
|
||||||
const [files, setFiles] = useState<FileItem[]>([]);
|
const [files, setFiles] = useState<FileItem[]>([]);
|
||||||
const [total, setTotal] = useState(0);
|
const [total, setTotal] = useState(0);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
@@ -471,16 +502,24 @@ export function PackageFilesDrawer({ pkg, open, onOpenChange }: PackageFilesDraw
|
|||||||
depth={0}
|
depth={0}
|
||||||
search={search}
|
search={search}
|
||||||
defaultOpen={true}
|
defaultOpen={true}
|
||||||
|
highlightTerm={highlightTerm}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
{/* Flat list for archives without folders */}
|
{/* Flat list for archives without folders */}
|
||||||
{filtered.map((file) => (
|
{filtered.map((file) => {
|
||||||
|
const isHighlighted = highlightTerm ? fileMatchesHighlight(file, highlightTerm) : false;
|
||||||
|
return (
|
||||||
<div
|
<div
|
||||||
key={file.id}
|
key={file.id}
|
||||||
className="flex items-center gap-3 rounded-md px-2 py-1.5 hover:bg-muted/50 transition-colors"
|
className={cn(
|
||||||
|
"flex items-center gap-3 rounded-md px-2 py-1.5 transition-colors",
|
||||||
|
isHighlighted
|
||||||
|
? "bg-amber-500/15 hover:bg-amber-500/20"
|
||||||
|
: "hover:bg-muted/50"
|
||||||
|
)}
|
||||||
>
|
>
|
||||||
<FileText className="h-3.5 w-3.5 shrink-0 text-muted-foreground" />
|
<FileText className="h-3.5 w-3.5 shrink-0 text-muted-foreground" />
|
||||||
<div className="min-w-0 flex-1">
|
<div className="min-w-0 flex-1">
|
||||||
@@ -500,7 +539,8 @@ export function PackageFilesDrawer({ pkg, open, onOpenChange }: PackageFilesDraw
|
|||||||
{formatBytes(file.uncompressedSize)}
|
{formatBytes(file.uncompressedSize)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
))}
|
);
|
||||||
|
})}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@@ -165,6 +165,7 @@ export function StlTable({
|
|||||||
onOpenChange={(open) => {
|
onOpenChange={(open) => {
|
||||||
if (!open) setViewPkg(null);
|
if (!open) setViewPkg(null);
|
||||||
}}
|
}}
|
||||||
|
highlightTerm={searchTerm}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user