soulseek: open a filtered row only when the filter hit something below it
every row in the filtered skeleton was rendered open, so searching a band name unfolded each of its albums into a wall of tracks that matched nothing. a row now starts open only when the filter matched a descendant; one that matched on its own name stays shut, and opening it leaves the filter behind and browses its real contents. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -246,13 +246,17 @@ type TreeNodeProps = {
|
||||
};
|
||||
|
||||
const TreeNode = ({ username, node, filtered, indent, initialOpen }: TreeNodeProps) => {
|
||||
const [open, setOpen] = useState(initialOpen);
|
||||
const [limit, setLimit] = useState(LEVEL_LIMIT);
|
||||
const [revealed, setRevealed] = useState(false);
|
||||
|
||||
// Filtering stops at a revealed row: from here down it's ordinary lazy browsing, so one click gets you
|
||||
// out of the filtered skeleton and into the real folder — which is usually why you searched.
|
||||
// Filtering stops at a revealed row: from here down it's ordinary lazy browsing, so opening a matched
|
||||
// folder shows what's really inside it rather than the filtered skeleton — which is why you searched.
|
||||
const scoped = revealed ? null : filtered;
|
||||
const scopedChildren = scoped?.childrenOf.get(node.name) ?? [];
|
||||
|
||||
// A filtered row starts open only when the filter matched something below it. One that matched on its own
|
||||
// name has nothing of its own to show, so it stays shut and behaves like any other collapsed folder.
|
||||
const [open, setOpen] = useState(() => (filtered ? scopedChildren.length > 0 : initialOpen));
|
||||
|
||||
const expandable = node.childCount > 0 || node.fileCount > 0;
|
||||
const level = useSoulseekBrowseLevel({
|
||||
@@ -262,7 +266,7 @@ const TreeNode = ({ username, node, filtered, indent, initialOpen }: TreeNodePro
|
||||
enabled: !scoped && open && node.childCount > 0,
|
||||
});
|
||||
|
||||
const children = scoped ? (scoped.childrenOf.get(node.name) ?? []) : (level.data?.nodes ?? []);
|
||||
const children = scoped ? scopedChildren : (level.data?.nodes ?? []);
|
||||
// In the filtered skeleton no level was fetched, so the row's own count is what says how many exist.
|
||||
const total = scoped ? node.childCount : (level.data?.total ?? node.childCount);
|
||||
const capped = !scoped && limit >= LEVEL_LIMIT_MAX;
|
||||
@@ -271,11 +275,18 @@ const TreeNode = ({ username, node, filtered, indent, initialOpen }: TreeNodePro
|
||||
const more = open && total > children.length && (!!scoped || !level.isPending);
|
||||
const isMatch = filtered?.matched.has(node.name) ?? false;
|
||||
|
||||
const toggle = () => {
|
||||
// Opening a matched row with nothing matched below it leaves the filter behind, rather than opening
|
||||
// into an empty folder.
|
||||
if (!open && scoped && !scopedChildren.length) setRevealed(true);
|
||||
setOpen((v) => !v);
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => expandable && setOpen((v) => !v)}
|
||||
onClick={() => expandable && toggle()}
|
||||
style={{ paddingLeft: 12 + indent * 14 }}
|
||||
className={`flex w-full items-center gap-1.5 py-1.5 pr-4 text-left text-sm transition-colors hover:bg-white/[0.03] ${
|
||||
expandable ? '' : 'cursor-default'
|
||||
|
||||
Reference in New Issue
Block a user