Add bulk delete functionality to worktree overview (#1208)
* auto-claude: subtask-1-1 - Add English translation keys for bulk delete dialog - Add bulkDeleteTitle, bulkDeleteDescription, deleting, deleteSelected to worktrees section in dialogs.json - Add selection section with selected, selectAll, clearSelection, deleteSelected to common.json Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * auto-claude: subtask-1-2 - Add French translation keys for bulk delete dialog Added French translations for: - dialogs.json: bulkDeleteTitle, bulkDeleteDescription, deleting, deleteSelected - common.json: selection section with selected, selectAll, clearSelection, deleteSelected Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * auto-claude: subtask-2-1 - Add selection mode toggle state (isSelectionMode) * auto-claude: subtask-3-1 - Add selection mode toggle button to Worktrees header * auto-claude: subtask-3-2 - Add selection controls bar below header (visible w * auto-claude: subtask-3-3 - Add Checkbox component to worktree Cards - Import Checkbox from ui/checkbox - Add Checkbox to Task Worktrees Card (visible only in selection mode) - Add Checkbox to Terminal Worktrees Card (visible only in selection mode) - Use prefixed IDs: 'task:{specName}' and 'terminal:{name}' - Update selection callbacks to handle prefixed IDs for both worktree types - Update selectAll/deselectAll to work with both task and terminal worktrees - Update isAllSelected/isSomeSelected computed values for combined worktrees Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * auto-claude: subtask-4-1 - Add bulk delete button that appears when items are selected - Add bulk delete button in selection controls bar with destructive styling - Button shows count of selected worktrees - Button is disabled when no items are selected - Add handleBulkDelete callback (handler implementation in next subtask) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * auto-claude: subtask-4-2 - Add bulk delete confirmation AlertDialog with i18n * auto-claude: subtask-4-3 - Implement handleBulkDelete function. Parse prefixe * auto-claude: subtask-5-1 - Clear selection when loadWorktrees is called - Clear selectedWorktreeIds when worktrees list is refreshed - Exit selection mode when list refreshes to prevent stale state - Existing single-delete functionality remains unchanged Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: use i18n keys for selection control text (qa-requested) Fixes: - Replace hardcoded 'Select all' / 'Deselect all' text with i18n keys - Replace hardcoded selection count text with i18n interpolation - Add 'selectedOfTotal' key to both EN and FR translation files Verification: - All hardcoded strings removed from Worktrees.tsx - TypeScript lint passes - Build completes successfully - All 1761 tests pass QA Fix Session: 1 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: resolve i18n violations and code quality issues in bulk delete - Replace hardcoded 'Refresh' button text with t('common:buttons.refresh') - Add i18n keys for all bulk delete error messages (en/fr) - Wrap findTaskForWorktree in useCallback to prevent unnecessary re-renders - Use named constants TASK_PREFIX/TERMINAL_PREFIX for ID parsing - Add whitespace-pre-line class to error display for proper newline rendering Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(worktrees): use prefix constants consistently and fix stale selection count - Replace hardcoded 'task:' and 'terminal:' strings with TASK_PREFIX/TERMINAL_PREFIX constants in selectAll, isAllSelected, isSomeSelected, and JSX rendering - Change selectedCount from raw Set.size to useMemo that filters against current worktrees arrays, preventing stale counts for externally deleted worktrees Addresses PR review feedback for bulk delete functionality. --------- Co-authored-by: Test User <test@example.com> Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { useEffect, useState, useCallback } from 'react';
|
||||
import { useEffect, useState, useCallback, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
GitBranch,
|
||||
@@ -16,10 +16,14 @@ import {
|
||||
ChevronRight,
|
||||
Check,
|
||||
X,
|
||||
Terminal
|
||||
Terminal,
|
||||
CheckSquare2,
|
||||
CheckSquare,
|
||||
Square
|
||||
} from 'lucide-react';
|
||||
import { Button } from './ui/button';
|
||||
import { Badge } from './ui/badge';
|
||||
import { Checkbox } from './ui/checkbox';
|
||||
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from './ui/card';
|
||||
import { ScrollArea } from './ui/scroll-area';
|
||||
import {
|
||||
@@ -45,12 +49,16 @@ import { useTaskStore } from '../stores/task-store';
|
||||
import type { WorktreeListItem, WorktreeMergeResult, TerminalWorktreeConfig, WorktreeStatus, Task, WorktreeCreatePROptions, WorktreeCreatePRResult } from '../../shared/types';
|
||||
import { CreatePRDialog } from './task-detail/task-review/CreatePRDialog';
|
||||
|
||||
// Prefix constants for worktree ID parsing
|
||||
const TASK_PREFIX = 'task:';
|
||||
const TERMINAL_PREFIX = 'terminal:';
|
||||
|
||||
interface WorktreesProps {
|
||||
projectId: string;
|
||||
}
|
||||
|
||||
export function Worktrees({ projectId }: WorktreesProps) {
|
||||
const { t } = useTranslation(['common']);
|
||||
const { t } = useTranslation(['common', 'dialogs']);
|
||||
const projects = useProjectStore((state) => state.projects);
|
||||
const selectedProject = projects.find((p) => p.id === projectId);
|
||||
const tasks = useTaskStore((state) => state.tasks);
|
||||
@@ -75,15 +83,83 @@ export function Worktrees({ projectId }: WorktreesProps) {
|
||||
const [worktreeToDelete, setWorktreeToDelete] = useState<WorktreeListItem | null>(null);
|
||||
const [isDeleting, setIsDeleting] = useState(false);
|
||||
|
||||
// Bulk delete confirmation state
|
||||
const [showBulkDeleteConfirm, setShowBulkDeleteConfirm] = useState(false);
|
||||
const [isBulkDeleting, setIsBulkDeleting] = useState(false);
|
||||
|
||||
// Create PR dialog state
|
||||
const [showCreatePRDialog, setShowCreatePRDialog] = useState(false);
|
||||
const [prWorktree, setPrWorktree] = useState<WorktreeListItem | null>(null);
|
||||
const [prTask, setPrTask] = useState<Task | null>(null);
|
||||
|
||||
// Selection state
|
||||
const [isSelectionMode, setIsSelectionMode] = useState(false);
|
||||
const [selectedWorktreeIds, setSelectedWorktreeIds] = useState<Set<string>>(new Set());
|
||||
|
||||
// Selection callbacks
|
||||
const toggleWorktree = useCallback((id: string) => {
|
||||
setSelectedWorktreeIds(prev => {
|
||||
const next = new Set(prev);
|
||||
if (next.has(id)) {
|
||||
next.delete(id);
|
||||
} else {
|
||||
next.add(id);
|
||||
}
|
||||
return next;
|
||||
});
|
||||
}, []);
|
||||
|
||||
const selectAll = useCallback(() => {
|
||||
const allIds = [
|
||||
...worktrees.map(w => `${TASK_PREFIX}${w.specName}`),
|
||||
...terminalWorktrees.map(wt => `${TERMINAL_PREFIX}${wt.name}`)
|
||||
];
|
||||
setSelectedWorktreeIds(new Set(allIds));
|
||||
}, [worktrees, terminalWorktrees]);
|
||||
|
||||
const deselectAll = useCallback(() => {
|
||||
setSelectedWorktreeIds(new Set());
|
||||
}, []);
|
||||
|
||||
// Computed selection values
|
||||
const totalWorktrees = worktrees.length + terminalWorktrees.length;
|
||||
|
||||
const isAllSelected = useMemo(
|
||||
() => totalWorktrees > 0 &&
|
||||
worktrees.every(w => selectedWorktreeIds.has(`${TASK_PREFIX}${w.specName}`)) &&
|
||||
terminalWorktrees.every(wt => selectedWorktreeIds.has(`${TERMINAL_PREFIX}${wt.name}`)),
|
||||
[worktrees, terminalWorktrees, selectedWorktreeIds, totalWorktrees]
|
||||
);
|
||||
|
||||
const isSomeSelected = useMemo(
|
||||
() => (
|
||||
worktrees.some(w => selectedWorktreeIds.has(`${TASK_PREFIX}${w.specName}`)) ||
|
||||
terminalWorktrees.some(wt => selectedWorktreeIds.has(`${TERMINAL_PREFIX}${wt.name}`))
|
||||
) && !isAllSelected,
|
||||
[worktrees, terminalWorktrees, selectedWorktreeIds, isAllSelected]
|
||||
);
|
||||
|
||||
// Compute selectedCount by filtering against current worktrees to exclude stale selections
|
||||
const selectedCount = useMemo(() => {
|
||||
const validTaskIds = new Set(worktrees.map(w => `${TASK_PREFIX}${w.specName}`));
|
||||
const validTerminalIds = new Set(terminalWorktrees.map(wt => `${TERMINAL_PREFIX}${wt.name}`));
|
||||
let count = 0;
|
||||
selectedWorktreeIds.forEach(id => {
|
||||
if (validTaskIds.has(id) || validTerminalIds.has(id)) {
|
||||
count++;
|
||||
}
|
||||
});
|
||||
return count;
|
||||
}, [worktrees, terminalWorktrees, selectedWorktreeIds]);
|
||||
|
||||
// Load worktrees (both task and terminal worktrees)
|
||||
const loadWorktrees = useCallback(async () => {
|
||||
if (!projectId || !selectedProject) return;
|
||||
|
||||
// Clear selection when refreshing list to prevent stale selections
|
||||
setSelectedWorktreeIds(new Set());
|
||||
setIsSelectionMode(false);
|
||||
|
||||
setIsLoading(true);
|
||||
setError(null);
|
||||
|
||||
@@ -123,9 +199,9 @@ export function Worktrees({ projectId }: WorktreesProps) {
|
||||
}, [loadWorktrees]);
|
||||
|
||||
// Find task for a worktree
|
||||
const findTaskForWorktree = (specName: string) => {
|
||||
const findTaskForWorktree = useCallback((specName: string) => {
|
||||
return tasks.find(t => t.specId === specName);
|
||||
};
|
||||
}, [tasks]);
|
||||
|
||||
// Handle merge
|
||||
const handleMerge = async () => {
|
||||
@@ -246,6 +322,84 @@ export function Worktrees({ projectId }: WorktreesProps) {
|
||||
}
|
||||
};
|
||||
|
||||
// Handle bulk delete - triggered from selection bar
|
||||
const handleBulkDelete = useCallback(() => {
|
||||
if (selectedWorktreeIds.size === 0) return;
|
||||
setShowBulkDeleteConfirm(true);
|
||||
}, [selectedWorktreeIds]);
|
||||
|
||||
// Execute bulk delete - called when user confirms in dialog
|
||||
const executeBulkDelete = useCallback(async () => {
|
||||
if (selectedWorktreeIds.size === 0 || !selectedProject) return;
|
||||
|
||||
setIsBulkDeleting(true);
|
||||
const errors: string[] = [];
|
||||
|
||||
// Parse selected IDs and separate by type
|
||||
const taskSpecNames: string[] = [];
|
||||
const terminalNames: string[] = [];
|
||||
|
||||
selectedWorktreeIds.forEach((id) => {
|
||||
if (id.startsWith(TASK_PREFIX)) {
|
||||
taskSpecNames.push(id.slice(TASK_PREFIX.length));
|
||||
} else if (id.startsWith(TERMINAL_PREFIX)) {
|
||||
terminalNames.push(id.slice(TERMINAL_PREFIX.length));
|
||||
}
|
||||
});
|
||||
|
||||
// Delete task worktrees
|
||||
for (const specName of taskSpecNames) {
|
||||
const task = findTaskForWorktree(specName);
|
||||
if (!task) {
|
||||
errors.push(t('common:errors.taskNotFoundForWorktree', { specName }));
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await window.electronAPI.discardWorktree(task.id);
|
||||
if (!result.success) {
|
||||
errors.push(result.error || t('common:errors.failedToDeleteTaskWorktree', { specName }));
|
||||
}
|
||||
} catch (err) {
|
||||
errors.push(err instanceof Error ? err.message : t('common:errors.failedToDeleteTaskWorktree', { specName }));
|
||||
}
|
||||
}
|
||||
|
||||
// Delete terminal worktrees
|
||||
for (const name of terminalNames) {
|
||||
const terminalWt = terminalWorktrees.find((wt) => wt.name === name);
|
||||
if (!terminalWt) {
|
||||
errors.push(t('common:errors.terminalWorktreeNotFound', { name }));
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await window.electronAPI.removeTerminalWorktree(
|
||||
selectedProject.path,
|
||||
terminalWt.name,
|
||||
terminalWt.hasGitBranch // Delete the branch too if it was created
|
||||
);
|
||||
if (!result.success) {
|
||||
errors.push(result.error || t('common:errors.failedToDeleteTerminalWorktree', { name }));
|
||||
}
|
||||
} catch (err) {
|
||||
errors.push(err instanceof Error ? err.message : t('common:errors.failedToDeleteTerminalWorktree', { name }));
|
||||
}
|
||||
}
|
||||
|
||||
// Clear selection and refresh list
|
||||
setSelectedWorktreeIds(new Set());
|
||||
setShowBulkDeleteConfirm(false);
|
||||
await loadWorktrees();
|
||||
|
||||
// Show error if any failures occurred
|
||||
if (errors.length > 0) {
|
||||
setError(`${t('common:errors.bulkDeletePartialFailure')}\n${errors.join('\n')}`);
|
||||
}
|
||||
|
||||
setIsBulkDeleting(false);
|
||||
}, [selectedWorktreeIds, selectedProject, terminalWorktrees, findTaskForWorktree, loadWorktrees, t]);
|
||||
|
||||
// Handle terminal worktree delete
|
||||
const handleDeleteTerminalWorktree = async () => {
|
||||
if (!terminalWorktreeToDelete || !selectedProject) return;
|
||||
@@ -292,17 +446,64 @@ export function Worktrees({ projectId }: WorktreesProps) {
|
||||
Manage isolated workspaces for your Auto Claude tasks
|
||||
</p>
|
||||
</div>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={loadWorktrees}
|
||||
disabled={isLoading}
|
||||
>
|
||||
<RefreshCw className={`h-4 w-4 mr-2 ${isLoading ? 'animate-spin' : ''}`} />
|
||||
Refresh
|
||||
</Button>
|
||||
<div className="flex items-center gap-2">
|
||||
<Button
|
||||
variant={isSelectionMode ? 'default' : 'outline'}
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
if (isSelectionMode) {
|
||||
setIsSelectionMode(false);
|
||||
setSelectedWorktreeIds(new Set());
|
||||
} else {
|
||||
setIsSelectionMode(true);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<CheckSquare2 className="h-4 w-4 mr-2" />
|
||||
{isSelectionMode ? t('common:selection.done') : t('common:selection.select')}
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={loadWorktrees}
|
||||
disabled={isLoading}
|
||||
>
|
||||
<RefreshCw className={`h-4 w-4 mr-2 ${isLoading ? 'animate-spin' : ''}`} />
|
||||
{t('common:buttons.refresh')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Selection controls bar - visible when selection mode is enabled */}
|
||||
{isSelectionMode && totalWorktrees > 0 && (
|
||||
<div className="flex items-center justify-between py-2 mb-4 border-b border-border shrink-0">
|
||||
<div className="flex items-center gap-3">
|
||||
<button
|
||||
onClick={isAllSelected ? deselectAll : selectAll}
|
||||
className="flex items-center gap-2 text-sm text-muted-foreground hover:text-foreground"
|
||||
>
|
||||
{/* tri-state icon: isAllSelected -> CheckSquare, isSomeSelected -> Minus, none -> Square */}
|
||||
{isAllSelected ? <CheckSquare className="h-4 w-4 text-primary" /> : isSomeSelected ? <Minus className="h-4 w-4" /> : <Square className="h-4 w-4" />}
|
||||
{isAllSelected ? t('common:selection.clearSelection') : t('common:selection.selectAll')}
|
||||
</button>
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{t('common:selection.selectedOfTotal', { selected: selectedCount, total: totalWorktrees })}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Button
|
||||
variant="destructive"
|
||||
size="sm"
|
||||
disabled={selectedWorktreeIds.size === 0}
|
||||
onClick={handleBulkDelete}
|
||||
>
|
||||
<Trash2 className="h-4 w-4 mr-2" />
|
||||
{t('common:buttons.delete')} ({selectedWorktreeIds.size})
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Error message */}
|
||||
{error && (
|
||||
<div className="mb-4 rounded-lg border border-destructive/50 bg-destructive/10 p-4 text-sm">
|
||||
@@ -310,7 +511,7 @@ export function Worktrees({ projectId }: WorktreesProps) {
|
||||
<AlertCircle className="h-4 w-4 text-destructive mt-0.5 shrink-0" />
|
||||
<div>
|
||||
<p className="font-medium text-destructive">Error</p>
|
||||
<p className="text-muted-foreground mt-1">{error}</p>
|
||||
<p className="text-muted-foreground mt-1 whitespace-pre-line">{error}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -350,20 +551,30 @@ export function Worktrees({ projectId }: WorktreesProps) {
|
||||
</h3>
|
||||
{worktrees.map((worktree) => {
|
||||
const task = findTaskForWorktree(worktree.specName);
|
||||
const taskId = `${TASK_PREFIX}${worktree.specName}`;
|
||||
return (
|
||||
<Card key={worktree.specName} className="overflow-hidden">
|
||||
<CardHeader className="pb-3">
|
||||
<div className="flex items-start justify-between">
|
||||
<div className="flex-1 min-w-0">
|
||||
<CardTitle className="text-base flex items-center gap-2">
|
||||
<GitBranch className="h-4 w-4 text-info shrink-0" />
|
||||
<span className="truncate">{worktree.branch}</span>
|
||||
</CardTitle>
|
||||
{task && (
|
||||
<CardDescription className="mt-1 truncate">
|
||||
{task.title}
|
||||
</CardDescription>
|
||||
<div className="flex items-start gap-3 flex-1 min-w-0">
|
||||
{isSelectionMode && (
|
||||
<Checkbox
|
||||
checked={selectedWorktreeIds.has(taskId)}
|
||||
onCheckedChange={() => toggleWorktree(taskId)}
|
||||
className="mt-1"
|
||||
/>
|
||||
)}
|
||||
<div className="flex-1 min-w-0">
|
||||
<CardTitle className="text-base flex items-center gap-2">
|
||||
<GitBranch className="h-4 w-4 text-info shrink-0" />
|
||||
<span className="truncate">{worktree.branch}</span>
|
||||
</CardTitle>
|
||||
{task && (
|
||||
<CardDescription className="mt-1 truncate">
|
||||
{task.title}
|
||||
</CardDescription>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<Badge variant="outline" className="shrink-0 ml-2">
|
||||
{worktree.specName}
|
||||
@@ -465,28 +676,39 @@ export function Worktrees({ projectId }: WorktreesProps) {
|
||||
<Terminal className="h-4 w-4" />
|
||||
Terminal Worktrees
|
||||
</h3>
|
||||
{terminalWorktrees.map((wt) => (
|
||||
<Card key={wt.name} className="overflow-hidden">
|
||||
<CardHeader className="pb-3">
|
||||
<div className="flex items-start justify-between">
|
||||
<div className="flex-1 min-w-0">
|
||||
<CardTitle className="text-base flex items-center gap-2">
|
||||
<FolderGit className="h-4 w-4 text-amber-500 shrink-0" />
|
||||
<span className="truncate">{wt.name}</span>
|
||||
</CardTitle>
|
||||
{wt.branchName && (
|
||||
<CardDescription className="mt-1 truncate font-mono text-xs">
|
||||
{wt.branchName}
|
||||
</CardDescription>
|
||||
{terminalWorktrees.map((wt) => {
|
||||
const terminalId = `${TERMINAL_PREFIX}${wt.name}`;
|
||||
return (
|
||||
<Card key={wt.name} className="overflow-hidden">
|
||||
<CardHeader className="pb-3">
|
||||
<div className="flex items-start justify-between">
|
||||
<div className="flex items-start gap-3 flex-1 min-w-0">
|
||||
{isSelectionMode && (
|
||||
<Checkbox
|
||||
checked={selectedWorktreeIds.has(terminalId)}
|
||||
onCheckedChange={() => toggleWorktree(terminalId)}
|
||||
className="mt-1"
|
||||
/>
|
||||
)}
|
||||
<div className="flex-1 min-w-0">
|
||||
<CardTitle className="text-base flex items-center gap-2">
|
||||
<FolderGit className="h-4 w-4 text-amber-500 shrink-0" />
|
||||
<span className="truncate">{wt.name}</span>
|
||||
</CardTitle>
|
||||
{wt.branchName && (
|
||||
<CardDescription className="mt-1 truncate font-mono text-xs">
|
||||
{wt.branchName}
|
||||
</CardDescription>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{wt.taskId && (
|
||||
<Badge variant="outline" className="shrink-0 ml-2">
|
||||
{wt.taskId}
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
{wt.taskId && (
|
||||
<Badge variant="outline" className="shrink-0 ml-2">
|
||||
{wt.taskId}
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
</CardHeader>
|
||||
</CardHeader>
|
||||
<CardContent className="pt-0">
|
||||
{/* Branch info */}
|
||||
{wt.baseBranch && wt.branchName && (
|
||||
@@ -529,7 +751,8 @@ export function Worktrees({ projectId }: WorktreesProps) {
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -719,6 +942,44 @@ export function Worktrees({ projectId }: WorktreesProps) {
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
|
||||
{/* Bulk Delete Confirmation Dialog */}
|
||||
<AlertDialog open={showBulkDeleteConfirm} onOpenChange={setShowBulkDeleteConfirm}>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle className="flex items-center gap-2">
|
||||
<AlertCircle className="h-5 w-5 text-destructive" />
|
||||
{t('dialogs:worktrees.bulkDeleteTitle', { count: selectedWorktreeIds.size })}
|
||||
</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
{t('dialogs:worktrees.bulkDeleteDescription')}
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel disabled={isBulkDeleting}>{t('common:buttons.cancel')}</AlertDialogCancel>
|
||||
<AlertDialogAction
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
executeBulkDelete();
|
||||
}}
|
||||
disabled={isBulkDeleting}
|
||||
className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
|
||||
>
|
||||
{isBulkDeleting ? (
|
||||
<>
|
||||
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
||||
{t('dialogs:worktrees.deleting')}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Trash2 className="mr-2 h-4 w-4" />
|
||||
{t('dialogs:worktrees.deleteSelected')}
|
||||
</>
|
||||
)}
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
|
||||
{/* Create PR Dialog */}
|
||||
{prTask && prWorktree && (
|
||||
<CreatePRDialog
|
||||
|
||||
@@ -93,6 +93,15 @@
|
||||
"required": "Required",
|
||||
"dismiss": "Dismiss"
|
||||
},
|
||||
"selection": {
|
||||
"select": "Select",
|
||||
"done": "Done",
|
||||
"selected": "{{count}} selected",
|
||||
"selectAll": "Select All",
|
||||
"clearSelection": "Clear Selection",
|
||||
"deleteSelected": "Delete Selected",
|
||||
"selectedOfTotal": "{{selected}} of {{total}} selected"
|
||||
},
|
||||
"time": {
|
||||
"justNow": "Just now",
|
||||
"minutesAgo": "{{count}}m ago",
|
||||
@@ -105,7 +114,12 @@
|
||||
"operationFailed": "Operation failed",
|
||||
"networkError": "Network error",
|
||||
"notFound": "Not found",
|
||||
"unauthorized": "Unauthorized"
|
||||
"unauthorized": "Unauthorized",
|
||||
"bulkDeletePartialFailure": "Some worktrees could not be deleted:",
|
||||
"taskNotFoundForWorktree": "Task not found for worktree: {{specName}}",
|
||||
"failedToDeleteTaskWorktree": "Failed to delete task worktree: {{specName}}",
|
||||
"terminalWorktreeNotFound": "Terminal worktree not found: {{name}}",
|
||||
"failedToDeleteTerminalWorktree": "Failed to delete terminal worktree: {{name}}"
|
||||
},
|
||||
"notification": {
|
||||
"accountSwitched": "Account Switched",
|
||||
|
||||
@@ -54,7 +54,11 @@
|
||||
"merge": "Merge Worktree",
|
||||
"mergeDescription": "Merge changes from this worktree into the base branch.",
|
||||
"delete": "Delete Worktree?",
|
||||
"deleteDescription": "This will permanently delete the worktree and all uncommitted changes. This action cannot be undone."
|
||||
"deleteDescription": "This will permanently delete the worktree and all uncommitted changes. This action cannot be undone.",
|
||||
"bulkDeleteTitle": "Delete {{count}} Worktrees?",
|
||||
"bulkDeleteDescription": "This will permanently delete the selected worktrees and all their uncommitted changes. This action cannot be undone.",
|
||||
"deleting": "Deleting...",
|
||||
"deleteSelected": "Delete Selected"
|
||||
},
|
||||
"worktreeCleanup": {
|
||||
"title": "Complete Task",
|
||||
|
||||
@@ -93,6 +93,15 @@
|
||||
"required": "Requis",
|
||||
"dismiss": "Ignorer"
|
||||
},
|
||||
"selection": {
|
||||
"select": "Sélectionner",
|
||||
"done": "Terminé",
|
||||
"selected": "{{count}} sélectionné(s)",
|
||||
"selectAll": "Tout sélectionner",
|
||||
"clearSelection": "Effacer la sélection",
|
||||
"deleteSelected": "Supprimer la sélection",
|
||||
"selectedOfTotal": "{{selected}} sur {{total}} sélectionné(s)"
|
||||
},
|
||||
"time": {
|
||||
"justNow": "À l'instant",
|
||||
"minutesAgo": "Il y a {{count}} min",
|
||||
@@ -105,7 +114,12 @@
|
||||
"operationFailed": "Opération échouée",
|
||||
"networkError": "Erreur réseau",
|
||||
"notFound": "Non trouvé",
|
||||
"unauthorized": "Non autorisé"
|
||||
"unauthorized": "Non autorisé",
|
||||
"bulkDeletePartialFailure": "Certains worktrees n'ont pas pu être supprimés :",
|
||||
"taskNotFoundForWorktree": "Tâche introuvable pour le worktree : {{specName}}",
|
||||
"failedToDeleteTaskWorktree": "Échec de la suppression du worktree de tâche : {{specName}}",
|
||||
"terminalWorktreeNotFound": "Worktree terminal introuvable : {{name}}",
|
||||
"failedToDeleteTerminalWorktree": "Échec de la suppression du worktree terminal : {{name}}"
|
||||
},
|
||||
"notification": {
|
||||
"accountSwitched": "Compte changé",
|
||||
|
||||
@@ -54,7 +54,11 @@
|
||||
"merge": "Fusionner le worktree",
|
||||
"mergeDescription": "Fusionner les modifications de ce worktree dans la branche de base.",
|
||||
"delete": "Supprimer le worktree ?",
|
||||
"deleteDescription": "Ceci supprimera définitivement le worktree et toutes les modifications non committées. Cette action est irréversible."
|
||||
"deleteDescription": "Ceci supprimera définitivement le worktree et toutes les modifications non committées. Cette action est irréversible.",
|
||||
"bulkDeleteTitle": "Supprimer {{count}} worktrees ?",
|
||||
"bulkDeleteDescription": "Ceci supprimera définitivement les worktrees sélectionnés et toutes leurs modifications non committées. Cette action est irréversible.",
|
||||
"deleting": "Suppression...",
|
||||
"deleteSelected": "Supprimer la sélection"
|
||||
},
|
||||
"worktreeCleanup": {
|
||||
"title": "Terminer la tâche",
|
||||
|
||||
Reference in New Issue
Block a user