fix: sidebar dropdown works properly for sub-sessions
This commit is contained in:
@@ -130,6 +130,30 @@ const treeContainsSessionId = (node: SessionNode, sessionId: string | null): boo
|
|||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const treeContainsMenuKey = (
|
||||||
|
node: SessionNode,
|
||||||
|
menuKey: string | null,
|
||||||
|
renderContext: 'project' | 'recent',
|
||||||
|
archivedBucket: boolean,
|
||||||
|
): boolean => {
|
||||||
|
if (!menuKey) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const nodeMenuKey = `${renderContext}:${archivedBucket ? 'archived' : 'active'}:${node.session.id}`;
|
||||||
|
if (nodeMenuKey === menuKey) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const child of node.children) {
|
||||||
|
if (treeContainsMenuKey(child, menuKey, renderContext, archivedBucket)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
const areEqual = (prev: Props, next: Props): boolean => {
|
const areEqual = (prev: Props, next: Props): boolean => {
|
||||||
const prevSession = prev.node.session;
|
const prevSession = prev.node.session;
|
||||||
const nextSession = next.node.session;
|
const nextSession = next.node.session;
|
||||||
@@ -159,9 +183,9 @@ const areEqual = (prev: Props, next: Props): boolean => {
|
|||||||
if (prev.editTitle !== next.editTitle && ((prev.editingId === prevSessionId) || (next.editingId === nextSessionId))) return false;
|
if (prev.editTitle !== next.editTitle && ((prev.editingId === prevSessionId) || (next.editingId === nextSessionId))) return false;
|
||||||
if ((prev.copiedSessionId === prevSessionId) !== (next.copiedSessionId === nextSessionId)) return false;
|
if ((prev.copiedSessionId === prevSessionId) !== (next.copiedSessionId === nextSessionId)) return false;
|
||||||
|
|
||||||
const prevMenuKey = `${prev.renderContext ?? 'project'}:${prev.archivedBucket ? 'archived' : 'active'}:${prevSessionId}`;
|
const prevMenuInTree = treeContainsMenuKey(prev.node, prev.openSidebarMenuKey, prev.renderContext ?? 'project', prev.archivedBucket ?? false);
|
||||||
const nextMenuKey = `${next.renderContext ?? 'project'}:${next.archivedBucket ? 'archived' : 'active'}:${nextSessionId}`;
|
const nextMenuInTree = treeContainsMenuKey(next.node, next.openSidebarMenuKey, next.renderContext ?? 'project', next.archivedBucket ?? false);
|
||||||
if ((prev.openSidebarMenuKey === prevMenuKey) !== (next.openSidebarMenuKey === nextMenuKey)) return false;
|
if (prevMenuInTree !== nextMenuInTree) return false;
|
||||||
|
|
||||||
const prevDirectory = normalizePath((prevSession as Session & { directory?: string | null }).directory ?? null)
|
const prevDirectory = normalizePath((prevSession as Session & { directory?: string | null }).directory ?? null)
|
||||||
?? normalizePath(prev.groupDirectory ?? null);
|
?? normalizePath(prev.groupDirectory ?? null);
|
||||||
@@ -426,6 +450,18 @@ function SessionNodeItemComponent(props: Props): React.ReactNode {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleMenuTriggerClick = (event: React.MouseEvent<HTMLButtonElement>) => {
|
const handleMenuTriggerClick = (event: React.MouseEvent<HTMLButtonElement>) => {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
setOpenSidebarMenuKey(isMenuOpen ? null : menuInstanceKey);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleMenuTriggerPointerDown = (event: React.PointerEvent<HTMLButtonElement>) => {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleMenuTriggerMouseDown = (event: React.MouseEvent<HTMLButtonElement>) => {
|
||||||
|
event.preventDefault();
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -568,7 +604,7 @@ function SessionNodeItemComponent(props: Props): React.ReactNode {
|
|||||||
'flex min-w-0 flex-1 cursor-pointer flex-col gap-0 overflow-hidden rounded-sm text-left focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/50 text-foreground select-none disabled:cursor-not-allowed transition-[padding]',
|
'flex min-w-0 flex-1 cursor-pointer flex-col gap-0 overflow-hidden rounded-sm text-left focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/50 text-foreground select-none disabled:cursor-not-allowed transition-[padding]',
|
||||||
mobileVariant
|
mobileVariant
|
||||||
? (isVSCode ? revealPaddingClass : 'pr-7')
|
? (isVSCode ? revealPaddingClass : 'pr-7')
|
||||||
: '',
|
: revealPaddingClass,
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div className={cn('flex w-full items-center min-w-0 flex-1 overflow-hidden', isMinimalMode ? 'gap-1' : 'gap-1')}>
|
<div className={cn('flex w-full items-center min-w-0 flex-1 overflow-hidden', isMinimalMode ? 'gap-1' : 'gap-1')}>
|
||||||
@@ -585,25 +621,6 @@ function SessionNodeItemComponent(props: Props): React.ReactNode {
|
|||||||
)}>
|
)}>
|
||||||
{sessionCompactUpdatedLabel}
|
{sessionCompactUpdatedLabel}
|
||||||
</span>
|
</span>
|
||||||
<DropdownMenu open={isMenuOpen} onOpenChange={handleMenuOpenChange}>
|
|
||||||
<DropdownMenuTrigger asChild>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className={cn(
|
|
||||||
'absolute inset-y-0 right-0 inline-flex h-4 w-4 items-center justify-center rounded-md text-muted-foreground hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/50 transition-opacity',
|
|
||||||
isMenuOpen
|
|
||||||
? 'opacity-100 pointer-events-auto'
|
|
||||||
: cn('opacity-0 pointer-events-none', revealOnHoverClass),
|
|
||||||
)}
|
|
||||||
aria-label="Session menu"
|
|
||||||
onClick={handleMenuTriggerClick}
|
|
||||||
onKeyDown={(event) => event.stopPropagation()}
|
|
||||||
>
|
|
||||||
<RiMore2Line className="h-2.5 w-2.5" />
|
|
||||||
</button>
|
|
||||||
</DropdownMenuTrigger>
|
|
||||||
{sessionMenuContent}
|
|
||||||
</DropdownMenu>
|
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
{pendingPermissionCount > 0 ? (
|
{pendingPermissionCount > 0 ? (
|
||||||
@@ -683,41 +700,38 @@ function SessionNodeItemComponent(props: Props): React.ReactNode {
|
|||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
{!isMinimalMode || mobileVariant ? (
|
<div className={cn(
|
||||||
<div className={cn(
|
'absolute right-0 top-1/2 z-10 -translate-y-1/2 transition-opacity',
|
||||||
'absolute right-0 top-1/2 z-10 -translate-y-1/2',
|
isMenuOpen
|
||||||
cn(
|
? 'opacity-100'
|
||||||
'transition-opacity',
|
: (mobileVariant && !isVSCode)
|
||||||
isMenuOpen
|
? 'opacity-100'
|
||||||
? 'opacity-100 pointer-events-auto'
|
: cn('opacity-0', revealOnHoverClass),
|
||||||
: (mobileVariant && !isVSCode)
|
)}>
|
||||||
? 'opacity-100 pointer-events-auto'
|
<DropdownMenu open={isMenuOpen} onOpenChange={handleMenuOpenChange}>
|
||||||
: cn('opacity-0 pointer-events-none', revealOnHoverClass),
|
<DropdownMenuTrigger asChild>
|
||||||
),
|
<button
|
||||||
)}>
|
type="button"
|
||||||
<DropdownMenu open={isMenuOpen} onOpenChange={handleMenuOpenChange}>
|
className={cn(
|
||||||
<DropdownMenuTrigger asChild>
|
'inline-flex items-center justify-center rounded-md text-muted-foreground hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/50 transition-opacity',
|
||||||
<button
|
isMinimalMode && !mobileVariant
|
||||||
type="button"
|
? (isMenuOpen
|
||||||
className={cn(
|
? 'h-4 w-4 opacity-100'
|
||||||
'inline-flex items-center justify-center rounded-md text-muted-foreground hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/50 transition-opacity',
|
: cn('h-4 w-4 opacity-0', revealOnHoverClass))
|
||||||
isMinimalMode && !mobileVariant
|
: 'h-6 w-6 opacity-100',
|
||||||
? (isMenuOpen
|
)}
|
||||||
? 'h-4 w-4 opacity-100 pointer-events-auto'
|
aria-label="Session menu"
|
||||||
: cn('h-4 w-4 opacity-0 pointer-events-none', revealOnHoverClass))
|
onPointerDown={handleMenuTriggerPointerDown}
|
||||||
: 'h-6 w-6 opacity-100',
|
onMouseDown={handleMenuTriggerMouseDown}
|
||||||
)}
|
onClick={handleMenuTriggerClick}
|
||||||
aria-label="Session menu"
|
onKeyDown={(event) => event.stopPropagation()}
|
||||||
onClick={handleMenuTriggerClick}
|
>
|
||||||
onKeyDown={(event) => event.stopPropagation()}
|
<RiMore2Line className={cn(isMinimalMode && !mobileVariant ? 'h-2.5 w-2.5' : 'h-3.5 w-3.5')} />
|
||||||
>
|
</button>
|
||||||
<RiMore2Line className={cn(isMinimalMode && !mobileVariant ? 'h-2.5 w-2.5' : 'h-3.5 w-3.5')} />
|
</DropdownMenuTrigger>
|
||||||
</button>
|
{sessionMenuContent}
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenu>
|
||||||
{sessionMenuContent}
|
</div>
|
||||||
</DropdownMenu>
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
</div>
|
</div>
|
||||||
</DraggableSessionRow>
|
</DraggableSessionRow>
|
||||||
{hasChildren && isExpanded
|
{hasChildren && isExpanded
|
||||||
|
|||||||
@@ -118,6 +118,14 @@ export const SortableProjectItem: React.FC<SortableProjectItemProps> = ({
|
|||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const handleMenuTriggerPointerDown = React.useCallback((event: React.PointerEvent<HTMLButtonElement>) => {
|
||||||
|
event.stopPropagation();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleMenuTriggerMouseDown = React.useCallback((event: React.MouseEvent<HTMLButtonElement>) => {
|
||||||
|
event.stopPropagation();
|
||||||
|
}, []);
|
||||||
|
|
||||||
const handleToggleMouseDown = React.useCallback((event: React.MouseEvent<HTMLButtonElement>) => {
|
const handleToggleMouseDown = React.useCallback((event: React.MouseEvent<HTMLButtonElement>) => {
|
||||||
if (event.button === 2 || (event.button === 0 && event.ctrlKey)) {
|
if (event.button === 2 || (event.button === 0 && event.ctrlKey)) {
|
||||||
suppressNextToggleRef.current = true;
|
suppressNextToggleRef.current = true;
|
||||||
@@ -250,6 +258,8 @@ export const SortableProjectItem: React.FC<SortableProjectItemProps> = ({
|
|||||||
: 'opacity-0 pointer-events-none group-hover/project:opacity-100 group-hover/project:pointer-events-auto group-focus-within/project:opacity-100 group-focus-within/project:pointer-events-auto',
|
: 'opacity-0 pointer-events-none group-hover/project:opacity-100 group-hover/project:pointer-events-auto group-focus-within/project:opacity-100 group-focus-within/project:pointer-events-auto',
|
||||||
)}
|
)}
|
||||||
aria-label="Project menu"
|
aria-label="Project menu"
|
||||||
|
onPointerDown={handleMenuTriggerPointerDown}
|
||||||
|
onMouseDown={handleMenuTriggerMouseDown}
|
||||||
onClick={handleMenuTriggerClick}
|
onClick={handleMenuTriggerClick}
|
||||||
>
|
>
|
||||||
<RiMore2Line className="h-3.5 w-3.5" />
|
<RiMore2Line className="h-3.5 w-3.5" />
|
||||||
|
|||||||
Reference in New Issue
Block a user