From 66bd03a58f128d7393ab66bd6496b22bf535688d Mon Sep 17 00:00:00 2001 From: Rodrigo Maia Date: Tue, 21 Mar 2023 17:34:47 -0300 Subject: [PATCH 01/10] WIP: adding tree view to support crud workspaces --- package.json | 29 ++++++++++++++++++++++------- src/extension.ts | 3 +++ src/workspacesProvider.ts | 32 ++++++++++++++++++++++++++++++++ 3 files changed, 57 insertions(+), 7 deletions(-) create mode 100644 src/workspacesProvider.ts diff --git a/package.json b/package.json index 1553bdff..35226be2 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,7 @@ "coder": [ { "id": "coderRemote", - "name": "", + "name": "Workspaces", "visibility": "visible", "icon": "media/logo.svg", "contextualTitle": "Coder Remote" @@ -54,11 +54,6 @@ "view": "coderRemote", "contents": "Coder is a platform that provisions remote development environments. \n[Login](command:coder.login)", "when": "!coder.authenticated && coder.loaded" - }, - { - "view": "coderRemote", - "contents": "You're logged in! \n[Open Workspace](command:coder.open)", - "when": "coder.authenticated && coder.loaded" } ], "commands": [ @@ -79,7 +74,27 @@ "title": "Coder: Update Workspace", "when": "coder.workspace.updatable" } - ] + ], + "menus": { + "view/title": [ + { + "command": "coder.logout", + "when": "coder.authenticated" + }, + { + "command": "coder.createWorkspace", + "when": "coder.authenticated", + "group": "navigation" + } + ], + "view/item/context": [ + { + "command": "coder.open", + "when": "coder.authenticated", + "group": "inline" + } + ] + } }, "scripts": { "vscode:prepublish": "yarn package", diff --git a/src/extension.ts b/src/extension.ts index e5e73cd7..b7ef102e 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -6,12 +6,15 @@ import * as vscode from "vscode" import { Commands } from "./commands" import { Remote } from "./remote" import { Storage } from "./storage" +import { WorkspaceProvider } from "./workspacesProvider" export async function activate(ctx: vscode.ExtensionContext): Promise { const output = vscode.window.createOutputChannel("Coder") const storage = new Storage(output, ctx.globalState, ctx.secrets, ctx.globalStorageUri, ctx.logUri) await storage.init() + vscode.window.registerTreeDataProvider("coderRemote", new WorkspaceProvider()) + getAuthenticatedUser() .then(() => { vscode.commands.executeCommand("setContext", "coder.authenticated", true) diff --git a/src/workspacesProvider.ts b/src/workspacesProvider.ts new file mode 100644 index 00000000..855d1ce2 --- /dev/null +++ b/src/workspacesProvider.ts @@ -0,0 +1,32 @@ +import { getWorkspaces } from "coder/site/src/api/api" +import * as path from "path" +import * as vscode from "vscode" + +export class WorkspaceProvider implements vscode.TreeDataProvider { + getTreeItem(element: Dependency): vscode.TreeItem { + return element + } + + getChildren(): Thenable { + return getWorkspaces({ + q: "owner:me", + }).then((workspaces) => { + const exampleWorkspaces = [{ name: "example1" }, { name: "example2" }] + return [...workspaces.workspaces, ...exampleWorkspaces].map( + (workspace) => new Dependency(workspace.name, vscode.TreeItemCollapsibleState.None), + ) + }) + } +} + +class Dependency extends vscode.TreeItem { + constructor(public readonly label: string, public readonly collapsibleState: vscode.TreeItemCollapsibleState) { + super(label, collapsibleState) + this.tooltip = `${this.label}` + } + + iconPath = { + light: path.join(__filename, "..", "..", "media", "logo.svg"), + dark: path.join(__filename, "..", "..", "media", "logo.svg"), + } +} From 4612bea6af37e4cb3b7b3efe4f8a2aecc439bdfb Mon Sep 17 00:00:00 2001 From: Rodrigo Maia Date: Fri, 24 Mar 2023 11:54:50 -0300 Subject: [PATCH 02/10] ::coffee:: wip --- .gitignore | 3 ++- package.json | 21 +++++++++++++++----- src/commands.ts | 5 +++++ src/extension.ts | 1 + src/workspacesProvider.ts | 42 ++++++++++++++++++++++++++++----------- 5 files changed, 54 insertions(+), 18 deletions(-) diff --git a/.gitignore b/.gitignore index d535c22b..75f80c5f 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,5 @@ /.vscode-test/ /.nyc_output/ /coverage/ -*.vsix \ No newline at end of file +*.vsix +yarn-error.log diff --git a/package.json b/package.json index 35226be2..ecdddfed 100644 --- a/package.json +++ b/package.json @@ -44,8 +44,7 @@ "id": "coderRemote", "name": "Workspaces", "visibility": "visible", - "icon": "media/logo.svg", - "contextualTitle": "Coder Remote" + "icon": "media/logo.svg" } ] }, @@ -69,6 +68,14 @@ "command": "coder.open", "title": "Coder: Open Workspace" }, + { + "command": "coder.createWorkspace", + "title": "Create Workspace" + }, + { + "command": "coder.removeWorkspace", + "title": "Remove Workspace" + }, { "command": "coder.workspace.update", "title": "Coder: Update Workspace", @@ -79,19 +86,23 @@ "view/title": [ { "command": "coder.logout", - "when": "coder.authenticated" + "when": "coder.authenticated && view == coderRemote" }, { "command": "coder.createWorkspace", - "when": "coder.authenticated", + "when": "coder.authenticated && view == coderRemote", "group": "navigation" } ], "view/item/context": [ { "command": "coder.open", - "when": "coder.authenticated", + "when": "coder.authenticated && viewItem.label !== 'My Workspaces' && view == coderRemote", "group": "inline" + }, + { + "command": "coder.removeWorkspace", + "when": "coder.authenticated" } ] } diff --git a/src/commands.ts b/src/commands.ts index ccc2c653..f8bfde1f 100644 --- a/src/commands.ts +++ b/src/commands.ts @@ -108,6 +108,11 @@ export class Commands { }) } + public async createWorkspace(): Promise { + const uri = this.storage.getURL() + "/templates" + await vscode.commands.executeCommand("vscode.open", uri) + } + public async open(...args: string[]): Promise { let workspaceOwner: string let workspaceName: string diff --git a/src/extension.ts b/src/extension.ts index b7ef102e..56b9e906 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -79,6 +79,7 @@ export async function activate(ctx: vscode.ExtensionContext): Promise { vscode.commands.registerCommand("coder.logout", commands.logout.bind(commands)) vscode.commands.registerCommand("coder.open", commands.open.bind(commands)) vscode.commands.registerCommand("coder.workspace.update", commands.updateWorkspace.bind(commands)) + vscode.commands.registerCommand("coder.createWorkspace", commands.createWorkspace.bind(commands)) // Since the "onResolveRemoteAuthority:ssh-remote" activation event exists // in package.json we're able to perform actions before the authority is diff --git a/src/workspacesProvider.ts b/src/workspacesProvider.ts index 855d1ce2..43d6fc76 100644 --- a/src/workspacesProvider.ts +++ b/src/workspacesProvider.ts @@ -2,24 +2,42 @@ import { getWorkspaces } from "coder/site/src/api/api" import * as path from "path" import * as vscode from "vscode" -export class WorkspaceProvider implements vscode.TreeDataProvider { - getTreeItem(element: Dependency): vscode.TreeItem { +export class WorkspaceProvider implements vscode.TreeDataProvider { + getTreeItem(element: TreeItem): vscode.TreeItem { return element } - getChildren(): Thenable { - return getWorkspaces({ - q: "owner:me", - }).then((workspaces) => { - const exampleWorkspaces = [{ name: "example1" }, { name: "example2" }] - return [...workspaces.workspaces, ...exampleWorkspaces].map( - (workspace) => new Dependency(workspace.name, vscode.TreeItemCollapsibleState.None), - ) - }) + getChildren(element?: TreeItem): Thenable { + if (!element) { + return Promise.resolve([ + new TreeItem("My Workspaces", vscode.TreeItemCollapsibleState.Expanded), + new TreeItem("All Workspaces", vscode.TreeItemCollapsibleState.None), + ]) + } + if (element.label === "My Workspaces") { + return getWorkspaces({ + q: "owner:me", + }).then((workspaces) => { + return workspaces.workspaces.map( + (workspace) => new TreeItem(workspace.name, vscode.TreeItemCollapsibleState.None), + ) + }) + } + if (element.label === "All Workspaces") { + return getWorkspaces({ + q: "owner:all", + }).then((workspaces) => { + const exampleWorkspaces = [{ name: "example1" }, { name: "example2" }] + return [...workspaces.workspaces, ...exampleWorkspaces].map( + (workspace) => new TreeItem(workspace.name, vscode.TreeItemCollapsibleState.None), + ) + }) + } + return Promise.resolve([]) } } -class Dependency extends vscode.TreeItem { +class TreeItem extends vscode.TreeItem { constructor(public readonly label: string, public readonly collapsibleState: vscode.TreeItemCollapsibleState) { super(label, collapsibleState) this.tooltip = `${this.label}` From 255d6b6b76b9e4b03b4db3bcf16a15df5ebeede8 Mon Sep 17 00:00:00 2001 From: Rodrigo Maia Date: Tue, 28 Mar 2023 11:17:09 -0300 Subject: [PATCH 03/10] simplify worktreeprovider --- package.json | 22 ++++++++++++---------- src/extension.ts | 3 ++- src/workspacesProvider.ts | 34 +++++++--------------------------- 3 files changed, 21 insertions(+), 38 deletions(-) diff --git a/package.json b/package.json index ecdddfed..7949bbc6 100644 --- a/package.json +++ b/package.json @@ -41,8 +41,14 @@ "views": { "coder": [ { - "id": "coderRemote", - "name": "Workspaces", + "id": "myWorkspaces", + "name": "My Workspaces", + "visibility": "visible", + "icon": "media/logo.svg" + }, + { + "id": "allWorkspaces", + "name": "All Workspaces", "visibility": "visible", "icon": "media/logo.svg" } @@ -50,7 +56,7 @@ }, "viewsWelcome": [ { - "view": "coderRemote", + "view": "myWorkspaces", "contents": "Coder is a platform that provisions remote development environments. \n[Login](command:coder.login)", "when": "!coder.authenticated && coder.loaded" } @@ -86,23 +92,19 @@ "view/title": [ { "command": "coder.logout", - "when": "coder.authenticated && view == coderRemote" + "when": "coder.authenticated && view == myWorkspaces" }, { "command": "coder.createWorkspace", - "when": "coder.authenticated && view == coderRemote", + "when": "coder.authenticated && view == myWorkspaces", "group": "navigation" } ], "view/item/context": [ { "command": "coder.open", - "when": "coder.authenticated && viewItem.label !== 'My Workspaces' && view == coderRemote", + "when": "coder.authenticated && view == myWorkspaces || coder.authenticated && view == allWorkspaces", "group": "inline" - }, - { - "command": "coder.removeWorkspace", - "when": "coder.authenticated" } ] } diff --git a/src/extension.ts b/src/extension.ts index 56b9e906..1d1a6caf 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -13,7 +13,8 @@ export async function activate(ctx: vscode.ExtensionContext): Promise { const storage = new Storage(output, ctx.globalState, ctx.secrets, ctx.globalStorageUri, ctx.logUri) await storage.init() - vscode.window.registerTreeDataProvider("coderRemote", new WorkspaceProvider()) + vscode.window.registerTreeDataProvider("myWorkspaces", new WorkspaceProvider("owner:me")) + vscode.window.registerTreeDataProvider("allWorkspaces", new WorkspaceProvider()) getAuthenticatedUser() .then(() => { diff --git a/src/workspacesProvider.ts b/src/workspacesProvider.ts index 43d6fc76..61b4e478 100644 --- a/src/workspacesProvider.ts +++ b/src/workspacesProvider.ts @@ -3,37 +3,17 @@ import * as path from "path" import * as vscode from "vscode" export class WorkspaceProvider implements vscode.TreeDataProvider { + constructor(private readonly getWorkspacesQuery?: string) {} getTreeItem(element: TreeItem): vscode.TreeItem { return element } - getChildren(element?: TreeItem): Thenable { - if (!element) { - return Promise.resolve([ - new TreeItem("My Workspaces", vscode.TreeItemCollapsibleState.Expanded), - new TreeItem("All Workspaces", vscode.TreeItemCollapsibleState.None), - ]) - } - if (element.label === "My Workspaces") { - return getWorkspaces({ - q: "owner:me", - }).then((workspaces) => { - return workspaces.workspaces.map( - (workspace) => new TreeItem(workspace.name, vscode.TreeItemCollapsibleState.None), - ) - }) - } - if (element.label === "All Workspaces") { - return getWorkspaces({ - q: "owner:all", - }).then((workspaces) => { - const exampleWorkspaces = [{ name: "example1" }, { name: "example2" }] - return [...workspaces.workspaces, ...exampleWorkspaces].map( - (workspace) => new TreeItem(workspace.name, vscode.TreeItemCollapsibleState.None), - ) - }) - } - return Promise.resolve([]) + getChildren(): Thenable { + return getWorkspaces({ q: this.getWorkspacesQuery }).then((workspaces) => { + return workspaces.workspaces.map( + (workspace) => new TreeItem(workspace.name, vscode.TreeItemCollapsibleState.None), + ) + }) } } From e9813d8bb1c2c7252aebbb7030f6de89ccc50796 Mon Sep 17 00:00:00 2001 From: Rodrigo Maia Date: Tue, 28 Mar 2023 15:17:23 -0300 Subject: [PATCH 04/10] implemente open workspace command --- package.json | 8 ++++++-- src/api-helper.ts | 16 +++++++++++++++ src/commands.ts | 31 +++++++++++++++++----------- src/extension.ts | 7 ++++--- src/workspacesProvider.ts | 43 +++++++++++++++++++++++++++++---------- 5 files changed, 77 insertions(+), 28 deletions(-) create mode 100644 src/api-helper.ts diff --git a/package.json b/package.json index 7949bbc6..5f672a8e 100644 --- a/package.json +++ b/package.json @@ -79,8 +79,8 @@ "title": "Create Workspace" }, { - "command": "coder.removeWorkspace", - "title": "Remove Workspace" + "command": "coder.navigateToWorkspace", + "title": "Open Workspace in Browser" }, { "command": "coder.workspace.update", @@ -105,6 +105,10 @@ "command": "coder.open", "when": "coder.authenticated && view == myWorkspaces || coder.authenticated && view == allWorkspaces", "group": "inline" + }, + { + "command": "coder.navigateToWorkspace", + "when": "coder.authenticated && view == myWorkspaces || coder.authenticated && view == allWorkspaces" } ] } diff --git a/src/api-helper.ts b/src/api-helper.ts new file mode 100644 index 00000000..75c0af83 --- /dev/null +++ b/src/api-helper.ts @@ -0,0 +1,16 @@ +import { Workspace, WorkspaceAgent } from "coder/site/src/api/typesGenerated" + +export function extractAgentsAndFolderPath( + workspace: Workspace, +): [agents: WorkspaceAgent[], folderPath: string | undefined] { + // TODO: multiple agent support + const agents = workspace.latest_build.resources.reduce((acc, resource) => { + return acc.concat(resource.agents || []) + }, [] as WorkspaceAgent[]) + + let folderPath = undefined + if (agents.length === 1) { + folderPath = agents[0].expanded_directory + } + return [agents, folderPath] +} diff --git a/src/commands.ts b/src/commands.ts index f8bfde1f..4250fb2c 100644 --- a/src/commands.ts +++ b/src/commands.ts @@ -2,8 +2,10 @@ import axios from "axios" import { getAuthenticatedUser, getWorkspaces, updateWorkspaceVersion } from "coder/site/src/api/api" import { Workspace, WorkspaceAgent } from "coder/site/src/api/typesGenerated" import * as vscode from "vscode" +import { extractAgentsAndFolderPath } from "./api-helper" import { Remote } from "./remote" import { Storage } from "./storage" +import { WorkspaceTreeItem } from "./workspacesProvider" export class Commands { public constructor(private readonly vscodeProposed: typeof vscode, private readonly storage: Storage) {} @@ -113,7 +115,12 @@ export class Commands { await vscode.commands.executeCommand("vscode.open", uri) } - public async open(...args: string[]): Promise { + public async navigateToWorkspace(workspace: WorkspaceTreeItem): Promise { + const uri = this.storage.getURL() + `/@${workspace.workspaceOwner}/${workspace.workspaceName}` + await vscode.commands.executeCommand("vscode.open", uri) + } + + public async open(...args: unknown[]): Promise { let workspaceOwner: string let workspaceName: string let folderPath: string | undefined @@ -170,19 +177,19 @@ export class Commands { workspaceOwner = workspace.owner_name workspaceName = workspace.name - // TODO: multiple agent support - const agents = workspace.latest_build.resources.reduce((acc, resource) => { - return acc.concat(resource.agents || []) - }, [] as WorkspaceAgent[]) - - if (agents.length === 1) { - folderPath = agents[0].expanded_directory - } + const [, folderPathExtracted] = extractAgentsAndFolderPath(workspace) + folderPath = folderPathExtracted + } else if (args.length === 2) { + // opening a workspace from the sidebar + const workspaceTreeItem = args[0] as WorkspaceTreeItem + workspaceOwner = workspaceTreeItem.workspaceOwner + workspaceName = workspaceTreeItem.workspaceName + folderPath = workspaceTreeItem.workspaceFolderPath } else { - workspaceOwner = args[0] - workspaceName = args[1] + workspaceOwner = args[0] as string + workspaceName = args[1] as string // workspaceAgent is reserved for args[2], but multiple agents aren't supported yet. - folderPath = args[3] + folderPath = args[3] as string | undefined } // A workspace can have multiple agents, but that's handled diff --git a/src/extension.ts b/src/extension.ts index 1d1a6caf..082033b5 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -6,15 +6,15 @@ import * as vscode from "vscode" import { Commands } from "./commands" import { Remote } from "./remote" import { Storage } from "./storage" -import { WorkspaceProvider } from "./workspacesProvider" +import { WorkspaceQuery, WorkspaceProvider } from "./workspacesProvider" export async function activate(ctx: vscode.ExtensionContext): Promise { const output = vscode.window.createOutputChannel("Coder") const storage = new Storage(output, ctx.globalState, ctx.secrets, ctx.globalStorageUri, ctx.logUri) await storage.init() - vscode.window.registerTreeDataProvider("myWorkspaces", new WorkspaceProvider("owner:me")) - vscode.window.registerTreeDataProvider("allWorkspaces", new WorkspaceProvider()) + vscode.window.registerTreeDataProvider("myWorkspaces", new WorkspaceProvider(WorkspaceQuery.Mine)) + vscode.window.registerTreeDataProvider("allWorkspaces", new WorkspaceProvider(WorkspaceQuery.All)) getAuthenticatedUser() .then(() => { @@ -81,6 +81,7 @@ export async function activate(ctx: vscode.ExtensionContext): Promise { vscode.commands.registerCommand("coder.open", commands.open.bind(commands)) vscode.commands.registerCommand("coder.workspace.update", commands.updateWorkspace.bind(commands)) vscode.commands.registerCommand("coder.createWorkspace", commands.createWorkspace.bind(commands)) + vscode.commands.registerCommand("coder.navigateToWorkspace", commands.navigateToWorkspace.bind(commands)) // Since the "onResolveRemoteAuthority:ssh-remote" activation event exists // in package.json we're able to perform actions before the authority is diff --git a/src/workspacesProvider.ts b/src/workspacesProvider.ts index 61b4e478..a34d6803 100644 --- a/src/workspacesProvider.ts +++ b/src/workspacesProvider.ts @@ -1,26 +1,47 @@ import { getWorkspaces } from "coder/site/src/api/api" import * as path from "path" import * as vscode from "vscode" +import { extractAgentsAndFolderPath } from "./api-helper" -export class WorkspaceProvider implements vscode.TreeDataProvider { - constructor(private readonly getWorkspacesQuery?: string) {} - getTreeItem(element: TreeItem): vscode.TreeItem { +export enum WorkspaceQuery { + Mine = "owner:me", + All = "", +} + +export class WorkspaceProvider implements vscode.TreeDataProvider { + constructor(private readonly getWorkspacesQuery: WorkspaceQuery) {} + + getTreeItem(element: WorkspaceTreeItem): vscode.TreeItem { return element } - getChildren(): Thenable { + getChildren(): Thenable { return getWorkspaces({ q: this.getWorkspacesQuery }).then((workspaces) => { - return workspaces.workspaces.map( - (workspace) => new TreeItem(workspace.name, vscode.TreeItemCollapsibleState.None), - ) + return workspaces.workspaces.map((workspace) => { + const status = + workspace.latest_build.status.substring(0, 1).toUpperCase() + workspace.latest_build.status.substring(1) + + const label = + this.getWorkspacesQuery === WorkspaceQuery.All + ? `${workspace.owner_name} / ${workspace.name}` + : workspace.name + const detail = `Template: ${workspace.template_display_name || workspace.template_name} • Status: ${status}` + const [, folderPath] = extractAgentsAndFolderPath(workspace) + return new WorkspaceTreeItem(label, detail, workspace.owner_name, workspace.name, folderPath) + }) }) } } -class TreeItem extends vscode.TreeItem { - constructor(public readonly label: string, public readonly collapsibleState: vscode.TreeItemCollapsibleState) { - super(label, collapsibleState) - this.tooltip = `${this.label}` +export class WorkspaceTreeItem extends vscode.TreeItem { + constructor( + public readonly label: string, + public readonly tooltip: string, + public readonly workspaceOwner: string, + public readonly workspaceName: string, + public readonly workspaceFolderPath: string | undefined, + ) { + super(label, vscode.TreeItemCollapsibleState.None) } iconPath = { From 40241893419679999b3fcc9b57d9ce6f80d36750 Mon Sep 17 00:00:00 2001 From: Rodrigo Maia Date: Tue, 28 Mar 2023 15:32:14 -0300 Subject: [PATCH 05/10] lint --- src/commands.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/commands.ts b/src/commands.ts index 4250fb2c..8d973887 100644 --- a/src/commands.ts +++ b/src/commands.ts @@ -1,6 +1,6 @@ import axios from "axios" import { getAuthenticatedUser, getWorkspaces, updateWorkspaceVersion } from "coder/site/src/api/api" -import { Workspace, WorkspaceAgent } from "coder/site/src/api/typesGenerated" +import { Workspace } from "coder/site/src/api/typesGenerated" import * as vscode from "vscode" import { extractAgentsAndFolderPath } from "./api-helper" import { Remote } from "./remote" From d31ba11ef9febd0a4e9b6fcb04f0bd3f76a26458 Mon Sep 17 00:00:00 2001 From: Rodrigo Maia Date: Tue, 28 Mar 2023 16:06:24 -0300 Subject: [PATCH 06/10] copy changes --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 5f672a8e..23368733 100644 --- a/package.json +++ b/package.json @@ -80,7 +80,7 @@ }, { "command": "coder.navigateToWorkspace", - "title": "Open Workspace in Browser" + "title": "Navigate to Workspace Page" }, { "command": "coder.workspace.update", From 2be39ce241f3ec36f56e0c40a3b7d50b65e8a2a0 Mon Sep 17 00:00:00 2001 From: Rodrigo Maia Date: Tue, 28 Mar 2023 16:58:07 -0300 Subject: [PATCH 07/10] login menu option --- package.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/package.json b/package.json index 23368733..f95a5003 100644 --- a/package.json +++ b/package.json @@ -94,6 +94,10 @@ "command": "coder.logout", "when": "coder.authenticated && view == myWorkspaces" }, + { + "command": "coder.login", + "when": "!coder.authenticated && view == myWorkspaces" + }, { "command": "coder.createWorkspace", "when": "coder.authenticated && view == myWorkspaces", From ddfe7f84b91a326f35cc46787fdd85e9ea0098ce Mon Sep 17 00:00:00 2001 From: Rodrigo Maia Date: Tue, 28 Mar 2023 17:08:00 -0300 Subject: [PATCH 08/10] add icons --- package.json | 29 ++++++++++++++++++++++++----- src/extension.ts | 11 +++++++++-- src/workspacesProvider.ts | 9 +++++++++ 3 files changed, 42 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index f95a5003..9fb3e276 100644 --- a/package.json +++ b/package.json @@ -68,24 +68,37 @@ }, { "command": "coder.logout", - "title": "Coder: Logout" + "title": "Coder: Logout", + "when": "coder.authenticated", + "icon": "$(sign-out)" }, { "command": "coder.open", - "title": "Coder: Open Workspace" + "title": "Coder: Open Workspace", + "icon": "$(play)" }, { "command": "coder.createWorkspace", - "title": "Create Workspace" + "title": "Create Workspace", + "when": "coder.authenticated", + "icon": "$(add)" }, { "command": "coder.navigateToWorkspace", - "title": "Navigate to Workspace Page" + "title": "Navigate to Workspace Page", + "when": "coder.authenticated", + "icon": "$(link-external)" }, { "command": "coder.workspace.update", "title": "Coder: Update Workspace", "when": "coder.workspace.updatable" + }, + { + "command": "coder.refreshWorkspaces", + "title": "Coder: Refresh Workspace", + "icon": "$(refresh)", + "when": "coder.authenticated" } ], "menus": { @@ -102,6 +115,11 @@ "command": "coder.createWorkspace", "when": "coder.authenticated && view == myWorkspaces", "group": "navigation" + }, + { + "command": "coder.refreshWorkspaces", + "when": "coder.authenticated && view == myWorkspaces", + "group": "navigation" } ], "view/item/context": [ @@ -112,7 +130,8 @@ }, { "command": "coder.navigateToWorkspace", - "when": "coder.authenticated && view == myWorkspaces || coder.authenticated && view == allWorkspaces" + "when": "coder.authenticated && view == myWorkspaces || coder.authenticated && view == allWorkspaces", + "group": "inline" } ] } diff --git a/src/extension.ts b/src/extension.ts index 082033b5..ca894d09 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -13,8 +13,11 @@ export async function activate(ctx: vscode.ExtensionContext): Promise { const storage = new Storage(output, ctx.globalState, ctx.secrets, ctx.globalStorageUri, ctx.logUri) await storage.init() - vscode.window.registerTreeDataProvider("myWorkspaces", new WorkspaceProvider(WorkspaceQuery.Mine)) - vscode.window.registerTreeDataProvider("allWorkspaces", new WorkspaceProvider(WorkspaceQuery.All)) + const myWorkspacesProvider = new WorkspaceProvider(WorkspaceQuery.Mine) + const allWorkspacesProvider = new WorkspaceProvider(WorkspaceQuery.All) + + vscode.window.registerTreeDataProvider("myWorkspaces", myWorkspacesProvider) + vscode.window.registerTreeDataProvider("allWorkspaces", allWorkspacesProvider) getAuthenticatedUser() .then(() => { @@ -82,6 +85,10 @@ export async function activate(ctx: vscode.ExtensionContext): Promise { vscode.commands.registerCommand("coder.workspace.update", commands.updateWorkspace.bind(commands)) vscode.commands.registerCommand("coder.createWorkspace", commands.createWorkspace.bind(commands)) vscode.commands.registerCommand("coder.navigateToWorkspace", commands.navigateToWorkspace.bind(commands)) + vscode.commands.registerCommand("coder.refreshWorkspaces", () => { + myWorkspacesProvider.refresh() + allWorkspacesProvider.refresh() + }) // Since the "onResolveRemoteAuthority:ssh-remote" activation event exists // in package.json we're able to perform actions before the authority is diff --git a/src/workspacesProvider.ts b/src/workspacesProvider.ts index a34d6803..f09b29e4 100644 --- a/src/workspacesProvider.ts +++ b/src/workspacesProvider.ts @@ -11,6 +11,15 @@ export enum WorkspaceQuery { export class WorkspaceProvider implements vscode.TreeDataProvider { constructor(private readonly getWorkspacesQuery: WorkspaceQuery) {} + private _onDidChangeTreeData: vscode.EventEmitter = + new vscode.EventEmitter() + readonly onDidChangeTreeData: vscode.Event = + this._onDidChangeTreeData.event + + refresh(): void { + this._onDidChangeTreeData.fire() + } + getTreeItem(element: WorkspaceTreeItem): vscode.TreeItem { return element } From 035af4aa47803cff429927e7b4ef49bb41c5f2c4 Mon Sep 17 00:00:00 2001 From: Rodrigo Maia Date: Tue, 28 Mar 2023 17:31:28 -0300 Subject: [PATCH 09/10] add workspace settings button --- package.json | 11 +++++++++++ src/commands.ts | 26 +++++++++++++++++++++++--- src/extension.ts | 4 ++++ 3 files changed, 38 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 9fb3e276..2b3f0132 100644 --- a/package.json +++ b/package.json @@ -89,6 +89,12 @@ "when": "coder.authenticated", "icon": "$(link-external)" }, + { + "command": "coder.navigateToWorkspaceSettings", + "title": "Edit Workspace Settings", + "when": "coder.authenticated", + "icon": "$(settings-gear)" + }, { "command": "coder.workspace.update", "title": "Coder: Update Workspace", @@ -132,6 +138,11 @@ "command": "coder.navigateToWorkspace", "when": "coder.authenticated && view == myWorkspaces || coder.authenticated && view == allWorkspaces", "group": "inline" + }, + { + "command": "coder.navigateToWorkspaceSettings", + "when": "coder.authenticated && view == myWorkspaces || coder.authenticated && view == allWorkspaces", + "group": "inline" } ] } diff --git a/src/commands.ts b/src/commands.ts index 8d973887..573529ef 100644 --- a/src/commands.ts +++ b/src/commands.ts @@ -115,9 +115,29 @@ export class Commands { await vscode.commands.executeCommand("vscode.open", uri) } - public async navigateToWorkspace(workspace: WorkspaceTreeItem): Promise { - const uri = this.storage.getURL() + `/@${workspace.workspaceOwner}/${workspace.workspaceName}` - await vscode.commands.executeCommand("vscode.open", uri) + public async navigateToWorkspace(workspace: WorkspaceTreeItem) { + if (workspace) { + const uri = this.storage.getURL() + `/@${workspace.workspaceOwner}/${workspace.workspaceName}` + await vscode.commands.executeCommand("vscode.open", uri) + } else if (this.storage.workspace) { + const uri = this.storage.getURL() + `/@${this.storage.workspace.owner_name}/${this.storage.workspace.name}` + await vscode.commands.executeCommand("vscode.open", uri) + } else { + vscode.window.showInformationMessage("No workspace found.") + } + } + + public async navigateToWorkspaceSettings(workspace: WorkspaceTreeItem) { + if (workspace) { + const uri = this.storage.getURL() + `/@${workspace.workspaceOwner}/${workspace.workspaceName}/settings` + await vscode.commands.executeCommand("vscode.open", uri) + } else if (this.storage.workspace) { + const uri = + this.storage.getURL() + `/@${this.storage.workspace.owner_name}/${this.storage.workspace.name}/settings` + await vscode.commands.executeCommand("vscode.open", uri) + } else { + vscode.window.showInformationMessage("No workspace found.") + } } public async open(...args: unknown[]): Promise { diff --git a/src/extension.ts b/src/extension.ts index ca894d09..fa7bb585 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -85,6 +85,10 @@ export async function activate(ctx: vscode.ExtensionContext): Promise { vscode.commands.registerCommand("coder.workspace.update", commands.updateWorkspace.bind(commands)) vscode.commands.registerCommand("coder.createWorkspace", commands.createWorkspace.bind(commands)) vscode.commands.registerCommand("coder.navigateToWorkspace", commands.navigateToWorkspace.bind(commands)) + vscode.commands.registerCommand( + "coder.navigateToWorkspaceSettings", + commands.navigateToWorkspaceSettings.bind(commands), + ) vscode.commands.registerCommand("coder.refreshWorkspaces", () => { myWorkspacesProvider.refresh() allWorkspacesProvider.refresh() From b0115028e0050484eff635c68cb8247aaa87d1f0 Mon Sep 17 00:00:00 2001 From: Rodrigo Maia Date: Wed, 29 Mar 2023 10:02:15 -0300 Subject: [PATCH 10/10] only show all workspaces tab for coder owners --- package.json | 3 ++- src/commands.ts | 3 +++ src/extension.ts | 9 +++++++-- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 2b3f0132..0c114daf 100644 --- a/package.json +++ b/package.json @@ -50,7 +50,8 @@ "id": "allWorkspaces", "name": "All Workspaces", "visibility": "visible", - "icon": "media/logo.svg" + "icon": "media/logo.svg", + "when": "coder.authenticated && coder.isOwner" } ] }, diff --git a/src/commands.ts b/src/commands.ts index 573529ef..bd4fc1b5 100644 --- a/src/commands.ts +++ b/src/commands.ts @@ -81,6 +81,9 @@ export class Commands { throw new Error("Failed to get authenticated user") } await vscode.commands.executeCommand("setContext", "coder.authenticated", true) + if (user.roles.find((role) => role.name === "owner")) { + await vscode.commands.executeCommand("setContext", "coder.isOwner", true) + } vscode.window .showInformationMessage( `Welcome to Coder, ${user.username}!`, diff --git a/src/extension.ts b/src/extension.ts index fa7bb585..7131dd95 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -20,8 +20,13 @@ export async function activate(ctx: vscode.ExtensionContext): Promise { vscode.window.registerTreeDataProvider("allWorkspaces", allWorkspacesProvider) getAuthenticatedUser() - .then(() => { - vscode.commands.executeCommand("setContext", "coder.authenticated", true) + .then(async (user) => { + if (user) { + vscode.commands.executeCommand("setContext", "coder.authenticated", true) + if (user.roles.find((role) => role.name === "owner")) { + await vscode.commands.executeCommand("setContext", "coder.isOwner", true) + } + } }) .catch(() => { // Not authenticated!