Skip to content
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

Webview codicons: styling and interactions with codicons #352

Open
wants to merge 13 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 58 additions & 2 deletions webview-codicons-sample/README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Cat Codicons
# Webview with Codicons

Demonstrates loading [codicons](https://github.com/microsoft/vscode-codicons) in a [webview](https://code.visualstudio.com/api/extension-guides/webview).

Expand All @@ -15,4 +15,60 @@ Demonstrates loading [codicons](https://github.com/microsoft/vscode-codicons) in
- `npm run watch` or `npm run compile`
- `F5` to start debugging

Run the `Cat Codicons: Show Cat Codicons` command to create the webview.
Run the `Webview Codicons: Show Webview Codicons` command to create the webview.

While similar user experience can be achieved by including image files into your extension and using the `<img>` tag in the Webview HTML, using Codicons provides far simpler way to support themes. While on the _Webview Codicons_ panel, try switching between the light and dark theme.

![demo](demo.gif)

## How to use Codicons

First, include the `vscode-codicons` package into the `dependencies` in `package.json`:

```json
"dependencies": {
"vscode-codicons": "0.0.12"
}
```

Second, refer to the Codicons font and styles from your Webview HTML:

Get resource paths:

```typescript
const styleUri = webview.asWebviewUri(vscode.Uri.joinPath(extensionUri, 'media', 'styles.css'));
const codiconsUri = webview.asWebviewUri(vscode.Uri.joinPath(extensionUri, 'node_modules', 'vscode-codicons', 'dist', 'codicon.css'));
const codiconsFontUri = webview.asWebviewUri(vscode.Uri.joinPath(extensionUri, 'node_modules', 'vscode-codicons', 'dist', 'codicon.ttf'));
```

Declare the Codicons resources in the Content Security Policy and link the stylesheets into your HTML:

```html
<meta http-equiv="Content-Security-Policy"
content="default-src 'none'; font-src ${codiconsFontUri}; style-src ${webview.cspSource} ${codiconsUri};">

<link href="${styleUri}" rel="stylesheet" />
<link href="${codiconsUri}" rel="stylesheet" />
```

Last, add any Codicon into your HTML:

```html
<i class="codicon codicon-check"></i>
```

## Styling codicons

Icons can be styled as any other HTML content:

```html
<div class="styledIcon"><i class="codicon codicon-check"></i> check</div>
```

```css
div.styledIcon .codicon {
font-size: 50px;
color: green;
padding: 3px;
}
```
Binary file modified webview-codicons-sample/demo.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
17 changes: 17 additions & 0 deletions webview-codicons-sample/media/buttons.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// @ts-ignore

// This script will be run within the webview itself
// It cannot access the main VS Code APIs directly.

function play() {
document.getElementById('play2').disabled = true;
document.getElementById('stop2').disabled = false;
}

function stop() {
document.getElementById('play2').disabled = false;
document.getElementById('stop2').disabled = true;
}

document.getElementById('play2').onclick = play;
document.getElementById('stop2').onclick = stop;
23 changes: 23 additions & 0 deletions webview-codicons-sample/media/mouseEvents.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// @ts-ignore

// This script will be run within the webview itself
// It cannot access the main VS Code APIs directly.

function play() {
if (!document.getElementById('play1').classList.contains('disabled')) {
document.getElementById('play1').classList.add('disabled');
document.getElementById('stop1').classList.remove('disabled');
document.getElementById('iconBarStatus').innerText = 'playing'
}
}

function stop() {
if (!document.getElementById('stop1').classList.contains('disabled')) {
document.getElementById('play1').classList.remove('disabled');
document.getElementById('stop1').classList.add('disabled');
document.getElementById('iconBarStatus').innerText = 'stopped'
}
}

document.getElementById('play1').onclick = play;
document.getElementById('stop1').onclick = stop;
41 changes: 40 additions & 1 deletion webview-codicons-sample/media/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -56,5 +56,44 @@ h1 {

#icons .icon .codicon {
font-size: 32px;
padding-bottom: 16px;
}

/* Codicon mouse events demo */

div.iconBar .codicon {
font-size: 60px;
cursor: pointer;
}

div.iconBar .disabled {
opacity: 0.3;
cursor: not-allowed;
}

/* Codicons in buttons demo */

div.iconButtonBar .iconButton .codicon {
font-size: 40px;
padding: 3px;
}

div.styledIcon .codicon {
font-size: 50px;
color: green;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is referencing a color that isn't a color token, something we avoid doing. Something like var(--vscode-debugIcon-startForeground would be preffered.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a really good point. Fixed.

padding: 3px;
}

/* Rotating the 'loading' Codicon to indicate _progress_. */

.rotate {
animation: rotation 2s infinite linear;
}

@keyframes rotation {
from {
transform: rotate(0deg);
}
to {
transform: rotate(359deg);
}
}
21 changes: 11 additions & 10 deletions webview-codicons-sample/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "cat-codicons",
"description": "Cat Codicons - Using codicons in webviews",
"version": "0.0.1",
"name": "webview-codicons",
"description": "Webview Codicons - Using codicons in webviews",
"version": "0.0.2",
"publisher": "vscode-samples",
"engines": {
"vscode": "^1.47.0"
Expand All @@ -10,7 +10,7 @@
"Other"
],
"activationEvents": [
"onCommand:catCodicons.show"
"onCommand:webviewCodicons.show"
],
"repository": {
"type": "git",
Expand All @@ -20,9 +20,9 @@
"contributes": {
"commands": [
{
"command": "catCodicons.show",
"title": "Show Cat Codicons",
"category": "Cat Codicons"
"command": "webviewCodicons.show",
"title": "Show Webview Codicons",
"category": "Webview Codicons"
}
]
},
Expand All @@ -32,14 +32,15 @@
"lint": "eslint . --ext .ts,.tsx",
"watch": "tsc -w -p ./"
},
"dependencies": {},
"dependencies": {
"vscode-codicons": "0.0.12"
},
"devDependencies": {
"@types/node": "^12.12.0",
"@types/vscode": "^1.47.0",
"@typescript-eslint/eslint-plugin": "^3.0.2",
"@typescript-eslint/parser": "^3.0.2",
"eslint": "^7.1.0",
"typescript": "^4.0.2",
"vscode-codicons": "0.0.7"
"typescript": "^4.0.2"
}
}
63 changes: 55 additions & 8 deletions webview-codicons-sample/src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,38 +2,52 @@ import * as vscode from 'vscode';

export function activate(context: vscode.ExtensionContext) {
context.subscriptions.push(
vscode.commands.registerCommand('catCodicons.show', () => {
CatCodiconsPanel.show(context.extensionUri);
vscode.commands.registerCommand('webviewCodicons.show', () => {
WebviewCodiconsPanel.show(context.extensionUri);
})
);
}


class CatCodiconsPanel {
class WebviewCodiconsPanel {

public static readonly viewType = 'catCodicons';
public static readonly viewType = 'webviewCodicons';

public static show(extensionUri: vscode.Uri) {
const column = vscode.window.activeTextEditor
? vscode.window.activeTextEditor.viewColumn
: undefined;

const panel = vscode.window.createWebviewPanel(
CatCodiconsPanel.viewType,
"Cat Codicons",
WebviewCodiconsPanel.viewType,
"Webview Codicons",
column || vscode.ViewColumn.One
);

panel.webview.options = {
// Allow scripts in the webview
enableScripts: true,

localResourceRoots: [
extensionUri
]
}

panel.webview.html = this._getHtmlForWebview(panel.webview, extensionUri);
}

private static _getHtmlForWebview(webview: vscode.Webview, extensionUri: vscode.Uri) {

// Get resource paths
const styleUri = webview.asWebviewUri(vscode.Uri.joinPath(extensionUri, 'media', 'styles.css'));
const scriptMouseEventsUri = webview.asWebviewUri(vscode.Uri.joinPath(extensionUri, 'media', 'mouseEvents.js'));
const scriptButtonsUri = webview.asWebviewUri(vscode.Uri.joinPath(extensionUri, 'media', 'buttons.js'));
const codiconsUri = webview.asWebviewUri(vscode.Uri.joinPath(extensionUri, 'node_modules', 'vscode-codicons', 'dist', 'codicon.css'));
const codiconsFontUri = webview.asWebviewUri(vscode.Uri.joinPath(extensionUri, 'node_modules', 'vscode-codicons', 'dist', 'codicon.ttf'));

// Use a nonce to only allow a specific script to be run.
const nonce = getNonce();

return `<!DOCTYPE html>
<html lang="en">
<head>
Expand All @@ -43,15 +57,40 @@ class CatCodiconsPanel {
Use a content security policy to only allow loading images from https or from our extension directory,
and only allow scripts that have a specific nonce.
-->
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; font-src ${codiconsFontUri}; style-src ${webview.cspSource} ${codiconsUri};">
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; font-src ${codiconsFontUri}; style-src ${webview.cspSource} ${codiconsUri}; script-src 'nonce-${nonce}'">

<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Cat Coding</title>
<title>Webview Coding</title>

<link href="${styleUri}" rel="stylesheet" />
<link href="${codiconsUri}" rel="stylesheet" />
</head>
<body>

<h1>styled codicons</h1>

<div id="icons">
<div class="styledIcon" title="This icon is styled with green foreground"><i class="codicon codicon-archive"></i></div>
<div class="icon" title="This icon is styled with continuous rotation"><i class="codicon codicon-loading rotate"></i></div>
</div>

<h1>codicons mouse events</h1>
<div class="iconBar">
<i id="play1" class="codicon codicon-play-circle"></i>
<i id="stop1" class="codicon disabled codicon-stop-circle"></i>
<div id=iconBarStatus>Click on the icon above</div>
</div>

<script nonce="${nonce}" src="${scriptMouseEventsUri}"></script>

<h1>codicons in buttons</h1>
<div class="iconButtonBar">
<button class="iconButton" id="play2"><i class="codicon codicon-play-circle"></i></button>
<button class="iconButton" id="stop2" disabled><i class="codicon codicon-stop-circle"></i></button>
</div>

<script nonce="${nonce}" src="${scriptButtonsUri}"></script>

<h1>codicons</h1>
<div id="icons">
<div class="icon"><i class="codicon codicon-account"></i> account</div>
Expand Down Expand Up @@ -385,3 +424,11 @@ class CatCodiconsPanel {
}
}

function getNonce() {
let text = '';
const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
for (let i = 0; i < 32; i++) {
text += possible.charAt(Math.floor(Math.random() * possible.length));
}
return text;
}