Răsfoiți Sursa

Merge pull request #1082 from RooVetGit/switch_tab_unsaved_check

Warn when switching tabs with unsaved content
Matt Rubens 10 luni în urmă
părinte
comite
bfd5024027
2 a modificat fișierele cu 56 adăugiri și 28 ștergeri
  1. 30 15
      webview-ui/src/App.tsx
  2. 26 13
      webview-ui/src/components/settings/SettingsView.tsx

+ 30 - 15
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<Tab>("chat")
+	const settingsRef = useRef<SettingsViewRef>(null)
 
-	const onMessage = useCallback((e: MessageEvent) => {
-		const message: ExtensionMessage = e.data
+	const switchTab = useCallback(
+		(newTab: Tab) => {
+			if (tab === "settings" && settingsRef.current?.checkUnsaveChanges) {
+				settingsRef.current.checkUnsaveChanges(() => setTab(newTab))
+			} else {
+				setTab(newTab)
+			}
+		},
+		[tab],
+	)
 
-		if (message.type === "action" && message.action) {
-			const newTab = tabsByMessageAction[message.action]
+	const onMessage = useCallback(
+		(e: MessageEvent) => {
+			const message: ExtensionMessage = e.data
 
-			if (newTab) {
-				setTab(newTab)
+			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 = () => {
 		<WelcomeView />
 	) : (
 		<>
-			{tab === "settings" && <SettingsView onDone={() => setTab("chat")} />}
-			{tab === "history" && <HistoryView onDone={() => setTab("chat")} />}
-			{tab === "mcp" && <McpView onDone={() => setTab("chat")} />}
-			{tab === "prompts" && <PromptsView onDone={() => setTab("chat")} />}
+			{tab === "settings" && <SettingsView ref={settingsRef} onDone={() => switchTab("chat")} />}
+			{tab === "history" && <HistoryView onDone={() => switchTab("chat")} />}
+			{tab === "mcp" && <McpView onDone={() => switchTab("chat")} />}
+			{tab === "prompts" && <PromptsView onDone={() => switchTab("chat")} />}
 			<ChatView
 				isHidden={tab !== "chat"}
 				showAnnouncement={showAnnouncement}
 				hideAnnouncement={() => setShowAnnouncement(false)}
-				showHistoryView={() => setTab("history")}
+				showHistoryView={() => switchTab("history")}
 			/>
 		</>
 	)

+ 26 - 13
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<SettingsViewRef, SettingsViewProps>(({ onDone }, ref) => {
 	const extensionState = useExtensionState()
 	const [apiErrorMessage, setApiErrorMessage] = useState<string | undefined>(undefined)
 	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)
 	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) => {
 					<AlertDialogHeader>
 						<AlertDialogTitle>Unsaved changes</AlertDialogTitle>
 						<AlertDialogDescription>
-							<span style={{ fontSize: "2em" }} className={`codicon codicon-warning align-middle mr-1`} />
+							<span className={`codicon codicon-warning align-middle mr-1`} />
 							Do you want to discard changes and continue?
 						</AlertDialogDescription>
 					</AlertDialogHeader>
@@ -890,6 +903,6 @@ const SettingsView = ({ onDone }: SettingsViewProps) => {
 			</div>
 		</div>
 	)
-}
+})
 
 export default memo(SettingsView)