diff --git a/packages/ui/src/components/session/SessionSidebar.tsx b/packages/ui/src/components/session/SessionSidebar.tsx index 2dde7c94..726d7db2 100644 --- a/packages/ui/src/components/session/SessionSidebar.tsx +++ b/packages/ui/src/components/session/SessionSidebar.tsx @@ -743,6 +743,34 @@ export const SessionSidebar: React.FC = ({ }); }, []); + const collapseAllProjects = React.useCallback(() => { + ignoreIntersectionUntil.current = Date.now() + 150; + setCollapsedProjects(() => { + const allIds = new Set(projects.map((p) => p.id)); + try { + safeStorage.setItem(PROJECT_COLLAPSE_STORAGE_KEY, JSON.stringify(Array.from(allIds))); + } catch { /* ignored */ } + if (!isVSCode) { + scheduleCollapsedProjectsPersist(allIds); + } + return allIds; + }); + }, [projects, isVSCode, safeStorage, scheduleCollapsedProjectsPersist]); + + const expandAllProjects = React.useCallback(() => { + ignoreIntersectionUntil.current = Date.now() + 150; + setCollapsedProjects(() => { + const empty = new Set(); + try { + safeStorage.setItem(PROJECT_COLLAPSE_STORAGE_KEY, JSON.stringify([])); + } catch { /* ignored */ } + if (!isVSCode) { + scheduleCollapsedProjectsPersist(empty); + } + return empty; + }); + }, [isVSCode, safeStorage, scheduleCollapsedProjectsPersist]); + const toggleProject = React.useCallback((projectId: string) => { // Ignore intersection events for a short period after toggling ignoreIntersectionUntil.current = Date.now() + 150; @@ -996,53 +1024,6 @@ export const SessionSidebar: React.FC = ({ ]; }, [activeNowSessions, sessionSidebarMetaById]); - const activitySessionIds = React.useMemo(() => { - const next = new Set(); - activitySections.forEach((section) => { - section.items.forEach((item) => { - next.add(item.node.session.id); - }); - }); - return next; - }, [activitySections]); - - const filteredProjectSections = React.useMemo(() => { - if (hasSessionSearchQuery || activitySessionIds.size === 0) { - return projectSections; - } - - const filterNodes = (nodes: SessionNode[]): SessionNode[] => { - return nodes.flatMap((node) => { - if (activitySessionIds.has(node.session.id)) { - return []; - } - return [{ - ...node, - children: filterNodes(node.children), - }]; - }); - }; - - return projectSections.map((section) => ({ - ...section, - groups: section.groups.map((group) => ({ - ...group, - sessions: filterNodes(group.sessions), - })), - })); - }, [hasSessionSearchQuery, activitySessionIds, projectSections]); - - const filteredSectionsForRender = React.useMemo(() => { - if (hasSessionSearchQuery || activitySessionIds.size === 0) { - return sectionsForRender; - } - - const sectionsByProjectId = new Map(filteredProjectSections.map((section) => [section.project.id, section])); - return sectionsForRender - .map((section) => sectionsByProjectId.get(section.project.id) ?? section) - .filter(Boolean); - }, [hasSessionSearchQuery, activitySessionIds, filteredProjectSections, sectionsForRender]); - const desktopHeaderActionButtonClass = 'inline-flex h-6 w-6 cursor-pointer items-center justify-center rounded-md leading-none text-foreground hover:bg-interactive-hover focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/50 disabled:cursor-not-allowed'; const mobileHeaderActionButtonClass = @@ -1352,12 +1333,14 @@ export const SessionSidebar: React.FC = ({ setSessionSearchQuery={setSessionSearchQuery} hasSessionSearchQuery={hasSessionSearchQuery} searchMatchCount={searchMatchCount} + collapseAllProjects={collapseAllProjects} + expandAllProjects={expandAllProjects} /> void; hasSessionSearchQuery: boolean; searchMatchCount: number; + collapseAllProjects: () => void; + expandAllProjects: () => void; }; export function SidebarHeader(props: Props): React.ReactNode { @@ -47,6 +52,8 @@ export function SidebarHeader(props: Props): React.ReactNode { setSessionSearchQuery, hasSessionSearchQuery, searchMatchCount, + collapseAllProjects, + expandAllProjects, } = props; const displayMode = useSessionDisplayStore((state) => state.displayMode); @@ -136,6 +143,15 @@ export function SidebarHeader(props: Props): React.ReactNode { Minimal {displayMode === 'minimal' ? : null} + + + + Collapse all + + + + Expand all +