-
Notifications
You must be signed in to change notification settings - Fork 43
feat: add experimental workspaces view #947
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 1 commit
f4681d7
d963f1d
b898481
84d8919
58075d5
8485cdd
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| # Coder Experimental Workspaces Webview Panel | ||
|
|
||
| This package contains the experimental Workspaces webview panel for the Coder VS Code extension. | ||
|
|
||
| This feature is currently in development and hidden from the Settings UI. | ||
|
EhabY marked this conversation as resolved.
Outdated
|
||
|
|
||
| ## Enabling the Feature | ||
|
|
||
| The workspaces panel is controlled by the `coder.experimental.workspacesPanel` configuration setting. | ||
|
|
||
| **This setting is hidden from the Settings UI** - it can only be enabled via settings.json: | ||
|
|
||
| 1. Open your VS Code settings.json (Cmd/Ctrl + Shift + P → "Preferences: Open User Settings (JSON)") | ||
| 2. Add the following: | ||
|
|
||
| ```json | ||
| { | ||
| "coder.experimental.workspacesPanel": true | ||
| } | ||
| ``` | ||
|
EhabY marked this conversation as resolved.
|
||
|
|
||
| 3. Reload VS Code | ||
|
|
||
| A new activity bar icon labeled **"Coder Remote (New)"** will appear in the activity bar when the setting is enabled. This creates a completely separate panel alongside the existing "Coder Remote" and "Coder Tasks" panels, allowing easy side-by-side comparison during development. | ||
|
|
||
| > [!NOTE] | ||
| > The new view will only appear after you instantiate the Coder context (i.e clicking Tasks or Workspaces). | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think I know why this exists, I do a similar hack for the Coder Tasks where we register a dummy empty view that shows the login button if it's not authenticated but show the webview when it is. Maybe we should do something like this?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Noted — didn't add the login view pattern in this pass since it's more of a follow-up concern, but it's a good idea. Could be a separate PR once we flesh out the panel. 🤖 Generated with Coder Agents |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| { | ||
| "name": "@repo/workspaces", | ||
| "version": "1.0.0", | ||
| "description": "Coder Workspaces webview panel", | ||
| "private": true, | ||
| "type": "module", | ||
| "scripts": { | ||
| "build": "vite build", | ||
| "dev": "vite build --watch", | ||
| "typecheck": "tsc --noEmit" | ||
| }, | ||
| "dependencies": { | ||
| "@repo/shared": "workspace:*", | ||
| "@repo/webview-shared": "workspace:*", | ||
| "@tanstack/react-query": "catalog:", | ||
| "@vscode-elements/react-elements": "catalog:", | ||
| "@vscode/codicons": "catalog:", | ||
| "date-fns": "catalog:", | ||
| "react": "catalog:", | ||
| "react-dom": "catalog:" | ||
| }, | ||
| "devDependencies": { | ||
| "@repo/mocks": "workspace:*", | ||
| "@repo/storybook-utils": "workspace:*", | ||
| "@rolldown/plugin-babel": "catalog:", | ||
| "@types/react": "catalog:", | ||
| "@types/react-dom": "catalog:", | ||
| "@vitejs/plugin-react": "catalog:", | ||
| "babel-plugin-react-compiler": "catalog:", | ||
| "typescript": "catalog:", | ||
| "vite": "catalog:" | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| export default function App() { | ||
| return <div>TODO</div>; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| declare module "*.css"; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| /* TODO */ |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| import { ErrorBoundary } from "@repo/webview-shared/react"; | ||
| import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; | ||
| import { StrictMode } from "react"; | ||
| import { createRoot } from "react-dom/client"; | ||
|
|
||
| import App from "./App"; | ||
| import "./index.css"; | ||
|
|
||
| const queryClient = new QueryClient(); | ||
|
|
||
| const root = document.getElementById("root"); | ||
| if (!root) { | ||
| throw new Error( | ||
| "Failed to find root element. The webview HTML must contain an element with id='root'.", | ||
| ); | ||
| } | ||
|
|
||
| createRoot(root).render( | ||
| <StrictMode> | ||
| <QueryClientProvider client={queryClient}> | ||
| <ErrorBoundary> | ||
| <App /> | ||
| </ErrorBoundary> | ||
| </QueryClientProvider> | ||
| </StrictMode>, | ||
| ); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| import "./src/index.css"; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| { | ||
| "extends": "../tsconfig.packages.json", | ||
| "compilerOptions": { | ||
| "paths": { | ||
| "@repo/shared": ["../shared/src"], | ||
| "@repo/webview-shared": ["../webview-shared/src"] | ||
| } | ||
| }, | ||
| "include": ["src", "storybook.preview.ts"] | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| import { createReactWebviewConfig } from "../webview-shared/createWebviewConfig"; | ||
|
|
||
| export default createReactWebviewConfig("workspaces", __dirname); |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,100 @@ | ||
| import * as vscode from "vscode"; | ||
|
|
||
| import { type CoderApi } from "../../api/coderApi"; | ||
| import { type Logger } from "../../logging/logger"; | ||
|
EhabY marked this conversation as resolved.
Outdated
|
||
| import { | ||
| dispatchCommand, | ||
| dispatchRequest, | ||
| isIpcCommand, | ||
| isIpcRequest, | ||
| notifyWebview, | ||
| } from "../dispatch"; | ||
| import { getWebviewHtml } from "../html"; | ||
|
|
||
| export class ExperimentalWorkspacesPanelProvider | ||
| implements vscode.WebviewViewProvider, vscode.Disposable | ||
| { | ||
| public static readonly viewType = "coder.experimental.workspacesPanel"; | ||
|
EhabY marked this conversation as resolved.
Outdated
|
||
|
|
||
| private view?: vscode.WebviewView; | ||
| private disposables: vscode.Disposable[] = []; | ||
|
|
||
| // Empty handlers for now - will be populated as we build out the API | ||
| private readonly requestHandlers = {}; | ||
| private readonly commandHandlers = {}; | ||
|
jakehwll marked this conversation as resolved.
Outdated
|
||
|
|
||
| constructor( | ||
| private readonly extensionUri: vscode.Uri, | ||
| private readonly client: CoderApi, | ||
|
EhabY marked this conversation as resolved.
Outdated
|
||
| private readonly logger: Logger, | ||
| ) {} | ||
|
|
||
| public refresh(): void { | ||
| // TODO: Implement refresh logic | ||
| this.logger.debug("Workspaces panel refresh requested"); | ||
| } | ||
|
|
||
| resolveWebviewView( | ||
| webviewView: vscode.WebviewView, | ||
| _context: vscode.WebviewViewResolveContext, | ||
| _token: vscode.CancellationToken, | ||
| ): void { | ||
| this.view = webviewView; | ||
|
|
||
| webviewView.webview.options = { | ||
| enableScripts: true, | ||
| localResourceRoots: [ | ||
| vscode.Uri.joinPath(this.extensionUri, "dist", "webviews"), | ||
|
EhabY marked this conversation as resolved.
Outdated
|
||
| ], | ||
| }; | ||
|
|
||
| for (const d of this.disposables) { | ||
| d.dispose(); | ||
| } | ||
| this.disposables = []; | ||
|
|
||
| this.disposables.push( | ||
| webviewView.webview.onDidReceiveMessage((message: unknown) => { | ||
| this.handleMessage(message).catch((err: unknown) => { | ||
| this.logger.error("Unhandled error in message handler", err); | ||
| }); | ||
| }), | ||
| ); | ||
|
|
||
| webviewView.webview.html = getWebviewHtml( | ||
| webviewView.webview, | ||
| this.extensionUri, | ||
| "workspaces", | ||
| "Coder Workspaces", | ||
| ); | ||
|
|
||
| webviewView.onDidDispose(() => { | ||
| for (const d of this.disposables) { | ||
| d.dispose(); | ||
| } | ||
| this.disposables = []; | ||
|
EhabY marked this conversation as resolved.
Outdated
|
||
| }); | ||
| } | ||
|
|
||
| private async handleMessage(message: unknown): Promise<void> { | ||
| const showErrorToUser = () => false; | ||
|
EhabY marked this conversation as resolved.
Outdated
|
||
| if (isIpcRequest(message)) { | ||
| await dispatchRequest(message, this.requestHandlers, this.view?.webview, { | ||
| logger: this.logger, | ||
| showErrorToUser, | ||
| }); | ||
| } else if (isIpcCommand(message)) { | ||
| await dispatchCommand(message, this.commandHandlers, { | ||
| logger: this.logger, | ||
| showErrorToUser, | ||
| }); | ||
|
EhabY marked this conversation as resolved.
|
||
| } | ||
| } | ||
|
|
||
| dispose(): void { | ||
| for (const d of this.disposables) { | ||
| d.dispose(); | ||
| } | ||
| this.disposables = []; | ||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.