diff --git a/src/App.tsx b/src/App.tsx index 3fe564d..b46c40e 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -2039,6 +2039,9 @@ const ProjectWorkspace = forwardRef< >({}); const [worktreePanelStatus, setWorktreePanelStatus] = useState(null); + const [deletingWorktreePaths, setDeletingWorktreePaths] = useState< + Set + >(() => new Set()); const [gitPushMenuPosition, setGitPushMenuPosition] = useState<{ target: GitPushMenuTarget; x: number; @@ -3749,6 +3752,12 @@ const ProjectWorkspace = forwardRef< return; } + setDeletingWorktreePaths((current) => { + const next = new Set(current); + next.add(worktree.path); + return next; + }); + try { await window.terminay.removeGitWorktree({ force: true, @@ -3760,6 +3769,11 @@ const ProjectWorkspace = forwardRef< void loadDirectory(parentDir || project.rootFolder); void refreshGitStatuses(); } catch (error) { + setDeletingWorktreePaths((current) => { + const next = new Set(current); + next.delete(worktree.path); + return next; + }); setErrorText(`Failed to delete worktree: ${String(error)}`); void refreshGitStatuses(); } @@ -4311,6 +4325,7 @@ const ProjectWorkspace = forwardRef< setDirectoryChildren({}); setDirectoryErrors({}); setGitStatuses({}); + setDeletingWorktreePaths(new Set()); setLoadingPaths({}); setExpandedPaths(project.rootFolder ? { [project.rootFolder]: true } : {}); @@ -4320,6 +4335,22 @@ const ProjectWorkspace = forwardRef< } }, [loadDirectory, project.rootFolder, refreshGitStatuses]); + useEffect(() => { + if (!worktreePanelStatus?.repoRoot) { + return; + } + + const visibleWorktreePaths = new Set( + worktreePanelStatus.worktrees.map((worktree) => worktree.path), + ); + setDeletingWorktreePaths((current) => { + const next = new Set( + Array.from(current).filter((path) => visibleWorktreePaths.has(path)), + ); + return next.size === current.size ? current : next; + }); + }, [worktreePanelStatus]); + useEffect(() => { if (!project.rootFolder || !project.isFileExplorerOpen) { return; @@ -7141,6 +7172,7 @@ const ProjectWorkspace = forwardRef< activePushMenuWorktreePath={ gitPushMenuPosition?.target?.worktreePath ?? null } + deletingWorktreePaths={deletingWorktreePaths} status={worktreePanelStatus} viewMode={settings.sidebar.gitPanelViewMode} onDeleteWorktree={handleDeleteWorktree} diff --git a/src/components/git-panel/WorktreesPanel.tsx b/src/components/git-panel/WorktreesPanel.tsx index b6cb29d..9f4de73 100644 --- a/src/components/git-panel/WorktreesPanel.tsx +++ b/src/components/git-panel/WorktreesPanel.tsx @@ -23,6 +23,7 @@ import './gitPanel.css'; export type WorktreesPanelProps = { activePushMenuWorktreePath?: string | null; + deletingWorktreePaths?: ReadonlySet; status: WorktreePanelStatus | null; viewMode: 'list' | 'tree'; onDeleteWorktree: (worktree: GitWorktreeStatus) => void; @@ -90,6 +91,7 @@ function formatWorktreeDate(value: string | null): string { export function WorktreesPanel(props: WorktreesPanelProps): JSX.Element { const { activePushMenuWorktreePath, + deletingWorktreePaths, status, viewMode, onDeleteWorktree, @@ -199,12 +201,15 @@ export function WorktreesPanel(props: WorktreesPanelProps): JSX.Element { return (
{status.worktrees.map((worktree) => { + const isDeleting = deletingWorktreePaths?.has(worktree.path) ?? false; const collapsed = collapsedWorktrees.has(worktree.path); const hasUnmergedOrUncommittedWork = - worktree.isDirtyBranch || worktree.entries.length > 0; + !isDeleting && + (worktree.isDirtyBranch || worktree.entries.length > 0); const hasLineChanges = - (worktree.lineAdditions ?? 0) > 0 || - (worktree.lineDeletions ?? 0) > 0; + !isDeleting && + ((worktree.lineAdditions ?? 0) > 0 || + (worktree.lineDeletions ?? 0) > 0); const WorktreeIcon = worktree.isMain ? FolderGit : GitBranch; const pushUnavailable = worktree.isBare || worktree.isPrunable || !!worktree.errorMessage; @@ -217,7 +222,13 @@ export function WorktreesPanel(props: WorktreesPanelProps): JSX.Element { }; return ( -
+
openContextMenu(event, worktree)} - title={getWorktreeTitle(worktree)} + onContextMenu={(event) => { + if (isDeleting) { + event.preventDefault(); + event.stopPropagation(); + return; + } + openContextMenu(event, worktree); + }} + title={ + isDeleting + ? `${worktree.path}\nDeleting…` + : getWorktreeTitle(worktree) + } >
- {collapsed ? null : worktree.errorMessage ? ( + {isDeleting || collapsed ? null : worktree.errorMessage ? (
{worktree.errorMessage}
) : worktree.isBare ? (
Bare worktree
diff --git a/src/components/git-panel/gitPanel.css b/src/components/git-panel/gitPanel.css index f6dd5d0..28bbe64 100644 --- a/src/components/git-panel/gitPanel.css +++ b/src/components/git-panel/gitPanel.css @@ -258,6 +258,10 @@ border-top: 1px solid rgba(255, 255, 255, 0.05); } +.worktrees-panel__worktree--deleting { + opacity: 0.52; +} + .worktrees-panel__worktree-header { box-sizing: border-box; min-height: 48px; @@ -276,6 +280,10 @@ background: rgba(255, 255, 255, 0.05); } +.worktrees-panel__worktree--deleting .worktrees-panel__worktree-header:hover { + background: transparent; +} + .worktrees-panel__worktree-toggle { all: unset; box-sizing: border-box; @@ -443,6 +451,13 @@ font-weight: 600; } +.worktrees-panel__deleting { + flex: 0 0 auto; + color: rgba(220, 226, 240, 0.72); + font-style: italic; + font-weight: 600; +} + .worktrees-panel__changes { position: relative; display: flex;