diff --git a/src/vs/workbench/browser/actions/layoutActions.ts b/src/vs/workbench/browser/actions/layoutActions.ts index 27a0f82271d3d..708aad9c546e6 100644 --- a/src/vs/workbench/browser/actions/layoutActions.ts +++ b/src/vs/workbench/browser/actions/layoutActions.ts @@ -22,7 +22,7 @@ import { IPaneCompositePartService } from '../../services/panecomposite/browser/ import { ToggleAuxiliaryBarAction } from '../parts/auxiliarybar/auxiliaryBarActions.js'; import { TogglePanelAction } from '../parts/panel/panelActions.js'; import { ICommandService } from '../../../platform/commands/common/commands.js'; -import { AuxiliaryBarVisibleContext, PanelAlignmentContext, PanelVisibleContext, SideBarVisibleContext, FocusedViewContext, InEditorZenModeContext, IsMainEditorCenteredLayoutContext, MainEditorAreaVisibleContext, IsMainWindowFullscreenContext, PanelPositionContext, IsAuxiliaryWindowFocusedContext, TitleBarStyleContext, PearAIVisibleContext } from '../../common/contextkeys.js'; +import { AuxiliaryBarVisibleContext, PanelAlignmentContext, PanelVisibleContext, SideBarVisibleContext, FocusedViewContext, InEditorZenModeContext, IsMainEditorCenteredLayoutContext, MainEditorAreaVisibleContext, IsMainWindowFullscreenContext, PanelPositionContext, IsAuxiliaryWindowFocusedContext, TitleBarStyleContext, PearAIVisibleContext, PearAICreatorVisibleContext } from '../../common/contextkeys.js'; import { Codicon } from '../../../base/common/codicons.js'; import { ThemeIcon } from '../../../base/common/themables.js'; import { DisposableStore } from '../../../base/common/lifecycle.js'; @@ -33,6 +33,7 @@ import { IKeybindingService } from '../../../platform/keybinding/common/keybindi import { TitlebarStyle } from '../../../platform/window/common/window.js'; import { IPreferencesService } from '../../services/preferences/common/preferences.js'; import { TogglePearOverlayAction } from '../parts/overlay/pearOverlayActions.js'; +import { ToggleCreatorOverlayAction } from '../parts/overlay/creatorView/creatorOverlayActions.js'; // Register Icons const menubarIcon = registerIcon('menuBar', Codicon.layoutMenubar, localize('menuBarIcon', "Represents the menu bar")); @@ -1393,7 +1394,8 @@ ToggleVisibilityActions.push(...[ CreateToggleLayoutItem(ToggleAuxiliaryBarAction.ID, AuxiliaryBarVisibleContext, localize('secondarySideBar', "PearAI Side Bar"), { whenA: ContextKeyExpr.equals('config.workbench.sideBar.location', 'left'), iconA: panelRightIcon, iconB: panelLeftIcon }), CreateToggleLayoutItem(TogglePanelAction.ID, PanelVisibleContext, localize('panel', "Panel"), panelIcon), CreateToggleLayoutItem(ToggleStatusbarVisibilityAction.ID, ContextKeyExpr.equals('config.workbench.statusBar.visible', true), localize('statusBar', "Status Bar"), statusBarIcon), - CreateToggleLayoutItem(TogglePearOverlayAction.ID, PearAIVisibleContext, 'PearAI', pearaiIcon) + CreateToggleLayoutItem(TogglePearOverlayAction.ID, PearAIVisibleContext, 'PearAI', pearaiIcon), + CreateToggleLayoutItem(ToggleCreatorOverlayAction.ID, PearAICreatorVisibleContext, 'PearAI Creator'), ]); const MoveSideBarActions: CustomizeLayoutItem[] = [ diff --git a/src/vs/workbench/browser/layout.ts b/src/vs/workbench/browser/layout.ts index 5b38084e30cfa..c0f66bdebe5ad 100644 --- a/src/vs/workbench/browser/layout.ts +++ b/src/vs/workbench/browser/layout.ts @@ -263,6 +263,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi private editorPartView!: ISerializableView; private statusBarPartView!: ISerializableView; private pearOverlayPartView!: ISerializableView; + private pearCreatorOverlayPartView!: ISerializableView; private environmentService!: IBrowserWorkbenchEnvironmentService; private extensionService!: IExtensionService; @@ -1489,6 +1490,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi const sideBar = this.getPart(Parts.SIDEBAR_PART); const statusBar = this.getPart(Parts.STATUSBAR_PART); const pearOverlayPart = this.getPart(Parts.PEAROVERLAY_PART); + const pearCreatorOverlayPart = this.getPart(Parts.PEARCREATOROVERLAY_PART); // View references for all parts this.titleBarPartView = titleBar; @@ -1500,6 +1502,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi this.auxiliaryBarPartView = auxiliaryBarPart; this.statusBarPartView = statusBar; this.pearOverlayPartView = pearOverlayPart; + this.pearCreatorOverlayPartView = pearCreatorOverlayPart; // Create a new container for PearOverlayPart const pearOverlayPartContainer = document.createElement("div"); @@ -1516,6 +1519,22 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi this.mainContainer.appendChild(pearOverlayPartContainer); pearOverlayPart.create(pearOverlayPartContainer); + // Create a container for the PearCreatorOverlayPart + + const pearCreatorOverlayPartContainer = document.createElement("div"); + pearCreatorOverlayPartContainer.style.position = "absolute"; + pearCreatorOverlayPartContainer.style.top = "0"; + pearCreatorOverlayPartContainer.style.left = "0"; + pearCreatorOverlayPartContainer.style.right = "0"; + pearCreatorOverlayPartContainer.style.bottom = "0"; + pearCreatorOverlayPartContainer.style.zIndex = "-10"; + pearCreatorOverlayPartContainer.style.display = "absolute"; + pearCreatorOverlayPartContainer.classList.add("pearcreatoroverlay-part-container"); + pearCreatorOverlayPartContainer.style.backgroundColor = 'transparent'; + + this.mainContainer.appendChild(pearCreatorOverlayPartContainer); + pearCreatorOverlayPart.create(pearCreatorOverlayPartContainer); + const viewMap = { [Parts.ACTIVITYBAR_PART]: this.activityBarPartView, [Parts.BANNER_PART]: this.bannerPartView, @@ -1526,6 +1545,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi [Parts.STATUSBAR_PART]: this.statusBarPartView, [Parts.AUXILIARYBAR_PART]: this.auxiliaryBarPartView, [Parts.PEAROVERLAY_PART]: this.pearOverlayPartView, + [Parts.PEARCREATOROVERLAY_PART]: this.pearCreatorOverlayPartView, }; const fromJSON = ({ type }: { type: Parts }) => viewMap[type]; diff --git a/src/vs/workbench/browser/parts/overlay/creatorView/creatorOverlayActions.ts b/src/vs/workbench/browser/parts/overlay/creatorView/creatorOverlayActions.ts new file mode 100644 index 0000000000000..c391878b5e71d --- /dev/null +++ b/src/vs/workbench/browser/parts/overlay/creatorView/creatorOverlayActions.ts @@ -0,0 +1,120 @@ +/* eslint-disable header/header */ + +import { + registerAction2, + Action2, +} from "../../../../../platform/actions/common/actions.js"; +import { ServicesAccessor } from "../../../../../platform/instantiation/common/instantiation.js"; +import { ICreatorOverlayService } from "./creatorOverlayService.js"; // Added .js extension +import { KeyCode, KeyMod } from "../../../../../base/common/keyCodes.js"; +// import { PearAICreatorVisibleContext } from "../../../../common/contextkeys.js"; + +export class HideCreatorLoadingOverlayAction extends Action2 { + static readonly ID = "workbench.action.hideCreatorLoadingOverlay"; + + constructor() { + super({ + id: HideCreatorLoadingOverlayAction.ID, + title: { + value: "Close Creator Loading Overlay", + original: "Close Creator Loading Overlay", + }, + f1: false, + }); + } + + run(accessor: ServicesAccessor): void { + const creatorOverlayService = accessor.get(ICreatorOverlayService); + creatorOverlayService.hideLoadingOverlay(); + } +} + +export class CloseCreatorOverlayAction extends Action2 { + static readonly ID = "workbench.action.closeCreatorView"; + + constructor() { + super({ + id: CloseCreatorOverlayAction.ID, + title: { + value: "Close Creator View Popup", + original: "Close Creator View Popup", + }, + f1: true, + keybinding: { + weight: 210, + primary: KeyCode.Escape, + // when: PearAICreatorVisibleContext, + }, + }); + } + + run(accessor: ServicesAccessor): void { + const creatorOverlayService = accessor.get(ICreatorOverlayService); + creatorOverlayService.hide(); + } +} + +export class ToggleCreatorOverlayAction extends Action2 { + static readonly ID = "workbench.action.toggleCreatorView"; + + constructor() { + super({ + id: ToggleCreatorOverlayAction.ID, + title: { + value: "Toggle Creator View Popup", + original: "Toggle Creator View Popup", + }, + f1: true, + keybinding: { + weight: 200, + primary: KeyMod.CtrlCmd | KeyCode.KeyE, + }, + }); + } + + run(accessor: ServicesAccessor): void { + const creatorOverlayService = accessor.get(ICreatorOverlayService); + creatorOverlayService.toggle(); + } +} + +export class LockCreatorOverlayAction extends Action2 { + static readonly ID = "workbench.action.lockCreatorView"; + + constructor() { + super({ + id: LockCreatorOverlayAction.ID, + title: { value: "Lock Creator View", original: "Lock Creator View" }, + f1: true, + }); + } + + run(accessor: ServicesAccessor): void { + const creatorOverlayService = accessor.get(ICreatorOverlayService); + creatorOverlayService.lock(); + } +} + +export class UnlockCreatorOverlayAction extends Action2 { + static readonly ID = "workbench.action.unlockCreatorView"; + + constructor() { + super({ + id: UnlockCreatorOverlayAction.ID, + title: { value: "Unlock Creator View", original: "Unlock Creator View" }, + f1: true, + }); + } + + run(accessor: ServicesAccessor): void { + const creatorOverlayService = accessor.get(ICreatorOverlayService); + creatorOverlayService.unlock(); + } +} + +// Register all actions +registerAction2(CloseCreatorOverlayAction); +registerAction2(ToggleCreatorOverlayAction); +registerAction2(LockCreatorOverlayAction); +registerAction2(UnlockCreatorOverlayAction); +registerAction2(HideCreatorLoadingOverlayAction); diff --git a/src/vs/workbench/browser/parts/overlay/creatorView/creatorOverlayPart.ts b/src/vs/workbench/browser/parts/overlay/creatorView/creatorOverlayPart.ts new file mode 100644 index 0000000000000..440779a524219 --- /dev/null +++ b/src/vs/workbench/browser/parts/overlay/creatorView/creatorOverlayPart.ts @@ -0,0 +1,468 @@ +/* eslint-disable local/code-no-unexternalized-strings */ +/* eslint-disable header/header */ + +import { Part } from "../../../../../workbench/browser/part.js"; +import { + IWorkbenchLayoutService, + Parts, +} from "../../../../../workbench/services/layout/browser/layoutService.js"; +import { IThemeService } from "../../../../../platform/theme/common/themeService.js"; +import { IStorageService } from "../../../../../platform/storage/common/storage.js"; +import { $, getActiveWindow } from "../../../../../base/browser/dom.js"; +import { CancellationTokenSource } from "../../../../../base/common/cancellation.js"; +import { IInstantiationService } from "../../../../../platform/instantiation/common/instantiation.js"; +import { WebviewExtensionDescription } from "../../../../../workbench/contrib/webview/browser/webview.js"; + +import { + IWebviewViewService, + WebviewView, +} from "../../../../../workbench/contrib/webviewView/browser/webviewViewService.js"; +import { WebviewService } from "../../../../../workbench/contrib/webview/browser/webviewService.js"; +import { URI } from "../../../../../base/common/uri.js"; +import { ExtensionIdentifier } from "../../../../../platform/extensions/common/extensions.js"; +import { IEditorGroupsService } from "../../../../../workbench/services/editor/common/editorGroupsService.js"; + +const CREATOR_VIEW_ID = "pearai.roo.creatorOverlayView"; +const CREATOR_OVERLAY_TITLE = "pearai.creatorOverlayView"; + +export class CreatorOverlayPart extends Part { + static readonly ID = "workbench.parts.pearcreatoroverlay"; + + readonly minimumWidth: number = 300; + readonly maximumWidth: number = 800; + readonly minimumHeight: number = 200; + readonly maximumHeight: number = 600; + + private overlayContainer: HTMLElement | undefined; + private webviewView: WebviewView | undefined; + private _webviewService: WebviewService | undefined; + private closeHandler: ((event: MouseEvent) => void) | undefined; + private loadingText: HTMLElement | undefined; + + private state: "loading" | "open" | "closed" = "loading"; + private _isLocked: boolean = false; + private isExtensionReady: boolean = false; + + constructor( + @IThemeService themeService: IThemeService, + @IStorageService storageService: IStorageService, + @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, + @IWebviewViewService + private readonly _webviewViewService: IWebviewViewService, + @IInstantiationService + private readonly _instantiationService: IInstantiationService, + @IEditorGroupsService + private readonly _editorGroupsService: IEditorGroupsService, + ) { + super( + CreatorOverlayPart.ID, + { hasTitle: false }, + themeService, + storageService, + layoutService, + ); + + this._webviewService = + this._instantiationService.createInstance(WebviewService); + + this.initialize(); + } + + isVisible(): boolean { + return this.state === "open"; + } + + private async initialize() { + this.state = "closed"; + + const extensionDescription: WebviewExtensionDescription = { + id: new ExtensionIdentifier(CREATOR_VIEW_ID), + location: URI.parse(""), + }; + + // Create the webview overlay + const webview = this._webviewService!.createWebviewOverlay({ + title: CREATOR_OVERLAY_TITLE, + options: { + enableFindWidget: false, + }, + contentOptions: { + allowScripts: true, + localResourceRoots: [], + }, + extension: extensionDescription, + }); + + // Set initial visibility - important for initial state + webview.container.style.display = "none"; + webview.container.style.opacity = "0"; + webview.container.style.zIndex = "-9999"; + webview.container.style.transition = "opacity 0.3s ease-in"; + webview.container.style.position = "absolute"; + webview.container.setAttribute("id", "creator-overlay-webview"); + + webview.claim(this, getActiveWindow(), undefined); + + // Initialize webviewView + this.webviewView = { + webview, + onDidChangeVisibility: () => { + return { dispose: () => {} }; + }, + onDispose: () => { + return { dispose: () => {} }; + }, + + get title(): string | undefined { + return CREATOR_OVERLAY_TITLE; + }, + set title(value: string | undefined) {}, + + get description(): string | undefined { + return undefined; + }, + set description(value: string | undefined) {}, + + get badge() { + return undefined; + }, + set badge(badge) {}, + + dispose: () => {}, + + show: (preserveFocus) => {}, + }; + + // Connect webviewView to the provider + const source = new CancellationTokenSource(); + try { + console.dir(`RESOLVING CreatorOverlayPart WEBVIEW SERVICE....`); + await this._webviewViewService.resolve( + CREATOR_VIEW_ID, + this.webviewView!, + source.token, + ); + + console.dir(`WEBVIEW CreatorOverlayPart SERVICE RESOLVED!`); + console.dir(this.webviewView); + + // Set up layout if everything is ready + if (this.overlayContainer && this.webviewView) { + this.webviewView.webview.layoutWebviewOverElement( + this.overlayContainer, + ); + } + } catch (error) { + console.error("Failed to resolve creator view:", error); + } + } + + protected override createContentArea(element: HTMLElement): HTMLElement { + // Use a single container for both the overlay and loading state + this.element = element; + this.overlayContainer = $("div.pearcreatoroverlay-part-container-inner"); + + if (this.overlayContainer) { + // Set up overlay container styles + this.overlayContainer.style.position = "fixed"; + this.overlayContainer.style.top = "0"; + this.overlayContainer.style.left = "0"; + this.overlayContainer.style.right = "0"; + this.overlayContainer.style.bottom = "0"; + this.overlayContainer.style.zIndex = "-9999"; // Initially hidden + this.overlayContainer.style.display = "none"; // Start with display none + this.overlayContainer.style.alignItems = "center"; + this.overlayContainer.style.justifyContent = "center"; + this.overlayContainer.style.transition = "opacity 0.3s ease-in-out"; + this.overlayContainer.style.opacity = "0"; + this.overlayContainer.style.width = "100%"; + this.overlayContainer.style.height = "100%"; + this.overlayContainer.onclick = (event) => { + // close the overlay if clicked outside the webview + if (event.target === this.overlayContainer) { + this.close(); + } + }; + + // Create container div for the gradient effect + const container = document.createElement("div"); + container.style.position = "relative"; + + // Create the gradient background element + const gradientBg = document.createElement("div"); + gradientBg.style.position = "absolute"; + gradientBg.style.top = "-20px"; + gradientBg.style.left = "-20px"; + gradientBg.style.right = "-20px"; + gradientBg.style.bottom = "-20px"; + gradientBg.style.background = + "linear-gradient(45deg, #ff00cc, #3333ff, #00ccff, #33cc33)"; + gradientBg.style.borderRadius = "40px"; + gradientBg.style.zIndex = "-1"; + gradientBg.style.filter = "blur(20px)"; + gradientBg.style.opacity = "0.7"; + + // Create loading text + this.loadingText = $("div.loading-text"); + this.loadingText.textContent = "Loading..."; + this.loadingText.style.fontSize = "20px"; + this.loadingText.style.backgroundColor = + "var(--vscode-editor-foreground)"; + this.loadingText.style.color = "var(--vscode-editorGhostText-foreground)"; + this.loadingText.style.color = "#333"; + this.loadingText.style.width = "300px"; + this.loadingText.style.textAlign = "center"; + this.loadingText.style.padding = "12px 20px"; + this.loadingText.style.borderRadius = "30px"; + this.loadingText.style.position = "relative"; // Important for z-index to work + this.loadingText.style.zIndex = "1"; + + // First add the gradient background to the container + container.appendChild(gradientBg); + // Then add the loading text to the container + container.appendChild(this.loadingText); + + // Add the container to the overlay container + this.overlayContainer.appendChild(container); + + // Add the overlay container to the main element + this.element.appendChild(this.overlayContainer); + } + + // Set up webview layout if both are ready + if (this.overlayContainer && this.webviewView) { + this.webviewView.webview.layoutWebviewOverElement(this.overlayContainer); + this.state = "closed"; // Ensure it starts closed + } + + // Make sure the element has its initial z-index set properly + this.element.style.zIndex = "-9999"; + + return element; + } + + override layout( + width: number, + height: number, + top: number, + left: number, + ): void { + super.layout(width, height, top, left); + + if (this.overlayContainer) { + this.overlayContainer.style.width = `${width}px`; + this.overlayContainer.style.height = `${height}px`; + } + + if (this.state === "open" && this.webviewView && this.overlayContainer) { + this.webviewView.webview.layoutWebviewOverElement(this.overlayContainer); + } + } + + private open() { + if (this.state === "open" || !this.overlayContainer || !this.webviewView) { + return; + } + + this.state = "open"; + + // Shows the parent element + this.element.style.zIndex = "20"; + + // Show the overlay container + this.overlayContainer.style.display = "flex"; + this.overlayContainer.style.opacity = "1"; + + // Show loading text if extension is not ready + if (!this.isExtensionReady && this.loadingText) { + this.loadingText.style.display = "block"; + } else if (this.loadingText) { + this.loadingText.style.display = "none"; + } + + // Show the webview container + const container = this.webviewView.webview.container; + if (container) { + container.style.display = "flex"; + container.style.zIndex = "1000"; + container.style.opacity = "1"; + } + + // Remove previous click handler if exists + if (this.closeHandler) { + this.overlayContainer.removeEventListener("click", this.closeHandler); + } + + // Create and store new click handler + this.closeHandler = (event) => { + // Only close if clicking directly on the overlay (not on the content) + if (!this.isLocked && event.target === this.overlayContainer) { + this.close(); + } + }; + + this.overlayContainer.addEventListener("click", this.closeHandler); + + if (this.webviewView && this.overlayContainer) { + this.webviewView.webview.layoutWebviewOverElement(this.overlayContainer); + } + + this.focus(); + } + + private close() { + if ( + this.isLocked || + this.state === "closed" || + !this.overlayContainer || + !this.webviewView + ) { + return; + } + + this.state = "closed"; + + // Hide the parent element completely + this.element.style.zIndex = "-9999"; + + // Fade out overlay container + this.overlayContainer.style.opacity = "0"; + + const container = this.webviewView.webview.container; + if (container) { + // Apply fade-out transition + container.style.opacity = "0"; + } + + // Hide elements after transition completes + setTimeout(() => { + if (this.overlayContainer) { + this.overlayContainer.style.zIndex = "-9999"; + this.overlayContainer.style.display = "none"; + } + + if (container) { + container.style.zIndex = "-9999"; + container.style.display = "none"; + } + + // Focus the active editor + this._editorGroupsService.activeGroup.focus(); + }, 300); // 300ms matches the transition duration + } + + private toggleOpenClose() { + this.state === "open" ? this.close() : this.open(); + } + + focus(): void { + if (this.webviewView) { + this.webviewView.webview.focus(); + } + } + + hideLoadingOverlay(): void { + // Instantly hide the loading visualization without transitions + // This maintains the current open/closed state of the overlay + + if (this.loadingText) { + // Immediately hide the loading text without transition + this.loadingText.style.display = "none"; + this.loadingText.style.opacity = "0"; + } + + // Set the extension as ready so future opens won't show loading + this.isExtensionReady = true; + + // If we're in open state, ensure the webview is visible + if (this.state === "open" && this.webviewView) { + const container = this.webviewView.webview.container; + if (container) { + container.style.zIndex = "1000"; + container.style.display = "flex"; + container.style.opacity = "1"; + } + } + } + + show(): void { + if (this.state === "loading") { + console.warn("Can't open Creator view while loading"); + return; + } + + // Add debug logging + console.log("CREATOR OVERLAY: show() called"); + + this.open(); + } + + hide(): void { + if (this.state === "loading") { + console.warn("Can't close Creator view while loading"); + return; + } + this.close(); + } + + toggle(): void { + if (this.state === "loading") { + console.warn("Can't toggle Creator view while loading"); + return; + } + this.toggleOpenClose(); + } + + public lock(): void { + this._isLocked = true; + } + + public unlock(): void { + this._isLocked = false; + } + + public get isLocked(): boolean { + return this._isLocked; + } + + public hideOverlayLoadingMessage(): void { + if (this.loadingText) { + // Hide the loading text + this.loadingText.style.transition = "opacity 0.3s ease-out"; + this.loadingText.style.opacity = "0"; + + // Only show webview if we're in the "open" state + const container = this.webviewView!.webview.container; + if (this.state === "open" && container) { + // Ensure proper z-index stacking + container.style.zIndex = "1000"; + + container.style.display = "flex"; + container.style.opacity = "0"; + container.style.transition = "opacity 0.3s ease-in"; + + // Slight delay to ensure smooth transition + setTimeout(() => { + container.style.opacity = "1"; + }, 50); + } else if (container) { + container.style.display = "none"; + container.style.opacity = "0"; + } + + // Clean up after animations complete + setTimeout(() => { + if (this.loadingText) { + this.loadingText.style.display = "none"; + this.isExtensionReady = true; + } + }, 300); + } + } + + toJSON(): object { + return { + type: Parts.PEARCREATOROVERLAY_PART, + }; + } +} diff --git a/src/vs/workbench/browser/parts/overlay/creatorView/creatorOverlayService.ts b/src/vs/workbench/browser/parts/overlay/creatorView/creatorOverlayService.ts new file mode 100644 index 0000000000000..4490236a3c778 --- /dev/null +++ b/src/vs/workbench/browser/parts/overlay/creatorView/creatorOverlayService.ts @@ -0,0 +1,229 @@ +/* eslint-disable header/header */ + +import { + registerSingleton, + InstantiationType, +} from "../../../../../platform/instantiation/common/extensions.js"; +import { + Disposable, + IDisposable, +} from "../../../../../base/common/lifecycle.js"; +import { CreatorOverlayPart } from "./creatorOverlayPart.js"; +import { + createDecorator, + IInstantiationService, +} from "../../../../../platform/instantiation/common/instantiation.js"; +import { IEditorService } from "../../../../services/editor/common/editorService.js"; +import { ITerminalService } from "../../../../contrib/terminal/browser/terminal.js"; +import { CommandsRegistry } from "../../../../../platform/commands/common/commands.js"; + +export const ICreatorOverlayService = createDecorator( + "creatorOverlayService", +); + +export interface ICreatorOverlayService extends IDisposable { + readonly _serviceBrand: undefined; + + /** + * Returns the CreatorOverlayPart instance. + */ + readonly creatorOverlayPart: CreatorOverlayPart; + + /** + * Shows the Creator view popup. + */ + show(): void; + + /** + * Hides the Creator view popup. + */ + hide(): void; + + /** + * Hides the loading overlay. + */ + hideLoadingOverlay(): void; + + /** + * Toggles the visibility of the Creator view popup. + */ + toggle(): void; + + /** + * Returns true if the Creator view popup is visible. + */ + isVisible(): boolean; + + /** + * Locks the Creator view popup. + */ + lock(): void; + + /** + * Unlocks the Creator view popup. + */ + unlock(): void; + + /** + * Returns true if the Creator view popup is locked. + */ + isLocked(): boolean; + + /** + * Hides the loading overlay message. + */ + hideOverlayLoadingMessage(): void; +} + +export class CreatorOverlayService + extends Disposable + implements ICreatorOverlayService +{ + declare readonly _serviceBrand: undefined; + + private readonly _creatorOverlayPart: CreatorOverlayPart; + + constructor( + @IInstantiationService + private readonly instantiationService: IInstantiationService, + @IEditorService private readonly _editorService: IEditorService, + @ITerminalService private readonly _terminalService: ITerminalService, + ) { + super(); + this._creatorOverlayPart = + this.instantiationService.createInstance(CreatorOverlayPart); + this.registerListeners(); + this.registerCommands(); + } + + private registerListeners(): void { + this._register( + this._editorService.onDidActiveEditorChange(() => { + // Optional: auto-hide when editor changes + // this.hide(); + }), + ); + + this._register( + this._terminalService.onDidFocusInstance(() => { + // Optional: auto-hide when terminal gets focus + // this.hide(); + }), + ); + } + + private registerCommands(): void { + // Register commands for external use + CommandsRegistry.registerCommand( + "pearai.isCreatorOverlayVisible", + (accessor) => { + const overlayService = accessor.get(ICreatorOverlayService); + return overlayService.isVisible(); + }, + ); + + CommandsRegistry.registerCommand( + "pearai.showCreatorOverlay", + (accessor) => { + const overlayService = accessor.get(ICreatorOverlayService); + overlayService.show(); + }, + ); + + CommandsRegistry.registerCommand( + "pearai.hideCreatorOverlay", + (accessor) => { + const overlayService = accessor.get(ICreatorOverlayService); + overlayService.hide(); + }, + ); + + CommandsRegistry.registerCommand("pearai.toggleCreator", (accessor) => { + const overlayService = accessor.get(ICreatorOverlayService); + overlayService.toggle(); + }); + + CommandsRegistry.registerCommand( + "pearai.lockCreatorOverlay", + (accessor) => { + const overlayService = accessor.get(ICreatorOverlayService); + overlayService.lock(); + }, + ); + + CommandsRegistry.registerCommand( + "pearai.unlockCreatorOverlay", + (accessor) => { + const overlayService = accessor.get(ICreatorOverlayService); + overlayService.unlock(); + }, + ); + + CommandsRegistry.registerCommand( + "pearai.isCreatorOverlayLocked", + (accessor) => { + const overlayService = accessor.get(ICreatorOverlayService); + return overlayService.isLocked(); + }, + ); + + CommandsRegistry.registerCommand( + "pearai.hideCreatorOverlayLoadingMessage", + (accessor) => { + const overlayService = accessor.get(ICreatorOverlayService); + overlayService.hideOverlayLoadingMessage(); + }, + ); + } + + get creatorOverlayPart(): CreatorOverlayPart { + return this._creatorOverlayPart; + } + + show(): void { + this._creatorOverlayPart.show(); + } + + hide(): void { + this._creatorOverlayPart.hide(); + } + + toggle(): void { + this._creatorOverlayPart.toggle(); + } + + lock(): void { + this._creatorOverlayPart.lock(); + } + + unlock(): void { + this._creatorOverlayPart.unlock(); + } + + hideLoadingOverlay(): void { + this._creatorOverlayPart.hideLoadingOverlay(); + } + + isLocked(): boolean { + return this._creatorOverlayPart.isLocked; + } + + hideOverlayLoadingMessage(): void { + this._creatorOverlayPart.hideOverlayLoadingMessage(); + } + + override dispose(): void { + super.dispose(); + this._creatorOverlayPart.dispose(); + } + + isVisible(): boolean { + return this._creatorOverlayPart.isVisible(); + } +} + +registerSingleton( + ICreatorOverlayService, + CreatorOverlayService, + InstantiationType.Eager, +); diff --git a/src/vs/workbench/browser/parts/overlay/pearOverlayActions.ts b/src/vs/workbench/browser/parts/overlay/pearOverlayActions.ts index a0e608c0df3ac..b1cd1ec7346d5 100644 --- a/src/vs/workbench/browser/parts/overlay/pearOverlayActions.ts +++ b/src/vs/workbench/browser/parts/overlay/pearOverlayActions.ts @@ -1,13 +1,20 @@ -import { registerAction2, Action2 } from "../../../../platform/actions/common/actions.js"; +/* eslint-disable header/header */ + +import { + registerAction2, + Action2, +} from "../../../../platform/actions/common/actions.js"; import { ServicesAccessor } from "../../../../platform/instantiation/common/instantiation.js"; import { IPearOverlayService } from "./pearOverlayService.js"; import { KeyCode, KeyMod } from "../../../../base/common/keyCodes.js"; -import { IStorageService } from '../../../../platform/storage/common/storage.js'; +import { IStorageService } from "../../../../platform/storage/common/storage.js"; import { PEARAI_FIRST_LAUNCH_KEY } from "./common.js"; -import { INotificationService, Severity } from '../../../../platform/notification/common/notification.js'; -import { ICommandService } from '../../../../platform/commands/common/commands.js'; - - +import { + INotificationService, + Severity, +} from "../../../../platform/notification/common/notification.js"; +import { ICommandService } from "../../../../platform/commands/common/commands.js"; +// import { PearAIVisibleContext } from "../../../common/contextkeys.js"; export class ClosePearOverlayAction extends Action2 { static readonly ID = "workbench.action.closePearAI"; @@ -20,6 +27,7 @@ export class ClosePearOverlayAction extends Action2 { keybinding: { weight: 200, primary: KeyCode.Escape, + // when: PearAIVisibleContext.negate(), }, }); } @@ -36,7 +44,10 @@ export class TogglePearOverlayAction extends Action2 { constructor() { super({ id: TogglePearOverlayAction.ID, - title: { value: "Toggle PearAI Popup", original: "Toggle PearAI Popup" }, + title: { + value: "Toggle PearAI Popup", + original: "Toggle PearAI Popup", + }, f1: true, keybinding: { weight: 200, @@ -57,7 +68,10 @@ export class MarkPearAIFirstLaunchCompleteAction extends Action2 { constructor() { super({ id: MarkPearAIFirstLaunchCompleteAction.ID, - title: { value: "Mark PearAI First Launch Key Complete", original: "Mark PearAI First Launch Key Complete" }, + title: { + value: "Mark PearAI First Launch Key Complete", + original: "Mark PearAI First Launch Key Complete", + }, f1: true, }); } @@ -92,7 +106,10 @@ export class ResetPearAIFirstLaunchKeyAction extends Action2 { constructor() { super({ id: ResetPearAIFirstLaunchKeyAction.ID, - title: { value: "Reset PearAI First Launch Key", original: "Reset PearAI First Launch Key" }, + title: { + value: "Reset PearAI First Launch Key", + original: "Reset PearAI First Launch Key", + }, f1: true, }); } @@ -100,24 +117,26 @@ export class ResetPearAIFirstLaunchKeyAction extends Action2 { run(accessor: ServicesAccessor): void { const storageService = accessor.get(IStorageService); const notificationService = accessor.get(INotificationService); - const commandService = accessor.get(ICommandService); // Get command service early + const commandService = accessor.get(ICommandService); // Get command service early storageService.store(PEARAI_FIRST_LAUNCH_KEY, false, 0, 0); notificationService.notify({ severity: Severity.Info, - message: 'Successfully reset PearAI first launch Key', + message: "Successfully reset PearAI first launch Key", actions: { - primary: [{ - id: 'reloadWindow', - label: 'Reload Window', - tooltip: 'Reload Window', - class: '', - enabled: true, - run: () => { - commandService.executeCommand('workbench.action.reloadWindow'); - } - }] - } + primary: [ + { + id: "reloadWindow", + label: "Reload Window", + tooltip: "Reload Window", + class: "", + enabled: true, + run: () => { + commandService.executeCommand("workbench.action.reloadWindow"); + }, + }, + ], + }, }); } } @@ -128,7 +147,10 @@ export class IsPearAIFirstLaunchAction extends Action2 { constructor() { super({ id: IsPearAIFirstLaunchAction.ID, - title: { value: "Is PearAI First Launch", original: "Is PearAI First Launch" }, + title: { + value: "Is PearAI First Launch", + original: "Is PearAI First Launch", + }, f1: true, }); } diff --git a/src/vs/workbench/browser/parts/overlay/pearOverlayPart.ts b/src/vs/workbench/browser/parts/overlay/pearOverlayPart.ts index 4701ecc191cfe..270a69cd8d424 100644 --- a/src/vs/workbench/browser/parts/overlay/pearOverlayPart.ts +++ b/src/vs/workbench/browser/parts/overlay/pearOverlayPart.ts @@ -156,11 +156,14 @@ export class PearOverlayPart extends Part { // 3. ask the webviewViewService to connect our webviewView to the webviewViewProvider, PearInventoryPanel const source = new CancellationTokenSource(); // todo add to disposables + console.dir(`RESOLVING pearOverlayPart WEBVIEW SERVICE...`); + await this._webviewViewService.resolve( PEARAI_CHAT_ID, this.webviewView!, source.token, ); + console.dir(`WEBVIEW CreatorOverlayPart SERVICE RESOLVED!`); // if both content and webview are ready, end loading state and open if (this.popupAreaOverlay && this.webviewView) { diff --git a/src/vs/workbench/browser/workbench.ts b/src/vs/workbench/browser/workbench.ts index 344be475e4c29..5b3e1a0893cca 100644 --- a/src/vs/workbench/browser/workbench.ts +++ b/src/vs/workbench/browser/workbench.ts @@ -51,6 +51,7 @@ import { AccessibleViewRegistry } from '../../platform/accessibility/browser/acc import { NotificationAccessibleView } from './parts/notifications/notificationAccessibleView.js'; import { IPearOverlayService } from '../browser/parts/overlay/pearOverlayService.js'; import { IShadowOverlayService } from './parts/overlay/onboardingShadow/shadowOverlayService.js'; +import { ICreatorOverlayService } from './parts/overlay/creatorView/creatorOverlayService.js'; export interface IWorkbenchOptions { @@ -344,7 +345,8 @@ export class Workbench extends Layout { { id: Parts.PANEL_PART, role: 'none', classes: ['panel', 'basepanel', positionToString(this.getPanelPosition())] }, { id: Parts.AUXILIARYBAR_PART, role: 'none', classes: ['auxiliarybar', 'basepanel', this.getSideBarPosition() === Position.LEFT ? 'right' : 'left'] }, { id: Parts.STATUSBAR_PART, role: 'status', classes: ['statusbar'] }, - { id: Parts.PEAROVERLAY_PART, role: 'none', classes: [] } + { id: Parts.PEAROVERLAY_PART, role: 'none', classes: [] }, + { id: Parts.PEARCREATOROVERLAY_PART, role: 'none', classes: [] }, ]) { const partContainer = this.createPart(id, role, classes); @@ -354,6 +356,12 @@ export class Workbench extends Layout { }); } + if(id === Parts.PEARCREATOROVERLAY_PART) { + instantiationService.invokeFunction(accessor => { + accessor.get(ICreatorOverlayService); + }); + } + // instantiate highlighting instantiationService.invokeFunction(accessor => { accessor.get(IShadowOverlayService); diff --git a/src/vs/workbench/common/contextkeys.ts b/src/vs/workbench/common/contextkeys.ts index a5e5dc4e4490d..3621c750d39b4 100644 --- a/src/vs/workbench/common/contextkeys.ts +++ b/src/vs/workbench/common/contextkeys.ts @@ -151,6 +151,7 @@ export const PanelAlignmentContext = new RawContextKey('panelAlignment', export const PanelVisibleContext = new RawContextKey('panelVisible', false, localize('panelVisible', "Whether the panel is visible")); export const PanelMaximizedContext = new RawContextKey('panelMaximized', false, localize('panelMaximized', "Whether the panel is maximized")); export const PearAIVisibleContext = new RawContextKey('pearaiVisible', true, localize('pearaiVisible', "Whether the PearAI overlay is visible")); +export const PearAICreatorVisibleContext = new RawContextKey('pearaiCreatorVisible', true, localize('pearaiCreatorVisible', "Whether the PearAI Creator overlay is visible")); //#endregion diff --git a/src/vs/workbench/services/layout/browser/layoutService.ts b/src/vs/workbench/services/layout/browser/layoutService.ts index 1fd84451372f2..c308443db2b8f 100644 --- a/src/vs/workbench/services/layout/browser/layoutService.ts +++ b/src/vs/workbench/services/layout/browser/layoutService.ts @@ -27,7 +27,8 @@ export const enum Parts { AUXILIARYBAR_PART = 'workbench.parts.auxiliarybar', EDITOR_PART = 'workbench.parts.editor', STATUSBAR_PART = 'workbench.parts.statusbar', - PEAROVERLAY_PART = 'workbench.parts.pearoverlay' + PEAROVERLAY_PART = 'workbench.parts.pearoverlay', + PEARCREATOROVERLAY_PART = 'workbench.parts.pearcreatoroverlay' } export const enum ZenModeSettings {