Add webview view sample

For https://github.com/microsoft/vscode/issues/46585
This commit is contained in:
Matt Bierner
2020-08-24 17:39:48 -07:00
parent 8d43af0868
commit 69409d5e5f
15 changed files with 1801 additions and 1 deletions

View File

@ -16,6 +16,13 @@ const samples = [
description: 'Webview Sample',
path: 'webview-sample',
guide: '/api/extension-guides/webview',
apis: ['window.registerWebviewViewProvider'],
contributions: []
},
{
description: 'Webview View Sample',
path: 'webview-view-sample',
guide: '/api/extension-guides/webview',
apis: ['window.createWebviewPanel', 'window.registerWebviewPanelSerializer'],
contributions: []
},

View File

@ -33,7 +33,8 @@ You need to have [node](https://nodejs.org/en/) and [npm](https://nodejs.org/en/
<!-- SAMPLES_BEGIN -->
| Sample | Guide on VS Code Website | API & Contribution |
| ------ | ----- | --- |
| [Webview Sample](https://github.com/Microsoft/vscode-extension-samples/tree/master/webview-sample) | [/api/extension-guides/webview](https://code.visualstudio.com/api/extension-guides/webview) | [window.createWebviewPanel](https://code.visualstudio.com/api/references/vscode-api#window.createWebviewPanel)<br>[window.registerWebviewPanelSerializer](https://code.visualstudio.com/api/references/vscode-api#window.registerWebviewPanelSerializer) |
| [Webview Sample](https://github.com/Microsoft/vscode-extension-samples/tree/master/webview-sample) | [/api/extension-guides/webview](https://code.visualstudio.com/api/extension-guides/webview) | [window.registerWebviewViewProvider](https://code.visualstudio.com/api/references/vscode-api#window.registerWebviewViewProvider) |
| [Webview View Sample](https://github.com/Microsoft/vscode-extension-samples/tree/master/webview-view-sample) | [/api/extension-guides/webview](https://code.visualstudio.com/api/extension-guides/webview) | [window.createWebviewPanel](https://code.visualstudio.com/api/references/vscode-api#window.createWebviewPanel)<br>[window.registerWebviewPanelSerializer](https://code.visualstudio.com/api/references/vscode-api#window.registerWebviewPanelSerializer) |
| [Status Bar Sample](https://github.com/Microsoft/vscode-extension-samples/tree/master/statusbar-sample) | N/A | [window.createStatusBarItem](https://code.visualstudio.com/api/references/vscode-api#window.createStatusBarItem)<br>[StatusBarItem](https://code.visualstudio.com/api/references/vscode-api#StatusBarItem) |
| [Tree View Sample](https://github.com/Microsoft/vscode-extension-samples/tree/master/tree-view-sample) | [/api/extension-guides/tree-view](https://code.visualstudio.com/api/extension-guides/tree-view) | [window.createTreeView](https://code.visualstudio.com/api/references/vscode-api#window.createTreeView)<br>[window.registerTreeDataProvider](https://code.visualstudio.com/api/references/vscode-api#window.registerTreeDataProvider)<br>[TreeView](https://code.visualstudio.com/api/references/vscode-api#TreeView)<br>[TreeDataProvider](https://code.visualstudio.com/api/references/vscode-api#TreeDataProvider)<br>[contributes.views](https://code.visualstudio.com/api/references/contribution-points#contributes.views)<br>[contributes.viewsContainers](https://code.visualstudio.com/api/references/contribution-points#contributes.viewsContainers) |
| [Task Provider Sample](https://github.com/Microsoft/vscode-extension-samples/tree/master/task-provider-sample) | [/api/extension-guides/task-provider](https://code.visualstudio.com/api/extension-guides/task-provider) | [tasks.registerTaskProvider](https://code.visualstudio.com/api/references/vscode-api#tasks.registerTaskProvider)<br>[Task](https://code.visualstudio.com/api/references/vscode-api#Task)<br>[ShellExecution](https://code.visualstudio.com/api/references/vscode-api#ShellExecution)<br>[contributes.taskDefinitions](https://code.visualstudio.com/api/references/contribution-points#contributes.taskDefinitions) |

View File

@ -0,0 +1,20 @@
/**@type {import('eslint').Linter.Config} */
// eslint-disable-next-line no-undef
module.exports = {
root: true,
parser: '@typescript-eslint/parser',
plugins: [
'@typescript-eslint',
],
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
],
rules: {
'semi': [2, "always"],
'@typescript-eslint/no-unused-vars': 0,
'@typescript-eslint/no-explicit-any': 0,
'@typescript-eslint/explicit-module-boundary-types': 0,
'@typescript-eslint/no-non-null-assertion': 0,
}
};

View File

@ -0,0 +1,9 @@
{
// See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations.
// Extension identifier format: ${publisher}.${name}. Example: vscode.csharp
// List of extensions which should be recommended for users of this workspace.
"recommendations": [
"dbaeumer.vscode-eslint"
]
}

18
webview-view-sample/.vscode/launch.json vendored Normal file
View File

@ -0,0 +1,18 @@
// A launch configuration that compiles the extension and then opens it inside a new window
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
{
"version": "0.2.0",
"configurations": [
{
"name": "Run Extension",
"type": "extensionHost",
"request": "launch",
"runtimeExecutable": "${execPath}",
"args": ["--extensionDevelopmentPath=${workspaceRoot}"],
"outFiles": ["${workspaceFolder}/out/**/*.js"],
"preLaunchTask": "npm: watch"
}
]
}

View File

@ -0,0 +1,3 @@
{
"editor.insertSpaces": false
}

20
webview-view-sample/.vscode/tasks.json vendored Normal file
View File

@ -0,0 +1,20 @@
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
{
"version": "2.0.0",
"tasks": [
{
"type": "npm",
"script": "watch",
"problemMatcher": "$tsc-watch",
"isBackground": true,
"presentation": {
"reveal": "never"
},
"group": {
"kind": "build",
"isDefault": true
}
}
]
}

View File

@ -0,0 +1,24 @@
# Calico Colors — Webview View API Sample
Demonstrates VS Code's proposed [webview view API](https://github.com/microsoft/vscode/issues/46585). This includes:
- Contributing a webview based view to the explorer.
- Posting messages from an extension to a webview view
- Posting message from a webview to an extension
- Persisting state in the view.
## VS Code API
### `vscode` module
- [`window.createWebviewPanel`](https://code.visualstudio.com/api/references/vscode-api#window.createWebviewPanel)
## Running the example
- Open this example in VS Code 1.49+
- `npm install`
- `npm run watch` or `npm run compile`
- `F5` to start debugging
In the explorer, expand the `Calico Colors` view.

View File

@ -0,0 +1,46 @@
body {
background: transparent;
}
.color-list {
list-style: none;
padding: 0;
}
.color-entry {
width: 100%;
display: flex;
margin-bottom: 0.4em;
border: 1px solid var(--vscode-input-border);
}
.color-preview {
width: 2em;
height: 2em;
}
.color-preview:hover {
outline: inset white;
}
.color-input {
display: block;
flex: 1;
width: 100%;
color: var(--vscode-input-foreground);
background-color: var(--vscode-input-background);
border: none;
padding: 0 0.6em;
}
.add-color-button {
display: block;
color: var(--vscode-button-foreground);
background: var(--vscode-button-background);
border: none;
margin: 0 auto;
}
.add-color-button:hover {
background: var(--vscode-button-hoverBackground);
}

View File

@ -0,0 +1,90 @@
//@ts-check
// This script will be run within the webview itself
// It cannot access the main VS Code APIs directly.
(function () {
const vscode = acquireVsCodeApi();
const oldState = vscode.getState() || { colors: [] };
/** @type {Array<{ value: string }>} */
let colors = oldState.colors;
updateColorList(colors);
document.querySelector('.add-color-button').addEventListener('click', () => {
addColor(colors, getNewCalicoColor, updateColorList);
});
// Handle messages sent from the extension to the webview
window.addEventListener('message', event => {
const message = event.data; // The json data that the extension sent
switch (message.type) {
case 'addColor':
addColor();
break;
}
});
/**
* @param {Array<{ value: string }>} colors
*/
function updateColorList(colors) {
const ul = document.querySelector('.color-list');
ul.textContent = '';
for (const color of colors) {
const li = document.createElement('li');
li.className = 'color-entry';
const colorPreview = document.createElement('div');
colorPreview.className = 'color-preview';
colorPreview.style.backgroundColor = `#${color.value}`;
colorPreview.addEventListener('click', () => {
onColorClicked(color.value);
});
li.appendChild(colorPreview);
const input = document.createElement('input');
input.className = 'color-input';
input.type = 'text';
input.value = color.value;
input.addEventListener('change', (e) => {
const value = e.target.value;
if (!value) {
// Treat empty value as delete
colors.splice(colors.indexOf(color), 1);
} else {
color.value = value;
}
updateColorList(colors);
});
li.appendChild(input);
ul.appendChild(li);
}
// Update the saved state
vscode.setState({ colors: colors });
}
/**
* @param {string} color
*/
function onColorClicked(color) {
vscode.postMessage({ type: 'colorSelected', value: color });
}
/**
* @returns string
*/
function getNewCalicoColor() {
const colors = ['020202', 'f1eeee', 'a85b20', 'daab70', 'efcb99'];
return colors[Math.floor(Math.random() * colors.length)];
}
}());
function addColor(colors, getNewCalicoColor, updateColorList) {
colors.push({ value: getNewCalicoColor() });
updateColorList(colors);
}

1227
webview-view-sample/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,54 @@
{
"name": "calico-colors",
"description": "Calico Colors - A Webview View API Sample",
"version": "0.0.1",
"publisher": "vscode-samplesa",
"enableProposedApi": true,
"engines": {
"vscode": "^1.47.0"
},
"categories": [
"Other"
],
"activationEvents": [
"onView:calicoColors.colorsView",
"onCommand:calicoColors.addColor"
],
"repository": {
"type": "git",
"url": "https://github.com/microsoft/vscode-extension-samples.git"
},
"main": "./out/extension.js",
"contributes": {
"views": {
"explorer": [
{
"type": "webview",
"id": "calicoColors.colorsView",
"name": "Calico Colors"
}
]
},
"commands": [
{
"command": "calicoColors.addColor",
"category": "Calico Colors",
"title": "Add Color"
}
]
},
"scripts": {
"vscode:prepublish": "npm run compile",
"compile": "tsc -p ./",
"lint": "eslint . --ext .ts,.tsx",
"watch": "tsc -w -p ./"
},
"dependencies": {},
"devDependencies": {
"@typescript-eslint/eslint-plugin": "^3.0.2",
"@typescript-eslint/parser": "^3.0.2",
"eslint": "^7.1.0",
"typescript": "^3.9.4",
"@types/vscode": "^1.47.0"
}
}

View File

@ -0,0 +1,115 @@
import * as vscode from 'vscode';
const simpleColorRegularExpression = /#?([0-9a-f]{3}|[0-9a-f]{6})/gi;
export function activate(context: vscode.ExtensionContext) {
const provider = new ColorsViewProvider(context.extensionUri);
context.subscriptions.push(
vscode.window.registerWebviewViewProvider(ColorsViewProvider.viewType, provider));
context.subscriptions.push(
vscode.commands.registerCommand('calicoColors.addColor', () => {
provider.addColor();
}));
}
class ColorsViewProvider implements vscode.WebviewViewProvider {
public static readonly viewType = 'calicoColors.colorsView';
private _view?: vscode.WebviewView;
constructor(
private readonly _extensionUri: vscode.Uri,
) { }
public resolveWebviewView(
webviewView: vscode.WebviewView,
context: vscode.WebviewViewResolveContext,
_token: vscode.CancellationToken,
) {
this._view = webviewView;
webviewView.webview.options = {
// Allow scripts in the webview
enableScripts: true,
localResourceRoots: [
this._extensionUri
]
};
webviewView.webview.html = this._getHtmlForWebview(webviewView.webview);
webviewView.webview.onDidReceiveMessage(data => {
switch (data.type) {
case 'colorSelected':
{
vscode.window.activeTextEditor?.insertSnippet(new vscode.SnippetString(`#${data.value}`));
break;
}
}
});
}
public addColor() {
// TODO: add reveal
if (this._view) {
this._view.webview.postMessage({ type: 'addColor' });
}
}
private _getHtmlForWebview(webview: vscode.Webview) {
// Local path to main script run in the webview
const scriptPathOnDisk = vscode.Uri.joinPath(this._extensionUri, 'media', 'main.js');
// And the uri we use to load this script in the webview
const scriptUri = webview.asWebviewUri(scriptPathOnDisk);
const styleUri = webview.asWebviewUri(vscode.Uri.joinPath(this._extensionUri, 'media', 'main.css'));
const catImagePath = webview.asWebviewUri(vscode.Uri.joinPath(this._extensionUri, 'media', 'cat.jpg'));
// Use a nonce to only allow specific scripts to be run
const nonce = getNonce();
return `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<!--
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'; img-src ${webview.cspSource}; script-src 'nonce-${nonce}';">
-->
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; img-src ${webview.cspSource}; style-src ${webview.cspSource}; script-src 'nonce-${nonce}';">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="${styleUri}" rel="stylesheet" />
<title>Cat Colors</title>
</head>
<body>
<ul class="color-list">
</ul>
<button class="add-color-button">Add Color</button>
<script nonce="${nonce}" src="${scriptUri}"></script>
</body>
</html>`;
}
}
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;
}

View File

@ -0,0 +1,154 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
/**
* This is the place for API experiments and proposals.
* These API are NOT stable and subject to change. They are only available in the Insiders
* distribution and CANNOT be used in published extensions.
*
* To test these API in local environment:
* - Use Insiders release of VS Code.
* - Add `"enableProposedApi": true` to your package.json.
* - Copy this file to your project.
*/
declare module 'vscode' {
//#region https://github.com/microsoft/vscode/issues/46585
/**
* A webview based view.
*/
export interface WebviewView {
/**
* Identifies the type of the webview view, such as `'hexEditor.dataView'`.
*/
readonly viewType: string;
/**
* The underlying webview for the view.
*/
readonly webview: Webview;
/**
* View title displayed in the UI.
*
* The view title is initially taken from the extension `package.json` contribution.
*/
title?: string;
/**
* Event fired when the view is disposed.
*
* Views are disposed of in a few cases:
*
* - When a view is collapsed and `retainContextWhenHidden` has not been set.
* - When a view is hidden by a user.
*
* Trying to use the view after it has been disposed throws an exception.
*/
readonly onDidDispose: Event<void>;
/**
* Tracks if the webview is currently visible.
*
* Views are visible when they are on the screen and expanded.
*/
readonly visible: boolean;
/**
* Event fired when the visibility of the view changes
*/
readonly onDidChangeVisibility: Event<void>;
}
interface WebviewViewResolveContext<T = unknown> {
/**
* Persisted state from the webview content.
*
* To save resources, VS Code normally deallocates webview views that are not visible. For example, if the user
* collapse a view or switching to another top level activity, the underlying webview document is deallocates.
*
* You can prevent this behavior by setting `retainContextWhenHidden` in the `WebviewOptions`. However this
* increases resource usage and should be avoided wherever possible. Instead, you can use persisted state to
* save off a webview's state so that it can be quickly recreated as needed.
*
* To save off a persisted state, inside the webview call `acquireVsCodeApi().setState()` with
* any json serializable object. To restore the state again, call `getState()`. For example:
*
* ```js
* // Within the webview
* const vscode = acquireVsCodeApi();
*
* // Get existing state
* const oldState = vscode.getState() || { value: 0 };
*
* // Update state
* setState({ value: oldState.value + 1 })
* ```
*
* VS Code ensures that the persisted state is saved correctly when a webview is hidden and across
* editor restarts.
*/
readonly state: T | undefined;
}
/**
* Provider for creating `WebviewView` elements.
*/
export interface WebviewViewProvider {
/**
* Revolves a webview view.
*
* `resolveWebviewView` is called when a view first becomes visible. This may happen when the view is
* first loaded or when the user hides and then shows a view again.
*
* @param webviewView Webview panel to restore. The serializer should take ownership of this panel. The
* provider must set the webview's `.html` and hook up all webview events it is interested in.
* @param context Additional metadata about the view being resolved.
* @param token Cancellation token indicating that the view being provided is no longer needed.
*
* @return Optional thenable indicating that the view has been fully resolved.
*/
resolveWebviewView(webviewView: WebviewView, context: WebviewViewResolveContext, token: CancellationToken): Thenable<void> | void;
}
namespace window {
/**
* Register a new provider for webview views.
*
* @param viewId Unique id of the view. This should match the `id` from the
* `views` contribution in the package.json.
* @param provider Provider for the webview views.
*
* @return Disposable that unregisters the provider.
*/
export function registerWebviewViewProvider(viewId: string, provider: WebviewViewProvider, options?: {
/**
* Content settings for the webview created for this view.
*/
readonly webviewOptions?: {
/**
* Controls if the webview panel's content (iframe) is kept around even when the panel
* is no longer visible.
*
* Normally the webview's html context is created when the panel becomes visible
* and destroyed when it is hidden. Extensions that have complex state
* or UI can set the `retainContextWhenHidden` to make VS Code keep the webview
* context around, even when the webview moves to a background tab. When a webview using
* `retainContextWhenHidden` becomes hidden, its scripts and other dynamic content are suspended.
* When the panel becomes visible again, the context is automatically restored
* in the exact same state it was in originally. You cannot send messages to a
* hidden webview, even with `retainContextWhenHidden` enabled.
*
* `retainContextWhenHidden` has a high memory overhead and should only be used if
* your panel's context cannot be quickly saved and restored.
*/
readonly retainContextWhenHidden?: boolean;
};
}): Disposable;
}
//#endregion
}

View File

@ -0,0 +1,12 @@
{
"compilerOptions": {
"module": "commonjs",
"target": "es2019",
"lib": ["ES2019"],
"outDir": "out",
"sourceMap": true,
"strict": true,
"rootDir": "src"
},
"exclude": ["node_modules", ".vscode-test"]
}