feat(ui): add recent section toggle

This commit is contained in:
Bohdan Triapitsyn
2026-05-03 16:32:14 +03:00
parent 5614012acb
commit 43f98e0344
9 changed files with 55 additions and 16 deletions
@@ -58,6 +58,7 @@ import {
} from './sidebar/ConfirmDialogs';
import { BulkActionBar } from './sidebar/BulkActionBar';
import { useSessionMultiSelectStore } from '@/stores/useSessionMultiSelectStore';
import { useSessionDisplayStore } from '@/stores/useSessionDisplayStore';
import { type SessionGroup, type SessionNode } from './sidebar/types';
import {
type ActiveNowEntry,
@@ -996,19 +997,27 @@ export const SessionSidebar: React.FC<SessionSidebarProps> = ({
return meta;
}, [projectSections, homeDirectory]);
const activeNowSessions = React.useMemo(
() => deriveActiveNowSessions(activeNowEntries, new Map(sessions.map((session) => [session.id, session])))
.sort((a, b) => compareSessionsByPinnedAndTime(a, b, pinnedSessionIds)),
[activeNowEntries, pinnedSessionIds, sessions],
);
const showRecentSection = useSessionDisplayStore((state) => state.showRecentSection);
const liveActiveSessions = React.useMemo(
() => deriveLiveActiveNowSessions(sessions, liveSessionStatuses),
[liveSessionStatuses, sessions],
);
const activeNowSessions = React.useMemo(() => {
if (!showRecentSection) {
return [];
}
return deriveActiveNowSessions(activeNowEntries, new Map(sessions.map((session) => [session.id, session])))
.sort((a, b) => compareSessionsByPinnedAndTime(a, b, pinnedSessionIds));
}, [activeNowEntries, pinnedSessionIds, sessions, showRecentSection]);
const liveActiveSessions = React.useMemo(() => {
if (!showRecentSection) {
return [];
}
return deriveLiveActiveNowSessions(sessions, liveSessionStatuses);
}, [liveSessionStatuses, sessions, showRecentSection]);
React.useEffect(() => {
if (liveActiveSessions.length === 0) {
if (!showRecentSection || liveActiveSessions.length === 0) {
return;
}
@@ -1020,9 +1029,13 @@ export const SessionSidebar: React.FC<SessionSidebarProps> = ({
persistActiveNowEntries(safeStorage, next);
return next;
});
}, [liveActiveSessions, safeStorage]);
}, [liveActiveSessions, safeStorage, showRecentSection]);
React.useEffect(() => {
if (!showRecentSection) {
return;
}
const allKnownSessionsById = new Map<string, Session>();
[...sessions, ...archivedSessions].forEach((session) => {
allKnownSessionsById.set(session.id, session);
@@ -1035,11 +1048,15 @@ export const SessionSidebar: React.FC<SessionSidebarProps> = ({
setActiveNowEntries(pruned);
persistActiveNowEntries(safeStorage, pruned);
}, [activeNowEntries, archivedSessions, safeStorage, sessions]);
}, [activeNowEntries, archivedSessions, safeStorage, sessions, showRecentSection]);
// Prefetch is wired below, after recentSessionIds is computed.
const activitySections = React.useMemo(() => {
if (!showRecentSection) {
return [];
}
const toItem = (session: Session) => {
const existing = sessionSidebarMetaById.get(session.id);
const sessionDirectory = normalizePath((session as Session & { directory?: string | null }).directory ?? null);
@@ -1054,11 +1071,11 @@ export const SessionSidebar: React.FC<SessionSidebarProps> = ({
return [
{ key: 'active-now' as const, title: t('sessions.sidebar.activity.recentTitle'), items: activeNowSessions.map(toItem) },
];
}, [activeNowSessions, sessionSidebarMetaById, t]);
}, [activeNowSessions, sessionSidebarMetaById, showRecentSection, t]);
const recentSessionIds = React.useMemo(() => {
return new Set(activitySections.flatMap((section) => section.items.map((item) => item.node.session.id)));
}, [activitySections]);
return new Set(activeNowSessions.map((session) => session.id));
}, [activeNowSessions]);
const recentSessionIdsList = React.useMemo(() => [...recentSessionIds], [recentSessionIds]);
@@ -1412,7 +1429,7 @@ export const SessionSidebar: React.FC<SessionSidebarProps> = ({
],
);
const topContent = !hasSessionSearchQuery ? (
const topContent = showRecentSection && !hasSessionSearchQuery ? (
<SidebarActivitySections
sections={activitySections}
renderSessionNode={renderSessionNode}
@@ -78,7 +78,9 @@ export function SidebarHeader(props: Props): React.ReactNode {
} = props;
const displayMode = useSessionDisplayStore((state) => state.displayMode);
const showRecentSection = useSessionDisplayStore((state) => state.showRecentSection);
const setDisplayMode = useSessionDisplayStore((state) => state.setDisplayMode);
const toggleRecentSection = useSessionDisplayStore((state) => state.toggleRecentSection);
if (hideDirectoryControls) {
return null;
@@ -240,6 +242,14 @@ export function SidebarHeader(props: Props): React.ReactNode {
{displayMode === 'minimal' ? <RiCheckLine className="h-4 w-4 text-primary" /> : null}
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem
onClick={toggleRecentSection}
className="flex items-center justify-between"
>
<span>{t('sessions.sidebar.header.displayMode.showRecent')}</span>
{showRecentSection ? <RiCheckLine className="h-4 w-4 text-primary" /> : null}
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem onClick={collapseAllProjects} className="flex items-center gap-2">
<RiContractUpDownLine className="h-4 w-4" />
<span>{t('sessions.sidebar.header.displayMode.collapseAll')}</span>
+1
View File
@@ -180,6 +180,7 @@ export const dict = {
'sessions.sidebar.header.displayMode.label': 'Display mode',
'sessions.sidebar.header.displayMode.default': 'Default',
'sessions.sidebar.header.displayMode.minimal': 'Minimal',
'sessions.sidebar.header.displayMode.showRecent': 'Show recent section',
'sessions.sidebar.header.displayMode.collapseAll': 'Collapse all',
'sessions.sidebar.header.displayMode.expandAll': 'Expand all',
'sessions.sidebar.header.search.matchCountSingle': '{count} match',
+1
View File
@@ -181,6 +181,7 @@ export const dict: Record<I18nKey, string> = {
"sessions.sidebar.header.displayMode.label": "Modo de visualización",
"sessions.sidebar.header.displayMode.default": "Predeterminado",
"sessions.sidebar.header.displayMode.minimal": "Mínimo",
"sessions.sidebar.header.displayMode.showRecent": "Mostrar recientes",
"sessions.sidebar.header.displayMode.collapseAll": "Colapsar todo",
"sessions.sidebar.header.displayMode.expandAll": "Expandir todo",
"sessions.sidebar.header.search.matchCountSingle": "{count} coincidencia",
+1
View File
@@ -181,6 +181,7 @@ export const dict: Record<I18nKey, string> = {
'sessions.sidebar.header.displayMode.label': '표시 모드',
'sessions.sidebar.header.displayMode.default': '기본값',
'sessions.sidebar.header.displayMode.minimal': '최소',
'sessions.sidebar.header.displayMode.showRecent': '최근 섹션 표시',
'sessions.sidebar.header.displayMode.collapseAll': '모두 접기',
'sessions.sidebar.header.displayMode.expandAll': '모두 펼치기',
'sessions.sidebar.header.search.matchCountSingle': '{count}개 일치',
@@ -181,6 +181,7 @@ export const dict: Record<I18nKey, string> = {
"sessions.sidebar.header.displayMode.label": "Modo de visualização",
"sessions.sidebar.header.displayMode.default": "Padrão",
"sessions.sidebar.header.displayMode.minimal": "Mínimo",
"sessions.sidebar.header.displayMode.showRecent": "Mostrar recentes",
"sessions.sidebar.header.displayMode.collapseAll": "Recolher tudo",
"sessions.sidebar.header.displayMode.expandAll": "Expandir tudo",
"sessions.sidebar.header.search.matchCountSingle": "{count} correspondência",
+1
View File
@@ -181,6 +181,7 @@ export const dict: Record<I18nKey, string> = {
"sessions.sidebar.header.displayMode.label": "Режим відображення",
"sessions.sidebar.header.displayMode.default": "За замовчуванням",
"sessions.sidebar.header.displayMode.minimal": "Мінімальний",
"sessions.sidebar.header.displayMode.showRecent": "Показувати нещодавні",
"sessions.sidebar.header.displayMode.collapseAll": "Згорнути все",
"sessions.sidebar.header.displayMode.expandAll": "Розгорнути все",
"sessions.sidebar.header.search.matchCountSingle": "{count} збіг",
@@ -181,6 +181,7 @@ export const dict: Record<I18nKey, string> = {
'sessions.sidebar.header.displayMode.label': '显示模式',
'sessions.sidebar.header.displayMode.default': '默认',
'sessions.sidebar.header.displayMode.minimal': '精简',
'sessions.sidebar.header.displayMode.showRecent': '显示最近部分',
'sessions.sidebar.header.displayMode.collapseAll': '全部折叠',
'sessions.sidebar.header.displayMode.expandAll': '全部展开',
'sessions.sidebar.header.search.matchCountSingle': '{count} 个匹配',
@@ -5,14 +5,20 @@ export type SessionDisplayMode = 'default' | 'minimal';
type SessionDisplayStore = {
displayMode: SessionDisplayMode;
showRecentSection: boolean;
setDisplayMode: (mode: SessionDisplayMode) => void;
setShowRecentSection: (show: boolean) => void;
toggleRecentSection: () => void;
};
export const useSessionDisplayStore = create<SessionDisplayStore>()(
persist(
(set) => ({
displayMode: 'default',
showRecentSection: true,
setDisplayMode: (mode) => set({ displayMode: mode }),
setShowRecentSection: (show) => set({ showRecentSection: show }),
toggleRecentSection: () => set((state) => ({ showRecentSection: !state.showRecentSection })),
}),
{
name: 'session-display-mode',