mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-09-07 08:26:51 +00:00
Add confirmation dialog for discarding changes.
This commit is contained in:
parent
2036e84d9d
commit
1fcdd33339
4 changed files with 112 additions and 19 deletions
|
|
@ -299,9 +299,6 @@ const ApiConfigManager = ({
|
|||
aria-labelledby="new-profile-title">
|
||||
<DialogContent className="p-4 max-w-sm">
|
||||
<DialogTitle>New Configuration Profile</DialogTitle>
|
||||
<button className="absolute right-4 top-4" aria-label="Close dialog" onClick={resetCreateState}>
|
||||
<span className="codicon codicon-close" />
|
||||
</button>
|
||||
<VSCodeTextField
|
||||
ref={newProfileInputRef}
|
||||
value={newProfileName}
|
||||
|
|
|
|||
|
|
@ -5,11 +5,12 @@ import { validateApiConfiguration, validateModelId } from "../../utils/validate"
|
|||
import { vscode } from "../../utils/vscode"
|
||||
import ApiOptions from "./ApiOptions"
|
||||
import ExperimentalFeature from "./ExperimentalFeature"
|
||||
import { EXPERIMENT_IDS, experimentConfigsMap } from "../../../../src/shared/experiments"
|
||||
import { EXPERIMENT_IDS, experimentConfigsMap, ExperimentId } from "../../../../src/shared/experiments"
|
||||
import ApiConfigManager from "./ApiConfigManager"
|
||||
import { Dropdown } from "vscrui"
|
||||
import type { DropdownOption } from "vscrui"
|
||||
import { ApiConfiguration } from "../../../../src/shared/api"
|
||||
import ConfirmDialog from "../ui/comfirm-dialog"
|
||||
|
||||
type SettingsViewProps = {
|
||||
onDone: () => void
|
||||
|
|
@ -21,6 +22,7 @@ const SettingsView = ({ onDone }: SettingsViewProps) => {
|
|||
const [modelIdErrorMessage, setModelIdErrorMessage] = useState<string | undefined>(undefined)
|
||||
const [commandInput, setCommandInput] = useState("")
|
||||
const prevApiConfigName = useRef(extensionState.currentApiConfigName)
|
||||
const [isDiscardDialogShow, setDiscardDialogShow] = useState(false)
|
||||
|
||||
// TODO: Reduce WebviewMessage/ExtensionState complexity
|
||||
const [cachedState, setCachedState] = useState(extensionState)
|
||||
|
|
@ -67,7 +69,7 @@ const SettingsView = ({ onDone }: SettingsViewProps) => {
|
|||
}, [currentApiConfigName, extensionState, isChangeDetected])
|
||||
|
||||
const setCachedStateField = useCallback(
|
||||
<K extends keyof ExtensionStateContextType>(field: K, value: ExtensionStateContextType[K]) =>
|
||||
<K extends keyof ExtensionStateContextType>(field: K, value: ExtensionStateContextType[K]) => {
|
||||
setCachedState((prevState) => {
|
||||
if (prevState[field] === value) {
|
||||
return prevState
|
||||
|
|
@ -77,7 +79,8 @@ const SettingsView = ({ onDone }: SettingsViewProps) => {
|
|||
...prevState,
|
||||
[field]: value,
|
||||
}
|
||||
}),
|
||||
})
|
||||
},
|
||||
[],
|
||||
)
|
||||
|
||||
|
|
@ -91,20 +94,27 @@ const SettingsView = ({ onDone }: SettingsViewProps) => {
|
|||
return {
|
||||
...prevState,
|
||||
apiConfiguration: {
|
||||
...apiConfiguration,
|
||||
...prevState.apiConfiguration,
|
||||
[field]: value,
|
||||
},
|
||||
}
|
||||
})
|
||||
},
|
||||
[apiConfiguration],
|
||||
[],
|
||||
)
|
||||
|
||||
const setExperimentEnabled = useCallback(
|
||||
(id: string, enabled: boolean) =>
|
||||
setCachedStateField("experiments", { ...cachedState.experiments, [id]: enabled }),
|
||||
[cachedState.experiments, setCachedStateField],
|
||||
)
|
||||
const setExperimentEnabled = useCallback((id: ExperimentId, enabled: boolean) => {
|
||||
setCachedState((prevState) => {
|
||||
if (prevState.experiments?.[id] === enabled) {
|
||||
return prevState
|
||||
}
|
||||
setChangeDetected(true)
|
||||
return {
|
||||
...prevState,
|
||||
experiments: { ...prevState.experiments, [id]: enabled },
|
||||
}
|
||||
})
|
||||
}, [])
|
||||
|
||||
const handleSubmit = () => {
|
||||
const apiValidationResult = validateApiConfiguration(apiConfiguration)
|
||||
|
|
@ -171,6 +181,24 @@ const SettingsView = ({ onDone }: SettingsViewProps) => {
|
|||
setModelIdErrorMessage(modelIdValidationResult)
|
||||
}, [apiConfiguration, extensionState.glamaModels, extensionState.openRouterModels])
|
||||
|
||||
const confirmDialogHandler = useRef<() => void>()
|
||||
const onConfirmDialogResult = useCallback((confirm: boolean) => {
|
||||
if (confirm) {
|
||||
confirmDialogHandler.current?.()
|
||||
}
|
||||
}, [])
|
||||
const checkUnsaveChanges = useCallback(
|
||||
(then: () => void) => {
|
||||
if (isChangeDetected) {
|
||||
confirmDialogHandler.current = then
|
||||
setDiscardDialogShow(true)
|
||||
} else {
|
||||
then()
|
||||
}
|
||||
},
|
||||
[isChangeDetected],
|
||||
)
|
||||
|
||||
const handleResetState = () => {
|
||||
vscode.postMessage({ type: "resetState" })
|
||||
}
|
||||
|
|
@ -215,6 +243,14 @@ const SettingsView = ({ onDone }: SettingsViewProps) => {
|
|||
flexDirection: "column",
|
||||
overflow: "hidden",
|
||||
}}>
|
||||
<ConfirmDialog
|
||||
icon="codicon-warning"
|
||||
title="Unsaved changes"
|
||||
message="Do you want to discard changes and continue?"
|
||||
show={isDiscardDialogShow}
|
||||
onResult={onConfirmDialogResult}
|
||||
onClose={() => setDiscardDialogShow(false)}
|
||||
aria-labelledby="unsave-warning-dialog"></ConfirmDialog>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
|
|
@ -240,7 +276,7 @@ const SettingsView = ({ onDone }: SettingsViewProps) => {
|
|||
<VSCodeButton
|
||||
appearance="secondary"
|
||||
title="Discard unsaved changes and close settings panel"
|
||||
onClick={onDone}>
|
||||
onClick={() => checkUnsaveChanges(onDone)}>
|
||||
Done
|
||||
</VSCodeButton>
|
||||
</div>
|
||||
|
|
@ -254,9 +290,11 @@ const SettingsView = ({ onDone }: SettingsViewProps) => {
|
|||
currentApiConfigName={currentApiConfigName}
|
||||
listApiConfigMeta={extensionState.listApiConfigMeta}
|
||||
onSelectConfig={(configName: string) => {
|
||||
vscode.postMessage({
|
||||
type: "loadApiConfiguration",
|
||||
text: configName,
|
||||
checkUnsaveChanges(() => {
|
||||
vscode.postMessage({
|
||||
type: "loadApiConfiguration",
|
||||
text: configName,
|
||||
})
|
||||
})
|
||||
}}
|
||||
onDeleteConfig={(configName: string) => {
|
||||
|
|
|
|||
58
webview-ui/src/components/ui/comfirm-dialog.tsx
Normal file
58
webview-ui/src/components/ui/comfirm-dialog.tsx
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
import { Dialog, DialogContent, DialogTitle } from "./dialog"
|
||||
import { VSCodeButton } from "@vscode/webview-ui-toolkit/react"
|
||||
import { useCallback } from "react"
|
||||
|
||||
export interface ConfirmDialogProps {
|
||||
show: boolean
|
||||
icon: string
|
||||
title?: string
|
||||
message: string
|
||||
onResult: (confirm: boolean) => void
|
||||
onClose: () => void
|
||||
}
|
||||
export const ConfirmDialog = ({ onResult, onClose, icon, show, title, message }: ConfirmDialogProps) => {
|
||||
const onCloseConfirmDialog = useCallback(
|
||||
(confirm: boolean) => {
|
||||
onResult(confirm)
|
||||
onClose()
|
||||
},
|
||||
[onClose, onResult],
|
||||
)
|
||||
return (
|
||||
<Dialog
|
||||
open={show}
|
||||
onOpenChange={(open) => {
|
||||
!open && onCloseConfirmDialog(false)
|
||||
}}
|
||||
aria-labelledby="unsave-warning-dialog">
|
||||
<DialogContent className="p-4 max-w-sm">
|
||||
<DialogTitle>{title}</DialogTitle>
|
||||
<p className="text-lg mt-2" data-testid="error-message">
|
||||
<span
|
||||
style={{ fontSize: "2em" }}
|
||||
className={`codicon align-middle mr-1 ${icon || "codicon-warning"}`}
|
||||
/>
|
||||
<span>{message}</span>
|
||||
</p>
|
||||
<div className="flex justify-end gap-2 mt-4">
|
||||
<VSCodeButton
|
||||
appearance="primary"
|
||||
onClick={() => {
|
||||
onCloseConfirmDialog(true)
|
||||
}}>
|
||||
Yes
|
||||
</VSCodeButton>
|
||||
<VSCodeButton
|
||||
appearance="secondary"
|
||||
onClick={() => {
|
||||
onCloseConfirmDialog(false)
|
||||
}}>
|
||||
No
|
||||
</VSCodeButton>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
|
||||
export default ConfirmDialog
|
||||
|
|
@ -21,7 +21,7 @@ const DialogOverlay = React.forwardRef<
|
|||
<DialogPrimitive.Overlay
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
|
||||
"fixed inset-0 z-50 bg-black/50 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
|
|
@ -43,7 +43,7 @@ const DialogContent = React.forwardRef<
|
|||
)}
|
||||
{...props}>
|
||||
{children}
|
||||
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
|
||||
<DialogPrimitive.Close className="cursor-pointer absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
|
||||
<Cross2Icon className="h-4 w-4" />
|
||||
<span className="sr-only">Close</span>
|
||||
</DialogPrimitive.Close>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue