diff --git a/webview-ui/src/App.tsx b/webview-ui/src/App.tsx index f66385f0dc..3980f38cc8 100644 --- a/webview-ui/src/App.tsx +++ b/webview-ui/src/App.tsx @@ -1,4 +1,4 @@ -import { useCallback, useEffect, useState } from "react" +import { useCallback, useEffect, useRef, useState } from "react" import { useEvent } from "react-use" import { ExtensionMessage } from "../../src/shared/ExtensionMessage" @@ -7,7 +7,7 @@ import { vscode } from "./utils/vscode" import { ExtensionStateContextProvider, useExtensionState } from "./context/ExtensionStateContext" import ChatView from "./components/chat/ChatView" import HistoryView from "./components/history/HistoryView" -import SettingsView from "./components/settings/SettingsView" +import SettingsView, { SettingsViewRef } from "./components/settings/SettingsView" import WelcomeView from "./components/welcome/WelcomeView" import McpView from "./components/mcp/McpView" import PromptsView from "./components/prompts/PromptsView" @@ -26,18 +26,33 @@ const App = () => { const { didHydrateState, showWelcome, shouldShowAnnouncement } = useExtensionState() const [showAnnouncement, setShowAnnouncement] = useState(false) const [tab, setTab] = useState("chat") + const settingsRef = useRef(null) - const onMessage = useCallback((e: MessageEvent) => { - const message: ExtensionMessage = e.data - - if (message.type === "action" && message.action) { - const newTab = tabsByMessageAction[message.action] - - if (newTab) { + const switchTab = useCallback( + (newTab: Tab) => { + if (tab === "settings" && settingsRef.current?.checkUnsaveChanges) { + settingsRef.current.checkUnsaveChanges(() => setTab(newTab)) + } else { setTab(newTab) } - } - }, []) + }, + [tab], + ) + + const onMessage = useCallback( + (e: MessageEvent) => { + const message: ExtensionMessage = e.data + + if (message.type === "action" && message.action) { + const newTab = tabsByMessageAction[message.action] + + if (newTab) { + switchTab(newTab) + } + } + }, + [switchTab], + ) useEvent("message", onMessage) @@ -58,15 +73,15 @@ const App = () => { ) : ( <> - {tab === "settings" && setTab("chat")} />} - {tab === "history" && setTab("chat")} />} - {tab === "mcp" && setTab("chat")} />} - {tab === "prompts" && setTab("chat")} />} + {tab === "settings" && switchTab("chat")} />} + {tab === "history" && switchTab("chat")} />} + {tab === "mcp" && switchTab("chat")} />} + {tab === "prompts" && switchTab("chat")} />} setShowAnnouncement(false)} - showHistoryView={() => setTab("history")} + showHistoryView={() => switchTab("history")} /> ) diff --git a/webview-ui/src/components/settings/SettingsView.tsx b/webview-ui/src/components/settings/SettingsView.tsx index 69f930962c..495bf49bd7 100644 --- a/webview-ui/src/components/settings/SettingsView.tsx +++ b/webview-ui/src/components/settings/SettingsView.tsx @@ -1,5 +1,5 @@ import { VSCodeButton, VSCodeCheckbox, VSCodeLink, VSCodeTextField } from "@vscode/webview-ui-toolkit/react" -import { memo, useCallback, useEffect, useRef, useState } from "react" +import { forwardRef, memo, useCallback, useEffect, useImperativeHandle, useRef, useState } from "react" import { ExtensionStateContextType, useExtensionState } from "../../context/ExtensionStateContext" import { validateApiConfiguration, validateModelId } from "../../utils/validate" import { vscode } from "../../utils/vscode" @@ -25,17 +25,22 @@ type SettingsViewProps = { onDone: () => void } -const SettingsView = ({ onDone }: SettingsViewProps) => { +export interface SettingsViewRef { + checkUnsaveChanges: (then: () => void) => void +} + +const SettingsView = forwardRef(({ onDone }, ref) => { const extensionState = useExtensionState() const [apiErrorMessage, setApiErrorMessage] = useState(undefined) 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) const [isChangeDetected, setChangeDetected] = useState(false) + const prevApiConfigName = useRef(extensionState.currentApiConfigName) + const confirmDialogHandler = useRef<() => void>() + + // TODO: Reduce WebviewMessage/ExtensionState complexity const { currentApiConfigName } = extensionState const { apiConfiguration, @@ -190,12 +195,6 @@ 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) { @@ -208,6 +207,20 @@ const SettingsView = ({ onDone }: SettingsViewProps) => { [isChangeDetected], ) + useImperativeHandle( + ref, + () => ({ + checkUnsaveChanges, + }), + [checkUnsaveChanges], + ) + + const onConfirmDialogResult = useCallback((confirm: boolean) => { + if (confirm) { + confirmDialogHandler.current?.() + } + }, []) + const handleResetState = () => { vscode.postMessage({ type: "resetState" }) } @@ -250,7 +263,7 @@ const SettingsView = ({ onDone }: SettingsViewProps) => { Unsaved changes - + Do you want to discard changes and continue? @@ -890,6 +903,6 @@ const SettingsView = ({ onDone }: SettingsViewProps) => { ) -} +}) export default memo(SettingsView)