From fd750864dbac930f9102a44781fe08a26d46cf9a Mon Sep 17 00:00:00 2001 From: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com> Date: Sat, 6 Jul 2024 01:31:16 -0400 Subject: [PATCH] Add sidebar code --- package.json | 40 +++++++- src/assets/icon.png | Bin 0 -> 2092 bytes src/extension.ts | 37 ++++++- src/providers/SidebarProvider.ts | 78 ++++++++++++++ src/utilities/getNonce.ts | 8 ++ src/utilities/getUri.ts | 11 ++ src/webview/main.ts | 169 +++++++++++++++++++++++++++++-- src/webview/styles.css | 45 ++++++++ 8 files changed, 372 insertions(+), 16 deletions(-) create mode 100644 src/assets/icon.png create mode 100644 src/providers/SidebarProvider.ts create mode 100644 src/webview/styles.css diff --git a/package.json b/package.json index cf6c694..82c3a46 100644 --- a/package.json +++ b/package.json @@ -12,12 +12,46 @@ "activationEvents": [], "main": "./dist/extension.js", "contributes": { + "viewsContainers": { + "activitybar": [ + { + "id": "custom-activitybar", + "title": "VSCode Extension", + "icon": "assets/logo_bito.svg" + } + ] + }, + "views": { + "custom-activitybar": [ + { + "type": "webview", + "id": "vscodeSidebar.openview", + "name": "View", + "contextualTitle": "View" + } + ] + }, "commands": [ { - "command": "claude-dev.helloWorld", - "title": "Hello World" + "command": "vscodeSidebar.openview", + "title": "Sidebar View" + }, + { + "command": "vscodeSidebar.menu.view", + "category": "vscode-extension-sidebar-html", + "title": "Sample WebView in VS Code Sidebar", + "icon": "$(clear-all)" } - ] + ], + "menus": { + "view/title": [ + { + "command": "vscodeSidebar.menu.view", + "group": "navigation", + "when": "view == vscodeSidebar.openview" + } + ] + } }, "scripts": { "vscode:prepublish": "npm run package", diff --git a/src/assets/icon.png b/src/assets/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..a6cde3a61abb81eb8fe29a6837b08b581209195b GIT binary patch literal 2092 zcmZWqdpy&7A773#967YYC~I>mwGAQ0rnQY$?w5>oGBzx>v8^_@cnWpIalchg;!NeK zNkuh|;s{6Omc%r-oIEJX{qii&PxZ*_d48|g=l6O4KA+F~^ZtE4-#@;;xH#FXC_$A# zAdm|A5YY|rmVjA+Hv#XAWFHm?w84i#Ah?hT1egnl9moi!gFw5|Po=f)`ID;C*x#RD z(t}su+^OT6*S@#qUK)pT&!l;9S;n%c&`k|GLEar3cad|NOF(TN8X6uh$r^e$Aj)Z2 zinP_POuvq6U4B(HKRq+5VkzP6yz2ydRIOip&%LjHD(UsCoQBT72BrVJOj1iCzSF9G zU~#nrYbN9)#>%ecoeCR6X;=L{Jwc6`U0~OYKFqvXM3xOKIPOFqm`+C=%W++r&f7RF3?dYa8x%`_i=FqHpEKy5(;Cpc z9re=d3E6s&_Km1>t-cYP9!98(J7axb9c~$t2nri^DBiE>nG3NU5X&2NRQ9VftLio% z+YvXO;*8%%WxP{<+Yc=JD4jwMbaVt60T>M0aGn8@2hawme z{I?I0&JEx&!gvfe3%1&q>d!vK!@}XKLTk_0|D+2T-<4S0Z({)iqE|C$6O=J}tv5im zS&d>`7y^2zH<7^v)&pqZ%*|}Rl>cAiyWlz^WgXFCJ#k&)e~F%4I)}h!0)jl;_jCD1 zUZ4C%v_Y@Vy>1U*m;5CPY#FCygI+r^oKow9F=Y@){s@_f_Xytj!TY!a#0&Cy+flz~ zZe1kbB2-aAHZsFAISFd+*h?9@Xu-@fi#ZwMaLEj}AR3J-uJKMmJ4%*l(Vr%3HO)V# zQ?d@HodtuH?lz^mXgm!a>3uN}7dX;qT@@(KC*&05N3|}t@LTh(gIdJV)3R_{`7q7o zfxP+offa0>lY9YK620*Xq#`2DLCS>(i-{Ij2U@aXr#V>gk{7~7C-HIE?&^)oX(K3y zCx2Y|t)93yDJT=GsT1;NanK9F>=Mb&H(Xe?Z8qkP{2-~OO4cqxlTv_E!@!=yMssw z0FUFcAD4L;j~36Mnw98$nd{(rOMFew1Euto4Z~4=5T{oAlCV!68mNMaA&5KGBy8&4 z&!b5rDn+}A9Jy!ccBKSpiY@8>1*F}H5lhk4NfOUbUHA6h+LA<0=!!*r`Jm=!@lrRW zAg_J`DrR9`TjDRlLXK!Dk`905jG3R2JyLw@)4A&dzgU37B$Ul{`ALJ0#Dm>ZyR{6P z>b&iIMKr8Wq42s>+LBi*FTi^?nt0Vg>2l`NlEF7}sUwBPXD!M_Lm>~jQ}Le7dt$&x zw-t*;5D7EB@7H!MY1RHsZJ*{pK7Z7MXZ@;uTNd=Z$yJsel#>lHd@SoozT&HK za{QV4?nP3-)=2Bdwp&uZqsFpZa1B=PqN8$$T5{H8mV^f8j7YZ3ywSzmOdO=T?r0|9 zIz?zgSVZ|pnZ0#Zj{Wsag|jO{L{qM3_EfUAvTfFwr?C4|jAn(l+``m(=oL(#(J-o< zPFljZ?hkkMR`ZU-Ds=hxib}nW<|ls~Ohq7li2wLocR46Uh)J*Q`fz9q?K0lOznl?2 zzRmwd7m0ln5;>wT`sI&l!&me>rw)ul=&C}aJD#td7o{(8NvDxMRHlM7HhABJ6#C?l zS>oM7jZ?=mexEc~DY;M8(W|^wo$JPYomwG5K@X&(V;df)U!(YIV3(topI3M&lnO7I z;W6dcrmP2dK?{Bs*Ig%-fn!t~+E&8eSkfZjmsFk?XYsdBDc7J1^}-dCrK)bZ*8A*s?b2W696_tml$Gc=Dyy+1=g9DG^xXuUeV zMCDjiKJ4ZL6;F5f6AJUD-GgxD6E@E$ioY@1Lsokou%7Ic44jx@6(Rkf^M4rG!O KQFPGn?0*21<$vt} literal 0 HcmV?d00001 diff --git a/src/extension.ts b/src/extension.ts index c004365..585b5df 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -2,6 +2,16 @@ // Import the module and reference it with the alias vscode in your code below import * as vscode from "vscode" import { HelloWorldPanel } from "./HelloWorldPanel" +import { SidebarProvider } from "./providers/SidebarProvider" + +/* +Built using https://github.com/microsoft/vscode-webview-ui-toolkit + +Inspired by +https://github.com/microsoft/vscode-webview-ui-toolkit-samples/tree/main/default/weather-webview +https://github.com/microsoft/vscode-webview-ui-toolkit-samples/tree/main/frameworks/hello-world-react-cra + +*/ // This method is called when your extension is activated // Your extension is activated the very first time the command is executed @@ -21,11 +31,32 @@ export function activate(context: vscode.ExtensionContext) { // context.subscriptions.push(disposable) - const helloCommand = vscode.commands.registerCommand("claude-dev.helloWorld", () => { - HelloWorldPanel.render(context.extensionUri) + // const helloCommand = vscode.commands.registerCommand("claude-dev.helloWorld", () => { + // HelloWorldPanel.render(context.extensionUri) + // }) + + // context.subscriptions.push(helloCommand) + + const provider = new SidebarProvider(context.extensionUri) + + context.subscriptions.push(vscode.window.registerWebviewViewProvider(SidebarProvider.viewType, provider)) + + context.subscriptions.push( + vscode.commands.registerCommand("vscodeSidebar.menu.view", () => { + const message = "Menu/Title of extension is clicked !" + vscode.window.showInformationMessage(message) + }) + ) + + // Command has been defined in the package.json file + // Provide the implementation of the command with registerCommand + // CommandId parameter must match the command field in package.json + let openWebView = vscode.commands.registerCommand("vscodeSidebar.openview", () => { + // Display a message box to the user + vscode.window.showInformationMessage('Command " Sidebar View [vscodeSidebar.openview] " called.') }) - context.subscriptions.push(helloCommand) + context.subscriptions.push(openWebView) } // This method is called when your extension is deactivated diff --git a/src/providers/SidebarProvider.ts b/src/providers/SidebarProvider.ts new file mode 100644 index 0000000..c16a5d2 --- /dev/null +++ b/src/providers/SidebarProvider.ts @@ -0,0 +1,78 @@ +import { getUri } from "../utilities/getUri" +import { getNonce } from "../utilities/getNonce" +//import * as weather from "weather-js" +import * as vscode from "vscode" + +export class SidebarProvider implements vscode.WebviewViewProvider { + public static readonly viewType = "vscodeSidebar.openview" + + private _view?: vscode.WebviewView + + constructor(private readonly _extensionUri: vscode.Uri) {} + + resolveWebviewView( + webviewView: vscode.WebviewView, + context: vscode.WebviewViewResolveContext, + token: vscode.CancellationToken + ): void | Thenable { + this._view = webviewView + + webviewView.webview.options = { + // Allow scripts in the webview + enableScripts: true, + localResourceRoots: [this._extensionUri], + } + webviewView.webview.html = this.getHtmlContent(webviewView.webview) + } + + private getHtmlContent(webview: vscode.Webview): string { + // Get the local path to main script run in the webview, + // then convert it to a uri we can use in the webview. + const scriptUri = webview.asWebviewUri(vscode.Uri.joinPath(this._extensionUri, "assets", "main.js")) + + const styleResetUri = webview.asWebviewUri(vscode.Uri.joinPath(this._extensionUri, "assets", "reset.css")) + const styleVSCodeUri = webview.asWebviewUri(vscode.Uri.joinPath(this._extensionUri, "assets", "vscode.css")) + + // Same for stylesheet + const stylesheetUri = webview.asWebviewUri(vscode.Uri.joinPath(this._extensionUri, "assets", "main.css")) + + // Use a nonce to only allow a specific script to be run. + const nonce = getNonce() + + return ` + + + + + + + + + + + + + + + + +
+
+
+

Subscribe today

+ + + + +

We won’t send you spam.

+

Unsubscribe at any time.

+ +
+
+
+ + + + ` + } +} diff --git a/src/utilities/getNonce.ts b/src/utilities/getNonce.ts index 63eecfd..b92871b 100644 --- a/src/utilities/getNonce.ts +++ b/src/utilities/getNonce.ts @@ -1,3 +1,11 @@ +/** + * A helper function that returns a unique alphanumeric identifier called a nonce. + * + * @remarks This function is primarily used to help enforce content security + * policies for resources/scripts being executed in a webview context. + * + * @returns A nonce + */ export function getNonce() { let text = "" const possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" diff --git a/src/utilities/getUri.ts b/src/utilities/getUri.ts index 9a2a5b3..b9f6807 100644 --- a/src/utilities/getUri.ts +++ b/src/utilities/getUri.ts @@ -1,5 +1,16 @@ import { Uri, Webview } from "vscode" +/** + * A helper function which will get the webview URI of a given file or resource. + * + * @remarks This URI can be used within a webview's HTML as a link to the + * given file/resource. + * + * @param webview A reference to the extension webview + * @param extensionUri The URI of the directory containing the extension + * @param pathList An array of strings representing the path to a file/resource + * @returns A URI pointing to the file/resource + */ export function getUri(webview: Webview, extensionUri: Uri, pathList: string[]) { return webview.asWebviewUri(Uri.joinPath(extensionUri, ...pathList)) } diff --git a/src/webview/main.ts b/src/webview/main.ts index 7244f8a..56a7cca 100644 --- a/src/webview/main.ts +++ b/src/webview/main.ts @@ -1,14 +1,8 @@ -import { provideVSCodeDesignSystem, vsCodeButton, vsCodeCheckbox } from "@vscode/webview-ui-toolkit" -// const toolkit = require("@vscode/webview-ui-toolkit") -// /* -// You must register the components you want to use -// */ + +/* provideVSCodeDesignSystem().register(vsCodeButton(), vsCodeCheckbox()) - const vscode = acquireVsCodeApi(); - window.addEventListener("load", main); - function main() { // To get improved type annotations/IntelliSense the associated class for // a given toolkit component can be imported and used to type cast a reference @@ -16,10 +10,165 @@ function main() { const howdyButton = document.getElementById("howdy") as Button; howdyButton?.addEventListener("click", handleHowdyClick); } - function handleHowdyClick() { vscode.postMessage({ command: "hello", text: "Hey there partner! 🀠", }); -} \ No newline at end of file +} + */ + + +import { + provideVSCodeDesignSystem, + Button, + Dropdown, + ProgressRing, + TextField, + vsCodeButton, + vsCodeDropdown, + vsCodeOption, + vsCodeTextField, + vsCodeProgressRing, + } from "@vscode/webview-ui-toolkit"; + + // In order to use the Webview UI Toolkit web components they + // must be registered with the browser (i.e. webview) using the + // syntax below. + provideVSCodeDesignSystem().register( + vsCodeButton(), + vsCodeDropdown(), + vsCodeOption(), + vsCodeProgressRing(), + vsCodeTextField() + ); + + // Get access to the VS Code API from within the webview context + const vscode = acquireVsCodeApi(); + + // Just like a regular webpage we need to wait for the webview + // DOM to load before we can reference any of the HTML elements + // or toolkit components + window.addEventListener("load", main); + + // Main function that gets executed once the webview DOM loads + function main() { + // To get improved type annotations/IntelliSense the associated class for + // a given toolkit component can be imported and used to type cast a reference + // to the element (i.e. the `as Button` syntax) + const checkWeatherButton = document.getElementById("check-weather-button") as Button; + checkWeatherButton.addEventListener("click", checkWeather); + + setVSCodeMessageListener(); + } + + function checkWeather() { + const location = document.getElementById("location") as TextField; + const unit = document.getElementById("unit") as Dropdown; + + // Passes a message back to the extension context with the location that + // should be searched for and the degree unit (F or C) that should be returned + vscode.postMessage({ + command: "weather", + location: location.value, + unit: unit.value, + }); + + displayLoadingState(); + } + + // Sets up an event listener to listen for messages passed from the extension context + // and executes code based on the message that is recieved + function setVSCodeMessageListener() { + window.addEventListener("message", (event) => { + const command = event.data.command; + + // switch (command) { + // case "weather": + // const weatherData = JSON.parse(event.data.payload); + // displayWeatherData(weatherData); + // break; + // case "error": + // displayError(event.data.message); + // break; + // } + }); + } + + function displayLoadingState() { + const loading = document.getElementById("loading") as ProgressRing; + const icon = document.getElementById("icon"); + const summary = document.getElementById("summary"); + if (loading && icon && summary) { + loading.classList.remove("hidden"); + icon.classList.add("hidden"); + summary.textContent = "Getting weather..."; + } + } + +// function displayWeatherData(weatherData) { +// const loading = document.getElementById("loading") as ProgressRing; +// const icon = document.getElementById("icon"); +// const summary = document.getElementById("summary"); +// if (loading && icon && summary) { +// loading.classList.add("hidden"); +// icon.classList.remove("hidden"); +// icon.textContent = getWeatherIcon(weatherData); +// summary.textContent = getWeatherSummary(weatherData); +// } +// } + +// function displayError(errorMsg) { +// const loading = document.getElementById("loading") as ProgressRing; +// const icon = document.getElementById("icon"); +// const summary = document.getElementById("summary"); +// if (loading && icon && summary) { +// loading.classList.add("hidden"); +// icon.classList.add("hidden"); +// summary.textContent = errorMsg; +// } +// } + +// function getWeatherSummary(weatherData) { +// const skyText = weatherData.current.skytext; +// const temperature = weatherData.current.temperature; +// const degreeType = weatherData.location.degreetype; + +// return `${skyText}, ${temperature}${degreeType}`; +// } + +// function getWeatherIcon(weatherData) { +// const skyText = weatherData.current.skytext.toLowerCase(); +// let icon = ""; + +// switch (skyText) { +// case "sunny": +// icon = "β˜€οΈ"; +// break; +// case "mostly sunny": +// icon = "🌀"; +// break; +// case "partly sunny": +// icon = "πŸŒ₯"; +// break; +// case "clear": +// icon = "β˜€οΈ"; +// break; +// case "fair": +// icon = "πŸŒ₯"; +// break; +// case "mostly cloudy": +// icon = "☁️"; +// break; +// case "cloudy": +// icon = "☁️"; +// break; +// case "rain showers": +// icon = "🌦"; +// break; +// default: +// icon = "✨"; +// } + +// return icon; +// } \ No newline at end of file diff --git a/src/webview/styles.css b/src/webview/styles.css new file mode 100644 index 0000000..4cb0dd7 --- /dev/null +++ b/src/webview/styles.css @@ -0,0 +1,45 @@ +h1 { + font-size: 1.5em; +} + +#search-container { + display: flex; + flex-direction: column; + align-items: flex-start; + justify-content: center; +} + +#location { + width: 100%; + margin-top: 0.5rem; +} + +#unit { + min-width: 30px; + width: 100%; + margin-top: 0.5rem; +} + +#check-weather-button { + margin-top: 0.5rem; +} + +#results-container { + display: flex; + align-items: center; + justify-content: space-around; + background-color: var(--vscode-input-background); + padding: 1rem; + margin: 1rem 0; + border-radius: 2px; +} + +#icon { + font-size: 3em; + padding: 0; + margin: 0; +} + +.hidden { + display: none; +}