Skip to content

Commit 1f5c434

Browse files
committed
Add hardcoded family for display
1 parent 1baaf16 commit 1f5c434

3 files changed

Lines changed: 67 additions & 252 deletions

File tree

apps/probe-viewer/src/components/Sidebar.tsx

Lines changed: 66 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@ import { useEffect, useMemo, useState } from "react";
22

33
import { useAppStore } from "../state/useAppStore";
44
import type { ManifestEntry } from "../types/probe";
5-
import { groupNeuropixels, variantLabel } from "../utils/neuropixelsGrouping";
5+
import { groupEntries } from "../grouping/groupEntries";
6+
import { getGroupingConfig } from "../grouping";
7+
import type { GroupNode } from "../grouping/types";
68

79
const MANUFACTURER_DISPLAY_NAMES: Record<string, string> = {
810
cambridgeneurotech: "Cambridge NeuroTech",
@@ -61,43 +63,34 @@ export function Sidebar() {
6163
}
6264
}, [filteredEntries, selectedProbeId, selectProbe]);
6365

64-
// Neuropixels (imec) gets a hierarchy-grouped list (platform -> family);
66+
// A manufacturer with a grouping config (only IMEC today) gets a hierarchy;
6567
// every other manufacturer keeps the simple flat list.
66-
const isNeuropixels = selectedManufacturer === "imec";
68+
const groupingConfig = selectedManufacturer
69+
? getGroupingConfig(selectedManufacturer)
70+
: undefined;
6771
const groups = useMemo(
68-
() => (isNeuropixels ? groupNeuropixels(filteredEntries) : []),
69-
[isNeuropixels, filteredEntries],
72+
() => (groupingConfig ? groupEntries(filteredEntries, groupingConfig) : null),
73+
[groupingConfig, filteredEntries],
7074
);
7175

7276
// While searching, force every group open so matches aren't hidden inside a
7377
// collapsed group (filteredEntries already contains only the hits). When the
7478
// search clears, the user's manual expand/collapse state takes over again.
7579
const isSearching = searchQuery.trim().length > 0;
7680

77-
// Both levels start collapsed (empty expanded sets). A platform shows its
78-
// families only when expanded; a family shows its probes only when expanded.
79-
const [expandedPlatforms, setExpandedPlatforms] = useState<Set<string>>(
80-
() => new Set(),
81-
);
82-
const [expandedFamilies, setExpandedFamilies] = useState<Set<string>>(
83-
() => new Set(),
84-
);
85-
const togglePlatform = (platform: string) =>
86-
setExpandedPlatforms((prev) => {
87-
const next = new Set(prev);
88-
if (next.has(platform)) next.delete(platform);
89-
else next.add(platform);
90-
return next;
91-
});
92-
const toggleFamily = (key: string) =>
93-
setExpandedFamilies((prev) => {
81+
// Every collapsible node starts collapsed. Expansion is keyed by the node's
82+
// full path (joined ancestor labels), so the same key is stable across the
83+
// arbitrary-depth hierarchy.
84+
const [expanded, setExpanded] = useState<Set<string>>(() => new Set());
85+
const toggle = (key: string) =>
86+
setExpanded((prev) => {
9487
const next = new Set(prev);
9588
if (next.has(key)) next.delete(key);
9689
else next.add(key);
9790
return next;
9891
});
9992

100-
const renderItem = (entry: ManifestEntry, showVariant: boolean) => (
93+
const renderItem = (entry: ManifestEntry) => (
10194
<button
10295
key={entry.id}
10396
type="button"
@@ -108,18 +101,58 @@ export function Sidebar() {
108101
}
109102
onClick={() => selectProbe(entry.id)}
110103
>
111-
<span className="sidebar-item-name">
112-
{entry.displayName}
113-
{showVariant && variantLabel(entry) ? (
114-
<span className="sidebar-item-variant"> {variantLabel(entry)}</span>
115-
) : null}
116-
</span>
104+
<span className="sidebar-item-name">{entry.displayName}</span>
117105
<span className="sidebar-item-meta">
118106
{entry.contactCount} contacts · {entry.shankCount} shanks
119107
</span>
120108
</button>
121109
);
122110

111+
// Renders one node of the grouped tree at any depth. A collapsible node is a
112+
// toggle header; a non-collapsible one is a static divider. Open nodes recurse
113+
// into their children, or list their entries when they are leaves.
114+
const renderNode = (node: GroupNode, depth: number, parentPath: string[]) => {
115+
const path = [...parentPath, node.label];
116+
const key = path.join("||");
117+
const open = !node.collapsible || isSearching || expanded.has(key);
118+
const wrapClass =
119+
depth === 0
120+
? "sidebar-group"
121+
: depth === 1
122+
? "sidebar-subgroup"
123+
: "sidebar-subdivision";
124+
return (
125+
<div className={wrapClass} key={key}>
126+
{node.collapsible ? (
127+
<button
128+
type="button"
129+
className={
130+
depth === 0 ? "sidebar-group-header" : "sidebar-subgroup-header"
131+
}
132+
aria-expanded={open}
133+
onClick={() => toggle(key)}
134+
>
135+
<span className="sidebar-group-caret">{open ? "▾" : "▸"}</span>
136+
<span
137+
className={
138+
depth === 0 ? "sidebar-group-title" : "sidebar-subgroup-title"
139+
}
140+
>
141+
{node.label}
142+
</span>
143+
<span className="sidebar-group-count">{node.count}</span>
144+
</button>
145+
) : (
146+
<p className="sidebar-subgroup-divider">{node.label}</p>
147+
)}
148+
{open &&
149+
(node.children
150+
? node.children.map((child) => renderNode(child, depth + 1, path))
151+
: node.entries?.map((entry) => renderItem(entry)))}
152+
</div>
153+
);
154+
};
155+
123156
return (
124157
<div className="sidebar">
125158
<header className="sidebar-header">
@@ -173,71 +206,12 @@ export function Sidebar() {
173206
)}
174207

175208
{manifestStatus === "success" &&
176-
!isNeuropixels &&
177-
filteredEntries.map((entry) => renderItem(entry, false))}
209+
!groups &&
210+
filteredEntries.map((entry) => renderItem(entry))}
178211

179212
{manifestStatus === "success" &&
180-
isNeuropixels &&
181-
groups.map((group) => {
182-
const platformOpen = isSearching || expandedPlatforms.has(group.platform);
183-
return (
184-
<div className="sidebar-group" key={group.platform}>
185-
<button
186-
type="button"
187-
className="sidebar-group-header"
188-
aria-expanded={platformOpen}
189-
onClick={() => togglePlatform(group.platform)}
190-
>
191-
<span className="sidebar-group-caret">
192-
{platformOpen ? "▾" : "▸"}
193-
</span>
194-
<span className="sidebar-group-title">{group.platform}</span>
195-
<span className="sidebar-group-count">{group.count}</span>
196-
</button>
197-
{platformOpen &&
198-
group.families.map((fam) => {
199-
const familyKey = `${group.platform}||${fam.family}`;
200-
const familyOpen = isSearching || expandedFamilies.has(familyKey);
201-
return (
202-
<div className="sidebar-subgroup" key={fam.family}>
203-
<button
204-
type="button"
205-
className="sidebar-subgroup-header"
206-
aria-expanded={familyOpen}
207-
onClick={() => toggleFamily(familyKey)}
208-
>
209-
<span className="sidebar-group-caret">
210-
{familyOpen ? "▾" : "▸"}
211-
</span>
212-
<span className="sidebar-subgroup-title">
213-
{fam.family}
214-
</span>
215-
<span className="sidebar-group-count">
216-
{fam.entries.length}
217-
</span>
218-
</button>
219-
{familyOpen &&
220-
fam.subgroups.map((sub) => (
221-
<div
222-
className="sidebar-subdivision"
223-
key={sub.label || "_flat"}
224-
>
225-
{sub.label && (
226-
<p className="sidebar-subgroup-divider">
227-
{sub.label}
228-
</p>
229-
)}
230-
{sub.entries.map((entry) =>
231-
renderItem(entry, true),
232-
)}
233-
</div>
234-
))}
235-
</div>
236-
);
237-
})}
238-
</div>
239-
);
240-
})}
213+
groups &&
214+
groups.map((node) => renderNode(node, 0, []))}
241215
</div>
242216
</div>
243217
);

apps/probe-viewer/src/utils/neuropixelsGrouping.ts

Lines changed: 0 additions & 160 deletions
This file was deleted.

apps/probe-viewer/tsconfig.app.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
"module": "ESNext",
88
"types": ["vite/client"],
99
"skipLibCheck": true,
10+
"resolveJsonModule": true,
1011

1112
/* Bundler mode */
1213
"moduleResolution": "bundler",

0 commit comments

Comments
 (0)