Add repo removal action to sidebar (#2165)

This commit is contained in:
Jinjing 2026-05-17 14:49:14 -07:00 committed by GitHub
parent 21a9c9909d
commit 71d49691ed
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 75 additions and 4 deletions

View File

@ -40,10 +40,11 @@ const RemoveFolderDialog = React.memo(function RemoveFolderDialog() {
<Dialog open={isOpen} onOpenChange={handleOpenChange}>
<DialogContent className="max-w-sm sm:max-w-sm" showCloseButton={false}>
<DialogHeader>
<DialogTitle className="text-sm">Remove Folder</DialogTitle>
<DialogTitle className="text-sm">Remove Project</DialogTitle>
<DialogDescription className="text-xs">
Remove <span className="break-all font-medium text-foreground">{displayName}</span> from
Orca? The folder will not be deleted from disk.
This only removes{' '}
<span className="break-all font-medium text-foreground">{displayName}</span> from Orca.
It is still on your disk.
</DialogDescription>
</DialogHeader>
<DialogFooter>

View File

@ -4,7 +4,7 @@ import {
measureElement as measureVirtualElementSize,
useVirtualizer
} from '@tanstack/react-virtual'
import { ChevronDown, CircleX, Plus, Workflow } from 'lucide-react'
import { ChevronDown, CircleX, Ellipsis, Plus, Trash2, Workflow } from 'lucide-react'
import { useAppStore } from '@/store'
import {
getAllWorktreesFromState,
@ -16,6 +16,12 @@ import WorktreeCard from './WorktreeCard'
import WorktreeCardAgents from './WorktreeCardAgents'
import { Button } from '@/components/ui/button'
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger
} from '@/components/ui/dropdown-menu'
import { cn } from '@/lib/utils'
import type {
Worktree,
@ -105,6 +111,12 @@ function isEditableTarget(target: EventTarget | null): boolean {
)
}
function stopRepoHeaderKeyboardToggle(event: React.KeyboardEvent<HTMLElement>): void {
if (event.key === 'Enter' || event.key === ' ') {
event.stopPropagation()
}
}
function getWorktreeOptionId(worktreeId: string): string {
return `worktree-list-option-${encodeURIComponent(worktreeId)}`
}
@ -121,6 +133,7 @@ type VirtualizedWorktreeViewportProps = {
toggleGroup: (key: string) => void
collapsedGroups: Set<string>
handleCreateForRepo: (repoId: string) => void
handleRemoveRepo: (repo: Repo) => void
activeModal: string
pendingRevealWorktreeId: string | null
clearPendingRevealWorktreeId: () => void
@ -251,6 +264,7 @@ const VirtualizedWorktreeViewport = React.memo(function VirtualizedWorktreeViewp
toggleGroup,
collapsedGroups,
handleCreateForRepo,
handleRemoveRepo,
activeModal,
pendingRevealWorktreeId,
clearPendingRevealWorktreeId,
@ -856,6 +870,50 @@ const VirtualizedWorktreeViewport = React.memo(function VirtualizedWorktreeViewp
/>
</div>
{row.repo && groupBy === 'repo' ? (
<DropdownMenu modal={false}>
<Tooltip>
<TooltipTrigger asChild>
<DropdownMenuTrigger asChild>
<Button
type="button"
variant="ghost"
size="icon-xs"
className="size-5 shrink-0 rounded-md text-muted-foreground opacity-0 transition-opacity hover:bg-accent/70 hover:text-foreground focus:opacity-100 group-hover:opacity-100 data-[state=open]:opacity-100"
aria-label={`Project actions for ${row.label}`}
onClick={(event) => event.stopPropagation()}
onKeyDown={stopRepoHeaderKeyboardToggle}
onPointerDown={(event) => event.stopPropagation()}
>
<Ellipsis className="size-3.5" />
</Button>
</DropdownMenuTrigger>
</TooltipTrigger>
<TooltipContent side="bottom" sideOffset={6}>
Project actions
</TooltipContent>
</Tooltip>
<DropdownMenuContent
align="end"
side="bottom"
sideOffset={6}
onClick={(event) => event.stopPropagation()}
>
<DropdownMenuItem
variant="destructive"
onSelect={() => {
if (row.repo) {
handleRemoveRepo(row.repo)
}
}}
>
<Trash2 className="size-3.5" />
Remove Project
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
) : null}
{row.repo && groupBy === 'repo' ? (
<Tooltip>
<TooltipTrigger asChild>
@ -865,6 +923,7 @@ const VirtualizedWorktreeViewport = React.memo(function VirtualizedWorktreeViewp
size="icon-xs"
className="size-5 shrink-0 rounded-md text-muted-foreground hover:bg-accent/70 hover:text-foreground transition-opacity"
aria-label={`Create worktree for ${row.label}`}
onKeyDown={stopRepoHeaderKeyboardToggle}
onClick={(event) => {
event.preventDefault()
event.stopPropagation()
@ -1618,6 +1677,16 @@ const WorktreeList = React.memo(function WorktreeList({
[openModal]
)
const handleRemoveRepo = useCallback(
(repo: Repo) => {
openModal('confirm-remove-folder', {
repoId: repo.id,
displayName: repo.displayName
})
},
[openModal]
)
const moveWorktreeToStatus = useCallback(
(worktreeId: string, status: WorkspaceStatus) => {
const current = worktreeMap.get(worktreeId)
@ -1732,6 +1801,7 @@ const WorktreeList = React.memo(function WorktreeList({
toggleGroup={toggleGroup}
collapsedGroups={collapsedGroups}
handleCreateForRepo={handleCreateForRepo}
handleRemoveRepo={handleRemoveRepo}
activeModal={activeModal}
pendingRevealWorktreeId={pendingRevealWorktreeId}
clearPendingRevealWorktreeId={clearPendingRevealWorktreeId}