Browse Source

feat: add direct navigation to settings sections (#7770)

Replace delayed scroll-to-settings approach with direct section targeting.
Settings sections can now be opened directly via navigateToSettings(section)
parameter, eliminating the need for setTimeout-based scrolling workarounds.
Ara 1 month ago
parent
commit
aa9573fb0a

+ 2 - 1
webview-ui/src/App.tsx

@@ -20,6 +20,7 @@ const AppContent = () => {
 		showMcp,
 		mcpTab,
 		showSettings,
+		settingsTargetSection,
 		showHistory,
 		showAccount,
 		showAnnouncement,
@@ -61,7 +62,7 @@ const AppContent = () => {
 
 	return (
 		<div className="flex h-screen w-full flex-col">
-			{showSettings && <SettingsView onDone={hideSettings} />}
+			{showSettings && <SettingsView onDone={hideSettings} targetSection={settingsTargetSection} />}
 			{showHistory && <HistoryView onDone={hideHistory} />}
 			{showMcp && <McpView initialTab={mcpTab} onDone={closeMcpView} />}
 			{showAccount && (

+ 3 - 13
webview-ui/src/components/browser/BrowserSettingsMenu.tsx

@@ -1,8 +1,8 @@
-import { EmptyRequest, StringRequest } from "@shared/proto/cline/common"
+import { EmptyRequest } from "@shared/proto/cline/common"
 import { VSCodeButton } from "@vscode/webview-ui-toolkit/react"
 import { useEffect, useRef, useState } from "react"
 import { useExtensionState } from "@/context/ExtensionStateContext"
-import { BrowserServiceClient, UiServiceClient } from "../../services/grpc-client"
+import { BrowserServiceClient } from "../../services/grpc-client"
 
 interface ConnectionInfo {
 	isConnected: boolean
@@ -63,17 +63,7 @@ export const BrowserSettingsMenu = () => {
 	}, [showInfoPopover])
 
 	const openBrowserSettings = () => {
-		// First open the settings panel using direct navigation
-		navigateToSettings()
-
-		// After a short delay, send a message to scroll to browser settings
-		setTimeout(async () => {
-			try {
-				await UiServiceClient.scrollToSettings(StringRequest.create({ value: "browser" }))
-			} catch (error) {
-				console.error("Error scrolling to browser settings:", error)
-			}
-		}, 300) // Give the settings panel time to open
+		navigateToSettings("browser")
 	}
 
 	const toggleInfoPopover = () => {

+ 2 - 13
webview-ui/src/components/chat/auto-approve-menu/AutoApproveBar.tsx

@@ -1,7 +1,5 @@
-import { StringRequest } from "@shared/proto/cline/common"
 import { useRef, useState } from "react"
 import { useExtensionState } from "@/context/ExtensionStateContext"
-import { UiServiceClient } from "@/services/grpc-client"
 import { getAsVar, VSC_TITLEBAR_INACTIVE_FOREGROUND } from "@/utils/vscStyles"
 import AutoApproveModal from "./AutoApproveModal"
 import { ACTION_METADATA } from "./constants"
@@ -16,19 +14,10 @@ const AutoApproveBar = ({ style }: AutoApproveBarProps) => {
 	const [isModalVisible, setIsModalVisible] = useState(false)
 	const buttonRef = useRef<HTMLDivElement>(null)
 
-	const handleNavigateToFeatures = async (e: React.MouseEvent) => {
+	const handleNavigateToFeatures = (e: React.MouseEvent) => {
 		e.preventDefault()
 		e.stopPropagation()
-
-		navigateToSettings()
-
-		setTimeout(async () => {
-			try {
-				await UiServiceClient.scrollToSettings(StringRequest.create({ value: "features" }))
-			} catch (error) {
-				console.error("Error scrolling to features settings:", error)
-			}
-		}, 300)
+		navigateToSettings("features")
 	}
 
 	const getEnabledActionsText = () => {

+ 2 - 15
webview-ui/src/components/chat/auto-approve-menu/AutoApproveModal.tsx

@@ -1,9 +1,7 @@
-import { StringRequest } from "@shared/proto/cline/common"
 import React, { useEffect, useRef, useState } from "react"
 import { useClickAway } from "react-use"
 import { useExtensionState } from "@/context/ExtensionStateContext"
 import { useAutoApproveActions } from "@/hooks/useAutoApproveActions"
-import { UiServiceClient } from "@/services/grpc-client"
 import { getAsVar, VSC_TITLEBAR_INACTIVE_FOREGROUND } from "@/utils/vscStyles"
 import AutoApproveMenuItem from "./AutoApproveMenuItem"
 import { ActionMetadata } from "./types"
@@ -21,21 +19,10 @@ const AutoApproveModal: React.FC<AutoApproveModalProps> = ({ isVisible, setIsVis
 	const { navigateToSettings } = useExtensionState()
 	const { isChecked, updateAction } = useAutoApproveActions()
 
-	const handleNotificationsLinkClick = async (e: React.MouseEvent) => {
+	const handleNotificationsLinkClick = (e: React.MouseEvent) => {
 		e.preventDefault()
 		e.stopPropagation()
-
-		// Navigate to settings
-		navigateToSettings()
-
-		// Scroll to general section
-		setTimeout(async () => {
-			try {
-				await UiServiceClient.scrollToSettings(StringRequest.create({ value: "general" }))
-			} catch (error) {
-				console.error("Error scrolling to general settings:", error)
-			}
-		}, 300)
+		navigateToSettings("general")
 	}
 
 	const modalRef = useRef<HTMLDivElement>(null)

+ 1 - 10
webview-ui/src/components/chat/task-header/TaskHeader.tsx

@@ -1,12 +1,10 @@
 import { ClineMessage } from "@shared/ExtensionMessage"
-import { StringRequest } from "@shared/proto/cline/common"
 import { ChevronDownIcon, ChevronRightIcon } from "lucide-react"
 import React, { useCallback, useLayoutEffect, useMemo, useState } from "react"
 import Thumbnails from "@/components/common/Thumbnails"
 import { getModeSpecificFields, normalizeApiConfiguration } from "@/components/settings/utils/providerUtils"
 import { useExtensionState } from "@/context/ExtensionStateContext"
 import { cn } from "@/lib/utils"
-import { UiServiceClient } from "@/services/grpc-client"
 import { getEnvironmentColor } from "@/utils/environmentColors"
 import CopyTaskButton from "./buttons/CopyTaskButton"
 import DeleteTaskButton from "./buttons/DeleteTaskButton"
@@ -103,14 +101,7 @@ const TaskHeader: React.FC<TaskHeaderProps> = ({
 	const toggleTaskExpanded = useCallback(() => setIsTaskExpanded(!isTaskExpanded), [setIsTaskExpanded, isTaskExpanded])
 
 	const handleCheckpointSettingsClick = useCallback(() => {
-		navigateToSettings()
-		setTimeout(async () => {
-			try {
-				await UiServiceClient.scrollToSettings(StringRequest.create({ value: "features" }))
-			} catch (error) {
-				console.error("Error scrolling to checkpoint settings:", error)
-			}
-		}, 300)
+		navigateToSettings("features")
 	}, [navigateToSettings])
 
 	const environmentBorderColor = getEnvironmentColor(environment, "border")

+ 3 - 13
webview-ui/src/components/common/CliInstallBanner.tsx

@@ -1,11 +1,10 @@
-import { StringRequest } from "@shared/proto/cline/common"
 import { EmptyRequest, Int64Request } from "@shared/proto/index.cline"
 import { VSCodeButton } from "@vscode/webview-ui-toolkit/react"
 import { Terminal, XIcon } from "lucide-react"
 import { useCallback, useEffect, useState } from "react"
 import { Button } from "@/components/ui/button"
 import { useExtensionState } from "@/context/ExtensionStateContext"
-import { StateServiceClient, UiServiceClient } from "@/services/grpc-client"
+import { StateServiceClient } from "@/services/grpc-client"
 import { isMacOSOrLinux } from "@/utils/platformUtils"
 import { getAsVar, VSC_INACTIVE_SELECTION_BACKGROUND } from "@/utils/vscStyles"
 
@@ -62,18 +61,9 @@ export const CliInstallBanner: React.FC = () => {
 		}
 	}
 
-	const handleEnableSubagents = async () => {
+	const handleEnableSubagents = () => {
 		if (!subagentsEnabled) {
-			// Navigate to settings and enable subagents
-			navigateToSettings()
-			// Scroll to features section after a brief delay to ensure settings is rendered
-			setTimeout(async () => {
-				try {
-					await UiServiceClient.scrollToSettings(StringRequest.create({ value: "features" }))
-				} catch (error) {
-					console.error("Error scrolling to features settings:", error)
-				}
-			}, 300)
+			navigateToSettings("features")
 		}
 	}
 

+ 3 - 17
webview-ui/src/components/mcp/configuration/tabs/installed/ConfigureServersView.tsx

@@ -1,7 +1,7 @@
-import { EmptyRequest, StringRequest } from "@shared/proto/cline/common"
+import { EmptyRequest } from "@shared/proto/cline/common"
 import { VSCodeButton, VSCodeLink } from "@vscode/webview-ui-toolkit/react"
 import { useExtensionState } from "@/context/ExtensionStateContext"
-import { McpServiceClient, UiServiceClient } from "@/services/grpc-client"
+import { McpServiceClient } from "@/services/grpc-client"
 import ServersToggleList from "./ServersToggleList"
 
 const ConfigureServersView = () => {
@@ -48,21 +48,7 @@ const ConfigureServersView = () => {
 				</VSCodeButton>
 
 				<div style={{ textAlign: "center" }}>
-					<VSCodeLink
-						onClick={() => {
-							// First open the settings panel using direct navigation
-							navigateToSettings()
-
-							// After a short delay, send a message to scroll to browser settings
-							setTimeout(async () => {
-								try {
-									await UiServiceClient.scrollToSettings(StringRequest.create({ value: "features" }))
-								} catch (error) {
-									console.error("Error scrolling to mcp settings:", error)
-								}
-							}, 300)
-						}}
-						style={{ fontSize: "12px" }}>
+					<VSCodeLink onClick={() => navigateToSettings("features")} style={{ fontSize: "12px" }}>
 						Advanced MCP Settings
 					</VSCodeLink>
 				</div>

+ 18 - 8
webview-ui/src/context/ExtensionStateContext.tsx

@@ -54,6 +54,7 @@ export interface ExtensionStateContextType extends ExtensionState {
 	hooksEnabled?: ClineFeatureSetting
 	mcpTab?: McpViewTab
 	showSettings: boolean
+	settingsTargetSection?: string
 	showHistory: boolean
 	showAccount: boolean
 	showAnnouncement: boolean
@@ -97,7 +98,7 @@ export interface ExtensionStateContextType extends ExtensionState {
 
 	// Navigation functions
 	navigateToMcp: (tab?: McpViewTab) => void
-	navigateToSettings: () => void
+	navigateToSettings: (targetSection?: string) => void
 	navigateToHistory: () => void
 	navigateToAccount: () => void
 	navigateToChat: () => void
@@ -123,6 +124,7 @@ export const ExtensionStateContextProvider: React.FC<{
 	const [showMcp, setShowMcp] = useState(false)
 	const [mcpTab, setMcpTab] = useState<McpViewTab | undefined>(undefined)
 	const [showSettings, setShowSettings] = useState(false)
+	const [settingsTargetSection, setSettingsTargetSection] = useState<string | undefined>(undefined)
 	const [showHistory, setShowHistory] = useState(false)
 	const [showAccount, setShowAccount] = useState(false)
 	const [showAnnouncement, setShowAnnouncement] = useState(false)
@@ -135,7 +137,10 @@ export const ExtensionStateContextProvider: React.FC<{
 	}, [setShowMcp, setMcpTab])
 
 	// Hide functions
-	const hideSettings = useCallback(() => setShowSettings(false), [setShowSettings])
+	const hideSettings = useCallback(() => {
+		setShowSettings(false)
+		setSettingsTargetSection(undefined)
+	}, [])
 	const hideHistory = useCallback(() => setShowHistory(false), [setShowHistory])
 	const hideAccount = useCallback(() => setShowAccount(false), [setShowAccount])
 	const hideAnnouncement = useCallback(() => setShowAnnouncement(false), [setShowAnnouncement])
@@ -155,12 +160,16 @@ export const ExtensionStateContextProvider: React.FC<{
 		[setShowMcp, setMcpTab, setShowSettings, setShowHistory, setShowAccount],
 	)
 
-	const navigateToSettings = useCallback(() => {
-		setShowHistory(false)
-		closeMcpView()
-		setShowAccount(false)
-		setShowSettings(true)
-	}, [setShowSettings, setShowHistory, closeMcpView, setShowAccount])
+	const navigateToSettings = useCallback(
+		(targetSection?: string) => {
+			setShowHistory(false)
+			closeMcpView()
+			setShowAccount(false)
+			setSettingsTargetSection(targetSection)
+			setShowSettings(true)
+		},
+		[closeMcpView],
+	)
 
 	const navigateToHistory = useCallback(() => {
 		setShowSettings(false)
@@ -705,6 +714,7 @@ export const ExtensionStateContextProvider: React.FC<{
 		showMcp,
 		mcpTab,
 		showSettings,
+		settingsTargetSection,
 		showHistory,
 		showAccount,
 		showAnnouncement,