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 TreeNode = ({ username, node, filtered, indent, initialOpen }: TreeNodeProps) => {
|
||||||
const [open, setOpen] = useState(initialOpen);
|
|
||||||
const [limit, setLimit] = useState(LEVEL_LIMIT);
|
const [limit, setLimit] = useState(LEVEL_LIMIT);
|
||||||
const [revealed, setRevealed] = useState(false);
|
const [revealed, setRevealed] = useState(false);
|
||||||
|
|
||||||
// Filtering stops at a revealed row: from here down it's ordinary lazy browsing, so one click gets you
|
// Filtering stops at a revealed row: from here down it's ordinary lazy browsing, so opening a matched
|
||||||
// out of the filtered skeleton and into the real folder — which is usually why you searched.
|
// folder shows what's really inside it rather than the filtered skeleton — which is why you searched.
|
||||||
const scoped = revealed ? null : filtered;
|
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 expandable = node.childCount > 0 || node.fileCount > 0;
|
||||||
const level = useSoulseekBrowseLevel({
|
const level = useSoulseekBrowseLevel({
|
||||||
@@ -262,7 +266,7 @@ const TreeNode = ({ username, node, filtered, indent, initialOpen }: TreeNodePro
|
|||||||
enabled: !scoped && open && node.childCount > 0,
|
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.
|
// 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 total = scoped ? node.childCount : (level.data?.total ?? node.childCount);
|
||||||
const capped = !scoped && limit >= LEVEL_LIMIT_MAX;
|
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 more = open && total > children.length && (!!scoped || !level.isPending);
|
||||||
const isMatch = filtered?.matched.has(node.name) ?? false;
|
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 (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => expandable && setOpen((v) => !v)}
|
onClick={() => expandable && toggle()}
|
||||||
style={{ paddingLeft: 12 + indent * 14 }}
|
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] ${
|
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'
|
expandable ? '' : 'cursor-default'
|
||||||
|
|||||||
Reference in New Issue
Block a user