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