diff --git a/typescript/vscode-extension/package.json b/typescript/vscode-extension/package.json index 248d7b31..d2847e9e 100644 --- a/typescript/vscode-extension/package.json +++ b/typescript/vscode-extension/package.json @@ -34,17 +34,17 @@ "view/title": [ { "command": "suibase.settings", - "when": "view == suibaseTreeView", + "when": "view == explorerView", "group": "navigation@0" }, { "command": "suibase.console", - "when": "view == suibaseTreeView", + "when": "view == explorerView", "group": "navigation@1" }, { "command": "suibase.refresh", - "when": "view == suibaseTreeView", + "when": "view == explorerView", "group": "navigation@2" } ] @@ -61,10 +61,11 @@ "views": { "suibaseSidebar": [ { - "id": "suibaseTreeView", + "id": "explorerView", "name": "Suibase", "icon": "media/dep.svg", - "contextualTitle": "Suibase" + "contextualTitle": "Suibase", + "type": "webview" } ] } @@ -98,4 +99,3 @@ "ws": "^8.14.2" } } - diff --git a/typescript/vscode-extension/package.json.with_webview b/typescript/vscode-extension/package.json.with_webview deleted file mode 100644 index e2b71cbf..00000000 --- a/typescript/vscode-extension/package.json.with_webview +++ /dev/null @@ -1,101 +0,0 @@ -{ - "name": "suibase", - "displayName": "suibase", - "description": "Streamline Sui Move development and testing.", - "version": "0.0.1", - "repository": "https://github.com/ChainMovers/suibase", - "engines": { - "vscode": "^1.82.0" - }, - "categories": [ - "Other" - ], - "activationEvents": [], - "main": "./out/extension.js", - "contributes": { - "commands": [ - { - "command": "suibase.settings", - "title": "settings", - "icon": "$(settings-view-bar-icon)" - }, - { - "command": "suibase.console", - "title": "console", - "icon": "$(debug-console)" - }, - { - "command": "suibase.refresh", - "title": "refresh", - "icon": "$(refresh)" - } - ], - "menus": { - "view/title": [ - { - "command": "suibase.settings", - "when": "view == explorerPanel", - "group": "navigation@0" - }, - { - "command": "suibase.console", - "when": "view == explorerPanel", - "group": "navigation@1" - }, - { - "command": "suibase.refresh", - "when": "view == explorerPanel", - "group": "navigation@2" - } - ] - }, - "viewsContainers": { - "activitybar": [ - { - "id": "suibaseSidebar", - "title": "Suibase", - "icon": "media/dep.svg" - } - ] - }, - "views": { - "suibaseSidebar": [ - { - "id": "explorerView", - "name": "Suibase", - "icon": "media/dep.svg", - "contextualTitle": "Suibase", - "type": "webview" - } - ] - } - }, - "scripts": { - "install:all": "pnpm install && cd webview-ui && pnpm install", - "start:webview": "cd webview-ui && pnpm run dev", - "build:webview": "cd webview-ui && pnpm run build", - "vscode:prepublish": "pnpm run compile", - "compile": "tsc -p ./", - "watch": "tsc -watch -p ./", - "pretest": "pnpm run compile && pnpm run lint", - "lint": "eslint src --ext ts", - "test": "node ./out/test/runTest.js" - }, - "devDependencies": { - "@types/mocha": "^10.0.1", - "@types/node": "16.x", - "@types/vscode": "^1.82.0", - "@types/ws": "^8.5.5", - "@typescript-eslint/eslint-plugin": "^6.4.1", - "@typescript-eslint/parser": "^6.4.1", - "@vscode/test-electron": "^2.3.4", - "eslint": "^8.47.0", - "glob": "^10.3.4", - "mocha": "^10.2.0", - "typescript": "^5.1.6" - }, - "dependencies": { - "rpc-websockets": "^7.6.0", - "ws": "^8.14.2" - } -} diff --git a/typescript/vscode-extension/src/SuibaseSidebar.ts b/typescript/vscode-extension/src/SuibaseSidebar.ts deleted file mode 100644 index 20f50610..00000000 --- a/typescript/vscode-extension/src/SuibaseSidebar.ts +++ /dev/null @@ -1,166 +0,0 @@ -import * as vscode from "vscode"; -import * as fs from "fs"; -import * as path from "path"; - -// References: -// https://code.visualstudio.com/api/extension-guides/tree-view -// https://github.com/microsoft/vscode-extension-samples/tree/main/tree-view-sample -export class DepNodeProvider implements vscode.TreeDataProvider { - private _onDidChangeTreeData: vscode.EventEmitter = new vscode.EventEmitter< - Dependency | undefined | void - >(); - readonly onDidChangeTreeData: vscode.Event = this._onDidChangeTreeData.event; - - constructor(private workdir: string, private workspaceRoot: string | undefined) {} - - refresh(): void { - this._onDidChangeTreeData.fire(); - } - - getTreeItem(element: Dependency): vscode.TreeItem { - return element; - } - - getChildren(element?: Dependency): Thenable { - /* - if (!this.workspaceRoot) { - vscode.window.showInformationMessage('No dependency in empty workspace'); - return Promise.resolve([]); - } - - if (element) { - return Promise.resolve(this.getDepsInPackageJson(path.join(this.workspaceRoot, 'node_modules', element.label, 'package.json'))); - } else { - const packageJsonPath = path.join(this.workspaceRoot, 'package.json'); - if (this.pathExists(packageJsonPath)) { - return Promise.resolve(this.getDepsInPackageJson(packageJsonPath)); - } else { - vscode.window.showInformationMessage('Workspace has no package.json'); - return Promise.resolve([]); - } - }*/ - if (element) { - return Promise.resolve(this.getDepsInPackageJson(this.workdir)); - } else { - return Promise.resolve(this.getDepsInPackageJson("NULL")); - } - } - - /** - * Given the path to package.json, read all its dependencies and devDependencies. - */ - private getDepsInPackageJson(packageJsonPath: string): Dependency[] { - const toDep = (moduleName: string, parentName: string): Dependency => { - if (parentName === "NULL") { - return new Dependency(moduleName, parentName, vscode.TreeItemCollapsibleState.Collapsed); - } else { - return new Dependency(moduleName, parentName, vscode.TreeItemCollapsibleState.None); - } - }; - const deps = [ - toDep("Localnet", packageJsonPath), - toDep("Devnet", packageJsonPath), - toDep("Testnet", packageJsonPath), - toDep("Mainnet", packageJsonPath), - ]; - return deps; - /* - const workspaceRoot = this.workspaceRoot; - if (this.pathExists(packageJsonPath) && workspaceRoot) { - const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8')); - - - const deps = packageJson.dependencies - ? Object.keys(packageJson.dependencies).map(dep => toDep(dep, packageJson.dependencies[dep])) - : []; - const devDeps = packageJson.devDependencies - ? Object.keys(packageJson.devDependencies).map(dep => toDep(dep, packageJson.devDependencies[dep])) - : []; - return deps.concat(devDeps); - } else { - return []; - } - */ - } - - private pathExists(p: string): boolean { - try { - fs.accessSync(p); - } catch (err) { - return false; - } - - return true; - } -} - -export class Dependency extends vscode.TreeItem { - constructor( - public readonly label: string, - private readonly version: string, - public readonly collapsibleState: vscode.TreeItemCollapsibleState, - public readonly command?: vscode.Command - ) { - super(label, collapsibleState); - - this.tooltip = `${this.label}-${this.version}`; - this.description = this.version; - } - - iconPath = { - light: path.join(__filename, "..", "..", "resources", "light", "dependency.svg"), - dark: path.join(__filename, "..", "..", "resources", "dark", "dependency.svg"), - }; - - contextValue = "dependency"; -} - -export class SuibaseSidebar { - private static instance?: SuibaseSidebar; - private static context?: vscode.ExtensionContext; - - private constructor() {} // Called only from activate(). - private dispose() {} // Called only from deactivate(). - - public static activate(context: vscode.ExtensionContext) { - if (SuibaseSidebar.instance) { - console.log("Error: SuibaseSidebar.activate() called more than once"); - return; - } - - SuibaseSidebar.context = context; - SuibaseSidebar.instance = new SuibaseSidebar(); - - // Registration of the tree view. - // Code for the tree view - const rootPath = - vscode.workspace.workspaceFolders && vscode.workspace.workspaceFolders.length > 0 - ? vscode.workspace.workspaceFolders[0].uri.fsPath - : undefined; - - // Do createTreeView if rootPath is defined - { - const tree = new DepNodeProvider("suibase", rootPath); - vscode.window.registerTreeDataProvider("suibaseTreeView", tree); - } - } - - public static deactivate() { - if (SuibaseSidebar.instance) { - SuibaseSidebar.instance.dispose(); - delete SuibaseSidebar.instance; - SuibaseSidebar.instance = undefined; - } else { - console.log("Error: SuibaseSidebar.deactivate() called out of order"); - } - - SuibaseSidebar.context = undefined; - } - - public static getInstance(): SuibaseSidebar | undefined { - if (!SuibaseSidebar.instance) { - console.log("Error: SuibaseSidebar.getInstance() called before activate()"); - } - return SuibaseSidebar.instance; - } -} diff --git a/typescript/vscode-extension/src/panels/BasePanel.ts b/typescript/vscode-extension/src/bases/BaseWebview.ts similarity index 67% rename from typescript/vscode-extension/src/panels/BasePanel.ts rename to typescript/vscode-extension/src/bases/BaseWebview.ts index 26d131d9..258fea25 100644 --- a/typescript/vscode-extension/src/panels/BasePanel.ts +++ b/typescript/vscode-extension/src/bases/BaseWebview.ts @@ -1,5 +1,5 @@ import * as vscode from "vscode"; -import { Disposable, WebviewPanel, window, Uri, ViewColumn } from "vscode"; +import { Disposable, Webview, WebviewPanel, window, Uri, ViewColumn } from "vscode"; import { getUri } from "../utilities/getUri"; import { getNonce } from "../utilities/getNonce"; @@ -8,52 +8,107 @@ import { getNonce } from "../utilities/getNonce"; * * Responsibilities include: * - Creating and rendering the webview. - * - Setting the HTML, CSS and Typescript content of the webview panel - * - Properly cleaning up and disposing of webview resources when the panel is closed + * - Setting the HTML, CSS and Typescript content of the webview. + * - Properly cleaning up and disposing of webview resources when closed * - Setting message listeners so data can be passed between the webview and extension */ -export class BasePanel { - // Create a map of BasePanel instances, each keyed by a unique string. - // They all share the same extension context. - //private static instances: Map; +export class BaseWebview implements vscode.WebviewViewProvider { private static context?: vscode.ExtensionContext; // Instance variables initialized in constructor() - private readonly panelKey: string; - private readonly panelTitle: string; + private readonly key: string; + private readonly title: string; private readonly extensionUri: Uri; + private readonly extensionUris: Uri[] = []; // Instance variables initialized on first render() + private webview: Webview | undefined; private panel: WebviewPanel | undefined; private disposables: Disposable[] = []; + // Allow the subclasses read-access to the panel variable. + protected getWebview() { + if (!this.panel) { + return this.webview; + } + return this.panel.webview; + } + + protected getPanel() { + return this.panel; + } + + // Every view in the extension should have a unique key. + // + // This key is used to identify the view when the html is rendered. + protected getKey() { + return this.key; + } + + protected getTitle() { + return this.title; + } + + /* + protected getExtensionUri() { + return this.extensionUri; + }*/ + /** - * The BasePanel class private constructor (called only from the render method). - * - * @param panel A reference to the webview panel - * @param extensionUri The URI of the directory containing the extension + * The BaseWebview class private constructor (called only from the derived class). */ - protected constructor(panelKey: string, panelTitle: string) { - if (!BasePanel.context) { + protected constructor(key: string, title: string) { + if (!BaseWebview.context) { console.log("Error: BasePanel.constructor called before activate()"); this.extensionUri = Uri.parse("file:///undefined"); } else { - this.extensionUri = BasePanel.context.extensionUri; + this.extensionUri = BaseWebview.context.extensionUri; + this.extensionUris = [ + Uri.joinPath(BaseWebview.context.extensionUri, "out"), + Uri.joinPath(BaseWebview.context.extensionUri, "webview-ui/public/build"), + Uri.joinPath(BaseWebview.context.extensionUri, "webview-ui/node_modules/@vscode/codicons/dist"), + ]; } - this.panelKey = panelKey; - this.panelTitle = panelTitle; + this.key = key; + this.title = title; // Set an event listener to listen for messages passed from the webview context this._setWebviewMessageListener(); } public static activate(context: vscode.ExtensionContext) { - BasePanel.context = context; + BaseWebview.context = context; } public static deactivate() { - BasePanel.context = undefined; + BaseWebview.context = undefined; + } + + public resolveWebviewView( + webviewView: vscode.WebviewView, + _context: vscode.WebviewViewResolveContext, + _token: vscode.CancellationToken + ) { + this.webview = webviewView.webview; + + webviewView.webview.options = { + // Allow scripts in the webview + enableScripts: true, + + localResourceRoots: this.extensionUris, + }; + + webviewView.webview.html = this._getWebviewContent(); + /* + webviewView.webview.onDidReceiveMessage((data) => { + switch (data.type) { + case "colorSelected": { + vscode.window.activeTextEditor?.insertSnippet(new vscode.SnippetString(`#${data.value}`)); + break; + } + } + });*/ } /** @@ -64,18 +119,18 @@ export class BasePanel { * @param panelKey A unique string that identifies the webview panel. * @param panelTitle The title shown for this webview panel. */ - protected render() { + protected renderPanel() { // Look if there is already a BasePanel instance for the given key. if (this.panel !== undefined) { // If the webview panel already exists reveal it this.panel.reveal(ViewColumn.One); - console.log("BasePanel _panel.reveal() called"); + console.log("BaseWebview render_panel reveal() called"); } else { // If a webview panel does not already exist create and show a new one // "this" here is the subclass that extends BasePanel. this.panel = window.createWebviewPanel( - this.panelKey, // Panel view type, must match what is in package.json - this.panelTitle, + this.key, // Panel view type, must match what is in package.json + this.title, // The editor column the panel should be displayed in ViewColumn.One, // Extra panel configurations @@ -83,11 +138,7 @@ export class BasePanel { // Enable JavaScript in the webview enableScripts: true, // Restrict the webview to only load resources from the `out` and `webview-ui/build` directories - localResourceRoots: [ - Uri.joinPath(this.extensionUri, "out"), - Uri.joinPath(this.extensionUri, "webview-ui/public/build"), - Uri.joinPath(this.extensionUri, "webview-ui/node_modules/@vscode/codicons/dist"), - ], + localResourceRoots: this.extensionUris, } ); @@ -135,28 +186,20 @@ export class BasePanel { * rendered within the webview panel */ private _getWebviewContent() { - if (!this.panel) { - // Should never happen, but just in case... show an error in the panel so the user can see (and report). - return "Error: Missing webview panel instance"; + let webview = this.getWebview(); + if (!webview) { + // Should never happen, but just in case... show an error so the user can see (and report). + return "Error: Missing webview instance"; } + // The CSS file from the Svelte build output - const stylesUri = getUri(this.panel.webview, this.extensionUri, [ - "webview-ui", - "public", - "build", - "bundle.css", - ]); + const stylesUri = getUri(webview, this.extensionUri, ["webview-ui", "public", "build", "bundle.css"]); // The JS file from the Svelte build output - const scriptUri = getUri(this.panel.webview, this.extensionUri, [ - "webview-ui", - "public", - "build", - "bundle.js", - ]); + const scriptUri = getUri(webview, this.extensionUri, ["webview-ui", "public", "build", "bundle.js"]); // The icon library being used. - const iconsUri = getUri(this.panel.webview, this.extensionUri, [ + const iconsUri = getUri(webview, this.extensionUri, [ "webview-ui", "node_modules", "@vscode/codicons", @@ -167,7 +210,7 @@ export class BasePanel { const nonce = getNonce(); // Origin for Content security policy source. - const cspSource = this.panel.webview.cspSource; + const cspSource = webview.cspSource; // Tip: Install the es6-string-html VS Code extension to enable code highlighting below @@ -184,7 +227,7 @@ export class BasePanel { @@ -203,8 +246,7 @@ export class BasePanel { */ private _setWebviewMessageListener() { if (!this.panel) { - console.log("Error: webview panel instance missing"); - return; + return; // For now, only panel supports message passing. } this.panel.webview.onDidReceiveMessage( diff --git a/typescript/vscode-extension/src/extension.ts b/typescript/vscode-extension/src/extension.ts index bbef4180..61d733a9 100644 --- a/typescript/vscode-extension/src/extension.ts +++ b/typescript/vscode-extension/src/extension.ts @@ -2,11 +2,10 @@ // Import the module and reference it with the alias vscode in your code below import * as vscode from "vscode"; -import { SuibaseSidebar } from "./SuibaseSidebar"; -//import { SuibaseSidebar } from "./sidebar/SuibaseSidebar"; +import { SuibaseSidebar } from "./sidebar/SuibaseSidebar"; import { SuibaseExec } from "./SuibaseExec"; import { SuibaseCommands } from "./SuibaseCommands"; -import { BasePanel } from "./panels/BasePanel"; +import { BaseWebview } from "./bases/BaseWebview"; // This method is called when the extension is activated by VSCode. export function activate(context: vscode.ExtensionContext) { @@ -15,7 +14,7 @@ export function activate(context: vscode.ExtensionContext) { // Low-level APIs SuibaseExec.activate(context); - BasePanel.activate(context); + BaseWebview.activate(context); // "Business logic" enabled next. SuibaseCommands.activate(context); @@ -36,7 +35,7 @@ export function deactivate() { SuibaseCommands.deactivate(); // Low-level APIs disabled last. - BasePanel.deactivate(); + BaseWebview.deactivate(); SuibaseExec.deactivate(); console.log("extension deactivate() completed"); diff --git a/typescript/vscode-extension/src/panels/ConsolePanel.ts b/typescript/vscode-extension/src/panels/ConsolePanel.ts index aced2788..646c4a87 100644 --- a/typescript/vscode-extension/src/panels/ConsolePanel.ts +++ b/typescript/vscode-extension/src/panels/ConsolePanel.ts @@ -1,11 +1,11 @@ -import { BasePanel } from "./BasePanel"; +import { BaseWebview } from "../bases/BaseWebview"; /** * This class manages the state and behavior of the ConsolePanel webview. * * This is a singleton. */ -export class ConsolePanel extends BasePanel { +export class ConsolePanel extends BaseWebview { private static instance?: ConsolePanel; /** @@ -15,7 +15,7 @@ export class ConsolePanel extends BasePanel { super("suibase.console", "Sui Console"); } - // Note: Does not use the activate/deactivate pattern (the BasePanel does). + // Note: Does not use the activate/deactivate pattern (the BaseWebview does). // Instead this subclass uses a render()/dispose() for its lifetime. // // This is because activate() always happens once and early while render() @@ -26,10 +26,10 @@ export class ConsolePanel extends BasePanel { if (!ConsolePanel.instance) { ConsolePanel.instance = new ConsolePanel(); } - ConsolePanel.instance.render(); + ConsolePanel.instance.renderPanel(); } - // Dispose is a callback triggered by VSCode (see BasePanel). + // Dispose is a callback triggered by VSCode (see BaseView). protected dispose() { console.log("ConsolePanel.dispose() called"); if (ConsolePanel.instance) { diff --git a/typescript/vscode-extension/src/panels/DashboardPanel.ts b/typescript/vscode-extension/src/panels/DashboardPanel.ts index 0e8b4020..6e72bf02 100644 --- a/typescript/vscode-extension/src/panels/DashboardPanel.ts +++ b/typescript/vscode-extension/src/panels/DashboardPanel.ts @@ -1,11 +1,11 @@ -import { BasePanel } from "./BasePanel"; +import { BaseWebview } from "../bases/BaseWebview"; /** * This class manages the state and behavior of the DashboardPanel webview. * * This is a singleton. */ -export class DashboardPanel extends BasePanel { +export class DashboardPanel extends BaseWebview { private static instance?: DashboardPanel; /** @@ -27,10 +27,10 @@ export class DashboardPanel extends BasePanel { if (!DashboardPanel.instance) { DashboardPanel.instance = new DashboardPanel(); } - DashboardPanel.instance.render(); + DashboardPanel.instance.renderPanel(); } - // Dispose is a callback triggered by VSCode (see BasePanel). + // Dispose is a callback triggered by VSCode (see BaseWebview). protected dispose() { console.log("DashboardPanel.dispose() called"); if (DashboardPanel.instance) { diff --git a/typescript/vscode-extension/src/panels/ExplorerPanel.ts b/typescript/vscode-extension/src/panels/ExplorerPanel.ts deleted file mode 100644 index 8bdccfbb..00000000 --- a/typescript/vscode-extension/src/panels/ExplorerPanel.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { BasePanel } from "./BasePanel"; - -/** - * This class manages the state and behavior of the DashboardPanel webview. - * - * This is a singleton. - */ -export class ExplorerPanel extends BasePanel { - private static instance?: ExplorerPanel; - - /** - * ExplorerPanel constructor called only from ExplorerPanel.render() - */ - private constructor() { - super("suibase.explorer", "Suibase Explorer"); - } - - // Note: Does not use the activate/deactivate pattern (the BasePanel does). - // Instead this subclass uses a render()/dispose() for its lifetime. - // - // This is because activate() always happens once and early while render() - // and dispose() may happen or not depending of the user actions to display - // the panel or not. - // - public static render() { - //DashboardPanel.instance = DashboardPanel.instance ?? new DashboardPanel(); - if (!ExplorerPanel.instance) { - ExplorerPanel.instance = new ExplorerPanel(); - } - ExplorerPanel.instance.render(); - } - - // Dispose is a callback triggered by VSCode (see BasePanel). - protected dispose() { - console.log("ExplorerPanel.dispose() called"); - if (ExplorerPanel.instance) { - super.dispose(); - delete ExplorerPanel.instance; - ExplorerPanel.instance = undefined; - } else { - console.log("Error: dispose() called out of order"); - } - } -} diff --git a/typescript/vscode-extension/src/sidebar/ExplorerView.ts b/typescript/vscode-extension/src/sidebar/ExplorerView.ts deleted file mode 100644 index 10b99223..00000000 --- a/typescript/vscode-extension/src/sidebar/ExplorerView.ts +++ /dev/null @@ -1,174 +0,0 @@ -import { - CancellationToken, - Disposable, - Uri, - Webview, - WebviewView, - WebviewViewProvider, - WebviewViewResolveContext, - window, - workspace, - extensions, - commands, -} from "vscode"; - -export class ExplorerView implements WebviewViewProvider, Disposable { - public static readonly viewType = "explorerView"; - private static instance: ExplorerView; - - private panel: WebviewView | null = null; - private disposable: Disposable | null = null; - private panelActions: any = null; - - private constructor(private readonly extPath: Uri) {} - - /** - * Creates the singleton instance for the panel - * @param extPath - */ - public static getInstance(extPath?: Uri): ExplorerView { - if (!ExplorerView.instance) { - ExplorerView.instance = new ExplorerView(extPath as Uri); - } - - return ExplorerView.instance; - } - - /** - * Retrieve the visibility of the webview - */ - get visible() { - return this.panel ? this.panel.visible : false; - } - - /** - * Webview panel dispose - */ - public dispose() { - if (this.disposable) { - this.disposable.dispose(); - } - } - - /** - * Default resolve webview panel - * @param webviewView - * @param context - * @param token - */ - public async resolveWebviewView( - webviewView: WebviewView, - context: WebviewViewResolveContext, - token: CancellationToken - ): Promise { - this.panel = webviewView; - - webviewView.webview.options = { - enableScripts: true, - enableCommandUris: true, - localResourceRoots: [this.extPath], - }; - - webviewView.webview.html = this.getWebviewContent(webviewView.webview); - - this.disposable = Disposable.from( - webviewView.onDidDispose(() => { - webviewView.webview.html = ""; - }, this) - ); - - // webviewView.onDidChangeVisibility(async () => { - // if (this.visible) { - // await this.getSettings(); - // } - // }); - - window.onDidChangeActiveTextEditor(async () => { - await this.getSettings(); - }, this); - - webviewView.webview.onDidReceiveMessage((msg) => { - switch (msg.command) { - case "trigger": - if (!!msg.data.data) { - commands.executeCommand(msg.data.command, msg.data.data); - } else { - commands.executeCommand(msg.data.command); - } - return; - case "getSettings": - this.getSettings(); - return; - default: - return; - } - }); - } - - /** - * Retrieve the extension settings - */ - private async getSettings() { - this.panelActions = []; - - const allExtensions = extensions.all.filter( - (e) => !e.id.startsWith("vscode") && !e.id.startsWith("ms-vscode") - ); - - for await (const ext of allExtensions) { - try { - const values = await commands.executeCommand(`${ext.id}.panel.registration`); - - this.panelActions.push(values); - } catch (e) { - // The extension does not have a panel registration - } - } - - const total = this.panelActions.length; - this.panel!.title = `${total} Connection${total === 1 ? "" : "s"}`; - - this.panel!.webview.postMessage({ - command: "settings", - data: this.panelActions, - }); - } - - private getNonce() { - let text = ""; - const possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; - for (let i = 0; i < 32; i++) { - text += possible.charAt(Math.floor(Math.random() * possible.length)); - } - return text; - } - - /** - * Retrieve the webview HTML contents - * @param webView - */ - private getWebviewContent(webView: Webview): string { - const stylesUri = webView.asWebviewUri(Uri.joinPath(this.extPath, "assets/media", "styles.css")); - const scriptUri = webView.asWebviewUri(Uri.joinPath(this.extPath, "dist", "viewpanel.js")); - const nonce = this.getNonce(); - - return ` - - - - - - - - - Extension panel - - -
- - - - - `; - } -} diff --git a/typescript/vscode-extension/src/sidebar/SuibaseSidebar.ts b/typescript/vscode-extension/src/sidebar/SuibaseSidebar.ts index 199998a8..a574e2cd 100644 --- a/typescript/vscode-extension/src/sidebar/SuibaseSidebar.ts +++ b/typescript/vscode-extension/src/sidebar/SuibaseSidebar.ts @@ -1,161 +1,28 @@ import * as vscode from "vscode"; -import * as fs from "fs"; -import * as path from "path"; -import { ExplorerView } from "./ExplorerView"; +import { BaseWebview } from "../bases/BaseWebview"; -// References: -// https://code.visualstudio.com/api/extension-guides/tree-view -// https://github.com/microsoft/vscode-extension-samples/tree/main/tree-view-sample -export class DepNodeProvider implements vscode.TreeDataProvider { - private _onDidChangeTreeData: vscode.EventEmitter = new vscode.EventEmitter< - Dependency | undefined | void - >(); - readonly onDidChangeTreeData: vscode.Event = this._onDidChangeTreeData.event; - - constructor(private workdir: string, private workspaceRoot: string | undefined) {} - - refresh(): void { - this._onDidChangeTreeData.fire(); - } - - getTreeItem(element: Dependency): vscode.TreeItem { - return element; - } - - getChildren(element?: Dependency): Thenable { - /* - if (!this.workspaceRoot) { - vscode.window.showInformationMessage('No dependency in empty workspace'); - return Promise.resolve([]); - } - - if (element) { - return Promise.resolve(this.getDepsInPackageJson(path.join(this.workspaceRoot, 'node_modules', element.label, 'package.json'))); - } else { - const packageJsonPath = path.join(this.workspaceRoot, 'package.json'); - if (this.pathExists(packageJsonPath)) { - return Promise.resolve(this.getDepsInPackageJson(packageJsonPath)); - } else { - vscode.window.showInformationMessage('Workspace has no package.json'); - return Promise.resolve([]); - } - }*/ - if (element) { - return Promise.resolve(this.getDepsInPackageJson(this.workdir)); - } else { - return Promise.resolve(this.getDepsInPackageJson("NULL")); - } - } - - /** - * Given the path to package.json, read all its dependencies and devDependencies. - */ - private getDepsInPackageJson(packageJsonPath: string): Dependency[] { - const toDep = (moduleName: string, parentName: string): Dependency => { - if (parentName === "NULL") { - return new Dependency(moduleName, parentName, vscode.TreeItemCollapsibleState.Collapsed); - } else { - return new Dependency(moduleName, parentName, vscode.TreeItemCollapsibleState.None); - } - }; - const deps = [ - toDep("Localnet", packageJsonPath), - toDep("Devnet", packageJsonPath), - toDep("Testnet", packageJsonPath), - toDep("Mainnet", packageJsonPath), - ]; - return deps; - /* - const workspaceRoot = this.workspaceRoot; - if (this.pathExists(packageJsonPath) && workspaceRoot) { - const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8')); - - - const deps = packageJson.dependencies - ? Object.keys(packageJson.dependencies).map(dep => toDep(dep, packageJson.dependencies[dep])) - : []; - const devDeps = packageJson.devDependencies - ? Object.keys(packageJson.devDependencies).map(dep => toDep(dep, packageJson.devDependencies[dep])) - : []; - return deps.concat(devDeps); - } else { - return []; - } - */ - } - - private pathExists(p: string): boolean { - try { - fs.accessSync(p); - } catch (err) { - return false; - } - - return true; - } -} - -export class Dependency extends vscode.TreeItem { - constructor( - public readonly label: string, - private readonly version: string, - public readonly collapsibleState: vscode.TreeItemCollapsibleState, - public readonly command?: vscode.Command - ) { - super(label, collapsibleState); - - this.tooltip = `${this.label}-${this.version}`; - this.description = this.version; - } - - iconPath = { - light: path.join(__filename, "..", "..", "resources", "light", "dependency.svg"), - dark: path.join(__filename, "..", "..", "resources", "dark", "dependency.svg"), - }; - - contextValue = "dependency"; -} - -export class SuibaseSidebar { +export class SuibaseSidebar extends BaseWebview { private static instance?: SuibaseSidebar; - private static context?: vscode.ExtensionContext; - private constructor() {} // Called only from activate(). - private dispose() {} // Called only from deactivate(). + private constructor() { + super("suibase.sidebar", "Sui Sidebar"); + } public static activate(context: vscode.ExtensionContext) { + console.log("SuibaseSidebar.activate() called"); if (SuibaseSidebar.instance) { console.log("Error: SuibaseSidebar.activate() called more than once"); return; } - SuibaseSidebar.context = context; SuibaseSidebar.instance = new SuibaseSidebar(); - /* - // Registration of the tree view. - // Code for the tree view - const rootPath = - vscode.workspace.workspaceFolders && vscode.workspace.workspaceFolders.length > 0 - ? vscode.workspace.workspaceFolders[0].uri.fsPath - : undefined; - - // Do createTreeView if rootPath is defined - { - const tree = new DepNodeProvider("suibase", rootPath); - vscode.window.registerTreeDataProvider("suibaseTreeView", tree); - } - */ - // Register for the explorerPanel view. - let explorerView = vscode.window.registerWebviewViewProvider( - "explorerView", - ExplorerView.getInstance(context.extensionUri), - { - webviewOptions: { - retainContextWhenHidden: true, - }, - } - ); + // Tell VSCode how to build the view using the SuibaseSidebar::BaseWebview::WebviewViewProvider + let explorerView = vscode.window.registerWebviewViewProvider("explorerView", SuibaseSidebar.instance, { + webviewOptions: { + retainContextWhenHidden: true, + }, + }); context.subscriptions.push(explorerView); } @@ -168,14 +35,17 @@ export class SuibaseSidebar { } else { console.log("Error: SuibaseSidebar.deactivate() called out of order"); } - - SuibaseSidebar.context = undefined; } - public static getInstance(): SuibaseSidebar | undefined { - if (!SuibaseSidebar.instance) { - console.log("Error: SuibaseSidebar.getInstance() called before activate()"); + // Dispose is a callback triggered by VSCode (see BaseWebview). + protected dispose() { + console.log("SuibaseSidebar.dispose() called"); + if (SuibaseSidebar.instance) { + super.dispose(); + delete SuibaseSidebar.instance; + SuibaseSidebar.instance = undefined; + } else { + console.log("Error: dispose() called out of order"); } - return SuibaseSidebar.instance; } } diff --git a/typescript/vscode-extension/webview-ui/src/App.svelte b/typescript/vscode-extension/webview-ui/src/App.svelte index 5ddcc1c5..b020e0ba 100644 --- a/typescript/vscode-extension/webview-ui/src/App.svelte +++ b/typescript/vscode-extension/webview-ui/src/App.svelte @@ -30,22 +30,25 @@ function handleHowdyClick() { VSCode.postMessage({ command: "hello", - text: "Hey there partner! 🤠", + text: "Howdy!", }); }
- {#if globalThis.suibase_panel_key == "suibase.settings"} + {#if globalThis.suibase_view_key == "suibase.settings"} - {:else if globalThis.suibase_panel_key == "suibase.console"} + + Config Howdy! + {:else if globalThis.suibase_view_key == "suibase.console"} - {:else if globalThis.suibase_panel_key == "explorer.console"} + + Console Howdy! + {:else if globalThis.suibase_view_key == "suibase.sidebar"} + + Explorer Howdy! {/if} - - - Howdy!