|
|
@@ -8,16 +8,32 @@ import { useRooPortal } from "@/components/ui/hooks"
|
|
|
import { vscode } from "@src/utils/vscode"
|
|
|
import { Checkpoint } from "./schema"
|
|
|
|
|
|
-type CheckpointMenuProps = {
|
|
|
+type CheckpointMenuBaseProps = {
|
|
|
ts: number
|
|
|
commitHash: string
|
|
|
currentHash?: string
|
|
|
checkpoint: Checkpoint
|
|
|
}
|
|
|
+type CheckpointMenuControlledProps = {
|
|
|
+ open: boolean
|
|
|
+ onOpenChange: (open: boolean) => void
|
|
|
+}
|
|
|
+type CheckpointMenuUncontrolledProps = {
|
|
|
+ open?: undefined
|
|
|
+ onOpenChange?: undefined
|
|
|
+}
|
|
|
+type CheckpointMenuProps = CheckpointMenuBaseProps & (CheckpointMenuControlledProps | CheckpointMenuUncontrolledProps)
|
|
|
|
|
|
-export const CheckpointMenu = ({ ts, commitHash, currentHash, checkpoint }: CheckpointMenuProps) => {
|
|
|
+export const CheckpointMenu = ({
|
|
|
+ ts,
|
|
|
+ commitHash,
|
|
|
+ currentHash,
|
|
|
+ checkpoint,
|
|
|
+ open,
|
|
|
+ onOpenChange,
|
|
|
+}: CheckpointMenuProps) => {
|
|
|
const { t } = useTranslation()
|
|
|
- const [isOpen, setIsOpen] = useState(false)
|
|
|
+ const [internalOpen, setInternalOpen] = useState(false)
|
|
|
const [isConfirming, setIsConfirming] = useState(false)
|
|
|
const portalContainer = useRooPortal("roo-portal")
|
|
|
|
|
|
@@ -25,6 +41,9 @@ export const CheckpointMenu = ({ ts, commitHash, currentHash, checkpoint }: Chec
|
|
|
|
|
|
const previousCommitHash = checkpoint?.from
|
|
|
|
|
|
+ const isOpen = open ?? internalOpen
|
|
|
+ const setOpen = onOpenChange ?? setInternalOpen
|
|
|
+
|
|
|
const onCheckpointDiff = useCallback(() => {
|
|
|
vscode.postMessage({
|
|
|
type: "checkpointDiff",
|
|
|
@@ -34,13 +53,23 @@ export const CheckpointMenu = ({ ts, commitHash, currentHash, checkpoint }: Chec
|
|
|
|
|
|
const onPreview = useCallback(() => {
|
|
|
vscode.postMessage({ type: "checkpointRestore", payload: { ts, commitHash, mode: "preview" } })
|
|
|
- setIsOpen(false)
|
|
|
- }, [ts, commitHash])
|
|
|
+ setOpen(false)
|
|
|
+ }, [ts, commitHash, setOpen])
|
|
|
|
|
|
const onRestore = useCallback(() => {
|
|
|
vscode.postMessage({ type: "checkpointRestore", payload: { ts, commitHash, mode: "restore" } })
|
|
|
- setIsOpen(false)
|
|
|
- }, [ts, commitHash])
|
|
|
+ setOpen(false)
|
|
|
+ }, [ts, commitHash, setOpen])
|
|
|
+
|
|
|
+ const handleOpenChange = useCallback(
|
|
|
+ (open: boolean) => {
|
|
|
+ setOpen(open)
|
|
|
+ if (!open) {
|
|
|
+ setIsConfirming(false)
|
|
|
+ }
|
|
|
+ },
|
|
|
+ [setOpen],
|
|
|
+ )
|
|
|
|
|
|
return (
|
|
|
<div className="flex flex-row gap-1">
|
|
|
@@ -49,15 +78,10 @@ export const CheckpointMenu = ({ ts, commitHash, currentHash, checkpoint }: Chec
|
|
|
<span className="codicon codicon-diff-single" />
|
|
|
</Button>
|
|
|
</StandardTooltip>
|
|
|
- <Popover
|
|
|
- open={isOpen}
|
|
|
- onOpenChange={(open) => {
|
|
|
- setIsOpen(open)
|
|
|
- setIsConfirming(false)
|
|
|
- }}>
|
|
|
+ <Popover open={isOpen} onOpenChange={handleOpenChange}>
|
|
|
<StandardTooltip content={t("chat:checkpoint.menu.restore")}>
|
|
|
<PopoverTrigger asChild>
|
|
|
- <Button variant="ghost" size="icon">
|
|
|
+ <Button variant="ghost" size="icon" aria-label={t("chat:checkpoint.menu.restore")}>
|
|
|
<span className="codicon codicon-history" />
|
|
|
</Button>
|
|
|
</PopoverTrigger>
|
|
|
@@ -66,7 +90,7 @@ export const CheckpointMenu = ({ ts, commitHash, currentHash, checkpoint }: Chec
|
|
|
<div className="flex flex-col gap-2">
|
|
|
{!isCurrent && (
|
|
|
<div className="flex flex-col gap-1 group hover:text-foreground">
|
|
|
- <Button variant="secondary" onClick={onPreview}>
|
|
|
+ <Button variant="secondary" onClick={onPreview} data-testid="restore-files-btn">
|
|
|
{t("chat:checkpoint.menu.restoreFiles")}
|
|
|
</Button>
|
|
|
<div className="text-muted transition-colors group-hover:text-foreground">
|
|
|
@@ -74,39 +98,50 @@ export const CheckpointMenu = ({ ts, commitHash, currentHash, checkpoint }: Chec
|
|
|
</div>
|
|
|
</div>
|
|
|
)}
|
|
|
- <div className="flex flex-col gap-1 group hover:text-foreground">
|
|
|
+ {!isCurrent && (
|
|
|
<div className="flex flex-col gap-1 group hover:text-foreground">
|
|
|
- {!isConfirming ? (
|
|
|
- <Button variant="secondary" onClick={() => setIsConfirming(true)}>
|
|
|
- {t("chat:checkpoint.menu.restoreFilesAndTask")}
|
|
|
- </Button>
|
|
|
- ) : (
|
|
|
- <>
|
|
|
- <Button variant="default" onClick={onRestore} className="grow">
|
|
|
- <div className="flex flex-row gap-1">
|
|
|
- <CheckIcon />
|
|
|
- <div>{t("chat:checkpoint.menu.confirm")}</div>
|
|
|
- </div>
|
|
|
- </Button>
|
|
|
- <Button variant="secondary" onClick={() => setIsConfirming(false)}>
|
|
|
- <div className="flex flex-row gap-1">
|
|
|
- <Cross2Icon />
|
|
|
- <div>{t("chat:checkpoint.menu.cancel")}</div>
|
|
|
- </div>
|
|
|
+ <div className="flex flex-col gap-1 group hover:text-foreground">
|
|
|
+ {!isConfirming ? (
|
|
|
+ <Button
|
|
|
+ variant="secondary"
|
|
|
+ onClick={() => setIsConfirming(true)}
|
|
|
+ data-testid="restore-files-and-task-btn">
|
|
|
+ {t("chat:checkpoint.menu.restoreFilesAndTask")}
|
|
|
</Button>
|
|
|
- </>
|
|
|
- )}
|
|
|
- {isConfirming ? (
|
|
|
- <div className="text-destructive font-bold">
|
|
|
- {t("chat:checkpoint.menu.cannotUndo")}
|
|
|
- </div>
|
|
|
- ) : (
|
|
|
- <div className="text-muted transition-colors group-hover:text-foreground">
|
|
|
- {t("chat:checkpoint.menu.restoreFilesAndTaskDescription")}
|
|
|
- </div>
|
|
|
- )}
|
|
|
+ ) : (
|
|
|
+ <>
|
|
|
+ <Button
|
|
|
+ variant="default"
|
|
|
+ onClick={onRestore}
|
|
|
+ className="grow"
|
|
|
+ data-testid="confirm-restore-btn">
|
|
|
+ <div className="flex flex-row gap-1">
|
|
|
+ <CheckIcon />
|
|
|
+ <div>{t("chat:checkpoint.menu.confirm")}</div>
|
|
|
+ </div>
|
|
|
+ </Button>
|
|
|
+ <Button variant="secondary" onClick={() => setIsConfirming(false)}>
|
|
|
+ <div className="flex flex-row gap-1">
|
|
|
+ <Cross2Icon />
|
|
|
+ <div>{t("chat:checkpoint.menu.cancel")}</div>
|
|
|
+ </div>
|
|
|
+ </Button>
|
|
|
+ </>
|
|
|
+ )}
|
|
|
+ {isConfirming ? (
|
|
|
+ <div
|
|
|
+ data-testid="checkpoint-confirm-warning"
|
|
|
+ className="text-destructive font-bold">
|
|
|
+ {t("chat:checkpoint.menu.cannotUndo")}
|
|
|
+ </div>
|
|
|
+ ) : (
|
|
|
+ <div className="text-muted transition-colors group-hover:text-foreground">
|
|
|
+ {t("chat:checkpoint.menu.restoreFilesAndTaskDescription")}
|
|
|
+ </div>
|
|
|
+ )}
|
|
|
+ </div>
|
|
|
</div>
|
|
|
- </div>
|
|
|
+ )}
|
|
|
</div>
|
|
|
</PopoverContent>
|
|
|
</Popover>
|