From 588e735493debc745a1cd418a1f0351f0b7a20ef Mon Sep 17 00:00:00 2001 From: Trevor Hudson Date: Wed, 7 May 2025 09:59:22 -0700 Subject: [PATCH] fix webview --- src/core/controller/index.ts | 3 +- src/core/storage/state-keys.ts | 1 + src/core/storage/state.ts | 3 ++ src/core/webview/index.ts | 43 ++++++++++++++++++- src/extension.ts | 42 ++++++++++++------ src/shared/ExtensionMessage.ts | 1 + webview-ui/src/App.tsx | 19 +++++++- .../src/components/welcome/WelcomeTabView.tsx | 5 --- 8 files changed, 95 insertions(+), 22 deletions(-) diff --git a/src/core/controller/index.ts b/src/core/controller/index.ts index b32f226cd6..a590cb5be8 100644 --- a/src/core/controller/index.ts +++ b/src/core/controller/index.ts @@ -1672,6 +1672,7 @@ Here is the project's README to help you get started:\n\n${mcpDetails.readmeCont planActSeparateModelsSetting, globalClineRulesToggles, shellIntegrationTimeout, + showWelcome, } = await getAllExtensionState(this.context) const localClineRulesToggles = @@ -1704,7 +1705,7 @@ Here is the project's README to help you get started:\n\n${mcpDetails.readmeCont mcpMarketplaceEnabled, telemetrySetting, planActSeparateModelsSetting, - showWelcome: false, + showWelcome, vscMachineId: vscode.env.machineId, globalClineRulesToggles: globalClineRulesToggles || {}, localClineRulesToggles: localClineRulesToggles || {}, diff --git a/src/core/storage/state-keys.ts b/src/core/storage/state-keys.ts index 63b914ed98..d85121a498 100644 --- a/src/core/storage/state-keys.ts +++ b/src/core/storage/state-keys.ts @@ -81,5 +81,6 @@ export type GlobalStateKey = | "favoritedModelIds" | "requestTimeoutMs" | "shellIntegrationTimeout" + | "showWelcome" export type LocalStateKey = "localClineRulesToggles" diff --git a/src/core/storage/state.ts b/src/core/storage/state.ts index 1a971ee7b8..e088c42587 100644 --- a/src/core/storage/state.ts +++ b/src/core/storage/state.ts @@ -131,6 +131,7 @@ export async function getAllExtensionState(context: vscode.ExtensionContext) { globalClineRulesToggles, requestTimeoutMs, shellIntegrationTimeout, + showWelcome, ] = await Promise.all([ getGlobalState(context, "apiProvider") as Promise, getGlobalState(context, "apiModelId") as Promise, @@ -210,6 +211,7 @@ export async function getAllExtensionState(context: vscode.ExtensionContext) { getGlobalState(context, "globalClineRulesToggles") as Promise, getGlobalState(context, "requestTimeoutMs") as Promise, getGlobalState(context, "shellIntegrationTimeout") as Promise, + getGlobalState(context, "showWelcome") as Promise, ]) let apiProvider: ApiProvider @@ -334,6 +336,7 @@ export async function getAllExtensionState(context: vscode.ExtensionContext) { telemetrySetting: telemetrySetting || "unset", planActSeparateModelsSetting, shellIntegrationTimeout: shellIntegrationTimeout || 4000, + showWelcome, } } diff --git a/src/core/webview/index.ts b/src/core/webview/index.ts index e73e8eeff6..c9368a981e 100644 --- a/src/core/webview/index.ts +++ b/src/core/webview/index.ts @@ -8,6 +8,7 @@ import { findLast } from "@shared/array" import { readFile } from "fs/promises" import path from "node:path" import { WebviewType } from "@shared/WebviewMessage" +import { setTimeout as setTimeoutPromise } from "node:timers/promises" /* https://github.com/microsoft/vscode-webview-ui-toolkit-samples/blob/main/default/weather-webview/src/providers/WeatherViewProvider.ts https://github.com/KumarVariable/vscode-extension-sidebar-html/blob/master/src/customSidebarViewProvider.ts @@ -87,12 +88,31 @@ export class WebviewProvider implements vscode.WebviewViewProvider { // WebviewView and WebviewPanel have all the same properties except for this visibility listener // panel webviewView.onDidChangeViewState( - () => { + async () => { if (this.view?.visible) { + // Check if showWelcome flag is set in global state + const showWelcome = await this.controller.context.globalState.get("showWelcome") + + // Send didBecomeVisible action this.controller.postMessageToWebview({ type: "action", action: "didBecomeVisible", }) + + // If showWelcome flag is set, send showWelcome message and clear the flag + // We're now sending it to all webviews to ensure it's displayed + if (showWelcome) { + console.log(`Sending showWelcome message to ${this.webviewType} webview`) + this.controller.postMessageToWebview({ + type: "showWelcome", + }) + + // Add a delay before clearing the flag to ensure the webview has time to process it + await setTimeoutPromise(1000) + + // Clear the flag after sending the message + await this.controller.context.globalState.update("showWelcome", undefined) + } } }, null, @@ -101,12 +121,30 @@ export class WebviewProvider implements vscode.WebviewViewProvider { } else if ("onDidChangeVisibility" in webviewView) { // sidebar webviewView.onDidChangeVisibility( - () => { + async () => { if (this.view?.visible) { + // Check if showWelcome flag is set in global state + const showWelcome = await this.controller.context.globalState.get("showWelcome") + + // Send didBecomeVisible action this.controller.postMessageToWebview({ type: "action", action: "didBecomeVisible", }) + + // If showWelcome flag is set, send showWelcome message and clear the flag + if (showWelcome) { + console.log(`Sending showWelcome message to ${this.webviewType} webview`) + this.controller.postMessageToWebview({ + type: "showWelcome", + }) + + // Add a delay before clearing the flag to ensure the webview has time to process it + await setTimeoutPromise(1000) + + // Clear the flag after sending the message + await this.controller.context.globalState.update("showWelcome", undefined) + } } }, null, @@ -222,6 +260,7 @@ export class WebviewProvider implements vscode.WebviewViewProvider { + diff --git a/src/extension.ts b/src/extension.ts index c4b184f634..9a2626e052 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -82,37 +82,46 @@ export function activate(context: vscode.ExtensionContext) { }), ) - const openClineInNewTab = async (): Promise => { + const openClineInNewTab = async (isWelcome = false): Promise => { Logger.log("Opening Cline in new tab") // (this example uses webviewProvider activation event which is necessary to deserialize cached webview, but since we use retainContextWhenHidden, we don't need to use that event) // https://github.com/microsoft/vscode-extension-samples/blob/main/webview-sample/src/extension.ts const tabWebview = new WebviewProvider(context, outputChannel, "tab") - //const column = vscode.window.activeTextEditor ? vscode.window.activeTextEditor.viewColumn : undefined - const lastCol = Math.max(...vscode.window.visibleTextEditors.map((editor) => editor.viewColumn || 0)) + let targetCol: vscode.ViewColumn - // Check if there are any visible text editors, otherwise open a new group to the right - const hasVisibleEditors = vscode.window.visibleTextEditors.length > 0 - if (!hasVisibleEditors) { - await vscode.commands.executeCommand("workbench.action.newGroupRight") + if (!isWelcome) { + //const column = vscode.window.activeTextEditor ? vscode.window.activeTextEditor.viewColumn : undefined + const lastCol = Math.max(...vscode.window.visibleTextEditors.map((editor) => editor.viewColumn || 0)) + + // Check if there are any visible text editors, otherwise open a new group to the right + const hasVisibleEditors = vscode.window.visibleTextEditors.length > 0 + if (!hasVisibleEditors) { + await vscode.commands.executeCommand("workbench.action.newGroupRight") + } + targetCol = hasVisibleEditors ? Math.max(lastCol + 1, 1) : vscode.ViewColumn.Two + } else { + targetCol = vscode.ViewColumn.One } - const targetCol = hasVisibleEditors ? Math.max(lastCol + 1, 1) : vscode.ViewColumn.Two const panel = vscode.window.createWebviewPanel(WebviewProvider.tabPanelId, "Cline", targetCol, { enableScripts: true, retainContextWhenHidden: true, localResourceRoots: [context.extensionUri], }) - // TODO: use better svg icon with light and dark variants (see https://stackoverflow.com/questions/58365687/vscode-extension-iconpath) panel.iconPath = { light: vscode.Uri.joinPath(context.extensionUri, "assets", "icons", "robot_panel_light.png"), dark: vscode.Uri.joinPath(context.extensionUri, "assets", "icons", "robot_panel_dark.png"), } - tabWebview.resolveWebviewView(panel) + + await tabWebview.resolveWebviewView(panel) // Lock the editor group so clicking on files doesn't open them over the panel - await setTimeoutPromise(100) await vscode.commands.executeCommand("workbench.action.lockEditorGroup") + + if (isWelcome) { + panel.reveal(targetCol) + } return tabWebview } @@ -432,8 +441,17 @@ export function activate(context: vscode.ExtensionContext) { const previous = context.globalState.get(VERSION_KEY) if (!previous) { - const tab = await openClineInNewTab() + console.log("First time installation detected, setting showWelcome flag") + // Set the showWelcome flag to true in global state + await context.globalState.update("showWelcome", true) + + // Open a single tab with the welcome screen + const tab = await openClineInNewTab(true) + console.log("Tab opened for welcome screen") + + // Send the showWelcome message directly to the webview await tab.controller.postMessageToWebview({ type: "showWelcome" }) + console.log("Sent showWelcome message directly to webview") // persist for the next activation await context.globalState.update(VERSION_KEY, current) diff --git a/src/shared/ExtensionMessage.ts b/src/shared/ExtensionMessage.ts index 5e10e6fae3..54afceb0ad 100644 --- a/src/shared/ExtensionMessage.ts +++ b/src/shared/ExtensionMessage.ts @@ -144,6 +144,7 @@ export interface ExtensionState { localClineRulesToggles: ClineRulesToggles localCursorRulesToggles: ClineRulesToggles localWindsurfRulesToggles: ClineRulesToggles + showWelcome?: boolean } export interface ClineMessage { diff --git a/webview-ui/src/App.tsx b/webview-ui/src/App.tsx index 2a2f1aaaa5..3306aa71cc 100644 --- a/webview-ui/src/App.tsx +++ b/webview-ui/src/App.tsx @@ -14,12 +14,21 @@ import { McpViewTab } from "@shared/mcp" import WelcomeWrapper from "./components/welcome/WelcomeWrapper" const AppContent = () => { - const { didHydrateState, shouldShowAnnouncement, showMcp, mcpTab, showWelcome } = useExtensionState() + const { didHydrateState, shouldShowAnnouncement, showMcp, mcpTab } = useExtensionState() const [showSettings, setShowSettings] = useState(false) const hideSettings = useCallback(() => setShowSettings(false), []) const [showHistory, setShowHistory] = useState(false) const [showAccount, setShowAccount] = useState(false) const [showAnnouncement, setShowAnnouncement] = useState(false) + // Use local state for welcome view, initialized from extension state + const [showWelcomeLocal, setShowWelcomeLocal] = useState(true) + const { setShowWelcome } = useExtensionState() + + // Sync local state with extension state + useEffect(() => { + console.log("Setting showWelcome in extension state to true") + setShowWelcome(true) + }, [setShowWelcome]) const { setShowMcp, setMcpTab } = useExtensionState() @@ -31,7 +40,12 @@ const AppContent = () => { const handleMessage = useCallback( (e: MessageEvent) => { const message: ExtensionMessage = e.data + console.log("Received message in App.tsx:", message) switch (message.type) { + case "showWelcome": + console.log("Received showWelcome message in App.tsx") + setShowWelcomeLocal(true) + break case "action": switch (message.action!) { case "settingsButtonClicked": @@ -66,6 +80,7 @@ const AppContent = () => { setShowHistory(false) closeMcpView() setShowAccount(false) + setShowWelcomeLocal(false) // Hide welcome view when chat button is clicked break } break @@ -98,7 +113,7 @@ const AppContent = () => { return ( <> - {showWelcome ? ( + {showWelcomeLocal ? ( ) : ( <> diff --git a/webview-ui/src/components/welcome/WelcomeTabView.tsx b/webview-ui/src/components/welcome/WelcomeTabView.tsx index 28b8c85122..ff8753d974 100644 --- a/webview-ui/src/components/welcome/WelcomeTabView.tsx +++ b/webview-ui/src/components/welcome/WelcomeTabView.tsx @@ -118,11 +118,6 @@ const WelcomeTabView = memo(({ onToggleView, showApiOptions, setShowApiOptions } - {showApiOptions !== undefined && setShowApiOptions !== undefined && !showApiOptions && ( - - )} {showApiOptions !== undefined && }