From 1fcdd3333924432a95d66d1757fe0793e288f19c Mon Sep 17 00:00:00 2001 From: System233 Date: Wed, 19 Feb 2025 02:15:14 +0800 Subject: [PATCH] Add confirmation dialog for discarding changes. --- .../components/settings/ApiConfigManager.tsx | 3 - .../src/components/settings/SettingsView.tsx | 66 +++++++++++++++---- .../src/components/ui/comfirm-dialog.tsx | 58 ++++++++++++++++ webview-ui/src/components/ui/dialog.tsx | 4 +- 4 files changed, 112 insertions(+), 19 deletions(-) create mode 100644 webview-ui/src/components/ui/comfirm-dialog.tsx diff --git a/webview-ui/src/components/settings/ApiConfigManager.tsx b/webview-ui/src/components/settings/ApiConfigManager.tsx index 15c9a2b0c0..7d969eace8 100644 --- a/webview-ui/src/components/settings/ApiConfigManager.tsx +++ b/webview-ui/src/components/settings/ApiConfigManager.tsx @@ -299,9 +299,6 @@ const ApiConfigManager = ({ aria-labelledby="new-profile-title"> New Configuration Profile - void @@ -21,6 +22,7 @@ const SettingsView = ({ onDone }: SettingsViewProps) => { const [modelIdErrorMessage, setModelIdErrorMessage] = useState(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( - (field: K, value: ExtensionStateContextType[K]) => + (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", }}> + setDiscardDialogShow(false)} + aria-labelledby="unsave-warning-dialog">
{ + onClick={() => checkUnsaveChanges(onDone)}> Done
@@ -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) => { diff --git a/webview-ui/src/components/ui/comfirm-dialog.tsx b/webview-ui/src/components/ui/comfirm-dialog.tsx new file mode 100644 index 0000000000..35d8f999cc --- /dev/null +++ b/webview-ui/src/components/ui/comfirm-dialog.tsx @@ -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 ( + { + !open && onCloseConfirmDialog(false) + }} + aria-labelledby="unsave-warning-dialog"> + + {title} +

+ + {message} +

+
+ { + onCloseConfirmDialog(true) + }}> + Yes + + { + onCloseConfirmDialog(false) + }}> + No + +
+
+
+ ) +} + +export default ConfirmDialog diff --git a/webview-ui/src/components/ui/dialog.tsx b/webview-ui/src/components/ui/dialog.tsx index 9025b03de1..a8c3542a54 100644 --- a/webview-ui/src/components/ui/dialog.tsx +++ b/webview-ui/src/components/ui/dialog.tsx @@ -21,7 +21,7 @@ const DialogOverlay = React.forwardRef< {children} - + Close