Merge 739d828e22 into 2318b6bdd0
This commit is contained in:
commit
9219b9d397
1 changed files with 71 additions and 16 deletions
|
|
@ -52,6 +52,7 @@ export const SnapshotsTable = ({ snapshots, repositoryId, backups, listSnapshots
|
|||
const { formatDateTime } = useTimeFormat();
|
||||
|
||||
const [selectedIds, setSelectedIds] = useState<Set<string>>(new Set());
|
||||
const [lastSelectedId, setLastSelectedId] = useState<string | null>(null);
|
||||
const [showBulkDeleteConfirm, setShowBulkDeleteConfirm] = useState(false);
|
||||
const [showReTagDialog, setShowReTagDialog] = useState(false);
|
||||
const [targetScheduleId, setTargetScheduleId] = useState<string>("");
|
||||
|
|
@ -70,6 +71,7 @@ export const SnapshotsTable = ({ snapshots, repositoryId, backups, listSnapshots
|
|||
void queryClient.invalidateQueries({ queryKey });
|
||||
setShowBulkDeleteConfirm(false);
|
||||
setSelectedIds(new Set());
|
||||
setLastSelectedId(null);
|
||||
},
|
||||
});
|
||||
|
||||
|
|
@ -81,6 +83,7 @@ export const SnapshotsTable = ({ snapshots, repositoryId, backups, listSnapshots
|
|||
onSuccess: () => {
|
||||
setShowReTagDialog(false);
|
||||
setSelectedIds(new Set());
|
||||
setLastSelectedId(null);
|
||||
setTargetScheduleId("");
|
||||
},
|
||||
});
|
||||
|
|
@ -92,11 +95,63 @@ export const SnapshotsTable = ({ snapshots, repositoryId, backups, listSnapshots
|
|||
const toggleSelectAll = () => {
|
||||
if (selectedIds.size === snapshots.length) {
|
||||
setSelectedIds(new Set());
|
||||
setLastSelectedId(null);
|
||||
} else {
|
||||
setSelectedIds(new Set(snapshots.map((s) => s.short_id)));
|
||||
setLastSelectedId(snapshots.length > 0 ? snapshots[snapshots.length - 1].short_id : null);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSnapshotSelection = (snapshotId: string, event?: React.MouseEvent | React.KeyboardEvent) => {
|
||||
const isShiftClick = event && "shiftKey" in event && event.shiftKey;
|
||||
|
||||
// Attempt range selection first
|
||||
if (isShiftClick && snapshots.length > 0) {
|
||||
const currentIndex = snapshots.findIndex((s) => s.short_id === snapshotId);
|
||||
|
||||
if (currentIndex !== -1) {
|
||||
// If lastSelectedId exists, use it; otherwise start from the first item (index 0)
|
||||
let startIndex: number;
|
||||
if (lastSelectedId) {
|
||||
startIndex = snapshots.findIndex((s) => s.short_id === lastSelectedId);
|
||||
// If lastSelectedId no longer exists in snapshots (stale reference), fall back to single selection
|
||||
if (startIndex === -1) {
|
||||
const newSelected = new Set(selectedIds);
|
||||
if (newSelected.has(snapshotId)) {
|
||||
newSelected.delete(snapshotId);
|
||||
} else {
|
||||
newSelected.add(snapshotId);
|
||||
}
|
||||
setSelectedIds(newSelected);
|
||||
setLastSelectedId(snapshotId);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
startIndex = 0;
|
||||
}
|
||||
|
||||
// Valid range selection - replace the entire selection with the new range
|
||||
const start = Math.min(startIndex, currentIndex);
|
||||
const end = Math.max(startIndex, currentIndex);
|
||||
const rangeIds = new Set(snapshots.slice(start, end + 1).map((s) => s.short_id));
|
||||
|
||||
setSelectedIds(rangeIds);
|
||||
setLastSelectedId(snapshotId);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Single selection toggle (used as fallback or when shift-click not applicable)
|
||||
const newSelected = new Set(selectedIds);
|
||||
if (newSelected.has(snapshotId)) {
|
||||
newSelected.delete(snapshotId);
|
||||
} else {
|
||||
newSelected.add(snapshotId);
|
||||
}
|
||||
setSelectedIds(newSelected);
|
||||
setLastSelectedId(snapshotId);
|
||||
};
|
||||
|
||||
const handleBulkDelete = () => {
|
||||
toast.promise(
|
||||
deleteSnapshots.mutateAsync({
|
||||
|
|
@ -162,17 +217,12 @@ export const SnapshotsTable = ({ snapshots, repositoryId, backups, listSnapshots
|
|||
className={cn("hover:bg-accent/50 cursor-pointer", isSelected && "bg-accent/30")}
|
||||
onClick={() => handleRowClick(snapshot.short_id)}
|
||||
>
|
||||
<TableCell onClick={(e) => e.stopPropagation()}>
|
||||
<TableCell onClick={(e: React.MouseEvent) => e.stopPropagation()}>
|
||||
<Checkbox
|
||||
checked={isSelected}
|
||||
onCheckedChange={() => {
|
||||
const newSelected = new Set(selectedIds);
|
||||
if (newSelected.has(snapshot.short_id)) {
|
||||
newSelected.delete(snapshot.short_id);
|
||||
} else {
|
||||
newSelected.add(snapshot.short_id);
|
||||
}
|
||||
setSelectedIds(newSelected);
|
||||
onClick={(e: React.MouseEvent<HTMLButtonElement>) => {
|
||||
e.stopPropagation();
|
||||
handleSnapshotSelection(snapshot.short_id, e);
|
||||
}}
|
||||
aria-label={`Select snapshot ${snapshot.short_id}` as string}
|
||||
/>
|
||||
|
|
@ -189,7 +239,7 @@ export const SnapshotsTable = ({ snapshots, repositoryId, backups, listSnapshots
|
|||
hidden={!backup}
|
||||
to={backup ? `/backups/$backupId` : "."}
|
||||
params={backup ? { backupId: backup.shortId } : {}}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
onClick={(e: React.MouseEvent) => e.stopPropagation()}
|
||||
className="hover:underline"
|
||||
>
|
||||
<span className="text-sm">{backup ? backup.name : "-"}</span>
|
||||
|
|
@ -216,7 +266,9 @@ export const SnapshotsTable = ({ snapshots, repositoryId, backups, listSnapshots
|
|||
<TableCell className="hidden md:table-cell">
|
||||
<div className="flex items-center justify-end gap-2">
|
||||
<Clock className="h-4 w-4 text-muted-foreground" />
|
||||
<span className="text-sm text-muted-foreground">{formatDuration(snapshot.duration / 1000)}</span>
|
||||
<span className="text-sm text-muted-foreground">
|
||||
{formatDuration(snapshot.duration / 1000)}
|
||||
</span>
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
|
|
@ -234,7 +286,10 @@ export const SnapshotsTable = ({ snapshots, repositoryId, backups, listSnapshots
|
|||
variant="ghost"
|
||||
size="icon"
|
||||
className="h-8 w-8 rounded-full"
|
||||
onClick={() => setSelectedIds(new Set())}
|
||||
onClick={() => {
|
||||
setSelectedIds(new Set());
|
||||
setLastSelectedId(null);
|
||||
}}
|
||||
>
|
||||
<X className="h-4 w-4" />
|
||||
</Button>
|
||||
|
|
@ -269,8 +324,8 @@ export const SnapshotsTable = ({ snapshots, repositoryId, backups, listSnapshots
|
|||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>Delete {selectedIds.size} snapshots?</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
This action cannot be undone. This will permanently delete the selected snapshots and all their data from
|
||||
the repository.
|
||||
This action cannot be undone. This will permanently delete the selected snapshots and all
|
||||
their data from the repository.
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
|
|
@ -291,8 +346,8 @@ export const SnapshotsTable = ({ snapshots, repositoryId, backups, listSnapshots
|
|||
<DialogHeader>
|
||||
<DialogTitle>Re-tag snapshots</DialogTitle>
|
||||
<DialogDescription>
|
||||
Select a backup schedule to re-tag the {selectedIds.size} selected snapshots. All {selectedIds.size}{" "}
|
||||
selected snapshots will be associated with the chosen schedule.
|
||||
Select a backup schedule to re-tag the {selectedIds.size} selected snapshots. All{" "}
|
||||
{selectedIds.size} selected snapshots will be associated with the chosen schedule.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="py-4">
|
||||
|
|
|
|||
Loading…
Reference in a new issue